From 749c5102487b558cc7d73961a6d0da2fceaa022d Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Fri, 12 Jun 2026 08:35:50 -0700 Subject: [PATCH 0001/2366] EDAC/mpc85xx: Orphan it Johannes doesn't have the hardware to test patches on it anymore and TTBOMK, no one else has shown interest so orphan the driver, for now at least. Signed-off-by: Borislav Petkov (AMD) Acked-by: Johannes Thumshirn Link: https://patch.msgid.link/20260612153839.GCaiwn_7qOic4KLF8P@fat_crate.local --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..7b37c88143f6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9414,9 +9414,8 @@ S: Maintained F: drivers/edac/igen6_edac.c EDAC-MPC85XX -M: Johannes Thumshirn L: linux-edac@vger.kernel.org -S: Maintained +S: Orphan F: drivers/edac/mpc85xx_edac.[ch] EDAC-NPCM From 90cfd27df4ba5f1c18a3454eb4b454bfe6baaf36 Mon Sep 17 00:00:00 2001 From: Yazen Ghannam Date: Mon, 29 Jun 2026 11:07:29 -0400 Subject: [PATCH 0002/2366] EDAC/debugfs: Remove the fake_inject debugfs interface The interface has a potential race condition between a real and fake error when updating the memory controller's error descriptor. There doesn't seem to be an active user base for this interface, so remove it. Closes: https://sashiko.dev/#/patchset/20260518160716.171578-1-yazen.ghannam%40amd.com Reported-by: sashiko-bot Suggested-by: Borislav Petkov Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Yazen Ghannam Signed-off-by: Borislav Petkov (AMD) Link: https://lore.kernel.org/linux-edac/20260611012336.GHaioOGB0NBxv5BZXS@fat_crate.local --- drivers/edac/debugfs.c | 65 +----------------------------------------- include/linux/edac.h | 3 -- 2 files changed, 1 insertion(+), 67 deletions(-) diff --git a/drivers/edac/debugfs.c b/drivers/edac/debugfs.c index 8195fc9c9354..447d0c620082 100644 --- a/drivers/edac/debugfs.c +++ b/drivers/edac/debugfs.c @@ -1,50 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-only -#include - #include "edac_module.h" static struct dentry *edac_debugfs; -static ssize_t edac_fake_inject_write(struct file *file, - const char __user *data, - size_t count, loff_t *ppos) -{ - struct device *dev = file->private_data; - struct mem_ctl_info *mci = to_mci(dev); - static enum hw_event_mc_err_type type; - u16 errcount = mci->fake_inject_count; - - if (!errcount) - errcount = 1; - - type = mci->fake_inject_ue ? HW_EVENT_ERR_UNCORRECTED - : HW_EVENT_ERR_CORRECTED; - - printk(KERN_DEBUG - "Generating %d %s fake error%s to %d.%d.%d to test core handling. NOTE: this won't test the driver-specific decoding logic.\n", - errcount, - (type == HW_EVENT_ERR_UNCORRECTED) ? "UE" : "CE", - str_plural(errcount), - mci->fake_inject_layer[0], - mci->fake_inject_layer[1], - mci->fake_inject_layer[2] - ); - edac_mc_handle_error(type, mci, errcount, 0, 0, 0, - mci->fake_inject_layer[0], - mci->fake_inject_layer[1], - mci->fake_inject_layer[2], - "FAKE ERROR", "for EDAC testing only"); - - return count; -} - -static const struct file_operations debug_fake_inject_fops = { - .open = simple_open, - .write = edac_fake_inject_write, - .llseek = generic_file_llseek, -}; - void __init edac_debugfs_init(void) { edac_debugfs = debugfs_create_dir("edac", NULL); @@ -57,29 +16,7 @@ void edac_debugfs_exit(void) void edac_create_debugfs_nodes(struct mem_ctl_info *mci) { - struct dentry *parent; - char name[80]; - int i; - - parent = debugfs_create_dir(mci->dev.kobj.name, edac_debugfs); - - for (i = 0; i < mci->n_layers; i++) { - sprintf(name, "fake_inject_%s", - edac_layer_name[mci->layers[i].type]); - debugfs_create_u8(name, S_IRUGO | S_IWUSR, parent, - &mci->fake_inject_layer[i]); - } - - debugfs_create_bool("fake_inject_ue", S_IRUGO | S_IWUSR, parent, - &mci->fake_inject_ue); - - debugfs_create_u16("fake_inject_count", S_IRUGO | S_IWUSR, parent, - &mci->fake_inject_count); - - debugfs_create_file("fake_inject", S_IWUSR, parent, &mci->dev, - &debug_fake_inject_fops); - - mci->debugfs = parent; + mci->debugfs = debugfs_create_dir(mci->dev.kobj.name, edac_debugfs); } /* Create a toplevel dir under EDAC's debugfs hierarchy */ diff --git a/include/linux/edac.h b/include/linux/edac.h index e6b4e51130e5..f7a8218f9cc0 100644 --- a/include/linux/edac.h +++ b/include/linux/edac.h @@ -598,9 +598,6 @@ struct mem_ctl_info { int op_state; struct dentry *debugfs; - u8 fake_inject_layer[EDAC_MAX_LAYERS]; - bool fake_inject_ue; - u16 fake_inject_count; /* * Memory Controller hierarchy From 07897bdf7a9c60455a175f6eb619c7d95e1d1765 Mon Sep 17 00:00:00 2001 From: Abhinav Ananthu Date: Fri, 20 Jun 2025 01:51:34 +0530 Subject: [PATCH 0003/2366] EDAC/sysfs: Use sysfs_emit_at() in dimmdev_location_show() Replace the use of scnprintf() with sysfs_emit_at() in dimmdev_location_show() to format the output into the sysfs buffer and thus improve clarity and ensure proper bounds checking in line with the preferred sysfs_emit() API usage for sysfs 'show' functions. No functional change intended. [ bp: Massage commit message. ] Signed-off-by: Abhinav Ananthu Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Qiuxu Zhuo Link: https://patch.msgid.link/20250619202133.11843-1-abhinav.ogl@gmail.com --- drivers/edac/edac_mc_sysfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/edac/edac_mc_sysfs.c b/drivers/edac/edac_mc_sysfs.c index c2ed6c696e54..9b4b5582fa9f 100644 --- a/drivers/edac/edac_mc_sysfs.c +++ b/drivers/edac/edac_mc_sysfs.c @@ -129,7 +129,7 @@ static ssize_t dimmdev_location_show(struct device *dev, ssize_t count; count = edac_dimm_info_location(dimm, data, PAGE_SIZE); - count += scnprintf(data + count, PAGE_SIZE - count, "\n"); + count += sysfs_emit_at(data, count, "\n"); return count; } From 97dfcb871ba776ba0e1ded1cdcbe94a357c2817e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 3 Jul 2026 19:38:03 +0200 Subject: [PATCH 0004/2366] MAINTAINERS: Remove Mark Gross from relevant entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sending mail to Mark's Intel address results in the Intel mail server rejecting the mail. Dave Hansen confirmed he left Intel. The kernel.org address seems to work, but there was no reply from Mark on the discussion about broken email settings and his maintainer entries. So drop him from all maintainer entries and move him to credits. Signed-off-by: Uwe Kleine-König Signed-off-by: Borislav Petkov (AMD) Acked-by: Dave Hansen Link: https://patch.msgid.link/20260703173803.3589003-2-ukleinek@kernel.org --- CREDITS | 4 ++++ MAINTAINERS | 7 ++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CREDITS b/CREDITS index 84793a967a0b..091412875a66 100644 --- a/CREDITS +++ b/CREDITS @@ -1503,6 +1503,10 @@ N: Andy Gross E: agross@kernel.org D: Qualcomm SoC subsystem and drivers +N: Mark Gross +E: markgross@kernel.org +D: x86/mellanox platform maintenance and various x86 specific drivers + N: Grant Grundler E: grantgrundler@gmail.com W: http://obmouse.sourceforge.net/ diff --git a/MAINTAINERS b/MAINTAINERS index 7b37c88143f6..f16c09f76e7b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9337,9 +9337,8 @@ S: Supported F: drivers/edac/dmc520_edac.c EDAC-E752X -M: Mark Gross L: linux-edac@vger.kernel.org -S: Maintained +S: Orphan F: drivers/edac/e752x_edac.c EDAC-E7XXX @@ -13201,7 +13200,6 @@ F: drivers/crypto/intel/keembay/ocs-aes.h INTEL KEEM BAY OCS ECC CRYPTO DRIVER M: Prabhjot Khurana -M: Mark Gross S: Maintained F: Documentation/devicetree/bindings/crypto/intel,keembay-ocs-ecc.yaml F: drivers/crypto/intel/keembay/Kconfig @@ -26642,8 +26640,7 @@ S: Maintained F: drivers/net/ethernet/tehuti/tn40* TELECOM CLOCK DRIVER FOR MCPL0010 -M: Mark Gross -S: Supported +S: Orphan F: drivers/char/tlclk.c TEMPO SEMICONDUCTOR DRIVERS From 4c3da04827dc01dc1cfc3d03654b7de656c42d80 Mon Sep 17 00:00:00 2001 From: Yazen Ghannam Date: Mon, 6 Jul 2026 16:21:15 -0500 Subject: [PATCH 0005/2366] RAS/AMD/ATL, EDAC/amd64: Only load ATL when needed The AMD Address Translation Library (ATL) will attempt to load on all AMD Zen/SMCA systems. However, only systems with DRAM ECC enabled will use the library. Other systems will fail to load the library and produce an unnecessary message to the user. More importantly, that thing is dead code loaded and unused. Remove the ATL module dependency table to prevent autoloading. Request ATL to load from EDAC once all system checks are complete. [ bp: Massage commit message. ] Fixes: 3f3174996be6 ("RAS: Introduce AMD Address Translation Library") Closes: https://lore.kernel.org/20260305154528.1171999-1-mario.limonciello@amd.com Reported-by: Mario Limonciello Signed-off-by: Yazen Ghannam Signed-off-by: Mario Limonciello Signed-off-by: Borislav Petkov (AMD) Tested-by: Deskhmukh Shrirang Link: https://lore.kernel.org/all/20260307144910.GA113343@yaz-khff2.amd.com --- drivers/edac/amd64_edac.c | 2 ++ drivers/ras/amd/atl/core.c | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/edac/amd64_edac.c b/drivers/edac/amd64_edac.c index c6aa69dbd9fb..475235c402e8 100644 --- a/drivers/edac/amd64_edac.c +++ b/drivers/edac/amd64_edac.c @@ -4173,6 +4173,8 @@ static int __init amd64_edac_init(void) goto err_pci; } + request_module_nowait("amd_atl"); + /* register stuff with EDAC MCE */ if (boot_cpu_data.x86 >= 0x17) { amd_register_ecc_decoder(decode_umc_error); diff --git a/drivers/ras/amd/atl/core.c b/drivers/ras/amd/atl/core.c index 0f7cd6dab0b0..d77dacdd4f56 100644 --- a/drivers/ras/amd/atl/core.c +++ b/drivers/ras/amd/atl/core.c @@ -190,7 +190,6 @@ static const struct x86_cpu_id amd_atl_cpuids[] = { X86_MATCH_FEATURE(X86_FEATURE_ZEN, NULL), { } }; -MODULE_DEVICE_TABLE(x86cpu, amd_atl_cpuids); static int __init amd_atl_init(void) { From d4486fc3098e176cb4a29fee037216484761f9ca Mon Sep 17 00:00:00 2001 From: Rounak Das Date: Wed, 8 Jul 2026 13:11:34 +0400 Subject: [PATCH 0006/2366] EDAC/altera: Use ECC manager compatible to select A10/S10 IRQ layout The SDMMC ECC IRQ layout selection uses CONFIG_64BIT to distinguish between Arria10 and Stratix10 paths. Detect the SoC once at probe via the device match table (.data) store it in struct altr_arria10_edac, and use it instead of CONFIG_64BIT. This keeps the decision correct for every ECC child device (OCRAM, SD/MMC, etc.) and avoids any runtime compatible lookup. Signed-off-by: Rounak Das Signed-off-by: Borislav Petkov (AMD) Acked-by: Dinh Nguyen Link: https://patch.msgid.link/20260708091135.94114-2-rounakdas2025@gmail.com --- drivers/edac/altera_edac.c | 107 +++++++++++++++++++------------------ drivers/edac/altera_edac.h | 1 + 2 files changed, 55 insertions(+), 53 deletions(-) diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 4edd2088c2db..24bdf7f5bac6 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -1507,6 +1507,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) int edac_idx, rc; struct device_node *np; const struct edac_device_prv_data *prv = &a10_sdmmceccb_data; + bool is_s10 = device->edac->is_s10; rc = altr_check_ecc_deps(device); if (rc) @@ -1548,15 +1549,14 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) /* * Update the PortB IRQs - A10 has 4, S10 has 2, Index accordingly - * - * FIXME: Instead of ifdefs with different architectures the driver - * should properly use compatibles. */ -#ifdef CONFIG_64BIT - altdev->sb_irq = irq_of_parse_and_map(np, 1); -#else - altdev->sb_irq = irq_of_parse_and_map(np, 2); -#endif + + /* Using compatibles to determine the IRQ Index */ + if (is_s10) + altdev->sb_irq = irq_of_parse_and_map(np, 1); + else + altdev->sb_irq = irq_of_parse_and_map(np, 2); + if (!altdev->sb_irq) { edac_printk(KERN_ERR, EDAC_DEVICE, "Error PortB SBIRQ alloc\n"); rc = -ENODEV; @@ -1570,29 +1570,28 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) goto err_release_group_1; } -#ifdef CONFIG_64BIT - /* Use IRQ to determine SError origin instead of assigning IRQ */ - rc = of_property_read_u32_index(np, "interrupts", 1, &altdev->db_irq); - if (rc) { - edac_printk(KERN_ERR, EDAC_DEVICE, - "Error PortB DBIRQ alloc\n"); - goto err_release_group_1; + if (is_s10) { + /* Use IRQ to determine SError origin instead of assigning IRQ */ + rc = of_property_read_u32_index(np, "interrupts", 1, &altdev->db_irq); + if (rc) { + edac_printk(KERN_ERR, EDAC_DEVICE, "Error PortB DBIRQ alloc\n"); + goto err_release_group_1; + } + } else { + altdev->db_irq = irq_of_parse_and_map(np, 3); + if (!altdev->db_irq) { + edac_printk(KERN_ERR, EDAC_DEVICE, "Error PortB DBIRQ alloc\n"); + rc = -ENODEV; + goto err_release_group_1; + } + rc = devm_request_irq(&altdev->ddev, altdev->db_irq, + prv->ecc_irq_handler, IRQF_TRIGGER_HIGH, + ecc_name, altdev); + if (rc) { + edac_printk(KERN_ERR, EDAC_DEVICE, "PortB DBERR IRQ error\n"); + goto err_release_group_1; + } } -#else - altdev->db_irq = irq_of_parse_and_map(np, 3); - if (!altdev->db_irq) { - edac_printk(KERN_ERR, EDAC_DEVICE, "Error PortB DBIRQ alloc\n"); - rc = -ENODEV; - goto err_release_group_1; - } - rc = devm_request_irq(&altdev->ddev, altdev->db_irq, - prv->ecc_irq_handler, IRQF_TRIGGER_HIGH, - ecc_name, altdev); - if (rc) { - edac_printk(KERN_ERR, EDAC_DEVICE, "PortB DBERR IRQ error\n"); - goto err_release_group_1; - } -#endif rc = edac_device_add_device(dci); if (rc) { @@ -1974,29 +1973,29 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac, goto err_release_group1; } -#ifdef CONFIG_64BIT - /* Use IRQ to determine SError origin instead of assigning IRQ */ - rc = of_property_read_u32_index(np, "interrupts", 0, &altdev->db_irq); - if (rc) { - edac_printk(KERN_ERR, EDAC_DEVICE, - "Unable to parse DB IRQ index\n"); - goto err_release_group1; + if (edac->is_s10) { + /* Use IRQ to determine SError origin instead of assigning IRQ */ + rc = of_property_read_u32_index(np, "interrupts", 0, &altdev->db_irq); + if (rc) { + edac_printk(KERN_ERR, EDAC_DEVICE, + "Unable to parse DB IRQ index\n"); + goto err_release_group1; + } + } else { + altdev->db_irq = irq_of_parse_and_map(np, 1); + if (!altdev->db_irq) { + edac_printk(KERN_ERR, EDAC_DEVICE, "Error allocating DBIRQ\n"); + rc = -ENODEV; + goto err_release_group1; + } + rc = devm_request_irq(edac->dev, altdev->db_irq, prv->ecc_irq_handler, + IRQF_TRIGGER_HIGH, + ecc_name, altdev); + if (rc) { + edac_printk(KERN_ERR, EDAC_DEVICE, "No DBERR IRQ resource\n"); + goto err_release_group1; + } } -#else - altdev->db_irq = irq_of_parse_and_map(np, 1); - if (!altdev->db_irq) { - edac_printk(KERN_ERR, EDAC_DEVICE, "Error allocating DBIRQ\n"); - rc = -ENODEV; - goto err_release_group1; - } - rc = devm_request_irq(edac->dev, altdev->db_irq, prv->ecc_irq_handler, - IRQF_TRIGGER_HIGH, - ecc_name, altdev); - if (rc) { - edac_printk(KERN_ERR, EDAC_DEVICE, "No DBERR IRQ resource\n"); - goto err_release_group1; - } -#endif rc = edac_device_add_device(dci); if (rc) { @@ -2122,6 +2121,8 @@ static int altr_edac_a10_probe(struct platform_device *pdev) platform_set_drvdata(pdev, edac); INIT_LIST_HEAD(&edac->a10_ecc_devices); + edac->is_s10 = !!device_get_match_data(&pdev->dev); + edac->ecc_mgr_map = altr_sysmgr_regmap_lookup_by_phandle(pdev->dev.of_node, "altr,sysmgr-syscon"); @@ -2207,7 +2208,7 @@ static int altr_edac_a10_probe(struct platform_device *pdev) static const struct of_device_id altr_edac_a10_of_match[] = { { .compatible = "altr,socfpga-a10-ecc-manager" }, - { .compatible = "altr,socfpga-s10-ecc-manager" }, + { .compatible = "altr,socfpga-s10-ecc-manager", .data = (void *)1 }, {}, }; MODULE_DEVICE_TABLE(of, altr_edac_a10_of_match); diff --git a/drivers/edac/altera_edac.h b/drivers/edac/altera_edac.h index f3e84172caa9..9387056fd65e 100644 --- a/drivers/edac/altera_edac.h +++ b/drivers/edac/altera_edac.h @@ -394,6 +394,7 @@ struct altr_arria10_edac { struct irq_chip irq_chip; struct list_head a10_ecc_devices; struct notifier_block panic_notifier; + bool is_s10; }; #endif /* #ifndef _ALTERA_EDAC_H */ From 11f5fd36076a2ef229ec5062c06954c955d90f9d Mon Sep 17 00:00:00 2001 From: Rounak Das Date: Wed, 8 Jul 2026 13:11:35 +0400 Subject: [PATCH 0007/2366] EDAC/altera: Remove remaining CONFIG_64BIT ifdefs in the DB-error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the remaining two CONFIG_64BIT ifdefs with the is_s10 flag, so driver behavior is fully determined by the ECC manager's compatible string rather than the build architecture. These two ifdefs guard the double-bit-error path, where SError handling and the arm_smccc_smc() reboot call are arm64-specific. Switching to is_s10 means s10_edac_dberr_handler() now compiles on 32-bit as well — this is safe because all the symbols it depends on (arm_smccc_smc, INTEL_SIP_SMC_ECC_DBE, and the S10 sysmgr defines) are already available on 32-bit socfpga. Since the function only executes when is_s10 is true, Arria10 behavior is unaffected. This is handled separately from the IRQ-index selection change, as the double-bit-error path is a distinct concern. Signed-off-by: Rounak Das Signed-off-by: Borislav Petkov (AMD) Acked-by: Dinh Nguyen Assisted-by: Claude:claude-sonnet-5 Link: https://patch.msgid.link/20260708091135.94114-3-rounakdas2025@gmail.com --- drivers/edac/altera_edac.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 24bdf7f5bac6..1d1e2b5ca14c 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -2058,7 +2058,6 @@ static const struct irq_domain_ops a10_eccmgr_ic_ops = { /************** Stratix 10 EDAC Double Bit Error Handler ************/ #define to_a10edac(p, m) container_of(p, struct altr_arria10_edac, m) -#ifdef CONFIG_64BIT /* panic routine issues reboot on non-zero panic_timeout */ extern int panic_timeout; @@ -2105,7 +2104,6 @@ static int s10_edac_dberr_handler(struct notifier_block *this, return NOTIFY_DONE; } -#endif /****************** Arria 10 EDAC Probe Function *********************/ static int altr_edac_a10_probe(struct platform_device *pdev) @@ -2154,8 +2152,7 @@ static int altr_edac_a10_probe(struct platform_device *pdev) irq_set_chained_handler_and_data(edac->sb_irq, altr_edac_a10_irq_handler, edac); - -#ifdef CONFIG_64BIT + if (edac->is_s10) { int dberror, err_addr; @@ -2178,15 +2175,14 @@ static int altr_edac_a10_probe(struct platform_device *pdev) regmap_write(edac->ecc_mgr_map, S10_SYSMGR_UE_ADDR_OFST, 0); } - } -#else - edac->db_irq = platform_get_irq(pdev, 1); - if (edac->db_irq < 0) - return edac->db_irq; + } else { + edac->db_irq = platform_get_irq(pdev, 1); + if (edac->db_irq < 0) + return edac->db_irq; - irq_set_chained_handler_and_data(edac->db_irq, - altr_edac_a10_irq_handler, edac); -#endif + irq_set_chained_handler_and_data(edac->db_irq, + altr_edac_a10_irq_handler, edac); + } for_each_child_of_node(pdev->dev.of_node, child) { if (!of_device_is_available(child)) From e09afa69e3f5d5a304940cf4c6ea17642a1e3993 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Thu, 25 Jun 2026 16:07:49 +0530 Subject: [PATCH 0008/2366] MAINTAINERS: Add Radhey Shyam Pandey as Xilinx EDAC reviewer I have volunteered to review Xilinx EDAC related changes. Add myself as a reviewer to stay aligned with ongoing patch activity and actively contribute to this subsystem. Signed-off-by: Radhey Shyam Pandey Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260625103749.1416083-1-radhey.shyam.pandey@amd.com --- MAINTAINERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index f16c09f76e7b..d413dac5c8b3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29589,12 +29589,14 @@ F: include/uapi/linux/xilinx-v4l2-controls.h XILINX VERSAL EDAC DRIVER M: Shubhrajyoti Datta M: Sai Krishna Potthuri +R: Radhey Shyam Pandey S: Maintained F: Documentation/devicetree/bindings/memory-controllers/xlnx,versal-ddrmc-edac.yaml F: drivers/edac/versal_edac.c XILINX VERSALNET EDAC DRIVER M: Shubhrajyoti Datta +R: Radhey Shyam Pandey S: Maintained F: Documentation/devicetree/bindings/memory-controllers/xlnx,versal-net-ddrmc5.yaml F: drivers/edac/versalnet_edac.c @@ -29632,6 +29634,7 @@ F: include/dt-bindings/dma/xlnx-zynqmp-dpdma.h XILINX ZYNQMP OCM EDAC DRIVER M: Shubhrajyoti Datta M: Sai Krishna Potthuri +R: Radhey Shyam Pandey S: Maintained F: Documentation/devicetree/bindings/memory-controllers/xlnx,zynqmp-ocmc-1.0.yaml F: drivers/edac/zynqmp_edac.c From 36a6518e746dcd2e30391c61ce6a8c4bcafd7bb7 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Mon, 13 Jul 2026 21:15:10 +0800 Subject: [PATCH 0009/2366] EDAC: Remove redundant dev_err() Since 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() and devm_request_threaded_irq() automatically log detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Andrew Jeffery # aspeed Link: https://patch.msgid.link/20260713131510.332386-1-panchuang@vivo.com --- drivers/edac/al_mc_edac.c | 12 ++---------- drivers/edac/aspeed_edac.c | 4 +--- drivers/edac/highbank_mc_edac.c | 4 +--- drivers/edac/thunderx_edac.c | 4 +--- drivers/edac/xgene_edac.c | 5 +---- 5 files changed, 6 insertions(+), 23 deletions(-) diff --git a/drivers/edac/al_mc_edac.c b/drivers/edac/al_mc_edac.c index 178b9e581a72..bf6921d8890b 100644 --- a/drivers/edac/al_mc_edac.c +++ b/drivers/edac/al_mc_edac.c @@ -302,12 +302,8 @@ static int al_mc_edac_probe(struct platform_device *pdev) IRQF_SHARED, pdev->name, pdev); - if (ret != 0) { - dev_err(&pdev->dev, - "failed to request UE IRQ %d (%d)\n", - al_mc->irq_ue, ret); + if (ret != 0) return ret; - } } if (al_mc->irq_ce > 0) { @@ -317,12 +313,8 @@ static int al_mc_edac_probe(struct platform_device *pdev) IRQF_SHARED, pdev->name, pdev); - if (ret != 0) { - dev_err(&pdev->dev, - "failed to request CE IRQ %d (%d)\n", - al_mc->irq_ce, ret); + if (ret != 0) return ret; - } } return 0; diff --git a/drivers/edac/aspeed_edac.c b/drivers/edac/aspeed_edac.c index dadb8acbee3d..6e069b255595 100644 --- a/drivers/edac/aspeed_edac.c +++ b/drivers/edac/aspeed_edac.c @@ -214,10 +214,8 @@ static int config_irq(void *ctx, struct platform_device *pdev) rc = devm_request_irq(&pdev->dev, irq, mcr_isr, IRQF_TRIGGER_HIGH, DRV_NAME, ctx); - if (rc) { - dev_err(&pdev->dev, "unable to request irq %d\n", irq); + if (rc) return rc; - } /* enable interrupts */ regmap_update_bits(aspeed_regmap, ASPEED_MCR_INTR_CTRL, diff --git a/drivers/edac/highbank_mc_edac.c b/drivers/edac/highbank_mc_edac.c index a8879d72d064..68d16cc8298d 100644 --- a/drivers/edac/highbank_mc_edac.c +++ b/drivers/edac/highbank_mc_edac.c @@ -235,10 +235,8 @@ static int highbank_mc_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); res = devm_request_irq(&pdev->dev, irq, highbank_mc_err_handler, 0, dev_name(&pdev->dev), mci); - if (res < 0) { - dev_err(&pdev->dev, "Unable to request irq %d\n", irq); + if (res < 0) goto err2; - } devres_close_group(&pdev->dev, NULL); return 0; diff --git a/drivers/edac/thunderx_edac.c b/drivers/edac/thunderx_edac.c index 75c04dfc3962..9c0a1e48f96f 100644 --- a/drivers/edac/thunderx_edac.c +++ b/drivers/edac/thunderx_edac.c @@ -729,10 +729,8 @@ static int thunderx_lmc_probe(struct pci_dev *pdev, thunderx_lmc_err_isr, thunderx_lmc_threaded_isr, 0, "[EDAC] ThunderX LMC", mci); - if (ret) { - dev_err(&pdev->dev, "Cannot set ISR: %d\n", ret); + if (ret) goto err_free; - } lmc->node = FIELD_GET(THUNDERX_NODE, pci_resource_start(pdev, 0)); diff --git a/drivers/edac/xgene_edac.c b/drivers/edac/xgene_edac.c index 9955396c9a52..62b8166dc287 100644 --- a/drivers/edac/xgene_edac.c +++ b/drivers/edac/xgene_edac.c @@ -1924,11 +1924,8 @@ static int xgene_edac_probe(struct platform_device *pdev) rc = devm_request_irq(&pdev->dev, irq, xgene_edac_isr, IRQF_SHARED, dev_name(&pdev->dev), edac); - if (rc) { - dev_err(&pdev->dev, - "Could not request IRQ %d\n", irq); + if (rc) goto out_err; - } } } From 0c4775d3a756b923c815327dc585ee167057ed52 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 24 Jul 2026 03:45:29 +0900 Subject: [PATCH 0010/2366] RAS/AMD/ATL: Remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260723184538.3888637-28-ekffu200098@gmail.com --- drivers/ras/amd/atl/map.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/ras/amd/atl/map.c b/drivers/ras/amd/atl/map.c index 24a05af747d5..4ec9333ef745 100644 --- a/drivers/ras/amd/atl/map.c +++ b/drivers/ras/amd/atl/map.c @@ -771,9 +771,5 @@ int get_address_map(struct addr_ctx *ctx) dump_address_map(&ctx->map); - ret = validate_address_map(ctx); - if (ret) - return ret; - - return ret; + return validate_address_map(ctx); } From 141556543c9917d7c3d527f7eca6e288ec6bb58b Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:29 +0800 Subject: [PATCH 0011/2366] EDAC/ie31200: Decouple DIMM width decoding from enum order The current method to get DIMM width relied on DEV_* enum ordering via a linear offset (+ DEV_X8), tightly coupling hardware encoding to enum layout. Replace it with explicit decoding to remove this dependency, as the enum is expected to grow with additional device widths. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-2-qiuxu.zhuo@intel.com --- drivers/edac/ie31200_edac.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/edac/ie31200_edac.c b/drivers/edac/ie31200_edac.c index e3bd6436669b..bfd54012ae47 100644 --- a/drivers/edac/ie31200_edac.c +++ b/drivers/edac/ie31200_edac.c @@ -416,7 +416,23 @@ static void populate_dimm_info(struct dimm_data *dd, u32 addr_decode, int dimm, { dd->size = field_get(cfg->reg_mad_dimm_size_mask[dimm], addr_decode) * cfg->reg_mad_dimm_size_granularity; dd->ranks = field_get(cfg->reg_mad_dimm_rank_mask[dimm], addr_decode) + 1; - dd->dtype = field_get(cfg->reg_mad_dimm_width_mask[dimm], addr_decode) + DEV_X8; + + switch (field_get(cfg->reg_mad_dimm_width_mask[dimm], addr_decode)) { + case 0: + dd->dtype = DEV_X8; + break; + case 1: + dd->dtype = DEV_X16; + break; + case 2: + dd->dtype = DEV_X32; + break; + case 3: + dd->dtype = DEV_X64; + break; + default: + dd->dtype = DEV_UNKNOWN; + } } static void ie31200_get_dimm_config(struct mem_ctl_info *mci, void __iomem *window, From f4008169bd320eedb9ddf2b39eeb21370ddac278 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:30 +0800 Subject: [PATCH 0012/2366] EDAC/igen6: Fix interleave boundary condition The address translation logic splits the memory space into interleaved and non-interleaved regions using a boundary at 2 * s_size. The current check uses '>' and incorrectly classifies the boundary address (2 * s_size) as part of the interleaved region. This leads to incorrect channel/sub-channel selection at the region boundary. Fix the classification by using '>=' so that the boundary address is handled in the non-interleaved region, matching the hardware layout. Fixes: 10590a9d4f23 ("EDAC/igen6: Add EDAC driver for Intel client SoCs using IBECC") Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-3-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index f1fc20d4ebf6..43b56a2eb547 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -1035,7 +1035,7 @@ static void decode_addr(u64 addr, u32 hash, u64 s_size, int l_map, { int intlv_bit = CHANNEL_HASH_LSB_MASK_BIT(hash) + 6; - if (addr > 2 * s_size) { + if (addr >= 2 * s_size) { *sub_addr = addr - s_size; *idx = l_map; return; From 540b79536f3a89a66c5b6c490110298d43025618 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:31 +0800 Subject: [PATCH 0013/2366] EDAC/igen6: Fix channel selection hash In channel selection hash mode, the hardware decoding logic always includes the channel interleave bit in XOR operations. However, the hash mask may or may not include this channel interleave bit. When the mask does include this bit, the current igen6_edac code performs XOR on the interleave bit twice, effectively ignoring it - which is incorrect. Fix this issue by ensuring the hash mask always includes the interleave bit, so XOR is performed on the interleave bit exactly once. Fixes: 10590a9d4f23 ("EDAC/igen6: Add EDAC driver for Intel client SoCs using IBECC") Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-4-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index 43b56a2eb547..f1fb644154ae 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -1009,14 +1009,22 @@ static void set_dimm_params(struct igen6_imc *imc, int chan) static int decode_chan_idx(u64 addr, u64 mask, int intlv_bit) { - u64 hash_addr = addr & mask, hash = 0; - u64 intlv = (addr >> intlv_bit) & 1; + u64 hash_addr, hash = 0; int i; + /* + * In hash mode, the @intlv_bit is the lowest selected bit of @addr + * to be XORed. While @mask may or may not include this @intlv_bit, + * we enforce that @mask includes @intlv_bit to ensure @intlv_bit is + * XORed exactly once. + */ + mask |= 1 << intlv_bit; + hash_addr = addr & mask; + for (i = 6; i < 20; i++) hash ^= (hash_addr >> i) & 1; - return (int)hash ^ intlv; + return (int)hash; } static u64 decode_channel_addr(u64 addr, int intlv_bit) From 7b348d0d401d478f1923ba20a34a61681d1f7971 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:32 +0800 Subject: [PATCH 0014/2366] EDAC/igen6: Fix channel address decode for non-hash mode In non-hash mode, decode_channel_addr() and channel index extraction used a hardcoded interleave bit position 6 instead of the actual intlv_bit parameter, causing incorrect channel address decoding. Fix this by using intlv_bit consistently in both hash and non-hash modes. Fixes: 10590a9d4f23 ("EDAC/igen6: Add EDAC driver for Intel client SoCs using IBECC") Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-5-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index f1fb644154ae..ea5628d780eb 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -1049,13 +1049,12 @@ static void decode_addr(u64 addr, u32 hash, u64 s_size, int l_map, return; } - if (CHANNEL_HASH_MODE(hash)) { - *sub_addr = decode_channel_addr(addr, intlv_bit); + *sub_addr = decode_channel_addr(addr, intlv_bit); + + if (CHANNEL_HASH_MODE(hash)) *idx = decode_chan_idx(addr, CHANNEL_HASH_MASK(hash), intlv_bit); - } else { - *sub_addr = decode_channel_addr(addr, 6); - *idx = GET_BITFIELD(addr, 6, 6); - } + else + *idx = GET_BITFIELD(addr, intlv_bit, intlv_bit); } static int igen6_decode(struct decoded_addr *res) From 0361f576ec0dffca13edc94580c8666146a91e02 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:33 +0800 Subject: [PATCH 0015/2366] EDAC/igen6: Fix Raptor Lake-P logged error address Raptor Lake-P was treated as using a different IBECC (In-Band ECC) error address format and therefore had a dedicated extraction path that shifted the logged address. However, Raptor Lake-P uses the same cache-line-granularity error address format as other IBECC platforms. The special handling causes the logged address to be decoded incorrectly. Fix the issue by removing Raptor Lake-P specific extraction logic and using the common path instead. This also allows reusing Alder Lake resource configuration data. Fixes: d23627a7688f ("EDAC/igen6: Add Intel Raptor Lake-P SoCs support") Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-6-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 39 ++++++--------------------------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index ea5628d780eb..12d718a50e1c 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -175,8 +175,6 @@ static struct res_config { /* Set imc->dimm_{l_size,s_size,l_map}[chan]. */ void (*set_dimm_params)(struct igen6_imc *imc, int chan); bool (*ibecc_available)(struct pci_dev *pdev); - /* Extract error address logged in IBECC */ - u64 (*err_addr)(u64 ecclog); /* Convert error address logged in IBECC to system physical address */ u64 (*err_addr_to_sys_addr)(u64 eaddr, int mc); /* Convert error address logged in IBECC to integrated memory controller address */ @@ -522,11 +520,6 @@ static u64 adl_err_addr_to_imc_addr(u64 eaddr, int mc) return imc_addr; } -static u64 rpl_p_err_addr(u64 ecclog) -{ - return field_get(res_cfg->reg_eccerrlog_addr_mask, ecclog); -} - static enum mem_type ptl_h_get_mem_type(struct igen6_imc *imc) { u32 mtype, val; @@ -716,22 +709,6 @@ static struct res_config adl_n_cfg = { .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, }; -static struct res_config rpl_p_cfg = { - .machine_check = true, - .num_imc = 2, - .reg_mchbar_mask = GENMASK_ULL(41, 17), - .reg_tom_mask = GENMASK_ULL(41, 20), - .reg_touud_mask = GENMASK_ULL(41, 20), - .reg_eccerrlog_addr_mask = GENMASK_ULL(45, 5), - .imc_base = 0xd800, - .ibecc_base = 0xd400, - .ibecc_error_log_offset = 0x68, - .ibecc_available = tgl_ibecc_available, - .err_addr = rpl_p_err_addr, - .err_addr_to_sys_addr = adl_err_addr_to_sys_addr, - .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, -}; - static struct res_config mtl_ps_cfg = { .machine_check = true, .num_imc = 2, @@ -877,11 +854,11 @@ static struct pci_device_id igen6_pci_tbl[] = { { PCI_VDEVICE(INTEL, DID_ASL_SKU1), .driver_data = (kernel_ulong_t)&adl_n_cfg }, { PCI_VDEVICE(INTEL, DID_ASL_SKU2), .driver_data = (kernel_ulong_t)&adl_n_cfg }, { PCI_VDEVICE(INTEL, DID_ASL_SKU3), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_RPL_P_SKU1), .driver_data = (kernel_ulong_t)&rpl_p_cfg }, - { PCI_VDEVICE(INTEL, DID_RPL_P_SKU2), .driver_data = (kernel_ulong_t)&rpl_p_cfg }, - { PCI_VDEVICE(INTEL, DID_RPL_P_SKU3), .driver_data = (kernel_ulong_t)&rpl_p_cfg }, - { PCI_VDEVICE(INTEL, DID_RPL_P_SKU4), .driver_data = (kernel_ulong_t)&rpl_p_cfg }, - { PCI_VDEVICE(INTEL, DID_RPL_P_SKU5), .driver_data = (kernel_ulong_t)&rpl_p_cfg }, + { PCI_VDEVICE(INTEL, DID_RPL_P_SKU1), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_RPL_P_SKU2), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_RPL_P_SKU3), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_RPL_P_SKU4), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_RPL_P_SKU5), .driver_data = (kernel_ulong_t)&adl_cfg }, { PCI_VDEVICE(INTEL, DID_MTL_PS_SKU1), .driver_data = (kernel_ulong_t)&mtl_ps_cfg }, { PCI_VDEVICE(INTEL, DID_MTL_PS_SKU2), .driver_data = (kernel_ulong_t)&mtl_ps_cfg }, { PCI_VDEVICE(INTEL, DID_MTL_PS_SKU3), .driver_data = (kernel_ulong_t)&mtl_ps_cfg }, @@ -1237,11 +1214,7 @@ static void ecclog_work_cb(struct work_struct *work) llist_for_each_entry_safe(node, tmp, head, llnode) { memset(&res, 0, sizeof(res)); - if (res_cfg->err_addr) - eaddr = res_cfg->err_addr(node->ecclog); - else - eaddr = node->ecclog & res_cfg->reg_eccerrlog_addr_mask; - + eaddr = node->ecclog & res_cfg->reg_eccerrlog_addr_mask; res.mc = node->mc; res.sys_addr = res_cfg->err_addr_to_sys_addr(eaddr, res.mc); res.imc_addr = res_cfg->err_addr_to_imc_addr(eaddr, res.mc); From a118a5e2f172a5122d387022e5d1d41d30750738 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:34 +0800 Subject: [PATCH 0016/2366] EDAC/igen6: Remove unnecessary XOR on the zero-valued interleave bit When reconstructing the removed interleave bit from an inflated memory slice address, where a zero was inserted at the interleave bit position, it's unnecessary to XOR this zero-valued interleave bit. Remove this unnecessary XOR operation. No functional changes intended. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-7-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index 12d718a50e1c..71ed50daeb19 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -448,16 +448,16 @@ static u64 mem_addr_to_sys_addr(u64 maddr) return maddr; } -static u64 mem_slice_hash(u64 addr, u64 mask, u64 hash_init, int intlv_bit) +static u64 mem_slice_hash(u64 addr, u64 mask, u64 hash_init) { + /* The interleave bit in @addr is a zero. */ u64 hash_addr = addr & mask, hash = hash_init; - u64 intlv = (addr >> intlv_bit) & 1; int i; for (i = 6; i < 20; i++) hash ^= (hash_addr >> i) & 1; - return hash ^ intlv; + return hash; } static u64 tgl_err_addr_to_mem_addr(u64 eaddr, int mc) @@ -478,7 +478,7 @@ static u64 tgl_err_addr_to_mem_addr(u64 eaddr, int mc) maddr = GET_BITFIELD(eaddr, intlv_bit, 63) << (intlv_bit + 1) | GET_BITFIELD(eaddr, 0, intlv_bit - 1); - hash = mem_slice_hash(maddr, mask, mc, intlv_bit); + hash = mem_slice_hash(maddr, mask, mc); return maddr | (hash << intlv_bit); } From 8ac9136d79e960b8b8b9a41b9d4076e4afe6de5e Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:35 +0800 Subject: [PATCH 0017/2366] EDAC/igen6: Simplify compute die ID comments The existing comments repeat information already implied by the code structure. Shorten them to SoC names only to reduce clutter and improve readability. No functional changes intended. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-8-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index 71ed50daeb19..2960af172c57 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -223,7 +223,8 @@ static char ecclog_buf[ECCLOG_POOL_SIZE]; static struct irq_work ecclog_irq_work; static struct work_struct ecclog_work; -/* Compute die IDs for Elkhart Lake with IBECC */ +/* SoC compute die IDs with IBECC capability. */ +/* Elkhart Lake */ #define DID_EHL_SKU5 0x4514 #define DID_EHL_SKU6 0x4528 #define DID_EHL_SKU7 0x452a @@ -236,22 +237,22 @@ static struct work_struct ecclog_work; #define DID_EHL_SKU14 0x4534 #define DID_EHL_SKU15 0x4536 -/* Compute die IDs for ICL-NNPI with IBECC */ +/* ICL-NNPI */ #define DID_ICL_SKU8 0x4581 #define DID_ICL_SKU10 0x4585 #define DID_ICL_SKU11 0x4589 #define DID_ICL_SKU12 0x458d -/* Compute die IDs for Tiger Lake with IBECC */ +/* Tiger Lake */ #define DID_TGL_SKU 0x9a14 -/* Compute die IDs for Alder Lake with IBECC */ +/* Alder Lake */ #define DID_ADL_SKU1 0x4601 #define DID_ADL_SKU2 0x4602 #define DID_ADL_SKU3 0x4621 #define DID_ADL_SKU4 0x4641 -/* Compute die IDs for Alder Lake-N with IBECC */ +/* Alder Lake-N */ #define DID_ADL_N_SKU1 0x4614 #define DID_ADL_N_SKU2 0x4617 #define DID_ADL_N_SKU3 0x461b @@ -265,38 +266,38 @@ static struct work_struct ecclog_work; #define DID_ADL_N_SKU11 0x467c #define DID_ADL_N_SKU12 0x4632 -/* Compute die IDs for Arizona Beach with IBECC */ +/* Arizona Beach */ #define DID_AZB_SKU1 0x4676 -/* Compute did IDs for Amston Lake with IBECC */ +/* Amston Lake */ #define DID_ASL_SKU1 0x464a #define DID_ASL_SKU2 0x4646 #define DID_ASL_SKU3 0x4652 -/* Compute die IDs for Raptor Lake-P with IBECC */ +/* Raptor Lake-P */ #define DID_RPL_P_SKU1 0xa706 #define DID_RPL_P_SKU2 0xa707 #define DID_RPL_P_SKU3 0xa708 #define DID_RPL_P_SKU4 0xa716 #define DID_RPL_P_SKU5 0xa718 -/* Compute die IDs for Meteor Lake-PS with IBECC */ +/* Meteor Lake-PS */ #define DID_MTL_PS_SKU1 0x7d21 #define DID_MTL_PS_SKU2 0x7d22 #define DID_MTL_PS_SKU3 0x7d23 #define DID_MTL_PS_SKU4 0x7d24 -/* Compute die IDs for Meteor Lake-P with IBECC */ +/* Meteor Lake-P */ #define DID_MTL_P_SKU1 0x7d01 #define DID_MTL_P_SKU2 0x7d02 #define DID_MTL_P_SKU3 0x7d14 -/* Compute die IDs for Arrow Lake-UH with IBECC */ +/* Arrow Lake-UH */ #define DID_ARL_UH_SKU1 0x7d06 #define DID_ARL_UH_SKU2 0x7d20 #define DID_ARL_UH_SKU3 0x7d30 -/* Compute die IDs for Panther Lake-H with IBECC */ +/* Panther Lake-H */ #define DID_PTL_H_SKU1 0xb000 #define DID_PTL_H_SKU2 0xb001 #define DID_PTL_H_SKU3 0xb002 @@ -312,10 +313,10 @@ static struct work_struct ecclog_work; #define DID_PTL_H_SKU13 0xb02a #define DID_PTL_H_SKU14 0xb00a -/* Compute die IDs for Wildcat Lake with IBECC */ +/* Wildcat Lake */ #define DID_WCL_SKU1 0xfd00 -/* Compute die IDs for Nova Lake-H/HX with IBECC */ +/* Nova Lake-H/HX */ #define DID_NVL_H_SKU1 0xd701 #define DID_NVL_H_SKU2 0xd702 #define DID_NVL_H_SKU3 0xd704 From e492449e39b7aee9bfcb1bda3181fa942d24cdd2 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:36 +0800 Subject: [PATCH 0018/2366] EDAC/igen6: Detect present memory controllers at runtime The igen6_edac currently relies on res_config::num_imc to describe the number of memory controllers supported by each SoC. As a result, adding support for a new platform requires updating this configuration even though the hardware can be discovered at runtime. Instead, detect the number of present memory controllers at runtime and size the driver state accordingly. This eliminates the need to update res_config whenever a new SoC variant is added. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-9-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 97 ++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index 2960af172c57..d468c655348c 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -42,7 +42,8 @@ #define GET_BITFIELD(v, lo, hi) (((v) & GENMASK_ULL(hi, lo)) >> (lo)) -#define NUM_IMC 2 /* Max memory controllers */ +/* Probing upper bound, not a hardware capability limit. */ +#define MAX_IMC_TO_PROBE 8 #define NUM_CHANNELS 2 /* Max channels */ #define NUM_DIMMS 2 /* Max DIMMs per channel */ @@ -182,11 +183,11 @@ static struct res_config { } *res_cfg; static struct igen6_pvt { - struct igen6_imc imc[NUM_IMC]; void __iomem *memss_pma_cr; u64 ms_hash; u64 ms_s_size; int ms_l_map; + struct igen6_imc imc[]; } *igen6_pvt; /* The top of low usable DRAM */ @@ -353,6 +354,46 @@ static int get_mchbar(struct pci_dev *pdev, u64 *mchbar) return 0; } +/* Check whether the memory controller is absent. */ +static bool imc_absent(void __iomem *window) +{ + return readl(window + MAD_INTER_CHANNEL_OFFSET) == ~0; +} + +/* Return MMIO base address of the memory controller if it's present, otherwise return NULL. */ +static void __iomem *map_imc_window(u64 mchbar, int pmc) +{ + void __iomem *window; + + window = ioremap(mchbar + pmc * MCHBAR_SIZE, MCHBAR_SIZE); + if (!window) + return NULL; + + if (imc_absent(window)) { + iounmap(window); + return NULL; + } + + return window; +} + +/* Return the number of present memory controllers. */ +static int get_imc_num(u64 mchbar) +{ + void __iomem *window; + int lmc, pmc; + + for (lmc = 0, pmc = 0; pmc < MAX_IMC_TO_PROBE; pmc++) { + window = map_imc_window(mchbar, pmc); + if (window) { + iounmap(window); + lmc++; + } + } + + return lmc; +} + static bool ehl_ibecc_available(struct pci_dev *pdev) { u32 v; @@ -1457,18 +1498,27 @@ static struct igen6_pvt *igen6_pvt_setup(struct pci_dev *pdev) { void __iomem *memss_pma_cr; struct igen6_pvt *pvt; + int imc_num, rc; u64 mchbar; - int rc; - - pvt = kzalloc_obj(*igen6_pvt); - if (!pvt) - return NULL; rc = get_mchbar(pdev, &mchbar); - if (rc) { - kfree(pvt); + if (rc) + return NULL; + + imc_num = get_imc_num(mchbar); + if (!imc_num) { + igen6_printk(KERN_ERR, "No mc found.\n"); return NULL; } + edac_dbg(2, "%d mcs found.\n", imc_num); + + /* Use the runtime detected IMC count. */ + if (res_cfg->num_imc != imc_num) + res_cfg->num_imc = imc_num; + + pvt = kzalloc_flex(*pvt, imc, imc_num); + if (!pvt) + return NULL; memss_pma_cr = ioremap(mchbar, MCHBAR_SIZE * 2); if (!memss_pma_cr) { @@ -1553,12 +1603,6 @@ static void igen6_check(struct mem_ctl_info *mci) irq_work_queue(&ecclog_irq_work); } -/* Check whether the memory controller is absent. */ -static bool igen6_imc_absent(void __iomem *window) -{ - return readl(window + MAD_INTER_CHANNEL_OFFSET) == ~0; -} - static void imc_release(struct device *dev) { /* Nothing to do, the 'imc' owns the 'dev' and will also release it. */ @@ -1670,26 +1714,15 @@ static int igen6_register_mcis(struct pci_dev *pdev, u64 mchbar) { void __iomem *window; int lmc, pmc, rc; - u64 base; - for (lmc = 0, pmc = 0; pmc < NUM_IMC; pmc++) { - base = mchbar + pmc * MCHBAR_SIZE; - window = ioremap(base, MCHBAR_SIZE); - if (!window) { - igen6_printk(KERN_ERR, "Failed to ioremap 0x%llx for mc%d\n", base, pmc); - rc = -ENOMEM; - goto out_unregister_mcis; - } - - if (igen6_imc_absent(window)) { - iounmap(window); - edac_dbg(2, "Skip absent mc%d\n", pmc); + for (lmc = 0, pmc = 0; pmc < MAX_IMC_TO_PROBE; pmc++) { + window = map_imc_window(mchbar, pmc); + if (!window) continue; - } rc = igen6_register_mci(lmc, window, pdev); if (rc) - goto out_iounmap; + goto err_unregister; /* Done, if all present MCs are detected and registered. */ if (++lmc >= res_cfg->num_imc) @@ -1709,10 +1742,8 @@ static int igen6_register_mcis(struct pci_dev *pdev, u64 mchbar) return 0; -out_iounmap: +err_unregister: iounmap(window); - -out_unregister_mcis: igen6_unregister_mcis(); return rc; From 1f43c17ce550e8ed628afd7fb3226c904076f864 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:37 +0800 Subject: [PATCH 0019/2366] EDAC/igen6: Remove redundant resource configuration tables Several resource configuration tables differ only in their num_imc value, while all other fields are identical. Their only purpose is to describe the number of memory controllers supported by a platform. Since IMC count is now detected at runtime, these duplicate tables no longer carry any unique platform information. Reuse the shared configurations and remove the redundant tables. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-10-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 66 +++++++++++---------------------------- 1 file changed, 19 insertions(+), 47 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index d468c655348c..e10e29f1a5f5 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -721,6 +721,7 @@ static struct res_config tgl_cfg = { .err_addr_to_imc_addr = tgl_err_addr_to_imc_addr, }; +/* Shared by Alder Lake, Alder Lake-N, Arizona Beach, Amston Lake, and Raptor Lake-P */ static struct res_config adl_cfg = { .machine_check = true, .num_imc = 2, @@ -736,21 +737,6 @@ static struct res_config adl_cfg = { .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, }; -static struct res_config adl_n_cfg = { - .machine_check = true, - .num_imc = 1, - .reg_mchbar_mask = GENMASK_ULL(41, 17), - .reg_tom_mask = GENMASK_ULL(41, 20), - .reg_touud_mask = GENMASK_ULL(41, 20), - .reg_eccerrlog_addr_mask = GENMASK_ULL(45, 5), - .imc_base = 0xd800, - .ibecc_base = 0xd400, - .ibecc_error_log_offset = 0x68, - .ibecc_available = tgl_ibecc_available, - .err_addr_to_sys_addr = adl_err_addr_to_sys_addr, - .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, -}; - static struct res_config mtl_ps_cfg = { .machine_check = true, .num_imc = 2, @@ -768,6 +754,7 @@ static struct res_config mtl_ps_cfg = { .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, }; +/* Shared by Meteor Lake-P, Arrow Lake-UH, and Wildcat Lake */ static struct res_config mtl_p_cfg = { .machine_check = true, .num_imc = 2, @@ -813,21 +800,6 @@ static struct res_config ptl_h_cfg = { .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, }; -static struct res_config wcl_cfg = { - .machine_check = true, - .num_imc = 1, - .reg_mchbar_mask = GENMASK_ULL(41, 17), - .reg_tom_mask = GENMASK_ULL(41, 20), - .reg_touud_mask = GENMASK_ULL(41, 20), - .reg_eccerrlog_addr_mask = GENMASK_ULL(38, 5), - .imc_base = 0xd800, - .ibecc_base = 0xd400, - .ibecc_error_log_offset = 0x170, - .ibecc_available = mtl_p_ibecc_available, - .err_addr_to_sys_addr = adl_err_addr_to_sys_addr, - .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, -}; - static struct res_config nvl_h_cfg = { .machine_check = true, .num_imc = 2, @@ -880,22 +852,22 @@ static struct pci_device_id igen6_pci_tbl[] = { { PCI_VDEVICE(INTEL, DID_ADL_SKU2), .driver_data = (kernel_ulong_t)&adl_cfg }, { PCI_VDEVICE(INTEL, DID_ADL_SKU3), .driver_data = (kernel_ulong_t)&adl_cfg }, { PCI_VDEVICE(INTEL, DID_ADL_SKU4), .driver_data = (kernel_ulong_t)&adl_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU1), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU2), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU3), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU4), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU5), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU6), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU7), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU8), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU9), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU10), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU11), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ADL_N_SKU12), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_AZB_SKU1), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ASL_SKU1), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ASL_SKU2), .driver_data = (kernel_ulong_t)&adl_n_cfg }, - { PCI_VDEVICE(INTEL, DID_ASL_SKU3), .driver_data = (kernel_ulong_t)&adl_n_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU1), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU2), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU3), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU4), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU5), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU6), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU7), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU8), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU9), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU10), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU11), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ADL_N_SKU12), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_AZB_SKU1), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ASL_SKU1), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ASL_SKU2), .driver_data = (kernel_ulong_t)&adl_cfg }, + { PCI_VDEVICE(INTEL, DID_ASL_SKU3), .driver_data = (kernel_ulong_t)&adl_cfg }, { PCI_VDEVICE(INTEL, DID_RPL_P_SKU1), .driver_data = (kernel_ulong_t)&adl_cfg }, { PCI_VDEVICE(INTEL, DID_RPL_P_SKU2), .driver_data = (kernel_ulong_t)&adl_cfg }, { PCI_VDEVICE(INTEL, DID_RPL_P_SKU3), .driver_data = (kernel_ulong_t)&adl_cfg }, @@ -911,6 +883,7 @@ static struct pci_device_id igen6_pci_tbl[] = { { PCI_VDEVICE(INTEL, DID_ARL_UH_SKU1), .driver_data = (kernel_ulong_t)&mtl_p_cfg }, { PCI_VDEVICE(INTEL, DID_ARL_UH_SKU2), .driver_data = (kernel_ulong_t)&mtl_p_cfg }, { PCI_VDEVICE(INTEL, DID_ARL_UH_SKU3), .driver_data = (kernel_ulong_t)&mtl_p_cfg }, + { PCI_VDEVICE(INTEL, DID_WCL_SKU1), .driver_data = (kernel_ulong_t)&mtl_p_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU1), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU2), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU3), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, @@ -925,7 +898,6 @@ static struct pci_device_id igen6_pci_tbl[] = { { PCI_VDEVICE(INTEL, DID_PTL_H_SKU12), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU13), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU14), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, - { PCI_VDEVICE(INTEL, DID_WCL_SKU1), .driver_data = (kernel_ulong_t)&wcl_cfg }, { PCI_VDEVICE(INTEL, DID_NVL_H_SKU1), .driver_data = (kernel_ulong_t)&nvl_h_cfg }, { PCI_VDEVICE(INTEL, DID_NVL_H_SKU2), .driver_data = (kernel_ulong_t)&nvl_h_cfg }, { PCI_VDEVICE(INTEL, DID_NVL_H_SKU3), .driver_data = (kernel_ulong_t)&nvl_h_cfg }, From 377c68b988d9c36beb6dceedf81ed0ff55331aa9 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:42:38 +0800 Subject: [PATCH 0020/2366] EDAC/igen6: Refactor address translation logic The igen6 EDAC driver implements similar interleave and hash translation logic at multiple levels of the memory hierarchy. The separate implementations duplicate decoding logic, making future changes harder and increasing the risk of behavior diverging. Consolidate the common address translation operations into shared helpers so all decoding paths use a single implementation. No functional changes intended. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260730024238.4096623-11-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 244 +++++++++++++++++++++++++------------- 1 file changed, 159 insertions(+), 85 deletions(-) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index e10e29f1a5f5..6abc9b203748 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -123,6 +123,43 @@ #define MEM_SLICE_HASH_MASK(v) (GET_BITFIELD(v, 6, 19) << 6) #define MEM_SLICE_HASH_LSB_MASK_BIT(v) GET_BITFIELD(v, 24, 26) +/* + * A slice represents a portion of memory space participating in an + * interleave relationship within the memory hierarchy. + * + * It can represent in different levels such as: + * + * - a pair of memory controllers + * - a memory controller + * - a memory channel + * - a memory sub-channel / DIMM + * + * +--------+ + * | | + * | Zone 1 | + * | | + * +--------+ +--------+ + * | | | | + * | | | | + * | Zone 0 | | Zone 0 | + * | | | | + * | | | | + * +--------+ +--------+ + * + * Slice L Slice S + * + * Memory space is divided into: + * + * - Zone 0 : Interleaved region + * - Zone 1 : Non-interleaved region (upper part of the large slice). + */ +struct slice { + /* Slice address. */ + u64 addr; + /* Slice that @addr belongs to. */ + int id; +}; + struct igen6_imc { int mc; struct mem_ctl_info *mci; @@ -323,6 +360,102 @@ static struct work_struct ecclog_work; #define DID_NVL_H_SKU3 0xd704 #define DID_NVL_H_SKU4 0xd705 +/* Remove the interleave bit and shift upper part down to fill gap. */ +static u64 squeeze_addr(u64 addr, int intlv_bit) +{ + u64 slice_addr; + + slice_addr = GET_BITFIELD(addr, intlv_bit + 1, 63) << intlv_bit; + slice_addr |= GET_BITFIELD(addr, 0, intlv_bit - 1); + + return slice_addr; +} + +/* Shift the upper bits up and insert a zero at the @intlv_bit bit position. */ +static u64 inflate_addr(u64 addr, int intlv_bit) +{ + u64 inflated_addr; + + /* Insert a zero at @intlv_bit position. */ + inflated_addr = GET_BITFIELD(addr, intlv_bit, 63) << (intlv_bit + 1); + inflated_addr |= GET_BITFIELD(addr, 0, intlv_bit - 1); + + return inflated_addr; +} + +static u64 compute_hash(u64 addr, u64 hash_mask, u64 hash_base, int intlv_bit) +{ + u64 hash_addr; + int i; + + /* + * In hash mode, @intlv_bit is the lowest selected bit of @addr + * to be XORed. While @mask may or may not include this @intlv_bit, + * we enforce that @mask includes @intlv_bit to ensure @intlv_bit is + * XORed exactly once. + */ + hash_mask |= BIT_ULL(intlv_bit); + hash_addr = addr & hash_mask; + + for (i = 6; i < 20; i++) + hash_base ^= (hash_addr >> i) & 1; + + return hash_base; +} + +/* + * Converts a higher-level address (system / IMC / channel) into a lower-level + * slice address and identifier. + */ +static void translate_to_lower_level(u64 addr, u64 hash_mask, u64 hash_base, + int intlv_bit, u64 s_size, int l_map, + struct slice *slice) +{ + /* In non-interleave zone. */ + if (addr >= 2 * s_size) { + slice->addr = addr - s_size; + slice->id = l_map; + return; + } + + /* In interleave zone. */ + slice->addr = squeeze_addr(addr, intlv_bit); + + /* Non-hash mode. */ + if (!hash_mask) { + slice->id = GET_BITFIELD(addr, intlv_bit, intlv_bit); + return; + } + + /* Hash mode. */ + slice->id = compute_hash(addr, hash_mask, hash_base, intlv_bit); +} + +/* Reconstruct address for upper memory hierarchy level. */ +static u64 translate_to_upper_level(u64 addr, u64 hash_mask, u64 hash_base, + int intlv_bit, u64 s_size) +{ + u64 inflated_addr, hash_val; + + /* In non-interleave zone. */ + if (addr >= s_size) + return addr + s_size; + + /* + * In interleave zone. + * + * Insert a zero at @intlv_bit position. + */ + inflated_addr = inflate_addr(addr, intlv_bit); + + /* + * Reconstruct the removed interleave bit and use it to replace + * the zero at @intlv_bit position. + */ + hash_val = compute_hash(inflated_addr, hash_mask, hash_base, intlv_bit); + return inflated_addr | (hash_val << intlv_bit); +} + static int get_mchbar(struct pci_dev *pdev, u64 *mchbar) { union { @@ -490,21 +623,9 @@ static u64 mem_addr_to_sys_addr(u64 maddr) return maddr; } -static u64 mem_slice_hash(u64 addr, u64 mask, u64 hash_init) -{ - /* The interleave bit in @addr is a zero. */ - u64 hash_addr = addr & mask, hash = hash_init; - int i; - - for (i = 6; i < 20; i++) - hash ^= (hash_addr >> i) & 1; - - return hash; -} - static u64 tgl_err_addr_to_mem_addr(u64 eaddr, int mc) { - u64 maddr, hash, mask, ms_s_size; + u64 mask, ms_s_size; int intlv_bit; u32 ms_hash; @@ -517,12 +638,7 @@ static u64 tgl_err_addr_to_mem_addr(u64 eaddr, int mc) mask = MEM_SLICE_HASH_MASK(ms_hash); intlv_bit = MEM_SLICE_HASH_LSB_MASK_BIT(ms_hash) + 6; - maddr = GET_BITFIELD(eaddr, intlv_bit, 63) << (intlv_bit + 1) | - GET_BITFIELD(eaddr, 0, intlv_bit - 1); - - hash = mem_slice_hash(maddr, mask, mc); - - return maddr | (hash << intlv_bit); + return translate_to_upper_level(eaddr, mask, mc, intlv_bit, ms_s_size); } static u64 tgl_err_addr_to_sys_addr(u64 eaddr, int mc) @@ -544,8 +660,9 @@ static u64 adl_err_addr_to_sys_addr(u64 eaddr, int mc) static u64 adl_err_addr_to_imc_addr(u64 eaddr, int mc) { - u64 imc_addr, ms_s_size = igen6_pvt->ms_s_size; + u64 ms_s_size = igen6_pvt->ms_s_size; struct igen6_imc *imc = &igen6_pvt->imc[mc]; + struct slice slice; int intlv_bit; u32 mc_hash; @@ -556,10 +673,8 @@ static u64 adl_err_addr_to_imc_addr(u64 eaddr, int mc) intlv_bit = MAC_MC_HASH_LSB(mc_hash) + 6; - imc_addr = GET_BITFIELD(eaddr, intlv_bit + 1, 63) << intlv_bit | - GET_BITFIELD(eaddr, 0, intlv_bit - 1); - - return imc_addr; + translate_to_lower_level(eaddr, 0, 0, intlv_bit, ms_s_size, 0, &slice); + return slice.addr; } static enum mem_type ptl_h_get_mem_type(struct igen6_imc *imc) @@ -998,62 +1113,13 @@ static void set_dimm_params(struct igen6_imc *imc, int chan) imc->dimm_s_size[chan] = MAD_DIMM_CH_DIMM_S_SIZE(val); } -static int decode_chan_idx(u64 addr, u64 mask, int intlv_bit) -{ - u64 hash_addr, hash = 0; - int i; - - /* - * In hash mode, the @intlv_bit is the lowest selected bit of @addr - * to be XORed. While @mask may or may not include this @intlv_bit, - * we enforce that @mask includes @intlv_bit to ensure @intlv_bit is - * XORed exactly once. - */ - mask |= 1 << intlv_bit; - hash_addr = addr & mask; - - for (i = 6; i < 20; i++) - hash ^= (hash_addr >> i) & 1; - - return (int)hash; -} - -static u64 decode_channel_addr(u64 addr, int intlv_bit) -{ - u64 channel_addr; - - /* Remove the interleave bit and shift upper part down to fill gap */ - channel_addr = GET_BITFIELD(addr, intlv_bit + 1, 63) << intlv_bit; - channel_addr |= GET_BITFIELD(addr, 0, intlv_bit - 1); - - return channel_addr; -} - -static void decode_addr(u64 addr, u32 hash, u64 s_size, int l_map, - int *idx, u64 *sub_addr) -{ - int intlv_bit = CHANNEL_HASH_LSB_MASK_BIT(hash) + 6; - - if (addr >= 2 * s_size) { - *sub_addr = addr - s_size; - *idx = l_map; - return; - } - - *sub_addr = decode_channel_addr(addr, intlv_bit); - - if (CHANNEL_HASH_MODE(hash)) - *idx = decode_chan_idx(addr, CHANNEL_HASH_MASK(hash), intlv_bit); - else - *idx = GET_BITFIELD(addr, intlv_bit, intlv_bit); -} - static int igen6_decode(struct decoded_addr *res) { struct igen6_imc *imc = &igen6_pvt->imc[res->mc]; - u64 addr = res->imc_addr, sub_addr, s_size; - int idx, l_map; - u32 hash; + u64 addr = res->imc_addr, s_size; + int intlv_bit, l_map; + u32 hash, hash_mask; + struct slice slice; if (addr >= igen6_tom) { edac_dbg(0, "Address 0x%llx out of range\n", addr); @@ -1064,17 +1130,25 @@ static int igen6_decode(struct decoded_addr *res) hash = readl(imc->window + CHANNEL_HASH_OFFSET); s_size = imc->ch_s_size; l_map = imc->ch_l_map; - decode_addr(addr, hash, s_size, l_map, &idx, &sub_addr); - res->channel_idx = idx; - res->channel_addr = sub_addr; + hash_mask = CHANNEL_HASH_MODE(hash) ? CHANNEL_HASH_MASK(hash) : 0; + intlv_bit = CHANNEL_HASH_LSB_MASK_BIT(hash) + 6; + + translate_to_lower_level(addr, hash_mask, 0, intlv_bit, s_size, l_map, &slice); + + res->channel_idx = slice.id; + res->channel_addr = slice.addr; /* Decode sub-channel/DIMM */ hash = readl(imc->window + CHANNEL_EHASH_OFFSET); - s_size = imc->dimm_s_size[idx]; - l_map = imc->dimm_l_map[idx]; - decode_addr(res->channel_addr, hash, s_size, l_map, &idx, &sub_addr); - res->sub_channel_idx = idx; - res->sub_channel_addr = sub_addr; + s_size = imc->dimm_s_size[res->channel_idx]; + l_map = imc->dimm_l_map[res->channel_idx]; + hash_mask = CHANNEL_HASH_MODE(hash) ? CHANNEL_HASH_MASK(hash) : 0; + intlv_bit = CHANNEL_HASH_LSB_MASK_BIT(hash) + 6; + + translate_to_lower_level(res->channel_addr, hash_mask, 0, intlv_bit, s_size, l_map, &slice); + + res->sub_channel_idx = slice.id; + res->sub_channel_addr = slice.addr; return 0; } From 1713cc6b0e1904cf2c2b477ff25faf163c43cbdf Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Thu, 30 Jul 2026 10:54:54 +0800 Subject: [PATCH 0021/2366] EDAC/igen6: Add Intel Starfire SoCs support Starfire is a derivative of Panther Lake SoC and shares a similar memory subsystem architecture. Add Starfire compute die ID and reuse Panther Lake's configuration data for EDAC support. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Tested-by: Jie Wang Link: https://patch.msgid.link/20260730025454.4099934-1-qiuxu.zhuo@intel.com --- drivers/edac/igen6_edac.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/edac/igen6_edac.c b/drivers/edac/igen6_edac.c index 6abc9b203748..776c5db2f598 100644 --- a/drivers/edac/igen6_edac.c +++ b/drivers/edac/igen6_edac.c @@ -351,6 +351,9 @@ static struct work_struct ecclog_work; #define DID_PTL_H_SKU13 0xb02a #define DID_PTL_H_SKU14 0xb00a +/* Starfire */ +#define DID_STF_SKU1 0xb02b + /* Wildcat Lake */ #define DID_WCL_SKU1 0xfd00 @@ -885,6 +888,7 @@ static struct res_config mtl_p_cfg = { .err_addr_to_imc_addr = adl_err_addr_to_imc_addr, }; +/* Shared by Panther Lake-H and Starfire */ static struct res_config ptl_h_cfg = { .machine_check = true, .num_imc = 2, @@ -1013,6 +1017,7 @@ static struct pci_device_id igen6_pci_tbl[] = { { PCI_VDEVICE(INTEL, DID_PTL_H_SKU12), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU13), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_PTL_H_SKU14), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, + { PCI_VDEVICE(INTEL, DID_STF_SKU1), .driver_data = (kernel_ulong_t)&ptl_h_cfg }, { PCI_VDEVICE(INTEL, DID_NVL_H_SKU1), .driver_data = (kernel_ulong_t)&nvl_h_cfg }, { PCI_VDEVICE(INTEL, DID_NVL_H_SKU2), .driver_data = (kernel_ulong_t)&nvl_h_cfg }, { PCI_VDEVICE(INTEL, DID_NVL_H_SKU3), .driver_data = (kernel_ulong_t)&nvl_h_cfg }, From 66cc9dec919dd63d8e4b3d386f7aed3ae684e645 Mon Sep 17 00:00:00 2001 From: Jad Keskes Date: Thu, 30 Jul 2026 15:55:48 +0100 Subject: [PATCH 0022/2366] EDAC/device_sysfs: Use kstrtouint() for poll_msec to prevent truncation The poll_msec sysfs store file uses simple_strtoul() which accepts an unsigned long, but the target field (poll_msec) is unsigned int. On 64-bit systems, a value > UINT_MAX is silently truncated when stored. Fix the mismatch by using kstrtouint() instead. This rejects values larger than UINT_MAX at parse time, making truncation impossible. Also add a check for value < 1 to reject the 0-delay case, which would cause the poll work to spin without delay and consume 100% CPU. Fixes: e27e3dac6517 ("drivers/edac: add edac_device class") Signed-off-by: Jad Keskes Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260730145549.148229-1-inasj268@gmail.com --- drivers/edac/edac_device_sysfs.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/edac/edac_device_sysfs.c b/drivers/edac/edac_device_sysfs.c index b1c2717cd023..6995ce039db9 100644 --- a/drivers/edac/edac_device_sysfs.c +++ b/drivers/edac/edac_device_sysfs.c @@ -88,14 +88,21 @@ static ssize_t edac_device_ctl_poll_msec_store(struct edac_device_ctl_info *ctl_info, const char *data, size_t count) { - unsigned long value; + unsigned int value; + int ret; /* get the value and enforce that it is non-zero, must be at least * one millisecond for the delay period, between scans * Then cancel last outstanding delay for the work request * and set a new one. */ - value = simple_strtoul(data, NULL, 0); + ret = kstrtouint(data, 0, &value); + if (ret < 0) + return ret; + + if (value < 1) + return -EINVAL; + edac_device_reset_delay_period(ctl_info, value); return count; From 9987979189133486632842d894603813b22937e1 Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Fri, 7 Aug 2026 14:16:17 -0700 Subject: [PATCH 0023/2366] EDAC/device_sysfs: Cleanup around edac_device_ctl_poll_msec_store() - Align function args - Fix comment style - Fixup formatting around edac_device_reset_delay_period() too The not-too-trivial change is converting the edac_device_reset_delay_period() msec argument to unsigned int as that is what the rest of the code expects. Signed-off-by: Borislav Petkov (AMD) --- drivers/edac/edac_device.c | 10 +++------- drivers/edac/edac_device_sysfs.c | 12 +++++------- drivers/edac/edac_module.h | 3 +-- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/drivers/edac/edac_device.c b/drivers/edac/edac_device.c index cf0d3c2dfc04..638be1f47c59 100644 --- a/drivers/edac/edac_device.c +++ b/drivers/edac/edac_device.c @@ -342,14 +342,10 @@ static void edac_device_workq_teardown(struct edac_device_ctl_info *edac_dev) } /* - * edac_device_reset_delay_period - * - * need to stop any outstanding workq queued up at this time - * because we will be resetting the sleep time. - * Then restart the workq on the new delay + * Stop any outstanding workq queued up at this time because sleep time will + * be reset. Then restart the workq on the new delay. */ -void edac_device_reset_delay_period(struct edac_device_ctl_info *edac_dev, - unsigned long msec) +void edac_device_reset_delay_period(struct edac_device_ctl_info *edac_dev, unsigned int msec) { edac_dev->poll_msec = msec; edac_dev->delay = msecs_to_jiffies(msec); diff --git a/drivers/edac/edac_device_sysfs.c b/drivers/edac/edac_device_sysfs.c index 6995ce039db9..6359007701ba 100644 --- a/drivers/edac/edac_device_sysfs.c +++ b/drivers/edac/edac_device_sysfs.c @@ -84,17 +84,15 @@ static ssize_t edac_device_ctl_poll_msec_show(struct edac_device_ctl_info return sprintf(data, "%u\n", ctl_info->poll_msec); } -static ssize_t edac_device_ctl_poll_msec_store(struct edac_device_ctl_info - *ctl_info, const char *data, - size_t count) +static ssize_t edac_device_ctl_poll_msec_store(struct edac_device_ctl_info *ctl_info, + const char *data, size_t count) { unsigned int value; int ret; - /* get the value and enforce that it is non-zero, must be at least - * one millisecond for the delay period, between scans - * Then cancel last outstanding delay for the work request - * and set a new one. + /* + * Get the value, make sure it is non-zero, must be at least one millisecond + * for the delay period between scans. */ ret = kstrtouint(data, 0, &value); if (ret < 0) diff --git a/drivers/edac/edac_module.h b/drivers/edac/edac_module.h index 47593afdc234..eceef5539186 100644 --- a/drivers/edac/edac_module.h +++ b/drivers/edac/edac_module.h @@ -52,8 +52,7 @@ bool edac_queue_work(struct delayed_work *work, unsigned long delay); bool edac_stop_work(struct delayed_work *work); bool edac_mod_work(struct delayed_work *work, unsigned long delay); -extern void edac_device_reset_delay_period(struct edac_device_ctl_info - *edac_dev, unsigned long msec); +extern void edac_device_reset_delay_period(struct edac_device_ctl_info *edac_dev, unsigned int msec); extern void edac_mc_reset_delay_period(unsigned long value); /* From d6eac3868143568cc68ab3cc4817227f4af115d3 Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Mon, 10 Aug 2026 06:40:13 -0700 Subject: [PATCH 0024/2366] EDAC/thunderx: Orphan it Robert doesn't have hardware to test patches anymore and no one else has shown interest in maintaining this driver, so orphan it, for now at least. Signed-off-by: Borislav Petkov (AMD) Acked-by: Robert Richter Link: https://lore.kernel.org/r/annRsN6UBDPsFLr2@rric.localdomain --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index d413dac5c8b3..671515d21bfc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9308,9 +9308,8 @@ S: Maintained F: drivers/edac/octeon_edac* EDAC-CAVIUM THUNDERX -M: Robert Richter L: linux-edac@vger.kernel.org -S: Odd Fixes +S: Orphan F: drivers/edac/thunderx_edac* EDAC-CORE From 94579f24e2b526a04eb41050af0ba018c6f528e7 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 13 Aug 2026 10:08:09 +0300 Subject: [PATCH 0025/2366] drm/virtio: Fix a NULL vs ERR_PTR() bug in virtio_gpu_user_framebuffer_create() Smatch complains that returning a NULL here will lead to a NULL pointer dereference in drm_mode_addfb2(). Return an error pointer instead. Fixes: dc5698e80cf7 ("Add virtio gpu driver.") Signed-off-by: Dan Carpenter Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/an1tWfHIHwtXd9SO@stanley.mountain --- drivers/gpu/drm/virtio/virtgpu_display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_display.c b/drivers/gpu/drm/virtio/virtgpu_display.c index 44ffffec550f..85ea252c658e 100644 --- a/drivers/gpu/drm/virtio/virtgpu_display.c +++ b/drivers/gpu/drm/virtio/virtgpu_display.c @@ -344,7 +344,7 @@ virtio_gpu_user_framebuffer_create(struct drm_device *dev, if (ret) { kfree(virtio_gpu_fb); drm_gem_object_put(obj); - return NULL; + return ERR_PTR(ret); } return &virtio_gpu_fb->base; From d96504ea631874220d89c455d735da51a796ead0 Mon Sep 17 00:00:00 2001 From: shechenglong Date: Tue, 11 Aug 2026 09:56:24 +0800 Subject: [PATCH 0026/2366] drm/virtio: check return value of vgdev_output_init() The return value of vgdev_output_init(), called by virtio_gpu_modeset_init(), is not checked. As a result, modeset initialization continues even if an output fails to initialize. check the return value and return the error to the caller. Signed-off-by: shechenglong Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260811015624.830-1-shechenglong@xfusion.com --- drivers/gpu/drm/virtio/virtgpu_display.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_display.c b/drivers/gpu/drm/virtio/virtgpu_display.c index 85ea252c658e..a1a875a0c706 100644 --- a/drivers/gpu/drm/virtio/virtgpu_display.c +++ b/drivers/gpu/drm/virtio/virtgpu_display.c @@ -378,8 +378,11 @@ int virtio_gpu_modeset_init(struct virtio_gpu_device *vgdev) vgdev->ddev->mode_config.fb_modifiers_not_supported = true; - for (i = 0 ; i < vgdev->num_scanouts; ++i) - vgdev_output_init(vgdev, i); + for (i = 0; i < vgdev->num_scanouts; ++i) { + ret = vgdev_output_init(vgdev, i); + if (ret) + return ret; + } ret = drm_vblank_init(vgdev->ddev, vgdev->num_scanouts); if (ret) From 61d85f99b5a55d4f717c4bb2f4c4acabf22ee3fc Mon Sep 17 00:00:00 2001 From: Anuj Bolewar Date: Sun, 2 Aug 2026 22:05:17 +0530 Subject: [PATCH 0027/2366] drm/virtio: reclaim pending vbufs before tearing down vqs virtio_gpu_free_vbufs() destroys the vbufs kmem_cache after the virtqueues have already been released. Commands that were queued but never completed by the device leave their vbuffers stranded in the virtqueue, so the cache still holds live objects when virtio_gpu_deinit() tears everything down. This triggers a WARNING in virtio_gpu_free_vbufs: BUG virtio-gpu-vbufs (Not tainted): Objects remaining in cache on __kmem_cache_shutdown() Drain any buffers still sitting in the control and cursor virtqueues in virtio_gpu_deinit() after the device has been reset and before the virtqueues are deleted, following the same pattern used by virtio_console's remove_vqs(). Each reclaimed buffer is released with free_vbuf(), dropping the reference on any GEM objects it holds. Pending RESOURCE_UNREF commands are handled as well: their resp_cb_data still references a GEM object, so it is cleaned up with virtio_gpu_cleanup_object() to avoid leaking it on teardown. Reported-by: syzbot+06f9b2a53ba4a5a47644@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=06f9b2a53ba4a5a47644 Signed-off-by: Anuj Bolewar Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260802-virtio-gpu-reclaim-vbufs-v2-1-5767fb860691@gmail.com --- drivers/gpu/drm/virtio/virtgpu_drv.h | 1 + drivers/gpu/drm/virtio/virtgpu_kms.c | 1 + drivers/gpu/drm/virtio/virtgpu_vq.c | 15 +++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index 7449907754a4..3e491c808734 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -332,6 +332,7 @@ void virtio_gpu_array_put_free_work(struct work_struct *work); /* virtgpu_vq.c */ int virtio_gpu_alloc_vbufs(struct virtio_gpu_device *vgdev); void virtio_gpu_free_vbufs(struct virtio_gpu_device *vgdev); +void virtio_gpu_reclaim_vbufs(struct virtio_gpu_device *vgdev); void virtio_gpu_cmd_create_resource(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo, struct virtio_gpu_object_params *params, diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c index b4329f28e976..e5a6ae679f36 100644 --- a/drivers/gpu/drm/virtio/virtgpu_kms.c +++ b/drivers/gpu/drm/virtio/virtgpu_kms.c @@ -298,6 +298,7 @@ void virtio_gpu_deinit(struct drm_device *dev) flush_work(&vgdev->cursorq.dequeue_work); flush_work(&vgdev->config_changed_work); virtio_reset_device(vgdev->vdev); + virtio_gpu_reclaim_vbufs(vgdev); vgdev->vdev->config->del_vqs(vgdev->vdev); } diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c index e5e1af8b8e8a..ab6106f4bdfc 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vq.c +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c @@ -208,6 +208,21 @@ static void free_vbuf(struct virtio_gpu_device *vgdev, kmem_cache_free(vgdev->vbufs, vbuf); } +void virtio_gpu_reclaim_vbufs(struct virtio_gpu_device *vgdev) +{ + struct virtio_gpu_vbuffer *vbuf; + + while ((vbuf = virtqueue_detach_unused_buf(vgdev->ctrlq.vq))) { + if (vbuf->objs) + virtio_gpu_array_put_free(vbuf->objs); + if (vbuf->resp_cb_data) + virtio_gpu_cleanup_object(vbuf->resp_cb_data); + free_vbuf(vgdev, vbuf); + } + while ((vbuf = virtqueue_detach_unused_buf(vgdev->cursorq.vq))) + free_vbuf(vgdev, vbuf); +} + static void reclaim_vbufs(struct virtqueue *vq, struct list_head *reclaim_list) { struct virtio_gpu_vbuffer *vbuf; From 6a736d2f9d0c6e6217fe7532bc4c50ceca71db78 Mon Sep 17 00:00:00 2001 From: Benjamin Leggett Date: Thu, 6 Aug 2026 18:54:22 -0400 Subject: [PATCH 0028/2366] drm/virtio: use the DMA API for resource backing on Xen On a Xen PV domain page addresses bear no relation to the real machine addresses the host would have to use to reach it. virtio_ring.c handles this correctly, vring_use_map_api() returns true for any xen_domain() regardless of VIRTIO_F_ACCESS_PLATFORM. virtio-gpu makes the same decision independently, but its copy looks only at the feature bit: bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev); QEMU does not set iommu_platform on virtio-vga by default, so VIRTIO_F_ACCESS_PLATFORM is not negotiated, use_dma_api is false, and virtio_gpu_object_shmem_init() describes the framebuffer's backing pages to the host with sg_phys(). Those are guest-physical addresses. In a PV domain they resolve, on the host side, to pages belonging to some other domain, so the host scans out unrelated memory. Move the decision into virtio_gpu_use_dma_api() and give it the xen_domain() check, like vring_use_map_api() has. This additionally enables the dma_sync_sgtable_for_device() calls in virtgpu_vq.c, which are required for correctness whenever swiotlb is in play. Reproduced with a Xen 4.21 PV dom0 nested inside QEMU 8.2 with virtio-vga, on both a distro 6.8 kernel and 6.18 LTS. A PVH dom0 works fine and doesn't need this fix because it is identity-mapped, only PV dom0s are affected. Fixes: a3b815f09bb8 ("drm/virtio: add iommu support.") Signed-off-by: Ben Leggett Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260806-virtgpu-xen-dma-v1-1-e499b345bbad@edera.io --- drivers/gpu/drm/virtio/virtgpu_drv.h | 20 ++++++++++++++++++++ drivers/gpu/drm/virtio/virtgpu_object.c | 2 +- drivers/gpu/drm/virtio/virtgpu_vq.c | 6 +++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index 3e491c808734..626aadf680bd 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -43,6 +43,8 @@ #include #include +#include + #define DRIVER_NAME "virtio_gpu" #define DRIVER_DESC "virtio GPU" @@ -60,6 +62,24 @@ /* See virtio_gpu_ctx_create. One additional character for NULL terminator. */ #define DEBUG_NAME_MAX_LEN 65 +/* + * Whether the host must be told about resource backing pages by DMA address + * rather than guest-physical address. + * + * This mirrors vring_use_map_api() in drivers/virtio/virtio_ring.c, including + * its xen_domain() case. + */ +static inline bool virtio_gpu_use_dma_api(const struct virtio_device *vdev) +{ + if (!virtio_has_dma_quirk(vdev)) + return true; + + if (xen_domain()) + return true; + + return false; +} + struct virtio_gpu_object_params { unsigned long size; bool dumb; diff --git a/drivers/gpu/drm/virtio/virtgpu_object.c b/drivers/gpu/drm/virtio/virtgpu_object.c index ec9efacc6919..1527c62be88b 100644 --- a/drivers/gpu/drm/virtio/virtgpu_object.c +++ b/drivers/gpu/drm/virtio/virtgpu_object.c @@ -163,7 +163,7 @@ static int virtio_gpu_object_shmem_init(struct virtio_gpu_device *vgdev, struct virtio_gpu_mem_entry **ents, unsigned int *nents) { - bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev); + bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev); struct scatterlist *sg; struct sg_table *pages; int si; diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c index ab6106f4bdfc..5e9b7b192db0 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vq.c +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c @@ -739,7 +739,7 @@ int virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(objs->objs[0]); struct virtio_gpu_transfer_to_host_2d *cmd_p; struct virtio_gpu_vbuffer *vbuf; - bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev); + bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev); if (virtio_gpu_is_shmem(bo) && use_dma_api) dma_sync_sgtable_for_device(vgdev->vdev->dev.parent, @@ -770,7 +770,7 @@ void virtio_gpu_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(objs->objs[0]); struct virtio_gpu_transfer_to_host_2d *cmd_p; struct virtio_gpu_vbuffer *vbuf; - bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev); + bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev); if (virtio_gpu_is_shmem(bo) && use_dma_api) dma_sync_sgtable_for_device(vgdev->vdev->dev.parent, @@ -1203,7 +1203,7 @@ void virtio_gpu_cmd_transfer_to_host_3d(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(objs->objs[0]); struct virtio_gpu_transfer_host_3d *cmd_p; struct virtio_gpu_vbuffer *vbuf; - bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev); + bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev); if (virtio_gpu_is_shmem(bo) && use_dma_api) dma_sync_sgtable_for_device(vgdev->vdev->dev.parent, From ab243f74ab4084ca5c8dec608cb5b0deb27db067 Mon Sep 17 00:00:00 2001 From: Youssef Samir Date: Fri, 31 Jul 2026 17:23:44 +0200 Subject: [PATCH 0029/2366] accel/qaic: Address potential out-of-bounds read in resp_worker() Although 'commit 2feec5ae5df7 ("accel/qaic: Handle DBC deactivation if the owner went away")' fixes the scenario it was intended for by walking the message and only decoding QAIC_TRANS_DEACTIVATE_FROM_DEV, if present, it skipped over the bounds checking code that is included in decode_message(). This could lead to issues such as reading past the slab allocation's end, infinite loops or kernel panics. For those issues to happen, a malformed wire message is needed to be sent from the device. Instead of duplicating the bounds checking code already present in decode_message(), use the function inside resp_worker(). Reported-by: Ruikai Peng Fixes: 2feec5ae5df7 ("accel/qaic: Handle DBC deactivation if the owner went away") Reviewed-by: Jeff Hugo Reviewed-by: Lizhi Hou Signed-off-by: Youssef Samir Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20260731152344.1905882-1-youssef.abdulrahman@oss.qualcomm.com --- drivers/accel/qaic/qaic_control.c | 46 ++++++++++++++++--------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/drivers/accel/qaic/qaic_control.c b/drivers/accel/qaic/qaic_control.c index 50bf3340e49c..2ccc55486aac 100644 --- a/drivers/accel/qaic/qaic_control.c +++ b/drivers/accel/qaic/qaic_control.c @@ -963,11 +963,13 @@ static int decode_status(struct qaic_device *qdev, void *trans, struct manage_ms static int decode_message(struct qaic_device *qdev, struct manage_msg *user_msg, struct wire_msg *msg, struct ioctl_resources *resources, - struct qaic_user *usr) + struct qaic_user *usr, bool orphaned_deactivate) { + u32 msg_hdr_count = le32_to_cpu(msg->hdr.count); u32 msg_hdr_len = le32_to_cpu(msg->hdr.len); struct wire_trans_hdr *trans_hdr; u32 msg_len = 0; + int trans_type; int ret; int i; @@ -975,10 +977,12 @@ static int decode_message(struct qaic_device *qdev, struct manage_msg *user_msg, msg_hdr_len > QAIC_MANAGE_MAX_MSG_LENGTH) return -EINVAL; - user_msg->len = 0; - user_msg->count = le32_to_cpu(msg->hdr.count); + if (user_msg) { + user_msg->len = 0; + user_msg->count = msg_hdr_count; + } - for (i = 0; i < user_msg->count; ++i) { + for (i = 0; i < msg_hdr_count; ++i) { u32 hdr_len; if (msg_len > msg_hdr_len - sizeof(*trans_hdr)) @@ -990,7 +994,20 @@ static int decode_message(struct qaic_device *qdev, struct manage_msg *user_msg, size_add(msg_len, hdr_len) > msg_hdr_len) return -EINVAL; - switch (le32_to_cpu(trans_hdr->type)) { + trans_type = le32_to_cpu(trans_hdr->type); + /* + * orphaned_deactivate is the case where a deactivate response + * is received from the device after the user owning the DBC, + * and the message requesting deactivation, has gone away. + * In this case, only process QAIC_TRANS_DEACTIVATE_FROM_DEV + * transaction and skip the others. + */ + if (orphaned_deactivate && trans_type != QAIC_TRANS_DEACTIVATE_FROM_DEV) { + msg_len += hdr_len; + continue; + } + + switch (trans_type) { case QAIC_TRANS_PASSTHROUGH_FROM_DEV: ret = decode_passthrough(qdev, trans_hdr, user_msg, &msg_len); break; @@ -1281,7 +1298,7 @@ static int qaic_manage(struct qaic_device *qdev, struct qaic_user *usr, struct m goto dma_cont_failed; } - ret = decode_message(qdev, user_msg, rsp, &resources, usr); + ret = decode_message(qdev, user_msg, rsp, &resources, usr, false); dma_cont_failed: free_dbc_buf(qdev, &resources); @@ -1446,22 +1463,7 @@ static void resp_worker(struct work_struct *work) * response to the QAIC_TRANS_TERMINATE_TO_DEV transaction, * otherwise, the user can issue an soc_reset to the device. */ - u32 msg_count = le32_to_cpu(msg->hdr.count); - u32 msg_len = le32_to_cpu(msg->hdr.len); - u32 len = 0; - int j; - - for (j = 0; j < msg_count && len < msg_len; ++j) { - struct wire_trans_hdr *trans_hdr; - - trans_hdr = (struct wire_trans_hdr *)(msg->data + len); - if (le32_to_cpu(trans_hdr->type) == QAIC_TRANS_DEACTIVATE_FROM_DEV) { - if (decode_deactivate(qdev, trans_hdr, &len, NULL)) - len += le32_to_cpu(trans_hdr->len); - } else { - len += le32_to_cpu(trans_hdr->len); - } - } + decode_message(qdev, NULL, msg, NULL, NULL, true); /* request must have timed out, drop packet */ kfree(msg); } From d042487dc118e494db2e2c1382310255c90ff544 Mon Sep 17 00:00:00 2001 From: Roshan Kumar Date: Tue, 28 Jul 2026 10:56:08 +0530 Subject: [PATCH 0030/2366] xfrm: iptfs: fix stack OOB read in iptfs_skb_reset_frag_walk() iptfs_skb_reset_frag_walk() advances to the fragment containing @offset with an unbounded loop: while (offset >= walk->past + walk->frags[walk->fragi].len) walk->past += walk->frags[walk->fragi++].len; walk->fragi is advanced and walk->frags[walk->fragi] is dereferenced without ever checking fragi against walk->nr_frags. When the requested offset is at or beyond the total length spanned by the walk's fragments, fragi runs past nr_frags and off the end of the fixed-size on-stack frags[MAX_SKB_FRAGS + 1] array, reading out-of-bounds stack memory. The two callers behave differently: iptfs_skb_add_frags() already guards against this with if (!walk->nr_frags || offset >= walk->total + walk->initial_offset) return len; but iptfs_skb_can_add_frags() has no such guard and calls iptfs_skb_reset_frag_walk() unconditionally, so it performs the out-of-range walk. Its own "fragi < walk->nr_frags" bound check runs only afterwards, too late to prevent the read. This is reachable from the receive path: a crafted IP-TFS (AGGFRAG) payload delivered to an IPTFS SA drives iptfs_reassem_cont() -> iptfs_skb_can_add_frags() with an offset past the fragment total, e.g.: BUG: KASAN: stack-out-of-bounds in iptfs_skb_reset_frag_walk+0x235/0x250 Read of size 4 at addr ffff888008ad7210 by task repro/345 iptfs_skb_reset_frag_walk+0x235/0x250 net/xfrm/xfrm_iptfs.c:392 iptfs_skb_can_add_frags+0x155/0x310 net/xfrm/xfrm_iptfs.c:420 iptfs_reassem_cont+0xcf8/0x1140 net/xfrm/xfrm_iptfs.c:902 iptfs_input_ordered+0x552/0x670 net/xfrm/xfrm_iptfs.c:1280 iptfs_input+0x3d6/0xde0 net/xfrm/xfrm_iptfs.c:1741 xfrm_input+0x282f/0x6140 net/xfrm/xfrm_input.c:700 xfrm4_esp_rcv+0x93/0x120 net/ipv4/xfrm4_protocol.c:104 ip_rcv+0x278/0x2d0 net/ipv4/ip_input.c:612 Give iptfs_skb_can_add_frags() the same up-front guard that iptfs_skb_add_frags() already has, so the walk is never entered with an out-of-range offset. When it triggers, the caller falls back to the existing linearize-and-copy path, which is safe. Fixes: 5f2b6a909574 ("xfrm: iptfs: add skb-fragment sharing code") Reported-by: Roshan Kumar Signed-off-by: Roshan Kumar Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_iptfs.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c index 597aedeac26e..2ce15c472cc4 100644 --- a/net/xfrm/xfrm_iptfs.c +++ b/net/xfrm/xfrm_iptfs.c @@ -416,6 +416,14 @@ static bool iptfs_skb_can_add_frags(const struct sk_buff *skb, if (skb_has_frag_list(skb) || skb->pp_recycle != walk->pp_recycle) return false; + /* Reject an @offset that is at or beyond the end of the walk's data + * before calling iptfs_skb_reset_frag_walk(), whose fragment-advance + * loop is otherwise unbounded and would index past walk->frags[]. + * This mirrors the guard already present in iptfs_skb_add_frags(). + */ + if (!walk->nr_frags || offset >= walk->total + walk->initial_offset) + return false; + /* Make offset relative to current frag after setting that */ offset = iptfs_skb_reset_frag_walk(walk, offset); From 89fefad9f971bc637fb22373078144f2563c4be9 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Thu, 30 Jul 2026 18:35:43 +0800 Subject: [PATCH 0031/2366] xfrm: serialize state GC with device state flush The deferred-device pass in xfrm_dev_state_flush() finds states under xfrm_state_dev_gc_lock, but drops the lock before calling xfrm_dev_state_free() because the driver callback may sleep. The device GC list does not hold an xfrm_state reference, so the state GC worker can destroy the same state concurrently. The race can proceed as follows: CPU 0 CPU 1 find x on the device GC list drop xfrm_state_dev_gc_lock read x->xso.dev xfrm_state_gc_destroy(x) xfrm_dev_state_free(x) xfrm_state_free(x) continue xfrm_dev_state_free(x) Both paths can invoke the driver callback and drop the device reference. CPU 0 can also access the xfrm_state after CPU 1 has freed it. KASAN reported: BUG: KASAN: slab-use-after-free in xfrm_dev_state_free+0x24c/0x2a0 Read of size 8 at addr ffff88810bbaa960 by task poc/102 Call Trace: xfrm_dev_state_free+0x24c/0x2a0 xfrm_dev_state_flush+0x353/0x400 xfrm_dev_event+0x26d/0x3a0 notifier_call_chain+0xc0/0x280 __dev_notify_flags+0x169/0x250 netif_change_flags+0xe7/0x160 dev_change_flags+0x96/0x220 devinet_ioctl+0x7f4/0x1880 Allocated by task 87: xfrm_state_alloc+0x1e/0x5c0 xfrm_add_sa+0xe7f/0x5820 xfrm_user_rcv_msg+0x4f3/0x940 Freed by task 57: kmem_cache_free+0xcb/0x3d0 xfrm_state_gc_task+0x4a8/0x650 process_one_work+0x63a/0x1070 Serialize xfrm_state destruction against the deferred-device pass with a mutex. Keep xfrm_state_dev_gc_lock limited to list operations and retain the existing callback and device-reference release ordering. Fixes: 07b87f9eea0c ("xfrm: Fix unregister netdevice hang on hardware offload.") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_state.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 36a4f6793ede..de097bba803b 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -226,6 +226,7 @@ static struct xfrm_state_afinfo __rcu *xfrm_state_afinfo[NPROTO]; static DEFINE_SPINLOCK(xfrm_state_gc_lock); static DEFINE_SPINLOCK(xfrm_state_dev_gc_lock); +static DEFINE_MUTEX(xfrm_state_gc_mutex); int __xfrm_state_delete(struct xfrm_state *x); @@ -632,8 +633,10 @@ static void xfrm_state_gc_task(struct work_struct *work) synchronize_rcu(); + mutex_lock(&xfrm_state_gc_mutex); hlist_for_each_entry_safe(x, tmp, &gc_list, gclist) xfrm_state_gc_destroy(x); + mutex_unlock(&xfrm_state_gc_mutex); } static enum hrtimer_restart xfrm_timer_handler(struct hrtimer *me) @@ -1000,6 +1003,7 @@ int xfrm_dev_state_flush(struct net *net, struct net_device *dev, bool task_vali out: spin_unlock_bh(&net->xfrm.xfrm_state_lock); + mutex_lock(&xfrm_state_gc_mutex); spin_lock_bh(&xfrm_state_dev_gc_lock); restart_gc: hlist_for_each_entry_safe(x, tmp, &xfrm_state_dev_gc_list, dev_gclist) { @@ -1014,6 +1018,7 @@ int xfrm_dev_state_flush(struct net *net, struct net_device *dev, bool task_vali } spin_unlock_bh(&xfrm_state_dev_gc_lock); + mutex_unlock(&xfrm_state_gc_mutex); xfrm_flush_gc(); From 42d100f5232f39b8ea7b00a7c2482325c7f032a4 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Fri, 31 Jul 2026 10:06:20 +0000 Subject: [PATCH 0032/2366] xfrm: add missing RCU read lock in xfrm_send_migrate_state() xfrm_nlmsg_multicast() requires the RCU read lock to be held because it safely dereferences the net->xfrm.nlsk pointer using rcu_dereference(). When it is called from xfrm_send_migrate_state(), the RCU read lock is not held, which triggers a suspicious RCU usage warning: WARNING: suspicious RCU usage net/xfrm/xfrm_user.c:1630 suspicious rcu_dereference_check() usage! Call Trace: lockdep_rcu_suspicious+0x13f/0x1d0 kernel/locking/lockdep.c:6876 xfrm_nlmsg_multicast+0x1d8/0x1f0 net/xfrm/xfrm_user.c:1630 xfrm_send_migrate_state+0x870/0xae0 net/xfrm/xfrm_user.c:3340 xfrm_do_migrate_state+0x1749/0x1e90 net/xfrm/xfrm_user.c:3507 xfrm_user_rcv_msg+0x7a8/0xf30 net/xfrm/xfrm_user.c:3907 Fix this by wrapping the xfrm_nlmsg_multicast() call in xfrm_send_migrate_state() with rcu_read_lock() and rcu_read_unlock(). Fixes: a9d155ea9b44 ("xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration") Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+c0e99a1aa85a286d7a3b@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c0e99a1aa85a286d7a3b Link: https://syzkaller.appspot.com/ai_job?id=8977f559-3a7e-4bb5-b4d6-1196956260b6 Signed-off-by: Aleksandr Nogikh Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_user.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index 6266a92cf302..980dbeb5a57d 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -3337,7 +3337,11 @@ static int xfrm_send_migrate_state(struct net *net, return err; } - return xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE); + rcu_read_lock(); + err = xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE); + rcu_read_unlock(); + + return err; } static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh, From dc33262be1fe43d0eb0b84fb58c6ed42e2f64a8c Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Mon, 3 Aug 2026 12:01:54 +0800 Subject: [PATCH 0033/2366] xfrm: iptfs: fix runt reassembly panic from short inner tot_len When the start of an inner packet is split across two outer packets such that fewer than 4 bytes land at the end of the first one, __input_process_payload() saves those bytes as a runt and skips the iplen/iphlen validation performed for in-place packets. When the continuation packet arrives, iptfs_reassem_cont() only requires the declared inner length to be >= sizeof(ra_runt) (6) before allocating the reassembly skb with that attacker-controlled length. However, __iptfs_iphlen() always returns the fixed minimum IP header size (20 for IPv4, 40 for IPv6), so for an inner IPv4 tot_len in [6, 19] the header-completion copy writes past the declared packet length, and the subsequent "ipremain -= copylen" underflows to ~4GB, leaving the payload copy length bounded only by blkoff (up to 64KB). At runtime the skb_put() tailroom check turns this into skb_over_panic(), i.e. an unprivileged kernel panic (DoS), reachable locally via userns+netns IPTFS SAs and remotely against IPTFS VPN gateways when the decrypted outer skb is linear (e.g. AF_PACKET taps, tun/tap delivery). Align the runt path with the normal path by requiring the declared inner length to cover at least the IP header size. This also subsumes the previous >= sizeof(ra_runt) check, since the minimum IP header is always larger than the runt buffer. This issue was found by the autokbug dynamic kernel fuzzer at Tencent Yunding Lab. Fixes: 075694765446 ("xfrm: iptfs: handle received fragmented inner packets") Reported-by: Henry Martin Signed-off-by: Henry Martin Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_iptfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c index 2ce15c472cc4..6920940a35b4 100644 --- a/net/xfrm/xfrm_iptfs.c +++ b/net/xfrm/xfrm_iptfs.c @@ -828,8 +828,8 @@ static u32 iptfs_reassem_cont(struct xfrm_iptfs_data *xtfs, u64 seq, * allocate an in progress skb */ ipremain = __iptfs_iplen(xtfs->ra_runt); - if (ipremain < sizeof(xtfs->ra_runt)) { - /* length has to be at least runtsize large */ + if (ipremain < __iptfs_iphlen(xtfs->ra_runt)) { + /* length has to be at least the IP header size */ XFRM_INC_STATS(xs_net(xtfs->x), LINUX_MIB_XFRMINIPTFSERROR); goto abandon; From 6973a21ee73c5567f883813c8ef414774b45892f Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Mon, 3 Aug 2026 21:28:58 +0800 Subject: [PATCH 0034/2366] ipv6: xfrm: use full sockets in local error paths xfrm6_local_rxpmtu() and xfrm6_local_error() dereference skb->sk as if it always pointed at a full IPv6 socket. That is not guaranteed. TCP SYN-ACK skbs can be owned by a TCP_NEW_SYN_RECV request_sock while the output path itself is driven by the full listener. If rerouting selects an IPv6 XFRM tunnel route with a lower MTU, the local PMTU/error handling path can reach these callbacks with that mini-socket still attached to the skb. The callbacks then miscast the request socket as a full inet/IPv6 socket and can read beyond the request_sock allocation when they access inet_sock or ipv6_pinfo state. Resolve the owner with skb_to_full_sk() in both callbacks and bail out when no full socket is attached. This matches the surrounding XFRM IPv6 PMTU/error logic, which already reasons about full sockets with skb_to_full_sk(). Fixes: dd767856a36e ("xfrm6: Don't call icmpv6_send on local error") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Steffen Klassert --- net/ipv6/xfrm6_output.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net/ipv6/xfrm6_output.c b/net/ipv6/xfrm6_output.c index 512bdaf13699..44b221a09a0c 100644 --- a/net/ipv6/xfrm6_output.c +++ b/net/ipv6/xfrm6_output.c @@ -19,7 +19,10 @@ void xfrm6_local_rxpmtu(struct sk_buff *skb, u32 mtu) { struct flowi6 fl6; - struct sock *sk = skb->sk; + struct sock *sk = skb_to_full_sk(skb); + + if (!sk) + return; fl6.flowi6_oif = sk->sk_bound_dev_if; fl6.daddr = ipv6_hdr(skb)->daddr; @@ -31,7 +34,10 @@ void xfrm6_local_error(struct sk_buff *skb, u32 mtu) { struct flowi6 fl6; const struct ipv6hdr *hdr; - struct sock *sk = skb->sk; + struct sock *sk = skb_to_full_sk(skb); + + if (!sk) + return; hdr = skb->encapsulation ? inner_ipv6_hdr(skb) : ipv6_hdr(skb); fl6.fl6_dport = inet_sk(sk)->inet_dport; From d1ebd9081879fd9ae9c8fb7e8928f19cc88ae320 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Tue, 4 Aug 2026 06:10:37 +0000 Subject: [PATCH 0035/2366] xfrm: fix compat ALLOCSPI request use-after-free xfrm_state_netlink() builds the ALLOCSPI response with dump_one_state(), which already calls alloc_compat() with the response skb and header. xfrm_alloc_userspi() then calls alloc_compat() again, but passes the original request skb and its header. For a compat request, the translator therefore interprets the 228-byte compat xfrm_userspi_info as the 232-byte native layout and reads four bytes past the declared payload. It also publishes the translated child through the request's frag_list. A multicast clone of the request shares skb_shared_info and can observe that child. xfrm_user_rcv_msg() frees it after the request handler returns, racing a compat receiver which may still be copying from it and resulting in a use-after-free. Remove the redundant conversion. The response keeps its correct compat translation from dump_one_state(), and no child is attached to the inbound request. Fixes: 5f3eea6b7e8f ("xfrm/compat: Attach xfrm dumps to 64=>32 bit translator") Assisted-by: Codex:gpt-5.6-sol Codex:gpt-5.5-cyber Signed-off-by: Kyle Zeng Co-developed-by: David Lee Signed-off-by: David Lee Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_user.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index 980dbeb5a57d..a2587c7e796b 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -1877,7 +1877,6 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh, struct net *net = sock_net(skb->sk); struct xfrm_state *x; struct xfrm_userspi_info *p; - struct xfrm_translator *xtr; struct sk_buff *resp_skb; xfrm_address_t *daddr; int family; @@ -1943,17 +1942,6 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh, goto out; } - xtr = xfrm_get_translator(); - if (xtr) { - err = xtr->alloc_compat(skb, nlmsg_hdr(skb)); - - xfrm_put_translator(xtr); - if (err) { - kfree_skb(resp_skb); - goto out; - } - } - err = nlmsg_unicast(xfrm_net_nlsk(net, skb), resp_skb, NETLINK_CB(skb).portid); out: From d2f5082f9e84653fa1a9e8aebaaff23e688f5e19 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 7 Aug 2026 17:15:33 +0000 Subject: [PATCH 0036/2366] xfrm: add missing rcu_read_lock(), skb_dst_force() and dev_hold() for xfrm_trans_reinject() syzbot reported a suspicious RCU usage warning in ip6_pkt_drop(): WARNING: suspicious RCU usage in ip6_pkt_drop include/net/addrconf.h:389 suspicious rcu_dereference_check() usage! Call Trace: __in6_dev_get_safely include/net/addrconf.h:389 [inline] ip6_pkt_drop+0x596/0x610 net/ipv6/route.c:4620 ip6_pkt_discard+0x1c/0x30 net/ipv6/route.c:4651 xfrm_trans_reinject+0x324/0x630 net/xfrm/xfrm_input.c:806 process_one_work kernel/workqueue.c:3322 [inline] process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486 When commit 4f4920669d21 ("xfrm: Reinject transport-mode packets through workqueue") converted xfrm_trans_reinject from a tasklet to a workqueue, the reinjection loop ceased running in softirq context. Workqueue workers run in process context where local_bh_disable() does not enter an RCU read-side critical section under CONFIG_PREEMPT_RCU. Because finish callbacks (such as ip6_rcv_finish) expect to run under an RCU read lock (performing route lookups, l3mdev lookups, and accessing RCU-protected data structures), invoking them in workqueue context without rcu_read_lock() triggers RCU lockdep warnings. Furthermore, packets queued to the workqueue via xfrm_trans_queue_net() may carry non-refcounted (noref) dst entries (e.g. from ip_route_input_noref). Additionally, on netdevice unregistration, dst_dev_put() replaces dst->dev with blackhole_netdev, so dst entries do not keep skb->dev alive while queued in the workqueue. Fix these issues by: 1. Calling skb_dst_force(skb) in xfrm_trans_queue_net() while still in the caller's RCU section to ensure dst is reference-counted before queuing. 2. Holding a reference on skb->dev via dev_hold()/dev_put() across workqueue deferral so skb->dev remains valid during finish() callback processing. 3. Acquiring rcu_read_lock() around the finish callback invocation loop in xfrm_trans_reinject(). Fixes: 4f4920669d21 ("xfrm: Reinject transport-mode packets through workqueue") Reported-by: syzbot Signed-off-by: Eric Dumazet Cc: Steffen Klassert Cc: Liu Jian Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_input.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c index eecab337bd0a..8f6109eada7e 100644 --- a/net/xfrm/xfrm_input.c +++ b/net/xfrm/xfrm_input.c @@ -800,12 +800,17 @@ static void xfrm_trans_reinject(struct work_struct *work) spin_unlock_bh(&trans->queue_lock); local_bh_disable(); + rcu_read_lock(); while ((skb = __skb_dequeue(&queue))) { struct net *net = XFRM_TRANS_SKB_CB(skb)->net; + struct net_device *dev = skb->dev; XFRM_TRANS_SKB_CB(skb)->finish(net, NULL, skb); + if (dev) + dev_put(dev); put_net(net); } + rcu_read_unlock(); local_bh_enable(); } @@ -821,12 +826,18 @@ int xfrm_trans_queue_net(struct net *net, struct sk_buff *skb, if (skb_queue_len(&trans->queue) >= READ_ONCE(net_hotdata.max_backlog)) return -ENOBUFS; + if (skb_dst(skb) && !skb_dst_force(skb)) + return -EHOSTUNREACH; + BUILD_BUG_ON(sizeof(struct xfrm_trans_cb) > sizeof(skb->cb)); hold_net = maybe_get_net(net); if (!hold_net) return -ENODEV; + if (skb->dev) + dev_hold(skb->dev); + XFRM_TRANS_SKB_CB(skb)->finish = finish; XFRM_TRANS_SKB_CB(skb)->net = hold_net; spin_lock_bh(&trans->queue_lock); From 5f01293930d18f8473681b378bb483b7087fc0dd Mon Sep 17 00:00:00 2001 From: Changwoo Min Date: Wed, 19 Aug 2026 01:04:29 +0900 Subject: [PATCH 0037/2366] sched_ext: Allow ops.cgroup_set_bandwidth() to be sleepable ops.cgroup_set_bandwidth() is delivered from scx_group_set_bandwidth(), which runs from the cpu.max cgroup interface write path (tg_set_bandwidth()) in process context. scx_group_set_bandwidth() holds percpu_down_read(&scx_cgroup_ops_rwsem), whose read side may sleep. The call site is therefore sleepable, like ops.cgroup_init(). bpf_scx_check_member() rejects a sleepable program on any member not on its allow-list, so a BPF scheduler cannot allocate -- which is sleepable -- when a cgroup gains a cpu.max limit at runtime; it must instead pre-reserve memory for a callback that cannot allocate. Add cgroup_set_bandwidth() to the allow-list so the callback can allocate on demand, and document that it may block. A scheduler must decide at load time whether to mark the callback sleepable, but the allow-list entry is a verifier property with no symbol to probe. Add a compatibility marker whose presence in the kernel's BTF lets userspace detect this support: DEFINE_SCX_COMPAT_MARKER() emits an empty, callerless function, here scx_compat_marker_cgroup_set_bandwidth_may_sleep(). It is __used __retain so neither the compiler nor the linker (under CONFIG_LD_DEAD_CODE_DATA_ELIMINATION) drops it. The markers share the scx_compat_marker_ prefix and are collected near the end of ext.c so more can be added as further capabilities appear. Signed-off-by: Changwoo Min Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 14 ++++++++++++++ kernel/sched/ext/internal.h | 23 ++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 10af28a9f2c0..b646711a45fe 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -8079,6 +8079,7 @@ static int bpf_scx_check_member(const struct btf_type *t, case offsetof(struct sched_ext_ops, cgroup_init): case offsetof(struct sched_ext_ops, cgroup_exit): case offsetof(struct sched_ext_ops, cgroup_prep_move): + case offsetof(struct sched_ext_ops, cgroup_set_bandwidth): #endif case offsetof(struct sched_ext_ops, cpu_online): case offsetof(struct sched_ext_ops, cpu_offline): @@ -11041,3 +11042,16 @@ static int __init scx_init(void) return 0; } __initcall(scx_init); + +/* + * Compatibility markers for userspace. Existence of a marker function + * represents that the kernel supports that sched-ext feature. + */ + +/* + * scx_compat_marker_cgroup_set_bandwidth_may_sleep: advertises that + * ops.cgroup_set_bandwidth() may be implemented as a sleepable callback. + */ +#ifdef CONFIG_EXT_GROUP_SCHED +DEFINE_SCX_COMPAT_MARKER(cgroup_set_bandwidth_may_sleep); +#endif /* CONFIG_EXT_GROUP_SCHED */ diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 27bbf5e04d90..53e136a47924 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -753,7 +753,7 @@ struct sched_ext_ops { * @burst_us: bandwidth control burst * * Update @cgrp's bandwidth control parameters. This is from the cpu.max - * cgroup interface. + * cgroup interface. This operation may block. * * @quota_us / @period_us determines the CPU bandwidth @cgrp is entitled * to. For example, if @period_us is 1_000_000 and @quota_us is @@ -2001,6 +2001,27 @@ struct scx_bstr_buf { char line[SCX_EXIT_MSG_LEN]; }; +/* Internal helper for DEFINE_SCX_COMPAT_MARKER(). */ +#define DECLARE_SCX_COMPAT_MARKER(func) \ + extern void scx_compat_marker_##func(void) + +/** + * DEFINE_SCX_COMPAT_MARKER() - define a userspace capability marker + * @func: marker suffix; the defined symbol is scx_compat_marker_@func + * + * Emit an empty, callerless function that is retained in the kernel's BTF. + * Its presence is part of the kernel<->userspace contract: userspace probes + * scx_compat_marker_@func (e.g. via BTF) to detect that this kernel supports + * the corresponding feature. + * + * The leading declaration suppresses the missing-prototype warning; the + * trailing declaration consumes the semicolon at the use site. + */ +#define DEFINE_SCX_COMPAT_MARKER(func) \ + DECLARE_SCX_COMPAT_MARKER(func); \ + __used __retain void scx_compat_marker_##func(void) {} \ + DECLARE_SCX_COMPAT_MARKER(func) + extern struct scx_sched __rcu *scx_root; DECLARE_PER_CPU(struct rq *, scx_locked_rq_state); From 6eca8f94d84106d3754b9df27f46a14572af9e7f Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 18 Aug 2026 08:47:21 -1000 Subject: [PATCH 0038/2366] workqueue: Annotate cb_lock nesting when draining a dead BH pool On PREEMPT_RT, bh_worker() wraps work item execution in pool->cb_lock to provide a handshake for canceling BH work items. When a CPU goes down, drain_dead_softirq_workfn() runs the dead pool's bh_worker() nested inside the local pool's bh_worker(), acquiring the cb_locks of two different pools without a nesting annotation. lockdep reports possible recursive locking: ============================================ WARNING: possible recursive locking detected -------------------------------------------- ktimers/0/16 is trying to acquire lock: ffff8880b873a990 (&pool->cb_lock){+...}-{3:3}, at: bh_worker+0x7d/0x880 but task is already holding lock: ffff8880b863a990 (&pool->cb_lock){+...}-{3:3}, at: bh_worker+0x7d/0x880 Call Trace: bh_worker+0x7d/0x880 kernel/workqueue.c:3688 drain_dead_softirq_workfn+0x95/0x220 kernel/workqueue.c:3763 process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405 bh_worker+0x46a/0x880 kernel/workqueue.c:3708 tasklet_action+0xc/0x70 kernel/softirq.c:965 The nesting can't deadlock. A pool's bh_worker() runs nested only while the pool's CPU is dead, entered from a live pool's bh_worker() on the draining CPU, so the ordering is always live to dead. CPU hotplug operations are serialized and the drain is synchronous, so the nesting depth never exceeds two. Annotate the inner acquisition with SINGLE_DEPTH_NESTING. Signed-off-by: Tejun Heo Reported-by: syzbot+1bd20115328f8254ed62@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1bd20115328f8254ed62 Fixes: ad7c7f4b9c6c ("workqueue: Provide a handshake for canceling BH workers") Cc: stable@vger.kernel.org # v6.18+ --- kernel/workqueue.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index bfeef512f6dd..c0b72dcc0f03 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -3197,7 +3197,16 @@ __acquires(&pool->lock) #ifdef CONFIG_PREEMPT_RT static void worker_lock_callback(struct worker_pool *pool) { - spin_lock(&pool->cb_lock); + /* + * SINGLE_DEPTH_NESTING is for a dead pool's bh_worker() running from + * drain_dead_softirq_workfn() inside a live pool's bh_worker(). The + * unlocked read is stable: the flag is only set while @pool's CPU is + * dead, inside a serialized hotplug operation. data_race() as the value + * only affects the lockdep annotation and the read can be elided when + * lockdep is disabled. + */ + spin_lock_nested(&pool->cb_lock, + data_race(pool->flags) & POOL_BH_DRAINING ? SINGLE_DEPTH_NESTING : 0); } static void worker_unlock_callback(struct worker_pool *pool) From f83af377c148f6ad94b41c0e8313f12adf45e1c1 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Tue, 18 Aug 2026 20:04:05 +0900 Subject: [PATCH 0039/2366] nvme-tcp: check the data direction of a C2HData PDU nvme_tcp_handle_c2h_data() finds the request by command id and checks that it has a payload, but it does not check that the command asked for data to be read. A controller that answers a write command with C2HData therefore reaches nvme_tcp_recv_data(), where _copy_to_iter() hits WARN_ON_ONCE(i->data_source) and returns 0. The receive path turns that into -EFAULT and resets the controller. No data is copied, so this is not memory corruption. What a controller gets is a kernel warning it can raise at will, which is fatal on a host booted with panic_on_warn. The send path already knows the direction - it consults rq_data_dir() when it builds a command - and nvme_tcp_handle_r2t() checks the length and the offset of the request it names. The C2HData path does not check the direction at all. Reject a C2HData PDU whose command is not a read. Rejecting it fails the command and resets the controller, as the neighbouring check in this function does; what goes away is the warning. [ 6.885580] ------------[ cut here ]------------ [ 6.886457] WARNING: lib/iov_iter.c:193 at _copy_to_iter+0x289/0x1330, CPU#0: kworker/0:1H/71 [ 6.888137] CPU: 0 UID: 0 PID: 71 Comm: kworker/0:1H Not tainted 7.2.0-rc5-NVMETCP-gf5098b6bae76 #1 PREEMPT(lazy) [ 6.891165] Workqueue: nvme_tcp_wq nvme_tcp_io_work [ 6.891875] RIP: 0010:_copy_to_iter+0x289/0x1330 [ 6.903739] Call Trace: [ 6.904085] [ 6.909254] __skb_datagram_iter+0x433/0x820 [ 6.911026] skb_copy_datagram_iter+0x37/0x120 [ 6.911622] nvme_tcp_recv_skb+0xa07/0x4320 [ 6.913378] __tcp_read_sock+0x1ab/0x810 [ 6.915788] nvme_tcp_try_recv+0x152/0x1e0 [ 6.918222] nvme_tcp_io_work+0x1e4/0x6c0 [ 6.926906] [ 6.927226] ---[ end trace 0000000000000000 ]--- [ 6.927878] nvme nvme0: queue 1 failed to copy request 0x71 data [ 6.928709] nvme nvme0: receive failed: -14 Fixes: 3f2304f8c6d6 ("nvme-tcp: add NVMe over TCP host driver") Cc: stable@vger.kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Yehyeong Lee Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 5fda9661bdb7..643fc503a477 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -684,6 +684,13 @@ static int nvme_tcp_handle_c2h_data(struct nvme_tcp_queue *queue, return -ENOENT; } + if (rq_data_dir(rq) != READ) { + dev_err(queue->ctrl->ctrl.device, + "queue %d tag %#x unexpected data for a write\n", + nvme_tcp_queue_id(queue), rq->tag); + return -EIO; + } + req = blk_mq_rq_to_pdu(rq); if (!blk_rq_payload_bytes(rq) || !req->curr_bio || !req->data_len) { dev_err(queue->ctrl->ctrl.device, From 3838e80fcfb32e62baffb63c6dc0a60153665a4d Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Mon, 17 Aug 2026 13:58:59 -0400 Subject: [PATCH 0040/2366] nvme: skip the zoned limits update if the zone info query failed nvme_query_zone_info() returns either a negative errno or a positive NVMe status code, but nvme_update_ns_info_block() only tests for the negative case: ret = nvme_query_zone_info(ns, lbaf, &zi); if (ret < 0) goto out; If the device fails the Identify Namespace (I/O Command Set specific) command, or the Identify Controller command issued by nvme_set_max_append(), the positive status falls through and setup continues with the zero-initialized zone info. nvme_update_zone_info() then marks the queue zoned with chunk_sectors and ns->head->zsze set to zero. blk_validate_zoned_limits() does not check chunk_sectors, so the limits commit succeeds. blk_revalidate_disk_zones() does reject the zero zone size, but by then the limits are live and nothing rolls them back, so I/O keeps being submitted to a zoned queue with a zero zone size and disk_zone_no() shifts by ilog2(0): nvme0n1: Invalid non power of two zone size (0) UBSAN: shift-out-of-bounds in include/linux/blkdev.h:747:16 shift exponent -1 is negative disk_zone_no include/linux/blkdev.h:747 [inline] bio_straddles_zones include/linux/blkdev.h:1058 [inline] blk_zone_wplug_handle_write block/blk-zoned.c:1423 [inline] blk_zone_plug_bio.cold+0x25/0x1c8 block/blk-zoned.c:1605 blk_mq_submit_bio+0x18fb/0x2870 block/blk-mq.c:3196 submit_bh_wbc+0x575/0x740 fs/buffer.c:2824 __block_write_full_folio+0x728/0xdd0 fs/buffer.c:1933 Any device, firmware or NVMe-oF target that fails this one command reaches this. Skip the zoned limits update in that case, and log which of the two things happened: during a revalidation the queue keeps the zone geometry it was last validated with, and on a first scan the namespace is registered without zoned limits, so that it is still available as a handle for admin commands. Neither of the paths in nvme_query_zone_info() that return a positive status logs anything, so the failure would otherwise be silent. zi.zone_size is an exact indicator: every path that returns a positive status returns before it is assigned, and after that the only failure left is -ENODEV, which the caller already handles. Found by FuzzNvme. Fixes: c85c9ab926a5 ("nvme: split nvme_update_zone_info") Cc: stable@vger.kernel.org Cc: Weidong Zhu Suggested-by: Keith Busch Reviewed-by: Christoph Hellwig Signed-off-by: Chao Shi Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 1322c678f4eb..74b7393dbe85 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2468,9 +2468,26 @@ static int nvme_update_ns_info_block(struct nvme_ns *ns, if (!nvme_update_disk_info(ns, id, nvm, &lim)) capacity = 0; + /* + * A failed zone info query leaves zi zero-initialized, so skip the + * zoned limits update instead of configuring the queue from it. + * During a revalidation that keeps the zone geometry the queue was + * last validated with; on a first scan the namespace is registered + * without zoned limits, so that it is still available as a handle + * for admin commands. + */ if (IS_ENABLED(CONFIG_BLK_DEV_ZONED) && - ns->head->ids.csi == NVME_CSI_ZNS) - nvme_update_zone_info(ns, &lim, &zi); + ns->head->ids.csi == NVME_CSI_ZNS) { + if (zi.zone_size) + nvme_update_zone_info(ns, &lim, &zi); + else + dev_warn(ns->ctrl->device, + "zone info query failed for nsid %u, %s\n", + ns->head->ns_id, + blk_queue_is_zoned(ns->disk->queue) ? + "keeping the previous zone limits" : + "not enabling zoned mode"); + } if ((ns->ctrl->vwc & NVME_CTRL_VWC_PRESENT) && !info->no_vwc) lim.features |= BLK_FEAT_WRITE_CACHE | BLK_FEAT_FUA; From d61828199c6cb4b76d48403c77023cd4bb9d09fc Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Wed, 19 Aug 2026 14:30:00 +0800 Subject: [PATCH 0041/2366] nvme-rdma: fix -EIO cleanup order in queue_rq On -EIO, the RDMA queue_rq path reports a host path error and then still cleans up the command and unmaps the SQE DMA. The path error helper completes the request, so that is double cleanup and DMA unmap after the request is already complete. Unmap the SQE first, then report the host path error. Skip the outer command cleanup on that path. Fixes: 62eca39722fd ("nvme-rdma: handle nvme_rdma_post_send failures better") Reviewed-by: Christoph Hellwig Signed-off-by: Xixin Liu Signed-off-by: Keith Busch --- drivers/nvme/host/rdma.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c index 01743ae01466..29ecbe71bb2e 100644 --- a/drivers/nvme/host/rdma.c +++ b/drivers/nvme/host/rdma.c @@ -2034,7 +2034,7 @@ static blk_status_t nvme_rdma_queue_rq(struct blk_mq_hw_ctx *hctx, struct ib_device *dev; bool queue_ready = test_bit(NVME_RDMA_Q_LIVE, &queue->flags); blk_status_t ret; - int err; + int err = 0; WARN_ON_ONCE(rq->tag < 0); @@ -2090,16 +2090,18 @@ static blk_status_t nvme_rdma_queue_rq(struct blk_mq_hw_ctx *hctx, err_unmap: nvme_rdma_unmap_data(queue, rq); err: - if (err == -EIO) - ret = nvme_host_path_error(rq); - else if (err == -ENOMEM || err == -EAGAIN) - ret = BLK_STS_RESOURCE; - else - ret = BLK_STS_IOERR; - nvme_cleanup_cmd(rq); + if (err != -EIO) { + nvme_cleanup_cmd(rq); + if (err == -ENOMEM || err == -EAGAIN) + ret = BLK_STS_RESOURCE; + else + ret = BLK_STS_IOERR; + } unmap_qe: ib_dma_unmap_single(dev, req->sqe.dma, sizeof(struct nvme_command), DMA_TO_DEVICE); + if (err == -EIO) + return nvme_host_path_error(rq); return ret; } From c1888444dc28310222dcc6e5c301d60d0943787f Mon Sep 17 00:00:00 2001 From: Kanchan Joshi Date: Tue, 18 Aug 2026 11:32:52 +0530 Subject: [PATCH 0042/2366] nvme: set ns->head in nvme_alloc_ns_head so that it becomes possible to submit non-admin commands. This is a prep patch with no functional changes. Reviewed-by: Christoph Hellwig Signed-off-by: Kanchan Joshi Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 74b7393dbe85..e7f945fefbc4 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -4018,10 +4018,11 @@ static void nvme_add_ns_cdev(struct nvme_ns *ns) set_bit(NVME_NS_CDEV_LIVE, &ns->flags); } -static struct nvme_ns_head *nvme_alloc_ns_head(struct nvme_ctrl *ctrl, +static struct nvme_ns_head *nvme_alloc_ns_head(struct nvme_ns *ns, struct nvme_ns_info *info) - __must_hold(&ctrl->subsys->lock) + __must_hold(&ns->ctrl->subsys->lock) { + struct nvme_ctrl *ctrl = ns->ctrl; struct nvme_ns_head *head; size_t size = sizeof(*head); int ret = -ENOMEM; @@ -4049,6 +4050,7 @@ static struct nvme_ns_head *nvme_alloc_ns_head(struct nvme_ctrl *ctrl, ratelimit_state_init(&head->rs_nuse, 5 * HZ, 1); ratelimit_set_flags(&head->rs_nuse, RATELIMIT_MSG_ON_RELEASE); kref_init(&head->ref); + ns->head = head; if (head->ids.csi) { ret = nvme_get_effects_log(ctrl, head->ids.csi, &head->effects); @@ -4072,6 +4074,7 @@ static struct nvme_ns_head *nvme_alloc_ns_head(struct nvme_ctrl *ctrl, ida_free(&ctrl->subsys->ns_ida, head->instance); out_free_head: kfree(head); + ns->head = NULL; out: if (ret > 0) ret = blk_status_to_errno(nvme_error_status(ret)); @@ -4158,7 +4161,7 @@ static int nvme_init_ns_head(struct nvme_ns *ns, struct nvme_ns_info *info) info->nsid); goto out_unlock; } - head = nvme_alloc_ns_head(ctrl, info); + head = nvme_alloc_ns_head(ns, info); if (IS_ERR(head)) { ret = PTR_ERR(head); goto out_unlock; From 56e1c6bbe4bb084d7ecf61698afdf70be23dd35f Mon Sep 17 00:00:00 2001 From: Kanchan Joshi Date: Tue, 18 Aug 2026 11:32:53 +0530 Subject: [PATCH 0043/2366] nvme: fix racy access to FDP placement id array nvme_query_fdp_info() is called per-path and therefore prone to races. It populates head->nr_plids/head->plids for fdp registration. But nothing protects that pair from concurrent access - two paths scanning the same namespace can race to populate it. Avoid the race by moving this initialization work to nvme_alloc_ns_head() which is called once per shared namespace. Fixes: 30b5f20bb2dd ("nvme: register fdp parameters with the block layer") Reported-by: Hari Mishal Link: https://lore.kernel.org/linux-nvme/20260725135111.14041-2-harimishal1@gmail.com/ Reviewed-by: Christoph Hellwig Signed-off-by: Kanchan Joshi Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 30 +++++++++++------------------- drivers/nvme/host/nvme.h | 1 + 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index e7f945fefbc4..5f2744be7388 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2341,14 +2341,6 @@ static int nvme_query_fdp_info(struct nvme_ns *ns, struct nvme_ns_info *info) size_t size; int i, ret; - /* - * The FDP configuration is static for the lifetime of the namespace, - * so return immediately if we've already registered this namespace's - * streams. - */ - if (head->nr_plids) - return 0; - ret = nvme_get_features(ctrl, NVME_FEAT_FDP, info->endgid, NULL, 0, &fdp); if (ret) { @@ -2395,6 +2387,7 @@ static int nvme_query_fdp_info(struct nvme_ns *ns, struct nvme_ns_info *info) for (i = 0; i < head->nr_plids; i++) head->plids[i] = le16_to_cpu(ruhs->ruhsd[i].pid); + head->write_stream_granularity = min(info->runs, U32_MAX); free: kfree(ruhs); return ret; @@ -2442,12 +2435,6 @@ static int nvme_update_ns_info_block(struct nvme_ns *ns, goto out; } - if (ns->ctrl->ctratt & NVME_CTRL_ATTR_FDPS) { - ret = nvme_query_fdp_info(ns, info); - if (ret < 0) - goto out; - } - if (nvme_invalid_lba_sz(le64_to_cpu(id->nsze), id->lbaf[lbaf].ds - SECTOR_SHIFT, &capacity)) { dev_warn_once(ns->ctrl->device, @@ -2507,10 +2494,7 @@ static int nvme_update_ns_info_block(struct nvme_ns *ns, capacity = 0; lim.max_write_streams = ns->head->nr_plids; - if (lim.max_write_streams) - lim.write_stream_granularity = min(info->runs, U32_MAX); - else - lim.write_stream_granularity = 0; + lim.write_stream_granularity = ns->head->write_stream_granularity; /* * Only set the DEAC bit if the device guarantees that reads from @@ -4059,15 +4043,23 @@ static struct nvme_ns_head *nvme_alloc_ns_head(struct nvme_ns *ns, } else head->effects = ctrl->effects; + if (ctrl->ctratt & NVME_CTRL_ATTR_FDPS) { + ret = nvme_query_fdp_info(ns, info); + if (ret < 0) + goto out_cleanup_srcu; + } + ret = nvme_mpath_alloc_disk(ctrl, head); if (ret) - goto out_cleanup_srcu; + goto out_cleanup_fdp; list_add_tail(&head->entry, &ctrl->subsys->nsheads); kref_get(&ctrl->subsys->ref); return head; +out_cleanup_fdp: + kfree(head->plids); out_cleanup_srcu: cleanup_srcu_struct(&head->srcu); out_ida_remove: diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 75e5d5a8a77c..c20e8ef8baa0 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -571,6 +571,7 @@ struct nvme_ns_head { u16 nr_plids; u16 *plids; + u32 write_stream_granularity; #ifdef CONFIG_NVME_MULTIPATH struct bio_list requeue_list __guarded_by(&requeue_lock); From 58e7c13c8f0468bdf7e10151d3fb556c6015ab2e Mon Sep 17 00:00:00 2001 From: Mohamed Khalfella Date: Tue, 11 Aug 2026 16:11:52 -0700 Subject: [PATCH 0044/2366] nvme: add opcode filtering for fault injection Currently NVMe fault injection applies to every command routed through nvme_should_fail(), which makes it hard to target a specific command type when reproducing an issue in error-handling paths. Add an "opcode" debugfs attribute alongside the existing "status" and "dont_retry" knobs. It defaults to 0xffff, meaning "match any opcode" and preserving the previous behavior. When set to a valid opcode (<= 0xff), fault injection is only considered for commands whose opcode matches. Reviewed-by: Christoph Hellwig Signed-off-by: Mohamed Khalfella Signed-off-by: Keith Busch --- .../fault-injection/nvme-fault-injection.rst | 65 +++++++++++++++++++ drivers/nvme/host/fault_inject.c | 14 +++- drivers/nvme/host/nvme.h | 1 + 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/Documentation/fault-injection/nvme-fault-injection.rst b/Documentation/fault-injection/nvme-fault-injection.rst index 1d4427890d75..09730acf0163 100644 --- a/Documentation/fault-injection/nvme-fault-injection.rst +++ b/Documentation/fault-injection/nvme-fault-injection.rst @@ -176,3 +176,68 @@ Message from dmesg:: secondary_startup_64+0xa4/0xb0 nvme nvme0: Could not set queue count (16385) nvme nvme0: IO queues not created + +Example 4: Inject an error into the first write command +------------------------------------------------------- + +:: + + echo 0x01 > /sys/kernel/debug/nvme0n1/fault_inject/opcode + echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/times + echo 100 > /sys/kernel/debug/nvme0n1/fault_inject/probability + dd if=/dev/zero of=/dev/nvme0n1 oflag=direct bs=512 count=1 + +Expected Result:: + + The first write command sent to nvme0n1 fails + +Message from dmesg:: + + FAULT_INJECTION: forcing a failure. + name fault_inject, interval 1, probability 100, space 0, times 1 + CPU: 4 UID: 0 PID: 0 Comm: swapper/4 Not tainted 7.1.0+ #5 PREEMPT(full) + Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-20240910_120124-localhost 04/01/2014 + Call Trace: + + dump_stack_lvl+0x6e/0xa0 + dump_stack+0x10/0x16 + should_fail_ex+0x461/0x510 + should_fail+0xb/0x20 + nvme_should_fail+0x11b/0x240 [nvme_core] + nvme_poll_cq+0x6ad/0xb30 [nvme] + nvme_irq+0x84/0xe0 [nvme] + ? __pfx_nvme_irq+0x10/0x10 [nvme] + ? rcu_core+0xa40/0xa90 + ? __pfx_sched_balance_softirq+0x10/0x10 + ? debug_smp_processor_id+0x17/0x20 + ? rcu_is_watching+0x13/0xa0 + __handle_irq_event_percpu+0x396/0x610 + handle_irq_event_percpu+0xf/0x90 + handle_irq_event+0xab/0x110 + handle_edge_irq+0x1a3/0x210 + __common_interrupt+0xff/0x170 + common_interrupt+0x90/0xc0 + + + asm_common_interrupt+0x27/0x40 + RIP: 0010:pv_native_safe_halt+0x13/0x20 + Code: 1f 84 00 00 00 00 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 8b 05 0a 2a 58 01 85 c0 7e 07 0f 00 2d ff cc 0d 00 fb f4 cc 0 + RSP: 0018:ffff888100a67e40 EFLAGS: 00000242 + RAX: 0000000000000001 RBX: ffff888100a49c40 RCX: ffffed102b6c645b + RDX: ffffed102b6c645b RSI: ffffffff82a0d3c0 RDI: ffffffff81428b9b + RBP: ffff888100a67e48 R08: ffffed102b6c645b R09: 0000000000000004 + R10: ffffed102b6c645a R11: 0000000000000001 R12: 0000000000000000 + R13: 0000000000000000 R14: ffffed1020149388 R15: dffffc0000000000 + ? do_idle+0x19b/0x2c0 + ? default_idle+0x9/0x20 + arch_cpu_idle+0x9/0x10 + default_idle_call+0x6b/0xa0 + do_idle+0x19b/0x2c0 + ? __pfx_do_idle+0x10/0x10 + ? complete_with_flags+0x63/0x70 + cpu_startup_entry+0x55/0x60 + start_secondary+0x1df/0x1e0 + common_startup_64+0x13e/0x158 + + nvme0n1: Write(0x1) @ LBA 0, 1 blocks, Invalid Command Opcode (sct 0x0 / sc 0x1) DNR + operation not supported error, dev nvme0n1, sector 0 op 0x1:(WRITE) flags 0x8800 phys_seg 1 prio class 2 diff --git a/drivers/nvme/host/fault_inject.c b/drivers/nvme/host/fault_inject.c index 105d6cb41c72..783e1999fef4 100644 --- a/drivers/nvme/host/fault_inject.c +++ b/drivers/nvme/host/fault_inject.c @@ -42,9 +42,11 @@ void nvme_fault_inject_init(struct nvme_fault_inject *fault_inj, } fault_inj->parent = parent; - /* create debugfs for status code and dont_retry */ + /* create debugfs for opcode, status code, and dont_retry */ + fault_inj->opcode = 0xffff; fault_inj->status = NVME_SC_INVALID_OPCODE; fault_inj->dont_retry = true; + debugfs_create_x16("opcode", 0600, dir, &fault_inj->opcode); debugfs_create_x16("status", 0600, dir, &fault_inj->status); debugfs_create_bool("dont_retry", 0600, dir, &fault_inj->dont_retry); } @@ -59,6 +61,7 @@ void nvme_should_fail(struct request *req) { struct gendisk *disk = req->q->disk; struct nvme_fault_inject *fault_inject = NULL; + struct nvme_command *cmd = nvme_req(req)->cmd; u16 status; if (disk) { @@ -72,7 +75,14 @@ void nvme_should_fail(struct request *req) fault_inject = &nvme_req(req)->ctrl->fault_inject; } - if (fault_inject && should_fail(&fault_inject->attr, 1)) { + if (!fault_inject) + return; + + if (fault_inject->opcode <= 0xff && + fault_inject->opcode != cmd->common.opcode) + return; + + if (should_fail(&fault_inject->attr, 1)) { /* inject status code and DNR bit */ status = fault_inject->status; if (fault_inject->dont_retry) diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index c20e8ef8baa0..2cff9fcbf740 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -323,6 +323,7 @@ struct nvme_fault_inject { #ifdef CONFIG_FAULT_INJECTION_DEBUG_FS struct fault_attr attr; struct dentry *parent; + u16 opcode; bool dont_retry; /* DNR, do not retry */ u16 status; /* status code */ #endif From fb1ed67788e21832b614c23767a088c08cfdd2f2 Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Thu, 13 Aug 2026 14:42:01 +0800 Subject: [PATCH 0045/2366] nvmet-rdma: fix queue leak when connect backlog is exceeded When pending disconnecting queues exceed the backlog limit, the connect path only drops the device reference and leaks the newly allocated queue and its IB resources. Fixes: badc53620fe8 ("nvme: target: rdma: fix ndev refcount leak on queue connect") Reviewed-by: Christoph Hellwig Signed-off-by: Xixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/rdma.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c index de5a88fbb233..542138fd669f 100644 --- a/drivers/nvme/target/rdma.c +++ b/drivers/nvme/target/rdma.c @@ -1627,19 +1627,13 @@ static int nvmet_rdma_queue_connect(struct rdma_cm_id *cm_id, mutex_unlock(&nvmet_rdma_queue_mutex); if (pending > NVMET_RDMA_BACKLOG) { ret = NVME_SC_CONNECT_CTRL_BUSY; - goto put_device; + goto free_queue; } } ret = nvmet_rdma_cm_accept(cm_id, queue, &event->param.conn); - if (ret) { - /* - * Don't destroy the cm_id in free path, as we implicitly - * destroy the cm_id here with non-zero ret code. - */ - queue->cm_id = NULL; + if (ret) goto free_queue; - } mutex_lock(&nvmet_rdma_queue_mutex); list_add_tail(&queue->queue_list, &nvmet_rdma_queue_list); @@ -1648,6 +1642,11 @@ static int nvmet_rdma_queue_connect(struct rdma_cm_id *cm_id, return 0; free_queue: + /* + * Don't destroy the cm_id in free path, as we implicitly + * destroy the cm_id here with non-zero ret code. + */ + queue->cm_id = NULL; nvmet_rdma_free_queue(queue); put_device: kref_put(&ndev->ref, nvmet_rdma_free_dev); From dc14753664240cedf669623b27ae9922b0618b25 Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Tue, 18 Aug 2026 02:06:55 +0300 Subject: [PATCH 0046/2366] accel/amdxdna: return early from a zero-length flush SYNC_BO does not constrain its size, so a request for zero bytes reaches drm_clflush_virt_range(), which ends with an unconditional clflushopt(end - 1). For an empty range that is the byte before the mapping, and abo->mem.kva comes from vmap(), so the access lands in the guard page below the vmalloc area and faults: BUG: unable to handle page fault for address: ffffd16fbbc70fff #PF: supervisor read access in kernel mode Oops: Oops: 0000 [#1] SMP NOPTI CPU: 7 UID: 1000 Comm: sync_bo_probe RIP: 0010:drm_clflush_virt_range+0x3c/0x70 Call Trace: amdxdna_drm_sync_bo_ioctl+0x124/0x430 [amdxdna] drm_ioctl+0x301/0x4c0 __x64_sys_ioctl+0x115/0x2f0 do_syscall_64+0xa6/0x3d0 Any process that can open the render node can do this. Reproduced 3 of 3 times on a Strix Point NPU (1022:17f0), by calling SYNC_BO with size 0 on an AMDXDNA_BO_SHARE object. The import arm takes the same request but flushes the whole scatterlist, so it survives it. Nothing needs flushing for an empty range, so answer before choosing a path. Fixes: e252e3f3488a ("accel/amdxdna: Revise device bo creation and free") Cc: stable@vger.kernel.org Signed-off-by: Taimuraz Kaitmazov Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260817230655.356785-1-taimuraz@kaitmazov.com --- drivers/accel/amdxdna/amdxdna_gem.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 1c63eff0a4a8..2a16de96e6a4 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -1247,6 +1247,9 @@ static int amdxdna_flush_bo(struct amdxdna_gem_obj *abo, u64 offset, u64 size) return -EINVAL; size = min(abo->mem.size, end) - offset; + if (!size) + return 0; + if (is_import_bo(abo)) drm_clflush_sg(abo->base.sgt); else if (amdxdna_gem_vmap(abo)) From 4fb8d6379d2c7ceecb2b3e111954d29089d59492 Mon Sep 17 00:00:00 2001 From: Liang Luo Date: Wed, 19 Aug 2026 11:12:44 +0800 Subject: [PATCH 0047/2366] sched_ext: Fix nonexistent field in sched-ext.rst example The ops.exit() example in sched-ext.rst reads ei->type, but struct scx_exit_info has never had a type field - the exit reason is exposed as ei->kind since the struct was introduced. A scheduler written following the example fails to compile with error: no member named 'type' in 'struct scx_exit_info' Use ei->kind. Fixes: fa48e8d2c7b5 ("sched_ext: Documentation: scheduler: Document extensible scheduler class") Signed-off-by: Liang Luo Signed-off-by: Tejun Heo --- Documentation/scheduler/sched-ext.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/scheduler/sched-ext.rst b/Documentation/scheduler/sched-ext.rst index 0e97fd019994..35b550671ca7 100644 --- a/Documentation/scheduler/sched-ext.rst +++ b/Documentation/scheduler/sched-ext.rst @@ -230,7 +230,7 @@ optional. The following modified excerpt is from void BPF_STRUCT_OPS(simple_exit, struct scx_exit_info *ei) { - exit_type = ei->type; + exit_type = ei->kind; } SEC(".struct_ops") From 0c893d170ff8efe7b4067552932d26e7defba307 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Wed, 19 Aug 2026 14:06:00 +0530 Subject: [PATCH 0048/2366] selftests/cgroup: set the test plan after the setup checks The cgroup tests announce their plan before checking whether cgroup v2 is available, so on a host without it they promise a number of results and then skip out after the first one: TAP version 13 1..3 ok 1 # SKIP cgroup v2 isn't mounted # Planned tests != run tests (3 != 1) # Totals: pass:0 fail:0 xfail:0 xpass:0 skip:1 error:0 ksft_exit_skip() can only emit a well formed "1..0 # SKIP" line while no plan has been printed, as the comment above it in kselftest.h points out. Move ksft_set_plan() below the setup checks that can skip, so that a skipped run reports: TAP version 13 1..0 # SKIP cgroup v2 isn't mounted Several of the tests skip more than once while setting up, for a missing or unwritable controller as well, so the plan goes after the last of them. test_core joins its two setup paths at the post_v2_setup label and sets the plan there. Reporting each planned test as skipped instead would keep the plan where it is, but the setup failures here mean the whole test cannot run rather than its individual cases being skipped, which is what "1..0 # SKIP" is for. Fixes: 1dc830ee4c15 ("selftests/cgroup: conform test to KTAP format output") Signed-off-by: Hemanth Selam Reviewed-by: Sarthak Sharma Signed-off-by: Tejun Heo --- tools/testing/selftests/cgroup/test_core.c | 2 +- tools/testing/selftests/cgroup/test_cpu.c | 2 +- tools/testing/selftests/cgroup/test_cpuset.c | 2 +- tools/testing/selftests/cgroup/test_freezer.c | 2 +- tools/testing/selftests/cgroup/test_kill.c | 2 +- tools/testing/selftests/cgroup/test_kmem.c | 2 +- tools/testing/selftests/cgroup/test_memcontrol.c | 2 +- tools/testing/selftests/cgroup/test_pids.c | 2 +- tools/testing/selftests/cgroup/test_zswap.c | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/cgroup/test_core.c b/tools/testing/selftests/cgroup/test_core.c index e9bee164bb70..20d2b63774c3 100644 --- a/tools/testing/selftests/cgroup/test_core.c +++ b/tools/testing/selftests/cgroup/test_core.c @@ -919,7 +919,6 @@ int main(int argc, char *argv[]) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), &nsdelegate)) { if (setup_named_v1_root(root, sizeof(root), CG_NAMED_NAME)) ksft_exit_skip("cgroup v2 isn't mounted and could not setup named v1 hierarchy\n"); @@ -932,6 +931,7 @@ int main(int argc, char *argv[]) ksft_exit_skip("Failed to set memory controller\n"); post_v2_setup: + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_cpu.c b/tools/testing/selftests/cgroup/test_cpu.c index f9f7017d9299..735a53bb222b 100644 --- a/tools/testing/selftests/cgroup/test_cpu.c +++ b/tools/testing/selftests/cgroup/test_cpu.c @@ -832,7 +832,6 @@ int main(int argc, char *argv[]) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); @@ -840,6 +839,7 @@ int main(int argc, char *argv[]) if (cg_write(root, "cgroup.subtree_control", "+cpu")) ksft_exit_skip("Failed to set cpu controller\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_cpuset.c b/tools/testing/selftests/cgroup/test_cpuset.c index 8c2d4d4ef1fc..3dfadd280c1c 100644 --- a/tools/testing/selftests/cgroup/test_cpuset.c +++ b/tools/testing/selftests/cgroup/test_cpuset.c @@ -497,7 +497,6 @@ int main(int argc, char *argv[]) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); @@ -505,6 +504,7 @@ int main(int argc, char *argv[]) if (cg_write(root, "cgroup.subtree_control", "+cpuset")) ksft_exit_skip("Failed to set cpuset controller\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_freezer.c b/tools/testing/selftests/cgroup/test_freezer.c index 0569e93fa6b0..f28bb02e9783 100644 --- a/tools/testing/selftests/cgroup/test_freezer.c +++ b/tools/testing/selftests/cgroup/test_freezer.c @@ -1491,9 +1491,9 @@ int main(int argc, char *argv[]) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_kill.c b/tools/testing/selftests/cgroup/test_kill.c index f6cd23a8ecc7..99cafd9dc013 100644 --- a/tools/testing/selftests/cgroup/test_kill.c +++ b/tools/testing/selftests/cgroup/test_kill.c @@ -278,9 +278,9 @@ int main(int argc, char *argv[]) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_kmem.c b/tools/testing/selftests/cgroup/test_kmem.c index 1db0ba1226b9..cb47561b4b44 100644 --- a/tools/testing/selftests/cgroup/test_kmem.c +++ b/tools/testing/selftests/cgroup/test_kmem.c @@ -426,7 +426,6 @@ int main(int argc, char **argv) int i; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); @@ -441,6 +440,7 @@ int main(int argc, char **argv) if (cg_write(root, "cgroup.subtree_control", "+memory")) ksft_exit_skip("Failed to set memory controller\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testing/selftests/cgroup/test_memcontrol.c index 0ebf796f3cff..3a84d068fbf3 100644 --- a/tools/testing/selftests/cgroup/test_memcontrol.c +++ b/tools/testing/selftests/cgroup/test_memcontrol.c @@ -1798,7 +1798,6 @@ int main(int argc, char **argv) page_size = BUF_SIZE; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); @@ -1823,6 +1822,7 @@ int main(int argc, char **argv) ksft_exit_skip("Failed to query cgroup mount option\n"); has_localevents = proc_status; + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_pids.c b/tools/testing/selftests/cgroup/test_pids.c index 9a387c815d2c..710109b53dfe 100644 --- a/tools/testing/selftests/cgroup/test_pids.c +++ b/tools/testing/selftests/cgroup/test_pids.c @@ -148,7 +148,6 @@ int main(int argc, char **argv) char root[PATH_MAX]; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); @@ -163,6 +162,7 @@ int main(int argc, char **argv) if (cg_write(root, "cgroup.subtree_control", "+pids")) ksft_exit_skip("Failed to set pids controller\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (int i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: diff --git a/tools/testing/selftests/cgroup/test_zswap.c b/tools/testing/selftests/cgroup/test_zswap.c index 49b36ee79160..6e7b89315bbf 100644 --- a/tools/testing/selftests/cgroup/test_zswap.c +++ b/tools/testing/selftests/cgroup/test_zswap.c @@ -810,7 +810,6 @@ int main(int argc, char **argv) page_size = BUF_SIZE; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tests)); if (cg_find_unified_root(root, sizeof(root), NULL)) ksft_exit_skip("cgroup v2 isn't mounted\n"); @@ -827,6 +826,7 @@ int main(int argc, char **argv) if (cg_write(root, "cgroup.subtree_control", "+memory")) ksft_exit_skip("Failed to set memory controller\n"); + ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { switch (tests[i].fn(root)) { case KSFT_PASS: From 7e2f2a377ac9f50296ad60bd331f0d4def7aee51 Mon Sep 17 00:00:00 2001 From: Zqiang Date: Thu, 16 Jul 2026 17:56:37 +0800 Subject: [PATCH 0049/2366] workqueue: Use raise_softirq() to trigger softirq in irq_work handler bh_pool_kick_normal() and bh_pool_kick_highpri() are registered via init_irq_work() without the IRQ_WORK_HARD_IRQ flag. On PREEMPT_RT, such irq_work items are processed by the per-CPU irq_workd kthread in preemptible task context with IRQs enabled. However, raise_softirq_irqoff() requires IRQs to be disabled. Calling it from irq_workd trips the lockdep assertion in __raise_softirq_irqoff() and the non-atomic update of the softirq pending mask can lose bits raised by an interrupt on the same CPU. Replace raise_softirq_irqoff() with raise_softirq() in the irq_work handlers. Fixes: 2f34d7337d98 ("workqueue: Fix queue_work_on() with BH workqueues") Cc: stable@vger.kernel.org # v6.9+ Signed-off-by: Zqiang Signed-off-by: Tejun Heo --- kernel/workqueue.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index c0b72dcc0f03..f2aed36cf7c0 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -8065,12 +8065,12 @@ static inline void wq_watchdog_init(void) { } static void bh_pool_kick_normal(struct irq_work *irq_work) { - raise_softirq_irqoff(TASKLET_SOFTIRQ); + raise_softirq(TASKLET_SOFTIRQ); } static void bh_pool_kick_highpri(struct irq_work *irq_work) { - raise_softirq_irqoff(HI_SOFTIRQ); + raise_softirq(HI_SOFTIRQ); } static void __init restrict_unbound_cpumask(const char *name, const struct cpumask *mask) From c7a2a3618290594867b4829900b434704ab31dbc Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Tue, 18 Aug 2026 16:05:10 +0300 Subject: [PATCH 0050/2366] x86/bpf: Make arch_bpf_trampoline_size allocate from EXECMEM_MODULE_DATA Jiri Olsa reports slowdown of tracing_multi benchmark that allocates huge number of trampolines [1]. The slowdown caused by extra protection changes in execmem_alloc_rw() and execmem_free(). With ROX caches enabled, all execmem allocations except EXECMEM_MODULE_DATA are ROX after the allocation. execmem_alloc_rw() temporarily sets them to W+NX and execmem_free() resets them back to ROX. The only user of bpf_jit_alloc_exec_rw() is x86::arch_bpf_trampoline_size() that only needs a temporary writable buffer in the modules address space. On x86 executable memory and module data are constrained to the same address range, so x86::arch_bpf_trampoline_size() can directly use execmem_alloc(EXECMEM_MODULE_DATA) Replace the call to bpf_jit_alloc_exec_rw() with a call to execmem_alloc(EXECMEM_MODULE_DATA) in x86::arch_bpf_trampoline_size() and drop bpf_jit_alloc_exec_rw() helper. Fixes: 5bf02dbf39fa ("bpf, x86: Make sure allocation in arch_bpf_trampoline_size() is writable") Reported-by: Jiri Olsa Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Daniel Borkmann Tested-by: Jiri Olsa Link: https://lore.kernel.org/all/an8r7EODLIL-bZM3@krava Link: https://lore.kernel.org/bpf/20260818130510.3110054-1-rppt@kernel.org --- arch/x86/net/bpf_jit_comp.c | 8 +++++--- include/linux/filter.h | 1 - kernel/bpf/core.c | 5 ----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c index 1a9fb530adc3..2853e87797a7 100644 --- a/arch/x86/net/bpf_jit_comp.c +++ b/arch/x86/net/bpf_jit_comp.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -3818,15 +3819,16 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, * * We cannot use kvmalloc here, because we need image to be in * module memory range. - * Since it must be writable use bpf_jit_alloc_exec_rw(). + * Since it must be writable use execmem_alloc(EXECMEM_MODULE_DATA) + * that returns writable memory in the module address space. */ - image = bpf_jit_alloc_exec_rw(PAGE_SIZE); + image = execmem_alloc(EXECMEM_MODULE_DATA, PAGE_SIZE); if (!image) return -ENOMEM; ret = __arch_prepare_bpf_trampoline(&im, image, image + PAGE_SIZE, image, m, flags, tnodes, func_addr); - bpf_jit_free_exec(image); + execmem_free(image); return ret; } diff --git a/include/linux/filter.h b/include/linux/filter.h index 4a9bc6a848f2..39decde7fc73 100644 --- a/include/linux/filter.h +++ b/include/linux/filter.h @@ -1376,7 +1376,6 @@ bpf_jit_binary_alloc(unsigned int proglen, u8 **image_ptr, void bpf_jit_binary_free(struct bpf_binary_header *hdr); u64 bpf_jit_alloc_exec_limit(void); void *bpf_jit_alloc_exec(unsigned long size); -void *bpf_jit_alloc_exec_rw(unsigned long size); void bpf_jit_free_exec(void *addr); void bpf_jit_free(struct bpf_prog *fp); struct bpf_binary_header * diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index d55e737ed75a..8b294dfc1ad4 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -1128,11 +1128,6 @@ void *bpf_jit_alloc_exec(unsigned long size) return execmem_alloc(EXECMEM_BPF, size); } -void *bpf_jit_alloc_exec_rw(unsigned long size) -{ - return execmem_alloc_rw(EXECMEM_BPF, size); -} - void bpf_jit_free_exec(void *addr) { execmem_free(addr); From 37e5c4f4d2856290b1c56e573ced91dcd88db8ec Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 20 Aug 2026 04:20:18 +0200 Subject: [PATCH 0051/2366] bpf: Reject invalid LDSX instruction in disassembly The signed-load mnemonic table has entries for byte, half-word, and word loads because BPF_MEMSX does not support double-word loads. A BPF_MEMSX | BPF_DW instruction nevertheless selects index 3, past the end of this table. Program Structure diagnostics can disassemble a malformed instruction before check_and_resolve_insns() rejects its opcode. Placing the invalid signed double-word load at the end of a program therefore triggers an out-of-bounds access while reporting subprogram fallthrough. Treat signed double-word loads as invalid in the disassembler and use the existing BUG_ldx fallback instead. Fixes: a8f427835394 ("bpf: Report Program Structure CFG errors") Reported-by: syzbot+3544d9b2a9206be8ba37@syzkaller.appspotmail.com Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Daniel Borkmann Reviewed-by: Jiayuan Chen Link: https://lore.kernel.org/bpf/20260820022020.3450479-2-memxor@gmail.com --- kernel/bpf/disasm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/disasm.c b/kernel/bpf/disasm.c index 50b3ca5149a0..b1a3fbe3fda5 100644 --- a/kernel/bpf/disasm.c +++ b/kernel/bpf/disasm.c @@ -295,7 +295,8 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs, verbose(cbs->private_data, "BUG_st_%02x", insn->code); } } else if (class == BPF_LDX) { - if (BPF_MODE(insn->code) != BPF_MEM && BPF_MODE(insn->code) != BPF_MEMSX) { + if ((BPF_MODE(insn->code) != BPF_MEM && BPF_MODE(insn->code) != BPF_MEMSX) || + (BPF_MODE(insn->code) == BPF_MEMSX && BPF_SIZE(insn->code) == BPF_DW)) { verbose(cbs->private_data, "BUG_ldx_%02x", insn->code); return; } From 175a58668e2d5e96c571177fc7a0d8997bfbb205 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 20 Aug 2026 04:20:19 +0200 Subject: [PATCH 0052/2366] selftests/bpf: Test invalid DW LDSX diagnostics An invalid BPF_MEMSX | BPF_DW instruction can reach Program Structure diagnostics before opcode validation when placed at the end of a subprogram. Exercise this path and require the disassembler fallback so table bounds regressions are caught. Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/bpf/20260820022020.3450479-3-memxor@gmail.com --- tools/testing/selftests/bpf/progs/verifier_cfg.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_cfg.c b/tools/testing/selftests/bpf/progs/verifier_cfg.c index c1f55e1d80a4..3c3bb03e8217 100644 --- a/tools/testing/selftests/bpf/progs/verifier_cfg.c +++ b/tools/testing/selftests/bpf/progs/verifier_cfg.c @@ -3,6 +3,7 @@ #include #include +#include "../../../include/linux/filter.h" #include "bpf_misc.h" SEC("socket") @@ -55,6 +56,19 @@ __naked void out_of_range_jump2(void) " ::: __clobber_all); } +SEC("socket") +__description("invalid DW LDSX instruction in diagnostics") +__failure __msg("BUG_ldx_99") +__log_level(2) +__naked void invalid_dw_ldsx(void) +{ + asm volatile (" \ + .8byte %[ldsx_dw]; \ +" : + : __imm_insn(ldsx_dw, BPF_RAW_INSN(BPF_LDX | BPF_MEMSX | BPF_DW, BPF_REG_0, BPF_REG_0, 0, 0)) + : __clobber_all); +} + SEC("socket") __description("loop (back-edge)") __failure __msg("unreachable insn 1") From 72c5ae18ebe6588101f2c1e96be61618ce06f182 Mon Sep 17 00:00:00 2001 From: Liang Luo Date: Thu, 20 Aug 2026 10:37:44 +0800 Subject: [PATCH 0053/2366] Docs/admin-guide/cgroup-v2: document BPF scheduler callbacks for cpu.max and cpu.idle The cpu.weight and cpu.weight.nice entries already state that the files also affect a BPF scheduler through the cgroup_set_weight callback. However, cpu.max, cpu.max.burst and cpu.idle only mention the fair-class scheduler, even though sched_ext implements the cgroup_set_bandwidth (notified with the period/quota from cpu.max and the burst from cpu.max.burst) and cgroup_set_idle callbacks from these interfaces. Mirror the cpu.weight wording for the three entries and generalize the category preamble to refer to the corresponding cgroup_set_* callback so it keeps covering the entries below. Suggested-by: Tejun Heo Signed-off-by: Liang Luo Signed-off-by: Tejun Heo --- Documentation/admin-guide/cgroup-v2.rst | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst index aed195a71cbf..3dc6889ebdb2 100644 --- a/Documentation/admin-guide/cgroup-v2.rst +++ b/Documentation/admin-guide/cgroup-v2.rst @@ -1130,9 +1130,9 @@ policy and the underlying scheduler. From the point of view of the cpu controlle processes can be categorized as follows: * Processes under the fair-class scheduler -* Processes under a BPF scheduler with the ``cgroup_set_weight`` callback +* Processes under a BPF scheduler with the corresponding ``cgroup_set_*`` callback * Everything else: ``SCHED_{FIFO,RR,DEADLINE}`` and processes under a BPF scheduler - without the ``cgroup_set_weight`` callback + without the corresponding ``cgroup_set_*`` callback For details on when a process is under the fair-class scheduler or a BPF scheduler, check out :ref:`Documentation/scheduler/sched-ext.rst `. @@ -1202,7 +1202,9 @@ will be referred to. All time durations are in microseconds. $PERIOD duration. "max" for $MAX indicates no limit. If only one number is written, $MAX is updated. - This file affects only processes under the fair-class scheduler. + This file affects only processes under the fair-class scheduler and a BPF + scheduler with the ``cgroup_set_bandwidth`` callback depending on what + the callback actually does. cpu.max.burst A read-write single value file which exists on non-root @@ -1210,7 +1212,9 @@ will be referred to. All time durations are in microseconds. The burst in the range [0, $MAX]. - This file affects only processes under the fair-class scheduler. + This file affects only processes under the fair-class scheduler and a BPF + scheduler with the ``cgroup_set_bandwidth`` callback depending on what + the callback actually does. cpu.pressure A read-write nested-keyed file. @@ -1262,7 +1266,9 @@ will be referred to. All time durations are in microseconds. own relative priorities, but the cgroup itself will be treated as very low priority relative to its peers. - This file affects only processes under the fair-class scheduler. + This file affects only processes under the fair-class scheduler and a BPF + scheduler with the ``cgroup_set_idle`` callback depending on what the + callback actually does. Memory ------ From a8c6daab4b0e276508b7ffdd66c60fd3020a9178 Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Thu, 20 Aug 2026 15:09:44 +0800 Subject: [PATCH 0054/2366] selftests/cgroup: Fix cg_run_in_subcgroups ignoring arg parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cg_run_in_subcgroups() discards its arg and always passes NULL to cg_run(), turning the (void *)100 from test_kmem_dead_cgroups() into NULL so no allocation occurs. This makes test_kmem_dead_cgroups() falsely pass without exercising the "dying cgroup with charged slab" scenario it intends to test. Pass the arg through to cg_run() to fix this. Fixes: 933dc80ec262 ("kselftests: cgroup: add kernel memory accounting tests") Signed-off-by: Hongfu Li Reviewed-by: Michal Koutný Signed-off-by: Tejun Heo --- tools/testing/selftests/cgroup/test_kmem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/cgroup/test_kmem.c b/tools/testing/selftests/cgroup/test_kmem.c index cb47561b4b44..437f2d35f205 100644 --- a/tools/testing/selftests/cgroup/test_kmem.c +++ b/tools/testing/selftests/cgroup/test_kmem.c @@ -145,7 +145,7 @@ static int cg_run_in_subcgroups(const char *parent, return -1; } - if (cg_run(child, fn, NULL)) { + if (cg_run(child, fn, arg)) { cg_destroy(child); free(child); return -1; From 150aeba624e8b7cac51c39440d7e8e1fd11de9a0 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Wed, 19 Aug 2026 20:58:29 +0800 Subject: [PATCH 0055/2366] bpf: Fix REG INVARIANTS VIOLATION on speculative pointer arithmetic Take the following unprivileged program as an example: r0 = bpf_map_lookup_elem(...) /* PTR_TO_MAP_VALUE, offset 0 */ ... 14: r0 += r1 /* r1 is a bounded scalar */ 15: r9 = r0 Loading it triggers a verifier warning from reg_bounds_sanity_check(): verifier bug: REG INVARIANTS VIOLATION (alu): const subreg tnum out of sync with range bounds r64={.base=0x0, .size=0x0} r32={.base=0x0, .size=0xffffffff} var_off=(0x0, 0x0) What happens: 1. Processing insn 14 (r0 += r1) in adjust_ptr_min_max_vals(), the new offset is computed into dst_reg's var_off and 32/64-bit ranges. 2. Because pointer registers do not track 32-bit subregister bounds, __mark_reg32_unbounded() first sets r32 to the full range; r32 is re-derived from the offset at the end of the function by reg_bounds_sync(). 3. On the unprivileged path, sanitize_ptr_alu() is called and, via sanitize_speculative_path() -> push_stack(), snapshots the current register state and schedules the next instruction (insn 15) to be verified directly as a speculative path. 4. That snapshot is taken between step 2 and the final reg_bounds_sync(): at this point dst_reg's var_off still holds the (const) original offset while r32 has just been blanked to the full range, i.e. the two are out of sync. When the speculative path later verifies insn 15 (r9 = r0), the inconsistent state reaches reg_bounds_sanity_check() and trips the warning. var_off and the 32-bit range must always be consistent. There are two ways to keep the snapshot consistent: 1. sync var_off and r32 before the snapshot so they match, or 2. leave r32 at its original (already consistent) value and blank it only after the snapshot. The whole point of sanitize_ptr_alu() is to insert a harmless masking sequence that keeps the access in bounds under speculation, so the state it snapshots should faithfully represent that. Take approach 2: move __mark_reg32_unbounded() to after sanitize_ptr_alu(), so the speculative snapshot keeps the pointer's original, consistent r32. The non-speculative path is unchanged: r32 is still blanked before the offset is applied and re-derived by reg_bounds_sync(). Fixes: 5f99f312bd3b ("bpf: add register bounds sanity checks and sanitization") Reported-by: Hiker Cl Closes: https://lore.kernel.org/bpf/CAGM=xGB1fJ9kT8XTitVo74B0WGqgjkoUHdLwzytwV0AyqeVApw@mail.gmail.com/ Signed-off-by: Jiayuan Chen Acked-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260819125840.286434-1-jiayuan.chen@linux.dev Signed-off-by: Kumar Kartikeya Dwivedi --- kernel/bpf/verifier.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index e421ea2b80c3..5e37ca75e5c4 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -14560,9 +14560,6 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn return -EINVAL; } - /* pointer types do not carry 32-bit bounds at the moment. */ - __mark_reg32_unbounded(dst_reg); - if (sanitize_needed(opcode)) { ret = sanitize_ptr_alu(env, insn, ptr_reg, off_reg, dst_reg, &info, false); @@ -14570,6 +14567,14 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn return sanitize_err(env, insn, ret); } + /* + * Pointer types do not carry 32-bit bounds at the moment. Blank r32 + * only after sanitize_ptr_alu() may have snapshotted dst_reg into a + * speculative path: otherwise reg_bounds_sanity_check() might hit some + * constraints violations. + */ + __mark_reg32_unbounded(dst_reg); + switch (opcode) { case BPF_ADD: /* From 7ee2f20bf20ed59fb269a260c4c4aff1e67f1b7c Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Wed, 19 Aug 2026 20:58:30 +0800 Subject: [PATCH 0056/2366] selftests/bpf: Add reg-invariants test for speculative pointer arithmetic An unprivileged socket filter does variable pointer arithmetic on a PTR_TO_MAP_VALUE whose offset collapses to a constant. The Spectre-v1 speculative path used to snapshot the pointer with a const offset and an unbounded r32, which tripped reg_bounds_sanity_check() on the following register move. Mark the test __success_unpriv (the speculative path only runs unprivileged) and flag it BPF_F_TEST_REG_INVARIANTS so the invariant violation becomes a hard load failure. The unprivileged run fails without the verifier fix and passes with it: verifier_bounds/spec_ptr_alu_const_offset @unpriv:FAIL # without fix verifier_bounds/spec_ptr_alu_const_offset @unpriv:OK # with fix Signed-off-by: Jiayuan Chen Tested-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260819125840.286434-2-jiayuan.chen@linux.dev Signed-off-by: Kumar Kartikeya Dwivedi --- .../selftests/bpf/progs/verifier_bounds.c | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_bounds.c b/tools/testing/selftests/bpf/progs/verifier_bounds.c index 1a273e416fed..df8d5309657e 100644 --- a/tools/testing/selftests/bpf/progs/verifier_bounds.c +++ b/tools/testing/selftests/bpf/progs/verifier_bounds.c @@ -2267,6 +2267,47 @@ __naked void deduce64_from_32_wrapping_32bit(void) : __clobber_all); } +/* + * Unprivileged variable pointer arithmetic on a PTR_TO_MAP_VALUE whose + * offset collapses to a constant. The Spectre-v1 speculative path snapshots + * the pointer while its r32 has just been blanked but its offset not yet + * synced; the following register move used to trip reg_bounds_sanity_check() + * ("const subreg tnum out of sync with range bounds"). With + * BPF_F_TEST_REG_INVARIANTS that violation turns into a load failure, so the + * unprivileged program must still load. + */ +SEC("socket") +__success __success_unpriv +__flag(BPF_F_TEST_REG_INVARIANTS) +__naked void spec_ptr_alu_const_offset(void) +{ + asm volatile (" \ + call %[bpf_ktime_get_ns]; \ + *(u64*)(r10 - 16) = r0; \ + r1 = 0; \ + *(u64*)(r10 - 8) = r1; \ + r2 = r10; \ + r2 += -8; \ + r1 = %[map_hash_8b] ll; \ + call %[bpf_map_lookup_elem]; \ + if r0 == 0 goto l0_%=; \ + r1 = *(u64*)(r10 - 16); \ + r2 = 0x40000000; \ + if r1 > r2 goto l0_%=; \ + if r1 s> 1 goto l0_%=; /* r1 in [0, 1] */ \ + r0 += r1; /* ptr += bounded scalar */ \ + r9 = r0; /* used to trip the warning */ \ + *(u8*)(r0 + 0) = r1; \ +l0_%=: r0 = 0; \ + exit; \ + " + : + : __imm(bpf_ktime_get_ns), + __imm(bpf_map_lookup_elem), + __imm_addr(map_hash_8b) + : __clobber_all); +} + /* Check that range_within() compares cnum ranges, not min/max projections. */ SEC("socket") __failure __msg("div by zero") From efebf6496685c93150df5bb0794363ae70c5f58a Mon Sep 17 00:00:00 2001 From: Hui Su Date: Fri, 7 Aug 2026 01:56:00 +0800 Subject: [PATCH 0057/2366] bpf: Fix infinite loop in pcpu_freelist push with one possible CPU __pcpu_freelist_push() can loop forever when only one CPU is possible and an NMI re-enters pcpu_freelist_push() while the interrupted context holds that CPU's freelist lock. After the current-CPU fast path fails, the fallback loop walks cpu_possible_mask while skipping the current CPU. With CONFIG_SMP=n, or when an SMP kernel is limited to one possible CPU with nr_cpus=1 or possible_cpus=1, there are no other possible CPUs to examine. The loop therefore makes no lock acquisition attempt and can never make progress. The following stack was observed on a UP system: NMI context: pcpu_freelist_push free_htab_elem htab_map_delete_elem [perf-event BPF program] __perf_event_overflow perf_event_nmi_handler exc_nmi Interrupted context: __pcpu_freelist_push pcpu_freelist_push free_htab_elem htab_map_delete_elem [raw_tp/sys_enter BPF program] __bpf_trace_sys_enter do_syscall_64 raw_res_spin_lock() detects the same-CPU recursive acquisition and returns -EDEADLK, but the subsequent fallback loop has no candidate head on a system with one possible CPU. Restore the extra fallback head that existed before the rqspinlock conversion. Keep the current-CPU fast path, then try the other possible CPUs and finally the extra head. The additional head lets a push, which cannot fail without losing a preallocated element, make progress when the only per-CPU head is held by the interrupted context. Also check the extra head from the pop path so that nodes placed there can be reused. Fixes: f2ac0e5d1c4d ("bpf: Convert percpu_freelist.c to rqspinlock") Signed-off-by: Hui Su Cc: stable@vger.kernel.org Link: https://lore.kernel.org/bpf/20260806175600.1993595-1-sh_def@163.com Signed-off-by: Kumar Kartikeya Dwivedi --- kernel/bpf/percpu_freelist.c | 35 +++++++++++++++++++++++++++-------- kernel/bpf/percpu_freelist.h | 1 + 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/kernel/bpf/percpu_freelist.c b/kernel/bpf/percpu_freelist.c index 632762b57299..06ce588d13a3 100644 --- a/kernel/bpf/percpu_freelist.c +++ b/kernel/bpf/percpu_freelist.c @@ -17,6 +17,8 @@ int pcpu_freelist_init(struct pcpu_freelist *s) raw_res_spin_lock_init(&head->lock); head->first = NULL; } + raw_res_spin_lock_init(&s->extralist.lock); + s->extralist.first = NULL; return 0; } @@ -46,22 +48,28 @@ void __pcpu_freelist_push(struct pcpu_freelist *s, struct pcpu_freelist_node *node) { struct pcpu_freelist_head *head; - int cpu; + int cpu, this_cpu; if (___pcpu_freelist_push(this_cpu_ptr(s->freelist), node)) return; + this_cpu = raw_smp_processor_id(); while (true) { - for_each_cpu_wrap(cpu, cpu_possible_mask, raw_smp_processor_id()) { - if (cpu == raw_smp_processor_id()) + for_each_cpu_wrap(cpu, cpu_possible_mask, this_cpu) { + if (cpu == this_cpu) continue; + head = per_cpu_ptr(s->freelist, cpu); - if (raw_res_spin_lock(&head->lock)) - continue; - pcpu_freelist_push_node(head, node); - raw_res_spin_unlock(&head->lock); - return; + if (___pcpu_freelist_push(head, node)) + return; } + + /* + * Push cannot fail. Use the extra list when none of the + * per-CPU freelists can accept the node. + */ + if (___pcpu_freelist_push(&s->extralist, node)) + return; } } @@ -117,6 +125,17 @@ static struct pcpu_freelist_node *___pcpu_freelist_pop(struct pcpu_freelist *s) } raw_res_spin_unlock(&head->lock); } + + /* Per-CPU lists are empty or unavailable, try the extra list. */ + head = &s->extralist; + if (!READ_ONCE(head->first)) + return NULL; + if (raw_res_spin_lock(&head->lock)) + return NULL; + node = head->first; + if (node) + WRITE_ONCE(head->first, node->next); + raw_res_spin_unlock(&head->lock); return node; } diff --git a/kernel/bpf/percpu_freelist.h b/kernel/bpf/percpu_freelist.h index 914798b74967..980cf2884fd2 100644 --- a/kernel/bpf/percpu_freelist.h +++ b/kernel/bpf/percpu_freelist.h @@ -14,6 +14,7 @@ struct pcpu_freelist_head { struct pcpu_freelist { struct pcpu_freelist_head __percpu *freelist; + struct pcpu_freelist_head extralist; }; struct pcpu_freelist_node { From 511585987d27d8cb668acebd399fc4deda23404c Mon Sep 17 00:00:00 2001 From: Marek Czernohous Date: Thu, 13 Aug 2026 01:13:27 +0200 Subject: [PATCH 0058/2366] drm/nouveau: unsubscribe the channel-kill event before the fence context nouveau_channel_del() tears the fence context down first and only drops the channel-kill subscription later, in the middle of the nvif object teardown: if (chan->fence) nouveau_fence(chan->cli->drm)->context_del(chan); ... nvif_object_dtor(&chan->vram); nvif_event_dtor(&chan->kill); The subscribed handler is nouveau_channel_killed(), which calls nouveau_channel_kill() and from there nouveau_fence_context_kill() on chan->fence. A kill event delivered in that window takes fctx->lock and walks fctx->pending on a fence context that context_del() has already freed. Nothing reaches this below Fermi today, because the subscription is gated on FERMI_CHANNEL_GPFIFO and nothing kills a channel there. On Fermi and newer the window is real but narrow, since a kill has to land exactly while the channel is being destroyed. That is reason enough on its own, which is why this carries a Fixes: tag. The last patch in this series subscribes Tesla channels as well; nothing kills those today, so it does not widen the exposure now, but it is the groundwork for a recovery path that would, and the ordering is better fixed before that lands than alongside it. Drop the subscription before anything it depends on is torn down. Fixes: ea13e5abf807 ("drm/nouveau: signal pending fences when channel has been killed") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Marek Czernohous Fixes: ea13e5abf807 ("drm/nouveau: signal pending fences when channel has been killed") Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260812231330.705425-2-mczernohous@gmail.com --- drivers/gpu/drm/nouveau/nouveau_chan.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_chan.c b/drivers/gpu/drm/nouveau/nouveau_chan.c index 598513f60449..f142f6310596 100644 --- a/drivers/gpu/drm/nouveau/nouveau_chan.c +++ b/drivers/gpu/drm/nouveau/nouveau_chan.c @@ -90,6 +90,14 @@ nouveau_channel_del(struct nouveau_channel **pchan) { struct nouveau_channel *chan = *pchan; if (chan) { + /* + * Drop the kill-event subscription first. Its handler + * dereferences chan->fence, which the fence context teardown + * below frees, so leaving it armed across the teardown leaves + * a window for a use-after-free. + */ + nvif_event_dtor(&chan->kill); + if (chan->fence) nouveau_fence(chan->cli->drm)->context_del(chan); @@ -100,7 +108,6 @@ nouveau_channel_del(struct nouveau_channel **pchan) nvif_object_dtor(&chan->nvsw); nvif_object_dtor(&chan->gart); nvif_object_dtor(&chan->vram); - nvif_event_dtor(&chan->kill); nvif_object_dtor(&chan->user); nvif_mem_dtor(&chan->mem_userd); nouveau_vma_del(&chan->sema.vma); From 41a28c865d1d5843f8cb9e0af17a8f4d9e2961ff Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Fri, 31 Jul 2026 11:10:10 +0800 Subject: [PATCH 0059/2366] xfs: don't hold buffer locks across sync transaction commit in xfs_sync_sb_buf xfs_sync_sb_buf() holds sb/rtsb buffer locks across a synchronous xfs_trans_commit(), which flushes the CIL push workqueue internally. If shutdown occurs during the CIL push, xfs_buf_item_unpin() needs to lock these buffers to fail them, causing a deadlock: setlabel: holds buf lock -> flush_workqueue(xfs-cil) CIL push worker: xfs_buf_item_unpin -> xfs_buf_lock(same buf) Remove the xfs_trans_bhold() calls so that commit releases the buffer locks normally. After the sync commit, re-acquire the buffers via mp->m_sb_bp / mp->m_rtsb_bp for the on-disk writeback. Fixes: f7664b31975b ("xfs: implement online get/set fs label") Reported-by: syzbot+837bcd54843dd6262f2f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=837bcd54843dd6262f2f Cc: stable@vger.kernel.org Signed-off-by: Yun Zhou Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtgroup.h | 6 +++++- fs/xfs/libxfs/xfs_sb.c | 37 +++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtgroup.h b/fs/xfs/libxfs/xfs_rtgroup.h index c0b9f9f2c413..fca2eb74908c 100644 --- a/fs/xfs/libxfs/xfs_rtgroup.h +++ b/fs/xfs/libxfs/xfs_rtgroup.h @@ -359,7 +359,11 @@ static inline int xfs_initialize_rtgroups(struct xfs_mount *mp, # define xfs_rtgroup_unlock(rtg, gf) ((void)0) # define xfs_rtgroup_trans_join(tp, rtg, gf) ((void)0) # define xfs_update_rtsb(bp, sb_bp) ((void)0) -# define xfs_log_rtsb(tp, sb_bp) (NULL) +static inline struct xfs_buf *xfs_log_rtsb(struct xfs_trans *tp, + const struct xfs_buf *sb_bp) +{ + return NULL; +} # define xfs_rtgroup_get_geometry(rtg, rgeo) (-EOPNOTSUPP) #endif /* CONFIG_XFS_RT */ diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c index 75f2a021ee6d..f0341adbb879 100644 --- a/fs/xfs/libxfs/xfs_sb.c +++ b/fs/xfs/libxfs/xfs_sb.c @@ -1470,36 +1470,33 @@ xfs_sync_sb_buf( bool update_rtsb) { struct xfs_trans *tp; - struct xfs_buf *bp; - struct xfs_buf *rtsb_bp = NULL; int error; error = xfs_trans_alloc(mp, &M_RES(mp)->tr_sb, 0, 0, 0, &tp); if (error) return error; - bp = xfs_trans_getsb(tp); xfs_log_sb(tp); - xfs_trans_bhold(tp, bp); - if (update_rtsb) { - rtsb_bp = xfs_log_rtsb(tp, bp); - if (rtsb_bp) - xfs_trans_bhold(tp, rtsb_bp); - } + if (update_rtsb) + xfs_log_rtsb(tp, xfs_trans_getsb(tp)); xfs_trans_set_sync(tp); error = xfs_trans_commit(tp); if (error) - goto out; - /* - * write out the sb buffer to get the changes to disk - */ - error = xfs_bwrite(bp); - if (!error && rtsb_bp) - error = xfs_bwrite(rtsb_bp); -out: - if (rtsb_bp) - xfs_buf_relse(rtsb_bp); - xfs_buf_relse(bp); + return error; + + /* Re-acquire and write the sb and rtsb to disk. */ + xfs_buf_lock(mp->m_sb_bp); + error = xfs_bwrite(mp->m_sb_bp); + xfs_buf_unlock(mp->m_sb_bp); + if (error) + return error; + + if (update_rtsb && mp->m_rtsb_bp) { + xfs_buf_lock(mp->m_rtsb_bp); + error = xfs_bwrite(mp->m_rtsb_bp); + xfs_buf_unlock(mp->m_rtsb_bp); + } + return error; } From b9b541e70d465a8c9cadf697eec7cb15b6653e7d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:36:12 +0200 Subject: [PATCH 0060/2366] xfs: split an assert in xfs_trans_log_buf Split the "irst <= last && last < BBTOB(bp->b_length)" assert into two to make it clear which condition fired. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_trans_buf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_trans_buf.c b/fs/xfs/xfs_trans_buf.c index 1e025848811a..a5d25b703dfc 100644 --- a/fs/xfs/xfs_trans_buf.c +++ b/fs/xfs/xfs_trans_buf.c @@ -521,7 +521,8 @@ xfs_trans_log_buf( { struct xfs_buf_log_item *bip = bp->b_log_item; - ASSERT(first <= last && last < BBTOB(bp->b_length)); + ASSERT(first <= last); + ASSERT(last < BBTOB(bp->b_length)); ASSERT(!(bip->bli_flags & XFS_BLI_ORDERED)); xfs_trans_dirty_buf(tp, bp); From 7fc296b379edc0fef83890097af3c9537fbf4364 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 16:09:21 +0200 Subject: [PATCH 0061/2366] xfs: don't flush and invalidate internal RT device twice in xfs_shutdown_devices Check for an internal RT device to remove a bit of extra work. Fixes: bdc03eb5f98f ("xfs: allow internal RT devices for zoned mode") Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index 4b2eeb7783f7..b24db75eaedc 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -445,7 +445,7 @@ xfs_shutdown_devices( blkdev_issue_flush(mp->m_logdev_targp->bt_bdev); invalidate_bdev(mp->m_logdev_targp->bt_bdev); } - if (mp->m_rtdev_targp) { + if (mp->m_rtdev_targp && mp->m_rtdev_targp != mp->m_ddev_targp) { blkdev_issue_flush(mp->m_rtdev_targp->bt_bdev); invalidate_bdev(mp->m_rtdev_targp->bt_bdev); } From 750a361bfc8a8c8178872f2aecb7507a6fbf41a4 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Fri, 7 Aug 2026 06:58:51 -0400 Subject: [PATCH 0062/2366] xfs: remove kmem_to_page() kmem_to_page() has been unused since commit 5ced480d4886 ("xfs: simplify building the bio in xlog_write_iclog"), so remove it. This also removes the last instance of 'struct page' in fs/xfs/. Signed-off-by: Tal Zussman Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_platform.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/fs/xfs/xfs_platform.h b/fs/xfs/xfs_platform.h index 59a33c60e0ca..5d542e95fe44 100644 --- a/fs/xfs/xfs_platform.h +++ b/fs/xfs/xfs_platform.h @@ -289,15 +289,4 @@ int xfs_rw_bdev(struct block_device *bdev, sector_t sector, unsigned int count, # define PTR_FMT "%p" #endif -/* - * Helper for IO routines to grab backing pages from allocated kernel memory. - */ -static inline struct page * -kmem_to_page(void *addr) -{ - if (is_vmalloc_addr(addr)) - return vmalloc_to_page(addr); - return virt_to_page(addr); -} - #endif /* _XFS_PLATFORM_H */ From 4e07cd78e159a8c6b028e5dc5f4e5bf06067d969 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 10 Aug 2026 08:38:38 -0700 Subject: [PATCH 0063/2366] xfs: use inode_init_always_gfp with __GFP_NOFAIL in xfs_inode_alloc Just like the inode allocation itself, allocation of the security data inside of inode_init_always(_gfp) must not fail here as we can be inside an already dirty transaction context. Note that we do not have to pass GFP_NOFS explicitly as we are already in a nofs context when in a transaction, as seen by the call to alloc_inode_sb. Also update the comment about this a bit to be more clear. Fixes: bf904248a2ad ("[XFS] Combine the XFS and Linux inodes") Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_icache.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c index 9d8dd30bd927..a857b8aa255c 100644 --- a/fs/xfs/xfs_icache.c +++ b/fs/xfs/xfs_icache.c @@ -82,24 +82,20 @@ static inline xa_mark_t ici_tag_to_mark(unsigned int tag) /* * Allocate and initialise an xfs_inode. + * + * This can happen in context of already dirtied transactions, so the memory + * allocations must not fail. */ struct xfs_inode * xfs_inode_alloc( struct xfs_mount *mp, xfs_ino_t ino) { + gfp_t gfp = GFP_KERNEL | __GFP_NOFAIL; struct xfs_inode *ip; - /* - * XXX: If this didn't occur in transactions, we could drop GFP_NOFAIL - * and return NULL here on ENOMEM. - */ - ip = alloc_inode_sb(mp->m_super, xfs_inode_cache, GFP_KERNEL | __GFP_NOFAIL); - - if (inode_init_always(mp->m_super, VFS_I(ip))) { - kmem_cache_free(xfs_inode_cache, ip); - return NULL; - } + ip = alloc_inode_sb(mp->m_super, xfs_inode_cache, gfp); + inode_init_always_gfp(mp->m_super, VFS_I(ip), gfp); VFS_I(ip)->i_ino = ino; /* VFS doesn't initialise i_mode! */ From ae285611891f8d1a691771d14ecb5c9de3319abf Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 11 Aug 2026 10:48:37 -0600 Subject: [PATCH 0064/2366] xfs: handle NULL open_zone for merged ioends in xfs_ioend_put_open_zones In theory we could fail multiple ioends before an open zone was assigned to them, and the iomap code could merge them. Check for NULL not only for the main ioend but also all merged ones on ->io_list to handle this case. Fixes: 058dd70c65ab ("xfs: implement buffered writes to zoned RT devices") Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Hans Holmberg Reviewed-by: Damien Le Moal Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_aops.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 74a6089abadf..5a444ef04967 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -89,8 +89,10 @@ xfs_ioend_put_open_zones( /* * Put the open zone for all ioends merged into this one (if any). */ - list_for_each_entry(tmp, &ioend->io_list, io_list) - xfs_open_zone_put(tmp->io_private); + list_for_each_entry(tmp, &ioend->io_list, io_list) { + if (tmp->io_private) + xfs_open_zone_put(tmp->io_private); + } /* * The main ioend might not have an open zone if the submission failed From 2d829cc76777a5335269768d7caa750e102f74d2 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 11 Aug 2026 10:48:38 -0600 Subject: [PATCH 0065/2366] xfs: fix racy open zone caching When testing on very fast storage devices, I've observed writers using io_uring creating many open zones with just a few kiB written to it, which then don't get used. I tracked this down to multiple io_uring helper threads finding a full zone in i_private, and then going on to select a one, with the final one winning the race and leaving it in i_private. Fix this by dropping full zones from i_private as soon we find them, checking cached for a cached zoned when a single writes needs a new zone, and by keeping an existing cached zone in xfs_set_cached_zone when it still has space available, dropping the newly found/allocated one instead. This uses i_flags_lock as a low-level spinlock for short hold times to avoid interactions with the ilock, which is used for completions. Signed-off-by: Christoph Hellwig Reviewed-by: Hans Holmberg Reviewed-by: Darrick J. Wong Reviewed-by: Damien Le Moal Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_alloc.c | 72 ++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index 7d13fa7ab30a..bdbb60cc5d5b 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -793,17 +793,35 @@ xfs_get_cached_zone( rcu_read_lock(); oz = VFS_I(ip)->i_private; - if (oz) { - /* - * GC only steals open zones at mount time, so no GC zones - * should end up in the cache. - */ - ASSERT(!oz->oz_is_gc); - if (!atomic_inc_not_zero(&oz->oz_ref)) - oz = NULL; - } - rcu_read_unlock(); + if (!oz) + goto out_unlock; + /* + * GC only steals open zones at mount time, so no GC zones should end up + * in the cache. + */ + ASSERT(!oz->oz_is_gc); + + /* + * Drop the old cached open zone if it is full. + */ + if (oz->oz_allocated == rtg_blocks(oz->oz_rtg)) { + spin_lock(&ip->i_flags_lock); + oz = VFS_I(ip)->i_private; + if (oz && oz->oz_allocated == rtg_blocks(oz->oz_rtg)) { + VFS_I(ip)->i_private = NULL; + spin_unlock(&ip->i_flags_lock); + xfs_open_zone_put(oz); + oz = NULL; + goto out_unlock; + } + spin_unlock(&ip->i_flags_lock); + } + + if (!atomic_inc_not_zero(&oz->oz_ref)) + oz = NULL; +out_unlock: + rcu_read_unlock(); return oz; } @@ -818,18 +836,41 @@ xfs_get_cached_zone( * that were every written to, but significantly simplifies the cached zone * lookup. Because the open_zone is clearly marked as full when all data * in the underlying RTG was written, the caching is always safe. + * + * Called with a reference on @oz held. And returns two references on the + * returned zone: one for the caller and one for pinning the zone in + * inode->i_private. */ -static void +static struct xfs_open_zone * xfs_set_cached_zone( struct xfs_inode *ip, struct xfs_open_zone *oz) { struct xfs_open_zone *old_oz; + /* + * If the open zone cached in the inode still has free space, use that + * instead of the new open zone just selected. This can happen when + * multiple threads race to perform zone selection for an inode. + * io_uring worker threads seem to be good way to trigger this. + * + * We need to grab an extra reference to this open zone as the caller + * owns a reference in addition to the i_private pointer. + */ + spin_lock(&ip->i_flags_lock); + old_oz = VFS_I(ip)->i_private; + if (old_oz && old_oz->oz_allocated < rtg_blocks(old_oz->oz_rtg) && + atomic_inc_not_zero(&old_oz->oz_ref)) { + spin_unlock(&ip->i_flags_lock); + xfs_open_zone_put(oz); + return old_oz; + } + VFS_I(ip)->i_private = oz; atomic_inc(&oz->oz_ref); - old_oz = xchg(&VFS_I(ip)->i_private, oz); + spin_unlock(&ip->i_flags_lock); if (old_oz) xfs_open_zone_put(old_oz); + return oz; } static void @@ -873,14 +914,13 @@ xfs_zone_alloc_and_submit( * the inode is still associated with a zone and use that if so. */ if (!*oz) - *oz = xfs_get_cached_zone(ip); - - if (!*oz) { select_zone: + *oz = xfs_get_cached_zone(ip); + if (!*oz) { *oz = xfs_select_zone(mp, write_hint, pack_tight); if (!*oz) goto out_error; - xfs_set_cached_zone(ip, *oz); + *oz = xfs_set_cached_zone(ip, *oz); } alloc_len = xfs_zone_alloc_blocks(*oz, XFS_B_TO_FSB(mp, ioend->io_size), From 4bc67fc800edcaae8a657e4d2fba12e64e856b9f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 11 Aug 2026 10:48:39 -0600 Subject: [PATCH 0066/2366] xfs: fix zoned write iomap flags assignments Don't overwrite IOMAP_F_DIRTY with IOMAP_F_ANON_WRITE, but ensure both flags are set instead. Note that in practice this is harmless as all zoned writes force a metadata transaction anyway, but incorrectly assigned flags are still a landmine that will cause problems at some point. Fixes: 058dd70c65ab ("xfs: implement buffered writes to zoned RT devices") Fixes: 2e2383405824 ("xfs: implement direct writes to zoned RT devices") Cc: stable@vger.kernel.org # v6.15 Signed-off-by: Christoph Hellwig Reviewed-by: Andrey Albershteyn Reviewed-by: Darrick J. Wong Reviewed-by: Hans Holmberg Reviewed-by: Damien Le Moal Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_aops.c | 3 +-- fs/xfs/xfs_iomap.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 5a444ef04967..53b94af92ac5 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -634,11 +634,10 @@ xfs_zoned_map_blocks( xfs_iunlock(ip, XFS_ILOCK_EXCL); wpc->iomap.type = IOMAP_MAPPED; - wpc->iomap.flags = IOMAP_F_DIRTY; wpc->iomap.bdev = mp->m_rtdev_targp->bt_bdev; wpc->iomap.offset = offset; wpc->iomap.length = XFS_FSB_TO_B(mp, count_fsb); - wpc->iomap.flags = IOMAP_F_ANON_WRITE; + wpc->iomap.flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; trace_xfs_zoned_map_blocks(ip, offset, wpc->iomap.length); return 0; diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index 71c45be8c652..d8c3c2be6760 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -1084,11 +1084,10 @@ xfs_zoned_direct_write_iomap_begin( } iomap->type = IOMAP_MAPPED; - iomap->flags = IOMAP_F_DIRTY; iomap->bdev = ip->i_mount->m_rtdev_targp->bt_bdev; iomap->offset = offset; iomap->length = length; - iomap->flags = IOMAP_F_ANON_WRITE; + iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; return 0; } From 0510346e8e308d2e2cb057ea7b408758b5d6f6cd Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 11 Aug 2026 10:48:40 -0600 Subject: [PATCH 0067/2366] xfs: factor out a xfs_iomap_set_anon_write helper De-duplicate the iomap setup for zoned writes. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Hans Holmberg Reviewed-by: Damien Le Moal Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_aops.c | 8 ++------ fs/xfs/xfs_iomap.c | 6 +----- fs/xfs/xfs_iomap.h | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 53b94af92ac5..1dc51235982c 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -633,12 +633,8 @@ xfs_zoned_map_blocks( XFS_BMAPI_REMAP); xfs_iunlock(ip, XFS_ILOCK_EXCL); - wpc->iomap.type = IOMAP_MAPPED; - wpc->iomap.bdev = mp->m_rtdev_targp->bt_bdev; - wpc->iomap.offset = offset; - wpc->iomap.length = XFS_FSB_TO_B(mp, count_fsb); - wpc->iomap.flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; - + xfs_iomap_set_anon_write(ip, &wpc->iomap, offset, + XFS_FSB_TO_B(mp, count_fsb)); trace_xfs_zoned_map_blocks(ip, offset, wpc->iomap.length); return 0; } diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index d8c3c2be6760..7c6238fed61e 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -1083,11 +1083,7 @@ xfs_zoned_direct_write_iomap_begin( return error; } - iomap->type = IOMAP_MAPPED; - iomap->bdev = ip->i_mount->m_rtdev_targp->bt_bdev; - iomap->offset = offset; - iomap->length = length; - iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; + xfs_iomap_set_anon_write(ip, iomap, offset, length); return 0; } diff --git a/fs/xfs/xfs_iomap.h b/fs/xfs/xfs_iomap.h index cffcec532ea6..c906c62d46f3 100644 --- a/fs/xfs/xfs_iomap.h +++ b/fs/xfs/xfs_iomap.h @@ -29,6 +29,20 @@ int xfs_zero_range(struct xfs_inode *ip, loff_t pos, loff_t len, int xfs_truncate_page(struct xfs_inode *ip, loff_t pos, struct xfs_zone_alloc_ctx *ac, bool *did_zero); +static inline void +xfs_iomap_set_anon_write( + struct xfs_inode *ip, + struct iomap *iomap, + loff_t offset, + loff_t length) +{ + iomap->type = IOMAP_MAPPED; + iomap->bdev = ip->i_mount->m_rtdev_targp->bt_bdev; + iomap->offset = offset; + iomap->length = length; + iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; +} + static inline xfs_filblks_t xfs_aligned_fsb_count( xfs_fileoff_t offset_fsb, From 6b855256eb9e652caf8b14eb1f69b6eb00a9d9d1 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 11 Aug 2026 10:48:41 -0600 Subject: [PATCH 0068/2366] xfs: split ioend handling into a separate source file The ioend handling used to be only for buffered writeback, but has been extended to direct I/O and reads. Split it into a new source file. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Hans Holmberg Reviewed-by: Damien Le Moal Signed-off-by: Carlos Maiolino --- fs/xfs/Makefile | 1 + fs/xfs/xfs_aops.c | 181 +------------------------------------------- fs/xfs/xfs_aops.h | 1 - fs/xfs/xfs_file.c | 2 +- fs/xfs/xfs_ioend.c | 184 +++++++++++++++++++++++++++++++++++++++++++++ fs/xfs/xfs_ioend.h | 16 ++++ 6 files changed, 203 insertions(+), 182 deletions(-) create mode 100644 fs/xfs/xfs_ioend.c create mode 100644 fs/xfs/xfs_ioend.h diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile index 9f7133e02576..399a207f2d0e 100644 --- a/fs/xfs/Makefile +++ b/fs/xfs/Makefile @@ -91,6 +91,7 @@ xfs-y += xfs_aops.o \ xfs_healthmon.o \ xfs_icache.o \ xfs_ioctl.o \ + xfs_ioend.o \ xfs_iomap.o \ xfs_iops.o \ xfs_inode.o \ diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 1dc51235982c..8b6119776fb3 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -20,6 +20,7 @@ #include "xfs_errortag.h" #include "xfs_error.h" #include "xfs_icache.h" +#include "xfs_ioend.h" #include "xfs_zone_alloc.h" #include "xfs_rtgroup.h" #include @@ -36,15 +37,6 @@ XFS_WPC(struct iomap_writepage_ctx *ctx) return container_of(ctx, struct xfs_writepage_ctx, ctx); } -/* - * Fast and loose check if this write could update the on-disk inode size. - */ -static inline bool xfs_ioend_is_append(struct iomap_ioend *ioend) -{ - return ioend->io_offset + ioend->io_size > - XFS_I(ioend->io_inode)->i_disk_size; -} - /* * Update on-disk file size now that data has been written to disk. */ @@ -80,177 +72,6 @@ xfs_setfilesize( return xfs_trans_commit(tp); } -static void -xfs_ioend_put_open_zones( - struct iomap_ioend *ioend) -{ - struct iomap_ioend *tmp; - - /* - * Put the open zone for all ioends merged into this one (if any). - */ - list_for_each_entry(tmp, &ioend->io_list, io_list) { - if (tmp->io_private) - xfs_open_zone_put(tmp->io_private); - } - - /* - * The main ioend might not have an open zone if the submission failed - * before xfs_zone_alloc_and_submit got called. - */ - if (ioend->io_private) - xfs_open_zone_put(ioend->io_private); -} - -/* - * IO write completion. - */ -STATIC void -xfs_end_ioend_write( - struct iomap_ioend *ioend) -{ - struct xfs_inode *ip = XFS_I(ioend->io_inode); - struct xfs_mount *mp = ip->i_mount; - bool is_zoned = xfs_is_zoned_inode(ip); - xfs_off_t offset = ioend->io_offset; - size_t size = ioend->io_size; - unsigned int nofs_flag; - int error; - - /* - * We can allocate memory here while doing writeback on behalf of - * memory reclaim. To avoid memory allocation deadlocks set the - * task-wide nofs context for the following operations. - */ - nofs_flag = memalloc_nofs_save(); - - /* - * Just clean up the in-memory structures if the fs has been shut down. - */ - if (xfs_is_shutdown(mp)) { - error = -EIO; - goto done; - } - - /* - * Clean up all COW blocks and underlying data fork delalloc blocks on - * I/O error. The delalloc punch is required because this ioend was - * mapped to blocks in the COW fork and the associated pages are no - * longer dirty. If we don't remove delalloc blocks here, they become - * stale and can corrupt free space accounting on unmount. - */ - error = blk_status_to_errno(ioend->io_bio.bi_status); - if (unlikely(error)) { - /* - * Zoned writes update the in-core open zone accounting before - * I/O submission. A failed write leaves that state - * inconsistent, so shut down the filesystem instead of letting - * later writers wait forever for open zone space to become - * available. - */ - if (is_zoned) { - xfs_force_shutdown(mp, SHUTDOWN_META_IO_ERROR); - goto done; - } - if (ioend->io_flags & IOMAP_IOEND_SHARED) { - ASSERT(!is_zoned); - xfs_reflink_cancel_cow_range(ip, offset, size, true); - xfs_bmap_punch_delalloc_range(ip, XFS_DATA_FORK, offset, - offset + size, NULL); - } - goto done; - } - - /* - * Success: commit the COW or unwritten blocks if needed. - */ - if (is_zoned) - error = xfs_zoned_end_io(ip, offset, size, ioend->io_sector, - ioend->io_private, NULLFSBLOCK); - else if (ioend->io_flags & IOMAP_IOEND_SHARED) - error = xfs_reflink_end_cow(ip, offset, size); - else if (ioend->io_flags & IOMAP_IOEND_UNWRITTEN) - error = xfs_iomap_write_unwritten(ip, offset, size, false); - - if (!error && - !(ioend->io_flags & IOMAP_IOEND_DIRECT) && - xfs_ioend_is_append(ioend)) - error = xfs_setfilesize(ip, offset, size); -done: - if (is_zoned) - xfs_ioend_put_open_zones(ioend); - iomap_finish_ioends(ioend, error); - memalloc_nofs_restore(nofs_flag); -} - -/* - * Finish all pending IO completions that require transactional modifications. - * - * We try to merge physical and logically contiguous ioends before completion to - * minimise the number of transactions we need to perform during IO completion. - * Both unwritten extent conversion and COW remapping need to iterate and modify - * one physical extent at a time, so we gain nothing by merging physically - * discontiguous extents here. - * - * The ioend chain length that we can be processing here is largely unbound in - * length and we may have to perform significant amounts of work on each ioend - * to complete it. Hence we have to be careful about holding the CPU for too - * long in this loop. - */ -void -xfs_end_io( - struct work_struct *work) -{ - struct xfs_inode *ip = - container_of(work, struct xfs_inode, i_ioend_work); - struct iomap_ioend *ioend; - struct list_head tmp; - unsigned long flags; - - spin_lock_irqsave(&ip->i_ioend_lock, flags); - list_replace_init(&ip->i_ioend_list, &tmp); - spin_unlock_irqrestore(&ip->i_ioend_lock, flags); - - iomap_sort_ioends(&tmp); - while ((ioend = list_first_entry_or_null(&tmp, struct iomap_ioend, - io_list))) { - list_del_init(&ioend->io_list); - iomap_ioend_try_merge(ioend, &tmp); - if (bio_op(&ioend->io_bio) == REQ_OP_READ) - iomap_finish_ioends(ioend, - blk_status_to_errno(ioend->io_bio.bi_status)); - else - xfs_end_ioend_write(ioend); - cond_resched(); - } -} - -void -xfs_end_bio( - struct bio *bio) -{ - struct iomap_ioend *ioend = iomap_ioend_from_bio(bio); - struct xfs_inode *ip = XFS_I(ioend->io_inode); - struct xfs_mount *mp = ip->i_mount; - unsigned long flags; - - /* - * For Appends record the actually written block number and set the - * boundary flag if needed. - */ - if (IS_ENABLED(CONFIG_XFS_RT) && bio_is_zone_append(bio)) { - ioend->io_sector = bio->bi_iter.bi_sector; - xfs_mark_rtg_boundary(ioend); - } - - spin_lock_irqsave(&ip->i_ioend_lock, flags); - if (list_empty(&ip->i_ioend_list)) - WARN_ON_ONCE(!queue_work(mp->m_unwritten_workqueue, - &ip->i_ioend_work)); - list_add_tail(&ioend->io_list, &ip->i_ioend_list); - spin_unlock_irqrestore(&ip->i_ioend_lock, flags); -} - /* * We cannot cancel the ioend directly on error. We may have already set other * pages under writeback and hence we have to run I/O completion to mark the diff --git a/fs/xfs/xfs_aops.h b/fs/xfs/xfs_aops.h index 5a7a0f1a0b49..d5ae5c9d4c26 100644 --- a/fs/xfs/xfs_aops.h +++ b/fs/xfs/xfs_aops.h @@ -10,6 +10,5 @@ extern const struct address_space_operations xfs_address_space_operations; extern const struct address_space_operations xfs_dax_aops; int xfs_setfilesize(struct xfs_inode *ip, xfs_off_t offset, size_t size); -void xfs_end_bio(struct bio *bio); #endif /* __XFS_AOPS_H__ */ diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 7bff07e31cbd..426a67b813a7 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -25,7 +25,7 @@ #include "xfs_iomap.h" #include "xfs_reflink.h" #include "xfs_file.h" -#include "xfs_aops.h" +#include "xfs_ioend.h" #include "xfs_zone_alloc.h" #include "xfs_error.h" #include "xfs_errortag.h" diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c new file mode 100644 index 000000000000..40695d18dac0 --- /dev/null +++ b/fs/xfs/xfs_ioend.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2025 Christoph Hellwig. + * All Rights Reserved. + */ +#include "xfs_platform.h" +#include "xfs_shared.h" +#include "xfs_format.h" +#include "xfs_log_format.h" +#include "xfs_trans_resv.h" +#include "xfs_mount.h" +#include "xfs_inode.h" +#include "xfs_iomap.h" +#include "xfs_trace.h" +#include "xfs_bmap_util.h" +#include "xfs_reflink.h" +#include "xfs_zone_alloc.h" +#include "xfs_ioend.h" + +static void +xfs_ioend_put_open_zones( + struct iomap_ioend *ioend) +{ + struct iomap_ioend *tmp; + + /* + * Put the open zone for all ioends merged into this one (if any). + */ + list_for_each_entry(tmp, &ioend->io_list, io_list) + xfs_open_zone_put(tmp->io_private); + + /* + * The main ioend might not have an open zone if the submission failed + * before xfs_zone_alloc_and_submit got called. + */ + if (ioend->io_private) + xfs_open_zone_put(ioend->io_private); +} + +static void +xfs_end_ioend_write( + struct iomap_ioend *ioend) +{ + struct xfs_inode *ip = XFS_I(ioend->io_inode); + struct xfs_mount *mp = ip->i_mount; + bool is_zoned = xfs_is_zoned_inode(ip); + xfs_off_t offset = ioend->io_offset; + size_t size = ioend->io_size; + unsigned int nofs_flag; + int error; + + /* + * We can allocate memory here while doing writeback on behalf of + * memory reclaim. To avoid memory allocation deadlocks set the + * task-wide nofs context for the following operations. + */ + nofs_flag = memalloc_nofs_save(); + + /* + * Just clean up the in-memory structures if the fs has been shut down. + */ + if (xfs_is_shutdown(mp)) { + error = -EIO; + goto done; + } + + /* + * Clean up all COW blocks and underlying data fork delalloc blocks on + * I/O error. The delalloc punch is required because this ioend was + * mapped to blocks in the COW fork and the associated pages are no + * longer dirty. If we don't remove delalloc blocks here, they become + * stale and can corrupt free space accounting on unmount. + */ + error = blk_status_to_errno(ioend->io_bio.bi_status); + if (unlikely(error)) { + /* + * Zoned writes update the in-core open zone accounting before + * I/O submission. A failed write leaves that state + * inconsistent, so shut down the filesystem instead of letting + * later writers wait forever for open zone space to become + * available. + */ + if (is_zoned) { + xfs_force_shutdown(mp, SHUTDOWN_META_IO_ERROR); + goto done; + } + if (ioend->io_flags & IOMAP_IOEND_SHARED) { + ASSERT(!is_zoned); + xfs_reflink_cancel_cow_range(ip, offset, size, true); + xfs_bmap_punch_delalloc_range(ip, XFS_DATA_FORK, offset, + offset + size, NULL); + } + goto done; + } + + /* + * Success: commit the COW or unwritten blocks if needed. + */ + if (is_zoned) + error = xfs_zoned_end_io(ip, offset, size, ioend->io_sector, + ioend->io_private, NULLFSBLOCK); + else if (ioend->io_flags & IOMAP_IOEND_SHARED) + error = xfs_reflink_end_cow(ip, offset, size); + else if (ioend->io_flags & IOMAP_IOEND_UNWRITTEN) + error = xfs_iomap_write_unwritten(ip, offset, size, false); + + if (!error && + !(ioend->io_flags & IOMAP_IOEND_DIRECT) && + xfs_ioend_is_append(ioend)) + error = xfs_setfilesize(ip, offset, size); +done: + if (is_zoned) + xfs_ioend_put_open_zones(ioend); + iomap_finish_ioends(ioend, error); + memalloc_nofs_restore(nofs_flag); +} + +/* + * Finish all pending IO completions that require transactional modifications. + * + * We try to merge physical and logically contiguous ioends before completion to + * minimise the number of transactions we need to perform during IO completion. + * Both unwritten extent conversion and COW remapping need to iterate and modify + * one physical extent at a time, so we gain nothing by merging physically + * discontiguous extents here. + * + * The ioend chain length that we can be processing here is largely unbound in + * length and we may have to perform significant amounts of work on each ioend + * to complete it. Hence we have to be careful about holding the CPU for too + * long in this loop. + */ +void +xfs_end_io( + struct work_struct *work) +{ + struct xfs_inode *ip = + container_of(work, struct xfs_inode, i_ioend_work); + struct iomap_ioend *ioend; + struct list_head tmp; + unsigned long flags; + + spin_lock_irqsave(&ip->i_ioend_lock, flags); + list_replace_init(&ip->i_ioend_list, &tmp); + spin_unlock_irqrestore(&ip->i_ioend_lock, flags); + + iomap_sort_ioends(&tmp); + while ((ioend = list_first_entry_or_null(&tmp, struct iomap_ioend, + io_list))) { + list_del_init(&ioend->io_list); + iomap_ioend_try_merge(ioend, &tmp); + if (bio_op(&ioend->io_bio) == REQ_OP_READ) + iomap_finish_ioends(ioend, + blk_status_to_errno(ioend->io_bio.bi_status)); + else + xfs_end_ioend_write(ioend); + cond_resched(); + } +} + +void +xfs_end_bio( + struct bio *bio) +{ + struct iomap_ioend *ioend = iomap_ioend_from_bio(bio); + struct xfs_inode *ip = XFS_I(ioend->io_inode); + struct xfs_mount *mp = ip->i_mount; + unsigned long flags; + + /* + * For Appends record the actually written block number and set the + * boundary flag if needed. + */ + if (IS_ENABLED(CONFIG_XFS_RT) && bio_is_zone_append(bio)) { + ioend->io_sector = bio->bi_iter.bi_sector; + xfs_mark_rtg_boundary(ioend); + } + + spin_lock_irqsave(&ip->i_ioend_lock, flags); + if (list_empty(&ip->i_ioend_list)) + WARN_ON_ONCE(!queue_work(mp->m_unwritten_workqueue, + &ip->i_ioend_work)); + list_add_tail(&ioend->io_list, &ip->i_ioend_list); + spin_unlock_irqrestore(&ip->i_ioend_lock, flags); +} diff --git a/fs/xfs/xfs_ioend.h b/fs/xfs/xfs_ioend.h new file mode 100644 index 000000000000..525865767fca --- /dev/null +++ b/fs/xfs/xfs_ioend.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __XFS_IOEND_H +#define __XFS_IOEND_H + +/* + * Fast and loose check if this write could update the on-disk inode size. + */ +static inline bool xfs_ioend_is_append(struct iomap_ioend *ioend) +{ + return ioend->io_offset + ioend->io_size > + XFS_I(ioend->io_inode)->i_disk_size; +} + +void xfs_end_bio(struct bio *bio); + +#endif /* __XFS_IOEND_H */ From 885435535bb1d07746916d4c8832f95767bf2d7e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 13 Aug 2026 16:56:12 +0200 Subject: [PATCH 0069/2366] xfs: restore bi_bdev in xfs_zone_gc_write_chunk xfs_zone_gc_write_chunk relies on bi_bdev to still be valid, which is not true when XFS is used on top of a stacked block device. This can lead to misdirected GC writes, writing of plain text when using dm-crypt, or miscalculated I/O limits in xfs_zone_gc_split_write. Fix this by reassigning bi_bdev. Fixes: 080d01c41d44 ("xfs: implement zoned garbage collection") Signed-off-by: Christoph Hellwig Reviewed-by: Damien Le Moal Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_gc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c index d0b85179a3d2..5fdcf98a2133 100644 --- a/fs/xfs/xfs_zone_gc.c +++ b/fs/xfs/xfs_zone_gc.c @@ -869,6 +869,11 @@ xfs_zone_gc_write_chunk( WRITE_ONCE(chunk->state, XFS_GC_BIO_NEW); list_move_tail(&chunk->entry, &data->writing); + /* + * If we run on top of stacked block device, the read I/O might have + * reset bi_bdev, restore it to the one we want. + */ + bio_set_dev(&chunk->bio, mp->m_rtdev_targp->bt_bdev); bio_reuse(&chunk->bio, REQ_OP_WRITE); while ((split_chunk = xfs_zone_gc_split_write(data, chunk))) xfs_zone_gc_submit_write(data, split_chunk); From e2f62a9744ebad3bcb6347a648e615026e9efeff Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Tue, 4 Aug 2026 11:45:51 +0200 Subject: [PATCH 0070/2366] xfs: fix capability check in xfs An user reported a bug where he managed to evade group's quota by changing a file's gid to a different group id the same user belonged to, even though quotas were enforced on both gids and the file's size was big enough to exceed the quota's hardlimit. Commit eba0549bc7d1 replaced a capable() call by a has_capability_noaudit() to prevent unnecessary selinux audit messages. Turns out that both calls have slightly different semantics even though their documentation seems similar. Where in a nutshell: capable() - Tests the task's effective credentials has_ns_capability_noaudit() - Tests the task's real credentials This most of the time has no practical difference but in some cases like changing attrs (specifically group id in this case) through a NFS client this will allow the quota code to use XFS_QMOPT_FORCE_RES, effectively bypassing quota accounting checks. Using instead ns_capable_noaudit() should fix this issue and prevent selinux audit messages. This also fix the remaining calls to has_capability_noaudit() Fixes: eba0549bc7d1 ("xfs: don't generate selinux audit messages for capability testing") Cc: stable@vger.kernel.org # v5.18 Reported-by: Dr. Thomas Orgis Signed-off-by: Carlos Maiolino Reviewed-by: Darrick J. Wong Reviewed-by: Serge Hallyn Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_fsmap.c | 2 +- fs/xfs/xfs_ioctl.c | 2 +- fs/xfs/xfs_iops.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_fsmap.c b/fs/xfs/xfs_fsmap.c index b6a3bc9f143c..7c79fbe0a74c 100644 --- a/fs/xfs/xfs_fsmap.c +++ b/fs/xfs/xfs_fsmap.c @@ -1175,7 +1175,7 @@ xfs_getfsmap( return -EINVAL; use_rmap = xfs_has_rmapbt(mp) && - has_capability_noaudit(current, CAP_SYS_ADMIN); + ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN); head->fmh_entries = 0; /* Set up our device handlers. */ diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 1b53701bebea..1a8af827dde1 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -647,7 +647,7 @@ xfs_ioctl_setattr_get_trans( goto out_error; error = xfs_trans_alloc_ichange(ip, NULL, NULL, pdqp, - has_capability_noaudit(current, CAP_FOWNER), &tp); + ns_capable_noaudit(&init_user_ns, CAP_FOWNER), &tp); if (error) goto out_error; diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 4a3299abf774..36a22d4a8cc4 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -834,7 +834,7 @@ xfs_setattr_nonsize( } error = xfs_trans_alloc_ichange(ip, udqp, gdqp, NULL, - has_capability_noaudit(current, CAP_FOWNER), &tp); + ns_capable_noaudit(&init_user_ns, CAP_FOWNER), &tp); if (error) goto out_dqrele; From 1b91724d0bdc470ed8f353d1cc8d3e4123b51ed5 Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Tue, 4 Aug 2026 11:45:52 +0200 Subject: [PATCH 0071/2366] capability: Add new capable_noaudit In some situations (quota enforcement bypass in this case) we'd like to check for a specific capability without triggering spurious audit messages from security modules like selinux. Add a new helper so we don't need to use ns_capable_noaudit() directly. Signed-off-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Serge Hallyn Signed-off-by: Carlos Maiolino --- include/linux/capability.h | 5 +++++ kernel/capability.c | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/include/linux/capability.h b/include/linux/capability.h index 37db92b3d6f8..f8532d92fcad 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -145,6 +145,7 @@ extern bool has_capability_noaudit(struct task_struct *t, int cap); extern bool has_ns_capability_noaudit(struct task_struct *t, struct user_namespace *ns, int cap); extern bool capable(int cap); +bool capable_noaudit(int cap); extern bool ns_capable(struct user_namespace *ns, int cap); extern bool ns_capable_noaudit(struct user_namespace *ns, int cap); extern bool ns_capable_setid(struct user_namespace *ns, int cap); @@ -167,6 +168,10 @@ static inline bool capable(int cap) { return true; } +static inline bool capable_noaudit(int cap) +{ + return true; +} static inline bool ns_capable(struct user_namespace *ns, int cap) { return true; diff --git a/kernel/capability.c b/kernel/capability.c index 829f49ae07b9..f4a7f1963c9d 100644 --- a/kernel/capability.c +++ b/kernel/capability.c @@ -416,6 +416,24 @@ bool capable(int cap) return ns_capable(&init_user_ns, cap); } EXPORT_SYMBOL(capable); + +/** + * capable_noaudit - Determine if the current task has a superior + * capability in effect by checking the process's effective + * capabilities (unaudited). + * @cap: The capability to be tested for + * + * This is the same as capable(), except it uses CAP_OPT_NOAUDIT as to prevent + * issuing spurious audit messages. + * + * This sets PF_SUPERPRIV on the task if the capability is available on the + * assumption that it's about to be used. + */ +bool capable_noaudit(int cap) +{ + return ns_capable_noaudit(&init_user_ns, cap); +} +EXPORT_SYMBOL(capable_noaudit); #endif /* CONFIG_MULTIUSER */ /** From 4642259374fc9eb99af4cf8b2d54d54ccb0de08e Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Tue, 4 Aug 2026 11:45:53 +0200 Subject: [PATCH 0072/2366] quota: Don't issue audit messages on quota enforcing Calling capable() to determine if we can bypass quota enforcement or not can trigger spurious audit messages. We don't really require it here so just use the capable_noaudit() version. Signed-off-by: Carlos Maiolino Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Acked-by: Jan Kara Signed-off-by: Carlos Maiolino --- fs/quota/dquot.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 204afc5e984b..1c78c695d0dd 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -1240,7 +1240,7 @@ static int ignore_hardlimit(struct dquot *dquot) { struct mem_dqinfo *info = &sb_dqopt(dquot->dq_sb)->info[dquot->dq_id.type]; - return capable(CAP_SYS_RESOURCE) && + return capable_noaudit(CAP_SYS_RESOURCE) && (info->dqi_format->qf_fmt_id != QFMT_VFS_OLD || !(info->dqi_flags & DQF_ROOT_SQUASH)); } From be9c45bdb19461889b16c91c185a284d665ea72d Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Tue, 4 Aug 2026 11:45:54 +0200 Subject: [PATCH 0073/2366] xfs: replace ns_capable_noaudit Now that capable_noaudit() is available, we don't need to keep using ns_capable_noaudit() and specifying the usernamespace every single time. Signed-off-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_fsmap.c | 3 +-- fs/xfs/xfs_ioctl.c | 2 +- fs/xfs/xfs_iops.c | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_fsmap.c b/fs/xfs/xfs_fsmap.c index 7c79fbe0a74c..041bb2105ec6 100644 --- a/fs/xfs/xfs_fsmap.c +++ b/fs/xfs/xfs_fsmap.c @@ -1174,8 +1174,7 @@ xfs_getfsmap( if (!xfs_getfsmap_check_keys(&head->fmh_keys[0], &head->fmh_keys[1])) return -EINVAL; - use_rmap = xfs_has_rmapbt(mp) && - ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN); + use_rmap = xfs_has_rmapbt(mp) && capable_noaudit(CAP_SYS_ADMIN); head->fmh_entries = 0; /* Set up our device handlers. */ diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 1a8af827dde1..96ca3e480cb9 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -647,7 +647,7 @@ xfs_ioctl_setattr_get_trans( goto out_error; error = xfs_trans_alloc_ichange(ip, NULL, NULL, pdqp, - ns_capable_noaudit(&init_user_ns, CAP_FOWNER), &tp); + capable_noaudit(CAP_FOWNER), &tp); if (error) goto out_error; diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 36a22d4a8cc4..d1306e723899 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -834,7 +834,7 @@ xfs_setattr_nonsize( } error = xfs_trans_alloc_ichange(ip, udqp, gdqp, NULL, - ns_capable_noaudit(&init_user_ns, CAP_FOWNER), &tp); + capable_noaudit(CAP_FOWNER), &tp); if (error) goto out_dqrele; From 412f89fb3988a344175899776c8bc7073524ad84 Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Tue, 4 Aug 2026 11:45:55 +0200 Subject: [PATCH 0074/2366] capability: unexport has_capability_noaudit This has been originally exported to be used in xfs. Giving we are not using it anymore, unexport for consistency. Signed-off-by: Carlos Maiolino Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Reviewed-by: Serge Hallyn Signed-off-by: Carlos Maiolino --- kernel/capability.c | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/capability.c b/kernel/capability.c index f4a7f1963c9d..90e6ab62f6db 100644 --- a/kernel/capability.c +++ b/kernel/capability.c @@ -326,7 +326,6 @@ bool has_capability_noaudit(struct task_struct *t, int cap) { return has_ns_capability_noaudit(t, &init_user_ns, cap); } -EXPORT_SYMBOL(has_capability_noaudit); static bool ns_capable_common(struct user_namespace *ns, int cap, From 0ecd56573c1f272c72298154a3854380876dbb7c Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Tue, 4 Aug 2026 12:41:13 +0200 Subject: [PATCH 0075/2366] ext4: Avoid entering writeback paths during fastcommit replay Fastcommit replay effectively happens in nojournal mode. This results in ext4_mark_iloc_dirty() setting I_METADATA_WRITEBACK flag and as a result we end up entering filesystem writeback functions. However during fastcommit replay s_writepages_rwsem isn't initialized yet and hence we crash. Fix the problem by avoiding setting I_METADATA_WRITEBACK during fastcommit replay. Journal replay flushes the whole block device after replay anyway so all metadata is properly persisted and replay is faster this way as a bonus. Fixes: c26339e1df33 ("ext4: Fix data integrity writeout issues in nojournal mode") Reported-by: Venkat Rao Bagalkote Reported-by: Ojaswin Mujoo Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260804104112.93202-2-jack@suse.cz Tested-by: Venkat Rao Bagalkote Reviewed-by: Ojaswin Mujoo Signed-off-by: Christian Brauner (Amutable) --- fs/ext4/inode.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index bd4b778df9eb..26f0f9714f03 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -6456,9 +6456,10 @@ int ext4_chunk_trans_blocks(struct inode *inode, int nrblocks) int ext4_mark_iloc_dirty(handle_t *handle, struct inode *inode, struct ext4_iloc *iloc) { + struct super_block *sb = inode->i_sb; int err = 0; - err = ext4_emergency_state(inode->i_sb); + err = ext4_emergency_state(sb); if (unlikely(err)) { put_bh(iloc->bh); return err; @@ -6473,9 +6474,13 @@ int ext4_mark_iloc_dirty(handle_t *handle, put_bh(iloc->bh); /* * Mark that there's metadata writeout pending for the inode so that it - * gets properly flushed on fsync(2) and similar. + * gets properly flushed on fsync(2) and similar. We don't bother for + * fastcommit replay as that flushes the whole bdev afterwards anyway. + * It is faster this way and we avoid entering fs writeback paths which + * aren't fully initialized yet. */ - if (!EXT4_SB(inode->i_sb)->s_journal) { + if (!ext4_handle_valid(handle) && + !(EXT4_SB(sb)->s_mount_state & EXT4_FC_REPLAY)) { /* * Inode didn't need to go through dirtying, make sure it is * attached to wb so that writeback can handle it. From 82e9343260dfc6dda6349f285d9a5eac3e0738d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Fri, 14 Aug 2026 10:20:05 +0000 Subject: [PATCH 0076/2366] nsfs: keep namespace tree fields stable until after RCU grace period MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct ns_common overlays struct ns_tree with the rcu_head used by kfree_rcu(). That lets the deferred-free machinery overwrite ns_id and __ns_ref_active as soon as a namespace is queued for freeing, even though nsfs tree walkers may still read those fields under RCU after ns_tree_remove(). KASAN reports slab UAF. Keep the tree state and deferred-free callback storage separate. Namespace tree readers can then continue to validate and take references until the grace period has elapsed. Signed-off-by: Jérémy Jean Link: https://patch.msgid.link/20260814102005.1939777-1-Jeremy.Jean@oss.cyber.gouv.fr Signed-off-by: Christian Brauner (Amutable) --- include/linux/ns/ns_common_types.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/linux/ns/ns_common_types.h b/include/linux/ns/ns_common_types.h index ea45c54e4435..6ed6b497831c 100644 --- a/include/linux/ns/ns_common_types.h +++ b/include/linux/ns/ns_common_types.h @@ -116,10 +116,8 @@ struct ns_common { struct dentry *stashed; const struct proc_ns_operations *ops; unsigned int inum; - union { - struct ns_tree; - struct rcu_head ns_rcu; - }; + struct ns_tree; + struct rcu_head ns_rcu; }; #define to_ns_common(__ns) \ From 445fcd33c501be4be41806715f5b7ec80200f9f7 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Fri, 21 Aug 2026 11:29:51 +0200 Subject: [PATCH 0077/2366] HID: hyperv: fix build breakage with certain configs If CONFIG_HID_HYPERV is built-in (=y) while CONFIG_KUNIT is built as a module (=m), the linker fails to resolve kunit_mem_assert_format when creating vmlinux. Fix the dependencies in Kconfig. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608190536.d9qCkWWc-lkp@intel.com/ Fixes: 83df7b5fa6735b5084ecd2 ("HID: hyperv: add KUnit coverage for device info bounds") Acked-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index aa7fa11a0197..a81bf51cbcf1 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1253,7 +1253,7 @@ config HID_HYPERV_MOUSE config HID_HYPERV_MOUSE_KUNIT_TEST bool "KUnit tests for Hyper-V mouse driver" if !KUNIT_ALL_TESTS - depends on KUNIT && HID_HYPERV_MOUSE + depends on KUNIT && (HID_HYPERV_MOUSE = KUNIT || KUNIT = y) default KUNIT_ALL_TESTS help Builds unit tests for the Hyper-V synthetic HID driver. From d0ad81b2b5feea2e8b08a529c0e0d1fbaca98333 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Fri, 21 Aug 2026 15:39:15 +0200 Subject: [PATCH 0078/2366] HID: hyperv: make pointer arithmetics understandable for FORTIFY_SOURCE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 83df7b5fa6735b5084ecd2 ("HID: hyperv: add KUnit coverage for device info bounds") introduced this piece of code report = ((u8 *)&info->hid_descriptor) + info->hid_descriptor.bLength; memset(report, 0x42, 4); to populate the report, making use of the fact that the report &info->hid_descriptor points to a struct hid_descriptor (which is a fixed-size struct). GCC's FORTIFY_SOURCE infer the object size from that specific struct field rather than the outer dynamically allocated info buffer. As a result, writing past sizeof(struct hid_descriptor) triggers the __write_overflow_field warning. Calculate the pointer offset using info directly, so the compiler evaluates the memory bounds against the allocated flexible layout of struct synthhid_device_info instead of the nested struct. Fixes: 83df7b5fa6735b5084ecd2 ("HID: hyperv: add KUnit coverage for device info bounds") Reported-by: Jürgen Groß Tested-by: Jürgen Groß Acked-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-hyperv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c index 6579bd19da13..cfc061dbdd24 100644 --- a/drivers/hid/hid-hyperv.c +++ b/drivers/hid/hid-hyperv.c @@ -687,7 +687,7 @@ static void mousevsc_device_info_valid_descriptor(struct kunit *test) info->hid_descriptor.bLength = sizeof(struct hid_descriptor); info->hid_descriptor.rpt_desc.wDescriptorLength = cpu_to_le16(4); - report = ((u8 *)&info->hid_descriptor) + info->hid_descriptor.bLength; + report = (u8 *)(info + 1); memset(report, 0x42, 4); mousevsc_on_receive_device_info(input_dev, info, sizeof(*info) + 4); @@ -713,7 +713,7 @@ static void mousevsc_device_info_report_desc_oob(struct kunit *test) info->hid_descriptor.bLength = sizeof(struct hid_descriptor); info->hid_descriptor.rpt_desc.wDescriptorLength = cpu_to_le16(64); - report = ((u8 *)&info->hid_descriptor) + info->hid_descriptor.bLength; + report = (u8 *)(info + 1); memset(report, 0x42, 8); mousevsc_on_receive_device_info(input_dev, info, sizeof(*info) + 8); From ed54bf564ac52699cf4def3d0c2125d493e756f9 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Fri, 14 Aug 2026 00:09:00 +0800 Subject: [PATCH 0079/2366] bpf: Fix BPF_F_CPU validation for sparse CPU IDs BPF_F_CPU stores the target CPU ID in the upper 32 bits of the map operation flags. bpf_map_check_op_flags() currently compares that ID with num_possible_cpus(), which is the number of possible CPUs rather than a bound on CPU IDs. On an arm64 QEMU guest with a CPU device-tree hole, the possible CPU mask was 0,2-3. A userspace program using raw bpf() syscalls creates a BPF_MAP_TYPE_PERCPU_ARRAY and performs update and lookup operations for each CPU by setting BPF_F_CPU and the CPU ID in the flags. With the old check, CPU 1 is incorrectly accepted while valid CPU 3 is rejected with -ERANGE. The CPU 1 update then reaches the per-CPU map access path and triggers: Unable to handle kernel paging request at virtual address ... pc : __pi_memcpy_generic+0x5c/0x22c lr : bpf_percpu_array_update+0x2dc/0x2e8 Call trace: __pi_memcpy_generic bpf_map_update_value map_update_elem __sys_bpf Check the CPU ID against nr_cpu_ids and cpu_possible() instead. This rejects CPU IDs outside the valid range and CPUs absent from the possible mask, while allowing valid sparse CPU IDs. Fixes: 2b421662c788 ("bpf: Introduce BPF_F_CPU and BPF_F_ALL_CPUS flags") Signed-off-by: Hui Su Signed-off-by: Andrii Nakryiko Acked-by: Leon Hwang Link: https://lore.kernel.org/bpf/20260813160858.1042834-3-sh_def@163.com --- include/linux/bpf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index ffa5626411ac..b7dbf3d9b5c0 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -4209,7 +4209,7 @@ static inline int bpf_map_check_op_flags(struct bpf_map *map, u64 flags, u64 all return -EINVAL; cpu = flags >> 32; - if ((flags & BPF_F_CPU) && cpu >= num_possible_cpus()) + if ((flags & BPF_F_CPU) && (cpu >= nr_cpu_ids || !cpu_possible(cpu))) return -ERANGE; } From 75b0a6db4300e4c2c9e97a0848deaa7acfb42fb7 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Thu, 13 Aug 2026 23:51:33 +0800 Subject: [PATCH 0080/2366] bpf: Fix percpu map update indexing with sparse CPU IDs Per-CPU array, hash, and cgroup storage map updates without BPF_F_CPU or BPF_F_ALL_CPUS use a value buffer whose per-CPU slots are packed in possible-CPU order. The buffer is sized as: round_up(value_size, 8) * num_possible_cpus() The update paths iterate over possible CPUs, but use the logical CPU ID to calculate the source offset: value + size * cpu This only works when possible CPU IDs are contiguous starting at zero. For example, with a possible CPU mask of 0,2-3, the buffer contains three slots corresponding to CPUs 0, 2, and 3. CPU2 is therefore expected to use slot 1 and CPU3 slot 2. Instead, the current code uses slots 2 and 3 respectively, causing incorrect per-CPU values and an out-of-bounds read from the update buffer for CPU3. The corresponding lookup paths already use a dense offset while iterating over possible CPUs. Do the same for the array, hash, and cgroup storage update paths, advancing the source offset once for each possible CPU. BPF_F_ALL_CPUS continues to use the same value for every CPU. Fixes: 8eb76cb03f0f ("bpf: Add BPF_F_CPU and BPF_F_ALL_CPUS flags support for percpu_array maps") Fixes: c6936161fd55 ("bpf: Add BPF_F_CPU and BPF_F_ALL_CPUS flags support for percpu_hash and lru_percpu_hash maps") Fixes: 47c79f05aa0d ("bpf: Add BPF_F_CPU and BPF_F_ALL_CPUS flags support for percpu_cgroup_storage maps") Signed-off-by: Hui Su Signed-off-by: Andrii Nakryiko Acked-by: Leon Hwang Link: https://lore.kernel.org/bpf/20260813155131.1022745-3-sh_def@163.com --- kernel/bpf/arraymap.c | 5 +++-- kernel/bpf/hashtab.c | 5 +++-- kernel/bpf/local_storage.c | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c index ef315b168b29..0ce26b538075 100644 --- a/kernel/bpf/arraymap.c +++ b/kernel/bpf/arraymap.c @@ -436,7 +436,7 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value, void __percpu *pptr; void *ptr, *val; u32 size; - int cpu; + int cpu, off = 0; if (unlikely((map_flags & BPF_F_LOCK) || (u32)map_flags > BPF_F_ALL_CPUS)) /* unknown flags */ @@ -468,9 +468,10 @@ int bpf_percpu_array_update(struct bpf_map *map, void *key, void *value, } for_each_possible_cpu(cpu) { ptr = per_cpu_ptr(pptr, cpu); - val = (map_flags & BPF_F_ALL_CPUS) ? value : value + size * cpu; + val = (map_flags & BPF_F_ALL_CPUS) ? value : value + off; copy_map_value(map, ptr, val); bpf_obj_cancel_fields(map, ptr); + off += size; } unlock: rcu_read_unlock(); diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index d40cb5dd446c..d8db1cebc193 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -1025,7 +1025,7 @@ static void pcpu_copy_value(struct bpf_htab *htab, void __percpu *pptr, } else { u32 size = round_up(htab->map.value_size, 8); void *val; - int cpu; + int cpu, off = 0; if (map_flags & BPF_F_CPU) { cpu = map_flags >> 32; @@ -1037,9 +1037,10 @@ static void pcpu_copy_value(struct bpf_htab *htab, void __percpu *pptr, for_each_possible_cpu(cpu) { ptr = per_cpu_ptr(pptr, cpu); - val = (map_flags & BPF_F_ALL_CPUS) ? value : value + size * cpu; + val = (map_flags & BPF_F_ALL_CPUS) ? value : value + off; copy_map_value(&htab->map, ptr, val); bpf_obj_cancel_fields(&htab->map, ptr); + off += size; } } } diff --git a/kernel/bpf/local_storage.c b/kernel/bpf/local_storage.c index 23267213a17f..83cd527a2542 100644 --- a/kernel/bpf/local_storage.c +++ b/kernel/bpf/local_storage.c @@ -220,7 +220,7 @@ int bpf_percpu_cgroup_storage_update(struct bpf_map *_map, void *key, struct bpf_cgroup_storage *storage; void *val; u32 size; - int cpu; + int cpu, off = 0; if ((u32)map_flags & ~(BPF_ANY | BPF_EXIST | BPF_F_CPU | BPF_F_ALL_CPUS)) return -EINVAL; @@ -245,8 +245,9 @@ int bpf_percpu_cgroup_storage_update(struct bpf_map *_map, void *key, } size = round_up(_map->value_size, 8); for_each_possible_cpu(cpu) { - val = (map_flags & BPF_F_ALL_CPUS) ? value : value + size * cpu; + val = (map_flags & BPF_F_ALL_CPUS) ? value : value + off; copy_map_value(_map, per_cpu_ptr(storage->percpu_buf, cpu), val); + off += size; } unlock: rcu_read_unlock(); From e10b8b4931e10dbcce5b369583461d81c69187e8 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 18 Aug 2026 09:48:40 -1000 Subject: [PATCH 0081/2366] sched_ext: Sync tools autogen enum headers from the scx repo Regenerate enum_defs.autogen.h against the current tree, picking up the dispatch verdict enums and dropping the marker for the removed SCX_RQ_IN_BALANCE. Add enums_abi.autogen.h, a table of 64-bit scx enumerator values generated from vmlinux.h, used as the substitution source when the running kernel's BTF truncates 64-bit enum values to 32 bits. Signed-off-by: Tejun Heo --- .../sched_ext/include/scx/enum_defs.autogen.h | 5 +- .../sched_ext/include/scx/enums_abi.autogen.h | 223 ++++++++++++++++++ 2 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 tools/sched_ext/include/scx/enums_abi.autogen.h diff --git a/tools/sched_ext/include/scx/enum_defs.autogen.h b/tools/sched_ext/include/scx/enum_defs.autogen.h index 19aa1de3e700..63b6b14b19bd 100644 --- a/tools/sched_ext/include/scx/enum_defs.autogen.h +++ b/tools/sched_ext/include/scx/enum_defs.autogen.h @@ -56,6 +56,10 @@ #define HAVE_SCX_DEQ_SLEEP #define HAVE_SCX_DEQ_CORE_SCHED_EXEC #define HAVE_SCX_DEQ_SCHED_CHANGE +#define HAVE_SCX_DSP_NONE +#define HAVE_SCX_DSP_LOCAL +#define HAVE_SCX_DSP_PREV +#define HAVE_SCX_DSP_RETRY #define HAVE_SCX_DSQ_FLAG_BUILTIN #define HAVE_SCX_DSQ_FLAG_LOCAL_ON #define HAVE_SCX_DSQ_INVALID @@ -188,7 +192,6 @@ #define HAVE_SCX_RQ_SUB_IDLE_RENOTIFY #define HAVE_SCX_RQ_ROOT_IDLE_RENOTIFY #define HAVE_SCX_RQ_IN_WAKEUP -#define HAVE_SCX_RQ_IN_BALANCE #define HAVE_SCX_RQ_IN_DISPATCH #define HAVE_SCX_SCHED_PCPU_BYPASSING #define HAVE_SCX_SLICE_OOB_DUR_BITS diff --git a/tools/sched_ext/include/scx/enums_abi.autogen.h b/tools/sched_ext/include/scx/enums_abi.autogen.h new file mode 100644 index 000000000000..d53899764f5a --- /dev/null +++ b/tools/sched_ext/include/scx/enums_abi.autogen.h @@ -0,0 +1,223 @@ +/* + * WARNING: This file is autogenerated from gen_enum_defs.py [1]. + * + * scx enumerator values from the vmlinux.h this tree is built against. + * Used as the substitution source when the running kernel's BTF lacks + * BTF_KIND_ENUM64 encoding and 64-bit enum values are truncated. + * + * [1] https://github.com/sched-ext/scx/blob/main/scripts/gen_enum_defs.py + */ + +#ifndef __ENUMS_ABI_AUTOGEN_H__ +#define __ENUMS_ABI_AUTOGEN_H__ + +struct __scx_enum_abi_val { + const char *type; + const char *name; + u64 val; +}; + +static const struct __scx_enum_abi_val __scx_enum_abi_vals[] + __attribute__((unused)) = { + { "scx_arena_consts", "SCX_ARENA_MIN_ORDER", 0x3LLU }, + { "scx_arena_consts", "SCX_ARENA_GROW_PAGES", 0x4LLU }, + { "scx_cap_flags", "__SCX_CAP_ENQ_IMMED", 0x0LLU }, + { "scx_cap_flags", "__SCX_CAP_ENQ", 0x1LLU }, + { "scx_cap_flags", "__SCX_CAP_PREEMPT", 0x2LLU }, + { "scx_cap_flags", "__SCX_CAP_PERF", 0x3LLU }, + { "scx_cap_flags", "__SCX_NR_CAPS", 0x4LLU }, + { "scx_cap_flags", "__SCX_CAP_ALL", 0xfLLU }, + { "scx_cap_flags", "SCX_CAP_ENQ_IMMED", 0x1LLU }, + { "scx_cap_flags", "SCX_CAP_ENQ", 0x2LLU }, + { "scx_cap_flags", "SCX_CAP_PREEMPT", 0x4LLU }, + { "scx_cap_flags", "SCX_CAP_PERF", 0x8LLU }, + { "scx_cap_flags", "SCX_CAP_BASE", 0x1LLU }, + { "scx_cap_flags", "SCX_CAPS_REENQ_ON_LOSS", 0x3LLU }, + { "scx_cid_consts", "SCX_CID_SHARD_SIZE_DFL", 0x18LLU }, + { "scx_cid_consts", "SCX_CID_SHARD_MAX_CPUS", 0x200LLU }, + { "scx_consts", "SCX_DSP_DFL_MAX_BATCH", 0x20LLU }, + { "scx_consts", "SCX_DSP_MAX_LOOPS", 0x20LLU }, + { "scx_consts", "SCX_WATCHDOG_MAX_TIMEOUT", 0x7530LLU }, + { "scx_consts", "SCX_RESCUE_DFL_BW_PPT", 0x14LLU }, + { "scx_consts", "SCX_RESCUE_MAX_BW_PPT", 0xfaLLU }, + { "scx_consts", "SCX_RESCUE_DISABLE", 0xffffffffLLU }, + { "scx_consts", "SCX_RESCUE_DFL_QUANTUM_US", 0x1388LLU }, + { "scx_consts", "SCX_RESCUE_MIN_QUANTUM_US", 0x3e8LLU }, + { "scx_consts", "SCX_RESCUE_MAX_QUANTUM_US", 0x186a0LLU }, + { "scx_consts", "SCX_RESCUE_MIN_SLICE_US", 0x3e8LLU }, + { "scx_consts", "SCX_RESCUE_OVERLOAD_MULT", 0x10LLU }, + { "scx_consts", "SCX_RESCUE_MIN_OVERLOAD_MS", 0x3e8LLU }, + { "scx_consts", "SCX_RESCUE_MAX_OVERLOAD_MS", 0x3a98LLU }, + { "scx_consts", "SCX_TID_CHUNK", 0x400LLU }, + { "scx_consts", "SCX_EXIT_BT_LEN", 0x40LLU }, + { "scx_consts", "SCX_EXIT_MSG_LEN", 0x400LLU }, + { "scx_consts", "SCX_EXIT_DUMP_DFL_LEN", 0x8000LLU }, + { "scx_consts", "SCX_CPUPERF_ONE", 0x400LLU }, + { "scx_consts", "SCX_TASK_ITER_BATCH", 0x20LLU }, + { "scx_consts", "SCX_BYPASS_HOST_NTH", 0x2LLU }, + { "scx_consts", "SCX_BYPASS_LB_DFL_INTV_US", 0x7a120LLU }, + { "scx_consts", "SCX_BYPASS_LB_DONOR_PCT", 0x7dLLU }, + { "scx_consts", "SCX_BYPASS_LB_MIN_DELTA_DIV", 0x4LLU }, + { "scx_consts", "SCX_BYPASS_LB_BATCH", 0x100LLU }, + { "scx_consts", "SCX_REENQ_MAX_REPEAT", 0x100LLU }, + { "scx_consts", "SCX_SUB_MAX_DEPTH", 0x4LLU }, + { "scx_cpu_preempt_reason", "SCX_CPU_PREEMPT_RT", 0x0LLU }, + { "scx_cpu_preempt_reason", "SCX_CPU_PREEMPT_DL", 0x1LLU }, + { "scx_cpu_preempt_reason", "SCX_CPU_PREEMPT_STOP", 0x2LLU }, + { "scx_cpu_preempt_reason", "SCX_CPU_PREEMPT_UNKNOWN", 0x3LLU }, + { "scx_deq_flags", "SCX_DEQ_SLEEP", 0x1LLU }, + { "scx_deq_flags", "SCX_DEQ_CORE_SCHED_EXEC", 0x100000000LLU }, + { "scx_deq_flags", "SCX_DEQ_SCHED_CHANGE", 0x200000000LLU }, + { "scx_dsp_verdict", "SCX_DSP_NONE", 0x0LLU }, + { "scx_dsp_verdict", "SCX_DSP_LOCAL", 0x1LLU }, + { "scx_dsp_verdict", "SCX_DSP_PREV", 0x2LLU }, + { "scx_dsp_verdict", "SCX_DSP_RETRY", 0x3LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_FLAG_BUILTIN", 0x8000000000000000LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_FLAG_LOCAL_ON", 0x4000000000000000LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_INVALID", 0x8000000000000000LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_GLOBAL", 0x8000000000000001LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_LOCAL", 0x8000000000000002LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_BYPASS", 0x8000000000000003LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_REJECT", 0x8000000000000004LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_RESCUE", 0x8000000000000005LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_LOCAL_ON", 0xc000000000000000LLU }, + { "scx_dsq_id_flags", "SCX_DSQ_LOCAL_CPU_MASK", 0xffffffffLLU }, + { "scx_dsq_iter_flags", "SCX_DSQ_ITER_REV", 0x10000LLU }, + { "scx_dsq_iter_flags", "__SCX_DSQ_ITER_HAS_SLICE", 0x40000000LLU }, + { "scx_dsq_iter_flags", "__SCX_DSQ_ITER_HAS_VTIME", 0x80000000LLU }, + { "scx_dsq_iter_flags", "__SCX_DSQ_ITER_USER_FLAGS", 0x10000LLU }, + { "scx_dsq_iter_flags", "__SCX_DSQ_ITER_ALL_FLAGS", 0xc0010000LLU }, + { "scx_dsq_lnode_flags", "SCX_DSQ_LNODE_ITER_CURSOR", 0x1LLU }, + { "scx_dsq_lnode_flags", "__SCX_DSQ_LNODE_PRIV_SHIFT", 0x10LLU }, + { "scx_enable_state", "SCX_ENABLING", 0x0LLU }, + { "scx_enable_state", "SCX_ENABLED", 0x1LLU }, + { "scx_enable_state", "SCX_DISABLING", 0x2LLU }, + { "scx_enable_state", "SCX_DISABLED", 0x3LLU }, + { "scx_enq_flags", "SCX_ENQ_WAKEUP", 0x1LLU }, + { "scx_enq_flags", "SCX_ENQ_HEAD", 0x10000LLU }, + { "scx_enq_flags", "SCX_ENQ_CPU_SELECTED", 0x100000LLU }, + { "scx_enq_flags", "SCX_ENQ_PREEMPT", 0x100000000LLU }, + { "scx_enq_flags", "SCX_ENQ_IMMED", 0x200000000LLU }, + { "scx_enq_flags", "SCX_ENQ_RESCUE", 0x400000000LLU }, + { "scx_enq_flags", "SCX_ENQ_REENQ", 0x10000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_LAST", 0x20000000000LLU }, + { "scx_enq_flags", "__SCX_ENQ_INTERNAL_MASK", 0xff00000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_CLEAR_OPSS", 0x100000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_DSQ_PRIQ", 0x200000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_NESTED", 0x400000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_GDSQ_FALLBACK", 0x800000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_IGNORE_CAPS", 0x1000000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_APPLY_SLICE", 0x2000000000000000LLU }, + { "scx_enq_flags", "SCX_ENQ_SLICE_DFL", 0x4000000000000000LLU }, + { "scx_ent_dsq_flags", "SCX_TASK_DSQ_ON_PRIQ", 0x1LLU }, + { "scx_ent_flags", "SCX_TASK_QUEUED", 0x1LLU }, + { "scx_ent_flags", "SCX_TASK_IN_CUSTODY", 0x2LLU }, + { "scx_ent_flags", "SCX_TASK_RESET_RUNNABLE_AT", 0x4LLU }, + { "scx_ent_flags", "SCX_TASK_DEQD_FOR_SLEEP", 0x8LLU }, + { "scx_ent_flags", "SCX_TASK_SUB_INIT", 0x10LLU }, + { "scx_ent_flags", "SCX_TASK_IMMED", 0x20LLU }, + { "scx_ent_flags", "SCX_TASK_PROTECTED", 0x40LLU }, + { "scx_ent_flags", "SCX_TASK_STATE_SHIFT", 0x8LLU }, + { "scx_ent_flags", "SCX_TASK_STATE_BITS", 0x3LLU }, + { "scx_ent_flags", "SCX_TASK_STATE_MASK", 0x700LLU }, + { "scx_ent_flags", "SCX_TASK_NONE", 0x0LLU }, + { "scx_ent_flags", "SCX_TASK_INIT_BEGIN", 0x100LLU }, + { "scx_ent_flags", "SCX_TASK_INIT", 0x200LLU }, + { "scx_ent_flags", "SCX_TASK_READY", 0x300LLU }, + { "scx_ent_flags", "SCX_TASK_ENABLED", 0x400LLU }, + { "scx_ent_flags", "SCX_TASK_DEAD", 0x500LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_REASON_SHIFT", 0xcLLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_REASON_BITS", 0x3LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_REASON_MASK", 0x7000LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_NONE", 0x0LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_KFUNC", 0x1000LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_IMMED", 0x2000LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_PREEMPTED", 0x3000LLU }, + { "scx_ent_flags", "SCX_TASK_REENQ_CAP", 0x4000LLU }, + { "scx_ent_flags", "SCX_TASK_CURSOR", 0xffffffff80000000LLU }, + { "scx_exit_code", "SCX_ECODE_RSN_HOTPLUG", 0x100000000LLU }, + { "scx_exit_code", "SCX_ECODE_RSN_CGROUP_OFFLINE", 0x200000000LLU }, + { "scx_exit_code", "SCX_ECODE_ACT_RESTART", 0x1000000000000LLU }, + { "scx_exit_flags", "SCX_EFLAG_INITIALIZED", 0x1LLU }, + { "scx_exit_kind", "SCX_EXIT_NONE", 0x0LLU }, + { "scx_exit_kind", "SCX_EXIT_DONE", 0x1LLU }, + { "scx_exit_kind", "SCX_EXIT_UNREG", 0x40LLU }, + { "scx_exit_kind", "SCX_EXIT_UNREG_BPF", 0x41LLU }, + { "scx_exit_kind", "SCX_EXIT_UNREG_KERN", 0x42LLU }, + { "scx_exit_kind", "SCX_EXIT_SYSRQ", 0x43LLU }, + { "scx_exit_kind", "SCX_EXIT_PARENT", 0x44LLU }, + { "scx_exit_kind", "SCX_EXIT_PARENT_KILL", 0x45LLU }, + { "scx_exit_kind", "SCX_EXIT_ERROR", 0x400LLU }, + { "scx_exit_kind", "SCX_EXIT_ERROR_BPF", 0x401LLU }, + { "scx_exit_kind", "SCX_EXIT_ERROR_STALL", 0x402LLU }, + { "scx_exit_kind", "SCX_EXIT_ERROR_REENQ", 0x403LLU }, + { "scx_exit_kind", "SCX_EXIT_ERROR_RESCUE", 0x404LLU }, + { "scx_kf_allow_flags", "SCX_KF_ALLOW_UNLOCKED", 0x1LLU }, + { "scx_kf_allow_flags", "SCX_KF_ALLOW_INIT_CIDS", 0x2LLU }, + { "scx_kf_allow_flags", "SCX_KF_ALLOW_CPU_RELEASE", 0x4LLU }, + { "scx_kf_allow_flags", "SCX_KF_ALLOW_DISPATCH", 0x8LLU }, + { "scx_kf_allow_flags", "SCX_KF_ALLOW_ENQUEUE", 0x10LLU }, + { "scx_kf_allow_flags", "SCX_KF_ALLOW_SELECT_CPU", 0x20LLU }, + { "scx_kick_flags", "SCX_KICK_IDLE", 0x1LLU }, + { "scx_kick_flags", "SCX_KICK_PREEMPT", 0x2LLU }, + { "scx_kick_flags", "SCX_KICK_WAIT", 0x4LLU }, + { "scx_opi", "SCX_OPI_BEGIN", 0x0LLU }, + { "scx_opi", "SCX_OPI_NORMAL_BEGIN", 0x0LLU }, + { "scx_opi", "SCX_OPI_NORMAL_END", 0x21LLU }, + { "scx_opi", "SCX_OPI_CPU_HOTPLUG_BEGIN", 0x21LLU }, + { "scx_opi", "SCX_OPI_CPU_HOTPLUG_END", 0x23LLU }, + { "scx_opi", "SCX_OPI_END", 0x23LLU }, + { "scx_ops_flags", "SCX_OPS_KEEP_BUILTIN_IDLE", 0x1LLU }, + { "scx_ops_flags", "SCX_OPS_ENQ_LAST", 0x2LLU }, + { "scx_ops_flags", "SCX_OPS_ENQ_EXITING", 0x4LLU }, + { "scx_ops_flags", "SCX_OPS_SWITCH_PARTIAL", 0x8LLU }, + { "scx_ops_flags", "SCX_OPS_ENQ_MIGRATION_DISABLED", 0x10LLU }, + { "scx_ops_flags", "SCX_OPS_ALLOW_QUEUED_WAKEUP", 0x20LLU }, + { "scx_ops_flags", "SCX_OPS_BUILTIN_IDLE_PER_NODE", 0x40LLU }, + { "scx_ops_flags", "SCX_OPS_ALWAYS_ENQ_IMMED", 0x80LLU }, + { "scx_ops_flags", "SCX_OPS_TID_TO_TASK", 0x100LLU }, + { "scx_ops_flags", "SCX_OPS_ALL_FLAGS", 0x1ffLLU }, + { "scx_ops_flags", "__SCX_OPS_INTERNAL_MASK", 0xff00000000000000LLU }, + { "scx_ops_flags", "SCX_OPS_HAS_CPU_PREEMPT", 0x100000000000000LLU }, + { "scx_ops_state", "SCX_OPSS_NONE", 0x0LLU }, + { "scx_ops_state", "SCX_OPSS_QUEUEING", 0x1LLU }, + { "scx_ops_state", "SCX_OPSS_QUEUED", 0x2LLU }, + { "scx_ops_state", "SCX_OPSS_DISPATCHING", 0x3LLU }, + { "scx_ops_state", "SCX_OPSS_QSEQ_SHIFT", 0x2LLU }, + { "scx_pick_idle_cpu_flags", "SCX_PICK_IDLE_CORE", 0x1LLU }, + { "scx_pick_idle_cpu_flags", "SCX_PICK_IDLE_IN_NODE", 0x2LLU }, + { "scx_public_consts", "SCX_OPS_NAME_LEN", 0x80LLU }, + { "scx_public_consts", "SCX_SLICE_DFL", 0x1312d00LLU }, + { "scx_public_consts", "SCX_SLICE_BYPASS", 0x4c4b40LLU }, + { "scx_public_consts", "SCX_SLICE_INF", 0xffffffffffffffffLLU }, + { "scx_reenq_flags", "SCX_REENQ_ANY", 0x1LLU }, + { "scx_reenq_flags", "SCX_REENQ_CAP_REVOKE", 0x2LLU }, + { "scx_reenq_flags", "__SCX_REENQ_FILTER_MASK", 0xffffLLU }, + { "scx_reenq_flags", "__SCX_REENQ_USER_MASK", 0x1LLU }, + { "scx_reenq_flags", "SCX_REENQ_TSR_RQ_OPEN", 0x100000000LLU }, + { "scx_reenq_flags", "SCX_REENQ_TSR_NOT_FIRST", 0x200000000LLU }, + { "scx_reenq_flags", "__SCX_REENQ_TSR_MASK", 0xf00000000LLU }, + { "scx_rq_flags", "SCX_RQ_ONLINE", 0x1LLU }, + { "scx_rq_flags", "SCX_RQ_CAN_STOP_TICK", 0x2LLU }, + { "scx_rq_flags", "SCX_RQ_CLK_VALID", 0x20LLU }, + { "scx_rq_flags", "SCX_RQ_BAL_CB_PENDING", 0x40LLU }, + { "scx_rq_flags", "SCX_RQ_SUB_IDLE_RENOTIFY", 0x80LLU }, + { "scx_rq_flags", "SCX_RQ_ROOT_IDLE_RENOTIFY", 0x100LLU }, + { "scx_rq_flags", "SCX_RQ_IN_WAKEUP", 0x10000LLU }, + { "scx_rq_flags", "SCX_RQ_IN_DISPATCH", 0x20000LLU }, + { "scx_sched_pcpu_flags", "SCX_SCHED_PCPU_BYPASSING", 0x1LLU }, + { "scx_slice_oob_consts", "SCX_SLICE_OOB_DUR_BITS", 0x2bLLU }, + { "scx_slice_oob_consts", "SCX_SLICE_OOB_ID_BITS", 0x14LLU }, + { "scx_slice_oob_consts", "SCX_SLICE_OOB_DUR_MASK", 0x7ffffffffffLLU }, + { "scx_slice_oob_consts", "SCX_SLICE_OOB_ID_SHIFT", 0x2bLLU }, + { "scx_slice_oob_consts", "SCX_SLICE_OOB_ID_MASK", 0xfffffLLU }, + { "scx_slice_oob_consts", "SCX_SLICE_OOB_PENDING", 0x8000000000000000LLU }, + { "scx_tg_flags", "SCX_TG_ONLINE", 0x1LLU }, + { "scx_tg_flags", "SCX_TG_INITED", 0x2LLU }, + { "scx_tg_flags", "SCX_TG_SUB_INIT", 0x4LLU }, + { "scx_wake_flags", "SCX_WAKE_FORK", 0x4LLU }, + { "scx_wake_flags", "SCX_WAKE_TTWU", 0x8LLU }, + { "scx_wake_flags", "SCX_WAKE_SYNC", 0x10LLU }, +}; + +#endif /* __ENUMS_ABI_AUTOGEN_H__ */ From 9e8581a090c02ffa35e8439b90b024956a735de9 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 18 Aug 2026 09:48:40 -1000 Subject: [PATCH 0082/2366] sched_ext: Sync common and compat headers from the scx repo Sync common.bpf.h, compat.bpf.h and compat.h with the scx repo, which accumulated the following: - __COMPAT_read_enum() can now recover 64-bit scx enum values from kernel BTF generated without BTF_KIND_ENUM64 support (pahole < 1.24 or --skip_encoding_btf_enum64, e.g. COS/GKE kernels), substituting values from the build-time vmlinux.h cross-checked against the low 32 bits the kernel does provide. - is_migration_disabled() no longer assumes the BPF prolog always disables migration. Since 8e4f0b1ebcf2 ("bpf: use rcu_read_lock_dont_migrate() for trampoline.c") the prolog only does so under CONFIG_PREEMPT_RCU, so the old current-task test under-reported on v6.18+ !PREEMPT_RCU kernels. A runtime probe on bpf_scx_reg() handles older kernels with backported trampoline behavior. - __COMPAT_scx_bpf_dsq_peek() is gated behind kernel v7.1 where 2f2ea7709266 ("sched_ext: Use dsq->first_task instead of list_empty() in dispatch_enqueue() FIFO-tail") fixed the kfunc spuriously returning NULL on non-empty FIFO DSQs, and the new scx_bpf_reenqueue_local_from_anywhere() provides a callable-from-anywhere reenqueue which prefers the generic scx_bpf_dsq_reenq(). Both were first posted by Gavin Guo and Changwoo Min and are picked up here with the review feedback folded in. - __COMPAT_scx_bpf_cpu_curr() and the scx_bpf_cpu_rq() declaration are restored. Schedulers built from these headers still run on pre-v6.18 kernels where scx_bpf_cpu_curr() does not resolve and the scx_bpf_cpu_rq() fallback still exists. - scx_clock_task() and scx_clock_pelt() document their stale-read behavior for remote idle CPUs under NO_HZ_IDLE. Link: https://lore.kernel.org/all/20260817143126.562923-1-changwoo@igalia.com Signed-off-by: Tejun Heo --- tools/sched_ext/include/scx/common.bpf.h | 140 +++++++++++++++++++---- tools/sched_ext/include/scx/compat.bpf.h | 68 +++++++++-- tools/sched_ext/include/scx/compat.h | 97 ++++++++++++++++ 3 files changed, 274 insertions(+), 31 deletions(-) diff --git a/tools/sched_ext/include/scx/common.bpf.h b/tools/sched_ext/include/scx/common.bpf.h index 979d4cabfaf9..76f5e025e107 100644 --- a/tools/sched_ext/include/scx/common.bpf.h +++ b/tools/sched_ext/include/scx/common.bpf.h @@ -48,6 +48,7 @@ extern int LINUX_KERNEL_VERSION __kconfig; extern const char CONFIG_CC_VERSION_TEXT[64] __kconfig __weak; extern const char CONFIG_LOCALVERSION[64] __kconfig __weak; +extern bool CONFIG_PREEMPT_RCU __kconfig __weak; /* * Earlier versions of clang/pahole lost upper 32bits in 64bit enums which can @@ -97,6 +98,7 @@ s32 scx_bpf_pick_any_cpu_node(const cpumask_t *cpus_allowed, int node, u64 flags s32 scx_bpf_pick_any_cpu(const cpumask_t *cpus_allowed, u64 flags) __ksym; bool scx_bpf_task_running(const struct task_struct *p) __ksym; s32 scx_bpf_task_cpu(const struct task_struct *p) __ksym; +struct rq *scx_bpf_cpu_rq(s32 cpu) __ksym __weak; struct rq *scx_bpf_locked_rq(void) __ksym; struct task_struct *scx_bpf_cpu_curr(s32 cpu) __ksym __weak; struct task_struct *scx_bpf_tid_to_task(u64 tid) __ksym __weak; @@ -527,32 +529,103 @@ static __always_inline const struct cpumask *cast_mask(struct bpf_cpumask *mask) return (const struct cpumask *)mask; } +/* + * True if the non-sleepable BPF trampoline prolog (__bpf_prog_enter) calls + * migrate_disable() for the current task. Recorded once by + * scx_lib_init_probe, an fentry program on bpf_scx_reg() that fires during + * the natural scheduler-attach call chain (auto-attached by scx_ops_attach!). + * + * Defaults to true (conservative). Over-reporting in is_migration_disabled() + * causes local-only dispatch, which is safe. Under-reporting can crash the + * scheduler, so we err high if the probe somehow fails to run. + */ +bool __scx_prolog_disables_migration __weak = true; + +/* + * scx_lib_init_probe - non-sleepable prolog probe. + * + * Attached to bpf_scx_reg(), the .reg callback in bpf_sched_ext_ops + * (kernel/sched/ext.c). The kernel's struct_ops machinery invokes + * bpf_scx_reg when userspace creates the scheduler link, before + * ops.init() fires. Its address is taken in the vtable, so the symbol + * is non-inlinable and has been stable since introduction. + * + * Entering via fentry runs us through __bpf_prog_enter -- the + * non-sleepable prolog that consumers of is_migration_disabled() live + * under. + * + * Loud warning: the prolog adds at most 1 to migration_disabled. + * Reading > 1 means something upstream in the + * bpf_struct_ops_link_create -> bpf_scx_reg path disabled migration + * before the prolog ran, invalidating the probe; audit and adjust. + */ +SEC("fentry/bpf_scx_reg") __weak +int scx_lib_init_probe(void *ctx) +{ + if (bpf_core_field_exists(((struct task_struct *)0)->migration_disabled)) { + const struct task_struct *p = bpf_get_current_task_btf(); + unsigned int md = p->migration_disabled; + + if (md > 1) + bpf_printk("scx_lib_init_probe: unexpected migration_disabled=%u " + "upstream of BPF prolog; probe result unreliable", + md); + + __scx_prolog_disables_migration = md > 0; + } + return 0; +} + /* * Return true if task @p cannot migrate to a different CPU, false * otherwise. + * + * IMPORTANT: designed for NON-SLEEPABLE BPF contexts only. Sleepable + * contexts (BPF_STRUCT_OPS_SLEEPABLE, SEC("syscall"), + * SEC("fentry.s/...")) enter via __bpf_prog_enter_sleepable() or + * __bpf_prog_enter_sleepable_recur(), both of which unconditionally + * call migrate_disable(); this helper can yield a false negative for + * p == current there, which can crash the scheduler. */ static inline bool is_migration_disabled(const struct task_struct *p) { /* - * Testing p->migration_disabled in a BPF code is tricky because the - * migration is _always_ disabled while running the BPF code. - * The prolog (__bpf_prog_enter) and epilog (__bpf_prog_exit) for BPF - * code execution disable and re-enable the migration of the current - * task, respectively. So, the _current_ task of the sched_ext ops is - * always migration-disabled. Moreover, p->migration_disabled could be - * two or greater when a sched_ext ops BPF code (e.g., ops.tick) is - * executed in the middle of the other BPF code execution. + * Testing p->migration_disabled in BPF is tricky because the BPF prolog + * (__bpf_prog_enter) may call migrate_disable() for the current task, + * making migration_disabled == 1 even for tasks that are not truly + * migration-disabled. * - * Therefore, we should decide that the _current_ task is - * migration-disabled only when its migration_disabled count is greater - * than one. In other words, when p->migration_disabled == 1, there is - * an ambiguity, so we should check if @p is the current task or not. + * Since commit 8e4f0b1ebcf2 ("bpf: use rcu_read_lock_dont_migrate() for + * trampoline.c"), the BPF prolog calls migrate_disable() only when + * CONFIG_PREEMPT_RCU is enabled. Two fast paths cover the common cases: + * + * 1) CONFIG_PREEMPT_RCU: prolog always calls migrate_disable(), so + * migration_disabled == 1 for the current task is ambiguous. + * Disambiguate by checking p == current. + * + * 2) v6.18+ without CONFIG_PREEMPT_RCU: prolog never calls + * migrate_disable(), so migration_disabled == 1 is unambiguously + * a real migrate_disable() call. + * + * A slow path handles pre-v6.18 kernels without CONFIG_PREEMPT_RCU, + * where the prolog historically called migrate_disable() unconditionally + * but a cherry-picked downstream kernel may not. The runtime-probed flag + * __scx_prolog_disables_migration (set by scx_lib_init_probe) distinguishes + * the two cases without relying on the kernel version alone. */ if (bpf_core_field_exists(p->migration_disabled)) { - if (p->migration_disabled == 1) - return bpf_get_current_task_btf() != p; - else - return p->migration_disabled; + if (p->migration_disabled == 1) { + /* Fast path: prolog always disables migration */ + if (CONFIG_PREEMPT_RCU) + return bpf_get_current_task_btf() != p; + /* Fast path: prolog never disables migration */ + if (LINUX_KERNEL_VERSION >= KERNEL_VERSION(6, 18, 0)) + return true; + /* Slow path: pre-v6.18, !PREEMPT_RCU - use runtime flag */ + return __scx_prolog_disables_migration ? + bpf_get_current_task_btf() != p : true; + } + return p->migration_disabled; } return false; } @@ -1021,7 +1094,20 @@ static inline u64 scx_clock_task(u32 cpu) { struct rq___local *rq = get_current_rq(cpu); - /* Equivalent to the kernel's rq_clock_task(). */ + /* + * Equivalent to the kernel's rq_clock_task(): wall-clock time minus + * cumulative IRQ time (CONFIG_IRQ_TIME_ACCOUNTING) and hypervisor + * steal time (CONFIG_PARAVIRT_TIME_ACCOUNTING). Without those configs, + * it equals rq->clock. + * + * Conceptually this clock advances during idle (the idle task counts + * as a running task), but rq->clock_task is only updated on scheduling + * events. With NO_HZ_IDLE (the default), the periodic tick is stopped + * on idle CPUs, so rq->clock_task is not refreshed while a CPU is + * idle. Reading this clock for a remote idle CPU from a BPF timer + * callback returns the value from when the CPU last went idle, making + * the delta over an idle interval effectively zero. + */ return rq ? rq->clock_task : 0; } @@ -1032,9 +1118,23 @@ static inline u64 scx_clock_pelt(u32 cpu) /* * Equivalent to the kernel's rq_clock_pelt(): subtracts * lost_idle_time from clock_pelt to absorb the jump that occurs - * when clock_pelt resyncs with clock_task at idle exit. The result - * is a continuous, capacity-invariant clock safe for both task - * execution time stamping and cross-idle measurements. + * when clock_pelt resyncs with clock_task at idle exit. The intent + * is a continuous, capacity- and frequency-invariant clock that is + * frozen during idle, IRQ, and hypervisor steal. + * + * However, like scx_clock_task(), this clock has a stale-read issue + * for remote idle CPUs with NO_HZ_IDLE (the default). clock_pelt + * itself advances at wall-clock rate (hardware-clock based), but + * lost_idle_time is only updated via update_rq_clock_pelt(), which + * requires update_rq_clock() to be called. With NO_HZ_IDLE, the + * periodic tick is stopped on idle CPUs, so lost_idle_time is not + * refreshed during idle. Reading this clock for a remote idle CPU + * from a BPF timer callback therefore returns a value that drifts + * at wall-clock rate -- the same stale behaviour as scx_clock_task(). + * + * Without NO_HZ_IDLE, periodic ticks keep lost_idle_time nearly in + * sync (stale by at most one tick period, ~1 ms), so the result is + * accurate. */ return rq ? (rq->clock_pelt - rq->lost_idle_time) : 0; } diff --git a/tools/sched_ext/include/scx/compat.bpf.h b/tools/sched_ext/include/scx/compat.bpf.h index 3ab642f92c8a..6944221f96cc 100644 --- a/tools/sched_ext/include/scx/compat.bpf.h +++ b/tools/sched_ext/include/scx/compat.bpf.h @@ -92,15 +92,20 @@ int bpf_cpumask_populate(struct bpf_cpumask *dst, void *src, size_t src__sz) __k /* * v6.19: Introduce lockless peek API for user DSQs. + * v7.1: Fix scx_bpf_dsq_peek() spuriously returning NULL on non-empty + * FIFO DSQs (2f2ea7709266). * - * Preserve the following macro until v6.21. + * The kfunc exists from v6.19 but can return NULL for a non-empty FIFO DSQ + * before the v7.1 fix. Require kernel version >= 7.1.0 before calling it; + * otherwise fall through to the bpf_iter_scx_dsq fallback below. */ static inline struct task_struct *__COMPAT_scx_bpf_dsq_peek(u64 dsq_id) { struct task_struct *p = NULL; struct bpf_iter_scx_dsq it; - if (bpf_ksym_exists(scx_bpf_dsq_peek)) + if (bpf_ksym_exists(scx_bpf_dsq_peek) && + LINUX_KERNEL_VERSION >= KERNEL_VERSION(7, 1, 0)) return scx_bpf_dsq_peek(dsq_id); if (!bpf_iter_scx_dsq_new(&it, dsq_id, 0)) p = bpf_iter_scx_dsq_next(&it); @@ -238,6 +243,26 @@ static inline bool __COMPAT_is_enq_cpu_selected(u64 enq_flags) scx_bpf_pick_any_cpu_node(cpus_allowed, node, flags) : \ scx_bpf_pick_any_cpu(cpus_allowed, flags)) +/* + * v6.18: Add a helper to retrieve the current task running on a CPU. + * + * The kernel tree dropped this helper and scx_bpf_cpu_rq(), but schedulers in + * this tree still support pre-v6.18 kernels where scx_bpf_cpu_curr() doesn't + * resolve and the scx_bpf_cpu_rq() fallback still exists. Keep it until + * pre-v6.18 kernels fall out of the support window. + */ +static inline struct task_struct *__COMPAT_scx_bpf_cpu_curr(int cpu) +{ + struct rq *rq; + + if (bpf_ksym_exists(scx_bpf_cpu_curr)) + return scx_bpf_cpu_curr(cpu); + + rq = scx_bpf_cpu_rq(cpu); + + return rq ? rq->curr : NULL; +} + /* * v6.19: To work around BPF maximum parameter limit, the following kfuncs are * replaced with variants that pack scalar arguments in a struct. Wrappers are @@ -378,6 +403,17 @@ static inline void scx_bpf_task_set_dsq_vtime(struct task_struct *p, u64 vtime) p->scx.dsq_vtime = vtime; } +/* + * v7.1: New scx_bpf_dsq_reenq() that allows re-enqueues on more DSQs. This + * will eventually deprecate scx_bpf_reenqueue_local(). + */ +void scx_bpf_dsq_reenq___compat(u64 dsq_id, u64 reenq_flags) __ksym __weak; + +static inline bool __COMPAT_has_generic_reenq(void) +{ + return bpf_ksym_exists(scx_bpf_dsq_reenq___compat); +} + /* * v6.19: The new void variant can be called from anywhere while the older v1 * variant can only be called from ops.cpu_release(). The double ___ prefixes on @@ -395,21 +431,31 @@ static inline bool __COMPAT_scx_bpf_reenqueue_local_from_anywhere(void) static inline void scx_bpf_reenqueue_local(void) { - if (__COMPAT_scx_bpf_reenqueue_local_from_anywhere()) + if (__COMPAT_has_generic_reenq()) + scx_bpf_dsq_reenq___compat(SCX_DSQ_LOCAL, 0); + else if (__COMPAT_scx_bpf_reenqueue_local_from_anywhere()) scx_bpf_reenqueue_local___v2___compat(); else scx_bpf_reenqueue_local___v1(); } -/* - * v7.1: New scx_bpf_dsq_reenq() that allows re-enqueues on more DSQs. This - * will eventually deprecate scx_bpf_reenqueue_local(). - */ -void scx_bpf_dsq_reenq___compat(u64 dsq_id, u64 reenq_flags) __ksym __weak; - -static inline bool __COMPAT_has_generic_reenq(void) +static inline int scx_bpf_reenqueue_local_from_anywhere(void) { - return bpf_ksym_exists(scx_bpf_dsq_reenq___compat); + /* + * The generic reenq kfunc and the v2 reenqueue-local variant can both be + * called from anywhere; v1 cannot. Test each ksym in its own branch with a + * distinct call: combining them with || would fold into a bitwise OR of the + * two ksym addresses, which the verifier rejects. + */ + if (__COMPAT_has_generic_reenq()) { + scx_bpf_dsq_reenq___compat(SCX_DSQ_LOCAL, 0); + return 0; + } + if (__COMPAT_scx_bpf_reenqueue_local_from_anywhere()) { + scx_bpf_reenqueue_local___v2___compat(); + return 0; + } + return -EOPNOTSUPP; } static inline void scx_bpf_dsq_reenq(u64 dsq_id, u64 reenq_flags) diff --git a/tools/sched_ext/include/scx/compat.h b/tools/sched_ext/include/scx/compat.h index d2e4384df5af..7c12df45fdba 100644 --- a/tools/sched_ext/include/scx/compat.h +++ b/tools/sched_ext/include/scx/compat.h @@ -10,9 +10,14 @@ #include #include #include +#include +#include #include +#include #include +#include "enums_abi.autogen.h" + struct btf *__COMPAT_vmlinux_btf __attribute__((weak)); static inline void __COMPAT_load_vmlinux_btf(void) @@ -23,6 +28,85 @@ static inline void __COMPAT_load_vmlinux_btf(void) } } +/* + * Recover the true value of a 64-bit enum enumerator whose kernel BTF entry + * was truncated to its low 32 bits. + * + * Kernels whose BTF was generated without BTF_KIND_ENUM64 support encode + * 64-bit enums as 8-byte BTF_KIND_ENUM entries whose enumerator values only + * carry the low 32 bits. This happens with pahole < 1.24, which predates + * ENUM64, and with pahole passing --skip_encoding_btf_enum64 (e.g. Google's + * Container-Optimized OS / GKE kernels deliberately pass it for backward + * compatibility with older BTF consumers). The high bits + * can't be recovered from kernel BTF, so substitute the value from the + * vmlinux.h this tree was built against, cross-checked against the low 32 + * bits the kernel did provide. + * + * Note that this is a best-effort recovery, not a ground truth. The + * substitution assumes the running kernel agrees with this tree's vmlinux.h + * on the high 32 bits, but only the low 32 bits can actually be verified. + * The cross-check is vacuous for enumerators whose value has no low bits + * set (e.g. SCX_DSQ_FLAG_BUILTIN, __SCX_ENQ_INTERNAL_MASK, + * SCX_ENQ_CLEAR_OPSS, SCX_ECODE_*): their lo32 is 0 and matches anything, + * so those substitutions rest entirely on the high bits never moving. An + * enumerator missing from the table (a kernel newer than this tree's + * vmlinux.h, or a stale autogen table) can't be recovered at all. If a + * substitution is ever wrong, the scheduler operates on bogus values (e.g. + * dispatching to nonexistent DSQ ids or silently dropping flags) and can + * wildly malfunction, which is why the mismatch and table-miss paths refuse + * instead of guessing. + */ +static inline bool __COMPAT_recover_truncated_enum64(const char *type, + const char *name, + u32 lo32, u64 *v) +{ + static bool warned; + size_t i; + + for (i = 0; i < sizeof(__scx_enum_abi_vals) / sizeof(__scx_enum_abi_vals[0]); i++) { + const struct __scx_enum_abi_val *e = &__scx_enum_abi_vals[i]; + + if (strcmp(e->type, type) || strcmp(e->name, name)) + continue; + + if (e->val <= (u64)UINT32_MAX) { + *v = lo32; + return true; + } + + if ((u32)e->val != lo32) { + fprintf(stderr, "ERROR: kernel BTF value of %s::%s (0x%x) doesn't match the low 32 bits of the vmlinux.h value (0x%llx); refusing to substitute\n", + type, name, lo32, (unsigned long long)e->val); + return false; + } + + if (!warned) { + fprintf(stderr, + "WARNING: kernel BTF lacks BTF_KIND_ENUM64 encoding (generated by\n" + "WARNING: pahole < 1.24 or with --skip_encoding_btf_enum64), so 64-bit\n" + "WARNING: scx enum values are truncated to their low 32 bits in kernel\n" + "WARNING: BTF. Substituting the full 64-bit values from the vmlinux.h\n" + "WARNING: this binary was built against, cross-checked against the low\n" + "WARNING: 32 bits the kernel does provide. The high 32 bits cannot be\n" + "WARNING: verified: if the running kernel's actual values differ from\n" + "WARNING: the build-time vmlinux.h (e.g. an enum that moved in a newer\n" + "WARNING: kernel), the scheduler will operate on bogus values, such as\n" + "WARNING: dispatching to nonexistent DSQ ids, and can wildly malfunction.\n"); + warned = true; + } + *v = e->val; + return true; + } + + /* + * Unknown enumerator (likely a stale autogen table). Fail + * pessimistically to avoid returning an invalid value. + */ + fprintf(stderr, "ERROR: kernel BTF truncates 64-bit enum %s::%s to 0x%x; 64-bit variant not found in vmlinux.h\n", + type, name, lo32); + return false; +} + static inline bool __COMPAT_read_enum(const char *type, const char *name, u64 *v) { const struct btf_type *t; @@ -46,6 +130,19 @@ static inline bool __COMPAT_read_enum(const char *type, const char *name, u64 *v n = btf__name_by_offset(__COMPAT_vmlinux_btf, e[i].name_off); SCX_BUG_ON(!n, "btf__name_by_offset()"); if (!strcmp(n, name)) { + /* + * Try to recover a 64-bit enum from an 8-byte + * BTF_KIND_ENUM that was encoded without ENUM64 + * support (old pahole or + * --skip_encoding_btf_enum64). Only scx_* + * types are covered by the substitution table; + * non-scx types fall through to the raw value + * so this generic utility keeps working for + * them. + */ + if (t->size == 8 && !strncmp(type, "scx_", 4)) + return __COMPAT_recover_truncated_enum64(type, name, + (u32)e[i].val, v); *v = e[i].val; return true; } From 3c0ebc4c07ff1147724d8f370203e62390ae7ee7 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Fri, 21 Aug 2026 10:01:22 +0200 Subject: [PATCH 0083/2366] vt: hide cursor prior to font changes to avoid out-of-bound reads KASAN reports slab-out-of-bounds errors: BUG: KASAN: slab-out-of-bounds in soft_cursor+0x3eb/0xb70 drivers/video/fbdev/core/softcursor.c:70 When changing the size of a sceen font, the amount of columns and rows on a screen may change and thus the current position of the cursor and the selection may suddenly lay outside of the current screen limits. Clear the selection and hide the cursor before any font changes to avoid such possible out of bounds accesses. Reported-by: Jaeyoung Chung Signed-off-by: Helge Deller Link: https://lore.kernel.org/all/20260819163440.3702924-1-jjy600901@snu.ac.kr/ --- drivers/tty/vt/vt.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index 8f467b22b799..57edf37495a8 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -4986,8 +4986,8 @@ static int con_font_set(struct vc_data *vc, const struct console_font_op *op) if (!vc->vc_sw->con_font_set) return -ENOSYS; - if (vc_is_sel(vc)) - clear_selection(); + /* hide selection and cursor prior font changes */ + hide_cursor(vc); return vc->vc_sw->con_font_set(vc, &font, vpitch, op->flags); } @@ -5011,8 +5011,9 @@ static int con_font_default(struct vc_data *vc, struct console_font_op *op) if (!vc->vc_sw->con_font_default) return -ENOSYS; - if (vc_is_sel(vc)) - clear_selection(); + /* hide selection and cursor prior font changes */ + hide_cursor(vc); + int ret = vc->vc_sw->con_font_default(vc, &font, s); if (ret) return ret; From cca061dccf563907061766191b2ce3f66b7c285a Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Fri, 21 Aug 2026 09:05:52 -1000 Subject: [PATCH 0084/2366] sched_ext: Fix spurious aborts in scx_bpf_dsq_move() on ownership change races scx_dsq_move() verifies that the task belongs to the calling scheduler before taking any locks and aborts the scheduler on mismatch. The task can lose the sched association at any point: It can run and fully exit, which clears the association, or get rehomed to a different sub-sched. Both are benign races, but the early ownership check escalates them into scheduler aborts. Move the ownership check below the cursor-lost check. Every ownership change dequeues the task first, so a task that is still on the iterated DSQ under the lock while owned elsewhere indicates a genuine violation and should abort. Also fix two stale comments still referencing sched_ext_free(), which has been renamed to sched_ext_dead(). Fixes: bb4d9fd55158 ("sched_ext: scx_dsq_move() should validate the task belongs to the right scheduler") Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index b646711a45fe..c539d15cda63 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -7694,7 +7694,7 @@ static void scx_root_enable_workfn(struct kthread_work *work) /* * Enable ops for every task. Fork is excluded by scx_fork_rwsem * preventing new tasks from being added. No need to exclude tasks - * leaving as sched_ext_free() can handle both prepped and enabled + * leaving as sched_ext_dead() can handle both prepped and enabled * tasks. Prep all tasks first and then enable them with preemption * disabled. * @@ -7786,7 +7786,7 @@ static void scx_root_enable_workfn(struct kthread_work *work) /* * We're fully committed and can't fail. The task READY -> ENABLED - * transitions here are synchronized against sched_ext_free() through + * transitions here are synchronized against sched_ext_dead() through * scx_tasks_lock. */ percpu_down_write(&scx_fork_rwsem); @@ -9004,12 +9004,6 @@ static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit, if (unlikely(READ_ONCE(sch->aborting))) return false; - if (unlikely(!scx_task_on_sched(sch, p))) { - scx_error(sch, "scx_bpf_dsq_move[_vtime]() on %s[%d] but the task belongs to a different scheduler", - p->comm, p->pid); - return false; - } - /* * Can be called from either ops.dispatch() holding the dispatched rq's * lock or any context where no rq lock is held. If latter, lock @p's @@ -9041,6 +9035,17 @@ static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit, goto out; } + /* + * @p has been on $src_dsq and can't move anymore. If @p is not on @sch, + * the caller didn't have authority over @p at the time of the call. + */ + if (unlikely(!scx_task_on_sched(sch, p))) { + scx_error(sch, "scx_bpf_dsq_move[_vtime]() on %s[%d] but the task belongs to a different scheduler", + p->comm, p->pid); + raw_spin_unlock(&src_dsq->lock); + goto out; + } + /* @p is still on $src_dsq and stable, determine the destination */ dst_dsq = find_dsq_for_dispatch(sch, locked_rq ?: this_rq(), dsq_id, task_cpu(p)); From 500cb24cd61bad8a2747ddfc49b7034899c82d94 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Sun, 16 Aug 2026 14:22:34 +0530 Subject: [PATCH 0085/2366] drm/gud: NUL-terminate TV mode names read from the device gud_connector_add_tv_mode() reads a buffer of fixed-size mode names from the USB device and passes pointers into it to drm_mode_create_tv_properties_legacy(), which calls strlen() on each one. Nothing guarantees the device NUL-terminates a name, so strlen() can run past the end of a slot and, for the last mode, past the end of the allocation. Terminate each name at the end of its slot before use. Fixes: 40e1a70b4aed ("drm: Add GUD USB Display driver") Reported-by: syzbot+916c888ba5f1a54c9526@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=916c888ba5f1a54c9526 Tested-by: syzbot+916c888ba5f1a54c9526@syzkaller.appspotmail.com Signed-off-by: Deepanshu Kartikey Acked-by: Ruben Wauters Cc: Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260816085234.22053-1-kartikey406@gmail.com --- drivers/gpu/drm/gud/gud_connector.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/gud/gud_connector.c b/drivers/gpu/drm/gud/gud_connector.c index ea0cca58b7c8..5c0065c876a7 100644 --- a/drivers/gpu/drm/gud/gud_connector.c +++ b/drivers/gpu/drm/gud/gud_connector.c @@ -396,8 +396,13 @@ static int gud_connector_add_tv_mode(struct gud_device *gdrm, struct drm_connect } num_modes = ret / GUD_CONNECTOR_TV_MODE_NAME_LEN; - for (i = 0; i < num_modes; i++) - modes[i] = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN]; + for (i = 0; i < num_modes; i++) { + char *mode = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN]; + + /* The device is not trusted to NUL-terminate the name */ + mode[GUD_CONNECTOR_TV_MODE_NAME_LEN - 1] = '\0'; + modes[i] = mode; + } ret = drm_mode_create_tv_properties_legacy(connector->dev, num_modes, modes); free: From da1ea35fea67ad841f4ada28dd61b41be65e5437 Mon Sep 17 00:00:00 2001 From: Tao Yu Date: Wed, 19 Aug 2026 15:28:35 +0800 Subject: [PATCH 0086/2366] drm/gud: validate TV mode names before creating enum property The GUD protocol returns TV mode names as fixed-size GUD_CONNECTOR_TV_MODE_NAME_LEN entries and requires each name to be NUL-terminated. gud_connector_add_tv_mode() currently passes each fixed-size entry directly to drm_mode_create_tv_properties_legacy(), which eventually reaches drm_property_add_enum() and strlen(). If a device returns an entry without a terminating NUL byte, strlen() reads past the end of the slot and can run beyond the allocated buffer, triggering an out-of-bounds read. Validate that each returned TV mode name contains a NUL terminator within its fixed-size slot before passing it to the DRM property code. If a malformed entry is found, reject the device response with -EIO. This fixes the out-of-bounds read without changing the handling of valid devices, and avoids silently truncating malformed protocol data. Reported-by: syzbot+9ae8e7884e451eaed5b4@syzkaller.appspotmail.com Fixes: 40e1a70b4aed ("drm: Add GUD USB Display driver") Signed-off-by: Tao Yu Reviewed-by: Ruben Wauters Cc: Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260819072835.4074130-1-tao1.yu@intel.com --- drivers/gpu/drm/gud/gud_connector.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/gud/gud_connector.c b/drivers/gpu/drm/gud/gud_connector.c index 5c0065c876a7..8141c3a1e30a 100644 --- a/drivers/gpu/drm/gud/gud_connector.c +++ b/drivers/gpu/drm/gud/gud_connector.c @@ -399,8 +399,11 @@ static int gud_connector_add_tv_mode(struct gud_device *gdrm, struct drm_connect for (i = 0; i < num_modes; i++) { char *mode = &buf[i * GUD_CONNECTOR_TV_MODE_NAME_LEN]; - /* The device is not trusted to NUL-terminate the name */ - mode[GUD_CONNECTOR_TV_MODE_NAME_LEN - 1] = '\0'; + if (!memchr(mode, '\0', GUD_CONNECTOR_TV_MODE_NAME_LEN)) { + ret = -EIO; + goto free; + } + modes[i] = mode; } From cb732d027aa18e1fcf9d2797f47d20b179ebc59c Mon Sep 17 00:00:00 2001 From: Sajal Gupta Date: Fri, 21 Aug 2026 12:46:13 +0530 Subject: [PATCH 0087/2366] drm/gud: validate GUD_ROTATION_0 is present in supported rotations The rotation argument to drm_plane_create_rotation_property() is set to DRM_MODE_ROTATE_0, and the device reported rotation bitmask is used as the supported_rotations argument. The driver never validates that GUD_ROTATION_0 is present, so a device that omits it from its GUD_PROPERTY_ROTATION triggers the WARN_ON(rotation & ~supported_rotations) in drm_plane_create_rotation_property() Fix this by skipping the creation of rotation property if the device doesn't have the GUD_ROTATION_0 bit Fixes: 40e1a70b4aed ("drm: Add GUD USB Display driver") Reported-by: syzbot+efe2810681f1b065d3a8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=efe2810681f1b065d3a8 Tested-by: syzbot+efe2810681f1b065d3a8@syzkaller.appspotmail.com Signed-off-by: Sajal Gupta Acked-by: Ruben Wauters Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260821071812.16500-1-sajal2005gupta@gmail.com --- drivers/gpu/drm/gud/gud_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/gud/gud_drv.c b/drivers/gpu/drm/gud/gud_drv.c index 89bd6ca36003..3a1b9e2a2eaa 100644 --- a/drivers/gpu/drm/gud/gud_drv.c +++ b/drivers/gpu/drm/gud/gud_drv.c @@ -289,6 +289,8 @@ static int gud_plane_add_properties(struct gud_device *gdrm) * but mask out any additions on future devices. */ val &= GUD_ROTATION_MASK; + if (!(val & GUD_ROTATION_0)) + continue; ret = drm_plane_create_rotation_property(&gdrm->plane, DRM_MODE_ROTATE_0, val); break; From 2afb8dc1f4390f164db8352f8e685e126e9db566 Mon Sep 17 00:00:00 2001 From: Siwei Zhang Date: Thu, 30 Jul 2026 19:40:08 +0800 Subject: [PATCH 0088/2366] xfrm: use hlist_del_init_rcu for state_cache and state_cache_input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 14acf9652e56 ("xfrm: defensively unhash xfrm_state lists in __xfrm_state_delete") converted bydst/bysrc/byseq/byspi from hlist_del_rcu() to hlist_del_init_rcu() so that a second __xfrm_state_delete() on the same object becomes a no-op rather than a write through LIST_POISON pprev. It missed state_cache and state_cache_input, which kept hlist_del_rcu(): - hlist_del_rcu() leaves pprev = LIST_POISON2 (non-NULL), so hlist_unhashed() returns false. - hlist_del_init_rcu() leaves pprev = NULL, so hlist_unhashed() returns true. A second __xfrm_state_delete() therefore enters __hlist_del() on the already-deleted state_cache/state_cache_input nodes and does WRITE_ONCE(*pprev, next) through LIST_POISON2 — a write use-after-free once the slab is reused. The corruption can in turn cause a subsequent hlist_for_each_entry_rcu traversal to follow a dangling next pointer, producing the read use-after-free reported in xfrm_input_state_lookup(). Switch state_cache and state_cache_input to hlist_del_init_rcu() to match the other four lists, closing the write use-after-free and, with it, the read use-after-free it spawns. Assisted-by: CodeBuddy:GLM-5.2 Fixes: 0045e3d80613 ("xfrm: Cache used outbound xfrm states at the policy.") Fixes: 81a331a0e72d ("xfrm: Add an inbound percpu state cache.") Cc: stable@vger.kernel.org Signed-off-by: Siwei Zhang Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_state.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index de097bba803b..e45aa1ed5b96 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -826,9 +826,9 @@ int __xfrm_state_delete(struct xfrm_state *x) if (!hlist_unhashed(&x->byseq)) hlist_del_init_rcu(&x->byseq); if (!hlist_unhashed(&x->state_cache)) - hlist_del_rcu(&x->state_cache); + hlist_del_init_rcu(&x->state_cache); if (!hlist_unhashed(&x->state_cache_input)) - hlist_del_rcu(&x->state_cache_input); + hlist_del_init_rcu(&x->state_cache_input); if (!hlist_unhashed(&x->byspi)) hlist_del_init_rcu(&x->byspi); From f89416eb3db151170a6f3c6dfc5239d26cdce4d2 Mon Sep 17 00:00:00 2001 From: Maher Azzouzi Date: Mon, 17 Aug 2026 14:37:52 +0100 Subject: [PATCH 0089/2366] esp: downgrade zerocopy managed frags before mutating skb frags On the out-of-place output path (esp->inplace == false) ESP rewrites the skb frag array: esp_output_head() appends a trailer frag and esp_output_tail() replaces the frags with a destination page, both referenced with get_page(). When the skb carries zerocopy managed frags (SKBFL_MANAGED_FRAG_REFS) the payload frags are owned by the ubuf and must not be referenced or unreferenced individually, but ESP mutates the frag array without ever downgrading the skb. This breaks the managed-frag invariant two ways: - esp_ssg_unref() walks the source scatterlist and drops a page reference for every frag, including the ubuf-owned payload frags, pushing their refcount below the GUP pin bias while the pages are still pinned, i.e. a use-after-free of the zerocopy pages; - esp_output_tail() installs its destination page as frag 0 with get_page() but leaves SKBFL_MANAGED_FRAG_REFS set, so skb_release_data() takes the skip_unref branch and never drops that reference, leaking the x->xfrag page at packet rate. Fix this the way every other frag-mutating site does (__ip_append_data(), __ip6_append_data(), tcp_sendmsg_locked()) and call skb_zcopy_downgrade_managed() before ESP touches the frag array: it takes a real reference on each existing frag and clears SKBFL_MANAGED_FRAG_REFS, so the per-frag unref in esp_ssg_unref() and the frag release in skb_release_data() are both balanced and no mixed-ownership frag array is left behind. Fixes: 753f1ca4e1e5 ("net: introduce managed frags infrastructure") Signed-off-by: Maher Azzouzi Signed-off-by: Steffen Klassert --- net/ipv4/esp4.c | 6 ++++++ net/ipv6/esp6.c | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c index dfc81ee969ae..faa48f5b9739 100644 --- a/net/ipv4/esp4.c +++ b/net/ipv4/esp4.c @@ -441,6 +441,12 @@ int esp_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info * esp->inplace = false; + /* Take real page refs and clear SKBFL_MANAGED_FRAG_REFS before + * we mutate the frag array, so the per-frag unref stays balanced + * for zerocopy managed frags (see __ip_append_data()). + */ + skb_zcopy_downgrade_managed(skb); + allocsize = ALIGN(tailen, L1_CACHE_BYTES); spin_lock_bh(&x->lock); diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c index 296b57926abb..a3a3857eed98 100644 --- a/net/ipv6/esp6.c +++ b/net/ipv6/esp6.c @@ -470,6 +470,12 @@ int esp6_output_head(struct xfrm_state *x, struct sk_buff *skb, struct esp_info esp->inplace = false; + /* Take real page refs and clear SKBFL_MANAGED_FRAG_REFS before + * we mutate the frag array, so the per-frag unref stays balanced + * for zerocopy managed frags (see __ip_append_data()). + */ + skb_zcopy_downgrade_managed(skb); + allocsize = ALIGN(tailen, L1_CACHE_BYTES); spin_lock_bh(&x->lock); From 72e91bba1190c91c76ef2f81476399eb66b87ed2 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Sat, 22 Aug 2026 11:43:08 +0200 Subject: [PATCH 0090/2366] fbdev: omapfb: Fix __be32 sparse warning in panel_enabled() This commit resolves a sparse warning in panel_enabled() by explicitly reading the display status into a __be32 variable. It then converts this value to CPU endianness using __be32_to_cpu() before checking the bits. This should fix this sparse warning: ../omapfb/displays/panel-sony-acx565akm.c:218:23: sparse: sparse: cast to restricted __be32 Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608211811.lps93xao-lkp@intel.com/ Signed-off-by: Helge Deller --- .../video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c b/drivers/video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c index 8f430d9e8054..0202ca8cbfc2 100644 --- a/drivers/video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c +++ b/drivers/video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c @@ -210,12 +210,13 @@ static void set_display_state(struct panel_drv_data *ddata, int enabled) static int panel_enabled(struct panel_drv_data *ddata) { + __be32 disp_status_be; u32 disp_status; int enabled; acx565akm_read(ddata, MIPID_CMD_READ_DISP_STATUS, - (u8 *)&disp_status, 4); - disp_status = __be32_to_cpu(disp_status); + (u8 *)&disp_status_be, 4); + disp_status = __be32_to_cpu(disp_status_be); enabled = (disp_status & (1 << 17)) && (disp_status & (1 << 10)); dev_dbg(&ddata->spi->dev, "LCD panel %senabled by bootloader (status 0x%04x)\n", From 94e6a058b16820e02f25e1221a4c4e713ba23550 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Sat, 22 Aug 2026 12:03:15 +0200 Subject: [PATCH 0091/2366] fbcon: Fix KASAN slab-out-of-bounds Read in fbcon_prepare_logo Ensure the logo will not exceed the screen size, which then should fix a reported KASAN: slab-out-of-bounds Read in fbcon_prepare_logo. Reported-by: syzbot+0c815b25cdb3678e7083@syzkaller.appspotmail.com Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fbcon.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c index 23b3c536d53d..01715873ea49 100644 --- a/drivers/video/fbdev/core/fbcon.c +++ b/drivers/video/fbdev/core/fbcon.c @@ -660,6 +660,13 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info, erase &= ~0x400; logo_height = fb_prepare_logo(info, par->rotate); logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height); + logo_lines = min(logo_lines, rows); + logo_lines = min(logo_lines, new_rows - 1); + if (logo_lines <= 0) { + logo_lines = 0; + logo_shown = FBCON_LOGO_DONTSHOW; + return; + } q = (unsigned short *) (vc->vc_origin + vc->vc_size_row * rows); step = logo_lines * cols; From 30d0aff2c65a277135cfd8ea28fa1ee75e0ea4e0 Mon Sep 17 00:00:00 2001 From: Baineng Shou Date: Mon, 17 Aug 2026 13:04:54 +0800 Subject: [PATCH 0092/2366] dma-buf: dma-heap: don't publish fd before copy_to_user() succeeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DMA_HEAP_IOCTL_ALLOC allocates a dma-buf and installs an fd into the caller's fd table via dma_buf_fd() -> fd_install() before dma_heap_ioctl() copies the result back to userspace. If the trailing copy_to_user() fails, userspace never learns the fd number, but the fd (and the underlying dma-buf reference) are already visible to other threads in the same process and are leaked for the lifetime of the process. The obvious "close it on the failure path" fix is unsafe: once fd_install() has run, another thread can already dup() the fd, send it via SCM_RIGHTS, or close() it and let its number be reused, so a subsequent close_fd() from the ioctl path can operate on an unrelated file. This was pointed out by Christian König on v1 [1]. Restructure the allocation path so that fd_install() is the last, unfailable step of a successful ioctl: 1. heap->ops->allocate() creates the dma_buf. 2. get_unused_fd_flags() reserves an fd number in the caller's fd table without publishing it, so no other thread can observe it. 3. copy_to_user() delivers the fd number to userspace; on failure the fd is returned with put_unused_fd() and the dma_buf reference is dropped with dma_buf_put(), leaving no user- visible state behind. 4. dma_buf_fd_install() publishes the fd and emits the trace_dma_buf_fd tracepoint -- from here on the ioctl cannot fail. A new dma_buf_fd_install() helper is introduced in dma-buf.c to wrap fd_install() together with the DMA_BUF_TRACE() call, preserving the export tracing that dma_buf_fd() provides. dma_heap_ioctl_allocate() is refactored to return the struct dma_buf * directly (returning ERR_PTR on failure) so the caller holds the dmabuf reference across steps 3 and 4. The failure at step 3 is easily reachable from userspace: pass a struct dma_heap_allocation_data that lives in a page whose protection is flipped to PROT_READ between copy_from_user() and copy_to_user() (e.g. via mprotect()). Before this change each such ioctl leaks one dmabuf fd; after it, the fd table is unchanged on failure and only /dev/dma_heap/ remains open. No UAPI or heap-driver interface change. [1] https://lore.kernel.org/dri-devel/175e98de-f414-47d7-81c1-c0fe0a8f7f62@amd.com/ Fixes: c02a81fba74f ("dma-buf: Add dma-buf heaps framework") Cc: stable@vger.kernel.org Reviewed-by: T.J. Mercier Acked-by: Christian König Acked-by: Sumit Semwal Signed-off-by: Baineng Shou Link: https://lore.kernel.org/r/20260817050457.1005285-2-shoubaineng@gmail.com Signed-off-by: Christian König --- drivers/dma-buf/dma-buf.c | 20 ++++++++++ drivers/dma-buf/dma-heap.c | 80 +++++++++++++++++++------------------- include/linux/dma-buf.h | 1 + 3 files changed, 61 insertions(+), 40 deletions(-) diff --git a/drivers/dma-buf/dma-buf.c b/drivers/dma-buf/dma-buf.c index d504c636dc29..4c9add51f9ef 100644 --- a/drivers/dma-buf/dma-buf.c +++ b/drivers/dma-buf/dma-buf.c @@ -803,6 +803,26 @@ int dma_buf_fd(struct dma_buf *dmabuf, int flags) } EXPORT_SYMBOL_NS_GPL(dma_buf_fd, "DMA_BUF"); +/** + * dma_buf_fd_install - install a reserved fd for a dma-buf + * @dmabuf: [in] pointer to dma_buf + * @fd: [in] fd reserved with get_unused_fd_flags() + * + * Publishes a previously reserved fd into the caller's fd table. + * Must only be called after all fallible work (e.g. copy_to_user) + * has succeeded, as it cannot be undone safely once called. + * + * The caller is responsible for having emitted the trace event + * (via dma_buf_fd() or get_unused_fd_flags() + this function) + * before calling this. + */ +void dma_buf_fd_install(struct dma_buf *dmabuf, int fd) +{ + DMA_BUF_TRACE(trace_dma_buf_fd, dmabuf, fd); + fd_install(fd, dmabuf->file); +} +EXPORT_SYMBOL_NS_GPL(dma_buf_fd_install, "DMA_BUF"); + /** * dma_buf_get - returns the struct dma_buf related to an fd * @fd: [in] fd associated with the struct dma_buf to be returned diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c index a76bf3f8b071..43c32fb28313 100644 --- a/drivers/dma-buf/dma-heap.c +++ b/drivers/dma-buf/dma-heap.c @@ -55,33 +55,6 @@ MODULE_PARM_DESC(mem_accounting, "Enable cgroup-based memory accounting for dma-buf heap allocations (default=false)."); EXPORT_SYMBOL_NS_GPL(mem_accounting, "DMA_BUF_HEAP"); -static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len, - u32 fd_flags, - u64 heap_flags) -{ - struct dma_buf *dmabuf; - int fd; - - /* - * Allocations from all heaps have to begin - * and end on page boundaries. - */ - len = PAGE_ALIGN(len); - if (!len) - return -EINVAL; - - dmabuf = heap->ops->allocate(heap, len, fd_flags, heap_flags); - if (IS_ERR(dmabuf)) - return PTR_ERR(dmabuf); - - fd = dma_buf_fd(dmabuf, fd_flags); - if (fd < 0) { - dma_buf_put(dmabuf); - /* just return, as put will call release and that will free */ - } - return fd; -} - static int dma_heap_open(struct inode *inode, struct file *file) { struct dma_heap *heap; @@ -99,30 +72,42 @@ static int dma_heap_open(struct inode *inode, struct file *file) return 0; } -static long dma_heap_ioctl_allocate(struct file *file, void *data) +static struct dma_buf *dma_heap_ioctl_allocate(struct file *file, void *data) { struct dma_heap_allocation_data *heap_allocation = data; struct dma_heap *heap = file->private_data; + struct dma_buf *dmabuf; int fd; + size_t len; if (heap_allocation->fd) - return -EINVAL; + return ERR_PTR(-EINVAL); if (heap_allocation->fd_flags & ~DMA_HEAP_VALID_FD_FLAGS) - return -EINVAL; + return ERR_PTR(-EINVAL); if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS) - return -EINVAL; + return ERR_PTR(-EINVAL); - fd = dma_heap_buffer_alloc(heap, heap_allocation->len, - heap_allocation->fd_flags, - heap_allocation->heap_flags); - if (fd < 0) - return fd; + len = PAGE_ALIGN(heap_allocation->len); + if (!len) + return ERR_PTR(-EINVAL); + + dmabuf = heap->ops->allocate(heap, len, heap_allocation->fd_flags, + heap_allocation->heap_flags); + + if (IS_ERR(dmabuf)) + return dmabuf; + + fd = get_unused_fd_flags(heap_allocation->fd_flags); + if (fd < 0) { + dma_buf_put(dmabuf); + return ERR_PTR(fd); + } heap_allocation->fd = fd; - return 0; + return dmabuf; } static unsigned int dma_heap_ioctl_cmds[] = { @@ -138,6 +123,8 @@ static long dma_heap_ioctl(struct file *file, unsigned int ucmd, unsigned int in_size, out_size, drv_size, ksize; int nr = _IOC_NR(ucmd); int ret = 0; + int fd; + struct dma_buf *dmabuf; if (nr >= ARRAY_SIZE(dma_heap_ioctl_cmds)) return -EINVAL; @@ -174,15 +161,28 @@ static long dma_heap_ioctl(struct file *file, unsigned int ucmd, switch (kcmd) { case DMA_HEAP_IOCTL_ALLOC: - ret = dma_heap_ioctl_allocate(file, kdata); + dmabuf = dma_heap_ioctl_allocate(file, kdata); + + if (IS_ERR(dmabuf)) { + ret = PTR_ERR(dmabuf); + break; + } + + fd = ((struct dma_heap_allocation_data *)kdata)->fd; + if (copy_to_user((void __user *)arg, kdata, out_size) != 0) { + put_unused_fd(fd); + dma_buf_put(dmabuf); + ret = -EFAULT; + } else { + dma_buf_fd_install(dmabuf, fd); + } + break; default: ret = -ENOTTY; goto err; } - if (copy_to_user((void __user *)arg, kdata, out_size) != 0) - ret = -EFAULT; err: if (kdata != stack_kdata) kfree(kdata); diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h index d1203da56fc5..d15b2b31d3c9 100644 --- a/include/linux/dma-buf.h +++ b/include/linux/dma-buf.h @@ -567,6 +567,7 @@ void dma_buf_unpin(struct dma_buf_attachment *attach); struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info); int dma_buf_fd(struct dma_buf *dmabuf, int flags); +void dma_buf_fd_install(struct dma_buf *dmabuf, int fd); struct dma_buf *dma_buf_get(int fd); void dma_buf_put(struct dma_buf *dmabuf); From a4a1a2bfcb29785292d634d7787edc6fb550714d Mon Sep 17 00:00:00 2001 From: Baineng Shou Date: Mon, 17 Aug 2026 13:04:55 +0800 Subject: [PATCH 0093/2366] misc: fastrpc: don't publish fd before copy_to_user() succeeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fastrpc_ioctl_alloc_dmabuf() calls dma_buf_fd() which installs the fd into the caller's fd table before copy_to_user() copies the fd number back to userspace. If copy_to_user() fails, the fd is already visible to other threads in the same process but the ioctl returns -EFAULT. The existing comment in the code even acknowledges the problem: "The usercopy failed, but we can't do much about it, as dma_buf_fd() already called fd_install()..." Now that dma_buf_fd_install() is available (introduced to fix the same issue in dma-heap), apply the same pattern here: reserve the fd with get_unused_fd_flags(), attempt copy_to_user(), and only on success call dma_buf_fd_install() to publish it atomically with the tracepoint. On copy_to_user() failure, put_unused_fd() and dma_buf_put() cleanly unwind without any user-visible side effects. Fixes: 6cffd79504ce ("misc: fastrpc: Add support for dmabuf exporter") Cc: stable@vger.kernel.org Acked-by: Christian König Acked-by: Sumit Semwal Signed-off-by: Baineng Shou Link: https://lore.kernel.org/r/20260817050457.1005285-3-shoubaineng@gmail.com Signed-off-by: Christian König --- drivers/misc/fastrpc.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index eb6c2a78d3c7..d7a9e12f5575 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -1712,24 +1712,20 @@ static int fastrpc_dmabuf_alloc(struct fastrpc_user *fl, char __user *argp) return err; } - bp.fd = dma_buf_fd(buf->dmabuf, O_ACCMODE); + bp.fd = get_unused_fd_flags(O_ACCMODE); if (bp.fd < 0) { dma_buf_put(buf->dmabuf); - return -EINVAL; + return bp.fd; } if (copy_to_user(argp, &bp, sizeof(bp))) { - /* - * The usercopy failed, but we can't do much about it, as - * dma_buf_fd() already called fd_install() and made the - * file descriptor accessible for the current process. It - * might already be closed and dmabuf no longer valid when - * we reach this point. Therefore "leak" the fd and rely on - * the process exit path to do any required cleanup. - */ + put_unused_fd(bp.fd); + dma_buf_put(buf->dmabuf); return -EFAULT; } + dma_buf_fd_install(buf->dmabuf, bp.fd); + return 0; } From 3e164bf592bbbdde269c5cadc60a96f69cc6eed7 Mon Sep 17 00:00:00 2001 From: Baineng Shou Date: Mon, 17 Aug 2026 13:04:56 +0800 Subject: [PATCH 0094/2366] drm/prime: use dma_buf_fd_install() to preserve export tracing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_gem_prime_handle_to_fd() open-codes fd reservation and install using get_unused_fd_flags() + fd_install() directly. This bypasses the DMA_BUF_TRACE() call that dma_buf_fd() emits, so observability tools relying on the trace_dma_buf_fd tracepoint silently miss all DRM PRIME exports. Replace the bare fd_install() with dma_buf_fd_install(), which wraps fd_install() together with DMA_BUF_TRACE(), restoring full tracepoint coverage. No functional change; the fd lifecycle (get_unused_fd_flags → work → install) is already correct. Note: this patch depends on dma_buf_fd_install() introduced in "dma-buf: dma-heap: don't publish fd before copy_to_user() succeeds" [1]. [1] https://lore.kernel.org/dri-devel/20260714114654.3885457-2-shoubaineng@gmail.com/ Suggested-by: Christian König Acked-by: Sumit Semwal Reviewed-by: Christian König Signed-off-by: Baineng Shou Link: https://lore.kernel.org/r/20260817050457.1005285-4-shoubaineng@gmail.com Signed-off-by: Christian König --- drivers/gpu/drm/drm_prime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_prime.c b/drivers/gpu/drm/drm_prime.c index 9b44c78cd77f..fe3436d1235d 100644 --- a/drivers/gpu/drm/drm_prime.c +++ b/drivers/gpu/drm/drm_prime.c @@ -524,7 +524,7 @@ int drm_gem_prime_handle_to_fd(struct drm_device *dev, return PTR_ERR(dmabuf); } - fd_install(fd, dmabuf->file); + dma_buf_fd_install(dmabuf, fd); *prime_fd = fd; return 0; } From 8985cbc927fd3e23dfebaa099ae0b6d2b38d3258 Mon Sep 17 00:00:00 2001 From: Baineng Shou Date: Mon, 17 Aug 2026 13:04:57 +0800 Subject: [PATCH 0095/2366] selftests: dmabuf-heaps: add fd-leak-on-EFAULT regression test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a test case that verifies no file descriptor is leaked when DMA_HEAP_IOCTL_ALLOC succeeds internally but copy_to_user() fails to deliver the fd number back to userspace. The failure is triggered by placing the ioctl argument in a private anonymous page and flipping it to PROT_READ (via mprotect) between the kernel's copy_from_user() and copy_to_user() calls. With the buggy kernel the ioctl returns -EFAULT but leaves an extra open fd in the process's fd table; with the fixed kernel the fd count is unchanged. This serves as a regression test for: "dma-buf: dma-heap: don't publish fd before copy_to_user() succeeds" Suggested-by: Sumit Semwal Reviewed-by: T.J. Mercier Acked-by: Sumit Semwal Signed-off-by: Baineng Shou Link: https://lore.kernel.org/r/20260817050457.1005285-5-shoubaineng@gmail.com Signed-off-by: Christian König --- .../selftests/dmabuf-heaps/dmabuf-heap.c | 113 +++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/dmabuf-heaps/dmabuf-heap.c b/tools/testing/selftests/dmabuf-heaps/dmabuf-heap.c index fc9694fc4e89..1d49df671919 100644 --- a/tools/testing/selftests/dmabuf-heaps/dmabuf-heap.c +++ b/tools/testing/selftests/dmabuf-heaps/dmabuf-heap.c @@ -390,6 +390,116 @@ static void test_alloc_errors(char *heap_name) close(heap_fd); } +/* + * count_open_fds - return the number of open file descriptors. + * + * The fd opened by opendir() itself is counted, but since it is opened + * and closed within each call, it cancels out when comparing two counts. + * Returns -1 on error. + */ +static int count_open_fds(void) +{ + DIR *d = opendir("/proc/self/fd"); + struct dirent *de; + int count = 0; + + if (!d) + return -1; + + while ((de = readdir(d))) + if (de->d_name[0] != '.') + count++; + closedir(d); + return count; +} + +/* + * test_alloc_no_fd_leak_on_efault - verify no fd is leaked when + * copy_to_user() fails during DMA_HEAP_IOCTL_ALLOC. + * + * The bug: dma_buf_fd() called fd_install() before copy_to_user(). + * If copy_to_user() then failed (e.g. via mprotect), the fd was + * silently installed in the fd table but never returned to userspace. + * + * The fix: reserve the fd with get_unused_fd_flags() first, attempt + * copy_to_user(), and only call fd_install() on success. + * + * We trigger the failure by placing the ioctl argument in a private + * anonymous page and flipping it to PROT_READ before the ioctl. + * Inside the kernel, copy_from_user() reads from the page (reads are + * allowed under PROT_READ, so it succeeds), but copy_to_user() that + * writes the fd number back faults, returning -EFAULT. We then + * count open file descriptors before and after; with the bug an extra + * fd is left in the table. + */ +static void test_alloc_no_fd_leak_on_efault(char *heap_name) +{ + int heap_fd = -1; + int fd_before, fd_after; + int ret; + long page_size; + struct dma_heap_allocation_data *req; + + ksft_print_msg("Testing fd leak when copy_to_user() fails:\n"); + + heap_fd = dmabuf_heap_open(heap_name); + + page_size = sysconf(_SC_PAGESIZE); + + /* + * Place the ioctl argument in its own private anonymous page so + * we can flip its protection independently. + */ + req = mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (req == MAP_FAILED) { + ksft_test_result_fail("mmap failed: %s\n", strerror(errno)); + goto out; + } + + memset(req, 0, sizeof(*req)); + req->len = page_size; + req->fd_flags = O_RDWR | O_CLOEXEC; + + fd_before = count_open_fds(); + if (fd_before < 0) { + ksft_test_result_fail("count_open_fds: %s\n", strerror(errno)); + munmap(req, page_size); + goto out; + } + + /* + * Make the page read-only so copy_to_user() will fault. The + * ioctl must fail with -1; if it returns success the test setup + * is broken (mprotect is synchronous, so there is no race). + */ + mprotect(req, page_size, PROT_READ); + + ret = ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, req); + + /* Re-allow writes so munmap can clean up */ + mprotect(req, page_size, PROT_READ | PROT_WRITE); + munmap(req, page_size); + + if (ret != -1) { + ksft_test_result_fail("ioctl returned %d, expected -1 EFAULT\n", + ret); + goto out; + } + + fd_after = count_open_fds(); + if (fd_after < 0) { + ksft_test_result_fail("count_open_fds: %s\n", strerror(errno)); + goto out; + } + + ksft_test_result(fd_before == fd_after, + "fd leak on EFAULT: before=%d after=%d\n", + fd_before, fd_after); +out: + close(heap_fd); +} + static int numer_of_heaps(void) { DIR *d = opendir(DEVPATH); @@ -420,7 +530,7 @@ int main(void) return KSFT_SKIP; } - ksft_set_plan(11 * numer_of_heaps()); + ksft_set_plan(12 * numer_of_heaps()); while ((dir = readdir(d))) { if (!strncmp(dir->d_name, ".", 2)) @@ -435,6 +545,7 @@ int main(void) test_alloc_zeroed(dir->d_name, ONE_MEG); test_alloc_compat(dir->d_name); test_alloc_errors(dir->d_name); + test_alloc_no_fd_leak_on_efault(dir->d_name); } closedir(d); From 72dd0ec09e7cc98ed58ddeac26575e5d1ab8a93d Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Tue, 4 Aug 2026 22:52:19 +0900 Subject: [PATCH 0096/2366] printk: Don't WARN on kthread_run failure. Since __kthread_create_on_node() returns -EINTR upon SIGKILL, we should not use WARN_ON() in order to catch kthread_run() failure. Reported-by: syzbot+1ebbc20f223b99446034@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1ebbc20f223b99446034 Fixes: 5f53ca3ff83b ("printk: Implement legacy printer kthread for PREEMPT_RT") Fixes: 76f258bf3f2a ("printk: nbcon: Introduce printer kthreads") Signed-off-by: Tetsuo Handa Reviewed-by: John Ogness Reviewed-by: Petr Mladek Link: https://patch.msgid.link/76bb4c1c-5d85-4635-b3bb-fc06f292c59e@I-love.SAKURA.ne.jp Signed-off-by: Petr Mladek --- kernel/printk/nbcon.c | 2 +- kernel/printk/printk.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index 4b03b019cd5e..a5921a84a80e 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -1382,7 +1382,7 @@ bool nbcon_kthread_create(struct console *con) return true; kt = kthread_run(nbcon_kthread_func, con, "pr/%s%d", con->name, con->index); - if (WARN_ON(IS_ERR(kt))) { + if (IS_ERR(kt)) { con_printk(KERN_ERR, con, "failed to start printing thread\n"); return false; } diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 3fcdf4b4e2e5..6d3d18a50da7 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -3732,7 +3732,7 @@ static bool legacy_kthread_create(void) lockdep_assert_console_list_lock_held(); kt = kthread_run(legacy_kthread_func, NULL, "pr/legacy"); - if (WARN_ON(IS_ERR(kt))) { + if (IS_ERR(kt)) { pr_err("failed to start legacy printing thread\n"); return false; } From b3709d354545e70388177500761f92d906c4dfd6 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Thu, 20 Aug 2026 20:35:43 -0700 Subject: [PATCH 0097/2366] accel/amdxdna: Remove __counted_by from struct amdxdna_cmd_chain struct amdxdna_cmd_chain contains a flexible array annotated with __counted_by(command_count). Since the structure is stored in shared AMDXDNA_BO_SHARE memory, userspace can modify command_count concurrently. If command_count is changed to zero, the bounds check generated from __counted_by may fail and trigger a kernel panic. Remove __counted_by to avoid relying on the userspace-controlled command_count for the flexible array bounds check. Fixes: aac243092b70 ("accel/amdxdna: Add command execution") Reviewed-by: Max Zhen Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260821033543.1839719-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_ctx.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/amdxdna/amdxdna_ctx.h b/drivers/accel/amdxdna/amdxdna_ctx.h index b6bef3af7dab..6e78bab8a02c 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.h +++ b/drivers/accel/amdxdna/amdxdna_ctx.h @@ -55,7 +55,7 @@ struct amdxdna_cmd_chain { u32 submit_index; u32 error_index; u32 reserved[3]; - u64 data[] __counted_by(command_count); + u64 data[]; }; /* From ef6d27af71e1dc43181ec797a6aaa77c27c36786 Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Tue, 18 Aug 2026 03:00:19 +0300 Subject: [PATCH 0098/2366] accel/amdxdna: reject a command chain that carries no commands A chain whose command_count is zero passes the payload length check, because struct_size(payload, data, 0) is just the header. The fill loop then does not run, so offset stays zero and the request is submitted with a zero-length buffer. On firmware without AIE2_NPU_COMMAND that ends at the opcode check, since op is still ERT_INVALID_CMD and aie2_get_chain_msg_op() answers MSG_OP_MAX_OPCODE. aie2_get_npu_chain_msg_op() answers MSG_OP_CHAIN_EXEC_NPU whatever it is given, so there the submission continues to drm_clflush_virt_range(cmd_buf, 0), which reads the byte before the buffer and faults on the vmap guard page. EXEC_CMD is reachable by any process that can open the render node. Reject the request instead. Fixes: 8ed8b0239617 ("accel/amdxdna: Add debug prints for command submission") Signed-off-by: Taimuraz Kaitmazov Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260818000019.369366-1-taimuraz@kaitmazov.com --- drivers/accel/amdxdna/aie2_message.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/amdxdna/aie2_message.c b/drivers/accel/amdxdna/aie2_message.c index dfe0fbdf066d..b4c49259a1a2 100644 --- a/drivers/accel/amdxdna/aie2_message.c +++ b/drivers/accel/amdxdna/aie2_message.c @@ -994,7 +994,7 @@ int aie2_cmdlist_multi_execbuf(struct amdxdna_hwctx *hwctx, } ccnt = payload->command_count; - if (payload_len < struct_size(payload, data, ccnt)) { + if (!ccnt || payload_len < struct_size(payload, data, ccnt)) { XDNA_DBG(xdna, "Invalid command count %d", ccnt); return -EINVAL; } From 7e33ba3a1d48c2d20ed270dec9d2d08332585c8e Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Thu, 20 Aug 2026 02:08:52 +0300 Subject: [PATCH 0099/2366] accel/amdxdna: put the chained BO when its mapping fails amdxdna_cmd_set_error() looks up the first BO of a command chain, which takes a reference, and drops it at the end of the function. The mapping of that BO is established in between, and the failure path returns without the put, so the reference is leaked. Ordinary use does not reach it. The chain has been submitted before any of this runs, so aie2_cmdlist_fill_slot() has already called amdxdna_cmd_get_op() on that BO and amdxdna_gem_vmap() has cached its address. What makes it reachable is that the BO is resolved again by handle here, and the handle is userspace's to recycle: closing it after submission and importing a dma-buf whose exporter implements no vmap onto the same id leaves amdxdna_gem_get_obj() returning an object this cannot map, since prime_import() types every import AMDXDNA_BO_SHARE. Fixes: d76856beb4a4 ("accel/amdxdna: Refactor GEM BO handling and add helper APIs for address retrieval") Signed-off-by: Taimuraz Kaitmazov Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260819230852.287751-1-taimuraz@kaitmazov.com --- drivers/accel/amdxdna/amdxdna_ctx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/accel/amdxdna/amdxdna_ctx.c b/drivers/accel/amdxdna/amdxdna_ctx.c index 31a414c3f0d9..888e857ec558 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.c +++ b/drivers/accel/amdxdna/amdxdna_ctx.c @@ -183,8 +183,10 @@ int amdxdna_cmd_set_error(struct amdxdna_gem_obj *abo, if (!abo) return -EINVAL; cmd = amdxdna_gem_vmap(abo); - if (!cmd) + if (!cmd) { + amdxdna_gem_put_obj(abo); return -ENOMEM; + } } memset(cmd->data, 0xff, abo->mem.size - sizeof(*cmd)); From 909a3f0e9d8b0d8001cf6e99808f88eca8e44328 Mon Sep 17 00:00:00 2001 From: Cheng Lingfei Date: Mon, 24 Aug 2026 20:45:46 +0800 Subject: [PATCH 0100/2366] docs: cgroup-v2: fix misc.events key format description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In misc cgroup, misc.events does not output a simple "max" key. Instead, each registered misc resource outputs a separate key suffixed with ".max" (i.e., ".max"). Update the documentation to clarify that the entry key is ".max". Suggested-by: Michal Koutný Signed-off-by: Cheng Lingfei Signed-off-by: Tejun Heo --- Documentation/admin-guide/cgroup-v2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst index 0a4f4eb15626..2ec582985b5b 100644 --- a/Documentation/admin-guide/cgroup-v2.rst +++ b/Documentation/admin-guide/cgroup-v2.rst @@ -3019,7 +3019,7 @@ Miscellaneous controller provides 3 interface files. If two misc resources (res_ change in this file generates a file modified event. All fields in this file are hierarchical. - max + .max The number of times the cgroup's resource usage was about to go over the max boundary. From 2bf404b1bd94f50747443234c0a4a5e18e2569bf Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Mon, 24 Aug 2026 10:01:38 +0800 Subject: [PATCH 0101/2366] selftests/cgroup: Drop invalid boot isolation comparison check_isolcpus() clears ISOLCPUS before rebuilding it from sched domain data. Comparing that empty value with /sys/devices/system/cpu/isolated makes the test fail whenever isolcpus=domain is present. That sysfs file is generated from HK_TYPE_DOMAIN_BOOT and does not change when cpuset updates HK_TYPE_DOMAIN. Re-reading it cannot validate dynamic housekeeping updates. The cpuset.cpus.isolated and sched domain checks already cover the two dynamic interfaces, so remove the invalid comparison. This can be reproduced on a kernel booted with isolcpus=domain,15: # tools/testing/selftests/cgroup/test_cpuset_prs.sh The test fails its first state-matrix isolation check before the change and continues past that check afterward. Fixes: 6df415aa46ec ("cgroup/cpuset: Defer housekeeping_update() calls from CPU hotplug to workqueue") Signed-off-by: Guopeng Zhang Reviewed-by: Waiman Long Signed-off-by: Tejun Heo --- tools/testing/selftests/cgroup/test_cpuset_prs.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh index da8f7b920178..fdb3185570d4 100755 --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh @@ -797,7 +797,6 @@ check_isolcpus() EXPECTED_ISOLCPUS=$1 ISCPUS=${CGROUP2}/cpuset.cpus.isolated ISOLCPUS=$(cat $ISCPUS) - HKICPUS=$(cat /sys/devices/system/cpu/isolated) LASTISOLCPU= SCHED_DOMAINS=/sys/kernel/debug/sched/domains if [[ $EXPECTED_ISOLCPUS = . ]] @@ -835,11 +834,6 @@ check_isolcpus() ISOLCPUS= EXPECTED_ISOLCPUS=$EXPECTED_SDOMAIN - # - # The inverse of HK_TYPE_DOMAIN cpumask in $HKICPUS should match $ISOLCPUS - # - [[ "$ISOLCPUS" != "$HKICPUS" ]] && return 1 - # # Use the sched domain in debugfs to check isolated CPUs, if available # From 6c37d7e074a4be1ba8da59f4ed5df8977b3daa43 Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Mon, 24 Aug 2026 10:01:39 +0800 Subject: [PATCH 0102/2366] cgroup/cpuset: Preserve boot-isolated CPUs on partition release isolated_cpus tracks CPUs isolated with isolcpus= as well as CPUs in isolated cpuset partitions. When an isolated partition is released, isolated_cpus_update() removes its whole CPU mask. This also clears CPUs which were already isolated at boot. This can be reproduced on a cgroup v2 system booted with isolcpus=domain,15: cd /sys/fs/cgroup echo +cpuset > cgroup.subtree_control mkdir cpuset-repro echo 15 > cpuset-repro/cpuset.cpus echo isolated > cpuset-repro/cpuset.cpus.partition echo member > cpuset-repro/cpuset.cpus.partition cat cpuset.cpus.isolated CPU 15 is absent before the change. It must remain in cpuset.cpus.isolated after the partition is released. Update isolated_cpus one CPU at a time and keep CPUs outside the boot-time domain housekeeping mask isolated. Fixes: c188f33c864e ("cgroup/cpuset: Account for boot time isolated CPUs") Signed-off-by: Guopeng Zhang Acked-by: Waiman Long Signed-off-by: Tejun Heo --- kernel/cgroup/cpuset.c | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 0c34013eda8e..8f24171b6055 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -1259,6 +1259,28 @@ static void reset_partition_data(struct cpuset *cs) cpumask_copy(cs->effective_cpus, parent->effective_cpus); } +/* Return true if isolated_cpus changes. */ +static bool isolated_cpu_update(int new_prs, int cpu) +{ + lockdep_assert_held(&callback_lock); + lockdep_assert_held(&cpuset_mutex); + + if (new_prs == PRS_ISOLATED) { + if (cpumask_test_cpu(cpu, isolated_cpus)) + return false; + cpumask_set_cpu(cpu, isolated_cpus); + return true; + } + + /* CPUs isolated at boot must remain isolated. */ + if (!cpumask_test_cpu(cpu, + housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT)) || + !cpumask_test_cpu(cpu, isolated_cpus)) + return false; + cpumask_clear_cpu(cpu, isolated_cpus); + return true; +} + /* * isolated_cpus_update - Update the isolated_cpus mask * @old_prs: old partition_root_state @@ -1267,19 +1289,16 @@ static void reset_partition_data(struct cpuset *cs) */ static void isolated_cpus_update(int old_prs, int new_prs, struct cpumask *xcpus) { + bool updated = false; + int cpu; + WARN_ON_ONCE(old_prs == new_prs); lockdep_assert_held(&callback_lock); lockdep_assert_held(&cpuset_mutex); - if (new_prs == PRS_ISOLATED) { - if (cpumask_subset(xcpus, isolated_cpus)) - return; - cpumask_or(isolated_cpus, isolated_cpus, xcpus); - } else { - if (!cpumask_intersects(xcpus, isolated_cpus)) - return; - cpumask_andnot(isolated_cpus, isolated_cpus, xcpus); - } - update_housekeeping = true; + for_each_cpu(cpu, xcpus) + updated |= isolated_cpu_update(new_prs, cpu); + if (updated) + update_housekeeping = true; } /* From 87d347a8c8545a9234d1dd215023064413284c34 Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Mon, 24 Aug 2026 10:01:40 +0800 Subject: [PATCH 0103/2366] selftests/cgroup: Add test for preserving boot-isolated CPUs Put a CPU isolated at boot into an isolated partition, change the partition back to member and check that the CPU remains isolated. Signed-off-by: Guopeng Zhang Reviewed-by: Waiman Long Signed-off-by: Tejun Heo --- .../selftests/cgroup/test_cpuset_prs.sh | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh index fdb3185570d4..131d8b4551ef 100755 --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh @@ -1155,6 +1155,63 @@ test_isolated() pause 0.05 } +# +# Select an online CPU isolated from scheduler domains at boot. +# $1: test name used in the skip message +# +get_boot_isolated_cpu() +{ + TEST_NAME=$1 + BOOT_ISOLATED_FILE=/sys/devices/system/cpu/isolated + + [[ -r $BOOT_ISOLATED_FILE ]] || { + echo "$TEST_NAME test SKIPPED: boot isolation state unavailable" + return 1 + } + BOOT_CPUS=$(cat $BOOT_ISOLATED_FILE) + [[ -n "$BOOT_CPUS" ]] || { + echo "$TEST_NAME test SKIPPED: no boot-isolated CPU" + return 1 + } + + BOOT_CPU=$(echo "$BOOT_CPUS" | sed -e 's/[,-].*//') + CPU_ONLINE=/sys/devices/system/cpu/cpu${BOOT_CPU}/online + [[ ! -e $CPU_ONLINE || $(cat $CPU_ONLINE) -eq 1 ]] || { + echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is offline" + return 1 + } +} + +# +# A CPU isolated at boot must stay isolated after it is released by a dynamic +# isolated partition. +# +test_boot_isolated() +{ + TEST_NAME="Boot-isolated CPU partition release" + get_boot_isolated_cpu "$TEST_NAME" || return 0 + echo "Running $TEST_NAME test ..." + + cd $CGROUP2/test + echo member > cpuset.cpus.partition + echo $BOOT_CPU > cpuset.cpus + [[ $(cat cpuset.cpus.effective) = "$BOOT_CPU" ]] || { + echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is unavailable" + echo "" > cpuset.cpus + cd $CGROUP2 + return 0 + } + test_partition isolated + test_partition member + check_isolcpus "." || { + echo "Boot-isolated CPU $BOOT_CPU was lost after partition release" + exit 1 + } + echo "" > cpuset.cpus + cd $CGROUP2 + echo "$TEST_NAME test PASSED." +} + # # Wait for inotify event for the given file and read it # $1: cgroup file to wait for @@ -1226,5 +1283,6 @@ trap cleanup 0 2 3 6 run_state_test TEST_MATRIX run_remote_state_test REMOTE_TEST_MATRIX test_isolated +test_boot_isolated test_inotify echo "All tests PASSED." From 6586705bc2dc06908309bf65d79efa53a347c9f0 Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Mon, 24 Aug 2026 21:21:16 +0800 Subject: [PATCH 0104/2366] docs/sched_ext: document that cgroup CPU knobs are scheduler-dependent The scheduler core communicates the initial cpu controller settings to the BPF scheduler through ops.cgroup_init() and reports subsequent changes through the corresponding ops.cgroup_set_*() callbacks. Whether and how a knob takes effect is up to the loaded scheduler: it may implement the corresponding callback partially or not at all, so cpu.max, cpu.weight and friends can silently have no effect. Document this in the basics section of sched-ext.rst. Signed-off-by: Tao Cui Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- Documentation/scheduler/sched-ext.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Documentation/scheduler/sched-ext.rst b/Documentation/scheduler/sched-ext.rst index 35b550671ca7..794ae80b3ba3 100644 --- a/Documentation/scheduler/sched-ext.rst +++ b/Documentation/scheduler/sched-ext.rst @@ -242,6 +242,21 @@ optional. The following modified excerpt is from .name = "simple", }; +Scheduler-Dependent Knobs +------------------------- + +The fair-class scheduler enforces CPU controller settings such as +``cpu.max``, ``cpu.weight`` and ``cpu.idle``. For sched_ext tasks, the +scheduler core communicates these settings to the BPF scheduler +through ``ops.cgroup_init()`` and reports subsequent changes through +the corresponding ``ops.cgroup_set_*()`` callbacks. Similarly, per-task +nice changes are converted to weights and reported through +``ops.set_weight()``. + +Each BPF scheduler is responsible for implementing the scheduling +semantics of these settings and may choose to ignore them. Consult the +loaded scheduler's documentation before relying on these controls. + Dispatch Queues --------------- From cf9c8aaea0d47410df8708bec195889f3a85cd3c Mon Sep 17 00:00:00 2001 From: Yao Kai Date: Mon, 24 Aug 2026 11:58:52 +0800 Subject: [PATCH 0105/2366] workqueue: Fix unbound pool lifetime for pending pwqs KASAN reports a use-after-free of an unbound worker_pool in node_activate_pending_pwq(): BUG: KASAN: slab-use-after-free in _raw_spin_trylock+0x6d/0x120 Read of size 4 at addr ffff8880089ce000 by task kworker/u22:0/318 CPU: 1 UID: 0 PID: 318 Comm: kworker/u22:0 Not tainted 7.2.0 #1 PREEMPT(lazy) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014 Workqueue: 0x0 (flush-8:0) Call Trace: dump_stack_lvl+0x53/0x70 print_report+0xce/0x610 kasan_report+0xce/0x100 _raw_spin_trylock+0x6d/0x120 pwq_dec_nr_in_flight+0x4b4/0xcb0 process_one_work+0x921/0x11a0 worker_thread+0x4d0/0xd20 kthread+0x2de/0x3c0 ret_from_fork+0x3aa/0x620 ret_from_fork_asm+0x1a/0x30 Allocated by task 311: alloc_pwq+0x439/0xca0 apply_wqattrs_prepare+0x75e/0xd10 apply_workqueue_attrs_locked+0x44/0xa0 wq_nice_store+0x350/0x450 Freed by task 0: kfree+0x127/0x3b0 rcu_core+0x523/0x1780 handle_softirqs+0x1b3/0x610 Last potentially related work creation: put_unbound_pool+0x3f3/0x7d0 pwq_release_workfn+0x494/0x8e0 kthread_worker_fn+0x1ff/0x790 Canceling the last inactive work skips pwq_dec_nr_active(), so an empty pwq can remain on pending_pwqs when its refcnt reaches zero. pwq_release_workfn() currently puts the pool before removing that pwq. If this drops the last pool reference, the pool can be RCU-freed while the pwq remains reachable, and node_activate_pending_pwq() may trylock the freed pool->lock. Remove the pwq from pending_pwqs before putting the pool. Fixes: 5797b1c18919 ("workqueue: Implement system-wide nr_active enforcement for unbound workqueues") Cc: stable@vger.kernel.org Signed-off-by: Yao Kai Signed-off-by: Tejun Heo --- kernel/workqueue.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index f2aed36cf7c0..0ee73dcd4a14 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -5294,12 +5294,6 @@ static void pwq_release_workfn(struct kthread_work *work) mutex_unlock(&wq->mutex); } - if (!is_percpu_pool(pool)) { - mutex_lock(&wq_pool_mutex); - put_unbound_pool(pool); - mutex_unlock(&wq_pool_mutex); - } - if (!list_empty(&pwq->pending_node)) { struct wq_node_nr_active *nna = wq_node_nr_active(pwq->wq, pwq->pool->node); @@ -5309,6 +5303,12 @@ static void pwq_release_workfn(struct kthread_work *work) raw_spin_unlock_irq(&nna->lock); } + if (!is_percpu_pool(pool)) { + mutex_lock(&wq_pool_mutex); + put_unbound_pool(pool); + mutex_unlock(&wq_pool_mutex); + } + kfree_rcu(pwq, rcu); /* From 7ab64476a610fe65858fdc37c7a30caa13e334ac Mon Sep 17 00:00:00 2001 From: GuoHan Zhao Date: Thu, 16 Jul 2026 14:52:19 +0800 Subject: [PATCH 0106/2366] accel/ethosu: check MMIO mapping errors in probe devm_platform_ioremap_resource() returns an error pointer when the register resource cannot be mapped. ethosu_probe() stores it and continues until initialization dereferences it through MMIO accessors. Return the mapping error before initializing the device. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Cc: stable@vger.kernel.org Signed-off-by: GuoHan Zhao Link: https://patch.msgid.link/20260716065219.931088-1-zhaoguohan@kylinos.cn Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c index ed9c748a54ad..b2901eb8a7a0 100644 --- a/drivers/accel/ethosu/ethosu_drv.c +++ b/drivers/accel/ethosu/ethosu_drv.c @@ -342,6 +342,8 @@ static int ethosu_probe(struct platform_device *pdev) dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); ethosudev->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ethosudev->regs)) + return PTR_ERR(ethosudev->regs); ethosudev->num_clks = devm_clk_bulk_get_all(&pdev->dev, ðosudev->clks); if (ethosudev->num_clks < 0) From db9deec5a345abc538d081fb221dc0b00a9695bd Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Mon, 24 Aug 2026 17:26:11 +0200 Subject: [PATCH 0107/2366] accel: ethosu: Don't read the U65 rounding mode as a storage mode Bits 15:14 of NPU_SET_{IFM,OFM}_PRECISION select the activation storage mode on U85 only. On U65 the same field holds the rounding mode, and the command stream parser has read it as a storage mode since the driver was added. That went unnoticed while unknown values fell through the switch, but now that they are rejected, every U65 command stream that asks for natural rounding (2) fails CMDSTREAM_BO_CREATE with -EINVAL. Mesa emits it for average pooling, concatenation, split, unpack, strided slice, LUT and argmax, which is 72 failures of the Teflon test suite on an i.MX93. Truncating rounding (1) is misread as well: it picks the two-tile address path and computes a bogus feature map size from tile bases the command stream never set. Read the field as a storage mode only on the hardware where it is one. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Fixes: 6b7e0066294d ("accel: ethosu: Handle U85 internal chaining buffer") Assisted-by: Claude:claude-opus-5 Signed-off-by: Tomeu Vizoso Link: https://patch.msgid.link/20260824152612.751007-1-tomeu@tomeuvizoso.net Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_gem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c index d50fed64d4d9..fa37a190e9ff 100644 --- a/drivers/accel/ethosu/ethosu_gem.c +++ b/drivers/accel/ethosu/ethosu_gem.c @@ -204,7 +204,7 @@ static u64 feat_matrix_length(struct ethosu_device *edev, struct feat_matrix *fm, u32 x, u32 y, u32 c, bool ofm) { - u32 element_size, storage = fm->precision >> 14; + u32 element_size, storage = ethosu_is_u65(edev) ? 0 : fm->precision >> 14; int tile = 0; u64 addr; From 1c942462c3969b287a86ab6dbb143324289d564e Mon Sep 17 00:00:00 2001 From: Vadim Klishko Date: Mon, 27 Jul 2026 22:17:18 -0600 Subject: [PATCH 0108/2366] HID: i2c-hid: Add a quirk for a Cirque I2C device. Cirque touchpads with PID D0C1 generate an error when probed by the I2C HID driver, resulting in no hidraw device created. Adding I2C_HID_QUIRK_NO_IRQ_AFTER_RESET fixes the issue. Signed-off-by: Vadim Klishko Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 1 + drivers/hid/i2c-hid/i2c-hid-core.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 341bf587863b..b3aca5aa9176 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -338,6 +338,7 @@ #define I2C_VENDOR_ID_CIRQUE 0x0488 #define I2C_PRODUCT_ID_CIRQUE_1063 0x1063 +#define I2C_PRODUCT_ID_CIRQUE_D0C1 0xD0C1 #define USB_VENDOR_ID_CJTOUCH 0x24b8 #define USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0020 0x0020 diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 0e725a0f0abe..0ff07fdab442 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -136,6 +136,8 @@ static const struct i2c_hid_quirks { I2C_HID_QUIRK_BAD_INPUT_SIZE }, { I2C_VENDOR_ID_CIRQUE, I2C_PRODUCT_ID_CIRQUE_1063, I2C_HID_QUIRK_NO_SLEEP_ON_SUSPEND }, + { I2C_VENDOR_ID_CIRQUE, I2C_PRODUCT_ID_CIRQUE_D0C1, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, /* * Without additional power on command, at least some QTEC devices send garbage */ From e8e60b6439eed340a611e9d7a5b9bcbd0ef62725 Mon Sep 17 00:00:00 2001 From: Dave Carey Date: Thu, 30 Jul 2026 08:43:36 -0400 Subject: [PATCH 0109/2366] HID: multitouch: Fix stale MT slots when contact count drops to zero MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The INGENIC 17EF:6161 touchscreen (Lenovo Yoga Book 9 14IAH10) reports HID_DG_CONTACTCOUNT=0 in the frame immediately following the last finger lift rather than omitting the frame entirely. In mt_touch_report() the existing code only updates num_expected when contact_count is non-zero, so a zero contact count on the first packet of a new frame leaves num_expected at its previous value (e.g. 2 for a two-finger gesture). The sync check "num_received >= num_expected" then evaluates "0 >= 2" and never fires, preventing INPUT_MT_DROP_UNUSED from releasing the stale slots. Those slots remain active in the kernel MT layer until the next touch, at which point they are released in a batch alongside the new contact — causing the userspace event consumer to miss the intervening finger-up sequence and corrupt its gesture session state. Fix by resetting num_expected to 0 when contact_count is zero and num_received is still 0 (i.e., this is the first and only packet of the frame, not a continuation packet in a multi-packet sequence). With num_expected=0 the sync check "0 >= 0" fires immediately, calling input_mt_sync_frame() which drops the stale slots via INPUT_MT_DROP_UNUSED. The num_received==0 guard is critical: continuation packets in a multi-packet frame arrive after at least one contact has already been processed (num_received>0), so they are correctly excluded from this path and the existing multi-packet logic is unaffected. Signed-off-by: Dave Carey Tested-by: Dave Carey Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-multitouch.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 2c41bacab1ca..451c7324e6a0 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -1321,21 +1321,18 @@ static void mt_touch_report(struct hid_device *hid, * Includes multi-packet support where subsequent * packets are sent with zero contactcount. */ - if (contact_count >= 0) { + if (contact_count > 0) + app->num_expected = contact_count; + else if (app->num_received == 0 && app->prev_scantime != scantime) { /* + * New multi-report frame: + * * For Win8 PTPs the first packet (td->num_received == 0) may * have a contactcount of 0 if there only is a button event. - * We double check that this is not a continuation packet - * of a possible multi-packet frame be checking that the - * timestamp has changed. + * + * Some other devices use a sentinel frame with 0 to release all contacts */ - if ((app->quirks & MT_QUIRK_WIN8_PTP_BUTTONS) && - app->num_received == 0 && - app->prev_scantime != scantime) - app->num_expected = contact_count; - /* A non 0 contact count always indicates a first packet */ - else if (contact_count) - app->num_expected = contact_count; + app->num_expected = 0; } app->prev_scantime = scantime; From ffe0486b139e45cd9c9ca2584f04a1910fe4f8a6 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 19 Aug 2026 15:38:53 +0200 Subject: [PATCH 0110/2366] console: fix /dev/kmsg reference in flags kernel doc Fix typo in the CON_EXTENDED flag kernel doc which is supposed to refer to '/dev/kmsg'. Fixes: 717a5651b109 ("console: Use BIT() macros for @flags values") Signed-off-by: Johan Hovold Reviewed-by: Petr Mladek Link: https://patch.msgid.link/20260819133853.286658-1-johan@kernel.org Signed-off-by: Petr Mladek --- include/linux/console.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/console.h b/include/linux/console.h index d624200cfc17..502d1abe3f50 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -173,7 +173,7 @@ static inline void con_debug_leave(void) { } * @CON_BRL: Indicates a braille device which is exempt from * receiving the printk spam for obvious reasons. * @CON_EXTENDED: The console supports the extended output format of - * /dev/kmesg which requires a larger output buffer. + * /dev/kmsg which requires a larger output buffer. * @CON_SUSPENDED: Indicates if a console is suspended. If true, the * printing callbacks must not be called. * @CON_NBCON: Console can operate outside of the legacy style console_lock From a8e04f3f894ccb52cfcd7e60125a9f35da4a616d Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Mon, 13 Jul 2026 11:34:14 +0200 Subject: [PATCH 0111/2366] HID: wacom: validate report length in wacom_intuos_pro2_bt_irq wacom_intuos_pro2_bt_irq() receives the wire report length in `len` but never consults it before parsing. After the report-id gate it unconditionally calls wacom_intuos_pro2_bt_pen() and then, selected by features.type, a fixed chain of sub-parsers, none of which receive `len`: wacom_intuos_pro2_bt_pen(wacom); if (type == INTUOSP2_BT || type == INTUOSP2S_BT) { wacom_intuos_pro2_bt_touch(wacom); wacom_intuos_pro2_bt_pad(wacom); wacom_intuos_pro2_bt_battery(wacom); } else { wacom_intuos_gen3_bt_pad(wacom); wacom_intuos_gen3_bt_battery(wacom); } Each sub-parser dereferences wacom->data at fixed offsets. The furthest byte touched on each branch is: INTUOSP2_BT / INTUOSP2S_BT: wacom_intuos_pro2_bt_pad() reads data[285] (the touchring byte), so the report must be at least 286 bytes; INTUOSHT3_BT ("gen3"): wacom_intuos_gen3_bt_battery() reads data[45], so the report must be at least 46 bytes. features.type is selected from the VID/PID id_table entry and wacom_setup_device_quirks() force-registers the pen/pad/touch inputs for that type independent of the report descriptor, so a malicious or malfunctioning paired/spoofed Bluetooth peripheral can advertise that VID/PID and send an undersized report that still satisfies the data[0] == 0x80/0x81 gate. The driver then reads past the received report and forwards the bytes to userspace via evdev (MSC_SERIAL / ABS_MISC / ABS_WHEEL on the pen and pad input nodes), an out-of-bounds read with a concrete userspace read-back channel, and a true out-of-bounds read on transports whose backing buffer is sized to the (small) report descriptor rather than a fixed-size staging buffer. This is the same class of bug commit 2f1763f62909 ("HID: wacom: fix out-of-bounds read in wacom_intuos_bt_irq") already hardened in the sibling wacom_intuos_bt_irq(), which guards each report id against its minimum length before parsing. Guard wacom_intuos_pro2_bt_irq() the same way: before parsing, reject reports shorter than the furthest offset the selected branch actually dereferences, warn, and bail out. Because the whole pen/touch/pad/ battery chain runs unconditionally per branch, a single up-front check against the maximum offset (286 bytes for INTUOSP2_BT/INTUOSP2S_BT, 46 bytes for the gen3 branch) bounds every sub-parser. Returning 0 on a short report also skips those calls for the same malformed report, which is the safe, conservative behavior. Fixes: 4922cd26f03c ("HID: wacom: Support 2nd-gen Intuos Pro's Bluetooth classic interface") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Hashimov Assisted-by: AuditCode-AI:2026.07 Acked-by: Jason Gerecke Signed-off-by: Jiri Kosina --- drivers/hid/wacom_wac.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index a29bf051ada7..8feb8027be95 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -1550,6 +1550,19 @@ static int wacom_intuos_pro2_bt_irq(struct wacom_wac *wacom, size_t len) return 0; } + if (wacom->features.type == INTUOSP2_BT || + wacom->features.type == INTUOSP2S_BT) { + if (len < 286) { + dev_warn(wacom->pen_input->dev.parent, + "Pro2 BT report too short: %zu bytes\n", len); + return 0; + } + } else if (len < 46) { + dev_warn(wacom->pen_input->dev.parent, + "Pro2 BT report too short: %zu bytes\n", len); + return 0; + } + wacom_intuos_pro2_bt_pen(wacom); if (wacom->features.type == INTUOSP2_BT || wacom->features.type == INTUOSP2S_BT) { From 55a4c98abb9694b067c6a031d11501f06b6b523c Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Sat, 1 Aug 2026 10:12:57 +0300 Subject: [PATCH 0112/2366] ufs: create the root dentry after loading cylinder metadata ufs_fill_super() installed sb->s_root before it loaded the cylinder group structures for a writable mount: sb->s_root = d_make_root(inode); ... if (!sb_rdonly(sb)) if (!ufs_read_cylinder_structures(sb)) goto failed; When ufs_read_cylinder_structures() failed, the error path freed the in-core superblock information and set sb->s_fs_info to NULL while sb->s_root stayed installed. get_tree_bdev() then reached deactivate_locked_super(), and because s_root was present, generic_shutdown_super() called sync_filesystem() and the put_super operation. Both dereference UFS_SB(sb), which is now NULL, so a mount that fails only while reading the cylinder groups oopses during teardown. A crafted image whose first cylinder group cannot be read reaches this path. Load the cylinder group metadata first and create the root dentry last, so the superblock is published to the VFS only once it is fully set up. ufs_setup_cstotal() and ufs_read_cylinder_structures() take only the super_block and do not use the root inode, so the reordering is safe. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Link: https://patch.msgid.link/20260801071306.59484-2-ali@iusegentoo.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/ufs/super.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/fs/ufs/super.c b/fs/ufs/super.c index 6dcf6d048cce..3569ac92b065 100644 --- a/fs/ufs/super.c +++ b/fs/ufs/super.c @@ -1199,6 +1199,15 @@ static int ufs_fill_super(struct super_block *sb, struct fs_context *fc) sb->s_maxbytes = ufs_max_bytes(sb); sb->s_max_links = UFS_LINK_MAX; + ufs_setup_cstotal(sb); + /* + * Read cylinder group structures + */ + if (!sb_rdonly(sb)) + if (!ufs_read_cylinder_structures(sb)) + goto failed; + + /* create the root dentry last, once UFS_SB(sb) is fully set up */ inode = ufs_iget(sb, UFS_ROOTINO); if (IS_ERR(inode)) { ret = PTR_ERR(inode); @@ -1210,14 +1219,6 @@ static int ufs_fill_super(struct super_block *sb, struct fs_context *fc) goto failed; } - ufs_setup_cstotal(sb); - /* - * Read cylinder group structures - */ - if (!sb_rdonly(sb)) - if (!ufs_read_cylinder_structures(sb)) - goto failed; - UFSD("EXIT\n"); return 0; From c9d263be26806d388129fab8c6904bed197fc6af Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Sat, 1 Aug 2026 10:12:58 +0300 Subject: [PATCH 0113/2366] ufs: validate cylinder group metadata before caching it ufs_read_cylinder() copies the cylinder group index and the rotor positions straight from the on-disk group and caches them without any check: ucpi->c_cgx = fs32_to_cpu(sb, ucg->cg_cgx); ucpi->c_rotor = fs32_to_cpu(sb, ucg->cg_rotor); ucpi->c_frotor = fs32_to_cpu(sb, ucg->cg_frotor); ucpi->c_irotor = fs32_to_cpu(sb, ucg->cg_irotor); They are then used as indices during allocation and free: - c_cgx indexes the cylinder summary array as UFS_SB(sb)->fs_cs(ucpi->c_cgx), so a value past s_ncg writes a 32 bit count outside the s_csp allocation. - c_frotor becomes a bitmap scan start, start = c_frotor >> 3, and then length = ((s_fpg + 7) >> 3) - start. A start beyond the block bitmap wraps the unsigned length to a huge value, so ubh_scanc() walks far past the cylinder group buffers. c_irotor drives the inode bitmap the same way. A crafted image can set any of these freely, turning an ordinary allocation into an out of bounds access. Reject a cylinder group whose recorded index does not match the group being read, or whose rotors fall outside the group, before the metadata is cached. Valid filesystems keep cg_cgx equal to the group number and the rotors within the group, so only malformed images are rejected. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Link: https://patch.msgid.link/20260801071306.59484-3-ali@iusegentoo.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/ufs/cylinder.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/ufs/cylinder.c b/fs/ufs/cylinder.c index a2813270c303..b930ee1cf853 100644 --- a/fs/ufs/cylinder.c +++ b/fs/ufs/cylinder.c @@ -68,6 +68,16 @@ static bool ufs_read_cylinder(struct super_block *sb, ucpi->c_clustersumoff = fs32_to_cpu(sb, ucg->cg_u.cg_44.cg_clustersumoff); ucpi->c_clusteroff = fs32_to_cpu(sb, ucg->cg_u.cg_44.cg_clusteroff); ucpi->c_nclusterblks = fs32_to_cpu(sb, ucg->cg_u.cg_44.cg_nclusterblks); + + /* these on-disk values become array and bitmap indices */ + if (ucpi->c_cgx != cgno || + ucpi->c_rotor >= uspi->s_fpg || + ucpi->c_frotor >= uspi->s_fpg || + ucpi->c_irotor >= uspi->s_ipg) { + ufs_error(sb, __func__, + "inconsistent metadata in cylinder group %u\n", cgno); + goto failed; + } UFSD("EXIT\n"); return true; From 08edfb34ee9ca54383970c65ed3a6013e84f5e16 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Sat, 1 Aug 2026 04:39:32 +0300 Subject: [PATCH 0114/2366] ufs: do not treat unreadable directory blocks as empty ufs_empty_dir() scans every directory block to decide whether a directory is empty before rmdir() removes it. When ufs_get_folio() cannot read or validate a block it returns an error pointer, and the loop currently skips that block with continue and keeps scanning the remaining blocks. If none of the readable blocks hold an entry, the function returns 1 and the caller unlinks the directory. A directory whose contents live in a block that cannot be read, for example because of an I/O error or corrupted directory metadata, is therefore seen as empty and removed, losing the entries it still holds. Follow the ext2 behaviour and treat an unreadable block as a reason to consider the directory not empty, so rmdir() fails instead of discarding data that could not be verified. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Ali Ahmet Memis Link: https://patch.msgid.link/20260801013942.279992-1-ali@iusegentoo.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/ufs/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ufs/dir.c b/fs/ufs/dir.c index e62fe5667671..ce43cf20b07c 100644 --- a/fs/ufs/dir.c +++ b/fs/ufs/dir.c @@ -590,7 +590,7 @@ int ufs_empty_dir(struct inode * inode) kaddr = ufs_get_folio(inode, i, &folio); if (IS_ERR(kaddr)) - continue; + return 0; de = (struct ufs_dir_entry *)kaddr; kaddr += ufs_last_byte(inode, i) - UFS_DIR_REC_LEN(1); From fe967191e5851ea79818c5fe4e781c3882139218 Mon Sep 17 00:00:00 2001 From: Moritz Tanner Date: Fri, 21 Aug 2026 10:54:51 +0200 Subject: [PATCH 0115/2366] fs: don't return -EINVAL for successful nested thaw Commit 7366f8b6fc6a ("fs: handle freezing from multiple devices") replaced the freeze_holders bitmask with per-holder counters to allow nested freezes. In the bitmask version, a thaw that released a shared hold while another holder remained returned 0. Since the rework, thaw_super_locked() drops the freeze reference via freeze_dec() but then returns -EINVAL when other freezers remain, misinforming the caller: the thaw did succeed, the superblock just stays frozen for the remaining holders. This breaks bdev-initiated freezing. When a filesystem is frozen with FIFREEZE and additionally frozen via bdev_freeze() -- which nests by design, see fs_bdev_freeze() -- the subsequent bdev_thaw() receives -EINVAL from the holder op although its freeze reference was dropped, and therefore keeps bd_fsfreeze_count elevated. Then device-mapper's unlock_fs() ignores bdev_thaw()'s return value, so nothing rebalances the count. After the user's FITHAW and umount, the block device can never be mounted again: dm-1: Can't mount, blockdev is frozen There is no way for userspace to drop the leaked count; only destroying the block device (or a reboot) recovers the device. Reproducer (any kernel since v6.8): dmsetup create dut --table "0 $(blockdev --getsz "$DEV") linear $DEV 0" mkfs.ext4 /dev/mapper/dut mount /dev/mapper/dut /mnt fsfreeze --freeze /mnt # freeze_ucount == 1 dmsetup suspend dut # bd_fsfreeze_count == 1, ucount == 2 dmsetup resume dut # ucount 2 -> 1, but thaw_super() # returns -EINVAL, so bdev_thaw() # keeps bd_fsfreeze_count at 1 fsfreeze --unfreeze /mnt # filesystem thaws fine umount /mnt mount /dev/mapper/dut /mnt # EBUSY, forever The same happens with fsfreeze held across an LVM snapshot of the origin volume. fs_bdev_thaw()'s documentation already describes the intended semantics: "If this function returns zero it doesn't mean that the filesystem is unfrozen as it may have been frozen multiple times". Restore them by returning 0 when a nested thaw drops its hold while other freezers remain. Thawing without holding a freeze still fails with -EINVAL as may_unfreeze() rejects that case before the reference count is touched. Fixes: 7366f8b6fc6a ("fs: handle freezing from multiple devices") Cc: stable@vger.kernel.org # needs adjustments for < 6.17 (no may_unfreeze()) Signed-off-by: Moritz Tanner Link: https://patch.msgid.link/20260821085451.65206-1-moritz.tanner@linbit.com Tested-by: Lars Ellenberg Reviewed-by: Lars Ellenberg Signed-off-by: Christian Brauner (Amutable) --- fs/super.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/super.c b/fs/super.c index 05e443173038..01db6124e409 100644 --- a/fs/super.c +++ b/fs/super.c @@ -2369,11 +2369,14 @@ static int thaw_super_locked(struct super_block *sb, enum freeze_holder who, goto out_unlock; /* - * All freezers share a single active reference. - * So just unlock in case there are any left. + * All freezers share a single active reference. If other freezers + * remain, drop our hold and report success; the superblock stays + * frozen until the last holder thaws it. */ - if (freeze_dec(sb, who)) + if (freeze_dec(sb, who)) { + error = 0; goto out_unlock; + } if (sb_rdonly(sb)) { sb->s_writers.frozen = SB_UNFROZEN; From 554ab79cfd104fbd76f3be82b0dfa8fc2b324799 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Tue, 28 Apr 2026 23:03:40 -0400 Subject: [PATCH 0116/2366] drm/nouveau/disp/r535: Add scanline position support + head state support That's right! It looks like this never actually got finished, something which I just noticed today when I saw this fun message spamming one of my test machine's kernel logs when enabling display debug output for nouveau: [drm:drm_crtc_vblank_helper_get_vblank_timestamp_internal] crtc 0 : scanoutpos query failed. So it looks like we've been falling back to DRM's core fallback for a while now, whoops. So, while it seems that we do have the option of doing this through GSP - that doesn't seem like a great idea. Mainly because reading this from GSP would involve a lot more latency then we should have for vblank handling due to the RPC communication. So instead of implementing that, just use gv100_head_state and gv100_head_rgpos for implementing .state and .rgpos. It seems to work perfectly fine! Fixes: 9e9944449023 ("drm/nouveau/disp/r535: initial support") Cc: Ben Skeggs Cc: Dave Airlie Cc: Timur Tabi Cc: Ben Skeggs Cc: James Jones Cc: Faith Ekstrand Cc: Suraj Kandpal Cc: Lyude Paul Cc: Aaron Kling Cc: Danilo Krummrich Cc: Zhang Enpei Cc: # v6.7+ Signed-off-by: Lyude Paul Signed-off-by: Dave Airlie Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260429030348.3930866-1-lyude@redhat.com (cherry picked from commit 804cb093b245c752f15d17186e0d404f10303593) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c | 4 ++-- drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h | 2 ++ drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 8 ++------ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c index dbd984da7501..0608266188d3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gv100.c @@ -253,7 +253,7 @@ gv100_head_vblank_get(struct nvkm_head *head) nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000004, 0x00000004); } -static void +void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline) { struct nvkm_device *device = head->disp->engine.subdev.device; @@ -263,7 +263,7 @@ gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline) *hline = nvkm_rd32(device, 0x616334 + hoff) & 0x0000ffff; } -static void +void gv100_head_state(struct nvkm_head *head, struct nvkm_head_state *state) { struct nvkm_device *device = head->disp->engine.subdev.device; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h index 856252bf559a..b642729c254f 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h @@ -53,6 +53,8 @@ void gf119_head_rgclk(struct nvkm_head *, int); int gv100_head_cnt(struct nvkm_disp *, unsigned long *); int gv100_head_new(struct nvkm_disp *, int id); +void gv100_head_state(struct nvkm_head *head, struct nvkm_head_state *state); +void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline); #define HEAD_MSG(h,l,f,a...) do { \ struct nvkm_head *_h = (h); \ diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c index 1155f079b0c3..e77733a5d9c3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c @@ -625,14 +625,10 @@ r535_head_vblank_get(struct nvkm_head *head) nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002); } -static void -r535_head_state(struct nvkm_head *head, struct nvkm_head_state *state) -{ -} - static const struct nvkm_head_func r535_head = { - .state = r535_head_state, + .state = gv100_head_state, + .rgpos = gv100_head_rgpos, .vblank_get = r535_head_vblank_get, .vblank_put = r535_head_vblank_put, }; From c6659e0ffc19b4ef0b3273c185cb8409a154eada Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:13:59 +0400 Subject: [PATCH 0117/2366] drm/nouveau/disp: move GSP head-timing ISR and vblank helpers to tu102.c The GSP-RM display code in rm/r535/disp.c owns a handful of direct MMIO routines: the head-timing (vblank) interrupt handler and the per-head vblank enable/disable. They program display registers, not RM, so they belong with the rest of the per-chip register code in engine/disp/. Move them to tu102.c (Turing is the first GSP-capable generation) as tu102_disp_intr() and tu102_head_vblank_get()/put(), exported for rm/r535/disp.c, which keeps calling them by name for now. No functional change. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-2-mohamedahmedegypt2001@gmail.com --- .../gpu/drm/nouveau/nvkm/engine/disp/head.h | 3 ++ .../gpu/drm/nouveau/nvkm/engine/disp/priv.h | 1 + .../gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 50 ++++++++++++++++++ .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 52 ++----------------- 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h index b642729c254f..986043e87554 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h @@ -56,6 +56,9 @@ int gv100_head_new(struct nvkm_disp *, int id); void gv100_head_state(struct nvkm_head *head, struct nvkm_head_state *state); void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline); +void tu102_head_vblank_get(struct nvkm_head *); +void tu102_head_vblank_put(struct nvkm_head *); + #define HEAD_MSG(h,l,f,a...) do { \ struct nvkm_head *_h = (h); \ nvkm_##l(&_h->disp->engine.subdev, "head-%d: "f"\n", _h->id, ##a); \ diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h index a3fd7cb7c488..722ec340e12a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h @@ -72,6 +72,7 @@ int gv100_disp_wndw_cnt(struct nvkm_disp *, unsigned long *); int gv100_disp_caps_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **); int tu102_disp_init(struct nvkm_disp *); +irqreturn_t tu102_disp_intr(struct nvkm_inth *); void nv50_disp_dptmds_war_2(struct nvkm_disp *, struct dcb_output *); void nv50_disp_dptmds_war_3(struct nvkm_disp *, struct dcb_output *); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c index dcb9f8ba374c..7b70b466fa36 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c @@ -104,6 +104,56 @@ tu102_sor_new(struct nvkm_disp *disp, int id) return nvkm_ior_new_(&tu102_sor, disp, SOR, id, hda & BIT(id)); } +/* The GSP-RM display path leaves head-timing (vblank) interrupts and their + * enables to us. These program the RM head-timing line (bit 1 of the + * per-head enable, not the bit nvkm's own gv100 path uses). + */ +void +tu102_head_vblank_put(struct nvkm_head *head) +{ + struct nvkm_device *device = head->disp->engine.subdev.device; + + nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000); +} + +void +tu102_head_vblank_get(struct nvkm_head *head) +{ + struct nvkm_device *device = head->disp->engine.subdev.device; + + nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002); + nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002); +} + +static void +tu102_disp_intr_head_timing(struct nvkm_disp *disp, int head) +{ + struct nvkm_subdev *subdev = &disp->engine.subdev; + struct nvkm_device *device = subdev->device; + u32 stat = nvkm_rd32(device, 0x611c00 + (head * 0x04)); + + if (stat & 0x00000002) { + nvkm_disp_vblank(disp, head); + + nvkm_wr32(device, 0x611800 + (head * 0x04), 0x00000002); + } +} + +irqreturn_t +tu102_disp_intr(struct nvkm_inth *inth) +{ + struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth); + struct nvkm_subdev *subdev = &disp->engine.subdev; + struct nvkm_device *device = subdev->device; + unsigned long mask = nvkm_rd32(device, 0x611ec0) & 0x000000ff; + int head; + + for_each_set_bit(head, &mask, 8) + tu102_disp_intr_head_timing(disp, head); + + return IRQ_HANDLED; +} + int tu102_disp_init(struct nvkm_disp *disp) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c index e77733a5d9c3..8e57bb6519e5 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c @@ -608,29 +608,12 @@ r535_sor_cnt(struct nvkm_disp *disp, unsigned long *pmask) return 4; } -static void -r535_head_vblank_put(struct nvkm_head *head) -{ - struct nvkm_device *device = head->disp->engine.subdev.device; - - nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000); -} - -static void -r535_head_vblank_get(struct nvkm_head *head) -{ - struct nvkm_device *device = head->disp->engine.subdev.device; - - nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002); - nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002); -} - static const struct nvkm_head_func r535_head = { .state = gv100_head_state, .rgpos = gv100_head_rgpos, - .vblank_get = r535_head_vblank_get, - .vblank_put = r535_head_vblank_put, + .vblank_get = tu102_head_vblank_get, + .vblank_put = tu102_head_vblank_put, }; static struct nvkm_conn * @@ -1404,35 +1387,6 @@ static const struct nvkm_event_func r535_disp_event = { }; -static void -r535_disp_intr_head_timing(struct nvkm_disp *disp, int head) -{ - struct nvkm_subdev *subdev = &disp->engine.subdev; - struct nvkm_device *device = subdev->device; - u32 stat = nvkm_rd32(device, 0x611c00 + (head * 0x04)); - - if (stat & 0x00000002) { - nvkm_disp_vblank(disp, head); - - nvkm_wr32(device, 0x611800 + (head * 0x04), 0x00000002); - } -} - -static irqreturn_t -r535_disp_intr(struct nvkm_inth *inth) -{ - struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth); - struct nvkm_subdev *subdev = &disp->engine.subdev; - struct nvkm_device *device = subdev->device; - unsigned long mask = nvkm_rd32(device, 0x611ec0) & 0x000000ff; - int head; - - for_each_set_bit(head, &mask, 8) - r535_disp_intr_head_timing(disp, head); - - return IRQ_HANDLED; -} - static void r535_disp_fini(struct nvkm_disp *disp, bool suspend) { @@ -1708,7 +1662,7 @@ r535_disp_oneinit(struct nvkm_disp *disp) return ret; ret = nvkm_inth_add(&device->vfn->intr, ret, NVKM_INTR_PRIO_NORMAL, &disp->engine.subdev, - r535_disp_intr, &disp->engine.subdev.inth); + tu102_disp_intr, &disp->engine.subdev.inth); if (ret) return ret; From eb1ffc3dc72d379a41e367a44b99fb61a15bf8ba Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:00 +0400 Subject: [PATCH 0118/2366] drm/nouveau/disp: move the GSP HDMI GCP AVMute write to engine/disp r535_sor_hdmi_audio() pairs two RM controls (a SET_OD_PACKET carrying the same General Control Packet, and the audio mute-stream toggle) with a direct write of the GCP AVMute bit through the SF GCP unit. The controls are RM and stay, but the direct write is register programming and moves next to the other per-chip display code as tu102_sor_hdmi_gcp(). No functional change. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-3-mohamedahmedegypt2001@gmail.com --- drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h | 1 + drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 15 +++++++++++++++ .../drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 9 +-------- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h index 3ba04bead2f9..5d682a774f2d 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h @@ -194,6 +194,7 @@ void gv100_sor_dp_audio_sym(struct nvkm_ior *, int, u16, u32); void gv100_sor_dp_watermark(struct nvkm_ior *, int, u8); extern const struct nvkm_ior_func_hda gv100_sor_hda; +void tu102_sor_hdmi_gcp(struct nvkm_ior *, int, bool); void tu102_sor_dp_vcpi(struct nvkm_ior *, int, u8, u8, u16, u16); int nv50_pior_cnt(struct nvkm_disp *, unsigned long *); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c index 7b70b466fa36..6cfd52c9056f 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c @@ -30,6 +30,21 @@ #include +/* General Control Packet: bracket an audio enable/disable with AVMute + * through the legacy GCP SF unit. Used by the GSP-RM path, which sends the + * equivalent packet via RM as well but keeps the direct write in sync. + */ +void +tu102_sor_hdmi_gcp(struct nvkm_ior *sor, int head, bool enable) +{ + struct nvkm_device *device = sor->disp->engine.subdev.device; + const u32 hdmi = head * 0x400; + + nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000000); + nvkm_wr32(device, 0x6f00cc + hdmi, !enable ? 0x00000001 : 0x00000010); + nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000001); +} + void tu102_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c index 8e57bb6519e5..cd4451e62512 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c @@ -545,16 +545,9 @@ r535_sor_hdmi_ctrl_audio(struct nvkm_outp *outp, bool enable) static void r535_sor_hdmi_audio(struct nvkm_ior *sor, int head, bool enable) { - struct nvkm_device *device = sor->disp->engine.subdev.device; - const u32 hdmi = head * 0x400; - r535_sor_hdmi_ctrl_audio(sor->asy.outp, enable); r535_sor_hdmi_ctrl_audio_mute(sor->asy.outp, !enable); - - /* General Control (GCP). */ - nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000000); - nvkm_wr32(device, 0x6f00cc + hdmi, !enable ? 0x00000001 : 0x00000010); - nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000001); + tu102_sor_hdmi_gcp(sor, head, enable); } static void From 9886aad51f4b5e7082209a153e404bcd8101356c Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:01 +0400 Subject: [PATCH 0119/2366] drm/nouveau/disp: route GSP-RM display MMIO through nvkm_disp_func hooks The GSP-RM display code in rm/r535/disp.c borrows a few register-programming routines from engine/disp (the head-timing interrupt handler, vblank enables, armed head state and scanout position readback, the AVI/VSI infoframe writers and the GCP AVMute write) and so far picked them by name, which means it has to know which chip it runs on the moment a generation changes any of them. Give nvkm_disp_func a .gsp table that each chip fills with exactly those hooks, add tu102_gsp_disp (TU1xx) and ga102_gsp_disp (GA10x onwards) carrying the current functions, hand them to r535_disp_new() instead of the full hardware tables, and make rm/r535/disp.c call through the hooks. The head hooks are a whole nvkm_head_func, so r535_head goes away and the chip's own table is handed to nvkm_head_new_(). r535_sor_hdmi gets infoframe forwarders, r535_sor_hdmi_audio() calls the GCP hook, and the interrupt handler comes from the table. The tables are per chip even though the two currently coincide, so a generation that changes a hook only touches its own file. rm/r535/disp.c no longer contains chip-specific register code, and a new display generation only has to provide its own table. No functional change. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-4-mohamedahmedegypt2001@gmail.com --- .../gpu/drm/nouveau/nvkm/engine/disp/ga102.c | 13 +++++++- .../gpu/drm/nouveau/nvkm/engine/disp/head.h | 1 + .../gpu/drm/nouveau/nvkm/engine/disp/priv.h | 14 +++++++++ .../gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 21 ++++++++++++- .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 31 +++++++++++-------- 5 files changed, 65 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c index ab0a85c92430..820834b5ee9b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c @@ -144,12 +144,23 @@ ga102_disp = { }, }; +static const struct nvkm_disp_func +ga102_gsp_disp = { + .uevent = &gv100_disp_chan_uevent, + .ramht_size = 0x2000, + .gsp.intr = tu102_disp_intr, + .gsp.head = &tu102_gsp_head, + .gsp.hdmi_gcp = tu102_sor_hdmi_gcp, + .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi, + .gsp.hdmi_infoframe_vsi = gv100_sor_hdmi_infoframe_vsi, +}; + int ga102_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, struct nvkm_disp **pdisp) { if (nvkm_gsp_rm(device->gsp)) - return r535_disp_new(&ga102_disp, device, type, inst, pdisp); + return r535_disp_new(&ga102_gsp_disp, device, type, inst, pdisp); return nvkm_disp_new_(&ga102_disp, device, type, inst, pdisp); } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h index 986043e87554..784521c2aca1 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h @@ -58,6 +58,7 @@ void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline); void tu102_head_vblank_get(struct nvkm_head *); void tu102_head_vblank_put(struct nvkm_head *); +extern const struct nvkm_head_func tu102_gsp_head; #define HEAD_MSG(h,l,f,a...) do { \ struct nvkm_head *_h = (h); \ diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h index 722ec340e12a..a9dbda67a7d4 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h @@ -5,6 +5,8 @@ #include #include struct nvkm_head; +struct nvkm_head_func; +struct nvkm_ior; struct nvkm_outp; struct dcb_output; @@ -34,6 +36,18 @@ struct nvkm_disp_func { int (*new)(struct nvkm_disp *, int id); } wndw, head, dac, sor, pior; + /* Register programming that the GSP-RM display path (rm/r535) needs from + * the chip, everything else on that path goes through RM. The hooks are + * called unconditionally and the head table is handed to nvkm_head_new_(). + */ + struct { + irqreturn_t (*intr)(struct nvkm_inth *); + const struct nvkm_head_func *head; + void (*hdmi_gcp)(struct nvkm_ior *, int head, bool enable); + void (*hdmi_infoframe_avi)(struct nvkm_ior *, int head, void *data, u32 size); + void (*hdmi_infoframe_vsi)(struct nvkm_ior *, int head, void *data, u32 size); + } gsp; + u16 ramht_size; struct nvkm_sclass root; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c index 6cfd52c9056f..948b1d2f954c 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c @@ -140,6 +140,14 @@ tu102_head_vblank_get(struct nvkm_head *head) nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002); } +const struct nvkm_head_func +tu102_gsp_head = { + .state = gv100_head_state, + .rgpos = gv100_head_rgpos, + .vblank_get = tu102_head_vblank_get, + .vblank_put = tu102_head_vblank_put, +}; + static void tu102_disp_intr_head_timing(struct nvkm_disp *disp, int head) { @@ -295,12 +303,23 @@ tu102_disp = { }, }; +static const struct nvkm_disp_func +tu102_gsp_disp = { + .uevent = &gv100_disp_chan_uevent, + .ramht_size = 0x2000, + .gsp.intr = tu102_disp_intr, + .gsp.head = &tu102_gsp_head, + .gsp.hdmi_gcp = tu102_sor_hdmi_gcp, + .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi, + .gsp.hdmi_infoframe_vsi = gv100_sor_hdmi_infoframe_vsi, +}; + int tu102_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, struct nvkm_disp **pdisp) { if (nvkm_gsp_rm(device->gsp)) - return r535_disp_new(&tu102_disp, device, type, inst, pdisp); + return r535_disp_new(&tu102_gsp_disp, device, type, inst, pdisp); return nvkm_disp_new_(&tu102_disp, device, type, inst, pdisp); } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c index cd4451e62512..bf97edcdfc95 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c @@ -547,7 +547,19 @@ r535_sor_hdmi_audio(struct nvkm_ior *sor, int head, bool enable) { r535_sor_hdmi_ctrl_audio(sor->asy.outp, enable); r535_sor_hdmi_ctrl_audio_mute(sor->asy.outp, !enable); - tu102_sor_hdmi_gcp(sor, head, enable); + sor->disp->func->gsp.hdmi_gcp(sor, head, enable); +} + +static void +r535_sor_hdmi_infoframe_avi(struct nvkm_ior *sor, int head, void *data, u32 size) +{ + sor->disp->func->gsp.hdmi_infoframe_avi(sor, head, data, size); +} + +static void +r535_sor_hdmi_infoframe_vsi(struct nvkm_ior *sor, int head, void *data, u32 size) +{ + sor->disp->func->gsp.hdmi_infoframe_vsi(sor, head, data, size); } static void @@ -575,8 +587,8 @@ r535_sor_hdmi = { .ctrl = r535_sor_hdmi_ctrl, .scdc = r535_sor_hdmi_scdc, /*TODO: SF_USER -> KMS. */ - .infoframe_avi = gv100_sor_hdmi_infoframe_avi, - .infoframe_vsi = gv100_sor_hdmi_infoframe_vsi, + .infoframe_avi = r535_sor_hdmi_infoframe_avi, + .infoframe_vsi = r535_sor_hdmi_infoframe_vsi, .audio = r535_sor_hdmi_audio, }; @@ -601,14 +613,6 @@ r535_sor_cnt(struct nvkm_disp *disp, unsigned long *pmask) return 4; } -static const struct nvkm_head_func -r535_head = { - .state = gv100_head_state, - .rgpos = gv100_head_rgpos, - .vblank_get = tu102_head_vblank_get, - .vblank_put = tu102_head_vblank_put, -}; - static struct nvkm_conn * r535_conn_new(struct nvkm_disp *disp, u32 id) { @@ -1606,7 +1610,7 @@ r535_disp_oneinit(struct nvkm_disp *disp) nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl); for_each_set_bit(i, &disp->head.mask, disp->head.nr) { - ret = nvkm_head_new_(&r535_head, disp, i); + ret = nvkm_head_new_(disp->func->gsp.head, disp, i); if (ret) return ret; } @@ -1655,7 +1659,7 @@ r535_disp_oneinit(struct nvkm_disp *disp) return ret; ret = nvkm_inth_add(&device->vfn->intr, ret, NVKM_INTR_PRIO_NORMAL, &disp->engine.subdev, - tu102_disp_intr, &disp->engine.subdev.inth); + disp->func->gsp.intr, &disp->engine.subdev.inth); if (ret) return ret; @@ -1688,6 +1692,7 @@ r535_disp_new(const struct nvkm_disp_func *hw, struct nvkm_device *device, rm->uevent = hw->uevent; rm->sor.cnt = r535_sor_cnt; rm->sor.new = r535_sor_new; + rm->gsp = hw->gsp; rm->ramht_size = hw->ramht_size; rm->root.oclass = gpu->disp.class.root; From 92f09dcb4e8473ab25764e950994ab7b6abce6dd Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:02 +0400 Subject: [PATCH 0120/2366] drm/nouveau/disp: fix HDMI vendor infoframes on GB20x The GSP path reuses the GV100 direct-MMIO infoframe writers on every chip. On GB20x that is only half right as while the legacy SF AVI unit is unchanged, the legacy VSI unit at 0x6f0100 was removed, so gv100_sor_hdmi_infoframe_vsi() writes into a reserved area and no vendor infoframe ever reaches the HW. This affects HDMI-VIC signalling which can impact some 4K modes for legacy HDMI 1.4 sinks. GB20x (NVDisplay 5.0+) reorganised the SF HDMI packet units. Per NVIDIA's published C971/CA71 DISP_SF_USER class headers, only three legacy units remain (AVI at +0x000, GCP at +0x040, ACR at +0x080), and vendor infoframes must instead be sent through the shared generic infoframe units at +0x130, whose 9-dword packet slots are loaded through the shared data port at +0x3f0/+0x3f4. Add a VSI writer using the same programming sequence OpenRM uses on these chips (nvhdmipkt_C971.c, programAdvancedInfoframeC971()): disable the unit and wait for it to idle, clear the SENT status, write the packet through the data port with a zero inserted in HB3 after the three header bytes, then enable the unit for every-frame transmission during vblank. Generic unit 1 is used for the VSI, matching the slot assignment in NVIDIA's nvkms (NVHDMIPKT_TYPE_SHARED_GENERIC2, unit 0 is reserved for extended metadata packets and unit 2 for the HDR DRM infoframe, if those are wired up later). GB20x so far shared GA10x's display entry point. Give it its own, gb202_disp_new(), with a gb202_gsp_disp table that supplies the VSI writer to the GSP path and otherwise carries the same hooks as GA10x. The following fixes fill in the rest of the GB20x differences there. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-5-mohamedahmedegypt2001@gmail.com --- .../drm/nouveau/include/nvkm/engine/disp.h | 1 + .../gpu/drm/nouveau/nvkm/engine/device/base.c | 10 +-- .../gpu/drm/nouveau/nvkm/engine/disp/Kbuild | 1 + .../gpu/drm/nouveau/nvkm/engine/disp/gb202.c | 88 +++++++++++++++++++ 4 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c diff --git a/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h b/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h index 7903d7470d19..01145db32c53 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h @@ -87,4 +87,5 @@ int gp102_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct int gv100_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **); int tu102_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **); int ga102_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **); +int gb202_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **); #endif diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c index ea62dc97f118..96c8a5b29999 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c @@ -2846,7 +2846,7 @@ nv1b2_chipset = { .pci = { 0x00000001, gh100_pci_new }, .timer = { 0x00000001, gk20a_timer_new }, .vfn = { 0x00000001, ga100_vfn_new }, - .disp = { 0x00000001, ga102_disp_new }, + .disp = { 0x00000001, gb202_disp_new }, .fifo = { 0x00000001, ga102_fifo_new }, }; @@ -2862,7 +2862,7 @@ nv1b3_chipset = { .pci = { 0x00000001, gh100_pci_new }, .timer = { 0x00000001, gk20a_timer_new }, .vfn = { 0x00000001, ga100_vfn_new }, - .disp = { 0x00000001, ga102_disp_new }, + .disp = { 0x00000001, gb202_disp_new }, .fifo = { 0x00000001, ga102_fifo_new }, }; @@ -2878,7 +2878,7 @@ nv1b5_chipset = { .pci = { 0x00000001, gh100_pci_new }, .timer = { 0x00000001, gk20a_timer_new }, .vfn = { 0x00000001, ga100_vfn_new }, - .disp = { 0x00000001, ga102_disp_new }, + .disp = { 0x00000001, gb202_disp_new }, .fifo = { 0x00000001, ga102_fifo_new }, }; @@ -2894,7 +2894,7 @@ nv1b6_chipset = { .pci = { 0x00000001, gh100_pci_new }, .timer = { 0x00000001, gk20a_timer_new }, .vfn = { 0x00000001, ga100_vfn_new }, - .disp = { 0x00000001, ga102_disp_new }, + .disp = { 0x00000001, gb202_disp_new }, .fifo = { 0x00000001, ga102_fifo_new }, }; @@ -2910,7 +2910,7 @@ nv1b7_chipset = { .pci = { 0x00000001, gh100_pci_new }, .timer = { 0x00000001, gk20a_timer_new }, .vfn = { 0x00000001, ga100_vfn_new }, - .disp = { 0x00000001, ga102_disp_new }, + .disp = { 0x00000001, gb202_disp_new }, .fifo = { 0x00000001, ga102_fifo_new }, }; diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild index e1aecd3fe96c..98d6ca5ac311 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild @@ -27,6 +27,7 @@ nvkm-y += nvkm/engine/disp/gp102.o nvkm-y += nvkm/engine/disp/gv100.o nvkm-y += nvkm/engine/disp/tu102.o nvkm-y += nvkm/engine/disp/ga102.o +nvkm-y += nvkm/engine/disp/gb202.o nvkm-y += nvkm/engine/disp/udisp.o nvkm-y += nvkm/engine/disp/uconn.o diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c new file mode 100644 index 000000000000..1e40de83e2bb --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2026 Valve Corp. + */ +#include "priv.h" +#include "head.h" +#include "ior.h" + +#include + +/* GB20x (NVD5.0) reorganised the SF HDMI packet units. The AVI unit is + * unchanged from GV100, but the legacy VSI unit is gone. Vendor infoframes + * are sent through the shared generic infoframe units instead. Register + * layout per NVIDIA's clc971.h/clca71.h, programming sequence per + * nvhdmipkt_C971.c:programAdvancedInfoframeC971(). + */ +static void +gb202_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 size) +{ + struct nvkm_device *device = ior->disp->engine.subdev.device; + const u32 hoff = head * 0x400; + /* Generic infoframe unit 1, the slot NVIDIA's driver uses for the VSI. */ + const u32 ctrl = 0x6f0138 + hoff; + u8 buf[36] = {}; + int i; + + /* Disable the unit and wait for it to go idle. */ + nvkm_mask(device, ctrl, 0x00000001, 0x00000000); + if (nvkm_msec(device, 2000, + if (!(nvkm_rd32(device, ctrl) & 0x00400000)) + break; + ) < 0) + return; + + if (!size) + return; + + /* Clear SENT status, and point the data port at unit 1's slot. */ + nvkm_mask(device, ctrl, 0x00800000, 0x00800000); + nvkm_wr32(device, 0x6f03f0 + hoff, 0x00000001); + + /* The data port takes the raw packet, except that a zero is inserted + * in HB3 after the three header bytes. A slot is 9 dwords (HB0-3 plus + * up to 32 payload bytes). An HDMI infoframe carries at most PB0-27, + * so the tail stays zero, and we always write the whole slot. + */ + size = min_t(u32, size, 31); + memcpy(buf, data, min_t(u32, size, 3)); + if (size > 3) + memcpy(&buf[4], (u8 *)data + 3, size - 3); + + for (i = 0; i < 36; i += 4) { + nvkm_wr32(device, 0x6f03f4 + hoff, buf[i + 0] | buf[i + 1] << 8 | + buf[i + 2] << 16 | + (u32)buf[i + 3] << 24); + } + + /* No flip ID or scanline matching. */ + nvkm_wr32(device, 0x6f013c + hoff, 0x00000000); + + /* ENABLE | RUN_MODE=ALWAYS | LOC=VBLANK | OFFSET=1 | SIZE=0. */ + nvkm_wr32(device, ctrl, 0x00000041); + + /* Audio priority low (the init value). */ + nvkm_wr32(device, 0x6f03f8 + hoff, 0x00000002); +} + +/* GB20x is GSP-only. This table supplies the register programming the + * GSP-RM display path needs from the chip. + */ +static const struct nvkm_disp_func +gb202_gsp_disp = { + .uevent = &gv100_disp_chan_uevent, + .ramht_size = 0x2000, + .gsp.intr = tu102_disp_intr, + .gsp.head = &tu102_gsp_head, + .gsp.hdmi_gcp = tu102_sor_hdmi_gcp, + /* The legacy AVI unit is unchanged on GB20x. */ + .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi, + .gsp.hdmi_infoframe_vsi = gb202_sor_hdmi_infoframe_vsi, +}; + +int +gb202_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, + struct nvkm_disp **pdisp) +{ + return r535_disp_new(&gb202_gsp_disp, device, type, inst, pdisp); +} From 764deff8450c9a83e335c17c32ea258ec25bb71e Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:03 +0400 Subject: [PATCH 0121/2366] drm/nouveau/disp: fix HDMI GCP AVMute register offsets on GB20x The GSP path brackets audio enablement with a General Control Packet AVMute toggle. r535_sor_hdmi_audio() calls the gsp.hdmi_gcp hook, which every chip so far serves with tu102_sor_hdmi_gcp() and the legacy GCP unit at 0x6f00c0/0x6f00cc. On GB20x the SF packet units were compacted and the old generic and VSI units are gone (ACR keeps slot 2) and the GCP unit moved from slot 3 to slot 1 (control 0x6f0040 and subpack 0x6f004c from NVIDIA's published clc971.h. The same offsets are also used by OpenRM's hdmiWriteGeneralCtrlPacketC871() on these chips). The old addresses are reserved on GB20x, so the AVMute writes were silent no-ops and mitigated only by the equivalent GCP r535_sor_hdmi_audio() already sends through the SET_OD_PACKET RM control. Add a GB20x GCP writer using the new offsets and hook it into gb202_gsp_disp, keeping the direct MMIO path in sync with the hardware as on earlier chips. Only SB0 (the AVMute bit) is written. On NVD5.0 the subpack register also carries SB1_CTRL (bit 24), which selects where the deep-color CD/PP fields are generated (hardware or from the driver, with the default being HW). hdmiWriteGeneralCtrlPacketC871() likewise writes only SB0-SB2. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-6-mohamedahmedegypt2001@gmail.com --- .../gpu/drm/nouveau/nvkm/engine/disp/gb202.c | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c index 1e40de83e2bb..face801af080 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c @@ -65,6 +65,24 @@ gb202_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 siz nvkm_wr32(device, 0x6f03f8 + hoff, 0x00000002); } +/* General Control Packet AVMute bracket. The GCP unit moved to slot 1 on + * NVD5.0. Only SB0 (the AVMute bit) is ours to write so we must not do a + * full write here: SB1 carries the deep-color CD/PP fields, and SB1_CTRL + * (bit 24, new with clc871.h) controls where their generation happens (HW + * or driver) on these chips, with the default being HW. + */ +static void +gb202_sor_hdmi_gcp(struct nvkm_ior *sor, int head, bool enable) +{ + struct nvkm_device *device = sor->disp->engine.subdev.device; + const u32 hdmi = head * 0x400; + + nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000000); + nvkm_mask(device, 0x6f004c + hdmi, 0x000000ff, !enable ? 0x00000001 : + 0x00000010); + nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000001); +} + /* GB20x is GSP-only. This table supplies the register programming the * GSP-RM display path needs from the chip. */ @@ -74,7 +92,7 @@ gb202_gsp_disp = { .ramht_size = 0x2000, .gsp.intr = tu102_disp_intr, .gsp.head = &tu102_gsp_head, - .gsp.hdmi_gcp = tu102_sor_hdmi_gcp, + .gsp.hdmi_gcp = gb202_sor_hdmi_gcp, /* The legacy AVI unit is unchanged on GB20x. */ .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi, .gsp.hdmi_infoframe_vsi = gb202_sor_hdmi_infoframe_vsi, From 39fd4b742720c68da8695ee1ffa85c5fea4f8e11 Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:04 +0400 Subject: [PATCH 0122/2366] drm/nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware NVIDIA removed the deprecated actualPclkHz/linkClkFreqHz fields and the whole Legacy{activeCnt, activeFrac, activePolarity, mvidWarEnabled, MvidWarParams} block from the SST sub-struct of NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS between the 535 and 570 releases (compared in OpenRM tags 535.113.01 vs 570.144), shrinking the struct. Everything nouveau writes sits at identical offsets in both layouts except the trailing SST.bEnableAudioOverRightPanel (written as zero), but the size is wrong on r570, which means r535_sor_dp_sst() and r535_sor_dp_vcpi() are sent with an incorrect size. Route the .sst/.vcpi IOR functions through nvkm_rm_api_disp the same way bl_ctrl and dp.get_caps/set_indexed_link_rates already are. Keep the existing implementation for r535 and add an r570 implementation built against the 570.144 layout, which already exists in r570/nvrm/disp.h but was unused until now. Also add the NV0073_CTRL_CMD_DP_CONFIG_STREAM define that was missing from the layout. Other DP controls sent through shared r535 code did not change layout between the tags. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-7-mohamedahmedegypt2001@gmail.com --- .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 33 ++++++++-- .../nouveau/nvkm/subdev/gsp/rm/r570/disp.c | 64 +++++++++++++++++++ .../nvkm/subdev/gsp/rm/r570/nvrm/disp.h | 2 + .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h | 5 ++ 4 files changed, 97 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c index bf97edcdfc95..7d1d4ee2af79 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c @@ -400,16 +400,16 @@ r535_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable) r535_sor_dp_audio_mute(sor, false); } -static void -r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn) +static int +r535_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn) { struct nvkm_disp *disp = sor->disp; struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl; ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl)); - if (WARN_ON(IS_ERR(ctrl))) - return; + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); ctrl->subDeviceInstance = 0; ctrl->head = head; @@ -429,12 +429,20 @@ r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u ctrl->MST.sendACT = 0; ctrl->MST.singleHeadMSTPipeline = 0; ctrl->MST.bEnableAudioOverRightPanel = 0; - WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl)); + return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); +} + +static void +r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn) +{ + const struct nvkm_rm_api *rmapi = sor->disp->engine.subdev.device->gsp->rm->api; + + WARN_ON(rmapi->disp->dp.vcpi(sor, head, slot, slot_nr, pbn, aligned_pbn)); } static int -r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef, - u32 watermark, u32 hblanksym, u32 vblanksym) +r535_dp_sst(struct nvkm_ior *sor, int head, bool ef, + u32 watermark, u32 hblanksym, u32 vblanksym) { struct nvkm_disp *disp = sor->disp; struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl; @@ -461,6 +469,15 @@ r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef, return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); } +static int +r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef, + u32 watermark, u32 hblanksym, u32 vblanksym) +{ + const struct nvkm_rm_api *rmapi = sor->disp->engine.subdev.device->gsp->rm->api; + + return rmapi->disp->dp.sst(sor, head, ef, watermark, hblanksym, vblanksym); +} + static const struct nvkm_ior_func_dp r535_sor_dp = { .sst = r535_sor_dp_sst, @@ -1734,6 +1751,8 @@ r535_disp = { .dp = { .get_caps = r535_dp_get_caps, .set_indexed_link_rates = r535_dp_set_indexed_link_rates, + .sst = r535_dp_sst, + .vcpi = r535_dp_vcpi, }, .chan = { .set_pushbuf = r535_disp_chan_set_pushbuf, diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c index a96e31c2d80b..8a23837f356e 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c @@ -5,6 +5,7 @@ #include #include +#include #include #include "nvhw/drf.h" @@ -74,6 +75,67 @@ r570_disp_chan_set_pushbuf(struct nvkm_disp *disp, s32 oclass, int inst, struct return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); } +static int +r570_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn) +{ + struct nvkm_disp *disp = sor->disp; + NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->head = head; + ctrl->sorIndex = sor->id; + ctrl->dpLink = sor->asy.link == 2; + ctrl->bEnableOverride = 1; + ctrl->bMST = 1; + ctrl->hBlankSym = 0; + ctrl->vBlankSym = 0; + ctrl->colorFormat = 0; + ctrl->bEnableTwoHeadOneOr = 0; + ctrl->singleHeadMultistreamMode = 0; + ctrl->MST.slotStart = slot; + ctrl->MST.slotEnd = slot + slot_nr - 1; + ctrl->MST.PBN = pbn; + ctrl->MST.Timeslice = aligned_pbn; + ctrl->MST.sendACT = 0; + ctrl->MST.singleHeadMSTPipeline = 0; + ctrl->MST.bEnableAudioOverRightPanel = 0; + return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); +} + +static int +r570_dp_sst(struct nvkm_ior *sor, int head, bool ef, + u32 watermark, u32 hblanksym, u32 vblanksym) +{ + struct nvkm_disp *disp = sor->disp; + NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, + NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->subDeviceInstance = 0; + ctrl->head = head; + ctrl->sorIndex = sor->id; + ctrl->dpLink = sor->asy.link == 2; + ctrl->bEnableOverride = 1; + ctrl->bMST = 0; + ctrl->hBlankSym = hblanksym; + ctrl->vBlankSym = vblanksym; + ctrl->colorFormat = 0; + ctrl->bEnableTwoHeadOneOr = 0; + ctrl->SST.bEnhancedFraming = ef; + ctrl->SST.tuSize = 64; + ctrl->SST.waterMark = watermark; + ctrl->SST.bEnableAudioOverRightPanel = 0; + return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl); +} + static int r570_dp_set_indexed_link_rates(struct nvkm_outp *outp) { @@ -255,6 +317,8 @@ r570_disp = { .dp = { .get_caps = r570_dp_get_caps, .set_indexed_link_rates = r570_dp_set_indexed_link_rates, + .sst = r570_dp_sst, + .vcpi = r570_dp_vcpi, }, .chan = { .set_pushbuf = r570_disp_chan_set_pushbuf, diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h index 06e972835d77..742b25a2a12d 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h @@ -256,6 +256,8 @@ typedef struct NV0073_CTRL_DP_CTRL_PARAMS { NvU32 eightLaneDpcdBaseAddr; } NV0073_CTRL_DP_CTRL_PARAMS; +#define NV0073_CTRL_CMD_DP_CONFIG_STREAM (0x731362U) /* finn: Evaluated from "(FINN_NV04_DISPLAY_COMMON_DP_INTERFACE_ID << 8) | NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS_MESSAGE_ID" */ + typedef struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS { NvU32 subDeviceInstance; NvU32 head; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h index a9af94adf9ef..fcd0221dcea1 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h @@ -6,6 +6,7 @@ #ifndef __NVKM_RM_H__ #define __NVKM_RM_H__ #include "handles.h" +struct nvkm_ior; struct nvkm_outp; struct r535_gr; @@ -93,6 +94,10 @@ struct nvkm_rm_api { struct { int (*get_caps)(struct nvkm_disp *, int *link_bw, bool *mst, bool *wm); int (*set_indexed_link_rates)(struct nvkm_outp *); + int (*sst)(struct nvkm_ior *, int head, bool ef, + u32 watermark, u32 hblanksym, u32 vblanksym); + int (*vcpi)(struct nvkm_ior *, int head, + u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn); } dp; struct { From 9421dfe912e55360e6b9301a110acb00df7e7320 Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:05 +0400 Subject: [PATCH 0123/2366] drm/nouveau/disp: fix head state readback on GB20x The GSP path reads armed head state and the RG scanout position through gv100_head_state() and gv100_head_rgpos() on every generation. gv100_head_state() reads the core channel's state mirror at a 0x400 per-head stride, which NVD5.0 (GB20x) doubled. Per NVIDIA's published CA7D class header every HEAD_SET method sits at 0x2000 + head * 0x800, while the mirror bases are unchanged (assembly at 0x680000, armed at +0x8000, per OpenRM's v03_00 channel-user-base HAL which is still used on DISPv0502). Add gb202_head_state(), the same readback at the 0x800 stride, and a gb202_gsp_head table to supply it. gv100_head_rgpos() is kept. The RG registers keep their per-head 0x800 stride on NVD5.0, and OpenRM's kdispReadRgLineCountAndFrameCount_v03_00 still reads NV_PDISP_RG_DPCA on DISPv0502. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-8-mohamedahmedegypt2001@gmail.com --- .../gpu/drm/nouveau/nvkm/engine/disp/gb202.c | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c index face801af080..765c42039a47 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c @@ -83,6 +83,55 @@ gb202_sor_hdmi_gcp(struct nvkm_ior *sor, int head, bool enable) nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000001); } +/* Same core-channel state mirror as gv100_head_state() (assembly at 0x680000, + * armed at +0x8000, per-head method offsets unchanged), but NVD5.0 spaces + * heads 0x800 apart (see NVCA7D_HEAD_SET_*(a) in clca7d.h). + */ +static void +gb202_head_state(struct nvkm_head *head, struct nvkm_head_state *state) +{ + struct nvkm_device *device = head->disp->engine.subdev.device; + const u32 hoff = (state == &head->arm) * 0x8000 + head->id * 0x800; + u32 data; + + data = nvkm_rd32(device, 0x682064 + hoff); + state->vtotal = (data & 0xffff0000) >> 16; + state->htotal = (data & 0x0000ffff); + data = nvkm_rd32(device, 0x682068 + hoff); + state->vsynce = (data & 0xffff0000) >> 16; + state->hsynce = (data & 0x0000ffff); + data = nvkm_rd32(device, 0x68206c + hoff); + state->vblanke = (data & 0xffff0000) >> 16; + state->hblanke = (data & 0x0000ffff); + data = nvkm_rd32(device, 0x682070 + hoff); + state->vblanks = (data & 0xffff0000) >> 16; + state->hblanks = (data & 0x0000ffff); + /* Bit 31 is ADJ1000DIV1001, not a HERTZ bit. We don't have enough bits + * to add the full clock in hz on Blackwell (35 bits), but state->hz + * is unused and obsolete under GSP so this is fine. + */ + state->hz = nvkm_rd32(device, 0x68200c + hoff) & 0x7fffffff; + + data = nvkm_rd32(device, 0x682004 + hoff); + switch ((data & 0x000000f0) >> 4) { + case 5: state->or.depth = 30; break; + case 4: state->or.depth = 24; break; + case 1: state->or.depth = 18; break; + default: + state->or.depth = 18; + WARN_ON(1); + break; + } +} + +static const struct nvkm_head_func +gb202_gsp_head = { + .state = gb202_head_state, + .rgpos = gv100_head_rgpos, + .vblank_get = tu102_head_vblank_get, + .vblank_put = tu102_head_vblank_put, +}; + /* GB20x is GSP-only. This table supplies the register programming the * GSP-RM display path needs from the chip. */ @@ -91,7 +140,7 @@ gb202_gsp_disp = { .uevent = &gv100_disp_chan_uevent, .ramht_size = 0x2000, .gsp.intr = tu102_disp_intr, - .gsp.head = &tu102_gsp_head, + .gsp.head = &gb202_gsp_head, .gsp.hdmi_gcp = gb202_sor_hdmi_gcp, /* The legacy AVI unit is unchanged on GB20x. */ .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi, From 5bb489b333237c1bf63a891a4362986253a0060a Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Tue, 25 Aug 2026 04:14:06 +0400 Subject: [PATCH 0124/2366] drm/nouveau/gsp: fix vblank interrupts on GB20x The GSP path programs per-head timing (vblank) interrupts the same way on every generation. NVD5.0 (GB20x) reworked the FE interrupt frontend around four message-based kernel vectors (high latency, low latency, PMU, and GSP) and moved RM head-timing interrupts to the dedicated low-latency vector: - The enable is NV_PDISP_FE_RM_INTR_EN1_HEAD_TIMING, 0x611ef0 + head*4 (570.144 kernel_head_0501.c, renamed kernel_head_0502.c from 575.51.02 on, and v05_01 dev_disp.h). - The vector is reported as a separate interrupt table entry, MC_ENGINE_IDX_DISP_LOW (intr_gb202.c, intrCacheDispIntrVectors). - The vector must be re-armed through NV_PDISP_FE_INTR_RETRIGGER(1) at 0x611f34 after servicing (kdispServiceInterrupt -> kdispIntrRetrigger_v05_01). The event latch (0x611800), per-head status (0x611c00), and dispatch summary (0x611ec0) the interrupt handler uses are unchanged on GB20x (kheadReadPendingVblank_v03_00 and kheadResetPendingLastData_v03_00 remain for DISPv0502+). On GB20x the old code enables head timing onto the legacy vector, leaves its handler there, and never re-arms the message-based vectors. Page flips still complete (nv50 sends those events from the commit path), so the desktop looks fine while DRM vblank waits and vblank sequence queries are affected. Supply GB20x vblank enables and an interrupt handler that re-arms the vector after servicing through gb202_gsp_disp, translate the low-latency interrupt table entry as a second NVKM_ENGINE_DISP instance, and add a gsp.intr_low_latency flag so r535_disp_oneinit() attaches the handler to that instance. GB20x was the last cross-file user of the TU1xx vblank enables, so make those static and drop their head.h prototypes. Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x") Cc: stable@vger.kernel.org Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260825001408.14219-9-mohamedahmedegypt2001@gmail.com --- .../gpu/drm/nouveau/nvkm/engine/disp/gb202.c | 42 +++++++++++++++++-- .../gpu/drm/nouveau/nvkm/engine/disp/head.h | 2 - .../gpu/drm/nouveau/nvkm/engine/disp/priv.h | 2 + .../gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 4 +- .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 10 ++++- .../drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c | 9 ++++ 6 files changed, 61 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c index 765c42039a47..d0360610f9fa 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c @@ -124,12 +124,46 @@ gb202_head_state(struct nvkm_head *head, struct nvkm_head_state *state) } } +/* NVD5.0 (GB20x and later) moved the RM head-timing interrupt enable to + * the low-latency vector's EN1 block. The event latch is unchanged. + */ +static void +gb202_head_vblank_put(struct nvkm_head *head) +{ + struct nvkm_device *device = head->disp->engine.subdev.device; + + nvkm_mask(device, 0x611ef0 + (head->id * 4), 0x00000002, 0x00000000); +} + +static void +gb202_head_vblank_get(struct nvkm_head *head) +{ + struct nvkm_device *device = head->disp->engine.subdev.device; + + nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002); + nvkm_mask(device, 0x611ef0 + (head->id * 4), 0x00000002, 0x00000002); +} + +static irqreturn_t +gb202_disp_intr(struct nvkm_inth *inth) +{ + struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth); + irqreturn_t ret = tu102_disp_intr(inth); + + /* The FE interrupt vectors are message-based on NVD5.0. Re-arm the + * low-latency vector so it fires again for any event that latched + * while we were servicing. + */ + nvkm_wr32(disp->engine.subdev.device, 0x611f34, 0x00000001); + return ret; +} + static const struct nvkm_head_func gb202_gsp_head = { .state = gb202_head_state, .rgpos = gv100_head_rgpos, - .vblank_get = tu102_head_vblank_get, - .vblank_put = tu102_head_vblank_put, + .vblank_get = gb202_head_vblank_get, + .vblank_put = gb202_head_vblank_put, }; /* GB20x is GSP-only. This table supplies the register programming the @@ -139,7 +173,9 @@ static const struct nvkm_disp_func gb202_gsp_disp = { .uevent = &gv100_disp_chan_uevent, .ramht_size = 0x2000, - .gsp.intr = tu102_disp_intr, + /* Head timing arrives on the dedicated low-latency vector. */ + .gsp.intr = gb202_disp_intr, + .gsp.intr_low_latency = true, .gsp.head = &gb202_gsp_head, .gsp.hdmi_gcp = gb202_sor_hdmi_gcp, /* The legacy AVI unit is unchanged on GB20x. */ diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h index 784521c2aca1..5976498da909 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h @@ -56,8 +56,6 @@ int gv100_head_new(struct nvkm_disp *, int id); void gv100_head_state(struct nvkm_head *head, struct nvkm_head_state *state); void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline); -void tu102_head_vblank_get(struct nvkm_head *); -void tu102_head_vblank_put(struct nvkm_head *); extern const struct nvkm_head_func tu102_gsp_head; #define HEAD_MSG(h,l,f,a...) do { \ diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h index a9dbda67a7d4..fde321dbd7c8 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h @@ -42,6 +42,8 @@ struct nvkm_disp_func { */ struct { irqreturn_t (*intr)(struct nvkm_inth *); + /* Head-timing interrupts arrive on a second DISP vector. */ + bool intr_low_latency; const struct nvkm_head_func *head; void (*hdmi_gcp)(struct nvkm_ior *, int head, bool enable); void (*hdmi_infoframe_avi)(struct nvkm_ior *, int head, void *data, u32 size); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c index 948b1d2f954c..f6c163072ff6 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c @@ -123,7 +123,7 @@ tu102_sor_new(struct nvkm_disp *disp, int id) * enables to us. These program the RM head-timing line (bit 1 of the * per-head enable, not the bit nvkm's own gv100 path uses). */ -void +static void tu102_head_vblank_put(struct nvkm_head *head) { struct nvkm_device *device = head->disp->engine.subdev.device; @@ -131,7 +131,7 @@ tu102_head_vblank_put(struct nvkm_head *head) nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000); } -void +static void tu102_head_vblank_get(struct nvkm_head *head) { struct nvkm_device *device = head->disp->engine.subdev.device; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c index 7d1d4ee2af79..f5f22173fc2c 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c @@ -1671,7 +1671,15 @@ r535_disp_oneinit(struct nvkm_disp *disp) if (ret) return ret; - ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, disp->engine.subdev.inst); + /* Chips that raise head-timing interrupts on a separate low-latency + * vector report it as a second DISP interrupt table entry, exposed + * as instance 1 by the RM engine-index translation (see + * r570_gsp_xlat_mc_engine_idx()). Their high-latency vector + * (instance 0) is left unhandled as no event nouveau enables is + * routed to it, and without a handler it stays masked. + */ + ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, + disp->func->gsp.intr_low_latency ? 1 : disp->engine.subdev.inst); if (ret < 0) return ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c index 996941c668ba..1488771c63fc 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c @@ -44,6 +44,15 @@ r570_gsp_xlat_mc_engine_idx(u32 mc_engine_idx, enum nvkm_subdev_type *ptype, int *ptype = NVKM_ENGINE_DISP; *pinst = 0; return true; + case MC_ENGINE_IDX_DISP_LOW: + /* GB20x+ report a separate low-latency display vector, used + * for head-timing interrupts. Expose it as a second DISP + * interrupt instance. r535_disp_oneinit() attaches the + * handler to it when the chip's gsp.intr_low_latency is set. + */ + *ptype = NVKM_ENGINE_DISP; + *pinst = 1; + return true; case MC_ENGINE_IDX_CE0 ... MC_ENGINE_IDX_CE19: *ptype = NVKM_ENGINE_CE; *pinst = mc_engine_idx - MC_ENGINE_IDX_CE0; From c6f48e59ece0123f6a11527ad4d89b21c2d65b87 Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Tue, 25 Aug 2026 18:41:34 +0800 Subject: [PATCH 0125/2366] drm/sysfb: ofdrm: Fix integer overflow in fb_size calculation The framebuffer size calculation `fb_size = linebytes * height` can overflow when both values are large (e.g., 46341 * 46341 > INT_MAX). Since linebytes and height are both int types, the multiplication is performed as int * int, which results in undefined behavior on overflow. Use check_mul_overflow() to detect and prevent this overflow, consistent with the approach used in simpledrm.c and corebootdrm.c. Signed-off-by: Shixiong Ou Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Fixes: c8a17756c425 ("drm/ofdrm: Add ofdrm for Open Firmware framebuffers") Cc: # v6.2+ Link: https://patch.msgid.link/20260825104134.669676-1-oushixiong1025@163.com --- drivers/gpu/drm/sysfb/ofdrm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sysfb/ofdrm.c b/drivers/gpu/drm/sysfb/ofdrm.c index 819aed466727..a6dc34b9ec0f 100644 --- a/drivers/gpu/drm/sysfb/ofdrm.c +++ b/drivers/gpu/drm/sysfb/ofdrm.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -913,7 +914,10 @@ static struct ofdrm_device *ofdrm_device_create(struct drm_driver *drv, return ERR_PTR(-EINVAL); } - fb_size = linebytes * height; + if (check_mul_overflow(linebytes, height, &fb_size)) { + drm_err(dev, "framebuffer size exceeds maximum\n"); + return ERR_PTR(-EINVAL); + } /* * Try to figure out the address of the framebuffer. Unfortunately, Open From 958f35cbb8955ca3fa439cd9f2092cb42414aa8c Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Fri, 31 Jul 2026 19:17:29 +0800 Subject: [PATCH 0126/2366] drm/sysfb: ofdrm: Fix is_avivo() constant comparison bug The is_avivo() function has a logic error where it compares a constant to another constant instead of checking the device parameter: (PCI_VENDOR_ID_ATI_R600 >= 0x9400) Signed-off-by: Shixiong Ou Reviewed-by: Thomas Zimmermann Fixes: f496834e1674 ("drm/ofdrm: Add per-model device function") Signed-off-by: Thomas Zimmermann Cc: # v6.2+ Link: https://patch.msgid.link/20260731111729.703116-1-oushixiong1025@163.com --- drivers/gpu/drm/sysfb/ofdrm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sysfb/ofdrm.c b/drivers/gpu/drm/sysfb/ofdrm.c index a6dc34b9ec0f..9d60db45139c 100644 --- a/drivers/gpu/drm/sysfb/ofdrm.c +++ b/drivers/gpu/drm/sysfb/ofdrm.c @@ -239,7 +239,7 @@ static bool is_avivo(u32 vendor, u32 device) /* This will match most R5xx */ return (vendor == PCI_VENDOR_ID_ATI) && ((device >= PCI_VENDOR_ID_ATI_R520 && device < 0x7800) || - (PCI_VENDOR_ID_ATI_R600 >= 0x9400)); + (device >= PCI_VENDOR_ID_ATI_R600)); } static enum ofdrm_model display_get_model_of(struct drm_device *dev, struct device_node *of_node) From 92312d333bf700798f92f30406c721bce87506f3 Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Tue, 25 Aug 2026 14:07:29 +0200 Subject: [PATCH 0127/2366] drm/cirrus-qemu: Validate BAR0 size during probe The `cirrus-qemu` driver relies on `CIRRUS_VRAM_SIZE` (4 MB) to validate framebuffer sizes. However, during PCI probe, the driver mapped BAR0 without verifying that its size matches `CIRRUS_VRAM_SIZE`. If a PCI device with a BAR0 smaller than 4 MB is bound to the driver, the mapped VRAM will be smaller than expected. Because validation checks assume 4 MB VRAM, framebuffers larger than the mapped memory can be created. When the display plane is updated (e.g. during release), `cirrus_primary_plane_helper_atomic_update()` copies the framebuffer to VRAM using `drm_fb_memcpy()`. Writing past the end of the mapped I/O memory causes a supervisor write page fault: BUG: unable to handle page fault for address: ffffc9000389c000 ... RIP: 0010:memcpy_toio+0x7c/0xe0 arch/x86/lib/iomem.c:110 ... Call Trace: iosys_map_memcpy_to include/linux/iosys-map.h:285 [inline] drm_fb_memcpy+0x325/0x5d0 drivers/gpu/drm/drm_format_helper.c:442 cirrus_primary_plane_helper_atomic_update+0x98a/0xb00 drivers/gpu/drm/tiny/cirrus-qemu.c:358 drm_atomic_helper_commit_planes+0x626/0xea0 drivers/gpu/drm/drm_atomic_helper.c:3038 drm_atomic_helper_commit_tail+0x60/0x510 drivers/gpu/drm/drm_atomic_helper.c:1989 commit_tail+0x2b1/0x3c0 drivers/gpu/drm/drm_atomic_helper.c:2074 drm_atomic_helper_commit+0xa77/0xb10 drivers/gpu/drm/drm_atomic_helper.c:2312 Fix this by validating in `cirrus_pci_probe()` that the PCI BAR0 resource is not less than `CIRRUS_VRAM_SIZE`, returning `-ENODEV` if it is less. Fixes: ab3e023b1b4c ("drm/cirrus: rewrite and modernize driver.") Assisted-by: Gemini:gemini-3.6-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+2442951a6abb004df963@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=2442951a6abb004df963 Link: https://syzkaller.appspot.com/ai_job?id=ba262a3a-bccf-4ad8-a1b0-583c55d34fd6 Signed-off-by: Slawomir Stepien Signed-off-by: Thomas Zimmermann Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260825120729.493611-1-sst@poczta.fm --- drivers/gpu/drm/tiny/cirrus-qemu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/tiny/cirrus-qemu.c b/drivers/gpu/drm/tiny/cirrus-qemu.c index 075221b431d3..3bf23fcf6574 100644 --- a/drivers/gpu/drm/tiny/cirrus-qemu.c +++ b/drivers/gpu/drm/tiny/cirrus-qemu.c @@ -582,6 +582,9 @@ static int cirrus_pci_probe(struct pci_dev *pdev, struct cirrus_device *cirrus; int ret; + if (pci_resource_len(pdev, 0) < CIRRUS_VRAM_SIZE) + return -ENODEV; + ret = aperture_remove_conflicting_pci_devices(pdev, cirrus_driver.name); if (ret) return ret; From d32b08284f44c20edb2ea3f64ba0a6a165036fb2 Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Mon, 17 Aug 2026 15:45:20 -0300 Subject: [PATCH 0128/2366] drm/atomic: remove bogus check for file_priv Since file_priv can never be NULL at prepare_signaling() as it is only called by drm_mode_atomic_ioctl(), remove the check. If that was not the case, skipping the rest of the block here would cause the drm_pending_vblank_event object to leak and fail to set up the fence in case out_fence_ptr is set. Since the check is unreachable, there is no possible leak. Signed-off-by: Thadeu Lima de Souza Cascardo Reviewed-by: Melissa Wen Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260817-drm_atomic_bogus_check-v2-1-2b9e60f32a7e@igalia.com --- drivers/gpu/drm/drm_atomic_uapi.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index e997917819e8..657c15474ed5 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -1445,9 +1445,6 @@ static int prepare_signaling(struct drm_device *dev, if (arg->flags & DRM_MODE_PAGE_FLIP_EVENT) { struct drm_pending_vblank_event *e = crtc_state->event; - if (!file_priv) - continue; - ret = drm_event_reserve_init(dev, file_priv, &e->base, &e->event.base); if (ret) { From 4d4be202165e832d74849b4a68e289a2a377039c Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Mon, 27 Jul 2026 17:45:49 -0300 Subject: [PATCH 0129/2366] drm: Fix drm_crtc_commit leak if signaled when PAGE_FLIP_EVENT is used Commit 1c6ceeee6ebb ("drm/atomic: Fix memleak on ERESTARTSYS during non-blocking commits") fixed a very similar issue when the event was allocated by drm_atomic_helper_setup_commit() itself. However, if the event is allocated in prepare_signaling(), it will also be set to NULL in complete_signaling(), which prevents drm_crtc_commit from being put in __drm_atomic_helper_crtc_destroy_state(). Dropping the reference when the event is set to NULL at complete_signaling() fixes the leak. The leak can be reproduced by sending a signal to the thread using DRM_MODE_PAGE_FLIP_EVENT and using a sw_sync fence to cause the atomic ioctl to block at drm_atomic_helper_wait_for_fences(). It happened both with amdgpu and vkms. Fixes: 24835e442f28 ("drm: reference count event->completion") Cc: stable@vger.kernel.org Signed-off-by: Thadeu Lima de Souza Cascardo Reviewed-by: Melissa Wen Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260727-drm_crtc_atomic_commit_leak-v1-1-23d9948a9d7c@igalia.com --- drivers/gpu/drm/drm_atomic_uapi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index 657c15474ed5..ae7667d1072d 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -1560,6 +1560,8 @@ static void complete_signaling(struct drm_device *dev, * to prevent a double free in drm_atomic_commit_clear. */ if (event && (event->base.fence || event->base.file_priv)) { + if (crtc_state->commit && crtc_state->commit->abort_completion) + drm_crtc_commit_put(crtc_state->commit); drm_event_cancel_free(dev, &event->base); crtc_state->event = NULL; } From 3d3de2aee17d1431694aa085039479b5679e5ad4 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:30 +0800 Subject: [PATCH 0130/2366] ntfs: return DT_UNKNOWN on inode lookup failure in readdir ntfs_reparse_tag_dt_types() returns PTR_ERR(vi) when ntfs_iget() fails, but its return type is unsigned int and the caller passes the value straight to dir_emit() as d_type. A stale or corrupt MFT reference in a directory index thus makes readdir report a garbage d_type value to userspace. Return DT_UNKNOWN on lookup failure instead. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/reparse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c index 5e483a2f9060..1cc6dfe19fed 100644 --- a/fs/ntfs/reparse.c +++ b/fs/ntfs/reparse.c @@ -405,7 +405,7 @@ unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mr vi = ntfs_iget(vol->sb, mref); if (IS_ERR(vi)) - return PTR_ERR(vi); + return DT_UNKNOWN; reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi), AT_REPARSE_POINT, NULL, 0, &attr_size); From 9692b1b4fc00cf89628bc43f71729ab21f14f8d3 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:32 +0800 Subject: [PATCH 0131/2366] ntfs: propagate reparse index insertion failure update_reparse_data() ignores the return value of set_reparse_index(). When index insertion fails, the code removes the just-written reparse data as cleanup but still returns 0, so symlink(2) (and WSL special file creation) reports success while no reparse data exists on disk. When there was no previous reparse data (oldsize == 0), the failure was likewise silently ignored. Propagate the error to the caller. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/reparse.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c index 1cc6dfe19fed..1a6073e22677 100644 --- a/fs/ntfs/reparse.c +++ b/fs/ntfs/reparse.c @@ -694,8 +694,9 @@ static int update_reparse_data(struct ntfs_inode *ni, struct ntfs_index_context goto put_rp_inode; } - if (set_reparse_index(ni, xr, ((const struct reparse_point *)value)->reparse_tag) && - oldsize > 0) { + err = set_reparse_index(ni, xr, + ((const struct reparse_point *)value)->reparse_tag); + if (err && oldsize > 0) { /* * If cannot index, try to remove the reparse * data and log the error. There will be an From ada728801999e25e610091362447372f1b25dd25 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:33 +0800 Subject: [PATCH 0132/2366] ntfs: return -ERANGE for undersized xattr buffer When the value buffer passed to getxattr(2) for system.dos_attrib, system.ntfs_attrib or system.ntfs_attrib_be is smaller than the attribute value, ntfs_getxattr() returns -ENODATA, which tells userspace the attribute does not exist. The xattr API expects -ERANGE in this case, and ntfs_get_ea() in the same file already returns -ERANGE for regular EAs. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/ea.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index cdd306933d73..f6dbdfe6ff15 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -615,7 +615,7 @@ static int ntfs_getxattr(const struct xattr_handler *handler, if (!buffer) { err = sizeof(u8); } else if (size < sizeof(u8)) { - err = -ENODATA; + err = -ERANGE; } else { err = sizeof(u8); *(u8 *)buffer = (u8)(le32_to_cpu(ni->flags) & 0x3F); @@ -628,7 +628,7 @@ static int ntfs_getxattr(const struct xattr_handler *handler, if (!buffer) { err = sizeof(u32); } else if (size < sizeof(u32)) { - err = -ENODATA; + err = -ERANGE; } else { err = sizeof(u32); *(u32 *)buffer = le32_to_cpu(ni->flags); From 8efe00b098b5b3618c885d2a35a5edccfbfbec7d Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:34 +0800 Subject: [PATCH 0133/2366] ntfs: preserve error code in ntfs_resident_attr_record_add() ntfs_resident_attr_record_add() collapses every failure to -EIO at its put_err_out label. This defeats the resident-to-non-resident fallback in ntfs_attr_add(), which relies on seeing -ENOSPC to convert the attribute when the MFT record has no room, and also hides -EEXIST and -ENOMEM from callers. Return the actual error code. Every path reaching the label has err set to a negative errno. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 60264833bb63..3663259f0b7d 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -2500,7 +2500,7 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, return offset; put_err_out: ntfs_attr_put_search_ctx(ctx); - return -EIO; + return err; } /* From ba1b61ddaa764f31b14abe1d547049682cc5824e Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:35 +0800 Subject: [PATCH 0134/2366] ntfs: return real error from ntfs_non_resident_attr_record_add() ntfs_non_resident_attr_record_add() returns -1 at its put_err_out label, which callers propagate as -EPERM to userspace. Return the actual error code. Every path reaching the label has err set to a negative errno. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 3663259f0b7d..4dff5c3f779b 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -2639,7 +2639,7 @@ static int ntfs_non_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, return offset; put_err_out: ntfs_attr_put_search_ctx(ctx); - return -1; + return err; } /* From cf06dcd572845723821b54a608fc2da995c3c8e2 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:36 +0800 Subject: [PATCH 0135/2366] ntfs: fix kmap_local leak in write_mft_record_nolock() error paths write_mft_record_nolock() maps the MFT record folio with kmap_local_folio(), but the pre_write_mst_fixup() and bio_add_folio() failure paths jump to the error label without unmapping it. kmap_local mappings are stack-ordered per task, so leaking one corrupts the nesting for any outer mapping. Unmap the folio on those error paths too. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/mft.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 984a0827f9ac..69b007e574fc 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -580,7 +580,7 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn err = pre_write_mst_fixup((struct ntfs_record *)fixup_m, vol->mft_record_size); if (err) { ntfs_error(vol->sb, "Failed to apply mst fixups!"); - goto err_out; + goto unmap_err_out; } folio_size = vol->mft_record_size / ni->mft_lcn_count; @@ -645,6 +645,8 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn return 0; put_bio_out: bio_put(bio); +unmap_err_out: + kunmap_local(kaddr); err_out: /* * The caller should mark the base inode as bad so no more I/O From be9e89ccb8e52a3e4b67feeb03ebd8133091dc7e Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:37 +0800 Subject: [PATCH 0136/2366] ntfs: only count successfully cleared runs when freeing clusters ntfs_cluster_free_from_rl_nolock() adds a run's length to nr_freed whenever the error bookkeeping condition is false, which includes cases where ntfs_bitmap_clear_run() actually failed - e.g. a second run failing with the same errno as an earlier one, or any failure after a non-ENOMEM error was already recorded. Since a failed ntfs_bitmap_clear_run() rolls back its partial modifications, no bits were cleared for that run, yet its length still inflates vol->free_clusters, corrupting statfs output and the allocator's free space gate. Only count runs whose bitmap clear succeeded. Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/lcnalloc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c index aa2e017a4384..795f71d26895 100644 --- a/fs/ntfs/lcnalloc.c +++ b/fs/ntfs/lcnalloc.c @@ -53,10 +53,10 @@ int ntfs_cluster_free_from_rl_nolock(struct ntfs_volume *vol, if (rl->lcn < 0) continue; err = ntfs_bitmap_clear_run(lcnbmp_vi, rl->lcn, rl->length); - if (unlikely(err && (!ret || ret == -ENOMEM) && ret != err)) - ret = err; - else + if (likely(!err)) nr_freed += rl->length; + else if (!ret || ret == -ENOMEM) + ret = err; } ntfs_inc_free_clusters(vol, nr_freed); ntfs_debug("Done."); From 5f2a22b36fe34c98f6d5e35ddb759ee53d684145 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:38 +0800 Subject: [PATCH 0137/2366] ntfs: skip free cluster decrement when rollback fails When the rollback in __ntfs_cluster_free() fails, the recursive call returns a negative errno and the subsequent ntfs_dec_free_clusters(vol, delta) subtracts that negative value, adding bogus clusters to the counter on an already-failing volume. Skip the decrement when the rollback failed. Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator") Signed-off-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/lcnalloc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c index 795f71d26895..0d6cd08ee2e7 100644 --- a/fs/ntfs/lcnalloc.c +++ b/fs/ntfs/lcnalloc.c @@ -1045,8 +1045,9 @@ s64 __ntfs_cluster_free(struct ntfs_inode *ni, const s64 start_vcn, s64 count, "Failed to rollback (error %i). Leaving inconsistent metadata! Unmount and run chkdsk.", (int)delta); NVolSetErrors(vol); + } else { + ntfs_dec_free_clusters(vol, delta); } - ntfs_dec_free_clusters(vol, delta); up_write(&vol->lcnbmp_lock); memalloc_nofs_restore(memalloc_flags); ntfs_error(vol->sb, "Aborting (error %i).", err); From 0e4c839905418d55bafe571a92533a1d1ac7b0a8 Mon Sep 17 00:00:00 2001 From: Dennis Tighe Date: Sun, 23 Aug 2026 22:08:40 -0700 Subject: [PATCH 0138/2366] ntfs: do not mark the volume clean in sync_fs when errors were recorded ntfs_put_super() and the remount-read-only path both clear the dirty bit only when NVolErrors(vol) is false. ntfs_sync_fs() clears it unconditionally, so any sync() on a volume that recorded an error marks that volume clean. A volume without this set is then seen as not needing recovery and it does not run one, so whatever went wrong is never repaired. This change skips resetting the dirty bit when there are volume errors. Reproduced on a volume whose $MFTMirr does not match $MFT, which sets the error flag while leaving the mount read-write: after a write and a sync, the on-disk volume flags read 0x0000 with this driver and 0x0001 with the guard in place. Fixes: 6251f0b0de7d ("ntfs: update super block operations") Assisted-by: claude:claude-opus-5 Signed-off-by: Dennis Tighe Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/super.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 30481e5d5dd4..a4dc64fb89ed 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -1862,7 +1862,8 @@ static int ntfs_sync_fs(struct super_block *sb, int wait) return 0; /* If there are some dirty buffers in the bdev inode */ - if (ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) { + if (!NVolErrors(vol) && + ntfs_clear_volume_flags(vol, VOLUME_IS_DIRTY)) { ntfs_warning(sb, "Failed to clear dirty bit in volume information flags. Run chkdsk."); err = -EIO; } From 8d139e3635c86e2c97c78d55538ce0b00b6d9986 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Mon, 24 Aug 2026 13:41:15 +0800 Subject: [PATCH 0139/2366] ntfs: fix incorrect MFT record pointer passed to ntfs_attr_record_resize ntfs_new_attr_flags() passes the wrong MFT record to ntfs_attr_record_resize(). When the attribute is in an extent record, ctx->mrec points to the extent but the function receives the base record pointer m, causing incorrect size calculations in memmove. Fix by passing ctx->mrec (the actual MFT record containing the attribute) instead of m (the base MFT record) to ntfs_attr_record_resize(). Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/ea.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index f6dbdfe6ff15..9f0222c172d9 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -764,7 +764,7 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr) memmove((u8 *)a + mp_ofs, (u8 *)a + old_mp_ofs, mp_size); } - err = ntfs_attr_record_resize(m, a, arec_size); + err = ntfs_attr_record_resize(ctx->mrec, a, arec_size); if (unlikely(err)) goto err_out; From 607a9478833db656e7ceac8e9e382fa4acfde545 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Tue, 25 Aug 2026 13:46:59 +0800 Subject: [PATCH 0140/2366] ntfs: treat any nonzero dio zero-range return as an error ntfs_dio_zero_range() returns either 0 or a negative errno from blkdev_issue_zeroout(); it never returns a positive value. The zeroing failure check in ntfs_attr_fallocate() therefore never fired, so a failed zeroing operation was silently ignored: the loop kept going, the newly allocated clusters were folded into initialized_size and the write could succeed leaving stale on-disk data. Treat any nonzero return as an error and abort the allocation. Fixes: 495e90fa33482 ("ntfs: update attrib operations") Assisted-by: atomcode:deepseek-v4-flash Signed-off-by: Wentao Guan Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 4dff5c3f779b..c55dc47d2261 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -5704,7 +5704,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo lcn << vol->cluster_size_bits, alloc_cnt << vol->cluster_size_bits); - if (err > 0) + if (err) goto out; } From a79899ca38af4ce5518a59a22f0eb1f59f7e6089 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Tue, 25 Aug 2026 17:54:05 +0800 Subject: [PATCH 0141/2366] ntfs: fix undefined behavior in mft/index record size calculation The boot sector validation allows clusters_per_mft_record and clusters_per_index_record to range from 0xE1 (-31) to 0xF7 (-9) when interpreted as signed values. When these are used as negative shift counts in expressions like `1 << -clusters_per_mft_record`, values like 0xE1 cause `1 << 31`, which shifts into the sign bit of a 32-bit signed integer, resulting in undefined behavior. Fix by using unsigned shift (1U << ...) instead of signed shift. This prevents undefined behavior while preserving the full valid range of negative values (-31 to -9) that may appear in NTFS boot sectors. The encoding scheme uses negative values to represent record sizes smaller than cluster_size: -log2(record_size). Common values include -10 (1024 bytes) for mft_record_size and -12 (4096 bytes) for index_record_size. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng Reviewed-by: Baolin Liu Signed-off-by: Namjae Jeon --- fs/ntfs/super.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index a4dc64fb89ed..2df64712335a 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -695,7 +695,7 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol, * = -log2(mft_record_size) bytes. mft_record_size normaly is * 1024 bytes, which is encoded as 0xF6 (-10 in decimal). */ - vol->mft_record_size = 1 << -clusters_per_mft_record; + vol->mft_record_size = 1U << -clusters_per_mft_record; vol->mft_record_size_mask = vol->mft_record_size - 1; vol->mft_record_size_bits = ffs(vol->mft_record_size) - 1; ntfs_debug("vol->mft_record_size = %i (0x%x)", vol->mft_record_size, @@ -732,7 +732,7 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol, * index_record_size normaly equals 4096 bytes, which is * encoded as 0xF4 (-12 in decimal). */ - vol->index_record_size = 1 << -clusters_per_index_record; + vol->index_record_size = 1U << -clusters_per_index_record; vol->index_record_size_mask = vol->index_record_size - 1; vol->index_record_size_bits = ffs(vol->index_record_size) - 1; ntfs_debug("vol->index_record_size = %i (0x%x)", From c8504fc1245f5322af5fa5c325ab05f9cf792b87 Mon Sep 17 00:00:00 2001 From: Dennis Tighe Date: Tue, 25 Aug 2026 21:44:24 -0700 Subject: [PATCH 0142/2366] ntfs: bound $AttrDef table walk to the loaded table size ntfs_attr_find_in_attrdef() walks the in-memory $AttrDef table, but the loop condition bounds only the start of each entry, not the whole entry: for (ad = vol->attrdef; (u8 *)ad - (u8 *)vol->attrdef < vol->attrdef_size && ad->type; ++ad) struct attr_def is 160 bytes; the guard reads ad->type at offset 128 and the loop body reads further fields. vol->attrdef is kvzalloc(i_size), where i_size is the on-disk $AttrDef data size, checked in load_and_init_attrdef() only as 0 < i_size <= 0x7fffffff. A volume whose $AttrDef data size is smaller than one entry (e.g. 120 bytes) makes the read of ad->type run past the allocation. Creating a file reaches this through ntfs_attr_size_bounds_check() and reads out of bounds: BUG: KASAN: slab-out-of-bounds in ntfs_attr_find_in_attrdef+0x66/0xa0 Read of size 4 at addr ffff888005833280 by task init/1 ntfs_attr_find_in_attrdef ntfs_attr_size_bounds_check ntfs_attr_can_be_non_resident ntfs_attr_add Require the whole entry to lie within attrdef_size in the loop guard, and reject at mount a $AttrDef too small to hold one attr_def entry. Fixes: 1e9ea7e04472 ("Revert "fs: Remove NTFS classic"") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dennis Tighe Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 4 ++-- fs/ntfs/super.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index c55dc47d2261..b3e941423a3f 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -1737,8 +1737,8 @@ static struct attr_def *ntfs_attr_find_in_attrdef(const struct ntfs_volume *vol, struct attr_def *ad; WARN_ON(!type); - for (ad = vol->attrdef; (u8 *)ad - (u8 *)vol->attrdef < - vol->attrdef_size && ad->type; ++ad) { + for (ad = vol->attrdef; (u8 *)ad - (u8 *)vol->attrdef <= + vol->attrdef_size - (s32)sizeof(*ad) && ad->type; ++ad) { /* We have not found it yet, carry on searching. */ if (likely(le32_to_cpu(ad->type) < le32_to_cpu(type))) continue; diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 2df64712335a..155dca5db7a9 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -1241,9 +1241,9 @@ static bool load_and_init_attrdef(struct ntfs_volume *vol) goto failed; } NInoSetSparseDisabled(NTFS_I(ino)); - /* The size of FILE_AttrDef must be above 0 and fit inside 31 bits. */ + /* FILE_AttrDef must hold at least one entry and fit inside 31 bits. */ i_size = i_size_read(ino); - if (i_size <= 0 || i_size > 0x7fffffff) + if (i_size < (s64)sizeof(struct attr_def) || i_size > 0x7fffffff) goto iput_failed; vol->attrdef = kvzalloc(i_size, GFP_NOFS); if (!vol->attrdef) From 323751a604e7533fa473874d999371592a614207 Mon Sep 17 00:00:00 2001 From: Dennis Tighe Date: Tue, 25 Aug 2026 22:09:34 -0700 Subject: [PATCH 0143/2366] ntfs: reject invalid sectors_per_cluster in the boot sector is_boot_sector_ntfs() checks the boot sector's sectors_per_cluster field with a range test that rejects 0x81..0xf3 but accepts 0 and other non-power-of-two counts. A zero value reaches parse_ntfs_boot_sector(): sectors_per_cluster_bits = ffs(sectors_per_cluster) - 1; ... vol->cluster_size = vol->sector_size << sectors_per_cluster_bits; ffs(0) is 0, so sectors_per_cluster_bits becomes (unsigned)-1 and the shift is undefined: UBSAN: shift-out-of-bounds in fs/ntfs/super.c:673:39 shift exponent 4294967295 is too large for 32-bit type 'int' This change rejects any non-power-of-two value, since it feeds the aforementioned shift via ffs() - 1, which only yields the correct shift for a power of two. Fixes: 6251f0b0de7d ("ntfs: update super block operations") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dennis Tighe Signed-off-by: Namjae Jeon --- fs/ntfs/super.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 155dca5db7a9..60d43339c590 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -557,8 +557,8 @@ static bool is_boot_sector_ntfs(const struct super_block *sb, * Check sectors per cluster value is valid and the cluster size * is not above the maximum (2MB). */ - if (b->bpb.sectors_per_cluster > 0x80 && - b->bpb.sectors_per_cluster < 0xf4) + if (b->bpb.sectors_per_cluster < 0xf4 && + !is_power_of_2(b->bpb.sectors_per_cluster)) goto not_ntfs; /* Check reserved/unused fields are really zero. */ From 21efadc62272cabee9bec27777ae75d84a9ca8a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Tue, 18 Aug 2026 18:00:02 +0200 Subject: [PATCH 0144/2366] Input: adp5588-keys - cache GPIO state before registering the gpiochip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So as not to clobber any pre-programmed GPIO state in the execution of its gpiochip ops, the driver caches things during probe time. However, since those ops can be called both during and immediately after the call to devm_gpiochip_add_data(), it is imperative that things are cached before that. That's not the case right now, so reorder the two steps to prevent any clobbering. In the concrete example which motivated this change, a bootloader was preconfiguring an important GPIO output to HIGH before booting the kernel. Linux would then inadvertently set that output to LOW while configuring a GPIO hog on a discrete GPIO line within the same 8-bit bank (because the cached value was 0=LOW). Fixes: ba9f507a1bea ("Input: adp5588-keys - export unused GPIO pins") Signed-off-by: Alvin Šipraga Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260818-adp5588-gpio-cache-v1-1-650a2674fc0d@analog.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/adp5588-keys.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/input/keyboard/adp5588-keys.c b/drivers/input/keyboard/adp5588-keys.c index 40371f5bd9ba..4f0ddff5baba 100644 --- a/drivers/input/keyboard/adp5588-keys.c +++ b/drivers/input/keyboard/adp5588-keys.c @@ -446,12 +446,6 @@ static int adp5588_gpio_add(struct adp5588_kpad *kpad) mutex_init(&kpad->gpio_lock); - error = devm_gpiochip_add_data(dev, &kpad->gc, kpad); - if (error) { - dev_err(dev, "gpiochip_add failed: %d\n", error); - return error; - } - for (i = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++) { kpad->dat_out[i] = adp5588_read(kpad->client, GPIO_DAT_OUT1 + i); @@ -459,6 +453,12 @@ static int adp5588_gpio_add(struct adp5588_kpad *kpad) kpad->pull_dis[i] = adp5588_read(kpad->client, GPIO_PULL1 + i); } + error = devm_gpiochip_add_data(dev, &kpad->gc, kpad); + if (error) { + dev_err(dev, "gpiochip_add failed: %d\n", error); + return error; + } + return 0; } From 4d7fa28e151a6a6f24098d9e60f558f8ea61ddaa Mon Sep 17 00:00:00 2001 From: Roman Vivchar Date: Fri, 14 Aug 2026 16:13:52 +0300 Subject: [PATCH 0145/2366] dt-bindings: input: mediatek,mt6779-keypad: add mt6572 Add a compatible string for the mt6572 keypad, that is compatible with mt6779. Signed-off-by: Roman Vivchar Reviewed-by: Mattijs Korpershoek Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260814-6572-dt-keypad-v1-1-2ceff2373211@protonmail.com Signed-off-by: Dmitry Torokhov --- .../devicetree/bindings/input/mediatek,mt6779-keypad.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml b/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml index 914dd3283df3..20fa41f691ed 100644 --- a/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml +++ b/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml @@ -26,6 +26,7 @@ properties: - const: mediatek,mt6779-keypad - items: - enum: + - mediatek,mt6572-keypad - mediatek,mt6873-keypad - mediatek,mt8183-keypad - mediatek,mt8365-keypad From 16b10f64c63f78220c3b4035f1ed6cd3bdcb0b02 Mon Sep 17 00:00:00 2001 From: Michail Tatas Date: Thu, 13 Aug 2026 20:36:50 +0300 Subject: [PATCH 0146/2366] gpiolib: Put fwnode reference on failure We get a reference to the fwnode handle which we pass to gpio_shared_make_ref. In case it fails we do not put the reference. Fix by putting the reference in the failure case Fixes: 49416483a953 ("gpio: shared: allow sharing a reset-gpios pin between reset-gpio and gpiolib") Cc: stable@vger.kernel.org Signed-off-by: Michail Tatas Link: https://patch.msgid.link/an4Asr4tx3D2QvLD@michalis-linux Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-shared.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c index 495bd3d0ddf0..5f9623e40b0f 100644 --- a/drivers/gpio/gpiolib-shared.c +++ b/drivers/gpio/gpiolib-shared.c @@ -261,10 +261,13 @@ static int gpio_shared_of_traverse(struct device_node *curr) con_id[con_id_len - suffix_len] = '\0'; } - ref = gpio_shared_make_ref(fwnode_handle_get(of_fwnode_handle(curr)), - con_id, args.args[1]); - if (!ref) + struct fwnode_handle *curr_fwnode = + fwnode_handle_get(of_fwnode_handle(curr)); + ref = gpio_shared_make_ref(curr_fwnode, con_id, args.args[1]); + if (!ref) { + fwnode_handle_put(curr_fwnode); return -ENOMEM; + } if (!list_empty(&entry->refs)) pr_debug("GPIO %u at %s is shared by multiple firmware nodes\n", From 53676a5e28231186c9f56d87b7998b640699bc15 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Sun, 23 Aug 2026 18:26:46 -0500 Subject: [PATCH 0147/2366] cifs: add revalidation on FSCTL failure in smb2_duplicate_extents() smb2_duplicate_extents() has no handling for FSCTL_DUPLICATE_EXTENTS_TO_FILE failure: when the FSCTL fails, local inode metadata may be stale from the pre-extension or from concurrent remote writes, but is never refreshed. Force revalidation on FSCTL failure and use i_size_read() for the pre-extension check. Fixes: cfc63fc8126a ("smb3: fix cached file size problems in duplicate extents (reflink)") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 7d6738ffcb80..bea4876b58cb 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -2218,7 +2218,7 @@ smb2_duplicate_extents(const unsigned int xid, trgtfile->fid.volatile_fid, tcon->tid, tcon->ses->Suid, src_off, dest_off, len); inode = d_inode(trgtfile->dentry); - if (inode->i_size < dest_off + len) { + if (i_size_read(inode) < dest_off + len) { rc = smb2_set_file_size(xid, tcon, trgtfile, dest_off + len, false); if (rc) goto duplicate_extents_out; @@ -2235,7 +2235,10 @@ smb2_duplicate_extents(const unsigned int xid, if (ret_data_len > 0) cifs_dbg(FYI, "Non-zero response length in duplicate extents\n"); - if (rc == 0) { + if (rc) { + CIFS_I(inode)->time = 0; /* force reval */ + cifs_invalidate_cache(inode, 0); + } else { qrc = SMB2_query_info(xid, tcon, trgtfile->fid.persistent_fid, trgtfile->fid.volatile_fid, &file_inf); spin_lock(&inode->i_lock); From 2d2a3adc91950f9a18829dadc7317fb5180a15c5 Mon Sep 17 00:00:00 2001 From: GuoHan Zhao Date: Fri, 17 Jul 2026 14:11:45 +0800 Subject: [PATCH 0148/2366] accel/ethosu: fix job completion fence cleanup ethosu_ioctl_submit_job() allocates done_fence before validating buffer handles. Errors after allocation call ethosu_job_err_cleanup(), which frees the job but leaks the uninitialized fence. A scheduler dependency error also lets ethosu_job_run() return before dma_fence_init(). Normal cleanup then passes a zeroed refcount to dma_fence_put(). Release done_fence in the common cleanup path and use dma_fence_was_initialized() to distinguish initialized fences from raw allocations. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Cc: stable@vger.kernel.org Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260716065219.931088-1-zhaoguohan@kylinos.cn?part=1 Signed-off-by: GuoHan Zhao Link: https://patch.msgid.link/20260717061145.1478139-6-zhaoguohan@kylinos.cn [robh: also fix goto] Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_job.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c index 1e2465279aae..1e4b65f62933 100644 --- a/drivers/accel/ethosu/ethosu_job.c +++ b/drivers/accel/ethosu/ethosu_job.c @@ -152,6 +152,13 @@ static void ethosu_job_err_cleanup(struct ethosu_job *job) drm_gem_object_put(job->cmd_bo); + if (job->done_fence) { + if (dma_fence_was_initialized(job->done_fence)) + dma_fence_put(job->done_fence); + else + dma_fence_free(job->done_fence); + } + kfree(job); } @@ -162,7 +169,6 @@ static void ethosu_job_cleanup(struct kref *ref) pm_runtime_put_autosuspend(job->dev->base.dev); - dma_fence_put(job->done_fence); dma_fence_put(job->inference_done_fence); ethosu_job_err_cleanup(job); @@ -393,7 +399,7 @@ static int ethosu_ioctl_submit_job(struct drm_device *dev, struct drm_file *file ejob->done_fence = kzalloc_obj(*ejob->done_fence); if (!ejob->done_fence) { ret = -ENOMEM; - goto out_cleanup_job; + goto out_put_job; } ret = drm_sched_job_init(&ejob->base, From d3ef6c097ba078e1f8c7239d76a0ce8b61e75095 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Wed, 26 Aug 2026 11:18:44 -0700 Subject: [PATCH 0149/2366] bpf: check_cond_jmp_op(): properly infer if register is null Nicholas Carlini reported a bug when verifier can incorrectly infer that a pointer is non-null. The bug occurs when two pointers are compared and one of them has a type w/o PTR_MAYBE_NULL flag, but which allows a value to be NULL at runtime. Here is an example: // `a` is PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED // `a` is 0 at runtime. // `b` is PTR_TO_MAP_VALUE | PTR_MAYBE_NULL void *a = bpf_rdonly_cast(0, 0); int *b = bpf_map_lookup_elem(...); if (a == b) *b = 42; // verifier does not catch null pointer dereference This happens because of a special case in check_cond_jmp_op(), which attempts to strip PTR_MAYBE_NULL flags from pointer types, when processing comparisons like `rA == rB`, if either rA or rB can't be null. The non-null property is derived based on the absence of PTR_MAYBE_NULL flag on rA's or rB's type. But that is not sufficient for types like PTR_TO_MEM, as in the example. This patch replaces type_may_be_null() call with reg_not_null(), which contains an allowlist of types for which absence of PTR_MAYBE_NULL actually means that the value can't be NULL at runtime. At the moment, the list in the reg_not_null() omits two types for which PTR_MAYBE_NULL is applicable: PTR_TO_XDP_SOCK and PTR_TO_BUF. In order to remain backward compatible, and assuming that only comparison between pointers of the same type makes sense, this commit extends reg_not_null(). W/o such an extension e.g. verifier_jeq_infer_not_null/null_ptr_to_map_value fails. reg_not_null() can be extended further, but I deem that out of scope for the fix at hand. Explicit base_type(...) != PTR_TO_BTF_ID checks in the check_cond_jmp_op() can be removed with migration to reg_not_null(), but that is a behavioural change, as the special case would start matching for PTR_TO_BTF_ID that is also is_trusted_reg(). I omit the behavioural change from this commit. Fixes: befae75856ab ("bpf: propagate nullness information for reg to reg comparisons") Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260826-bug-029-bad-non-null-inference-v2-1-136789ace9e9@localhost Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 5e37ca75e5c4..e64035683795 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -355,6 +355,8 @@ static bool reg_not_null(struct bpf_verifier_env *env, const struct bpf_reg_stat type = base_type(type); return type == PTR_TO_SOCKET || type == PTR_TO_TCP_SOCK || + type == PTR_TO_XDP_SOCK || + type == PTR_TO_BUF || type == PTR_TO_MAP_VALUE || type == PTR_TO_MAP_KEY || type == PTR_TO_SOCK_COMMON || @@ -16968,7 +16970,6 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, */ if (!is_jmp32 && BPF_SRC(insn->code) == BPF_X && __is_pointer_value(false, src_reg) && __is_pointer_value(false, dst_reg) && - type_may_be_null(src_reg->type) != type_may_be_null(dst_reg->type) && base_type(src_reg->type) != PTR_TO_BTF_ID && base_type(dst_reg->type) != PTR_TO_BTF_ID) { eq_branch_regs = NULL; @@ -16984,9 +16985,11 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, break; } if (eq_branch_regs) { - if (type_may_be_null(src_reg->type)) + /* src == dst && dst != NULL => src != NULL */ + if (reg_not_null(env, dst_reg) && type_may_be_null(src_reg->type)) mark_ptr_not_null_reg(&eq_branch_regs[insn->src_reg]); - else + /* src == dst && src != NULL => dst != NULL */ + if (reg_not_null(env, src_reg) && type_may_be_null(dst_reg->type)) mark_ptr_not_null_reg(&eq_branch_regs[insn->dst_reg]); } } From ce6dcd0aed185432d02cafc82b738318af257ccd Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Wed, 26 Aug 2026 11:18:45 -0700 Subject: [PATCH 0150/2366] selftests/bpf: a demo for check_cond_jmp_op() non-null inference bug A comparison between PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED and PTR_TO_MAP_VALUE_OR_NULL should not infer that map pointer is not null. A bug in check_cond_jmp_op() made such inference possible. Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260826-bug-029-bad-non-null-inference-v2-2-136789ace9e9@localhost Signed-off-by: Alexei Starovoitov --- .../bpf/progs/verifier_jeq_infer_not_null.c | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c b/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c index 3d1e8de4390c..b412a542ef76 100644 --- a/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c +++ b/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c @@ -3,7 +3,9 @@ #include #include +#include #include "bpf_misc.h" +#include "bpf_kfuncs.h" struct { __uint(type, BPF_MAP_TYPE_XSKMAP); @@ -12,6 +14,13 @@ struct { __type(value, int); } map_xskmap SEC(".maps"); +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1); + __type(key, int); + __type(value, int); +} map_hash SEC(".maps"); + /* This is equivalent to the following program: * * r6 = skb->sk; @@ -264,4 +273,47 @@ __naked void jne_reg_reg_null_check(void) : __clobber_all); } +/* + * A comparison between PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED and + * PTR_TO_MAP_VALUE_OR_NULL should not infer that map pointer is not null. + * A bug in check_cond_jmp_op() made such inference possible. + */ +SEC("raw_tp") +__failure +__msg("error: invalid dereference of R0 (a nullable map value pointer)") +__msg(">>> 11 | (61) r0 = *(u32 *)(r0 +0)") +__naked void untrusted_mem_does_not_infer_map_value_non_null(void) +{ + asm volatile (" \ + /* r6 = bpf_rdonly_cast(0, 0); */ \ + r1 = 0; \ + r2 = 0; \ + call %[bpf_rdonly_cast]; \ + r6 = r0; \ + /* r0 = bpf_map_lookup_elem(map_hash, &key); */ \ + *(u64 *)(r10 - 8) = 0; \ + r1 = %[map_hash] ll; \ + r2 = r10; \ + r2 += -8; \ + call %[bpf_map_lookup_elem]; \ + /* \ + * buggy verifier assumed that r6 can't be null \ + * and marked r0 non-null as well. \ + */ \ + if r6 != r0 goto 1f; \ + r0 = *(u32 *)(r0 + 0); \ +1: r0 = 0; \ + exit; \ +" : + : __imm(bpf_rdonly_cast), + __imm(bpf_map_lookup_elem), + __imm_addr(map_hash) + : __clobber_all); +} + +void kfunc_root(void) +{ + bpf_rdonly_cast(0, 0); +} + char _license[] SEC("license") = "GPL"; From 6faa235a649e78a82e3230b849607f446ba65ed5 Mon Sep 17 00:00:00 2001 From: Dennis Tighe Date: Sun, 23 Aug 2026 00:13:25 -0700 Subject: [PATCH 0151/2366] ntfs: compute bi_sector in 512-byte units bi_sector counts in 512 byte sectors and not in multiples of the volume's sector size. Under "normal" circumstances (with 512 byte sectors in NTFS) the current code works as is; however, when we have a 4k sector size on the volume the current usage of NTFS_B_TO_SECTOR() and ntfs_bytes_to_sector() end up converting to the number of 4k sectors after mount. Reads work today on 4k volumes as bdev-io.c as performing the shift correctly inline. With writes, we end up with significant silent disk corruption on these volumes. This fixes changes to use the new ntfs_bytes_to_bio_sector() function everywhere we're performing this calculation (including the existing read path). For the change in inode.c it removes a dead code block rather than updating. Fixes: 40796051991d ("ntfs: update in-memory, on-disk structures and headers") Assisted-by: Claude:claude-opus-5 Signed-off-by: Dennis Tighe Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/bdev-io.c | 2 +- fs/ntfs/compress.c | 2 +- fs/ntfs/inode.c | 10 ++-------- fs/ntfs/mft.c | 12 ++++++------ fs/ntfs/ntfs.h | 10 ++++------ 5 files changed, 14 insertions(+), 22 deletions(-) diff --git a/fs/ntfs/bdev-io.c b/fs/ntfs/bdev-io.c index 86db4d9298ed..4f27eed3b072 100644 --- a/fs/ntfs/bdev-io.c +++ b/fs/ntfs/bdev-io.c @@ -34,7 +34,7 @@ int ntfs_bdev_read(struct block_device *bdev, char *data, loff_t start, size_t s int error; struct bio *bio; blk_opf_t op; - sector_t sector = start >> SECTOR_SHIFT; + sector_t sector = ntfs_bytes_to_bio_sector(start); if (start & (SECTOR_SIZE - 1)) return -EINVAL; diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c index 2225630b19d7..197d8607fc63 100644 --- a/fs/ntfs/compress.c +++ b/fs/ntfs/compress.c @@ -1414,7 +1414,7 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages, bio_pos = ntfs_cluster_to_bytes(vol, bio_lcn); bio = bio_alloc(vol->sb->s_bdev, DIV_ROUND_UP(bio_size, PAGE_SIZE), REQ_OP_WRITE, GFP_NOIO); - bio->bi_iter.bi_sector = ntfs_bytes_to_sector(vol, bio_pos); + bio->bi_iter.bi_sector = ntfs_bytes_to_bio_sector(bio_pos); for (i = 0; bio_size; i++) { unsigned int len = min_t(unsigned int, bio_size, PAGE_SIZE); diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 32edb4045178..5aedc045f65a 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -1852,7 +1852,7 @@ int ntfs_read_inode_mount(struct inode *vi) struct mft_record *m = NULL; struct attr_record *a; struct ntfs_attr_search_ctx *ctx; - unsigned int i, nr_blocks; + unsigned int i; int err; size_t new_rl_count; @@ -1896,11 +1896,6 @@ int ntfs_read_inode_mount(struct inode *vi) goto err_out; } - /* Determine the first block of the $MFT/$DATA attribute. */ - nr_blocks = ntfs_bytes_to_sector(vol, vol->mft_record_size); - if (!nr_blocks) - nr_blocks = 1; - /* Load $MFT/$DATA's first mft record. */ err = ntfs_bdev_read(sb->s_bdev, (char *)m, ntfs_cluster_to_bytes(vol, vol->mft_lcn), i); @@ -3780,8 +3775,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); bio->bi_iter.bi_sector = - ntfs_bytes_to_sector(vol, - ntfs_cluster_to_bytes(vol, lcn) + + ntfs_bytes_to_bio_sector(ntfs_cluster_to_bytes(vol, lcn) + lcn_folio_off); length = min_t(unsigned long, diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 69b007e574fc..7e58c99f1728 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -499,8 +499,8 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); bio->bi_iter.bi_sector = - NTFS_B_TO_SECTOR(vol, NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) + - lcn_folio_off + folio_ofs); + ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) + + lcn_folio_off + folio_ofs); if (bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs)) err = submit_bio_wait(bio); @@ -592,8 +592,8 @@ int write_mft_record_nolock(struct ntfs_inode *ni, struct mft_record *m, int syn bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); bio->bi_iter.bi_sector = - NTFS_B_TO_SECTOR(vol, NTFS_CLU_TO_B(vol, ni->mft_lcn[i]) + - clu_off); + ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, ni->mft_lcn[i]) + + clu_off); if (!bio_add_folio(bio, folio, folio_size, ni->folio_ofs + offset)) { @@ -2742,8 +2742,8 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); bio->bi_iter.bi_sector = - ntfs_bytes_to_sector(vol, - ntfs_cluster_to_bytes(vol, lcn) + off); + ntfs_bytes_to_bio_sector( + ntfs_cluster_to_bytes(vol, lcn) + off); } if (vol->cluster_size == NTFS_BLOCK_SIZE && diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h index df5a75d506f6..45f77848a9cf 100644 --- a/fs/ntfs/ntfs.h +++ b/fs/ntfs/ntfs.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "volume.h" @@ -71,8 +72,6 @@ #define NTFS_CLU_TO_POFS(vol, clu) (((u64)(clu) << (vol)->cluster_size_bits) & \ ~PAGE_MASK) -#define NTFS_B_TO_SECTOR(vol, b) ((b) >> ((vol)->sb)->s_blocksize_bits) - enum { NTFS_BLOCK_SIZE = 512, NTFS_BLOCK_SIZE_BITS = 9, @@ -154,11 +153,10 @@ static inline u64 ntfs_cluster_to_poff(const struct ntfs_volume *vol, return (clu << vol->cluster_size_bits) & ~PAGE_MASK; } -/* Convert byte offset to sector (block) number. */ -static inline sector_t ntfs_bytes_to_sector(const struct ntfs_volume *vol, - u64 bytes) +/* Convert a byte offset on the volume to a bio sector number. */ +static inline sector_t ntfs_bytes_to_bio_sector(u64 bytes) { - return bytes >> vol->sb->s_blocksize_bits; + return bytes >> SECTOR_SHIFT; } /* Global variables. */ From acb1095fd2db884b417cb70808c886e4b615ff05 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Mon, 24 Aug 2026 15:59:35 +0800 Subject: [PATCH 0152/2366] ntfs: fix memmove overlap in ntfs_new_attr_flags When the record shrinks while the payload offsets increase (e.g., enabling compression reduces padding, making arec_size < old_arec_size, but the header grows by 8 bytes), moving the name first can overwrite the old mapping_pairs before they are copied. Move mapping_pairs first in this case. Since mp_ofs is derived from name_ofs, they always change in the same direction. Checking name_ofs alone is sufficient. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/ea.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 9f0222c172d9..0bc29bf1f050 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -753,15 +753,36 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr) old_arec_size = le32_to_cpu(a->length); /* - * Move payloads before shrinking the record. Otherwise resizing moves + * Move payloads before shrinking the record. Otherwise resizing moves * the following attribute over the old payload before it can be copied. + * + * When offsets increase, move mapping_pairs first to avoid name + * overwriting the start of mapping_pairs. */ if (arec_size < old_arec_size) { - if (a->name_length && name_ofs != old_name_ofs) - memmove((u8 *)a + name_ofs, (u8 *)a + old_name_ofs, - a->name_length * sizeof(__le16)); - if (mp_ofs != old_mp_ofs) - memmove((u8 *)a + mp_ofs, (u8 *)a + old_mp_ofs, mp_size); + if (name_ofs > old_name_ofs) { + /* Payload offsets increased: move mapping pairs first. */ + if (mp_ofs != old_mp_ofs) + memmove((u8 *)a + mp_ofs, + (u8 *)a + old_mp_ofs, + mp_size); + if (a->name_length && name_ofs != old_name_ofs) + memmove((u8 *)a + name_ofs, + (u8 *)a + old_name_ofs, + a->name_length * + sizeof(__le16)); + } else { + /* Payload offsets decreased or unchanged: move name first. */ + if (a->name_length && name_ofs != old_name_ofs) + memmove((u8 *)a + name_ofs, + (u8 *)a + old_name_ofs, + a->name_length * + sizeof(__le16)); + if (mp_ofs != old_mp_ofs) + memmove((u8 *)a + mp_ofs, + (u8 *)a + old_mp_ofs, + mp_size); + } } err = ntfs_attr_record_resize(ctx->mrec, a, arec_size); From 67aded1da114dc44808315f249bd9e7e440f799d Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Thu, 27 Aug 2026 13:58:44 +0800 Subject: [PATCH 0153/2366] ntfs: fix race between fallocate and mmap reads The fallocate implementation only takes invalidate_lock for punch hole, collapse range, and insert range operations. For standard allocation modes (mode == 0, FALLOC_FL_KEEP_SIZE), the lock is not held. During ntfs_attr_fallocate(), new clusters are mapped to the runlist via ntfs_attr_map_cluster() before being zeroed by ntfs_dio_zero_range(). This creates a window where concurrent mmap page faults can read uninitialized disk data. Since mmap uses filemap_fault() which takes invalidate_lock in shared mode, it can fault in pages during this window and expose old disk contents to userspace. This is an information leak and data integrity issue. Fix by taking invalidate_lock for all fallocate operations, not just for punch/collapse/insert modes. This prevents concurrent page faults from accessing unzeroed clusters during the allocation window. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Cc: stable@vger.kernel.org Reviewed-by: Baolin Liu Reviewed-by: Hyunchul Lee Signed-off-by: Hongling Zeng Signed-off-by: Namjae Jeon --- fs/ntfs/file.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 88747217ba61..1969e4f444f7 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -1116,7 +1116,6 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le struct ntfs_volume *vol = ni->vol; int err = 0; loff_t old_size; - bool map_locked = false; if (mode & ~(NTFS_FALLOC_FL_SUPPORTED)) return -EOPNOTSUPP; @@ -1148,16 +1147,13 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le inode_lock(vi); if (NInoCompressed(ni) || NInoEncrypted(ni) || NInoWofCompressed(ni)) { - err = -EOPNOTSUPP; - goto out; + inode_unlock(vi); + return -EOPNOTSUPP; } inode_dio_wait(vi); - if (mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_COLLAPSE_RANGE | - FALLOC_FL_INSERT_RANGE)) { - filemap_invalidate_lock(vi->i_mapping); - map_locked = true; - } + /* Take invalidate_lock for all fallocate operations to prevent races */ + filemap_invalidate_lock(vi->i_mapping); switch (mode & FALLOC_FL_MODE_MASK) { case FALLOC_FL_ALLOCATE_RANGE: @@ -1182,8 +1178,7 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le err = file_modified(file); out: - if (map_locked) - filemap_invalidate_unlock(vi->i_mapping); + filemap_invalidate_unlock(vi->i_mapping); if (!err) { if (mode == 0 && NInoNonResident(ni) && offset > old_size) { From ac727d86fb84bdc9626ba9c756c26767459f3083 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Thu, 27 Aug 2026 14:43:17 +0800 Subject: [PATCH 0154/2366] ntfs: leave HasEA flag untouched on setxattr failure In ntfs_set_ea(), the exit path unconditionally updates the HasEA flag based on ea_info_qsize. When an error occurs before ea_info_qsize is updated, NInoClearHasEA() hides existing on-disk EAs until the inode is evicted. Only update the flag on success. Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations") Signed-off-by: Baolin Liu Signed-off-by: Namjae Jeon --- fs/ntfs/ea.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 0bc29bf1f050..3f4ba7667522 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -404,10 +404,12 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len, *packed_ea_size = p_ea_info->ea_length; mark_mft_record_dirty(ni); out: - if (ea_info_qsize > 0) - NInoSetHasEA(ni); - else - NInoClearHasEA(ni); + if (!err) { + if (ea_info_qsize > 0) + NInoSetHasEA(ni); + else + NInoClearHasEA(ni); + } kvfree(ea_buf); kvfree(old_ea_buf); From 20839d02c0cf7437bc508d4c5430538d9dc4f428 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 26 Aug 2026 12:54:21 +0200 Subject: [PATCH 0155/2366] drm/tegra: Add blend mode properties The default programming in the driver matches the "coverage" blend mode, so add the corresponding pixel blend mode property to let userspace know about it. Tested-by: Jon Hunter Acked-by: Jon Hunter Signed-off-by: Thierry Reding Link: https://patch.msgid.link/20260826105421.1825331-1-thierry.reding@kernel.org --- drivers/gpu/drm/tegra/dc.c | 6 ++++++ drivers/gpu/drm/tegra/hub.c | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 0b3fcc7011b3..fefc3761a4bc 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -904,6 +904,7 @@ static struct drm_plane *tegra_primary_plane_create(struct drm_device *drm, struct tegra_dc *dc) { unsigned long possible_crtcs = tegra_plane_get_possible_crtcs(drm); + unsigned int blend_caps = BIT(DRM_MODE_BLEND_COVERAGE); enum drm_plane_type type = DRM_PLANE_TYPE_PRIMARY; struct tegra_plane *plane; unsigned int num_formats; @@ -939,6 +940,7 @@ static struct drm_plane *tegra_primary_plane_create(struct drm_device *drm, } drm_plane_helper_add(&plane->base, &tegra_plane_helper_funcs); + drm_plane_create_blend_mode_property(&plane->base, blend_caps); drm_plane_create_zpos_property(&plane->base, plane->index, 0, 255); err = drm_plane_create_rotation_property(&plane->base, @@ -1209,6 +1211,7 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, struct tegra_dc *dc) { unsigned long possible_crtcs = tegra_plane_get_possible_crtcs(drm); + unsigned int blend_caps = BIT(DRM_MODE_BLEND_COVERAGE); struct tegra_plane *plane; unsigned int num_formats; const u32 *formats; @@ -1252,6 +1255,7 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, } drm_plane_helper_add(&plane->base, &tegra_cursor_plane_helper_funcs); + drm_plane_create_blend_mode_property(&plane->base, blend_caps); drm_plane_create_zpos_immutable_property(&plane->base, 255); return &plane->base; @@ -1356,6 +1360,7 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm, bool cursor) { unsigned long possible_crtcs = tegra_plane_get_possible_crtcs(drm); + unsigned int blend_caps = BIT(DRM_MODE_BLEND_COVERAGE); struct tegra_plane *plane; unsigned int num_formats; enum drm_plane_type type; @@ -1394,6 +1399,7 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm, } drm_plane_helper_add(&plane->base, &tegra_plane_helper_funcs); + drm_plane_create_blend_mode_property(&plane->base, blend_caps); drm_plane_create_zpos_property(&plane->base, plane->index, 0, 255); err = drm_plane_create_rotation_property(&plane->base, diff --git a/drivers/gpu/drm/tegra/hub.c b/drivers/gpu/drm/tegra/hub.c index bd442bfd4540..448f49f3a7d7 100644 --- a/drivers/gpu/drm/tegra/hub.c +++ b/drivers/gpu/drm/tegra/hub.c @@ -759,6 +759,7 @@ struct drm_plane *tegra_shared_plane_create(struct drm_device *drm, unsigned int index, enum drm_plane_type type) { + unsigned int blend_caps = BIT(DRM_MODE_BLEND_COVERAGE); struct tegra_drm *tegra = drm->dev_private; struct tegra_display_hub *hub = tegra->hub; struct tegra_shared_plane *plane; @@ -797,6 +798,7 @@ struct drm_plane *tegra_shared_plane_create(struct drm_device *drm, } drm_plane_helper_add(p, &tegra_shared_plane_helper_funcs); + drm_plane_create_blend_mode_property(p, blend_caps); drm_plane_create_zpos_property(p, 0, 0, 255); return p; From aefbda23eeba234c3ff0f135dc5be6e294bd25a6 Mon Sep 17 00:00:00 2001 From: Alexei Turtanov <9alexei9@gmail.com> Date: Fri, 28 Aug 2026 14:22:39 +0300 Subject: [PATCH 0156/2366] Input: atkbd - skip deactivate for Xiaomi Redmi Book Pro 16 2026 The internal keyboard of the Xiaomi Redmi Book Pro 16 2026 (board TM2425) does not work: atkbd_probe() succeeds and every command is ACKed, but no scancodes ever arrive afterwards. Testing on the hardware through serio_raw shows that ATKBD_CMD_RESET_DIS (0xF5) is the culprit. After 0xF5 the embedded controller keeps ACKing commands but stops delivering scancodes, and neither ATKBD_CMD_ENABLE (0xF4) nor ATKBD_CMD_RESET_BAT (0xFF) bring them back. Only re-enabling the keyboard interface at the controller level (i8042 command 0xAE, or rewriting the command byte as i8042_port_close() does) revives it. Running the init sequence without 0xF5 (0xED 0x00, 0xF3 0x00, 0xF4) keeps the keyboard working. 'i8042.dumbkbd=1' also works around this, but then the driver never writes to the keyboard and the LEDs cannot be controlled. Use the existing atkbd_deactivate_fixup quirk instead, as done for the sibling TM2424 by commit 3a046db33bb9 ("Input: atkbd - skip deactivate for Xiaomi Book Pro 14's internal keyboard"). Tested on v7.2: keyboard, Caps Lock LED and s2idle suspend/resume all work. DMI: XIAOMI REDMI Book Pro 16 2026/TM2425, BIOS RMAPT6B0P0909 05/22/2026 Fixes: 9cf6e24c9fbf ("Input: atkbd - do not skip atkbd_deactivate() when skipping ATKBD_CMD_GETID") Cc: stable@vger.kernel.org Signed-off-by: Alexei Turtanov <9alexei9@gmail.com> Link: https://patch.msgid.link/20260828112239.18081-1-9alexei9@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/atkbd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index 5736f4bc5a50..2627434099ff 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -1946,6 +1946,14 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = { }, .callback = atkbd_deactivate_fixup, }, + { + /* Xiaomi Redmi Book Pro 16 2026 (TM2425) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "XIAOMI"), + DMI_MATCH(DMI_PRODUCT_NAME, "REDMI Book Pro 16 2026"), + }, + .callback = atkbd_deactivate_fixup, + }, { } }; From 2f3536bff8823d3c5fdbbe15e17bfca696cc2b2e Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Thu, 27 Aug 2026 15:48:23 -0700 Subject: [PATCH 0157/2366] bpf: don't downgrade half-dead scalar zero spills to STACK_ZERO states.c:__clean_func_state() can downgrade scalar zero spill to STACK_ZERO in the following case: *(u64 *)(r10 - 8) = 0; ... checkpoint ... r1 = *(u32 *)(r10 - 4); ... no reads from r10-8 ... Here 4 bytes at r10-8 are dead and verifier changes scalar spill to a combination: 0000pppp (p stands for poison). Such a change breaks precision propagation chains. All places that produce STACK_ZERO should call bpf_mark_chain_precision() for the zero source. This patch fixes the bug in a simplest way possible: avoids converting stack spills of zero to STACK_ZERO. Two smarter approaches are possible: - do bpf_mark_chain_precision() from __clean_func_state() - check slot liveness information in check_stack_write_fixed_off() I investigated both and the changes required are a bit tricky, hence go with a simple fix for the time being. Fixes: be23266b4a08 ("bpf: 4-byte precise clean_verifier_state") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260827-bug-011-cleanfunc-stack-zero-simple-v1-v1-1-c0e996589a52@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/states.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c index 4e6aafad33bd..66fb11b6c6a7 100644 --- a/kernel/bpf/states.c +++ b/kernel/bpf/states.c @@ -445,22 +445,19 @@ static void __clean_func_state(struct bpf_verifier_env *env, struct bpf_reg_state *spill = &st->stack[i].spilled_ptr; if (lo_live && stype == STACK_SPILL) { - u8 val = STACK_MISC; - if (spill->type != SCALAR_VALUE) continue; - /* - * 8 byte spill of scalar 0 where half slot is dead - * should become STACK_ZERO in lo 4 bytes. + * Can't replace with STACK_ZERO, because + * that requires bpf_mark_chain_precision(). */ if (bpf_register_is_null(spill)) - val = STACK_ZERO; + continue; for (j = 0; j < 4; j++) { u8 *t = &st->stack[i].slot_type[j]; if (*t == STACK_SPILL) - *t = val; + *t = STACK_MISC; } } bpf_mark_reg_not_init(env, spill); From c6ff14f1cd9e9b7d5631882ff509fbc29e90cfe0 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Thu, 27 Aug 2026 15:48:24 -0700 Subject: [PATCH 0158/2366] selftests/bpf: half-dead scalar zero stack spill test A test case demonstrating unsafe pruning when spill of a scalar zero spilled on a first pass in replaced by STACK_ZERO in the __clean_func_state(). Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260827-bug-011-cleanfunc-stack-zero-simple-v1-v1-2-c0e996589a52@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/verifier_spill_fill.c | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c index 8b166c42c4e0..39a1766dae3f 100644 --- a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c +++ b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c @@ -1403,6 +1403,46 @@ __naked void partial_fill_from_cleaned_pointer_spill(void) ::: __clobber_all); } +SEC("raw_tp") +__failure +__msg("access may be outside object bounds") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void imprecise_scalar_spill_half_dead(void) +{ + asm volatile ( + /* + * Fork two paths: the one explored first spills an imprecise zero, + * the one explored second, an imprecise non-zero scalar. + */ + "call %[bpf_get_prandom_u32];" + "if r0 > 42 goto 1f;" + "r6 = 0;" + "goto 2f;" +"1:" + /* causes out of bounds access on a second path. */ + "r6 = 100500;" +"2:" + /* Force a checkpoint before the spill. */ + "goto +0;" + "*(u64 *)(r10 - 8) = r6;" + /* + * Force stack cleanup, only the low half of the spill is alive, + * so the dead high half is degraded to raw stack bytes. + * Buggy verifier converted it to STACK_ZERO w/o proper precision propagation. + */ + "goto +0;" + "r7 = *(u32 *)(r10 - 4);" + /* Use r7 as an offset into a one-byte buffer. */ + "r1 = %[single_byte_buf] ll;" + "r1 += r7;" + "r0 = *(u8 *)(r1 + 0);" + "exit;" +: +: __imm(bpf_get_prandom_u32), + __imm_addr(single_byte_buf) +: __clobber_all); +} + /* check valid spill/fill, ptr to tp buffer */ SEC("raw_tracepoint.w") __success From 5046d2880fec7d49ebed2fd2866747ee1d06ad71 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Thu, 27 Aug 2026 18:25:14 +0000 Subject: [PATCH 0159/2366] ipv4: avoid divide by zero in fib_rebalance fib_rebalance() computes the total eligible nexthop weight in one pass and programs upper bounds in a second pass. A concurrent change to ignore_routes_with_linkdown can make the first pass return zero while the second pass sees an eligible nexthop, resulting in division by zero. If the first pass reports a zero total, set each nexthop upper bound to -1 and skip the division. This matches the IPv6 fix in commit d2c26c2911dd ("ipv6: avoid divide by zero in rt6_multipath_rebalance") and preserves the lock-free rebalance path. Fixes: 0e884c78ee19 ("ipv4: L3 hash-based multipath") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zihan Xi Reviewed-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260827182514.4667-2-zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv4/fib_semantics.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c index 0483519b7fb0..7a362f2e2c2b 100644 --- a/net/ipv4/fib_semantics.c +++ b/net/ipv4/fib_semantics.c @@ -874,7 +874,7 @@ static void fib_rebalance(struct fib_info *fi) change_nexthops(fi) { int upper_bound; - if (nexthop_nh->fib_nh_flags & RTNH_F_DEAD) { + if (!total || nexthop_nh->fib_nh_flags & RTNH_F_DEAD) { upper_bound = -1; } else if (ip_ignore_linkdown(nexthop_nh->fib_nh_dev) && nexthop_nh->fib_nh_flags & RTNH_F_LINKDOWN) { From dddf197f29ba5e47a476dde82bf542ca2e0d5e5e Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Wed, 26 Aug 2026 03:01:41 +0800 Subject: [PATCH 0160/2366] tipc: protect node reset trace dump with node lock The tipc_node_reset_links trace event asks tipc_node_dump() to walk the node's link entries. Unlike the other node events that request link data, this event runs without the node lock. This permits bearer teardown to free a link while the trace callback is dumping it: CPU 0 CPU 1 trace_tipc_node_reset_links() tipc_node_dump() l = n->links[0].link tipc_node_write_lock() kfree(l) n->links[0].link = NULL tipc_node_write_unlock() tipc_link_dump(l) tipc_link_dump() then dereferences the stale pointer. KASAN reported: BUG: KASAN: slab-use-after-free in tipc_link_dump Read of size 4 by task poc/115 Call Trace: tipc_link_dump+0x10cb/0x16b0 tipc_node_dump+0x4bb/0x740 trace_event_raw_event_tipc_node_class+0x258/0x360 tipc_node_reset_links+0x14d/0x1a0 tipc_rcv+0x13f5/0x3030 tipc_udp_recv+0x4e3/0x670 Allocated by task 0: tipc_link_create+0x1e1/0x1020 tipc_node_check_dest+0x7d2/0x11a0 tipc_disc_rcv+0xdbf/0x1430 Freed by task 89: kfree+0x131/0x3c0 tipc_node_link_down+0x267/0x4b0 tipc_node_delete_links+0xec/0x160 bearer_disable+0x107/0x260 Take the node write lock around the trace event. This serializes the dump against tipc_node_link_down(delete=true), which frees the link under the same write lock. Fixes: eb18a510b5cd ("tipc: add trace_events for tipc node") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/20260825190141.242219-1-nicoyip.dev@gmail.com Signed-off-by: Jakub Kicinski --- net/tipc/node.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/tipc/node.c b/net/tipc/node.c index 683a136e53ef..bd91378b7540 100644 --- a/net/tipc/node.c +++ b/net/tipc/node.c @@ -1333,7 +1333,9 @@ static void tipc_node_reset_links(struct tipc_node *n) pr_warn("Resetting all links to %x\n", n->addr); + tipc_node_write_lock(n); trace_tipc_node_reset_links(n, true, " "); + tipc_node_write_unlock_fast(n); for (i = 0; i < MAX_BEARERS; i++) { tipc_node_link_down(n, i, false); } From 7fcc2fe39fed1cb98a7374a113ff3800e8f9af80 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 25 Aug 2026 08:45:51 +0000 Subject: [PATCH 0161/2366] net: icmp: avoid invalid transport header access in icmp_send tracepoint syzbot reported a WARNING triggered by DEBUG_NET_WARN_ON_ONCE(): WARNING: at skb_transport_header include/linux/skbuff.h:3087 [inline] WARNING: at udp_hdr include/linux/udp.h:23 [inline] WARNING: at do_trace_event_raw_event_icmp_send include/trace/events/icmp.h:30 [inline] WARNING: at trace_event_raw_event_icmp_send+0x48c/0x6ec include/trace/events/icmp.h:11 Call trace: skb_transport_header include/linux/skbuff.h:3087 [inline] udp_hdr include/linux/udp.h:23 [inline] do_trace_event_raw_event_icmp_send include/trace/events/icmp.h:30 [inline] trace_event_raw_event_icmp_send+0x48c/0x6ec include/trace/events/icmp.h:11 __traceiter_icmp_send include/trace/events/icmp.h:11 [inline] __do_trace_icmp_send include/trace/events/icmp.h:11 [inline] trace_icmp_send+0x320/0x49c include/trace/events/icmp.h:11 __icmp_send+0xcfc/0x11d8 net/ipv4/icmp.c:1013 ipv4_send_dest_unreach net/ipv4/route.c:1280 [inline] ipv4_link_failure+0x57c/0x8dc net/ipv4/route.c:1287 dst_link_failure include/net/dst.h:438 [inline] vti_tunnel_xmit+0xe40/0x17a4 net/ipv4/ip_vti.c:307 TP_fast_assign() unconditionally calls udp_hdr(skb) before checking whether the packet is UDP. Furthermore, __icmp_send() can be invoked from paths (e.g., link failures, ARP errors, forwarding, AF_PACKET) where skb->transport_header was never initialized (~0U). Under CONFIG_DEBUG_NET=y, calling skb_transport_header(skb) triggers DEBUG_NET_WARN_ON_ONCE(!skb_transport_header_was_set(skb)). Fix this by: 1. Only parsing transport info when iph->protocol == IPPROTO_UDP. 2. Using skb_header_pointer() at skb_network_offset(skb) + (iph->ihl << 2) to safely fetch the UDP header without assuming transport_header is set. Fixes: db3efdcf70c7 ("net/ipv4: add tracepoint for icmp_send") Reported-by: syzbot+6d2762674103618994b0@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a8d5538.91706f20.ef82.0009.GAE@google.com/T/#u Signed-off-by: Eric Dumazet Cc: Peilin He Cc: xu xin Cc: Steven Rostedt Reviewed-by: Jiayuan Chen Reviewed-by: David Ahern Link: https://patch.msgid.link/20260825084551.1562967-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/trace/events/icmp.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/include/trace/events/icmp.h b/include/trace/events/icmp.h index 09ae115099df..6937b778ae54 100644 --- a/include/trace/events/icmp.h +++ b/include/trace/events/icmp.h @@ -27,17 +27,20 @@ TRACE_EVENT(icmp_send, TP_fast_assign( struct iphdr *iph = ip_hdr(skb); - struct udphdr *uh = udp_hdr(skb); - int proto_4 = iph->protocol; + struct udphdr _uh, *uh = NULL; __be32 *p32; __entry->skbaddr = skb; __entry->type = type; __entry->code = code; - if (proto_4 != IPPROTO_UDP || (u8 *)uh < skb->head || - (u8 *)uh + sizeof(struct udphdr) - > skb_tail_pointer(skb)) { + if (iph->protocol == IPPROTO_UDP) + uh = skb_header_pointer(skb, + skb_network_offset(skb) + + (iph->ihl << 2), + sizeof(_uh), &_uh); + + if (!uh) { __entry->sport = 0; __entry->dport = 0; __entry->ulen = 0; From 2a004bfb62bdb847f25a8001e104bf33922a2cf4 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Thu, 27 Aug 2026 00:01:11 +0200 Subject: [PATCH 0162/2366] netlink: specs: fix the conntrack filter type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CTA_FILTER doesn't contain nested tuple attributes, instead it contains bit masks that specify which tuple attributes to filter on. The values for filtering are taken from the top-level CTA_TUPLE_ORIG and CTA_TUPLE_REPLY, which are also missing in the attribute list for the dump request. The bits themselves somehow are not in the public headers, so not defining them in the spec either for now. Once they are public in uAPI, they can be added here with enum-as-flags. Fixes: 23fc9311a526 ("netlink: specs: add conntrack dump and stats dump support") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Asbjørn Sloth Tønnesen Link: https://patch.msgid.link/20260826220444.4054714-2-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/conntrack.yaml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Documentation/netlink/specs/conntrack.yaml b/Documentation/netlink/specs/conntrack.yaml index db7cddcda50a..6ba28cb1c2ab 100644 --- a/Documentation/netlink/specs/conntrack.yaml +++ b/Documentation/netlink/specs/conntrack.yaml @@ -360,6 +360,17 @@ attribute-sets: name: tsoff type: u32 byte-order: big-endian + - + name: filter-attrs + attributes: + - + name: orig-flags + type: u32 + doc: bitmask of tuple fields to filter on, original direction + - + name: reply-flags + type: u32 + doc: bitmask of tuple fields to filter on, reply direction - name: conntrack-attrs attributes: @@ -466,7 +477,7 @@ attribute-sets: - name: filter type: nest - nested-attributes: tuple-attrs + nested-attributes: filter-attrs - name: status-mask type: u32 @@ -591,6 +602,8 @@ operations: request: value: 0x101 attributes: + - tuple-orig + - tuple-reply - mark - filter - status From 8b348496cbec0d5ca24a99f668096e2e16e8fbeb Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Thu, 27 Aug 2026 00:01:12 +0200 Subject: [PATCH 0163/2366] netlink: specs: add missing mask attributes for conntrack dump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'mark-mask' and 'status-mask' are defined and supported by the conntrack dump, but missing from the list of arguments. While at it, the order of the arguments should follow the order of their definition in the enum ctattr_type. That appears to be a common convention for other spec files. Fixes: 23fc9311a526 ("netlink: specs: add conntrack dump and stats dump support") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Asbjørn Sloth Tønnesen Link: https://patch.msgid.link/20260826220444.4054714-3-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/conntrack.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/netlink/specs/conntrack.yaml b/Documentation/netlink/specs/conntrack.yaml index 6ba28cb1c2ab..b1eb102ab843 100644 --- a/Documentation/netlink/specs/conntrack.yaml +++ b/Documentation/netlink/specs/conntrack.yaml @@ -604,10 +604,12 @@ operations: attributes: - tuple-orig - tuple-reply - - mark - - filter - status + - mark - zone + - mark-mask + - filter + - status-mask reply: value: 0x100 attributes: From 18666c73afe95eeca8707c699b63f96ce3acda42 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 27 Aug 2026 09:59:36 +0000 Subject: [PATCH 0164/2366] tcp: use GFP_ATOMIC in tcp_send_active_reset() tcp_send_active_reset() can be called from contexts where gfp_any() (in tcp_disconnect()) or sk->sk_allocation (in __tcp_close() and mptcp_do_fastclose()) evaluates to GFP_KERNEL, which includes __GFP_FS and __GFP_DIRECT_RECLAIM. Allocating with GFP_KERNEL while holding the socket lock (sk_lock) creates a lockdep dependency: sk_lock -> fs_reclaim This causes false-positive lockdep circular locking warnings with storage subsystems (such as nvme-tcp) that acquire socket locks in block I/O paths and invoke tcp_disconnect() or close sockets upon teardown: set->srcu -> sk_lock -> fs_reclaim -> elevator_lock -> set->srcu Active resets are small RST packet headers that should never enter direct reclaim or block while holding socket locks. Use sk_gfp_mask(sk, GFP_ATOMIC | __GFP_NOWARN) inside tcp_send_active_reset() and remove its priority argument. This preserves __GFP_MEMALLOC access for SOCK_MEMALLOC sockets, suppresses allocation failure warnings, and aligns with other control packet allocations (e.g. tcp_send_fin(), __tcp_send_ack(), tcp_xmit_probe_skb()). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet Acked-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260827095936.551524-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/tcp.h | 3 +-- net/ipv4/tcp.c | 14 ++++++-------- net/ipv4/tcp_output.c | 4 ++-- net/ipv4/tcp_timer.c | 6 +++--- net/mptcp/protocol.c | 3 +-- net/mptcp/protocol.h | 2 +- 6 files changed, 14 insertions(+), 18 deletions(-) diff --git a/include/net/tcp.h b/include/net/tcp.h index 670c20876f26..436495ff2271 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -765,8 +765,7 @@ int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue, void tcp_send_probe0(struct sock *); int tcp_write_wakeup(struct sock *, int mib); void tcp_send_fin(struct sock *sk); -void tcp_send_active_reset(struct sock *sk, gfp_t priority, - enum sk_rst_reason reason); +void tcp_send_active_reset(struct sock *sk, enum sk_rst_reason reason); int tcp_send_synack(struct sock *); void tcp_push_one(struct sock *, unsigned int mss_now); void __tcp_send_ack(struct sock *sk, u32 rcv_nxt, u16 flags); diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index b4237d0e994d..93d723d8c109 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -3182,8 +3182,7 @@ void __tcp_close(struct sock *sk, long timeout) /* Unread data was tossed, zap the connection. */ NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE); tcp_set_state(sk, TCP_CLOSE); - tcp_send_active_reset(sk, sk->sk_allocation, - SK_RST_REASON_TCP_ABORT_ON_CLOSE); + tcp_send_active_reset(sk, SK_RST_REASON_TCP_ABORT_ON_CLOSE); } else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) { /* Check zero linger _after_ checking for unread data. */ sk->sk_prot->disconnect(sk, 0); @@ -3257,7 +3256,7 @@ void __tcp_close(struct sock *sk, long timeout) struct tcp_sock *tp = tcp_sk(sk); if (READ_ONCE(tp->linger2) < 0) { tcp_set_state(sk, TCP_CLOSE); - tcp_send_active_reset(sk, GFP_ATOMIC, + tcp_send_active_reset(sk, SK_RST_REASON_TCP_ABORT_ON_LINGER); __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONLINGER); @@ -3276,7 +3275,7 @@ void __tcp_close(struct sock *sk, long timeout) if (sk->sk_state != TCP_CLOSE) { if (tcp_check_oom(sk, 0)) { tcp_set_state(sk, TCP_CLOSE); - tcp_send_active_reset(sk, GFP_ATOMIC, + tcp_send_active_reset(sk, SK_RST_REASON_TCP_ABORT_ON_MEMORY); __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONMEMORY); @@ -3377,14 +3376,14 @@ int tcp_disconnect(struct sock *sk, int flags) } else if (unlikely(tp->repair)) { WRITE_ONCE(sk->sk_err, ECONNABORTED); } else if (tcp_need_reset(old_state)) { - tcp_send_active_reset(sk, gfp_any(), SK_RST_REASON_TCP_STATE); + tcp_send_active_reset(sk, SK_RST_REASON_TCP_STATE); WRITE_ONCE(sk->sk_err, ECONNRESET); } else if (tp->snd_nxt != tp->write_seq && (1 << old_state) & (TCPF_CLOSING | TCPF_LAST_ACK)) { /* The last check adjusts for discrepancy of Linux wrt. RFC * states */ - tcp_send_active_reset(sk, gfp_any(), + tcp_send_active_reset(sk, SK_RST_REASON_TCP_DISCONNECT_WITH_DATA); WRITE_ONCE(sk->sk_err, ECONNRESET); } else if (old_state == TCP_SYN_SENT) @@ -5147,8 +5146,7 @@ int tcp_abort(struct sock *sk, int err) bh_lock_sock(sk); if (tcp_need_reset(sk->sk_state)) - tcp_send_active_reset(sk, GFP_ATOMIC, - SK_RST_REASON_TCP_STATE); + tcp_send_active_reset(sk, SK_RST_REASON_TCP_STATE); tcp_done_with_error(sk, err); bh_unlock_sock(sk); diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index 6f4dca4a4de9..c5ffffee4349 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -3849,9 +3849,9 @@ void tcp_send_fin(struct sock *sk) * was unread data in the receive queue. This behavior is recommended * by RFC 2525, section 2.17. -DaveM */ -void tcp_send_active_reset(struct sock *sk, gfp_t priority, - enum sk_rst_reason reason) +void tcp_send_active_reset(struct sock *sk, enum sk_rst_reason reason) { + gfp_t priority = sk_gfp_mask(sk, GFP_ATOMIC | __GFP_NOWARN); struct sk_buff *skb; TCP_INC_STATS(sock_net(sk), TCP_MIB_OUTRSTS); diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c index 1038e7ba9c2e..e56eae4bc341 100644 --- a/net/ipv4/tcp_timer.c +++ b/net/ipv4/tcp_timer.c @@ -126,7 +126,7 @@ static int tcp_out_of_resources(struct sock *sk, bool do_reset) (!tp->snd_wnd && !tp->packets_out)) do_reset = true; if (do_reset) - tcp_send_active_reset(sk, GFP_ATOMIC, + tcp_send_active_reset(sk, SK_RST_REASON_TCP_ABORT_ON_MEMORY); tcp_done(sk); __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONMEMORY); @@ -809,7 +809,7 @@ static void tcp_keepalive_timer(struct timer_list *t) goto out; } } - tcp_send_active_reset(sk, GFP_ATOMIC, SK_RST_REASON_TCP_STATE); + tcp_send_active_reset(sk, SK_RST_REASON_TCP_STATE); goto death; } @@ -836,7 +836,7 @@ static void tcp_keepalive_timer(struct timer_list *t) icsk->icsk_probes_out > 0) || (user_timeout == 0 && icsk->icsk_probes_out >= keepalive_probes(tp))) { - tcp_send_active_reset(sk, GFP_ATOMIC, + tcp_send_active_reset(sk, SK_RST_REASON_TCP_KEEPALIVE_TIMEOUT); tcp_write_err(sk); goto out; diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index b474d03620a7..e1f08f71cdb1 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -3109,8 +3109,7 @@ static void mptcp_do_fastclose(struct sock *sk) */ inet_csk(ssk)->icsk_ack.rcv_mss = TCP_MIN_MSS; - tcp_send_active_reset(ssk, ssk->sk_allocation, - SK_RST_REASON_TCP_ABORT_ON_CLOSE); + tcp_send_active_reset(ssk, SK_RST_REASON_TCP_ABORT_ON_CLOSE); unlock: release_sock(ssk); } diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 06a107d4e839..87ccb84e9927 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -690,7 +690,7 @@ mptcp_send_active_reset_reason(struct sock *sk) enum sk_rst_reason reason; reason = sk_rst_convert_mptcp_reason(subflow->reset_reason); - tcp_send_active_reset(sk, GFP_ATOMIC, reason); + tcp_send_active_reset(sk, reason); } /* Made the fwd mem carried by the given skb available to the msk, From a5d946466a95621fa2769720d59ea336003aa1a5 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 26 Aug 2026 15:03:15 +0200 Subject: [PATCH 0165/2366] net: stmmac: fix dma mapping leak in stmmac_tso_xmit() In stmmac_tso_xmit(), if the DMA mapping of an skb fragment fails, the frame is dropped but the DMA mappings already created for the linear part and for the fragments mapped before the failure are never unmapped, leaking DMA mappings. Fix the leak by walking back over the descriptors used by the frame and releasing each of them with stmmac_free_tx_buffer(). Moreover, release the descriptors with stmmac_release_tx_desc() unmapping the DMA buffers. Fixes: f748be531d70 ("stmmac: support new GMAC4") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260826-stmmac_dma_unmap_tso-v1-1-a2753d1576ba@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 53 ++++++++++++------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index f2fc89176654..d576059c04df 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4319,6 +4319,7 @@ static bool stmmac_vlan_insert(struct stmmac_priv *priv, struct sk_buff *skb, /** * stmmac_tso_allocator - close entry point of the driver * @priv: driver private structure + * @entry: TX queue buffer index * @des: buffer start address * @total_len: total length to fill in descriptors * @last_segment: condition for the last descriptor @@ -4327,8 +4328,9 @@ static bool stmmac_vlan_insert(struct stmmac_priv *priv, struct sk_buff *skb, * This function fills descriptor and request new descriptors according to * buffer length to fill */ -static void stmmac_tso_allocator(struct stmmac_priv *priv, dma_addr_t des, - int total_len, bool last_segment, u32 queue) +static void stmmac_tso_allocator(struct stmmac_priv *priv, u32 *entry, + dma_addr_t des, int total_len, + bool last_segment, u32 queue) { struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue]; struct dma_desc *desc; @@ -4340,14 +4342,13 @@ static void stmmac_tso_allocator(struct stmmac_priv *priv, dma_addr_t des, while (tmp_len > 0) { dma_addr_t curr_addr; - tx_q->cur_tx = STMMAC_NEXT_ENTRY(tx_q->cur_tx, - priv->dma_conf.dma_tx_size); - WARN_ON(tx_q->tx_skbuff[tx_q->cur_tx]); + *entry = STMMAC_NEXT_ENTRY(*entry, priv->dma_conf.dma_tx_size); + WARN_ON(tx_q->tx_skbuff[*entry]); if (tx_q->tbs & STMMAC_TBS_AVAIL) - desc = &tx_q->dma_entx[tx_q->cur_tx].basic; + desc = &tx_q->dma_entx[*entry].basic; else - desc = &tx_q->dma_tx[tx_q->cur_tx]; + desc = &tx_q->dma_tx[*entry]; curr_addr = des + (total_len - tmp_len); stmmac_set_desc_addr(priv, desc, curr_addr); @@ -4486,7 +4487,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) { struct dma_desc *desc, *first, *mss_desc = NULL; struct stmmac_priv *priv = netdev_priv(dev); - unsigned int first_entry, tx_packets; + unsigned int first_entry, entry, tx_packets; struct stmmac_txq_stats *txq_stats; struct stmmac_tx_queue *tx_q; bool set_ic, is_last_segment; @@ -4549,22 +4550,24 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) } first_entry = tx_q->cur_tx; - WARN_ON(tx_q->tx_skbuff[first_entry]); + entry = first_entry; + + WARN_ON(tx_q->tx_skbuff[entry]); if (tx_q->tbs & STMMAC_TBS_AVAIL) - desc = &tx_q->dma_entx[first_entry].basic; + desc = &tx_q->dma_entx[entry].basic; else - desc = &tx_q->dma_tx[first_entry]; + desc = &tx_q->dma_tx[entry]; first = desc; /* first descriptor: fill Headers on Buf1 */ des = dma_map_single(priv->device, skb->data, skb_headlen(skb), DMA_TO_DEVICE); if (dma_mapping_error(priv->device, des)) - goto dma_map_err; + goto error; stmmac_set_desc_addr(priv, first, des); - stmmac_tso_allocator(priv, des + proto_hdr_len, pay_len, + stmmac_tso_allocator(priv, &entry, des + proto_hdr_len, pay_len, (nfrags == 0), queue); /* In case two or more DMA transmit descriptors are allocated for this @@ -4579,8 +4582,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) * this DMA buffer right after the DMA engine completely finishes the * full buffer transmission. */ - stmmac_set_tx_skb_dma_entry(tx_q, tx_q->cur_tx, des, skb_headlen(skb), - false); + stmmac_set_tx_skb_dma_entry(tx_q, entry, des, skb_headlen(skb), false); /* Prepare fragments */ for (i = 0; i < nfrags; i++) { @@ -4590,14 +4592,15 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) skb_frag_size(frag), DMA_TO_DEVICE); if (dma_mapping_error(priv->device, des)) - goto dma_map_err; + goto error_dma_unmap; - stmmac_tso_allocator(priv, des, skb_frag_size(frag), + stmmac_tso_allocator(priv, &entry, des, skb_frag_size(frag), (i == nfrags - 1), queue); - stmmac_set_tx_skb_dma_entry(tx_q, tx_q->cur_tx, des, + stmmac_set_tx_skb_dma_entry(tx_q, entry, des, skb_frag_size(frag), true); } + tx_q->cur_tx = entry; stmmac_set_tx_dma_last_segment(tx_q, tx_q->cur_tx); @@ -4702,7 +4705,19 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) return NETDEV_TX_OK; -dma_map_err: +error_dma_unmap: + for (;;) { + desc = stmmac_get_tx_desc(priv, tx_q, first_entry); + stmmac_release_tx_desc(priv, desc, priv->descriptor_mode); + stmmac_free_tx_buffer(priv, &priv->dma_conf, queue, + first_entry); + if (first_entry == entry) + break; + + first_entry = STMMAC_NEXT_ENTRY(first_entry, + priv->dma_conf.dma_tx_size); + } +error: dev_err(priv->device, "Tx dma map failed\n"); dev_kfree_skb(skb); priv->xstats.tx_dropped++; From e01620844c5c88b6fcf819d171df8e3976a0e76f Mon Sep 17 00:00:00 2001 From: Jerome Tollet Date: Mon, 24 Aug 2026 16:16:44 +0200 Subject: [PATCH 0166/2366] net/mlx5e: Prevent stale XSK buffer release on refill retry When an XDP redirect to an AF_XDP socket fails because its RX ring is full, the XSK core frees the buffer. During the subsequent batched refill of a legacy cyclic RQ, mlx5e also releases the WQE's XSK buffer before allocating a replacement. If that refill succeeds only partially, a WQE left without a replacement retains its old buffer pointer. The buffer can meanwhile be allocated to another WQE. A later refill retry can then free the live buffer through the stale pointer and publish the same UMEM frame twice. Mark the WQE as released immediately after the driver-side free. The flag is already cleared when a replacement buffer is assigned, so refill retries no longer release stale pointers. The failure is silent and produces no kernel warning or splat. A standalone legacy cyclic-RQ zero-copy libxsk reproducer, using 64-byte UDP traffic offered at 12 Mpps, detected it: stock stopped after 2,854,914 packets in 4.094 seconds, with 4,542 xdp_rx_ring_full events and 64 ownership/double-publication errors. With this change it processed 356,904,225 packets in 30 seconds despite 571,405 xdp_rx_ring_full events, with no ownership or data errors. Fixes: 3f93f82988bc ("net/mlx5e: RX, Defer page release in legacy rq for better recycling") Cc: stable@vger.kernel.org Suggested-by: Daniel Borkmann Reviewed-by: Dragos Tatulea Signed-off-by: Jerome Tollet Link: https://patch.msgid.link/20260824141645.23700-2-jtollet@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_rx.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c index 206cf9db3466..7bd0606a5253 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c @@ -410,8 +410,11 @@ static inline void mlx5e_free_rx_wqe(struct mlx5e_rq *rq, static void mlx5e_xsk_free_rx_wqe(struct mlx5e_wqe_frag_info *wi) { - if (!(wi->flags & BIT(MLX5E_WQE_FRAG_SKIP_RELEASE))) - xsk_buff_free(*wi->xskp); + if (wi->flags & BIT(MLX5E_WQE_FRAG_SKIP_RELEASE)) + return; + + xsk_buff_free(*wi->xskp); + wi->flags |= BIT(MLX5E_WQE_FRAG_SKIP_RELEASE); } static void mlx5e_dealloc_rx_wqe(struct mlx5e_rq *rq, u16 ix) From 63811edf512584c946e5e96b100e9e280703bbb5 Mon Sep 17 00:00:00 2001 From: Jerome Tollet Date: Mon, 24 Aug 2026 16:16:45 +0200 Subject: [PATCH 0167/2366] net/mlx5e: Prevent stale XSK buffer release on MPWQE refill retry With AF_XDP on a striding RQ, mlx5e defers releasing XSK buffers until an MPWQE is refilled. If XSK allocation then returns -ENOMEM, actual_wq_head is not advanced and a later NAPI poll retries the same WQE. mlx5e_free_rx_mpwqe() leaves each released slot marked as releasable. On retry it can therefore call xsk_buff_free() again through stale pointers after the frames have returned to the XSK pool and been reallocated. Set all skip_release_bitmap bits in the common error path of mlx5e_xsk_alloc_rx_mpwqe(). This matches mlx5e_alloc_rx_mpwqe(). A successful allocation already clears the bitmap after replacing every buffer, so retries become idempotent without changing the success path. Fault injection forced three consecutive failures for one selected MPWQE. Both an early allocation failure and a partial 8-of-16-buffer unwind released the original 16 XSK buffers only once. Each error left a full bitmap, the following NAPI retry skipped the release, and a later successful allocation cleared it. A 20-second AF_XDP zero-copy pressure run exercised 1,575,262 buffer allocation failures without invalid descriptors, WQE errors, or kernel warnings. Fixes: 4c2a13236807 ("net/mlx5e: RX, Defer page release in striding rq for better recycling") Cc: stable@vger.kernel.org Signed-off-by: Jerome Tollet Reviewed-by: Dragos Tatulea Link: https://patch.msgid.link/20260824141645.23700-3-jtollet@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/xsk/rx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/rx.c index 4f984f6a2cb9..55ec6387ab28 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xsk/rx.c @@ -3,6 +3,7 @@ #include "rx.h" #include "en/xdp.h" +#include #include #include @@ -156,6 +157,7 @@ int mlx5e_xsk_alloc_rx_mpwqe(struct mlx5e_rq *rq, u16 ix) xsk_buff_free(xsk_buffs[batch]); err: + bitmap_fill(wi->skip_release_bitmap, rq->mpwqe.pages_per_wqe); rq->stats->buff_alloc_err++; return -ENOMEM; } From 28a57fb2c5df4deb42a06e52fd36c14b37aa0034 Mon Sep 17 00:00:00 2001 From: Dong Chenchen Date: Tue, 25 Aug 2026 20:39:09 +0800 Subject: [PATCH 0168/2366] net: iptunnel: fix stale transport header during tunnel decapsulation Syzbot reported a crash in qdisc_pkt_len_segs_init() caused by a stale transport_header offset after tunnel decapsulation. BUG: unable to handle page fault for address: ffffed102091a42e Oops: Oops: 0000 [#1] SMP KASAN NOPTI CPU: 0 UID: 0 PID: 340 Comm: qdisc_uaf_repro Not tainted 7.2.0-rc4-00061-g248951ddc14d #256 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 RIP: 0010:__asan_load2 qdisc_pkt_len_segs_init (net/core/dev.c:4145) __dev_queue_xmit (net/core/dev.c:4787) br_dev_queue_push_xmit (net/bridge/br_forward.c:53) br_handle_frame_finish (net/bridge/br_input.c:229) br_handle_frame (net/bridge/br_input.c:315) __netif_receive_skb_core.constprop.0 (net/core/dev.c:6099) __netif_receive_skb_list_core (net/core/dev.c:6287) netif_receive_skb_list_internal (net/core/dev.c:6445) napi_complete_done (net/core/dev.c:6813) gro_cell_poll (net/core/gro_cells.c:74) __napi_poll (net/core/dev.c:7735) net_rx_action (net/core/dev.c:7798 net/core/dev.c:7955) handle_softirqs (kernel/softirq.c:622) do_softirq (kernel/softirq.c:523 kernel/softirq.c:510 ) __local_bh_enable_ip (kernel/softirq.c:450) tun_get_user (drivers/net/tun.c:1986 (discriminator 1)) tun_chr_write_iter (drivers/net/tun.c:2032) The issue is completely latent until qdisc read transport header in commit 7fb4c1967011 ("net: pull headers in qdisc_pkt_len_segs_init()"). The crash requires four conditions to line up: 1. The incoming packet is encapsulated and carries GSO metadata. The outer transport header offset is stored in skb->transport_header while the packet is still in the outer tunnel context. 2. The tunnel receiver strips the outer headers. skb->data is advanced to the inner frame, but skb->transport_header is left pointing to the now-removed outer L4 header, so it becomes a negative offset relative to the new data. 3. The inner frame is not delivered to the local IP stack. Instead, it is forwarded at L2 by a bridge or HSR, so ip_rcv_core() never runs and the transport header is not reset to the inner L4 offset. 4. The forwarding path calls __dev_queue_xmit(), which enters qdisc_pkt_len_segs_init(). That function computes the GSO header length from skb_transport_offset(skb). Because the offset is negative, the unsigned cast overflows and pskb_may_pull(skb, hdr_len + sizeof(struct tcphdr)) reads past the end of the skb, triggering a KASAN fault or page fault. The issue specifically requires GSO packets (shinfo->gso_size != 0), which are processed/aggregated through gro_cells. Fix this by clearing transport_header to the ~0U sentinel in gro_cell for all tunnnel driver. GTP does not support GRO/GSO, drop the evil GSO packets in GTP directly. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: syzbot+83181a31faf9455499c5@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/69de2bee.a00a0220.475f0.0041.GAE@google.com/T/ Suggested-by: Eric Dumazet Signed-off-by: Dong Chenchen Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260825123909.1463121-1-dongchenchen2@huawei.com Signed-off-by: Jakub Kicinski --- drivers/net/gtp.c | 5 +++++ include/linux/skbuff.h | 5 +++++ net/core/gro_cells.c | 2 ++ 3 files changed, 12 insertions(+) diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 298efc76a56b..69fe5717846b 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -318,6 +318,11 @@ static int gtp_inner_proto(struct sk_buff *skb, unsigned int hdrlen, static int gtp_rx(struct pdp_ctx *pctx, struct sk_buff *skb, unsigned int hdrlen, unsigned int role, __u16 inner_proto) { + if (skb_is_gso(skb)) { + netdev_dbg(pctx->dev, "GSO is not supported in GTP\n"); + goto err; + } + if (!gtp_check_ms(skb, pctx, hdrlen, role, inner_proto)) { netdev_dbg(pctx->dev, "No PDP ctx for this MS\n"); return 1; diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 671c13494566..421f6fc45451 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -3082,6 +3082,11 @@ static inline bool skb_transport_header_was_set(const struct sk_buff *skb) return skb->transport_header != (typeof(skb->transport_header))~0U; } +static inline void skb_unset_transport_header(struct sk_buff *skb) +{ + skb->transport_header = (typeof(skb->transport_header))~0U; +} + static inline unsigned char *skb_transport_header(const struct sk_buff *skb) { DEBUG_NET_WARN_ON_ONCE(!skb_transport_header_was_set(skb)); diff --git a/net/core/gro_cells.c b/net/core/gro_cells.c index 1b84385c04bd..d8c0a2867120 100644 --- a/net/core/gro_cells.c +++ b/net/core/gro_cells.c @@ -22,6 +22,8 @@ int gro_cells_receive(struct gro_cells *gcells, struct sk_buff *skb) if (unlikely(!(dev->flags & IFF_UP))) goto drop; + skb_unset_transport_header(skb); + if (!gcells->cells || skb_cloned(skb) || netif_elide_gro(dev)) { res = netif_rx(skb); goto unlock; From 13eb543cebef6d6c3ec42e31afe3856f51b7126b Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 24 Aug 2026 12:39:00 -0300 Subject: [PATCH 0169/2366] net/sched: act_api: budget all shared attributes in notify skbs tcf_action_shared_attrs_size() is supposed to return an upper bound on the netlink attributes every action dump emits outside of TCA_ACT_OPTIONS, so that tcf_add_notify_msg(), tcf_del_notify_msg() and friends can allocate an skb large enough for the reply. It has fallen behind the dump path and is now an underestimate for every single action. Attributes, such as, TCA_ACT_IN_HW_COUNT and TCA_STATS_BASIC_HW are emitted unconditionally and never accounted for. TCA_STATS_PKT64, TCA_ACT_USED_HW_STATS, TCA_STATS_RATE_EST, TCA_STATS_RATE_EST64 require specific conditions, but are also not accounted for. Fix the issue by budgeting all of them so that we have a legitimate upper bound. Even tough for of them require specific conditions, they are cheap so, to avoid overcomplicating, we opted to account for them unconditionally as well to account for a real worst case scenario. Fixes: 4e76e75d6aba ("net sched actions: calculate add/delete event message size") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260810164357.1653956-1-victor%40mojatatu.com Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260824153903.4143642-2-victor@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index b4415d358c91..766162b0b810 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -443,12 +443,21 @@ static size_t tcf_action_shared_attrs_size(const struct tc_action *act) + nla_total_size(IFNAMSIZ) /* TCA_ACT_KIND */ + cookie_len /* TCA_ACT_COOKIE */ + nla_total_size(sizeof(struct nla_bitfield32)) /* TCA_ACT_HW_STATS */ + /* TCA_ACT_USED_HW_STATS */ + + nla_total_size(sizeof(struct nla_bitfield32)) + + nla_total_size(sizeof(u32)) /* TCA_ACT_IN_HW_COUNT */ + nla_total_size(0) /* TCA_ACT_STATS nested */ + nla_total_size(sizeof(struct nla_bitfield32)) /* TCA_ACT_FLAGS */ /* TCA_STATS_BASIC */ + nla_total_size_64bit(sizeof(struct gnet_stats_basic)) - /* TCA_STATS_PKT64 */ - + nla_total_size_64bit(sizeof(u64)) + /* TCA_STATS_BASIC_HW */ + + nla_total_size_64bit(sizeof(struct gnet_stats_basic)) + /* TCA_STATS_PKT64, emitted by both of the basic copies above */ + + 2 * nla_total_size_64bit(sizeof(u64)) + /* TCA_STATS_RATE_EST */ + + nla_total_size_64bit(sizeof(struct gnet_stats_rate_est)) + /* TCA_STATS_RATE_EST64 */ + + nla_total_size_64bit(sizeof(struct gnet_stats_rate_est64)) /* TCA_STATS_QUEUE */ + nla_total_size_64bit(sizeof(struct gnet_stats_queue)) + nla_total_size(0) /* TCA_ACT_OPTIONS nested */ From e9ca46ebc3262b498626c4095826b8fa034bbf21 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 24 Aug 2026 12:39:01 -0300 Subject: [PATCH 0170/2366] net/sched: act_api: size the RTM_GETACTION reply from the actions tca_action_gd() already walks every requested action and accumulates attr_size += tcf_action_fill_size(act), then wraps the result in tcf_action_full_attrs_size(). For RTM_DELACTION that value is handed to tcf_del_notify_msg(), which allocates max(attr_size, NLMSG_GOODSIZE). For RTM_GETACTION it is silently discarded and tcf_get_notify() allocates a fixed NLMSG_GOODSIZE skb instead. Any action whose dump exceeds that fixed budget therefore cannot be read back. For example, act_pedit overruns the budget with 32 actions of four munge keys each, act_police with 32 policers once the optional rate/peakrate/result/avrate attributes are present Fix this by passing attr_size through and allocate the reply the way the add and delete paths do. Note on exposure: RTM_GETACTION is the only one of the three action commands that is not capability checked - tc_ctl_action() requires CAP_NET_ADMIN for RTM_NEWACTION and RTM_DELACTION only - so this turns a fixed NLMSG_GOODSIZE reply into a user sized allocation on an unprivileged path. It is bounded by TCA_ACT_MAX_PRIO actions per request, and tca_action_gd() does not reject duplicate indices, so a single large action can be requested 32 times; an act_bpf program near BPF_MAXINSNS is about 32KB of dump, or roughly 1MB for one request. Creating such an action still requires CAP_NET_ADMIN, and the add and delete paths have sized their skbs this way since the Fixes commit. Should this ever need bounding, GFP_KERNEL_ACCOUNT would charge the reply to the caller's memcg. Fixes: 4e76e75d6aba ("net sched actions: calculate add/delete event message size") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260810164357.1653956-1-victor%40mojatatu.com Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260824153903.4143642-3-victor@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 766162b0b810..20b6501fd33b 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -1697,12 +1697,12 @@ static int tca_get_fill(struct sk_buff *skb, struct tc_action *actions[], static int tcf_get_notify(struct net *net, u32 portid, struct nlmsghdr *n, - struct tc_action *actions[], int event, + struct tc_action *actions[], size_t attr_size, int event, struct netlink_ext_ack *extack) { struct sk_buff *skb; - skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL); + skb = alloc_skb(max(attr_size, NLMSG_GOODSIZE), GFP_KERNEL); if (!skb) return -ENOBUFS; if (tca_get_fill(skb, actions, portid, n->nlmsg_seq, 0, event, @@ -2053,7 +2053,8 @@ tca_action_gd(struct net *net, struct nlattr *nla, struct nlmsghdr *n, attr_size = tcf_action_full_attrs_size(attr_size); if (event == RTM_GETACTION) - ret = tcf_get_notify(net, portid, n, actions, event, extack); + ret = tcf_get_notify(net, portid, n, actions, attr_size, event, + extack); else { /* delete */ ret = tcf_del_notify(net, n, actions, portid, attr_size, extack); if (ret) From 251367a0a3319fa565daf7468b0afd933b1f5ab1 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 24 Aug 2026 12:39:02 -0300 Subject: [PATCH 0171/2366] net/sched: act_api: fix skb sizing and action leak on reoffload delete tcf_reoffload_del_notify_msg() sizes the RTM_DELACTION skb with tcf_action_fill_size(action) alone. Unlike every other notification path it never wraps that in tcf_action_full_attrs_size(), so the nlmsg_put() header, struct tcamsg and the TCA_ACT_TAB nest that tca_get_fill() emits - 24 bytes on x86_64 - are not budgeted. As long as the single action stays well under NLMSG_GOODSIZE the floor in alloc_skb() hides this, but once its fill size crosses NLMSG_GOODSIZE the allocation is exactly 24 bytes short and tca_get_fill() runs out of tailroom. That is now easy to reach for an offloadable act_pedit with a large tcfp_nkeys, which commit 8e2efb3f45a5 ("net/sched: add get_fill_size callbacks for actions missing them") started accounting for properly. When that happens tcf_reoffload_del_notify() returns early, before tcf_idr_release_unsafe(), and tcf_action_reoffload_cb() discards the return value: if (tc_act_skip_sw(p->tcfa_flags) && !tc_act_in_hw(p)) tcf_reoffload_del_notify(net, p); The action has just lost its last hardware instance and is skip_sw, so it is left installed while processing no packets, and with no notification to tell userspace about it. An -ENOBUFS from alloc_skb() gets the same treatment. Fix this by budgeting the message header the way the add and delete paths do, and release the action even when the notification cannot be built - dropping the notification is strictly better than leaking a dead action, and there is no caller left to report the error to. Fixes: 13926d19a11e ("flow_offload: add reoffload process to update hw_count") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260810164357.1653956-1-victor%40mojatatu.com Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Reviewed-by: Pedro Tammela Link: https://patch.msgid.link/20260824153903.4143642-4-victor@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 20b6501fd33b..37eced84dfa5 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -1867,11 +1867,13 @@ static int tcf_action_delete(struct net *net, struct tc_action *actions[]) static struct sk_buff *tcf_reoffload_del_notify_msg(struct net *net, struct tc_action *action) { - size_t attr_size = tcf_action_fill_size(action); struct tc_action *actions[TCA_ACT_MAX_PRIO] = { [0] = action, }; struct sk_buff *skb; + size_t attr_size; + + attr_size = tcf_action_full_attrs_size(tcf_action_fill_size(action)); skb = alloc_skb(max(attr_size, NLMSG_GOODSIZE), GFP_KERNEL); if (!skb) @@ -1888,15 +1890,18 @@ static struct sk_buff *tcf_reoffload_del_notify_msg(struct net *net, static int tcf_reoffload_del_notify(struct net *net, struct tc_action *action) { const struct tc_action_ops *ops = action->ops; - struct sk_buff *skb; + struct sk_buff *skb = NULL; int ret; - if (!rtnl_notify_needed(net, 0, RTNLGRP_TC)) { - skb = NULL; - } else { + if (rtnl_notify_needed(net, 0, RTNLGRP_TC)) { skb = tcf_reoffload_del_notify_msg(net, action); + /* The action has already lost its hardware instance and is + * skip_sw, so it must be released whether or not the + * notification can be built. Drop the notification rather + * than leave an action behind that processes no packets. + */ if (IS_ERR(skb)) - return PTR_ERR(skb); + skb = NULL; } ret = tcf_idr_release_unsafe(action); From 5271b79b7ad68dcb222e893773f92bdabf7750f3 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft Security FORGE Labs)" Date: Thu, 27 Aug 2026 19:55:10 -0400 Subject: [PATCH 0172/2366] tcp: fix use-after-free in do_tcp_getsockopt(TCP_CONGESTION) do_tcp_getsockopt() reads icsk->icsk_ca_ops->name without holding rcu_read_lock(). Since commit 0baf26b0fcd7 ("bpf: tcp: Support tcp_congestion_ops in bpf"), icsk_ca_ops can point to dynamically allocated BPF struct_ops memory that may be freed concurrently via setsockopt(TCP_CONGESTION), leading to a use-after-free. BUG: KASAN: slab-use-after-free in _copy_to_user+0x37/0x60 Read of size 16 at addr ffff888013505260 by task exploit/149 _copy_to_user+0x37/0x60 do_tcp_getsockopt+0x158a/0x2460 (net/ipv4/tcp.c:4585) tcp_getsockopt+0x91/0xf0 __sys_getsockopt+0xf7/0x170 Fix this by holding rcu_read_lock() around the ca_ops->name access, using READ_ONCE() to load icsk_ca_ops, and copying the name to a stack buffer before releasing the lock. Also annotate the relevant icsk_ca_ops stores with WRITE_ONCE() to fix the accompanying KCSAN data-race issue. Fixes: 0baf26b0fcd7 ("bpf: tcp: Support tcp_congestion_ops in bpf") Suggested-by: Eric Dumazet Reported-by: Xiang Mei (Microsoft) Link: https://lore.kernel.org/all/20260821182449.79785-2-blbllhy@gmail.com/ Cc: AutonomousCodeSecurity@microsoft.com Cc: stable@vger.kernel.org Reviewed-by: Eric Dumazet Signed-off-by: Cen Zhang (Microsoft Security FORGE Labs) Reviewed-by: Jiayuan Chen Reviewed-by: Matthieu Baerts (NGI0) Reviewed-by: Breno Leitao Link: https://patch.msgid.link/d3f97f1acbf0010898148be6e6406e4b8b4a5c84.1787870710.git.blbllhy@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/tcp.c | 14 +++++++++++--- net/ipv4/tcp_cong.c | 4 ++-- net/ipv4/tcp_dctcp.c | 2 +- net/ipv4/tcp_minisocks.c | 2 +- net/ipv4/tcp_output.c | 2 +- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 93d723d8c109..740999c9efff 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -4576,16 +4576,24 @@ int do_tcp_getsockopt(struct sock *sk, int level, val = !inet_csk_in_pingpong_mode(sk); break; - case TCP_CONGESTION: + case TCP_CONGESTION: { + char ca_name[TCP_CA_NAME_MAX] = {}; + if (copy_from_sockptr(&len, optlen, sizeof(int))) return -EFAULT; len = min_t(unsigned int, len, TCP_CA_NAME_MAX); if (copy_to_sockptr(optlen, &len, sizeof(int))) return -EFAULT; - if (copy_to_sockptr(optval, icsk->icsk_ca_ops->name, len)) + + rcu_read_lock(); + memcpy(ca_name, READ_ONCE(icsk->icsk_ca_ops)->name, + sizeof(ca_name)); + rcu_read_unlock(); + + if (copy_to_sockptr(optval, ca_name, len)) return -EFAULT; return 0; - + } case TCP_ULP: if (copy_from_sockptr(&len, optlen, sizeof(int))) return -EFAULT; diff --git a/net/ipv4/tcp_cong.c b/net/ipv4/tcp_cong.c index e9f6c77e0631..8e83ef81fc18 100644 --- a/net/ipv4/tcp_cong.c +++ b/net/ipv4/tcp_cong.c @@ -223,7 +223,7 @@ void tcp_assign_congestion_control(struct sock *sk) ca = rcu_dereference(net->ipv4.tcp_congestion_control); if (unlikely(!bpf_try_module_get(ca, ca->owner))) ca = &tcp_reno; - icsk->icsk_ca_ops = ca; + WRITE_ONCE(icsk->icsk_ca_ops, ca); rcu_read_unlock(); memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv)); @@ -253,7 +253,7 @@ static void tcp_reinit_congestion_control(struct sock *sk, struct inet_connection_sock *icsk = inet_csk(sk); tcp_cleanup_congestion_control(sk); - icsk->icsk_ca_ops = ca; + WRITE_ONCE(icsk->icsk_ca_ops, ca); icsk->icsk_ca_setsockopt = 1; memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv)); diff --git a/net/ipv4/tcp_dctcp.c b/net/ipv4/tcp_dctcp.c index 274e628e7cf8..99f68c2992d0 100644 --- a/net/ipv4/tcp_dctcp.c +++ b/net/ipv4/tcp_dctcp.c @@ -111,7 +111,7 @@ __bpf_kfunc static void dctcp_init(struct sock *sk) /* No ECN support? Fall back to Reno. Also need to clear * ECT from sk since it is set during 3WHS for DCTCP. */ - inet_csk(sk)->icsk_ca_ops = &dctcp_reno; + WRITE_ONCE(inet_csk(sk)->icsk_ca_ops, &dctcp_reno); INET_ECN_dontxmit(sk); } diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c index f3fa0b18eda0..0ddfd5af6e58 100644 --- a/net/ipv4/tcp_minisocks.c +++ b/net/ipv4/tcp_minisocks.c @@ -507,7 +507,7 @@ void tcp_ca_openreq_child(struct sock *sk, const struct dst_entry *dst) ca = tcp_ca_find_key(ca_key); if (likely(ca && bpf_try_module_get(ca, ca->owner))) { icsk->icsk_ca_dst_locked = tcp_ca_dst_locked(dst); - icsk->icsk_ca_ops = ca; + WRITE_ONCE(icsk->icsk_ca_ops, ca); ca_got_dst = true; } rcu_read_unlock(); diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index c5ffffee4349..d960e3de7d50 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -4092,7 +4092,7 @@ static void tcp_ca_dst_init(struct sock *sk, const struct dst_entry *dst) if (likely(ca && bpf_try_module_get(ca, ca->owner))) { bpf_module_put(icsk->icsk_ca_ops, icsk->icsk_ca_ops->owner); icsk->icsk_ca_dst_locked = tcp_ca_dst_locked(dst); - icsk->icsk_ca_ops = ca; + WRITE_ONCE(icsk->icsk_ca_ops, ca); } rcu_read_unlock(); } From 385e474086c2e7e29e2dded690be40dc273e20ee Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft Security FORGE Labs)" Date: Thu, 27 Aug 2026 19:55:11 -0400 Subject: [PATCH 0173/2366] tcp: fix use-after-free in do_tcp_getsockopt(TCP_CC_INFO) do_tcp_getsockopt() reads icsk->icsk_ca_ops and dereferences the get_info function pointer without rcu_read_lock(). With BPF struct_ops congestion control, ca_ops can point to dynamically allocated memory that is freed concurrently, resulting in a use-after-free when the kernel dereferences or calls through the stale pointer. BUG: KASAN: slab-use-after-free in do_tcp_getsockopt+0x2037/0x23e0 Read of size 8 at addr ffff888013701258 by task exploit/149 do_tcp_getsockopt+0x2037/0x23e0 (net/ipv4/tcp.c:4564) tcp_getsockopt+0x91/0xf0 __sys_getsockopt+0xf7/0x170 Fix this by wrapping the ca_ops load and get_info call within rcu_read_lock()/rcu_read_unlock(), and using READ_ONCE() to load the icsk_ca_ops pointer. Fixes: 0baf26b0fcd7 ("bpf: tcp: Support tcp_congestion_ops in bpf") Suggested-by: Eric Dumazet Cc: AutonomousCodeSecurity@microsoft.com Cc: stable@vger.kernel.org Reviewed-by: Eric Dumazet Signed-off-by: Cen Zhang (Microsoft Security FORGE Labs) Reviewed-by: Jiayuan Chen Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/65fd3816ed5d541d9edd4bf4fcf97104a2cf907a.1787870710.git.blbllhy@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/tcp.c | 4 +++- net/ipv4/tcp_dctcp.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 740999c9efff..1c867a302444 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -4561,9 +4561,11 @@ int do_tcp_getsockopt(struct sock *sk, int level, if (copy_from_sockptr(&len, optlen, sizeof(int))) return -EFAULT; - ca_ops = icsk->icsk_ca_ops; + rcu_read_lock(); + ca_ops = READ_ONCE(icsk->icsk_ca_ops); if (ca_ops && ca_ops->get_info) sz = ca_ops->get_info(sk, ~0U, &attr, &info); + rcu_read_unlock(); len = min_t(unsigned int, len, sz); if (copy_to_sockptr(optlen, &len, sizeof(int))) diff --git a/net/ipv4/tcp_dctcp.c b/net/ipv4/tcp_dctcp.c index 99f68c2992d0..5b457f68a581 100644 --- a/net/ipv4/tcp_dctcp.c +++ b/net/ipv4/tcp_dctcp.c @@ -228,7 +228,7 @@ static size_t dctcp_get_info(struct sock *sk, u32 ext, int *attr, if (ext & (1 << (INET_DIAG_DCTCPINFO - 1)) || ext & (1 << (INET_DIAG_VEGASINFO - 1))) { memset(&info->dctcp, 0, sizeof(info->dctcp)); - if (inet_csk(sk)->icsk_ca_ops != &dctcp_reno) { + if (READ_ONCE(inet_csk(sk)->icsk_ca_ops) != &dctcp_reno) { info->dctcp.dctcp_enabled = 1; info->dctcp.dctcp_ce_state = (u16) ca->ce_state; info->dctcp.dctcp_alpha = ca->dctcp_alpha; From 2188569e7e1b0bc3f3b557dc97ab7a02befc11c8 Mon Sep 17 00:00:00 2001 From: Xin Long Date: Wed, 26 Aug 2026 15:49:04 -0400 Subject: [PATCH 0174/2366] sctp: fix a TOCTOU race in SCTP_CMD_TIMER_START The SCTP_CMD_TIMER_START handler checks timer_pending() before calling timer_reduce(). The timer can expire and detach between these operations, causing timer_reduce() to rearm the timer without taking the association reference required for the newly armed timer. The timer callback later unconditionally drops its association reference, which can leave the association reference count unbalanced and result in use-after-free during association teardown. Use the return value of timer_reduce() to determine whether the timer was actually armed. Take the association reference only when timer_reduce() successfully starts a new timer, closing the race between checking the timer state and rearming it. This issue was reported by Nico Yip (@_cyeaa_) working with TrendAI Zero Day Initiative. Fixes: 20a785aa52c8 ("sctp: Don't add the shutdown timer if its already been added") Reported-by: Zero Day Initiative Signed-off-by: Xin Long Link: https://patch.msgid.link/9d8f1b5c50329d5ea7c642128d35681abaa9ed20.1787773744.git.lucien.xin@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/sm_sideeffect.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c index 94716406d602..0d99b7e8c082 100644 --- a/net/sctp/sm_sideeffect.c +++ b/net/sctp/sm_sideeffect.c @@ -1545,17 +1545,8 @@ static int sctp_cmd_interpreter(enum sctp_event_type event_type, timeout = asoc->timeouts[cmd->obj.to]; BUG_ON(!timeout); - /* - * SCTP has a hard time with timer starts. Because we process - * timer starts as side effects, it can be hard to tell if we - * have already started a timer or not, which leads to BUG - * halts when we call add_timer. So here, instead of just starting - * a timer, if the timer is already started, and just mod - * the timer with the shorter of the two expiration times - */ - if (!timer_pending(timer)) + if (!timer_reduce(timer, jiffies + timeout)) sctp_association_hold(asoc); - timer_reduce(timer, jiffies + timeout); break; case SCTP_CMD_TIMER_RESTART: From 98f0a1422e285f6132a73932ebd4fe5c6f513261 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Wed, 19 Aug 2026 19:42:41 +0800 Subject: [PATCH 0175/2366] scsi: fnic: Initialize the NVMe local port info before registering nvfnic_add_lport() declares struct nvme_fc_port_info on the stack and fills in four of its five members, leaving dev_loss_tmo holding whatever the stack happened to contain before the call. The structure is then handed to nvme_fc_register_localport(). nvfnic_add_tport(), which registers the remote port a few lines further down, memsets its own struct nvme_fc_port_info first, so only the local port path passes uninitialized data across the transport interface. The NVMe/FC transport documents dev_loss_tmo as "Used only on a remoteport" and does not read it in nvme_fc_register_localport(), so there is no behavioural change today. Initialize the structure anyway: the driver must not depend on which members the transport happens to consume, and any member added to struct nvme_fc_port_info later would silently start out as stack garbage. Signed-off-by: Linmao Li Tested-by: Karan Tilak Kumar Reviewed-by: Karan Tilak Kumar Link: https://patch.msgid.link/20260819114242.3598034-2-lilinmao@kylinos.cn Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/fnic/fnic_nvme.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/fnic/fnic_nvme.c b/drivers/scsi/fnic/fnic_nvme.c index b237948dcafd..00d9d5d439a3 100644 --- a/drivers/scsi/fnic/fnic_nvme.c +++ b/drivers/scsi/fnic/fnic_nvme.c @@ -2216,7 +2216,7 @@ int nvfnic_add_tport(struct fnic *fnic, struct fnic_tport_s *tport, int nvfnic_add_lport(struct fnic *fnic) { - struct nvme_fc_port_info pinfo; + struct nvme_fc_port_info pinfo = {}; struct fnic_iport_s *iport = &fnic->iport; int ret = 0; From 3f92a64545165bdbb36dee8fa35626b295463313 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Mon, 24 Aug 2026 19:36:18 +0800 Subject: [PATCH 0176/2366] scsi: pm8001: Use rollback index when freeing MSI-X vectors pm8001_request_msix() unwinds previously registered handlers with free_irq() when request_irq() fails. The rollback loop uses the failing index i for every iteration instead of the already registered vector index j. That passes the wrong IRQ/dev_id pair to free_irq() and leaves the earlier handlers installed. Use j for both pci_irq_vector() and the matching irq_vector entry in the rollback loop. Fixes: a76037ff3479 ("scsi: pm8001: switch to pci_irq_alloc_vectors") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Acked-by: Jack Wang Link: https://patch.msgid.link/20260824113618.2239100-1-runyu.xiao@seu.edu.cn Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/pm8001/pm8001_init.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/scsi/pm8001/pm8001_init.c b/drivers/scsi/pm8001/pm8001_init.c index e93ea76b565e..54b35893261a 100644 --- a/drivers/scsi/pm8001/pm8001_init.c +++ b/drivers/scsi/pm8001/pm8001_init.c @@ -1029,8 +1029,8 @@ static u32 pm8001_request_msix(struct pm8001_hba_info *pm8001_ha) &(pm8001_ha->irq_vector[i])); if (rc) { for (j = 0; j < i; j++) { - free_irq(pci_irq_vector(pm8001_ha->pdev, i), - &(pm8001_ha->irq_vector[i])); + free_irq(pci_irq_vector(pm8001_ha->pdev, j), + &pm8001_ha->irq_vector[j]); } pci_free_irq_vectors(pm8001_ha->pdev); break; From 9a0716348dafe9c6d3529991a50b96c6d18abb51 Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Thu, 27 Aug 2026 16:50:37 +0530 Subject: [PATCH 0177/2366] scsi: ibmvfc: Fix kernel-doc name for ibmvfc_scsi_relogin() Commit e0fca728a89f ("scsi: ibmvfc: delete NVMe/FC targets as well as SCSI") renamed ibmvfc_relogin() to ibmvfc_scsi_relogin() but left the kernel-doc comment referring to the old name, so a W=1 build warns: drivers/scsi/ibmvscsi/ibmvfc-core.c:1901: warning: expecting prototype for ibmvfc_relogin(). Prototype was for ibmvfc_scsi_relogin() instead Update the kernel-doc comment to use the current function name. Fixes: e0fca728a89f ("scsi: ibmvfc: delete NVMe/FC targets as well as SCSI") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608271026.iMLmrwz4-lkp@intel.com/ Signed-off-by: Muhammad Falak R Wani Reviewed-by: Dave Marquardt Acked-by: Tyrel Datwyler Link: https://patch.msgid.link/dd866cf2321381694af027fbd726bcbd63ac3751.1787828961.git.falakreyaz@gmail.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index b3bc3ce872d6..78c59af769b5 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -1894,7 +1894,7 @@ static void ibmvfc_log_error(struct ibmvfc_event *evt) } /** - * ibmvfc_relogin - Log back into the specified device + * ibmvfc_scsi_relogin - Log back into the specified device * @sdev: scsi device struct * **/ From 9a69cc5f192f356c1c7b4fa2821da4a8cf684829 Mon Sep 17 00:00:00 2001 From: Muhammad Falak R Wani Date: Thu, 27 Aug 2026 16:50:38 +0530 Subject: [PATCH 0178/2366] scsi: ibmvfc: Document protocol parameter of ibmvfc_alloc_target() Commit 249313b3f7b5 ("scsi: ibmvfc: allocate targets based on protocol") added a protocol parameter to ibmvfc_alloc_target() but did not describe it in the function's kernel-doc comment, so a W=1 build warns: drivers/scsi/ibmvscsi/ibmvfc-core.c:4996: warning: Function parameter or struct member 'protocol' not described in 'ibmvfc_alloc_target' Add the missing parameter description. Fixes: 249313b3f7b5 ("scsi: ibmvfc: allocate targets based on protocol") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608270829.lHI1FAdO-lkp@intel.com/ Signed-off-by: Muhammad Falak R Wani Reviewed-by: Dave Marquardt Acked-by: Tyrel Datwyler Link: https://patch.msgid.link/b073968ae020b6ae0240e91341a92f428587ebd9.1787828961.git.falakreyaz@gmail.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/ibmvscsi/ibmvfc-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c index 78c59af769b5..3534ac45e9b8 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc-core.c +++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c @@ -4987,6 +4987,7 @@ static void ibmvfc_tgt_query_target(struct ibmvfc_target *tgt) * ibmvfc_alloc_target - Allocate and initialize an ibmvfc target * @vhost: ibmvfc host struct * @target: Holds SCSI ID to allocate target forand the WWPN + * @protocol: protocol of the target to allocate * * Returns: * 0 on success / other on failure From a3756f53baf1830c65149cfcb81cb96360976cf3 Mon Sep 17 00:00:00 2001 From: Nitin Rawat Date: Tue, 25 Aug 2026 20:22:02 +0530 Subject: [PATCH 0179/2366] scsi: ufs: ufs-qcom: Restore HS/LS link startup mode for Qualcomm UFS controller v6.2+ The link startup mode (HS LSS - high-speed link startup, or LS LSS - low-speed link startup) is decided in the boot stage based on the bootconfig GPIO. This selection is carried forward through the secondary stage bootloaders and finally to HLOS via the spare configuration register (REG_UFS_DEBUG_SPARE_CFG). On Qualcomm UFS controller v6.2 and later, bit 31 in the spare configuration register indicates the high-speed link startup mode selection, as per the Hardware Programming Guide (HPG). The spare register value is read during host driver initialization but gets cleared after UFS reset. Preserve the spare register value during initialization and restore it during link startup to maintain the bootloader-configured link startup mode. Signed-off-by: Nitin Rawat Tested-by: Mukesh Ojha Link: https://patch.msgid.link/20260825145203.265579-2-nitin.rawat@oss.qualcomm.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/host/ufs-qcom.c | 15 ++++++++++++--- drivers/ufs/host/ufs-qcom.h | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c index 62396212a0a7..8893ea7e4d84 100644 --- a/drivers/ufs/host/ufs-qcom.c +++ b/drivers/ufs/host/ufs-qcom.c @@ -715,6 +715,7 @@ static void ufs_qcom_link_startup_post_change(struct ufs_hba *hba) static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, enum ufs_notify_change_status status) { + struct ufs_qcom_host *host = ufshcd_get_variant(hba); int err = 0; switch (status) { @@ -737,6 +738,14 @@ static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, */ err = ufshcd_disable_host_tx_lcc(hba); + /* + * Restore HS/LS link startup mode set by bootloader + * after UFS reset clears REG_UFS_DEBUG_SPARE_CFG. + */ + if (host->hw_ver.major > 0x6 || + (host->hw_ver.major == 0x6 && host->hw_ver.minor >= 0x2)) + ufshcd_writel(hba, host->boot_spare_cfg, + REG_UFS_DEBUG_SPARE_CFG); break; case POST_CHANGE: ufs_qcom_link_startup_post_change(hba); @@ -1325,7 +1334,7 @@ static void ufs_qcom_advertise_quirks(struct ufs_hba *hba) static void ufs_qcom_set_phy_gear(struct ufs_qcom_host *host) { struct ufs_host_params *host_params = &host->host_params; - u32 val, dev_major; + u32 dev_major; /* * Default to powering up the PHY to the max gear possible, which is @@ -1344,8 +1353,8 @@ static void ufs_qcom_set_phy_gear(struct ufs_qcom_host *host) */ host->phy_gear = UFS_HS_G2; } else if (host->hw_ver.major >= 0x5) { - val = ufshcd_readl(host->hba, REG_UFS_DEBUG_SPARE_CFG); - dev_major = FIELD_GET(UFS_DEV_VER_MAJOR_MASK, val); + host->boot_spare_cfg = ufshcd_readl(host->hba, REG_UFS_DEBUG_SPARE_CFG); + dev_major = FIELD_GET(UFS_DEV_VER_MAJOR_MASK, host->boot_spare_cfg); /* * Since the UFS device version is populated, let's remove the diff --git a/drivers/ufs/host/ufs-qcom.h b/drivers/ufs/host/ufs-qcom.h index e20b3ca50577..a5ad5ce44a19 100644 --- a/drivers/ufs/host/ufs-qcom.h +++ b/drivers/ufs/host/ufs-qcom.h @@ -361,6 +361,7 @@ struct ufs_qcom_host { bool esi_enabled; u32 saved_tx_eq_g1_setting; + u32 boot_spare_cfg; }; struct ufs_qcom_drvdata { From b2ededcb271b37510366cbf6853be193d681ba5c Mon Sep 17 00:00:00 2001 From: Nitin Rawat Date: Tue, 25 Aug 2026 20:22:03 +0530 Subject: [PATCH 0180/2366] scsi: ufs: ufs-qcom: Fix sequential read variance The current devfreq downdifferential threshold of 5% causes overly aggressive frequency downscaling, leading to performance degradation sometimes during sequential read workloads. Update the UFS devfreq downdifferential threshold to 65. This widens the hysteresis window and prevents overly aggressive downscaling, ensuring that frequency is maintained for loads above 5% and scaling down occurs only when utilization falls below this level, while scale-up still triggers above the 70% threshold. Reviewed-by: Konrad Dybcio Signed-off-by: Nitin Rawat Link: https://patch.msgid.link/20260825145203.265579-3-nitin.rawat@oss.qualcomm.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/host/ufs-qcom.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c index 8893ea7e4d84..b31c04b5461e 100644 --- a/drivers/ufs/host/ufs-qcom.c +++ b/drivers/ufs/host/ufs-qcom.c @@ -2291,7 +2291,7 @@ static void ufs_qcom_config_scaling_param(struct ufs_hba *hba, p->polling_ms = 60; p->timer = DEVFREQ_TIMER_DELAYED; d->upthreshold = 70; - d->downdifferential = 5; + d->downdifferential = 65; hba->clk_scaling.suspend_on_no_request = true; } From dba9e2181ca5e875f98b8b9b4535cdaab87dcb0d Mon Sep 17 00:00:00 2001 From: "Milan P. Gandhi" Date: Wed, 12 Aug 2026 16:03:43 +0530 Subject: [PATCH 0181/2366] scsi: mpi3mr: Fix NULL pointer dereference in mpi3mr_sas_port_add() sas_port_alloc_num() can return NULL on memory allocation failure. The return value is passed directly to sas_port_add() without a NULL check, which causes a NULL pointer dereference. Additionally, if sas_port_add() fails, the allocated port is not freed before jumping to out_fail, leaking the sas_port structure. Call sas_port_free() to properly release it. Fixes: e22bae30667a ("scsi: mpi3mr: Add expander devices to STL") Signed-off-by: Milan P. Gandhi Reviewed-by: Laurence Oberman Link: https://patch.msgid.link/20260812103344.174247-2-mgandhi@redhat.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/mpi3mr/mpi3mr_transport.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/scsi/mpi3mr/mpi3mr_transport.c b/drivers/scsi/mpi3mr/mpi3mr_transport.c index 240f67a8e2e3..ea2c04384a0e 100644 --- a/drivers/scsi/mpi3mr/mpi3mr_transport.c +++ b/drivers/scsi/mpi3mr/mpi3mr_transport.c @@ -1428,9 +1428,15 @@ static struct mpi3mr_sas_port *mpi3mr_sas_port_add(struct mpi3mr_ioc *mrioc, } port = sas_port_alloc_num(mr_sas_node->parent_dev); + if (!port) { + ioc_err(mrioc, "failure at %s:%d/%s()!\n", + __FILE__, __LINE__, __func__); + goto out_fail; + } if ((sas_port_add(port))) { ioc_err(mrioc, "failure at %s:%d/%s()!\n", __FILE__, __LINE__, __func__); + sas_port_free(port); goto out_fail; } From 419d129f970aaa6567dbac366b0c93784bf9ec97 Mon Sep 17 00:00:00 2001 From: "Milan P. Gandhi" Date: Wed, 12 Aug 2026 16:03:44 +0530 Subject: [PATCH 0182/2366] scsi: mpi3mr: Fix target device refcount leak in mpi3mr_sas_port_add() mpi3mr_get_tgtdev_by_addr() increments the target device kref when it returns a device. If a subsequent error triggers a goto out_fail after the tgtdev reference is acquired, the reference is never released because the out_fail path does not call mpi3mr_tgtdev_put(). This prevents the target device structure from ever being freed. Add a tgtdev put in the out_fail path, guarded by a NULL check since tgtdev is only acquired for SAS_END_DEVICE types and the same cleanup path is shared by earlier error cases where tgtdev is still NULL. Fixes: e22bae30667a ("scsi: mpi3mr: Add expander devices to STL") Signed-off-by: Milan P. Gandhi Reviewed-by: Laurence Oberman Link: https://patch.msgid.link/20260812103344.174247-3-mgandhi@redhat.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/mpi3mr/mpi3mr_transport.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/scsi/mpi3mr/mpi3mr_transport.c b/drivers/scsi/mpi3mr/mpi3mr_transport.c index ea2c04384a0e..232af978d737 100644 --- a/drivers/scsi/mpi3mr/mpi3mr_transport.c +++ b/drivers/scsi/mpi3mr/mpi3mr_transport.c @@ -1507,6 +1507,8 @@ static struct mpi3mr_sas_port *mpi3mr_sas_port_add(struct mpi3mr_ioc *mrioc, list_for_each_entry_safe(mr_sas_phy, next, &mr_sas_port->phy_list, port_siblings) list_del(&mr_sas_phy->port_siblings); + if (tgtdev) + mpi3mr_tgtdev_put(tgtdev); kfree(mr_sas_port); return NULL; } From 11300f8ddee301dca9914561f24bea4168de076d Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Mon, 6 Jul 2026 16:44:43 +0800 Subject: [PATCH 0183/2366] scsi: sg: Report request-table problems when any status is set SG_GET_REQUEST_TABLE reports per-request diagnostic state through sg_req_info::problem. The field is meant to indicate whether there is an error to report for a completed request. sg_fill_request_table() currently combines masked_status, host_status and driver_status with bitwise AND. This only reports a problem when all three status fields are non-zero at the same time. A normal target check condition, for example, has masked_status set while host_status and driver_status may both be zero, so the request is incorrectly reported as clean. Use the same condition as sg_new_read(), which sets SG_INFO_CHECK when any of the three status fields is non-zero. Signed-off-by: Xu Rao Reviewed-by: Bart Van Assche Cc: stable@vger.kernel.org Link: https://patch.msgid.link/26BF67F369E2123E+20260706084443.805598-1-raoxu@uniontech.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/sg.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c index 74cd4e8a61c2..5408f002e6c0 100644 --- a/drivers/scsi/sg.c +++ b/drivers/scsi/sg.c @@ -863,10 +863,9 @@ sg_fill_request_table(Sg_fd *sfp, sg_req_info_t *rinfo) if (val >= SG_MAX_QUEUE) break; rinfo[val].req_state = srp->done + 1; - rinfo[val].problem = - srp->header.masked_status & - srp->header.host_status & - srp->header.driver_status; + rinfo[val].problem = srp->header.masked_status || + srp->header.host_status || + srp->header.driver_status; if (srp->done) rinfo[val].duration = srp->header.duration; From ef675ea168453a9b3e635b8ac543f92938bdd03b Mon Sep 17 00:00:00 2001 From: sangram kumar yerra Date: Tue, 18 Aug 2026 16:58:29 +0530 Subject: [PATCH 0184/2366] scsi: ufs: ufs-pci: Add support for Intel UFS 4.0 HS-Gear5 Reliable HS-Gear5 operation on Intel UFS 4.0 controllers requires configuring PA_INITIAL_ADAPT before changing the power mode. Without this setting, the link fails to train reliably at Gear5. Add a pwr_change_notify() hook to configure the adaptation mode before the power mode transition. Enable this only for UFS 4.0 and later controllers by checking hba->ufs_version. Wire the hook into the existing Meteor Lake family variant operations table (ufs_intel_mtl_hba_vops) instead of introducing a separate table, since the Intel UFS 4.0 PCI variant (PCI ID 8086:D335) already uses this vops table and the hook is internally gated on UFS version >= 4.0. Use PA_INITIAL_ADAPT when the negotiated TX power mode is FAST_MODE or FASTAUTO_MODE. Otherwise, reset the adaptation mode to PA_NO_ADAPT, which is the default setting. Fixes: 096cd6b7adf2 ("scsi: ufs: ufs-pci: Add support for Intel Nova Lake") Signed-off-by: sangram kumar yerra Reviewed-by: Adrian Hunter Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260818112830.453402-2-sangram.k.y@intel.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/host/ufshcd-pci.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/ufs/host/ufshcd-pci.c b/drivers/ufs/host/ufshcd-pci.c index f2433879b0eb..93bfafc25018 100644 --- a/drivers/ufs/host/ufshcd-pci.c +++ b/drivers/ufs/host/ufshcd-pci.c @@ -181,6 +181,25 @@ static int ufs_intel_lkf_pwr_change_notify(struct ufs_hba *hba, return err; } +static int ufs_intel_nvl_pwr_change_notify(struct ufs_hba *hba, + enum ufs_notify_change_status stage, + struct ufs_pa_layer_attr *dev_req_params) +{ + int adapt_val; + + if (stage != PRE_CHANGE || hba->ufs_version < ufshci_version(4, 0)) + return 0; + + if (dev_req_params->pwr_tx == FAST_MODE || dev_req_params->pwr_tx == FASTAUTO_MODE) + adapt_val = PA_INITIAL_ADAPT; + else + adapt_val = PA_NO_ADAPT; + + ufshcd_dme_configure_adapt(hba, dev_req_params->gear_tx, adapt_val); + + return 0; +} + static int ufs_intel_lkf_apply_dev_quirks(struct ufs_hba *hba) { u32 granularity, peer_granularity; @@ -527,6 +546,7 @@ static struct ufs_hba_variant_ops ufs_intel_mtl_hba_vops = { .exit = ufs_intel_common_exit, .hce_enable_notify = ufs_intel_hce_enable_notify, .link_startup_notify = ufs_intel_link_startup_notify, + .pwr_change_notify = ufs_intel_nvl_pwr_change_notify, .resume = ufs_intel_resume, .device_reset = ufs_intel_device_reset, }; From c46cc9cee39bd6f395ab9ac98b1794705df13d7c Mon Sep 17 00:00:00 2001 From: sangram kumar yerra Date: Tue, 18 Aug 2026 16:58:30 +0530 Subject: [PATCH 0185/2366] scsi: ufs: ufs-pci: Add MCQ support for Intel UFS 4.0 controllers The Intel UFS 4.0 PCI variant (PCI ID 8086:D335) advertises MCQ support in its capability register. However, ufshcd_alloc_mcq() also requires an .op_runtime_config hook to locate the per-queue operation and runtime (OPR) register blocks, which was not provided by this variant operations table. As a result, MCQ initialization fails and ufshcd_add_scsi_host() prints "MCQ mode is disabled, err=%d\n" before falling back to legacy single-doorbell (SDB) mode. Add ufs_intel_mcq_config_resource() to initialize the MCQ configuration base and add ufs_intel_op_runtime_config() to set up the OPR register offsets and stride. Wire both hooks into the variant operations table so MCQ is enabled when supported by the hardware. Fixes: 096cd6b7adf2 ("scsi: ufs: ufs-pci: Add support for Intel Nova Lake") Signed-off-by: sangram kumar yerra Reviewed-by: Adrian Hunter Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260818112830.453402-3-sangram.k.y@intel.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/host/ufshcd-pci.c | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/drivers/ufs/host/ufshcd-pci.c b/drivers/ufs/host/ufshcd-pci.c index 93bfafc25018..21bb11c724be 100644 --- a/drivers/ufs/host/ufshcd-pci.c +++ b/drivers/ufs/host/ufshcd-pci.c @@ -460,6 +460,43 @@ static int ufs_intel_mtl_init(struct ufs_hba *hba) return ufs_intel_common_init(hba); } +static int ufs_intel_mcq_config_resource(struct ufs_hba *hba) +{ + hba->mcq_base = hba->mmio_base + ufshcd_mcq_queue_cfg_addr(hba); + + return 0; +} + +/* + * This Intel UFS4.0 controller maps MCQ doorbell and interrupt-status + * registers into the same PCI BAR as the legacy HCI space, at this + * fixed offset/stride. + */ +#define UFS_INTEL_SQDAO0 0x2800 +#define UFS_INTEL_SQISAO0 0x2814 +#define UFS_INTEL_CQDAO0 0x281C +#define UFS_INTEL_CQISAO0 0x2824 +#define UFS_INTEL_MCQ_STRIDE 0x30 + +static int ufs_intel_op_runtime_config(struct ufs_hba *hba) +{ + struct ufshcd_mcq_opr_info_t *opr; + int i; + + hba->mcq_opr[OPR_SQD].offset = UFS_INTEL_SQDAO0; + hba->mcq_opr[OPR_SQIS].offset = UFS_INTEL_SQISAO0; + hba->mcq_opr[OPR_CQD].offset = UFS_INTEL_CQDAO0; + hba->mcq_opr[OPR_CQIS].offset = UFS_INTEL_CQISAO0; + + for (i = 0; i < OPR_MAX; i++) { + opr = &hba->mcq_opr[i]; + opr->stride = UFS_INTEL_MCQ_STRIDE; + opr->base = hba->mmio_base + opr->offset; + } + + return 0; +} + static int ufs_qemu_get_hba_mac(struct ufs_hba *hba) { return MAX_SUPP_MAC; @@ -547,6 +584,8 @@ static struct ufs_hba_variant_ops ufs_intel_mtl_hba_vops = { .hce_enable_notify = ufs_intel_hce_enable_notify, .link_startup_notify = ufs_intel_link_startup_notify, .pwr_change_notify = ufs_intel_nvl_pwr_change_notify, + .mcq_config_resource = ufs_intel_mcq_config_resource, + .op_runtime_config = ufs_intel_op_runtime_config, .resume = ufs_intel_resume, .device_reset = ufs_intel_device_reset, }; From d5869dae5080e976d4b03cc33eb7ceb527f242bf Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Fri, 17 Jul 2026 16:38:28 +0200 Subject: [PATCH 0186/2366] scsi: target: iscsi: Fix hang for aborted WRITE_PENDING commands When a LUN_RESET aborts a WRITE command that is in the TRANSPORT_WRITE_PENDING state, the target core sets CMD_T_ABORTED and waits for the frontend to finish processing. If the initiator subsequently sends the remaining dataout PDUs, __iscsit_check_dataout_hdr() catches the payload, stops the dataout timer if the sequence is final and finally dumps the data. However, the iSCSI target doesn't trigger the completion process for these aborted commands. Because of this, the abort path hangs indefinitely in target_put_cmd_and_wait(), leading to a deadlocked target worker thread. Fix this by explicitly calling target_complete_cmd() when the final dataout PDU is received for an aborted WRITE command. target_complete_cmd() detects the CMD_T_ABORTED flag and cleanly routes the command into target_abort_work, allowing the abort completion to successfully unblock. Signed-off-by: Maurizio Lombardi Reviewed-by: Laurence Oberman Link: https://patch.msgid.link/20260717143828.76291-2-mlombard@redhat.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/target/iscsi/iscsi_target.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 62ada3a52210..124ff269b8e7 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -1533,8 +1533,10 @@ __iscsit_check_dataout_hdr(struct iscsit_conn *conn, void *buf, */ if (se_cmd->transport_state & CMD_T_ABORTED) { if (hdr->flags & ISCSI_FLAG_CMD_FINAL && - --cmd->outstanding_r2ts < 1) + --cmd->outstanding_r2ts < 1) { iscsit_stop_dataout_timer(cmd); + target_complete_cmd(se_cmd, SAM_STAT_TASK_ABORTED); + } return iscsit_dump_data_payload(conn, payload_length, 1); } From 28d75dd3eb60812b3a87cbdf0d52c42f51b28a78 Mon Sep 17 00:00:00 2001 From: Yonghong Song Date: Fri, 28 Aug 2026 10:05:34 -0700 Subject: [PATCH 0187/2366] selftests/bpf: Bound the offset accumulator in __tld_fetch_key() The LLVM commit c7f4a76da024 [1] "[InstCombine] fold ((x - 1) | (2^k - 1)) + 1 to (x + (2^k - 1)) & -(2^k)" caused test_task_local_data.bpf.o to fail verification: The sequence of 8193 jumps is too complex. processed 188770 insns (limit 1000000) max_states_per_insn 34 total_states 8238 peak_states 12330 mark_read 0 TLD_ROUND_UP(x, 8) expands to ((((x) - 1) | 7) + 1), exactly the pattern that [1] rewrites, so the accumulation in __tld_fetch_key() off += TLD_ROUND_UP(metadata[i].size, 8); is now compiled as (x + 7) & -8 instead of ((x - 1) | 7) + 1. Both are correct, but they leave the verifier in very different states. Note that 'off' is marked as precise. Without [1], "size - 1" wraps at zero (size is a __u16), so the verifier loses all bounds on the increment: 211: (69) r1 = *(u16 *)(r1 +62) ; R1=scalar(...,umax32=0xffff,var_off=(0x0; 0xffff)) 212: (04) w1 += -1 ; R1=scalar(smin=0,smax=umax=0xffffffff,smin32=-1,smax32=0xfffe,var_off=(0x0; 0xffffffff)) 213: (44) w1 |= 7 ; R1=scalar(smin=umin=umin32=7,smax=umax=0xffffffff,var_off=(0x7; 0xfffffff8)) 214: (0c) w6 += w1 ; R6=scalar(smin=umin=umin32=7,smax=umax=0xffffffff,var_off=(0x7; 0xfffffff8)) 215: (04) w6 += 1 ; R6=scalar(smin=0,smax=umax=umax32=0xfffffff8,var_off=(0x0; 0xfffffff8)) Note that 'w6' will be used in the next iteration. In the next iteration after insn 215, the R6 range will be the same as previous iteration. The iterator loop converges at depth 2. With [1] the increment stays precisely bounded at [0, 0x10006]: 211: (69) r9 = *(u16 *)(r1 +62) ; R9=scalar(...,umax32=0xffff,var_off=(0x0; 0xffff)) 212: (04) w9 += 7 ; R9=scalar(...,umax32=0x10006,var_off=(0x0; 0x1ffff)) 213: (54) w9 &= 131064 ; R9=scalar(...,umax32=0x10006,var_off=(0x0; 0x1fff8)) 214: (0c) w9 += w6 ; R9=scalar(...,umax32=0x10006,var_off=(0x0; 0x1fff8)) 215: (bf) r1 = r10 216: (07) r1 += -8 217: (85) call bpf_iter_num_next 218: (bc) w6 = w9 In the next iteration, we will have 211: (69) r9 = *(u16 *)(r1 +62) ; R9=scalar(...,umax32=0xffff,var_off=(0x0; 0xffff)) 212: (04) w9 += 7 ; R9=scalar(...,umax32=0x10006,var_off=(0x0; 0x1ffff)) 213: (54) w9 &= 131064 ; R9=scalar(...,umax32=0x10006,var_off=(0x0; 0x1fff8)) 214: (0c) w9 += w6 ; R9=scalar(...,umax32=0x2000c,var_off=(0x0; 0x3fff8)) ... so 'off' umax grows by 0x10006 on every iteration and the loop-head state never repeats: 218: (bc) w6 = w9 ; R6=scalar(...,umax32=0x10006,var_off=(0x0; 0x1fff8)) 218: (bc) w6 = w9 ; R6=scalar(...,umax32=0x2000c,var_off=(0x0; 0x3fff8)) 218: (bc) w6 = w9 ; R6=scalar(...,umax32=0x30012,var_off=(0x0; 0x3fff8)) ... 218: (bc) w6 = w9 ; R6=scalar(...,umax32=0xff95fd6,var_off=(0x0; 0xffffff8)) That last one is iterator depth 4090. Saturating umax would take ~65531 iterations; the verifier gives up long before that. Note the loop does not diverge from the start. widen_imprecise_scalars() blows 'off' up to an unbounded scalar while it is still imprecise, and that alone converges the first three passes through the loop at depth 4. Once mark_chain_precision() reaches the loop body, maybe_widen_reg() starts skipping the register, and no widening ever happens again. In the failing log widening fires exactly 6 times out of 4098 arrivals at the iter_next() checkpoint, all of them before the umax starts accumulating. With [1] and this fix, here is one full trip through the loop body, entered with 'off' (R6) already clamped by the previous iteration: 208: frame1: R6=scalar(...,umax32=4088,var_off=(0x0; 0xff8)) 208: (67) r7 <<= 6 ; R7=scalar(...,umax32=3968,var_off=(0x0; 0xfc0)) 209: (bf) r1 = r9 ; R1=mem(id=54,sz=4036,imm=4) 210: (0f) r1 += r7 211: (69) r1 = *(u16 *)(r1 +62) ; R1=scalar(...,umax32=0xffff,var_off=(0x0; 0xffff)) 212: (04) w1 += 7 ; R1=scalar(...,umax32=0x10006,var_off=(0x0; 0x1ffff)) 213: (54) w1 &= 131064 ; R1=scalar(...,umax32=0x10006,var_off=(0x0; 0x1fff8)) 214: (0c) w1 += w6 ; R1=scalar(...,umax32=0x10ffe,var_off=(0x0; 0x1fff8)) R6=scalar(...,umax32=4088,var_off=(0x0; 0xff8)) 215: (bc) w6 = w1 ; R6=scalar(...,umax32=0x10ffe,var_off=(0x0; 0x1fff8)) 216: (26) if w1 > 0xff8 goto pc+1 ; R6=scalar(...,umax32=4088,var_off=(0x0; 0xff8)) 217: (05) goto pc-27 This makes the loop body a fixpoint. 'off' (w6) enters at 208 as [0, 4088] with var_off=(0x0; 0xff8); the increment computed at 212/213 is [0, 0x10006], so 214/215 leave it at [0, 0x10ffe]; then 216 truncates it straight back to [0, 4088]/(0x0; 0xff8), and only then is the back edge at 217 taken. Convergence no longer depends on the widening window above. Verification converges at iterator depth 3. [1] https://github.com/llvm/llvm-project/pull/216436 Signed-off-by: Yonghong Song Link: https://lore.kernel.org/r/20260828170534.1011183-1-yonghong.song@linux.dev Signed-off-by: Alexei Starovoitov --- tools/testing/selftests/bpf/progs/task_local_data.bpf.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/task_local_data.bpf.h b/tools/testing/selftests/bpf/progs/task_local_data.bpf.h index 0df8a12fd61e..a31a399870be 100644 --- a/tools/testing/selftests/bpf/progs/task_local_data.bpf.h +++ b/tools/testing/selftests/bpf/progs/task_local_data.bpf.h @@ -61,6 +61,7 @@ #define TLD_ROUND_UP(x, y) ((((x) - 1) | TLD_ROUND_MASK(x, y)) + 1) #define TLD_MAX_DATA_CNT (__PAGE_SIZE / sizeof(struct tld_metadata) - 1) +#define TLD_DATA_SIZE (__PAGE_SIZE - sizeof(__u64)) #ifndef TLD_NAME_LEN #define TLD_NAME_LEN 62 @@ -189,6 +190,8 @@ static int __tld_fetch_key(struct tld_object *tld_obj, const char *name, int i_s return start + off; off += TLD_ROUND_UP(metadata[i].size, 8); + if (off > TLD_DATA_SIZE) + break; } return -cnt; From 41a52ba4a5fe25b2cca431fe76fb5f3d8ad35139 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Fri, 28 Aug 2026 12:58:43 +0800 Subject: [PATCH 0188/2366] ntfs: read WOF chunks outside the decompression lock WOF decompression uses four module-global workspaces, one per compression format, each with a static mutex. ntfs_read_wof_compressed_block() takes that mutex once and holds it across the whole chunk loop, so both block reads run inside it: mutex_lock(ws->lock); for each chunk { parse_wof_chunk_table(..., ws->input, ...); /* reads disk */ ntfs_read_wof_chunk(..., ws->input, ...); /* reads disk */ decompress into ws->output; } mutex_unlock(ws->lock); Readers of system-compressed files then serialise system-wide on the disk waits, not just on the decompressor scratch the lock exists for. One reader sleeping in submit_bio_wait() blocks all the rest. The waits dominate. Reading an 8 MiB xpress4k file (2048 chunks at a 48% compressed ratio, so 2048 acquisitions and 4096 block reads) and timing ws->lock against the part of it spent in ntfs_bdev_read(): backing store held of that in I/O held after virtio, host page cache 348 ms 321 ms (92%) 24.6 ms virtio, throttled 100 MB/s 978 ms 948 ms (96%) 36.6 ms The page-cache row is a lower bound, having no seek cost at all, and the share still grows with slower storage because only the wait scales while decompression stays near 26 ms. The reads are inside the lock only because they land in ws->input, a buffer shared through the workspace. Nothing else requires it: parse_wof_chunk_table() and ntfs_read_wof_chunk() already take the buffer as a parameter and both set *chunk_mem to a pointer inside it, so a caller-owned buffer works unchanged. Allocate that buffer per call, do both reads without the lock, and take the lock only around decompression, which is the step needing ws->output and ws->scratch. squashfs is arranged this way already: its squashfs_decompress() is handed a bio that has been read, and locks only for the CPU work. Block reads are unchanged in number, they just no longer run under the lock, and hold time stops tracking device speed. This also unnests two per-inode locks from the global one, runlist->lock taken by both reads and base_ni->mrec_lock taken for a resident stream. A resident chunk needs no I/O at all, yet used to queue behind a reader blocked in submit_bio_wait() and then take mrec_lock inside the global mutex. The buffer is 4608 bytes for xpress4k and at most 33280 for lzx32k. This path already does GFP_NOFS allocations per call in ntfs_attr_iget(), and in ntfs_attr_get_search_ctx() for a resident stream, so one more does not change how it behaves under memory pressure. The workspace keeps output and scratch, 4 KiB to 32 KiB and 6224 bytes (xpress) or 10240 (lzx), and its "already allocated" test moves from ws->input to ws->output. The lock is now taken per chunk rather than per call, which differs only for a folio spanning several chunks: a few more uncontended mutex operations in exchange for not holding it across the reads between them. Verified under QEMU against an uncompressed copy of the same data, on an 8 MiB file and a 100000 byte one, the latter covering the tail chunk that is not a full comp_unit. Signed-off-by: Zhan Xusheng Signed-off-by: Namjae Jeon --- fs/ntfs/wof.c | 127 +++++++++++++++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 48 deletions(-) diff --git a/fs/ntfs/wof.c b/fs/ntfs/wof.c index 8f84c2212eee..9847259e5b1a 100644 --- a/fs/ntfs/wof.c +++ b/fs/ntfs/wof.c @@ -39,8 +39,6 @@ struct ntfs_wof_workspace { struct mutex *lock; const struct ntfs_codec_ops *codec; u32 comp_unit; - void *input; - size_t input_size; void *output; void *scratch; }; @@ -97,30 +95,36 @@ static struct ntfs_wof_workspace *ntfs_wof_workspace(u8 block_size_bits) } } +/* + * Size of the buffer a chunk is read into. A chunk is read straight off the + * device, so the buffer has to hold @comp_unit bytes plus the leading partial + * sector. + */ +static size_t ntfs_wof_input_size(const struct ntfs_wof_workspace *ws) +{ + return round_up((size_t)ws->comp_unit + 511, 512); +} + static int ntfs_wof_workspace_prepare(struct ntfs_wof_workspace *ws) { - void *input, *output, *scratch; + void *output, *scratch; size_t scratch_size; - if (ws->input) + if (ws->output) return 0; - ws->input_size = round_up((size_t)ws->comp_unit + 511, 512); scratch_size = ws->codec->scratch_size(ws->comp_unit); if (!scratch_size) return -EINVAL; - input = kvmalloc(ws->input_size, GFP_NOFS); output = kvmalloc(ws->comp_unit, GFP_NOFS); scratch = kvzalloc(scratch_size, GFP_NOFS); - if (!input || !output || !scratch) { - kvfree(input); + if (!output || !scratch) { kvfree(output); kvfree(scratch); return -ENOMEM; } - ws->input = input; ws->output = output; ws->scratch = scratch; return 0; @@ -134,10 +138,8 @@ void ntfs_wof_free_workspaces(void) struct ntfs_wof_workspace *ws = ntfs_wof_workspaces[i]; mutex_lock(ws->lock); - kvfree(ws->input); kvfree(ws->output); kvfree(ws->scratch); - ws->input = NULL; ws->output = NULL; ws->scratch = NULL; mutex_unlock(ws->lock); @@ -602,6 +604,51 @@ static int ntfs_wof_try_direct(struct ntfs_wof_workspace *ws, chunk_end, src, src_len, dst_len); } +/* + * Decompress one chunk into @folio. Only this step needs the workspace, so it + * is the only step that takes the workspace lock. + */ +static int ntfs_wof_decompress_chunk(struct ntfs_wof_workspace *ws, + struct ntfs_volume *vol, + struct address_space *mapping, + struct folio *folio, loff_t folio_start, + loff_t folio_end, u64 chunk_file_offset, + char *chunk_mem, u32 chunk_size, + u32 decomp_size) +{ + loff_t chunk_end = chunk_file_offset + decomp_size; + loff_t copy_start, copy_end; + int err; + + mutex_lock(ws->lock); + err = ntfs_wof_workspace_prepare(ws); + if (err) + goto out_unlock; + + err = ntfs_wof_try_direct(ws, mapping, folio, chunk_file_offset, + chunk_end, chunk_mem, chunk_size, + decomp_size); + if (err != -EAGAIN) + goto out_unlock; + + err = ntfs_wof_decode(ws, chunk_mem, chunk_size, ws->output, + decomp_size); + if (err) { + ntfs_error(vol->sb, "Decompression failed: %d", err); + err = -EINVAL; + goto out_unlock; + } + + copy_start = max_t(loff_t, folio_start, chunk_file_offset); + copy_end = min_t(loff_t, folio_end, chunk_file_offset + decomp_size); + memcpy_to_folio(folio, copy_start - folio_start, + ws->output + copy_start - chunk_file_offset, + copy_end - copy_start); +out_unlock: + mutex_unlock(ws->lock); + return err; +} + int ntfs_read_wof_compressed_block(struct folio *folio) { struct address_space *mapping = folio->mapping; @@ -613,6 +660,8 @@ int ntfs_read_wof_compressed_block(struct folio *folio) loff_t folio_start = folio_pos(folio); loff_t folio_end = folio_next_pos(folio); char *chunk_mem; + void *input; + size_t input_size; u32 decomp_size; u64 chunk_count, chunk_idx, last_chunk, chunk_offset; int err = 0; @@ -652,10 +701,12 @@ int ntfs_read_wof_compressed_block(struct folio *folio) goto out_iput; } - mutex_lock(ws->lock); - err = ntfs_wof_workspace_prepare(ws); - if (err) - goto out_unlock_ws; + input_size = ntfs_wof_input_size(ws); + input = kvmalloc(input_size, GFP_NOFS); + if (!input) { + err = -ENOMEM; + goto out_iput; + } chunk_idx = div_u64(folio_start, ws->comp_unit); last_chunk = @@ -663,55 +714,35 @@ int ntfs_read_wof_compressed_block(struct folio *folio) chunk_count = DIV_ROUND_UP_ULL(i_size, ws->comp_unit); for (; chunk_idx <= last_chunk; chunk_idx++) { u32 chunk_size; - u64 chunk_file_offset; - loff_t chunk_end, copy_start, copy_end; decomp_size = chunk_idx + 1 == chunk_count ? i_size - chunk_idx * ws->comp_unit : ws->comp_unit; err = parse_wof_chunk_table(ni, wof_ni, chunk_idx, chunk_count, decomp_size, &chunk_offset, - &chunk_size, ws->input, - ws->input_size); + &chunk_size, input, input_size); if (err) - goto out_unlock_ws; + goto out_free_input; err = ntfs_read_wof_chunk(vol, wof_ni, chunk_offset, chunk_size, - ws->input, ws->input_size, - &chunk_mem); + input, input_size, &chunk_mem); if (err) - goto out_unlock_ws; + goto out_free_input; - chunk_file_offset = chunk_idx * ws->comp_unit; - chunk_end = chunk_file_offset + decomp_size; - err = ntfs_wof_try_direct(ws, mapping, folio, chunk_file_offset, - chunk_end, chunk_mem, chunk_size, - decomp_size); - if (!err) - continue; - if (err != -EAGAIN) - goto out_unlock_ws; - - err = ntfs_wof_decode(ws, chunk_mem, chunk_size, ws->output, - decomp_size); - if (err) { - ntfs_error(vol->sb, "Decompression failed: %d", err); - err = -EINVAL; - goto out_unlock_ws; - } - copy_start = max_t(loff_t, folio_start, chunk_file_offset); - copy_end = min_t(loff_t, folio_end, - chunk_file_offset + decomp_size); - memcpy_to_folio(folio, copy_start - folio_start, - ws->output + copy_start - chunk_file_offset, - copy_end - copy_start); + err = ntfs_wof_decompress_chunk(ws, vol, mapping, folio, + folio_start, folio_end, + chunk_idx * ws->comp_unit, + chunk_mem, chunk_size, + decomp_size); + if (err) + goto out_free_input; } if (folio_end > i_size) folio_zero_segment(folio, i_size - folio_start, folio_size(folio)); -out_unlock_ws: - mutex_unlock(ws->lock); +out_free_input: + kvfree(input); out_iput: iput(wof_inode); out: From 03c6ecc4b4b13a3901f207152451fdd2d82e40c4 Mon Sep 17 00:00:00 2001 From: Jacopo Labardi Date: Sun, 30 Aug 2026 02:15:36 +0200 Subject: [PATCH 0189/2366] ntfs: fix FITRIM range alignment ntfs_trim_fs() aligns the start of a free extent up to the device discard granularity, but derives the discard length by aligning the original extent length down. When the free extent start is not discard-aligned, adding that length to the aligned start can extend the discard past the free extent and into allocated clusters. For example, with 4 KiB clusters and 32 KiB discard granularity, the free extent [4 KiB, 36 KiB) becomes the discard range [32 KiB, 64 KiB), so 28 KiB beyond the free extent may be discarded. Align the absolute end of the free extent down and derive the length from the two aligned endpoints. Skip extents that contain no full discard unit. Reproduced with a 4 KiB-cluster NTFS filesystem on scsi_debug configured for 32 KiB discard granularity and read-zero-after-trim. Before this change, FITRIM zeroed seven allocated 4 KiB clusters following an unaligned 32 KiB hole. With this change, the same data remains intact across FITRIM and remount. Fixes: 11ccc9107dc4 ("ntfs: update runlist handling and cluster allocator") Cc: stable@vger.kernel.org Assisted-by: OpenAI Codex:GPT-5.6 Sol Max Signed-off-by: Jacopo Labardi Signed-off-by: Namjae Jeon --- fs/ntfs/bitmap.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/ntfs/bitmap.c b/fs/ntfs/bitmap.c index b1436b3151b9..1840b7d84c62 100644 --- a/fs/ntfs/bitmap.c +++ b/fs/ntfs/bitmap.c @@ -64,7 +64,7 @@ int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range) end = start_buf; while (end < end_buf) { - u64 aligned_start, aligned_count; + u64 aligned_start, aligned_end, aligned_count; u64 start = find_next_zero_bit(bitmap, end_buf - start_buf, end - start_buf) + start_buf; if (start >= end_buf) @@ -74,8 +74,10 @@ int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range) start - start_buf) + start_buf; aligned_start = ALIGN(ntfs_cluster_to_bytes(vol, start), dq); - aligned_count = - ALIGN_DOWN(ntfs_cluster_to_bytes(vol, end - start), dq); + aligned_end = ALIGN_DOWN(ntfs_cluster_to_bytes(vol, end), dq); + if (aligned_start >= aligned_end) + continue; + aligned_count = aligned_end - aligned_start; if (aligned_count >= range->minlen) { ret = blkdev_issue_discard(vol->sb->s_bdev, aligned_start >> 9, aligned_count >> 9, GFP_NOFS); From a155ac8f0c523bd53f412196dcbb104ad1f4595f Mon Sep 17 00:00:00 2001 From: Boqun Feng Date: Sat, 29 Aug 2026 14:34:12 -0700 Subject: [PATCH 0190/2366] interrupt: Disable interrupt before modifying hardirq_disable counter Currently a softirq may be pending longer then expected if the triggering interrupt happens in-between hardirq_disable_enter() and _local_interrupt_disable() in local_interrupt_disable(): local_interrupt_disable(): hardirq_disable_enter(); ... __irq_exit_rcu(): // false because hardirq_disable_count() is not 0 if (.. && !hardirq_disable_count() && ..) { invoke_softirq(); } _local_interrupt_disable(); , it'll defer the softirq to the next interrupt which can be forever. The order between hardirq_disable_enter() and _local_interrupt_disable() is to optimize re-disabling interrupts if they are already disabled, but as 1) local_interrupt_disable() is not widely used yet and 2) the proper way to achieve this optimization may need fixing up the counter at entry/exit time [1], so reverse the order for now to avoid the softirq pending issue. Because of this fix, the part of saving the current state is separated from irq disabling, and the logic of local_interrupt_disable() becomes: local_irq_save(flags); if (counter++ == 0) { this_cpu(local_interrupt_disable_state) = flags; } Therefore change the helper function _local_interrupt_disable() to _local_interrupt_save_state() which only saves the current irqflags (when interrupts get disabled the first time). Fixes: e901c1510e24 ("irq,spin_lock: Add counted interrupt disabling/enabling") Reported-by: Thomas Gleixner Signed-off-by: Boqun Feng Signed-off-by: Thomas Gleixner Reviewed-by: Bradley Morgan Link: https://patch.msgid.link/20260829213412.14303-1-boqun@kernel.org Link: https://lore.kernel.org/lkml/87v78wezid.ffs@fw13/ [1] Closes: https://lore.kernel.org/lkml/87jypbfu1t.ffs@fw13/ --- include/linux/interrupt_rc.h | 19 ++++++++----------- kernel/softirq.c | 17 ++++------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/include/linux/interrupt_rc.h b/include/linux/interrupt_rc.h index b9a7f05ecf42..e68e1bedba66 100644 --- a/include/linux/interrupt_rc.h +++ b/include/linux/interrupt_rc.h @@ -20,11 +20,8 @@ /* Per-CPU interrupt disabling state for local_interrupt_{disable,enable}(). */ DECLARE_PER_CPU(unsigned long, local_interrupt_disable_state); -static __always_inline void __local_interrupt_disable(void) +static __always_inline void __local_interrupt_save_state(unsigned long flags) { - unsigned long flags; - - local_irq_save(flags); raw_cpu_write(local_interrupt_disable_state, flags); } @@ -36,9 +33,9 @@ static __always_inline void __local_interrupt_enable(void) } #ifndef INSTANTIATE_EXPORTED_INTERRUPT_DISABLE -static __always_inline void _local_interrupt_disable(void) +static __always_inline void _local_interrupt_save_state(unsigned long flags) { - __local_interrupt_disable(); + __local_interrupt_save_state(flags); } static __always_inline void _local_interrupt_enable(void) @@ -46,27 +43,27 @@ static __always_inline void _local_interrupt_enable(void) __local_interrupt_enable(); } #else -extern void _local_interrupt_disable(void); +extern void _local_interrupt_save_state(unsigned long flags); extern void _local_interrupt_enable(void); #endif #else /* !MODULE */ -extern void _local_interrupt_disable(void); +extern void _local_interrupt_save_state(unsigned long flags); extern void _local_interrupt_enable(void); #endif /* !MODULE */ static inline void local_interrupt_disable(void) { int new_count; + unsigned long flags; WARN_ON_ONCE(in_nmi()); + local_irq_save(flags); new_count = hardirq_disable_enter(); - /* Interrupts can happen here, but it's OK, see __irq_exit_rcu(). */ - if ((new_count & HARDIRQ_DISABLE_MASK) == HARDIRQ_DISABLE_OFFSET) - _local_interrupt_disable(); + _local_interrupt_save_state(flags); } static inline void local_interrupt_enable(void) diff --git a/kernel/softirq.c b/kernel/softirq.c index 7980a4a232f9..5d02c36c40e3 100644 --- a/kernel/softirq.c +++ b/kernel/softirq.c @@ -91,11 +91,11 @@ EXPORT_PER_CPU_SYMBOL_GPL(hardirq_context); DEFINE_PER_CPU(unsigned long, local_interrupt_disable_state); -void _local_interrupt_disable(void) +void _local_interrupt_save_state(unsigned long flags) { - __local_interrupt_disable(); + __local_interrupt_save_state(flags); } -EXPORT_SYMBOL(_local_interrupt_disable); +EXPORT_SYMBOL(_local_interrupt_save_state); void _local_interrupt_enable(void) { @@ -749,16 +749,7 @@ static inline void __irq_exit_rcu(void) #endif account_hardirq_exit(current); preempt_count_sub(HARDIRQ_OFFSET); - /* - * Interrupts may happen between hardirq_disable_enter() and - * local_irq_save() in local_interrupt_disable(), if irq_exit() invokes - * softirq here, we may have a softirq handler calling - * local_interrupt_disable() but it won't disable the IRQ because - * hardirq disabling count is already 1, hence we need to prevent - * invoking softirq when a local_interrupt_disable() is ongoing. - */ - if (!in_interrupt() && !hardirq_disable_count() && - local_softirq_pending()) { + if (!in_interrupt() && local_softirq_pending()) { /* * If we left hrtimers unarmed, make sure to arm them now, * before enabling interrupts to run softirq. From 1519dc88c87f5346dae0464d7d6da1b6bf1f6e8e Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:29 +0800 Subject: [PATCH 0191/2366] smb/client: validate new EOF for insert range smb3_insert_range() does not check if the new file size (i_size + len) is valid. This allows FALLOC_FL_INSERT_RANGE to bypass RLIMIT_FSIZE, exceed s_maxbytes, or produce a size outside the loff_t range. Use check_add_overflow() to calculate the new EOF. Validate it with inode_newsize_ok() before modifying the file. Reproducer, using a file on a CIFS mount: bash -c ' FILE=/mnt/cifs/repro trap "" SIGXFSZ ulimit -f 3072 # RLIMIT_FSIZE = 3 MiB # A regular write is stopped at 3 MiB. dd if=/dev/zero of="$FILE" bs=1M count=4 status=none stat -c "size after write: %s" "$FILE" # Insert 2 MiB into a 2 MiB file. truncate -s 2M "$FILE" fallocate -i -o 0 -l 2M "$FILE" stat -c "size after insert: %s" "$FILE" ' Before this change, the regular write stops at the 3 MiB limit, but insert range grows the file to 4 MiB: dd: error writing '/mnt/cifs/repro': File too large size after write: 3145728 size after insert: 4194304 After this change, insert range also fails at the limit and leaves the 2 MiB file unchanged: dd: error writing '/mnt/cifs/repro': File too large size after write: 3145728 fallocate: fallocate failed: File too large size after insert: 2097152 Fixes: 7fe6fe95b936 ("cifs: add FALLOC_FL_INSERT_RANGE support") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index bea4876b58cb..1ee67b4f1c77 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3985,7 +3985,8 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, struct cifsFileInfo *cfile = file->private_data; struct inode *inode = file_inode(file); struct cifsInodeInfo *cifsi = CIFS_I(inode); - __u64 count, old_eof, new_eof; + u64 count; + loff_t old_eof, new_eof; xid = get_xid(); @@ -3995,8 +3996,15 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, goto out; } + if (check_add_overflow(old_eof, len, &new_eof)) { + rc = -EFBIG; + goto out; + } + rc = inode_newsize_ok(inode, new_eof); + if (rc) + goto out; + count = old_eof - off; - new_eof = old_eof + len; filemap_invalidate_lock(inode->i_mapping); rc = filemap_write_and_wait_range(inode->i_mapping, off, new_eof - 1); From 88972e35750792e717af287dc71f42a03b5cbce4 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:30 +0800 Subject: [PATCH 0192/2366] smb/client: validate new EOF for zero range When FALLOC_FL_ZERO_RANGE is used without FALLOC_FL_KEEP_SIZE, smb3_zero_range() may extend EOF without checking RLIMIT_FSIZE, allowing the file to grow beyond the caller's file-size limit. Fix this by calling inode_newsize_ok() before sending the zero-range request when the operation would extend EOF. Reproducer, using a file on a CIFS mount: bash -c ' FILE=/mnt/cifs/repro trap "" SIGXFSZ ulimit -f 3072 truncate -s 2M "$FILE" fallocate --zero-range -o 0 -l 4M "$FILE" echo "fallocate rc=$?" stat -c "file size=%s" "$FILE" ' Before this change, the operation succeeds despite the 3 MiB limit: fallocate rc=0 file size=4194304 After this change, fallocate fails and leaves the file at 2 MiB. Fixes: 72c419d9b073 ("cifs: fix smb3_zero_range so it can expand the file-size when required") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 1ee67b4f1c77..f41fc71f1ba7 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3444,6 +3444,13 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, trace_smb3_zero_enter(xid, cfile->fid.persistent_fid, tcon->tid, ses->Suid, offset, len); + new_size = offset + len; + if (!keep_size && i_size_read(inode) < new_size) { + rc = inode_newsize_ok(inode, new_size); + if (rc) + goto out; + } + filemap_invalidate_lock(inode->i_mapping); netfs_read_sizes(inode, &i_size, &remote_i_size, &zero_point); @@ -3474,7 +3481,6 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, /* * do we also need to change the size of the file? */ - new_size = offset + len; if (keep_size == false && (unsigned long long)i_size_read(inode) < new_size) { rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, new_size); @@ -3491,6 +3497,7 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, zero_range_exit: filemap_invalidate_unlock(inode->i_mapping); + out: free_xid(xid); if (rc) trace_smb3_zero_err(xid, cfile->fid.persistent_fid, tcon->tid, From cd03ce4950d80147ac8f20bc03c42b75b0352407 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:31 +0800 Subject: [PATCH 0193/2366] smb/client: mark file sparse before emulating insert range The SMB client emulates FALLOC_FL_INSERT_RANGE with SET_EOF, COPYCHUNK and SET_ZERO_DATA. SET_ZERO_DATA creates a hole only when the file is sparse. On a non-sparse file, it clears the inserted range but leaves its blocks allocated, causing the extent count check in xfstests generic/064 to fail. Fix this by marking the file sparse before modifying it. This patch produces the expected sparse extents in xfstests generic/064 only when the server-reported block size is compatible with the server's deallocation granularity. For ksmbd, the reported block size follows the backing filesystem, and the test passes. For Samba, the test passes with a block size matching the backend granularity, for example, 4 KiB on Btrfs, but not with the default 1 KiB value. For Windows Server 2022, 4 KiB inserts do not generate holes, while aligned inserts of 64 KiB or larger do. Fixes: 7fe6fe95b936 ("cifs: add FALLOC_FL_INSERT_RANGE support") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index f41fc71f1ba7..ac730eab382b 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4013,6 +4013,11 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, count = old_eof - off; + /* SET_ZERO_DATA creates a hole only in a sparse file. */ + rc = smb2_set_sparse(xid, tcon, cfile, inode, true); + if (rc) + goto out; + filemap_invalidate_lock(inode->i_mapping); rc = filemap_write_and_wait_range(inode->i_mapping, off, new_eof - 1); if (rc < 0) From 0923ae9f23cc9460b0df6fc124cd56ec4436411b Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:32 +0800 Subject: [PATCH 0194/2366] smb/client: fix data corruption in emulated insert range smb3_insert_range() shifts [off, EOF) right with COPYCHUNK, copying from low to high offsets. When the ranges overlap, the copy can overwrite source data that has not yet been copied. For a 1 MiB insert at offset 0: offset: 0 1M 2M 3M 4M 5M before: | A | B | C | D | expected: | hole | A | B | C | D | current: | hole | A | A | A | A | (corrupted) Let x be the insertion offset, L the total length to move, delta the insert length, and C the normal chunk size allowed by the server. Insert range maps [x, x + L) -> [x + delta, x + delta + L). When delta >= L, the complete source and target ranges are disjoint, so the normal copy order and chunk size are safe: offset: 0 4 8 12 16 20 24 28 32 source: [--S0--][--S1--][--S2--][--S3--] target: [--T0--][--T1--][--T2--][--T3--] When delta < L, the complete source and target ranges overlap, so the copy must proceed from EOF backwards. There are two subcases. If delta >= C, each corresponding source and target chunk is disjoint. The 1 MiB example has L = 4 MiB and delta = C = 1 MiB: offset: 0 1M 2M 3M 4M 5M source: [--S0--][--S1--][--S2--][--S3--] target: [--T0--][--T1--][--T2--][--T3--] Copying S0 from [0, 1M) to [1M, 2M) overwrites S1 before it is copied. Processing chunks from EOF backwards prevents this inter-chunk overwrite. If delta < C, the source and target ranges of a normal chunk also overlap. For example, with L = 16, delta = 2 and C = 4: offset: 0 2 4 6 8 10 12 14 16 18 source: [--S0--][--S1--][--S2--][--S3--] target: [--T0--][--T1--][--T2--][--T3--] Here S0 and T0 overlap over [2,4), S1 and T1 over [6,8), and so on. Backward ordering cannot control how the server copies bytes inside one descriptor, so the chunk size must be limited to delta. Fix this by copying overlapping right shifts from EOF backwards. Limit the chunk size to delta when delta < C so that each chunk's source and target ranges do not overlap. Using larger chunks would require a way to identify servers that safely handle overlapping COPYCHUNK descriptors. Therefore: delta >= L: keep the normal copy order and chunk size delta < L: delta >= C: copy backwards and keep the normal chunk size delta < C: copy backwards and limit the chunk size to delta Only the delta < C subcase requires reducing the chunk size for data integrity. Reproducer: bash -c ' MNT=/mnt/scratch # Generate four 1 MiB random blocks: [A][B][C][D]. dd if=/dev/urandom of=/tmp/src bs=1M count=4 status=none # With C = 1 MiB, test delta = C and delta < C. for delta in 1M 1K; do truncate -s 0 /tmp/expected truncate -s "$delta" /tmp/expected cat /tmp/src >> /tmp/expected cp /tmp/src "$MNT/file" fallocate --insert-range -o 0 -l "$delta" "$MNT/file" if cmp -s /tmp/expected "$MNT/file"; then echo "delta=$delta: OK" else echo "delta=$delta: CORRUPTED" fi done ' The corruption reproduces with Samba and ksmbd, while Windows handles the overlapping COPYCHUNK ranges safely. The 1 MiB case tests delta >= C, while the 1 KiB case tests delta < C. Before this change, the reproducer reports: delta=1M: CORRUPTED delta=1K: CORRUPTED After this change, it passes against both ksmbd and Samba: delta=1M: OK delta=1K: OK Fixes: 7fe6fe95b936 ("cifs: add FALLOC_FL_INSERT_RANGE support") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 132 ++++++++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 26 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index ac730eab382b..2fe5223b8a26 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -1839,31 +1839,31 @@ smb2_ioctl_query_info(const unsigned int xid, * * @tcon: destination file tcon * @bytes_left: how many bytes are left to copy + * @chunk_size: maximum size of a single chunk * * Return: maximum number of chunks with which Chunks[] can be filled. */ static inline u32 -calc_chunk_count(struct cifs_tcon *tcon, u64 bytes_left) +calc_chunk_count(struct cifs_tcon *tcon, u64 bytes_left, u32 chunk_size) { u32 max_chunks = READ_ONCE(tcon->max_chunks); u32 max_bytes_copy = READ_ONCE(tcon->max_bytes_copy); - u32 max_bytes_chunk = READ_ONCE(tcon->max_bytes_chunk); u64 need; u32 allowed; - if (!max_bytes_chunk || !max_bytes_copy || !max_chunks) + if (!chunk_size || !max_bytes_copy || !max_chunks) return 0; /* chunks needed for the remaining bytes */ - need = DIV_ROUND_UP_ULL(bytes_left, max_bytes_chunk); + need = DIV_ROUND_UP_ULL(bytes_left, chunk_size); /* chunks allowed per cc request */ - allowed = DIV_ROUND_UP(max_bytes_copy, max_bytes_chunk); + allowed = DIV_ROUND_UP(max_bytes_copy, chunk_size); return (u32)umin(need, umin(max_chunks, allowed)); } /** - * smb2_copychunk_range - server-side copy of data range + * __smb2_copychunk_range - server-side copy of data range * * @xid: transaction id * @src_file: source file @@ -1875,15 +1875,15 @@ calc_chunk_count(struct cifs_tcon *tcon, u64 bytes_left) * Obtains a resume key for @src_file and issues FSCTL_SRV_COPYCHUNK_WRITE * IOCTLs, splitting the request into chunks limited by tcon->max_*. * - * Return: @len on success; negative errno on failure. + * Return: 0 on success; negative errno on failure. */ -static ssize_t -smb2_copychunk_range(const unsigned int xid, - struct cifsFileInfo *src_file, - struct cifsFileInfo *dst_file, - u64 src_off, - u64 len, - u64 dst_off) +static int +__smb2_copychunk_range(const unsigned int xid, + struct cifsFileInfo *src_file, + struct cifsFileInfo *dst_file, + u64 src_off, + u64 len, + u64 dst_off) { int rc = 0; unsigned int ret_data_len = 0; @@ -1891,12 +1891,14 @@ smb2_copychunk_range(const unsigned int xid, struct copychunk_ioctl_rsp *cc_rsp = NULL; struct cifs_tcon *tcon; struct srv_copychunk *chunk; - u32 chunks, chunk_count, chunk_bytes; + u32 chunks, chunk_count, chunk_bytes, chunk_size; u32 copy_bytes, copy_bytes_left; u32 chunks_written, bytes_written; u64 total_bytes_left = len; u64 src_off_prev, dst_off_prev; + u64 max_chunk = 0; u32 retries = 0; + bool reverse = false; tcon = tlink_tcon(dst_file->tlink); @@ -1904,8 +1906,50 @@ smb2_copychunk_range(const unsigned int xid, dst_file->fid.volatile_fid, tcon->tid, tcon->ses->Suid, src_off, dst_off, len); + /* + * Same-file left shifts are safe in forward order. For a right shift, + * let L be the copy length, delta the distance between the source and + * destination, and C the normal chunk size: + * + * delta >= L: copy forwards using C + * delta < L: + * delta >= C: copy backwards using C + * delta < C: copy backwards with chunks limited to delta + * + * Copying backwards prevents one chunk from overwriting data needed by + * a later chunk. Limiting the chunk size to delta prevents an individual + * chunk from overlapping itself. + * This limit can be removed once all supported servers handle overlapping + * descriptors safely. + * + * A small right shift over a large range may therefore require many + * chunks. + */ + if (src_file == dst_file && dst_off > src_off) { + u64 delta = dst_off - src_off; + + if (delta < len) { + reverse = true; + max_chunk = delta; + } + } + + /* + * A backward copy walks the offsets down from the end of the range. + * Do this once, outside the retry loop, so a retry does not move the + * offsets again. + */ + if (reverse) { + src_off += len; + dst_off += len; + } + retry: - chunk_count = calc_chunk_count(tcon, total_bytes_left); + chunk_size = READ_ONCE(tcon->max_bytes_chunk); + if (max_chunk && max_chunk < chunk_size) + chunk_size = (u32)max_chunk; + + chunk_count = calc_chunk_count(tcon, total_bytes_left, chunk_size); if (!chunk_count) { rc = -EOPNOTSUPP; goto out; @@ -1946,16 +1990,21 @@ smb2_copychunk_range(const unsigned int xid, while (copy_bytes_left > 0 && chunks < chunk_count) { chunk = &cc_req->Chunks[chunks++]; + chunk_bytes = umin(copy_bytes_left, chunk_size); + if (reverse) { + src_off -= chunk_bytes; + dst_off -= chunk_bytes; + } + chunk->SourceOffset = cpu_to_le64(src_off); chunk->TargetOffset = cpu_to_le64(dst_off); - - chunk_bytes = umin(copy_bytes_left, tcon->max_bytes_chunk); - chunk->Length = cpu_to_le32(chunk_bytes); /* Buffer is zeroed, no need to set chunk->Reserved = 0 */ - src_off += chunk_bytes; - dst_off += chunk_bytes; + if (!reverse) { + src_off += chunk_bytes; + dst_off += chunk_bytes; + } copy_bytes_left -= chunk_bytes; copy_bytes += chunk_bytes; @@ -2003,6 +2052,18 @@ smb2_copychunk_range(const unsigned int xid, goto out; } + /* + * A successful COPYCHUNK should copy every descriptor (MS-SMB2 + * 3.3.5.15.6). Reject a short backward copy because the rewind + * below only supports forward copying. + */ + if (unlikely(reverse && bytes_written < copy_bytes)) { + cifs_tcon_dbg(VFS, "Copychunk short write %u/%u (reverse)\n", + bytes_written, copy_bytes); + rc = -EIO; + goto out; + } + /* Partial write: rewind */ if (bytes_written < copy_bytes) { u32 delta = copy_bytes - bytes_written; @@ -2064,10 +2125,27 @@ smb2_copychunk_range(const unsigned int xid, trace_smb3_copychunk_done(xid, src_file->fid.volatile_fid, dst_file->fid.volatile_fid, tcon->tid, tcon->ses->Suid, src_off, dst_off, len); - return len; + return 0; } } +static ssize_t +smb2_copychunk_range(const unsigned int xid, + struct cifsFileInfo *src_file, + struct cifsFileInfo *dst_file, + u64 src_off, + u64 len, + u64 dst_off) +{ + int rc; + + rc = __smb2_copychunk_range(xid, src_file, dst_file, src_off, len, + dst_off); + if (rc) + return rc; + return len; +} + static int smb2_flush_file(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_fid *fid) @@ -3992,7 +4070,6 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, struct cifsFileInfo *cfile = file->private_data; struct inode *inode = file_inode(file); struct cifsInodeInfo *cifsi = CIFS_I(inode); - u64 count; loff_t old_eof, new_eof; xid = get_xid(); @@ -4011,8 +4088,6 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, if (rc) goto out; - count = old_eof - off; - /* SET_ZERO_DATA creates a hole only in a sparse file. */ rc = smb2_set_sparse(xid, tcon, cfile, inode, true); if (rc) @@ -4036,7 +4111,12 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, spin_unlock(&inode->i_lock); fscache_resize_cookie(cifs_inode_cookie(inode), i_size_read(inode)); - rc = smb2_copychunk_range(xid, cfile, cfile, off, count, off + len); + /* + * Move [off, old_eof) right by len. The helper copies backwards if the + * source and destination ranges overlap. + */ + rc = __smb2_copychunk_range(xid, cfile, cfile, off, old_eof - off, + off + len); if (rc < 0) goto out_2; spin_lock(&inode->i_lock); From 7811701d6af7db76481a82b9bc3c4adf7863acf5 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:33 +0800 Subject: [PATCH 0195/2366] smb/client: fix integer truncation in collapse range smb3_collapse_range() stores the ssize_t return value of smb2_copychunk_range() in an int. A successful copy larger than INT_MAX is truncated to a negative value and treated as an error. Reproducer: MNT=/mnt/scratch truncate -s 2056M "$MNT/file" fallocate --collapse-range -o 1M -l 1M "$MNT/file" Fix this by using __smb2_copychunk_range(), which reports success as zero instead of returning the copied byte count. Before this change, the reproducer fails with: fallocate: fallocate failed: Success and the file size remains unchanged at 2056 MiB. After this change, the reproducer succeeds and the file size becomes the expected 2055 MiB. Fixes: 5476b5dd82c8 ("cifs: add support for FALLOC_FL_COLLAPSE_RANGE") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 2fe5223b8a26..1d476a8af863 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4036,8 +4036,8 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, spin_unlock(&inode->i_lock); netfs_wait_for_outstanding_io(inode); - rc = smb2_copychunk_range(xid, cfile, cfile, off + len, - old_eof - off - len, off); + rc = __smb2_copychunk_range(xid, cfile, cfile, off + len, + old_eof - off - len, off); if (rc < 0) goto out_2; From 01261a6fa48b62f5ead8e88aaca1e27cb9ab9032 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:34 +0800 Subject: [PATCH 0196/2366] smb/client: fix stale page cache in insert/collapse range smb3_insert_range() and smb3_collapse_range() use truncate_pagecache_range() to invalidate the affected page cache. However, if off or old_eof is not page-aligned, the boundary pages are only partially zeroed and remain uptodate. As a result, the client may return stale data after a successful insert/collapse range operation. For example, with 4K pages: page 0 page 1 page 2 0------4K 4K------8K 8K------12K ^ ^ off=2K old_eof=10K Page 1 is removed from the page cache, while the boundary pages are only partially zeroed. After COPYCHUNK moves the data on the server, these cached pages may still return stale data. This can be reproduced on a CIFS mount: bash -c ' FILE=/mnt/scratch/repro # Use a 6 KiB file so EOF is not page-aligned. dd if=/dev/urandom of=/tmp/src bs=1K count=6 status=none # Expected: a 4 KiB hole followed by the original data. rm -f /tmp/expected truncate -s 4K /tmp/expected cat /tmp/src >> /tmp/expected cp /tmp/src "$FILE" # Prime the page cache before moving data on the server. cat "$FILE" > /dev/null fallocate --insert-range -o 0 -l 4K "$FILE" if cmp -s /tmp/expected "$FILE"; then echo "readback: OK" else echo "readback: STALE DATA" fi ' Fix this by writing back dirty data and discarding the page cache from the start of the page containing off to EOF before moving data on the server. Fixes: 9c8b7a293f50 ("smb3: fix temporary data corruption in insert range") Fixes: fa30a81f255a ("smb3: fix temporary data corruption in collapse range") Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 1d476a8af863..cd5e31b578f9 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4026,15 +4026,22 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, } filemap_invalidate_lock(inode->i_mapping); - rc = filemap_write_and_wait_range(inode->i_mapping, off, old_eof - 1); + rc = filemap_write_and_wait_range(inode->i_mapping, + round_down(off, PAGE_SIZE), + old_eof - 1); if (rc < 0) goto out_2; - truncate_pagecache_range(inode, off, old_eof); + netfs_wait_for_outstanding_io(inode); + /* + * Invalidate cached folios from the page containing off to EOF before + * moving data on the server, so subsequent reads do not see stale data. + */ + truncate_pagecache_range(inode, round_down(off, PAGE_SIZE), -1); + spin_lock(&inode->i_lock); netfs_write_zero_point(inode, old_eof); spin_unlock(&inode->i_lock); - netfs_wait_for_outstanding_io(inode); rc = __smb2_copychunk_range(xid, cfile, cfile, off + len, old_eof - off - len, off); @@ -4094,11 +4101,17 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, goto out; filemap_invalidate_lock(inode->i_mapping); - rc = filemap_write_and_wait_range(inode->i_mapping, off, new_eof - 1); + rc = filemap_write_and_wait_range(inode->i_mapping, + round_down(off, PAGE_SIZE), + old_eof - 1); if (rc < 0) goto out_2; - truncate_pagecache_range(inode, off, old_eof); netfs_wait_for_outstanding_io(inode); + /* + * Invalidate cached folios from the page containing off to EOF before + * moving data on the server, so subsequent reads do not see stale data. + */ + truncate_pagecache_range(inode, round_down(off, PAGE_SIZE), -1); rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, new_eof); From 448ba0ae65ca61064183564d2983c9aa59bd6ba7 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Fri, 28 Aug 2026 15:19:35 +0800 Subject: [PATCH 0197/2366] smb/client: invalidate fscache for fallocate range operations smb3_zero_range(), smb3_punch_hole(), smb3_insert_range(), and smb3_collapse_range() modify file contents through server-side range operations. These operations discard the affected page cache, but leave the FS-Cache cookie valid, so a later read may return data cached before the range operation. Fix this by invalidating FS-Cache after outstanding I/O has completed and before modifying the file on the server. Run the following as root on a CIFS mount with fsc enabled and an active CacheFiles backend: bash -c ' MNT=/mnt/cifs FILE="$MNT/repro" # Generate four 1 MiB random blocks: [A][B][C][D]. dd if=/dev/urandom of=/tmp/src bs=1M count=4 status=none # Expected contents after zeroing B: [A][zero][C][D]. cp /tmp/src /tmp/expected dd if=/dev/zero of=/tmp/expected bs=1M seek=1 count=1 \ conv=notrunc status=none cp /tmp/src "$FILE" # Populate FS-Cache, then discard the page cache. sync echo 1 > /proc/sys/vm/drop_caches cat "$FILE" > /dev/null sync echo 1 > /proc/sys/vm/drop_caches fallocate --zero-range -o 1M -l 1M "$FILE" if cmp -s /tmp/expected "$FILE"; then echo "readback: OK" else echo "readback: STALE DATA" fi ' Before this change, the readback differs from /tmp/expected: readback: STALE DATA After this change, it matches: readback: OK Fixes: 30175628bf7f ("[SMB3] Enable fallocate -z support for SMB3 mounts") Fixes: 31742c5a3317 ("enable fallocate punch hole ("fallocate -p") for SMB3") Fixes: 5476b5dd82c8 ("cifs: add support for FALLOC_FL_COLLAPSE_RANGE") Fixes: 7fe6fe95b936 ("cifs: add FALLOC_FL_INSERT_RANGE support") Signed-off-by: Huiwen He Suggested-by: Namjae Jeon Reviewed-by: ChenXiaoSong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index cd5e31b578f9..cb4fd09f996e 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3552,6 +3552,9 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, if (keep_size == false && !CIFS_CACHE_READ(cifsi)) goto zero_range_exit; + fscache_invalidate(cifs_inode_cookie(inode), NULL, + i_size_read(inode), 0); + rc = smb3_zero_data(file, tcon, offset, len, xid); if (rc < 0) goto zero_range_exit; @@ -3621,6 +3624,8 @@ static long smb3_punch_hole(struct file *file, struct cifs_tcon *tcon, */ truncate_pagecache_range(inode, offset, offset + len - 1); netfs_wait_for_outstanding_io(inode); + fscache_invalidate(cifs_inode_cookie(inode), NULL, + i_size_read(inode), 0); cifs_dbg(FYI, "Offset %lld len %lld\n", offset, len); @@ -4038,6 +4043,7 @@ static long smb3_collapse_range(struct file *file, struct cifs_tcon *tcon, * moving data on the server, so subsequent reads do not see stale data. */ truncate_pagecache_range(inode, round_down(off, PAGE_SIZE), -1); + fscache_invalidate(cifs_inode_cookie(inode), NULL, old_eof, 0); spin_lock(&inode->i_lock); netfs_write_zero_point(inode, old_eof); @@ -4112,6 +4118,7 @@ static long smb3_insert_range(struct file *file, struct cifs_tcon *tcon, * moving data on the server, so subsequent reads do not see stale data. */ truncate_pagecache_range(inode, round_down(off, PAGE_SIZE), -1); + fscache_invalidate(cifs_inode_cookie(inode), NULL, old_eof, 0); rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, new_eof); From d83a21bb26015bfdd79b0440fe816b271b8bbab3 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 25 Aug 2026 10:30:43 +0200 Subject: [PATCH 0198/2366] smb: client: transport: Fix debug printing in __release_mid() Long time ago during upgrading printk():s to the respective pr_() calls one misconversion happened and nobody has noticed that. So, previously printk(KERN_DEBUG) + printk() worked as one long debug print since the trailing '\n' is only present in the followup printk() format string. The culprit change missed that and split the message to two on the different levels. Restore the original behaviour to make users be less confused in the most likely never happen cases of partially getting that message. Fixes: 0b456f04bcdf ("cifs: convert printk(LEVEL...) to pr_") Signed-off-by: Andy Shevchenko Signed-off-by: Paulo Alcantara --- fs/smb/client/transport.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index fdf4e50c27ce..e266859818a4 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -101,12 +101,11 @@ void __release_mid(struct TCP_Server_Info *server, struct mid_q_entry *midEntry) trace_smb3_slow_rsp(smb_cmd, midEntry->mid, midEntry->pid, midEntry->when_sent, midEntry->when_received); if (cifsFYI & CIFS_TIMER) { - pr_debug("slow rsp: cmd %d mid %llu", - midEntry->command, midEntry->mid); - cifs_info("A: 0x%lx S: 0x%lx R: 0x%lx\n", - now - midEntry->when_alloc, - now - midEntry->when_sent, - now - midEntry->when_received); + pr_debug("slow rsp: cmd %d mid %llu A: 0x%lx S: 0x%lx R: 0x%lx\n", + midEntry->command, midEntry->mid, + now - midEntry->when_alloc, + now - midEntry->when_sent, + now - midEntry->when_received); } } #endif From 69499395867332364ed4ac8b546537eb3bab6ebb Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 24 Aug 2026 16:59:12 -0300 Subject: [PATCH 0199/2366] smb: client: fix multiuser mount with krb5 Customer reported that they could no longer mount their SMB shares with multiuser mount option and krb5. Turned out that the client wasn't duplicating username option when creating multiuser connections, therefore failing to retrieve credentials as cifs.upcall(8) couldn't find them in keytab. Fix this by duplicating username option (if set) from original fs context before creating multiuser connections with krb5. Reproducer: ``` $ ktutil ktutil: add_entry -password -p testuser -k 1 -e aes256-cts Password for testuser@ZELDA.TEST: ktutil: write_kt /etc/krb5.keytab ktutil: quit $ klist -ke Keytab name: FILE:/etc/krb5.keytab KVNO Principal ---- ---------------------------------------------------------------- 1 testuser@ZELDA.TEST (aes256-cts-hmac-sha1-96) $ mount.cifs //w22-root2/scratch /mnt/1 -o \ uid=1000,sec=krb5,username=testuser@ZELDA.TEST,multiuser mount error(13): Permission denied Refer to the mount.cifs(8) manual page (e.g. man mount.cifs) and kernel log messages (dmesg) ``` Reported-by: Jacob Shivers Fixes: 12b4c5d98cd7 ("smb: client: fix krb5 mount with username option") Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/connect.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index bcd7f1ae99ba..b6e98eb31673 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -4189,14 +4189,25 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, return rc; } -static int -cifs_set_vol_auth(struct smb3_fs_context *ctx, struct cifs_ses *ses) +static int set_fs_context_auth(struct smb3_fs_context *ctx, + struct cifs_ses *ses) { ctx->sectype = ses->sectype; - /* krb5 is special, since we don't need username or pw */ - if (ctx->sectype == Kerberos) + /* + * krb5 is special as we might need to pass username (passwordless) down + * to cifs.upcall(8) for keytab. + */ + if (ctx->sectype == Kerberos) { + if (ses->user_name && ses->user_name[0]) { + ctx->username = kstrndup(ses->user_name, + CIFS_MAX_USERNAME_LEN, + GFP_KERNEL); + if (!ctx->username) + return -ENOMEM; + } return 0; + } return cifs_set_cifscreds(ctx, ses); } @@ -4236,7 +4247,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid) ctx->dfs_root_ses = master_tcon->ses->dfs_root_ses; ctx->unicode = master_tcon->ses->unicode; - rc = cifs_set_vol_auth(ctx, master_tcon->ses); + rc = set_fs_context_auth(ctx, master_tcon->ses); if (rc) { tcon = ERR_PTR(rc); goto out; From 1dac61e2c29d1a71784604b48cf3b1234a65ec29 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 26 Aug 2026 21:13:36 -0500 Subject: [PATCH 0200/2366] smb: client: fix heap overflow in cifs_do_set_acl() cifs_set_acl() validates ACL size using posix_acl_xattr_size(): 4 + (count * 8) // 4-byte header + 8 bytes per ACE cifs_do_set_acl() then calls posix_acl_to_cifs() to write the CIFS wire format into the same buffer: 6 + (count * 10) // 6-byte header + 10 bytes per ACE An ACL that passes the xattr-based check in cifs_set_acl() can overflow the heap when posix_acl_to_cifs() writes the larger CIFS format. Validate the CIFS format size against the remaining buffer space and USHRT_MAX before converting--data_count is __u16, so sizes above USHRT_MAX truncate the on-wire packet length, causing the server to apply a partial ACL. Replace MaxDataCount = 1000 with min(CIFSMaxBufSize, USHRT_MAX). Fixes: dc1af4c4b4721 ("cifs: implement set acl method") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index f5aad5f61dce..230af243247c 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -3555,6 +3555,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon, int rc = 0; int bytes_returned = 0; __u16 params, byte_count, data_count, param_offset, offset; + size_t cifs_acl_size, bytes_available; cifs_dbg(FYI, "In SetPosixACL (Unix) for path %s\n", fileName); setAclRetry: @@ -3574,8 +3575,7 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon, } params = 6 + name_len; pSMB->MaxParameterCount = cpu_to_le16(2); - /* BB find max SMB size from sess */ - pSMB->MaxDataCount = cpu_to_le16(1000); + pSMB->MaxDataCount = cpu_to_le16(min_t(unsigned int, CIFSMaxBufSize, USHRT_MAX)); pSMB->MaxSetupCount = 0; pSMB->Reserved = 0; pSMB->Flags = 0; @@ -3587,6 +3587,15 @@ int cifs_do_set_acl(const unsigned int xid, struct cifs_tcon *tcon, parm_data = ((char *)pSMB) + offset; pSMB->ParameterOffset = cpu_to_le16(param_offset); + /* make sure we can fit the larger cifs_posix_aces in the buffer */ + cifs_acl_size = sizeof(struct cifs_posix_acl) + + (acl->a_count * sizeof(struct cifs_posix_ace)); + bytes_available = (CIFSMaxBufSize + MAX_HEADER_SIZE(tcon->ses->server)) - offset; + if (cifs_acl_size > bytes_available || cifs_acl_size > USHRT_MAX) { + rc = -E2BIG; + goto setACLerrorExit; + } + /* convert to on the wire format for POSIX ACL */ data_count = posix_acl_to_cifs(parm_data, acl, acl_type); From fe39cd9d48f2346605f3746e0cc19e89d5f373eb Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 26 Aug 2026 20:57:38 -0500 Subject: [PATCH 0201/2366] cifs: don't update i_size in cifs_do_truncate without a cached handle If find_writable_file() returns null, cifs_file_flush will return 0 without issuing set_file_size, and the outer 'if (!rc)' block will set i_size to 0 before telling the server to truncate. If the cifs_open() then fails, the inode will have size 0, while the server file is unchanged. Move the netfs_resize_file() and cifs_setsize() into the 'if (cfile)', so they only run after a successful set_file_size. In the no-handle else branch, evict stale pages with truncate_inode_pages before the O_TRUNC open to dispose of old cache pages, and let the open response set the i_size. Fixes: 110fee6b9bb5 ("smb: client: fix missing timestamp updates with O_TRUNC") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Acked-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/file.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 389083f9ce00..100acc76e9be 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -1012,10 +1012,26 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry) server = tcon->ses->server; rc = server->ops->set_file_size(xid, tcon, cfile, 0, false); - } - if (!rc) { - netfs_resize_file(&cinode->netfs, 0, true); - cifs_setsize(inode, 0); + if (!rc) { + inode_lock(inode); + filemap_invalidate_lock(inode->i_mapping); + netfs_resize_file(&cinode->netfs, 0, true); + cifs_setsize(inode, 0); + filemap_invalidate_unlock(inode->i_mapping); + inode_unlock(inode); + cifs_invalidate_cache(inode, 0); + } + } else { + /* + * No cached handle; evict stale pages so they can't + * be served after the file is later extended; let + * the server's O_TRUNC open response set the i_size + */ + inode_lock(inode); + filemap_invalidate_lock(inode->i_mapping); + truncate_inode_pages(inode->i_mapping, 0); + filemap_invalidate_unlock(inode->i_mapping); + inode_unlock(inode); cifs_invalidate_cache(inode, 0); } } From 2af470916a208b576ac9975d221d9a378cf8ace9 Mon Sep 17 00:00:00 2001 From: Boqun Feng Date: Thu, 27 Aug 2026 12:48:35 -0700 Subject: [PATCH 0202/2366] preempt: Remove hardirq_disable_count() It turns out the previous usage of hardirq_disable_count() in __irq_exit_rcu() would cause softirq pending issues. Without that usage, hardirq_disable_count() doesn't need to exist, so remove it. Also move hardirq_disable_enter/exit() into the Rust specific interrupt_rc header. [ tglx: Move the helpers over ] Signed-off-by: Boqun Feng Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260827194835.38968-1-boqun@kernel.org --- include/linux/interrupt_rc.h | 3 +++ include/linux/preempt.h | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/include/linux/interrupt_rc.h b/include/linux/interrupt_rc.h index e68e1bedba66..a9ed937a80e7 100644 --- a/include/linux/interrupt_rc.h +++ b/include/linux/interrupt_rc.h @@ -52,6 +52,9 @@ extern void _local_interrupt_save_state(unsigned long flags); extern void _local_interrupt_enable(void); #endif /* !MODULE */ +#define hardirq_disable_enter() __preempt_count_add_return(HARDIRQ_DISABLE_OFFSET) +#define hardirq_disable_exit() __preempt_count_sub_return(HARDIRQ_DISABLE_OFFSET) + static inline void local_interrupt_disable(void) { int new_count; diff --git a/include/linux/preempt.h b/include/linux/preempt.h index 8299657f0f86..2e689de7b29a 100644 --- a/include/linux/preempt.h +++ b/include/linux/preempt.h @@ -168,10 +168,6 @@ static __always_inline unsigned char interrupt_context_level(void) #define in_softirq() (softirq_count()) #define in_interrupt() (irq_count()) -#define hardirq_disable_count() ((preempt_count() & HARDIRQ_DISABLE_MASK) >> HARDIRQ_DISABLE_SHIFT) -#define hardirq_disable_enter() __preempt_count_add_return(HARDIRQ_DISABLE_OFFSET) -#define hardirq_disable_exit() __preempt_count_sub_return(HARDIRQ_DISABLE_OFFSET) - /* * The preempt_count offset after preempt_disable(); */ From 2cb0b0b1ed69430bf73740377ea0a1c44c50db63 Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Fri, 28 Aug 2026 12:24:25 +0800 Subject: [PATCH 0203/2366] sctp: fix soft lockup from unpadded ASCONF-ACK parameter iteration sctp_verify_asconf() walks ASCONF-ACK parameters with sctp_walk_params(), which advances by SCTP_PAD4(length), while the consumer sctp_get_asconf_response() iterates the same parameters advancing by the raw length, without padding. A single odd-length parameter desynchronises the two walks and makes the consumer interpret attacker-controlled bytes at a misaligned offset. When those bytes yield a length of zero, the while loop over asconf_ack_len makes no progress, spinning forever in softirq context, and the watchdog reports a soft lockup. All reads stay within the received skb, so the lockup is a pure remote denial of service. A remote peer can trigger it with a crafted ASCONF-ACK on an ADD-IP enabled association with an outstanding ASCONF (RFC 5061 section 4.1.2 requires the chunk to be authenticated, but the predefined empty key id 0 allows the peer to compute the same association HMAC from publicly exchanged parameters, so the gate does not help). The SCTP_PARAM_ERR_CAUSE case of sctp_verify_asconf() also performs no length check, letting a parameter without a complete error header reach the consumer, which reads errhdr.cause past the end of the parameter, an out-of-bounds read. Reject SCTP_PARAM_ERR_CAUSE parameters shorter than sizeof(struct sctp_addip_param) + sizeof(struct sctp_errhdr) at the verifier, and advance the consumer iterator with the same padding rule as the verifier to keep the two walks in lockstep. The verifier change guarantees a complete error header in every ERR_CAUSE parameter the consumer can see, so the consumer's asconf_ack_len check is dropped and it returns err_param->cause directly. The consumer padding fix is still required because odd lengths remain valid for SCTP_PARAM_ERR_CAUSE per RFC 5061. The issue was found by ZeroHive, a vulnerability hunting agent at Tencent Yunding Lab. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Henry Martin Acked-by: Xin Long Link: https://patch.msgid.link/20260828042431.3873725-1-bsdhenrymartin@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/sm_make_chunk.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c index 236e25abc7a4..84a4c97d0f75 100644 --- a/net/sctp/sm_make_chunk.c +++ b/net/sctp/sm_make_chunk.c @@ -3215,6 +3215,9 @@ bool sctp_verify_asconf(const struct sctp_association *asoc, *errp = param.p; switch (param.p->type) { case SCTP_PARAM_ERR_CAUSE: + if (length < sizeof(struct sctp_addip_param) + + sizeof(struct sctp_errhdr)) + return false; break; case SCTP_PARAM_IPV4_ADDRESS: if (length != sizeof(struct sctp_ipv4addr_param)) @@ -3448,20 +3451,15 @@ static __be16 sctp_get_asconf_response(struct sctp_chunk *asconf_ack, case SCTP_PARAM_ERR_CAUSE: length = sizeof(*asconf_ack_param); err_param = (void *)asconf_ack_param + length; - asconf_ack_len -= length; - if (asconf_ack_len > 0) - return err_param->cause; - else - return SCTP_ERROR_INV_PARAM; - break; + return err_param->cause; default: return SCTP_ERROR_INV_PARAM; } } length = ntohs(asconf_ack_param->param_hdr.length); - asconf_ack_param = (void *)asconf_ack_param + length; - asconf_ack_len -= length; + asconf_ack_param = (void *)asconf_ack_param + SCTP_PAD4(length); + asconf_ack_len -= SCTP_PAD4(length); } return err_code; From ac08d183dac0441e41f77bbad50798fe609d90f1 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Fri, 28 Aug 2026 09:29:18 +0800 Subject: [PATCH 0204/2366] raw: annotate disconnect-side IPv4 match writers raw_v4_match() reads inet_daddr, inet_rcv_saddr and sk_bound_dev_if locklessly under RCU. Bind and connect writers are annotated, but __udp_disconnect() still clears the same fields using plain stores. Commit 18f116931f52e ("raw: annotate lockless match fields in raw_v4_match()") added the lockless readers and annotated the raw bind and datagram connect writers. Its v4 revision intentionally left the shared disconnect-side IPv4 writers for follow-up cleanup. Complete that follow-up by using WRITE_ONCE() for the disconnect-side stores, including the inet_rcv_saddr reset in inet_reset_saddr(), to pair with the lockless raw socket matcher. Fixes: 0daf07e52709 ("raw: convert raw sockets to RCU") Link: https://lore.kernel.org/netdev/20260716142958.3064224-1-runyu.xiao@seu.edu.cn/ Suggested-by: Runyu Xiao Signed-off-by: Jackie Liu Signed-off-by: Xuanqiang Luo Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260828012918.1461-1-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- include/net/ip.h | 3 ++- net/ipv4/udp.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/net/ip.h b/include/net/ip.h index a8f57b4f4aa2..6f602df72ee6 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -704,7 +704,8 @@ static inline void ip_ipgre_mc_map(__be32 naddr, const unsigned char *broadcast, static __inline__ void inet_reset_saddr(struct sock *sk) { - inet_sk(sk)->inet_rcv_saddr = inet_sk(sk)->inet_saddr = 0; + inet_sk(sk)->inet_saddr = 0; + WRITE_ONCE(inet_sk(sk)->inet_rcv_saddr, 0); #if IS_ENABLED(CONFIG_IPV6) if (sk->sk_family == PF_INET6) { struct ipv6_pinfo *np = inet6_sk(sk); diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index af9603217444..6ff5670bf6ed 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -2166,10 +2166,10 @@ int __udp_disconnect(struct sock *sk, int flags) */ sk->sk_state = TCP_CLOSE; - inet->inet_daddr = 0; + WRITE_ONCE(inet->inet_daddr, 0); inet->inet_dport = 0; sock_rps_reset_rxhash(sk); - sk->sk_bound_dev_if = 0; + WRITE_ONCE(sk->sk_bound_dev_if, 0); if (!(sk->sk_userlocks & SOCK_BINDADDR_LOCK)) { inet_reset_saddr(sk); if (sk->sk_prot->rehash && From ac8d6b28d48c5d951dcd923d33e461588e762a6d Mon Sep 17 00:00:00 2001 From: James Nugraha Date: Fri, 28 Aug 2026 09:22:19 +1000 Subject: [PATCH 0205/2366] net: amd-xgbe: discard rx packets with bad FCS amd-xgbe driver currently sets the MAC_RCR.DCRCC bit whenever RX is enabled. This disables hardware FCS validation, causing packets with bad FCS to be accepted unconditionally. This change unsets DCRCC so that packets with bad FCS will be dropped, in-line with typical behaviours of many other network controllers. Tests: - Verified that packets with bad FCS are now dropped. - Verified that receiving packets with bad FCS will increment the `rx_crc_errors` counter. Fixes: c5aa9e3b8156 ("amd-xgbe: Initial AMD 10GbE platform driver") Signed-off-by: James Nugraha Link: https://patch.msgid.link/20260827232220.69907-1-aslan.jnn@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/xgbe/xgbe-dev.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-dev.c b/drivers/net/ethernet/amd/xgbe/xgbe-dev.c index 2de974213090..e2e850c1b90b 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-dev.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-dev.c @@ -3400,7 +3400,7 @@ static void xgbe_enable_rx(struct xgbe_prv_data *pdata) XGMAC_IOWRITE(pdata, MAC_RQC0R, reg_val); /* Enable MAC Rx */ - XGMAC_IOWRITE_BITS(pdata, MAC_RCR, DCRCC, 1); + XGMAC_IOWRITE_BITS(pdata, MAC_RCR, DCRCC, 0); XGMAC_IOWRITE_BITS(pdata, MAC_RCR, CST, 1); XGMAC_IOWRITE_BITS(pdata, MAC_RCR, ACS, 1); XGMAC_IOWRITE_BITS(pdata, MAC_RCR, RE, 1); @@ -3411,7 +3411,6 @@ static void xgbe_disable_rx(struct xgbe_prv_data *pdata) unsigned int i; /* Disable MAC Rx */ - XGMAC_IOWRITE_BITS(pdata, MAC_RCR, DCRCC, 0); XGMAC_IOWRITE_BITS(pdata, MAC_RCR, CST, 0); XGMAC_IOWRITE_BITS(pdata, MAC_RCR, ACS, 0); XGMAC_IOWRITE_BITS(pdata, MAC_RCR, RE, 0); From 6cfc1b90cb86f4aabc69fb8e30128e07e2cdfa3a Mon Sep 17 00:00:00 2001 From: Charles Vosburgh Date: Thu, 27 Aug 2026 17:32:53 -0400 Subject: [PATCH 0206/2366] sctp: validate chunk length in the inqueue parser SCTP chunks always include a four-byte generic header, but sctp_inq_pop() currently accepts shorter declared lengths. A zero-length chunk leaves chunk_end at the current header. When ASCONF is covered by the association's SCTP-AUTH policy, sctp_assoc_bh_rcv() can continue before the state machine performs its normal chunk-length check. sctp_inq_pop() then returns the same malformed chunk repeatedly and the receive softirq can lock up. A remote SCTP peer can trigger this after establishing an association on a kernel built with CONFIG_IP_SCTP and configured with net.sctp.addip_enable=1 and net.sctp.auth_enable=1. The reproducer did not require application credentials, a shared SCTP AUTH key, or net.sctp.addip_noauth_enable=1. On commit f967455fb2a5 ("seg6: reset IP6CB after IPv6 decapsulation"), one zero-length ASCONF caused repeated watchdog soft-lockup reports in a two-vCPU KVM guest. All 3 pre-trigger health probes succeeded, while 36 of 37 post-trigger probes failed. With this change, all 37 post-trigger probes succeeded and no equivalent soft-lockup signature appeared. Reject chunks shorter than the generic SCTP header at the shared inqueue parser boundary. Mark the packet for discard before either caller can continue processing it, while preserving the four-byte generic minimum. Declared-length 1 through 4 controls and kernel-generated ASCONF traffic remained healthy. The patched sctp_hello selftest passed for IPv4 and IPv6. The complete private reproducer and validation evidence are available directly to maintainers on request. Fixes: bbd0d59809f9 ("[SCTP]: Implement the receive and verification of AUTH chunk") Cc: stable@vger.kernel.org Signed-off-by: Charles Vosburgh Acked-by: Xin Long Link: https://patch.msgid.link/20260827-sctp-zero-chunk-inqueue-v2-1-2e7669c6a6cb@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/inqueue.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/sctp/inqueue.c b/net/sctp/inqueue.c index 5f988b3a8814..d666cec6b194 100644 --- a/net/sctp/inqueue.c +++ b/net/sctp/inqueue.c @@ -212,8 +212,10 @@ struct sctp_chunk *sctp_inq_pop(struct sctp_inq *queue) chunk->chunk_end = ((__u8 *)ch) + SCTP_PAD4(ntohs(ch->length)); skb_pull(chunk->skb, sizeof(*ch)); chunk->subh.v = NULL; /* Subheader is no longer valid. */ - - if (chunk->chunk_end + sizeof(*ch) <= skb_tail_pointer(chunk->skb)) { + if (unlikely(ntohs(ch->length) < sizeof(*ch))) { + chunk->pdiscard = 1; + } else if (chunk->chunk_end + sizeof(*ch) <= + skb_tail_pointer(chunk->skb)) { /* This is not a singleton */ chunk->singleton = 0; } else if (chunk->chunk_end > skb_tail_pointer(chunk->skb)) { From 4aa61c88b4e292e10abdfd791334b8272108d68a Mon Sep 17 00:00:00 2001 From: Baul Lee Date: Thu, 27 Aug 2026 02:36:04 +0900 Subject: [PATCH 0207/2366] vxlan: mdb: Fix use-after-free in vxlan_mdb_remote_src_del() vxlan_mdb_is_valid_source(), which validates MDBE_ATTR_SOURCE and every MDBE_ATTR_SRC_LIST member, accepts the all-zeros address. A source list is only accepted on a (*, G) entry, whose source is the all-zeros address, and for each member of the list an (S, G) entry is derived from it by substituting the source. Entries are keyed by a plain memcmp() of struct vxlan_mdb_entry_key, so if MDBE_ATTR_SOURCE is present and holds the all-zeros address and the source list holds it as well, the derived (S, G) key is byte-identical to the (*, G) key and resolves to the same entry. Omitting MDBE_ATTR_SOURCE is not equivalent, as the key is then left with a zero address family. vxlan_mdb_remote_src_del() removes the forwarding entry of a source before freeing the source entry: vxlan_mdb_remote_src_fwd_del(vxlan, group, remote, &ent->addr); vxlan_mdb_remote_src_entry_del(ent); With the keys aliased, the first call deletes the remote of the entry that owns 'ent' instead of a separate (S, G) entry, and frees 'ent'. The second call then runs on the freed entry, and its hlist_del() reads ->pprev and ->next out of it and writes through them. Adding the (*, G) entry with NLM_F_REPLACE and no source list marks the all-zeros source for deletion and reaches this from the sweep at the end of vxlan_mdb_remote_srcs_replace(). BUG: KASAN: slab-use-after-free in __vxlan_mdb_add+0x1cd/0xd70 Read of size 8 at addr ffff888102852500 by task poc/84 __vxlan_mdb_add+0x1cd/0xd70 vxlan_mdb_add+0xc0/0x140 rtnl_mdb_add+0x157/0x2a0 rtnetlink_rcv_msg+0x207/0x5a0 Allocated by task 84: __kmalloc_cache_noprof+0x153/0x360 vxlan_mdb_remote_srcs_add+0x2eb/0x440 __vxlan_mdb_add+0x803/0xd70 Freed by task 84: kfree+0x14c/0x3b0 vxlan_mdb_remote_del+0x129/0x1a0 __vxlan_mdb_del+0x4f/0xe0 vxlan_mdb_remote_src_fwd_del.isra.0+0x162/0x1b0 __vxlan_mdb_add+0x1c5/0xd70 The MDB operations are netns-scoped, so an unprivileged user can perform them in a new user and network namespace. Reject the all-zeros address in vxlan_mdb_is_valid_source(), which covers both call sites. A (*, G) entry is expressed by omitting the source, so nothing legitimate is refused. Discovered by XBOW, triaged by Baul Lee Fixes: a3a48de5eade ("vxlan: mdb: Add MDB control path support") Signed-off-by: Baul Lee Reviewed-by: Ido Schimmel Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260826173604.90158-1-baul.lee@xbow.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_mdb.c | 8 ++++++++ tools/testing/selftests/net/test_vxlan_mdb.sh | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/net/vxlan/vxlan_mdb.c b/drivers/net/vxlan/vxlan_mdb.c index d71e1925ecfd..841f42ffecb9 100644 --- a/drivers/net/vxlan/vxlan_mdb.c +++ b/drivers/net/vxlan/vxlan_mdb.c @@ -354,6 +354,10 @@ static bool vxlan_mdb_is_valid_source(const struct nlattr *attr, __be16 proto, NL_SET_ERR_MSG_MOD(extack, "IPv4 multicast source address is not allowed"); return false; } + if (ipv4_is_zeronet(nla_get_in_addr(attr))) { + NL_SET_ERR_MSG_MOD(extack, "IPv4 all-zeros source address is not allowed"); + return false; + } break; #if IS_ENABLED(CONFIG_IPV6) case htons(ETH_P_IPV6): { @@ -368,6 +372,10 @@ static bool vxlan_mdb_is_valid_source(const struct nlattr *attr, __be16 proto, NL_SET_ERR_MSG_MOD(extack, "IPv6 multicast source address is not allowed"); return false; } + if (ipv6_addr_any(&src)) { + NL_SET_ERR_MSG_MOD(extack, "IPv6 all-zeros source address is not allowed"); + return false; + } break; } #endif diff --git a/tools/testing/selftests/net/test_vxlan_mdb.sh b/tools/testing/selftests/net/test_vxlan_mdb.sh index 58da5de99ac4..f9600aabd4a2 100755 --- a/tools/testing/selftests/net/test_vxlan_mdb.sh +++ b/tools/testing/selftests/net/test_vxlan_mdb.sh @@ -685,6 +685,9 @@ star_g_common() run_cmd "bridge -n $ns1 mdb add dev vx0 port vx0 grp $grp permanent filter_mode exclude source_list $grp dst $vtep_ip src_vni 10010" log_test $? 255 "Invalid source in source list" + run_cmd "bridge -n $ns1 mdb add dev vx0 port vx0 grp $grp permanent filter_mode exclude source_list $all_zeros_grp dst $vtep_ip src_vni 10010" + log_test $? 255 "All-zeros source in source list" + run_cmd "bridge -n $ns1 mdb add dev vx0 port vx0 grp $grp permanent source_list $src1 dst $vtep_ip src_vni 10010" log_test $? 255 "Source list without filter mode" } @@ -784,6 +787,9 @@ sg_common() run_cmd "bridge -n $ns1 mdb add dev vx0 port vx0 grp $grp src $grp permanent dst $vtep_ip src_vni 10010" log_test $? 255 "(S, G) with an invalid source list" + run_cmd "bridge -n $ns1 mdb add dev vx0 port vx0 grp $grp src $all_zeros_grp permanent dst $vtep_ip src_vni 10010" + log_test $? 255 "(S, G) with an all-zeros source" + run_cmd "bridge -n $ns1 mdb add dev vx0 port vx0 grp $all_zeros_grp src $src permanent dst $vtep_ip src_vni 10010" log_test $? 255 "All-zeros group with source" } From a8455260b2e9c024d1872ac1c094793d55a7e537 Mon Sep 17 00:00:00 2001 From: Maciej Fijalkowski Date: Fri, 28 Aug 2026 18:49:18 +0200 Subject: [PATCH 0208/2366] ipvlan: unregister upper devices outside pnodes_lock syzbot reported the following circular locking dependency: xs->mutex -> netdev lock -> pnodes_lock -> net->xdp.lock -> xs->mutex The pnodes_lock -> net->xdp.lock edge is recorded when ipvlan_device_event(NETDEV_UNREGISTER) calls unregister_netdevice_many() while holding pnodes_lock. A nested NETDEV_UNREGISTER notification for an IPvlan device enters xsk_notifier(), which acquires net->xdp.lock. Keep pnodes_lock only while marking the upper devices as dying, removing them from port->ipvlans, and queueing them for unregistration. Once the devices have been detached from the protected list, release pnodes_lock before unregister_netdevice_many() invokes notifier callbacks. The port remains alive across unregistration because ipvlan_device_event() holds the reference acquired by ipvlan_port_get(). The dying flag prevents a concurrent ->dellink() callback from deleting a queued device again. Fixes: 35add1093e2f ("ipvlan: Protect ipvl_port.ipvlans with mutex.") Reported-by: syzbot+aa48b5fe7bfda62d1682@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=aa48b5fe7bfda62d1682 Signed-off-by: Maciej Fijalkowski Reviewed-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260828164918.451364-1-maciej.fijalkowski@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ipvlan/ipvlan_main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index 4939cf67b336..f29864db662a 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c @@ -848,7 +848,6 @@ static int ipvlan_device_event(struct notifier_block *unused, __ipvlan_link_delete(net, ipvlan->dev, &lst_kill); } - unregister_netdevice_many(&lst_kill); break; } case NETDEV_FEAT_CHANGE: @@ -899,6 +898,9 @@ static int ipvlan_device_event(struct notifier_block *unused, mutex_unlock(&port->pnodes_lock); + /* Avoid invoking nested netdevice notifiers under pnodes_lock. */ + unregister_netdevice_many(&lst_kill); + ipvlan_port_put(port); return ret; From aab55360fa11a2c054798a484ac67ad606f563e4 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Thu, 27 Aug 2026 22:42:31 -0600 Subject: [PATCH 0209/2366] watchdog: sunxi_wdt: preserve boot-enabled watchdog sunxi_wdt_probe() unconditionally stops the watchdog even when firmware left it running. This opens an unprotected interval during boot and prevents CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED from taking over the active watchdog. Detect an enabled watchdog and decode its programmed interval. Preserve representable timeouts, and round the 0.5-second interval up to the minimum representable one-second timeout. Use the configured timeout for reserved interval encodings. Set the Linux reset mode and ping the watchdog without clearing its enable bit, then mark it hardware-running before registration so the watchdog core services it until userspace takes control. Leave disabled watchdogs untouched. Fixes: d00680ed0026 ("watchdog: sunxi: New watchdog driver for Allwinner A10/A13") Cc: stable@vger.kernel.org Signed-off-by: James Hilliard Link: https://patch.msgid.link/20260827-submit-sunxi-wdt-boot-enabled-v1-v2-1-610d37dccc97@gmail.com Signed-off-by: Guenter Roeck --- drivers/watchdog/sunxi_wdt.c | 45 +++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/sunxi_wdt.c b/drivers/watchdog/sunxi_wdt.c index b6c761acc3de..3db34524ed13 100644 --- a/drivers/watchdog/sunxi_wdt.c +++ b/drivers/watchdog/sunxi_wdt.c @@ -128,6 +128,38 @@ static int sunxi_wdt_ping(struct watchdog_device *wdt_dev) return 0; } +static bool sunxi_wdt_is_running(struct watchdog_device *wdt_dev) +{ + struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); + const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs; + + return readl(sunxi_wdt->wdt_base + regs->wdt_mode) & WDT_MODE_EN; +} + +static unsigned int sunxi_wdt_get_timeout(struct watchdog_device *wdt_dev) +{ + struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); + const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs; + unsigned int timeout; + u32 interval; + + interval = readl(sunxi_wdt->wdt_base + regs->wdt_mode); + interval >>= regs->wdt_timeout_shift; + interval &= WDT_TIMEOUT_MASK; + /* Round the 0.5-second interval up to the minimum representable timeout. */ + if (!interval) + return WDT_MIN_TIMEOUT; + + for (timeout = WDT_MIN_TIMEOUT; + timeout < ARRAY_SIZE(wdt_timeout_map); timeout++) { + if (wdt_timeout_map[timeout] == interval) + return timeout; + } + + /* Reserved interval encoding. */ + return 0; +} + static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev, unsigned int timeout) { @@ -259,6 +291,7 @@ static int sunxi_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct sunxi_wdt_dev *sunxi_wdt; + unsigned int running_timeout; int err; sunxi_wdt = devm_kzalloc(dev, sizeof(*sunxi_wdt), GFP_KERNEL); @@ -286,7 +319,17 @@ static int sunxi_wdt_probe(struct platform_device *pdev) watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt); - sunxi_wdt_stop(&sunxi_wdt->wdt_dev); + if (sunxi_wdt_is_running(&sunxi_wdt->wdt_dev)) { + running_timeout = sunxi_wdt_get_timeout(&sunxi_wdt->wdt_dev); + if (running_timeout) + sunxi_wdt->wdt_dev.timeout = running_timeout; + + err = sunxi_wdt_start(&sunxi_wdt->wdt_dev); + if (err) + return err; + + set_bit(WDOG_HW_RUNNING, &sunxi_wdt->wdt_dev.status); + } watchdog_stop_on_reboot(&sunxi_wdt->wdt_dev); err = devm_watchdog_register_device(dev, &sunxi_wdt->wdt_dev); From e3eceb76515910746e6268c4e4ac1c07516ebd7b Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Thu, 27 Aug 2026 04:46:59 +0000 Subject: [PATCH 0210/2366] watchdog: msc313e: Fix NULL pointer dereference in PM callbacks msc313e_wdt_probe() doesn't set the driver data for the platform device. As a result, dev_get_drvdata() in msc313e_wdt_suspend() and msc313e_wdt_resume() will return NULL, leading to a NULL pointer dereference afterward. Set the platform device driver data in msc313e_wdt_probe(). Fixes: e9800b799464 ("watchdog: Add Mstar MSC313e WDT driver") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260827044700.554333-2-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index d962589e2c55..f69d66971c41 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -124,6 +124,7 @@ static int msc313e_wdt_probe(struct platform_device *pdev) set_bit(WDOG_HW_RUNNING, &priv->wdev.status); watchdog_set_drvdata(&priv->wdev, priv); + platform_set_drvdata(pdev, priv); watchdog_init_timeout(&priv->wdev, timeout, dev); watchdog_stop_on_reboot(&priv->wdev); From 51cfe54f815ae175c7d1126b983d4d7c89715004 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 4 Aug 2026 22:08:54 -0700 Subject: [PATCH 0211/2366] Input: rmi_smbus - fix out-of-bounds read in rmi_smb_write_block() When chunking writes into SMBus blocks in rmi_smb_write_block(), the loop calculates block_len using the original total length (len) instead of the remaining length (cur_len). If len is greater than 32 bytes (SMB_MAX_COUNT), block_len remains 32 for every iteration, even on the final partial chunk where fewer than 32 bytes remain. This causes smb_block_write() to read 32 bytes from the advanced data buffer pointer, reading past the end of the input buffer. Fix this by calculating block_len using cur_len and advancing the buffer and address pointers by block_len. Fixes: 82264d0cf7ae ("Input: synaptics-rmi4 - add SMBus support") Cc: stable@vger.kernel.org Reported-by: sashiko-bot@kernel.org Assisted-by: LLM Link: https://patch.msgid.link/anLFSMKSoKyyZ272@google.com Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_smbus.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/input/rmi4/rmi_smbus.c b/drivers/input/rmi4/rmi_smbus.c index 3160714a514a..8b61c2382297 100644 --- a/drivers/input/rmi4/rmi_smbus.c +++ b/drivers/input/rmi4/rmi_smbus.c @@ -140,7 +140,7 @@ static int rmi_smb_write_block(struct rmi_transport_dev *xport, u16 rmiaddr, u8 commandcode; struct rmi_smb_xport *rmi_smb = container_of(xport, struct rmi_smb_xport, xport); - int cur_len = (int)len; + size_t cur_len = len; mutex_lock(&rmi_smb->page_mutex); @@ -148,7 +148,7 @@ static int rmi_smb_write_block(struct rmi_transport_dev *xport, u16 rmiaddr, /* * break into 32 bytes chunks to write get command code */ - int block_len = min_t(int, len, SMB_MAX_COUNT); + int block_len = min_t(size_t, cur_len, SMB_MAX_COUNT); retval = rmi_smb_get_command_code(xport, rmiaddr, block_len, false, &commandcode); @@ -161,9 +161,9 @@ static int rmi_smb_write_block(struct rmi_transport_dev *xport, u16 rmiaddr, goto exit; /* prepare to write next block of bytes */ - cur_len -= SMB_MAX_COUNT; - databuff += SMB_MAX_COUNT; - rmiaddr += SMB_MAX_COUNT; + cur_len -= block_len; + databuff += block_len; + rmiaddr += block_len; } exit: mutex_unlock(&rmi_smb->page_mutex); From fe10579b6dc3f0dac61e51e1797cacbba5039ac2 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 5 Aug 2026 22:44:23 -0700 Subject: [PATCH 0212/2366] Input: synaptics-rmi4 - fix GPF in suspend and resume when unbound Transport drivers (such as rmi_i2c and rmi_spi) invoke rmi_driver_suspend() and rmi_driver_resume() on their child rmi_dev device during system power management events. However, transport drivers are fully registered and operational even if the physical RMI driver failed to bind or probe the rmi_dev device. When rmi_driver_suspend() or rmi_driver_resume() is called on an unbound rmi_dev, dev_get_drvdata() returns NULL. Calling rmi_disable_irq() or rmi_enable_irq() without driver data attached causes a NULL pointer dereference and General Protection Fault when attempting to lock data->enabled_mutex. Fix this by checking if driver data is attached to rmi_dev in rmi_driver_suspend() and rmi_driver_resume(), exiting early if no driver data is present. Fixes: 2b6a321da9a2 ("Input: synaptics-rmi4 - add support for Synaptics RMI4 devices") Reported-by: syzbot+09103639e39c989e3ed3@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=09103639e39c989e3ed3 Cc: stable@vger.kernel.org Assisted-by: LLM Link: https://patch.msgid.link/anQe8UiyUR4x0flD@google.com Signed-off-by: Dmitry Torokhov --- drivers/input/rmi4/rmi_driver.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c index 5d49a9021c7d..a349dfd17519 100644 --- a/drivers/input/rmi4/rmi_driver.c +++ b/drivers/input/rmi4/rmi_driver.c @@ -991,6 +991,15 @@ int rmi_driver_suspend(struct rmi_device *rmi_dev, bool enable_wake) { int retval; + /* + * Transport driver will try to suspend RMI device even if physical + * driver did not bind to the RMI device, because transport device + * (I2C, SPI) is fully registered and operational. Exit early if + * there is no driver data attached to the RMI device. + */ + if (!dev_get_drvdata(&rmi_dev->dev)) + return 0; + retval = rmi_suspend_functions(rmi_dev); if (retval) dev_warn(&rmi_dev->dev, "Failed to suspend functions: %d\n", @@ -1005,6 +1014,10 @@ int rmi_driver_resume(struct rmi_device *rmi_dev, bool clear_wake) { int retval; + /* Skip if not fully bound to RMI driver */ + if (!dev_get_drvdata(&rmi_dev->dev)) + return 0; + rmi_enable_irq(rmi_dev, clear_wake); retval = rmi_resume_functions(rmi_dev); From 173cb3147ba26449e6023df56f9bcd34ce6d1c3b Mon Sep 17 00:00:00 2001 From: Antoine Monnet Date: Sat, 15 Aug 2026 14:50:00 +0300 Subject: [PATCH 0213/2366] ASoC: tas2783-sdw: split a stereo stream across the two mono amps A board with two TAS2783 aggregated on one link renders mono: the two amplifiers are mono parts that each carry one channel of the stereo stream, but snd_sdw_params_to_config() hands every codec the full channel mask for playback. The pair stays in mirror mode, both amps render the same channel, and the other channel is never reproduced. Claim a single channel per amplifier instead. The index comes from the machine-assigned component name prefix rather than the SoundWire unique_id, which is board-specific: soc_sdw_ti_amp.c names the amplifiers tas2783-1..4. The bit that is set does not choose the side. sdw_compute_slave_ports() advances the payload offset by hweight32(ch_mask) and never looks at which bit it is, so a one-channel mask fixes mono by defeating mirror mode, and left and right then follow the amplifier's position in the codec order of the DAI link. That was measured: inverting the two masks between the amplifiers does not move the audio. On the boards this has been run on the codec order matches the prefix numbering, so the sides come out as the machine driver names them, but the mapping is not an ABI promise the bus allocator could honour. Reported-by: Robin Everaars Closes: https://lore.kernel.org/all/20260805183517.8665-1-robineveraars@pm.me/ Suggested-by: Robin Everaars Signed-off-by: Antoine Monnet Tested-by: Andrey Golovko Signed-off-by: Andrey Golovko Link: https://patch.msgid.link/20260815113000.4488-1-andrey.golovko@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2783-sdw.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index eaebb0ebbe0d..7fcf8214bf34 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -1001,6 +1001,31 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, /* SoundWire specific configuration */ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + + /* + * The two mono amps each render one channel of the stereo stream: + * snd_sdw_params_to_config() hands every codec the full mask for + * playback, which leaves the pair in mirror mode and one channel + * unreproduced. Claim a single channel instead, keyed off the + * machine-assigned component prefix rather than the SoundWire + * address, which is board-specific: soc_sdw_ti_amp.c names the amps + * tas2783-1..4. + * + * Which side an amp then renders does not follow from the bit that + * is set - sdw_compute_slave_ports() advances the payload offset by + * the popcount of ch_mask and never looks at which bit it is - but + * from the amp's position in the codec order of the DAI link, which + * on these boards matches the prefix numbering. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + params_channels(params) == 2 && component->name_prefix) { + const char *idx_str = strrchr(component->name_prefix, '-'); + unsigned long idx; + + if (idx_str && !kstrtoul(idx_str + 1, 10, &idx) && idx) + port_config.ch_mask = (idx & 1) ? BIT(0) : BIT(1); + } + /* port 1 for playback */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) port_config.num = 1; From 72bd92bd8190d7869ecb462649ca40f297822a33 Mon Sep 17 00:00:00 2001 From: Jason Andryuk Date: Tue, 25 Aug 2026 17:48:02 -0400 Subject: [PATCH 0214/2366] x86/amd_node: Avoid divide by zero on virtualized systems On a virtualized system, the number of nodes does not have a relationship to the number of roots. A Xen PVH dom0 can calculate roots_per_node as 0, which crashes with a divide by zero in: if (count++ % roots_per_node) because the underlying topology code on Xen ends up making num_nodes 2 and num_roots 1 and the integer division result is 0. The issue is seen with Xen, but it could affect other systems. Set roots_per_node to 1 in this case. Print a firmware bug when this is performed for non-virtualized systems. [ bp: Massage commit message. ] Fixes: 0a4b61d9c2e4 ("x86/amd_node: Fix AMD root device caching") Suggested-by: Borislav Petkov Signed-off-by: Jason Andryuk Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Yazen Ghannam Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260825214805.39148-2-jason.andryuk@amd.com --- arch/x86/kernel/amd_node.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/x86/kernel/amd_node.c b/arch/x86/kernel/amd_node.c index 0be01725a2a4..408b9fd48349 100644 --- a/arch/x86/kernel/amd_node.c +++ b/arch/x86/kernel/amd_node.c @@ -287,6 +287,11 @@ static int __init amd_smn_init(void) return -ENOMEM; roots_per_node = num_roots / num_nodes; + if (!roots_per_node) { + if (!cpu_feature_enabled(X86_FEATURE_HYPERVISOR)) + pr_warn(FW_BUG "Error detecting roots per node.\n"); + roots_per_node = 1; + } count = 0; node = 0; From ae9464c65e9d1ad4df4fed516cee9bca3bc614cc Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 25 Aug 2026 09:23:45 +0300 Subject: [PATCH 0215/2366] perf symbol: Do not use debug file as the binary type dso__load() sets the binary type of a DSO to the type of the first symbol source found. For a DSO with a separate debug file linked via .gnu-debuglink, that is DSO_BINARY_TYPE__DEBUGLINK, which makes dso__get_filename() return the name of the debug file instead of the file that was actually executed. Consumers that need to read instruction bytes, such as Intel PT decoding in 'perf script', then read from the debug file and produce wrong instructions. Prefer DSO_BINARY_TYPE__BUILD_ID_CACHE, and otherwise DSO_BINARY_TYPE__SYSTEM_PATH_DSO, over debug-only types, which restores the behaviour of using a file that contains the executed instructions. This is a workaround. Properly separating the binary file used for instructions from the file used for debug symbols is left for later. Example: Create a shared object with a separate .gnu_debuglink debug file. Note that 'objcopy --only-keep-debug' leaves .text as NOBITS, so instructions read from the debug file are zeros: # cat > foo.c << EOF unsigned long foo_work(unsigned long n) { unsigned long s = 0; for (unsigned long i = 0; i < n; i++) s = s * 31 + i; return s; } EOF # cat > main.c << EOF #include unsigned long foo_work(unsigned long n); int main(void) { printf("%lu\n", foo_work(1000)); return 0; } EOF # gcc -g -O2 -shared -fPIC -o libfoo.so foo.c # gcc -g -O2 -o main main.c -L. -lfoo -Wl,-rpath,'$ORIGIN' # objcopy --only-keep-debug libfoo.so libfoo.so.debug # objcopy --strip-debug libfoo.so # objcopy --add-gnu-debuglink=libfoo.so.debug libfoo.so # perf record -e intel_pt//u ./main Note that branch samples must be requested, because it is the resolving of the branch target symbol that causes dso__load() to be called, and hence the binary type to be set, before the decoder walks the code. With '--itrace=e' alone, nothing loads symbols for libfoo.so, the binary type is left as DSO_BINARY_TYPE__NOT_FOUND, the correct file is read anyway, and no errors are reported either way. Before: # perf.before script --itrace=be 2>&1 | grep "instruction trace error" instruction trace error type 1 time 2350.467489498 cpu 9 pid 75634 tid 75634 ip 0x77d48480718f code 6: Trace doesn't match instruction instruction trace error type 1 time 2350.467489832 cpu 9 pid 75634 tid 75634 ip 0x77d484807341 code 6: Trace doesn't match instruction instruction trace error type 1 time 2350.467496412 cpu 9 pid 75634 tid 75634 ip 0x5b4de37a8074 code 6: Trace doesn't match instruction instruction trace error type 1 time 2350.467593393 cpu 9 pid 75634 tid 75634 ip 0x77d4848070d0 code 6: Trace doesn't match instruction instruction trace error type 1 time 2350.467593954 cpu 9 pid 75634 tid 75634 ip 0x77d4848075a8 code 6: Trace doesn't match instruction instruction trace error type 1 time 2350.467595728 cpu 9 pid 75634 tid 75634 ip 0x77d4848324de code 6: Trace doesn't match instruction 6 instruction trace errors After: # perf script --itrace=be 2>&1 | grep "instruction trace error" # Fixes: 5363c306787c8 ("perf symbol: Set binary_type of dso when loading") Reported-by: Todd Lipcon Closes: https://lore.kernel.org/all/CAGH6UiG=RJLqBU3kLu9XJciPyPO1HZkbAPERguVUMRuWQgqf=A@mail.gmail.com/ Signed-off-by: Adrian Hunter Signed-off-by: Namhyung Kim --- tools/perf/util/symbol.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 94f9c8faedda..3587ad243159 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -1947,7 +1947,16 @@ int dso__load(struct dso *dso, struct map *map) if (next_slot) { ss_pos++; - if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND) + /* + * The binary type is used to find the file containing + * the executed instructions, so prefer the types that + * refer to the actual object over debug-only files such + * as DSO_BINARY_TYPE__DEBUGLINK. + */ + if (dso__binary_type(dso) == DSO_BINARY_TYPE__NOT_FOUND || + symtab_type == DSO_BINARY_TYPE__BUILD_ID_CACHE || + (symtab_type == DSO_BINARY_TYPE__SYSTEM_PATH_DSO && + dso__binary_type(dso) != DSO_BINARY_TYPE__BUILD_ID_CACHE)) dso__set_binary_type(dso, symtab_type); if (syms_ss && runtime_ss) From e11811a552252740bd396ec38378e9570ee16578 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 26 Aug 2026 14:19:57 +0100 Subject: [PATCH 0216/2366] OPP: of: Fix potential multiplication overflow when calculating freq The multiplication be32_to_cpup(val++) * 1000 is performed using 32 bit unsigned integers and hence uses a 32 bit multiplication; this will overflow if be32_to_cpup(val++) is greater than 4294967 (which is very unlikely at present). The result is assigned to an unsigned long (which is a 64 bit value on 64 bit systems), so fix this potential overflow by casting the first operand of the multiplication to an unsigned int. Fixes: b496dfbc94ab ("PM / OPP: Initialize OPP table from device tree") Signed-off-by: Colin Ian King Signed-off-by: Viresh Kumar --- drivers/opp/of.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/opp/of.c b/drivers/opp/of.c index c02e20632fa6..9c4fd1f0e944 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -1039,7 +1039,7 @@ static int _of_add_opp_table_v1(struct device *dev, struct opp_table *opp_table) val = prop->value; while (nr) { - unsigned long freq = be32_to_cpup(val++) * 1000; + unsigned long freq = (unsigned long)be32_to_cpup(val++) * 1000; unsigned long volt = be32_to_cpup(val++); struct dev_pm_opp_data data = { .freq = freq, From a5096d4927d1eb607d51a7342a7e7591a3838c19 Mon Sep 17 00:00:00 2001 From: Sumeet Pawnikar Date: Sat, 29 Aug 2026 19:19:24 +0530 Subject: [PATCH 0217/2366] opp: Use %pe to print symbolic error name Replace PTR_ERR() and %ld with %pe and pass the original pointer directly to dev_dbg(), dev_warn(), dev_err() or pr_err(). The %pe format specifier prints a symbolic error name (e.g. -ENOMEM) when CONFIG_SYMBOLIC_ERRNAME is enabled, otherwise it falls back gracefully and prints the raw integer value. This makes error messages more readable without any functional change. Signed-off-by: Sumeet Pawnikar Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 24 ++++++++++++------------ drivers/opp/of.c | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index cd0e82dae776..1e3b80a1f88e 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -453,8 +453,8 @@ int dev_pm_opp_get_opp_count(struct device *dev) _find_opp_table(dev); if (IS_ERR(opp_table)) { - dev_dbg(dev, "%s: OPP table not found (%ld)\n", - __func__, PTR_ERR(opp_table)); + dev_dbg(dev, "%s: OPP table not found (%pe)\n", + __func__, opp_table); return PTR_ERR(opp_table); } @@ -611,8 +611,8 @@ _find_key(struct device *dev, unsigned long *key, int index, bool available, _find_opp_table(dev); if (IS_ERR(opp_table)) { - dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, - PTR_ERR(opp_table)); + dev_err(dev, "%s: OPP table not found (%pe)\n", __func__, + opp_table); return ERR_CAST(opp_table); } @@ -722,8 +722,8 @@ struct dev_pm_opp *dev_pm_opp_find_key_exact(struct device *dev, struct opp_table *opp_table __free(put_opp_table) = _find_opp_table(dev); if (IS_ERR(opp_table)) { - dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, - PTR_ERR(opp_table)); + dev_err(dev, "%s: OPP table not found (%pe)\n", __func__, + opp_table); return ERR_CAST(opp_table); } @@ -1036,8 +1036,8 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg, /* Regulator not available for device */ if (IS_ERR(reg)) { - dev_dbg(dev, "%s: regulator not available: %ld\n", __func__, - PTR_ERR(reg)); + dev_dbg(dev, "%s: regulator not available: %pe\n", __func__, + reg); return 0; } @@ -1448,8 +1448,8 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) temp_freq = freq; opp = _find_freq_ceil(opp_table, &temp_freq); if (IS_ERR(opp)) { - dev_err(dev, "%s: failed to find OPP for freq %lu (%ld)\n", - __func__, freq, PTR_ERR(opp)); + dev_err(dev, "%s: failed to find OPP for freq %lu (%pe)\n", + __func__, freq, opp); return PTR_ERR(opp); } @@ -2869,8 +2869,8 @@ static int _opp_set_availability(struct device *dev, unsigned long freq, struct dev_pm_opp *opp __free(put_opp) = ERR_PTR(-ENODEV), *tmp_opp; if (IS_ERR(opp_table)) { - dev_warn(dev, "%s: Device OPP not found (%ld)\n", __func__, - PTR_ERR(opp_table)); + dev_warn(dev, "%s: Device OPP not found (%pe)\n", __func__, + opp_table); return PTR_ERR(opp_table); } diff --git a/drivers/opp/of.c b/drivers/opp/of.c index 9c4fd1f0e944..2f3bbde9a9e1 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -1345,8 +1345,8 @@ int of_get_required_opp_performance_state(struct device_node *np, int index) _find_table_of_opp_np(required_np); if (IS_ERR(opp_table)) { - pr_err("%s: Failed to find required OPP table %pOF: %ld\n", - __func__, np, PTR_ERR(opp_table)); + pr_err("%s: Failed to find required OPP table %pOF: %pe\n", + __func__, np, opp_table); return PTR_ERR(opp_table); } From aadea57f532882d8bab444646863c7ef8a778ff1 Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Tue, 25 Aug 2026 17:47:33 +0800 Subject: [PATCH 0218/2366] perf powerpc-vpadtl: Fix raw_size of DTL samples In powerpc_vpadtl_sample(), raw_data of the synthetic sample points to a struct powerpc_vpadtl_entry (48 bytes), but raw_size is set to sizeof(record). record is a struct powerpc_vpadtl_entry pointer, so sizeof(record) is the size of the pointer (8 bytes on 64-bit) rather than the size of the record itself. As a result, consumers that bound their access to raw_data by raw_size only see or copy the first 8 bytes of each DTL entry instead of the full record. Use sizeof(*record) so that raw_size reflects the actual length of the raw data. Fixes: 8644834a482a ("perf powerpc: Process the DTL entries in queue and deliver samples") Signed-off-by: Wang Yan Reviewed-by: Athira Rajeev Reviewed-by: Arnaldo Carvalho de Melo Signed-off-by: Namhyung Kim --- tools/perf/util/powerpc-vpadtl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/perf/util/powerpc-vpadtl.c b/tools/perf/util/powerpc-vpadtl.c index 710f3093f3f9..af6783cfdb53 100644 --- a/tools/perf/util/powerpc-vpadtl.c +++ b/tools/perf/util/powerpc-vpadtl.c @@ -196,7 +196,7 @@ static int powerpc_vpadtl_sample(struct powerpc_vpadtl_entry *record, sample.cpumode = PERF_RECORD_MISC_KERNEL; sample.time = save; sample.raw_data = record; - sample.raw_size = sizeof(record); + sample.raw_size = sizeof(*record); event.sample.header.type = PERF_RECORD_SAMPLE; event.sample.header.misc = sample.cpumode; event.sample.header.size = sizeof(struct perf_event_header); From a1530ef451f53b63dcf4a2805a5524b590a146ad Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 18 Aug 2026 10:09:33 +0800 Subject: [PATCH 0219/2366] media: rppx1: describe the MAIN_POST white balance gains block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rppx1_ext_params_blocks_info[] is indexed by block type and is built with designated initialisers, so a block type without an entry is left as a zero-sized hole. RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST is the only such hole: a block reporting that type and a size of zero passes v4l2_isp_params_validate_buffer(), which then has nothing to advance the walk with. Describe the block, so the array covers all block types the uAPI defines. The MAIN_POST white balance gains module is probed and started with the rest of the POST pipe, and RPPX1_PARAMS_MAX_SIZE already reserves room for all three white balance gains blocks. The block is not dispatched by rppx1_params(), in line with the other described blocks that have no users yet. Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Signed-off-by: Linmao Li Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- drivers/media/platform/dreamchip/rppx1/rpp_params.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c index a75a27a8afd0..5e6727d58946 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c @@ -25,6 +25,7 @@ rppx1_ext_params_blocks_info[] = { RPPX1_PARAMS_BLOCK_INFO(LSC_PRE2, lsc), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE1, awbg), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE2, awbg), + RPPX1_PARAMS_BLOCK_INFO(AWBG_POST, awbg), RPPX1_PARAMS_BLOCK_INFO(CCOR_POST, ccor), RPPX1_PARAMS_BLOCK_INFO(HIST_PRE1, hist), RPPX1_PARAMS_BLOCK_INFO(HIST_PRE2, hist), From e04ffff543db06308a8100f6aa3a65aebcd9834c Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 18 Aug 2026 17:59:29 +0800 Subject: [PATCH 0220/2366] media: rppx1: bls: read the raw pattern from the PRE2 acquisition module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rppx1_bls_swap_regs() gets the Bayer pattern from the acquisition module. The PRE1 path uses pre1.acq, but the PRE2 path mistakenly uses pre2.bls. The BLS module does not store a Bayer pattern, so PRE2 would read zero (RGGB) and map fixed black levels to the wrong colour registers. PRE2 is not started or dispatched yet, so the bug is currently latent. Read the pattern from pre2.acq, as the PRE1 path does, so that enabling PRE2 does not start out with the wrong register mapping. Signed-off-by: Linmao Li Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- drivers/media/platform/dreamchip/rppx1/rppx1_bls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c b/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c index 01a61db279bf..71c5561457d5 100644 --- a/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c +++ b/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c @@ -70,7 +70,7 @@ rppx1_bls_swap_regs(struct rpp_module *mod, const u32 input[4], u32 output[4]) /* Swap to pattern used in our path, PRE1 or PRE2. */ struct rpp_module *acq = mod == &mod->rpp->pre1.bls ? - &mod->rpp->pre1.acq : &mod->rpp->pre2.bls; + &mod->rpp->pre1.acq : &mod->rpp->pre2.acq; enum rpp_raw_pattern pattern = acq->info.acq.raw_pattern; for (unsigned int i = 0; i < 4; ++i) From b12e20c6ac156a307acdf0545432eb3b6cb41f8f Mon Sep 17 00:00:00 2001 From: Ahmet Eray Karadag Date: Mon, 15 Dec 2025 06:14:34 +0300 Subject: [PATCH 0221/2366] adfs: fix memory leak in sb->s_fs_info Syzbot reported a memory leak in adfs during the mount process. The issue arises because the ownership of the allocated (struct adfs_sb_info) is transferred from the filesystem context to the superblock via sget_fc(). This function sets fc->s_fs_info to NULL after the transfer. The ADFS filesystem previously used the default kill_block_super for superblock destruction. This helper performs generic cleanup but does not free the private sb->s_fs_info data. Since fc->s_fs_info is set to NULL during the transfer, the standard context cleanup (adfs_free_fc) also skips freeing this memory. As a result, if the superblock is destroyed, the allocated struct adfs_sb_info is leaked. Fix this by implementing a custom .kill_sb callback (adfs_kill_sb) that explicitly frees sb->s_fs_info before invoking the generic kill_block_super. Reported-by: syzbot+1c70732df5fd4f0e4fbb@syzkaller.appspotmail.com Signed-off-by: Ahmet Eray Karadag Link: https://patch.msgid.link/20251215031433.182205-2-eraykrdg1@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/adfs/super.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/fs/adfs/super.c b/fs/adfs/super.c index a4cd0a5159dd..888aa81a6b39 100644 --- a/fs/adfs/super.c +++ b/fs/adfs/super.c @@ -92,10 +92,7 @@ static int adfs_checkdiscrecord(struct adfs_discrecord *dr) static void adfs_put_super(struct super_block *sb) { - struct adfs_sb_info *asb = ADFS_SB(sb); - adfs_free_map(sb); - kfree_rcu(asb, rcu); } static int adfs_show_options(struct seq_file *seq, struct dentry *root) @@ -365,7 +362,7 @@ static int adfs_fill_super(struct super_block *sb, struct fs_context *fc) ret = -EINVAL; } if (ret) - goto error; + return ret; /* set up enough so that we can read an inode */ sb->s_op = &adfs_sops; @@ -406,15 +403,9 @@ static int adfs_fill_super(struct super_block *sb, struct fs_context *fc) if (!sb->s_root) { adfs_free_map(sb); adfs_error(sb, "get root inode failed\n"); - ret = -EIO; - goto error; + return -EIO; } return 0; - -error: - sb->s_fs_info = NULL; - kfree(asb); - return ret; } static int adfs_get_tree(struct fs_context *fc) @@ -465,10 +456,19 @@ static int adfs_init_fs_context(struct fs_context *fc) return 0; } +static void adfs_kill_sb(struct super_block *sb) +{ + struct adfs_sb_info *asb = ADFS_SB(sb); + + kill_block_super(sb); + + kfree_rcu(asb, rcu); +} + static struct file_system_type adfs_fs_type = { .owner = THIS_MODULE, .name = "adfs", - .kill_sb = kill_block_super, + .kill_sb = adfs_kill_sb, .fs_flags = FS_REQUIRES_DEV, .init_fs_context = adfs_init_fs_context, .parameters = adfs_param_spec, From 077304dfa7d1c641fbc10310c831ba8505887bd2 Mon Sep 17 00:00:00 2001 From: Will Porter Date: Thu, 27 Aug 2026 18:21:38 -0500 Subject: [PATCH 0222/2366] ALSA: usb-audio: Add PM guard to Studio 1810c controls The Studio 1810c control callbacks issue vendor transfers without preventing runtime suspend or disconnect. A transfer attempted after runtime suspend can fail because the USB device cannot accept submissions. Take snd_usb_lock before the driver data and USB mutexes in both callbacks. This resumes the device before either the state read or control write. It also prevents disconnect cleanup from racing the complete operation. Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Will Porter Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260827232143.149197-2-mrwillporter@gmail.com --- sound/usb/mixer_s1810c.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/usb/mixer_s1810c.c b/sound/usb/mixer_s1810c.c index 2e5a8d37ec57..bdb5e3aaff3b 100644 --- a/sound/usb/mixer_s1810c.c +++ b/sound/usb/mixer_s1810c.c @@ -474,6 +474,10 @@ snd_s1810c_switch_get(struct snd_kcontrol *kctl, u32 state = 0; int ret; + CLASS(snd_usb_lock, pm)(mixer->chip); + if (pm.err < 0) + return -EIO; + guard(mutex)(&private->data_mutex); ret = snd_s1810c_get_switch_state(mixer, kctl, &state); if (ret < 0) @@ -504,6 +508,10 @@ snd_s1810c_switch_set(struct snd_kcontrol *kctl, u32 newval = 0; int ret = 0; + CLASS(snd_usb_lock, pm)(mixer->chip); + if (pm.err < 0) + return -EIO; + guard(mutex)(&private->data_mutex); ret = snd_s1810c_get_switch_state(mixer, kctl, &curval); if (ret < 0) From 9e6c9d7531de7c6da66431a54d3c89f9514da937 Mon Sep 17 00:00:00 2001 From: Will Porter Date: Thu, 27 Aug 2026 18:21:39 -0500 Subject: [PATCH 0223/2366] ALSA: usb-audio: Add PM guards to US-16x08 transfers The TASCAM control helpers submit vendor requests without preventing runtime suspend or disconnect. This affects mixer writes and the volatile meter path. Protect both send and receive helpers with snd_usb_lock. Acquire the PM guard before chip->mutex in the receive path so autoresume cannot invert the mutex order. Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Will Porter Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260827232143.149197-3-mrwillporter@gmail.com --- sound/usb/mixer_us16x08.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c index ebff185cbd2c..14fb1ad764a7 100644 --- a/sound/usb/mixer_us16x08.c +++ b/sound/usb/mixer_us16x08.c @@ -151,6 +151,9 @@ static const char *const route_names[] = { static int snd_us16x08_recv_urb(struct snd_usb_audio *chip, unsigned char *buf, int size) { + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return -EIO; guard(mutex)(&chip->mutex); snd_usb_ctl_msg(chip->dev, @@ -165,6 +168,10 @@ static int snd_us16x08_recv_urb(struct snd_usb_audio *chip, */ static int snd_us16x08_send_urb(struct snd_usb_audio *chip, char *buf, int size) { + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return -EIO; + return snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), SND_US16X08_URB_REQUEST, SND_US16X08_URB_REQUESTTYPE, 0, 0, buf, size); From 103da4a7bca6bc6ae263c5e4e3053fa166691731 Mon Sep 17 00:00:00 2001 From: Will Porter Date: Thu, 27 Aug 2026 18:21:40 -0500 Subject: [PATCH 0224/2366] ALSA: usb-audio: Add PM guard to Scarlett meter reads The Scarlett Gen 1 meter callback reads the device without preventing runtime suspend or disconnect. The transfer can fail when userspace polls the volatile control after the device suspends. Hold snd_usb_lock across the meter request. This matches the guarded Forte and common mixer control paths in this file. Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Will Porter Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260827232143.149197-4-mrwillporter@gmail.com --- sound/usb/mixer_scarlett.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c index 673eb8d8724d..369968565c19 100644 --- a/sound/usb/mixer_scarlett.c +++ b/sound/usb/mixer_scarlett.c @@ -707,6 +707,10 @@ static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, int idx = snd_usb_ctrl_intf(elem->head.mixer->hostif) | (elem->head.id << 8); int err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return -EIO; + err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_MEM, From de018804734757fcb7b16156b5fb297129f9ca99 Mon Sep 17 00:00:00 2001 From: Will Porter Date: Thu, 27 Aug 2026 18:21:41 -0500 Subject: [PATCH 0225/2366] ALSA: usb-audio: Guard Scarlett2 protocol transfers Scarlett2 controls and hwdep operations reach the proprietary USB transport without preventing runtime suspend or disconnect. Protect the central request-and-response helper. One reference then covers the command, acknowledgment wait, and response. The runtime and system resume hook submits only the notification URB. It takes no protocol mutex. Thus, the transport guard does not invert the existing data_mutex or usb_mutex order. Keep the suspend-time config save on the unguarded helper because it runs inside the USB suspend callback. This change protects each USB transaction. It does not hold a runtime-PM reference across the asynchronous flash-erase interval. Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Will Porter Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260827232143.149197-5-mrwillporter@gmail.com --- sound/usb/mixer_scarlett2.c | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index 502854cc9f9f..ed5fe746d438 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -2603,9 +2603,9 @@ static int scarlett2_usb_rx(struct usb_device *dev, int interface, } /* Send a proprietary format request to the Scarlett interface */ -static int scarlett2_usb( - struct usb_mixer_interface *mixer, u32 cmd, - void *req_data, u16 req_size, void *resp_data, u16 resp_size) +static int scarlett2_usb_nopm(struct usb_mixer_interface *mixer, u32 cmd, + void *req_data, u16 req_size, + void *resp_data, u16 resp_size) { struct scarlett2_data *private = mixer->private_data; struct usb_device *dev = mixer->chip->dev; @@ -2713,6 +2713,18 @@ static int scarlett2_usb( return err; } +static int scarlett2_usb(struct usb_mixer_interface *mixer, u32 cmd, + void *req_data, u16 req_size, + void *resp_data, u16 resp_size) +{ + CLASS(snd_usb_lock, pm)(mixer->chip); + if (pm.err < 0) + return -EIO; + + return scarlett2_usb_nopm(mixer, cmd, req_data, req_size, + resp_data, resp_size); +} + /* Send a USB message to get data; result placed in *buf */ static int scarlett2_usb_get( struct usb_mixer_interface *mixer, @@ -3020,9 +3032,21 @@ static int scarlett2_usb_set_config_buf( /* Send SCARLETT2_USB_DATA_CMD SCARLETT2_USB_CONFIG_SAVE */ static void scarlett2_config_save(struct usb_mixer_interface *mixer) { - int err; + __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE); + int err = scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD, + &req, sizeof(req), NULL, 0); + + if (err < 0) + usb_audio_err(mixer->chip, "config save failed: %d\n", err); +} + +/* The USB suspend callback must not acquire another PM reference. */ +static void scarlett2_config_save_nopm(struct usb_mixer_interface *mixer) +{ + __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE); + int err = scarlett2_usb_nopm(mixer, SCARLETT2_USB_DATA_CMD, + &req, sizeof(req), NULL, 0); - err = scarlett2_usb_activate_config(mixer, SCARLETT2_USB_CONFIG_SAVE); if (err < 0) usb_audio_err(mixer->chip, "config save failed: %d\n", err); } @@ -8639,7 +8663,7 @@ static void scarlett2_private_suspend(struct usb_mixer_interface *mixer) struct scarlett2_data *private = mixer->private_data; if (cancel_delayed_work_sync(&private->work)) - scarlett2_config_save(private->mixer); + scarlett2_config_save_nopm(private->mixer); scarlett2_cleanup_urb(mixer); } From 9d4ae593fc578f6c88ce48a8b974e306de159e99 Mon Sep 17 00:00:00 2001 From: Will Porter Date: Thu, 27 Aug 2026 18:21:42 -0500 Subject: [PATCH 0226/2366] ALSA: usb-audio: Add PM guards to RME Digiface controls The RME Digiface status and register helpers issue vendor requests without preventing runtime suspend or disconnect. The volatile status controls can repeatedly reach these unguarded paths while userspace polls them. Protect both helpers with snd_usb_lock. All Digiface get and put callbacks then resume the device and hold the disconnect reference across their transfer. Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Will Porter Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260827232143.149197-6-mrwillporter@gmail.com --- sound/usb/mixer_quirks.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index a1f5592cc5d5..fc622eb95dc5 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -3480,6 +3480,10 @@ static int snd_rme_digiface_write_reg(struct snd_kcontrol *kcontrol, int item, u struct usb_device *dev = chip->dev; int err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return -EIO; + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), item, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, @@ -3499,6 +3503,10 @@ static int snd_rme_digiface_read_status(struct snd_kcontrol *kcontrol, u32 statu __le32 buf[4] = {}; int err; + CLASS(snd_usb_lock, pm)(chip); + if (pm.err < 0) + return -EIO; + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), RME_DIGIFACE_READ_STATUS, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, From d476d5995c8a146b03e56eb60940a0ac3524a655 Mon Sep 17 00:00:00 2001 From: Will Porter Date: Thu, 27 Aug 2026 18:21:43 -0500 Subject: [PATCH 0227/2366] ALSA: usb-audio: Guard FCP protocol transfers FCP meter and hwdep operations issue control transfers without preventing runtime suspend or disconnect. Protect the central request-and-response transport. One reference then covers the command, acknowledgment wait, and response. The initial step-zero request bypasses that transport. Hold an outer reference across the complete initialization sequence so the device stays active through step zero, notification-URB setup, and both initialization commands. The central transport keeps its guard for calls outside initialization; the existing active counter balances the nested calls. FCP has no private resume callback. Its suspend callback only removes the notification URB. Taking the initialization and transport guards under the existing protocol mutex causes no resume-side lock inversion. Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Will Porter Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260827232143.149197-7-mrwillporter@gmail.com --- sound/usb/fcp.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/usb/fcp.c b/sound/usb/fcp.c index 5fc2131b4561..68bb7eabf107 100644 --- a/sound/usb/fcp.c +++ b/sound/usb/fcp.c @@ -191,6 +191,10 @@ static int fcp_usb(struct usb_mixer_interface *mixer, u32 opcode, const int max_retries = 5; int err; + CLASS(snd_usb_lock, pm)(mixer->chip); + if (pm.err < 0) + return -EIO; + if (!private->urb) return -ENODEV; @@ -1026,6 +1030,10 @@ static int fcp_init(struct usb_mixer_interface *mixer, struct usb_device *dev = mixer->chip->dev; int err; + CLASS(snd_usb_lock, pm)(mixer->chip); + if (pm.err < 0) + return -EIO; + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), FCP_USB_REQ_STEP0, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, From f18e8774f4d3137fa0a5fb8ffa83d59a719666d8 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 27 Aug 2026 14:42:54 +0100 Subject: [PATCH 0228/2366] netfs: Fix uninitialized return value in netfs_unbuffered_write() If preparation of the first subrequest fails, netfs_unbuffered_write() exits its loop before ret is initialized. The empty-iterator check can do the same. For synchronous writes, netfs_unbuffered_write_iter_locked() may then return an unrelated error instead of wreq->error. This is reachable through CIFS if cifs_prepare_write() fails to reopen the file or obtain credits. Initialize ret to 0 so the caller returns wreq->error if no data was written, or the number of bytes already written otherwise. Found with Clang's -Wconditional-uninitialized. Fixes: a0b4c7a49137e ("netfs: Fix unbuffered/DIO writes to dispatch subrequests in strict sequence") Cc: stable@vger.kernel.org Signed-off-by: Karl Mehltretter Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-2-dhowells@redhat.com Acked-by: Paulo Alcantara Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/direct_write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index c16fbad286a1..b04019097ab8 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -95,7 +95,7 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq) { struct netfs_io_subrequest *subreq = NULL; struct netfs_io_stream *stream = &wreq->io_streams[0]; - int ret; + int ret = 0; _enter("%llx", wreq->len); From c753a33664e4e86246f7491a93d9a77c1a673b5d Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:42:55 +0100 Subject: [PATCH 0229/2366] netfs: Fix unbuffered/DIO write partial transfer error return Fix unbuffered/DIO write to return the amount of data transferred in preference to an error if a partial transfer has been achieved, and to prefer an error stashed in the request over the one returned by netfs_unbuffered_write() (likely -EINTR or -ERESTARTSYS). Fixes: a0b4c7a49137e ("netfs: Fix unbuffered/DIO writes to dispatch subrequests in strict sequence") Link: https://sashiko.dev/#/patchset/20260824120224.504575-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-3-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/direct_write.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index b04019097ab8..544a4243fc59 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -139,13 +139,11 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq) if (test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) { retry = true; } else if (test_bit(NETFS_SREQ_FAILED, &subreq->flags)) { - ret = subreq->error; - wreq->error = ret; + wreq->error = subreq->error; netfs_see_subrequest(subreq, netfs_sreq_trace_see_failed); subreq = NULL; break; } - ret = 0; if (!retry) { netfs_unbuffered_write_collect(wreq, stream, subreq); @@ -288,11 +286,11 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter * ret = -EIOCBQUEUED; } else { ret = netfs_unbuffered_write(wreq); - if (ret < 0) { - _debug("begin = %zd", ret); - } else { + if (wreq->transferred) { iocb->ki_pos += wreq->transferred; - ret = wreq->transferred ?: wreq->error; + ret = wreq->transferred; + } else if (wreq->error) { + ret = wreq->error; } netfs_put_request(wreq, netfs_rreq_trace_put_complete); From 0bfe2571a6af653611860d0e24c4e4c83bae7a54 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:42:56 +0100 Subject: [PATCH 0230/2366] netfs: Fix error vs transferred passed to ->ki_complete() Fix netfs_unbuffered_write_done() to pass the amount written to ->ki_complete() rather than the error in the event of a partially complete transfer. Fixes: a0b4c7a49137e ("netfs: Fix unbuffered/DIO writes to dispatch subrequests in strict sequence") Link: https://sashiko.dev/#/patchset/20260824120224.504575-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-4-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/direct_write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index 544a4243fc59..f7d7e1b54653 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -51,7 +51,7 @@ static void netfs_unbuffered_write_done(struct netfs_io_request *wreq) wreq->iocb->ki_pos += written; if (wreq->iocb->ki_complete) { trace_netfs_rreq(wreq, netfs_rreq_trace_ki_complete); - wreq->iocb->ki_complete(wreq->iocb, wreq->error ?: written); + wreq->iocb->ki_complete(wreq->iocb, written ?: wreq->error); } wreq->iocb = VFS_PTR_POISON; } From 741416a8003b77e636dafade408f808d96ac3f47 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:42:57 +0100 Subject: [PATCH 0231/2366] netfs: Fix i_size update for partial transfer Fix netfs_unbuffered_write_done() to pass the amount written to netfs_update_i_size() in the event of a partial transfer that ends in an error. That said, it might be better for the filesystem to mark the inode data as invalid and recheck it in case something like a network error occurred that prevented the reply from the server from being received. Fixes: a0b4c7a49137e ("netfs: Fix unbuffered/DIO writes to dispatch subrequests in strict sequence") Link: https://sashiko.dev/#/patchset/20260824120224.504575-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-5-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/direct_write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index f7d7e1b54653..f33ccddaa826 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -21,7 +21,7 @@ static void netfs_unbuffered_write_done(struct netfs_io_request *wreq) /* Okay, declare that all I/O is complete. */ trace_netfs_rreq(wreq, netfs_rreq_trace_write_done); - if (!wreq->error) + if (wreq->transferred) netfs_update_i_size(ictx, &ictx->inode, wreq->start, wreq->transferred); if (wreq->origin == NETFS_DIO_WRITE && From 3c30087e27598d9d359763e8be9bd3017fe08348 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:42:58 +0100 Subject: [PATCH 0232/2366] netfs: Fix subreq ref leak Fix a subrequest ref leak in netfs_unbuffered_write() in the event that subreq->io_iter ends up zero length during preparation. Fixes: a0b4c7a49137e ("netfs: Fix unbuffered/DIO writes to dispatch subrequests in strict sequence") Link: https://sashiko.dev/#/patchset/20260824120224.504575-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-6-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/direct_write.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index f33ccddaa826..fbcfadb232ee 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -121,8 +121,14 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq) } iov_iter_truncate(&subreq->io_iter, wreq->len - wreq->transferred); - if (!iov_iter_count(&subreq->io_iter)) + if (!iov_iter_count(&subreq->io_iter)) { + pr_warn("netfs: Unexpected zero-length iterator R=%08x\n", + wreq->debug_id); + __set_bit(NETFS_SREQ_FAILED, &subreq->flags); + netfs_write_subrequest_terminated(subreq, -EIO); + wreq->error = -EIO; break; + } subreq->len = netfs_limit_iter(&subreq->io_iter, 0, stream->sreq_max_len, From 8fb45a934661419c04a44d4cfea1e0df7dcf2805 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Thu, 27 Aug 2026 14:42:59 +0100 Subject: [PATCH 0233/2366] netfs: break unbuffered write when netfs_alloc_subrequest() fails syzbot reported a null-ptr-deref below [1] following a fault injection in netfs_alloc_subrequest(). [0] When netfs_alloc_subrequest() fails, subreq is NULL. Later, netfs_prepare_write() tries to initialize members of subreq(e.g., source), the issue in [1] is triggered. Let's handle the error of netfs_prepare_write() properly. [0] FAULT_INJECTION: forcing a failure. name failslab, interval 1, probability 0, space 0, times 0 Call Trace: netfs_alloc_subrequest+0x116/0x3f0 netfs_prepare_write+0x76/0x7b0 netfs_unbuffered_write+0x75c/0x2020 netfs_unbuffered_write_iter_locked+0x7d6/0xa80 netfs_unbuffered_write_iter+0x442/0x720 v9fs_file_write_iter+0xbf/0x100 vfs_write+0x6ac/0x1050 [1] KASAN: null-ptr-deref in range [0x00000000000000a8-0x00000000000000af] RIP: 0010:netfs_prepare_write+0xbc/0x7b0 fs/netfs/write_issue.c:173 Call Trace: netfs_unbuffered_write+0x75c/0x2020 fs/netfs/direct_write.c:111 netfs_unbuffered_write_iter_locked+0x7d6/0xa80 fs/netfs/direct_write.c:290 netfs_unbuffered_write_iter+0x442/0x720 fs/netfs/direct_write.c:382 v9fs_file_write_iter+0xbf/0x100 fs/9p/vfs_file.c:409 new_sync_write fs/read_write.c:595 [inline] Fixes: 288ace2f57c9 ("netfs: New writeback implementation") Reported-by: syzbot+6a13fc77eb6f0802be2d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6a13fc77eb6f0802be2d Tested-by: syzbot+6a13fc77eb6f0802be2d@syzkaller.appspotmail.com Signed-off-by: Edward Adam Davis Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-7-dhowells@redhat.com Acked-by: Paulo Alcantara Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/direct_write.c | 5 +++++ fs/netfs/write_issue.c | 2 ++ 2 files changed, 7 insertions(+) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index fbcfadb232ee..2361277416c7 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -110,6 +110,11 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq) if (!subreq) { netfs_prepare_write(wreq, stream, wreq->start + wreq->transferred); subreq = stream->construct; + if (!subreq) { + wreq->error = -ENOMEM; + ret = -ENOMEM; + break; + } stream->construct = NULL; } diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 2d9cfcd43658..851f6f93ad45 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -170,6 +170,8 @@ void netfs_prepare_write(struct netfs_io_request *wreq, rolling_buffer_make_space(&wreq->buffer, wreq->gfp); subreq = netfs_alloc_subrequest(wreq); + if (!subreq) + return; subreq->source = stream->source; subreq->start = start; subreq->stream_nr = stream->stream_nr; From fed0b33e6c584986ba70018ec9f9787a98216e64 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:43:00 +0100 Subject: [PATCH 0234/2366] netfs: Fix readahead synchronisation issues by loading all folios upfront There are some synchronisation issues that derive from the app thread adding more folios to the rolling buffer whilst the collector thread is looking at them or trying to clear them, such as determining the setting of front_folio_order when the next folio hasn't been added yet, The reason for the rolling buffer approach is that loading the buffer upfront and then dropping all the refs just acquired is quite a slow operation, and loading progressively allows some of the cost to be deferred until after at least some of the I/O is started. Instead, a better way is to load all the folios into the rolling buffer upfront - and then drop the refs later, once the I/O is in progress. (Even better would be for the refs not to be there at all.) Fix this by changing the rolling buffer loader to load all the folios selected by the VM for readahead upfront into the folio queue. The folio queue is allocated a batch worth at a time as we don't know how many folios are involved (the readahead_control struct, alas, has a page count, not a folio count). The folio refs acquired from readahead are then dropped in bulk once the first subrequest is dispatched as it's quite a slow operation. The collector waits for NETFS_RREQ_NEED_PUT_RA_REFS to be cleared so that it doesn't unlock folios before the xarray has been scanned for them. This simplifies the buffer handling later and isn't noticeably slower as the xarray doesn't need to be modified and the folios are all already pre-locked. Fixes: ee4cdf7ba857 ("netfs: Speed up buffered reading") Link: https://sashiko.dev/#/patchset/20260824120224.504575-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-8-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara (Red Hat) cc: Matthew Wilcox cc: netfs@lists.linux.dev cc: linux-mm@kvack.org cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 101 ++++++++++++++++++++------------- fs/netfs/internal.h | 1 + fs/netfs/misc.c | 19 +++++++ fs/netfs/read_collect.c | 7 +++ fs/netfs/read_retry.c | 7 +++ fs/netfs/rolling_buffer.c | 79 +++++++++++++++++--------- include/linux/netfs.h | 1 + include/linux/rolling_buffer.h | 6 +- include/trace/events/netfs.h | 3 + 9 files changed, 153 insertions(+), 71 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 7fdfa4f27e34..303fdce54fba 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -54,6 +54,42 @@ static void netfs_rreq_expand(struct netfs_io_request *rreq, } } +/* + * Drop the folio refs acquired from the readahead API. + */ +static void netfs_bulk_drop_ra_refs(struct netfs_io_request *rreq) +{ + struct folio_batch fbatch; + struct folio *folio; + pgoff_t nr_pages = DIV_ROUND_UP(rreq->len, PAGE_SIZE); + pgoff_t first = rreq->start / PAGE_SIZE; + XA_STATE(xas, &rreq->mapping->i_pages, first); + + folio_batch_init(&fbatch); + + rcu_read_lock(); + + xas_for_each(&xas, folio, first + nr_pages - 1) { + if (xas_retry(&xas, folio)) + continue; + + if (!folio_batch_add(&fbatch, folio)) + folio_batch_release(&fbatch); + } + + rcu_read_unlock(); + folio_batch_release(&fbatch); + trace_netfs_rreq(rreq, netfs_rreq_trace_ra_put_ref); + clear_bit_unlock(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags); + wake_up(&rreq->waitq); +} + +static void netfs_maybe_bulk_drop_ra_refs(struct netfs_io_request *rreq) +{ + if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) + netfs_bulk_drop_ra_refs(rreq); +} + /* * Begin an operation, and fetch the stored zero point value from the cookie if * available. @@ -74,12 +110,8 @@ static int netfs_begin_cache_read(struct netfs_io_request *rreq, struct netfs_in * * Returns the limited size if successful and -ENOMEM if insufficient memory * available. - * - * [!] NOTE: This must be run in the same thread as ->issue_read() was called - * in as we access the readahead_control struct. */ -static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq, - struct readahead_control *ractl) +static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq) { struct netfs_io_request *rreq = subreq->rreq; size_t rsize = subreq->len; @@ -87,30 +119,6 @@ static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq, if (subreq->source == NETFS_DOWNLOAD_FROM_SERVER) rsize = umin(rsize, rreq->io_streams[0].sreq_max_len); - if (ractl) { - /* If we don't have sufficient folios in the rolling buffer, - * extract a folioq's worth from the readahead region at a time - * into the buffer. Note that this acquires a ref on each page - * that we will need to release later - but we don't want to do - * that until after we've started the I/O. - */ - struct folio_batch put_batch; - - folio_batch_init(&put_batch); - while (rreq->submitted < subreq->start + rsize) { - ssize_t added; - - added = rolling_buffer_load_from_ra(&rreq->buffer, ractl, - &put_batch); - if (added < 0) { - folio_batch_release(&put_batch); - return added; - } - rreq->submitted += added; - } - folio_batch_release(&put_batch); - } - subreq->len = rsize; if (unlikely(rreq->io_streams[0].sreq_max_segs)) { size_t limit = netfs_limit_iter(&rreq->buffer.iter, 0, rsize, @@ -208,8 +216,7 @@ static void netfs_issue_read(struct netfs_io_request *rreq, * slicing up the region to be read according to available cache blocks and * network rsize. */ -static void netfs_read_to_pagecache(struct netfs_io_request *rreq, - struct readahead_control *ractl) +static void netfs_read_to_pagecache(struct netfs_io_request *rreq) { unsigned long long start = rreq->start; ssize_t size = rreq->len; @@ -288,7 +295,7 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq, break; issue: - slice = netfs_prepare_read_iterator(subreq, ractl); + slice = netfs_prepare_read_iterator(subreq); if (slice < 0) { ret = slice; netfs_cancel_read(subreq, ret); @@ -302,6 +309,7 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq, } netfs_issue_read(rreq, subreq); + netfs_maybe_bulk_drop_ra_refs(rreq); if (test_bit(NETFS_RREQ_PAUSE, &rreq->flags)) netfs_wait_for_paused_read(rreq); @@ -339,7 +347,8 @@ void netfs_readahead(struct readahead_control *ractl) { struct netfs_io_request *rreq; struct netfs_inode *ictx = netfs_inode(ractl->mapping->host); - unsigned long long start = readahead_pos(ractl); + ssize_t added; + uoff_t start = readahead_pos(ractl); size_t size = readahead_length(ractl); int ret; @@ -360,11 +369,23 @@ void netfs_readahead(struct readahead_control *ractl) netfs_rreq_expand(rreq, ractl); - rreq->submitted = rreq->start; - if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST, rreq->gfp) < 0) + /* Load the folios to be read into a bvecq chain. Note that this + * acquires a ref on each folio that we will need to release later - + * but we don't want to do that until after we've started the I/O. + */ + added = rolling_buffer_bulk_load_from_ra(&rreq->buffer, ractl, + rreq->debug_id, rreq->gfp); + if (added < 0) { + ret = added; goto cleanup_free; - netfs_read_to_pagecache(rreq, ractl); + } + __set_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags); + rreq->submitted = rreq->start + added; + rreq->cleaned_to = rreq->start; + + netfs_read_to_pagecache(rreq); + netfs_maybe_bulk_drop_ra_refs(rreq); return netfs_put_request(rreq, netfs_rreq_trace_put_return); cleanup_free: @@ -457,7 +478,7 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) iov_iter_bvec(&rreq->buffer.iter, ITER_DEST, bvec, i, rreq->len); rreq->submitted = rreq->start + flen; - netfs_read_to_pagecache(rreq, NULL); + netfs_read_to_pagecache(rreq); ret = netfs_wait_for_read(rreq); if (ret >= 0) { @@ -532,7 +553,7 @@ int netfs_read_folio(struct file *file, struct folio *folio) if (ret < 0) goto discard; - netfs_read_to_pagecache(rreq, NULL); + netfs_read_to_pagecache(rreq); ret = netfs_wait_for_read(rreq); netfs_put_request(rreq, netfs_rreq_trace_put_return); return ret < 0 ? ret : 0; @@ -689,7 +710,7 @@ int netfs_write_begin(struct netfs_inode *ctx, if (ret < 0) goto error_put; - netfs_read_to_pagecache(rreq, NULL); + netfs_read_to_pagecache(rreq); ret = netfs_wait_for_read(rreq); netfs_put_request(rreq, netfs_rreq_trace_put_return); if (ret < 0) @@ -754,7 +775,7 @@ int netfs_prefetch_for_write(struct file *file, struct folio *folio, if (ret < 0) goto error_put; - netfs_read_to_pagecache(rreq, NULL); + netfs_read_to_pagecache(rreq); ret = netfs_wait_for_read(rreq); netfs_put_request(rreq, netfs_rreq_trace_put_return); return ret < 0 ? ret : 0; diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 420ee7b26580..bd8b2d633f96 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -79,6 +79,7 @@ ssize_t netfs_wait_for_read(struct netfs_io_request *rreq); ssize_t netfs_wait_for_write(struct netfs_io_request *rreq); void netfs_wait_for_paused_read(struct netfs_io_request *rreq); void netfs_wait_for_paused_write(struct netfs_io_request *rreq); +void netfs_wait_for_put_ra_refs(struct netfs_io_request *rreq); /* * objects.c diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c index 5d554512ed23..f5c1c463f4ff 100644 --- a/fs/netfs/misc.c +++ b/fs/netfs/misc.c @@ -563,3 +563,22 @@ void netfs_wait_for_paused_write(struct netfs_io_request *rreq) { return netfs_wait_for_pause(rreq, netfs_write_collection); } + +/* + * Wait for the readahead-acquired refs to be put. + */ +void netfs_wait_for_put_ra_refs(struct netfs_io_request *rreq) +{ + DEFINE_WAIT(myself); + + for (;;) { + trace_netfs_rreq(rreq, netfs_rreq_trace_wait_put_ra_refs); + prepare_to_wait(&rreq->waitq, &myself, TASK_UNINTERRUPTIBLE); + if (!test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) + break; + schedule(); + } + + trace_netfs_rreq(rreq, netfs_rreq_trace_waited_put_ra_refs); + finish_wait(&rreq->waitq, &myself); +} diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 23660a590124..edf7cea7e2f9 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -118,6 +118,13 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, slot = 0; } + /* We have to wait for readahead refs to have been released before we + * can unlock any folios as the ref-dropper walks i_pages and the only + * thing preventing these folios from being removed is the folio lock. + */ + if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) + netfs_wait_for_put_ra_refs(rreq); + for (;;) { struct folio *folio; unsigned long long fpos, fend; diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c index 2b42758e01ec..dd463a485139 100644 --- a/fs/netfs/read_retry.c +++ b/fs/netfs/read_retry.c @@ -292,6 +292,13 @@ void netfs_unlock_abandoned_read_pages(struct netfs_io_request *rreq) { struct folio_queue *p; + /* We have to wait for readahead refs to have been released before we + * can unlock any folios as the ref-dropper walks i_pages and the only + * thing preventing these folios from being removed is the folio lock. + */ + if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) + netfs_wait_for_put_ra_refs(rreq); + for (p = rreq->buffer.tail; p; p = p->next) { for (int slot = 0; slot < folioq_count(p); slot++) { struct folio *folio = folioq_folio(p, slot); diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c index 8c0026836f9c..424e77a9a109 100644 --- a/fs/netfs/rolling_buffer.c +++ b/fs/netfs/rolling_buffer.c @@ -115,42 +115,65 @@ int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp) } /* - * Decant the list of folios to read into a rolling buffer. + * Decant the entire list of folios to read into a rolling buffer. */ -ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, - struct readahead_control *ractl, - struct folio_batch *put_batch) +ssize_t rolling_buffer_bulk_load_from_ra(struct rolling_buffer *roll, + struct readahead_control *ractl, + unsigned int rreq_id, gfp_t gfp) { struct folio_queue *fq; - struct page **vec; - int nr, ix, to; - ssize_t size = 0; + ssize_t loaded = 0; - if (rolling_buffer_make_space(roll, GFP_KERNEL) < 0) - return -ENOMEM; + while (ractl->_nr_pages - ractl->_batch_count > 0) { + unsigned int nr; - fq = roll->head; - vec = (struct page **)fq->vec.folios; - nr = __readahead_batch(ractl, vec + folio_batch_count(&fq->vec), - folio_batch_space(&fq->vec)); - ix = fq->vec.nr; - to = ix + nr; - fq->vec.nr = to; - for (; ix < to; ix++) { - struct folio *folio = folioq_folio(fq, ix); - unsigned int order = folio_order(folio); + /* Allocate a folioq to put some folios into and attach it to + * the rolling buffer. + */ + fq = netfs_folioq_alloc(rreq_id, gfp, + netfs_trace_folioq_make_space); + if (!fq) + goto nomem_unlock; + fq->prev = roll->head; + if (!roll->tail) + roll->tail = fq; + else + roll->head->next = fq; + roll->head = fq; - fq->orders[ix] = order; - size += PAGE_SIZE << order; - trace_netfs_folio(folio, netfs_folio_trace_read); - if (!folio_batch_add(put_batch, folio)) - folio_batch_release(put_batch); + /* Get a batch of folios and note their orders. */ + nr = __readahead_batch(ractl, (struct page **)fq->vec.folios, + folioq_nr_slots(fq)); + if (WARN_ON_ONCE(!nr)) + break; + fq->vec.nr = nr; + + for (int slot = 0; slot < nr; slot++) { + struct folio *folio = folioq_folio(fq, slot); + unsigned int order; + + order = folio_order(folio); + fq->orders[slot] = order; + loaded += PAGE_SIZE << order; + trace_netfs_folio(folio, netfs_folio_trace_read); + } } - WRITE_ONCE(roll->iter.count, roll->iter.count + size); - /* Store the counter after setting the slot. */ - smp_store_release(&roll->next_head_slot, to); - return size; + WRITE_ONCE(roll->iter.count, loaded); + iov_iter_folio_queue(&roll->iter, ITER_DEST, roll->tail, 0, 0, loaded); + return loaded; + +nomem_unlock: + for (fq = roll->tail; fq; fq = fq->next) { + for (int slot = 0; slot < folioq_count(fq); slot++) { + folio_unlock(fq->vec.folios[slot]); + folioq_mark(fq, slot); + } + } + rolling_buffer_clear(roll); + roll->head = NULL; + roll->tail = NULL; + return -ENOMEM; } /* diff --git a/include/linux/netfs.h b/include/linux/netfs.h index f837a501008c..5c538d0c5d79 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -278,6 +278,7 @@ struct netfs_io_request { #define NETFS_RREQ_FOLIO_COPY_TO_CACHE 10 /* Copy current folio to cache from read */ #define NETFS_RREQ_UPLOAD_TO_SERVER 11 /* Need to write to the server */ #define NETFS_RREQ_USE_IO_ITER 12 /* Use ->io_iter rather than ->i_pages */ +#define NETFS_RREQ_NEED_PUT_RA_REFS 17 /* Need to put the folio refs RA gave us */ #define NETFS_RREQ_USE_PGPRIV2 31 /* [DEPRECATED] Use PG_private_2 to mark * write to cache on read */ const struct netfs_request_ops *netfs_ops; diff --git a/include/linux/rolling_buffer.h b/include/linux/rolling_buffer.h index 9e5dad29669c..a97f7cfaacaa 100644 --- a/include/linux/rolling_buffer.h +++ b/include/linux/rolling_buffer.h @@ -45,9 +45,9 @@ struct rolling_buffer_snapshot { int rolling_buffer_init(struct rolling_buffer *roll, unsigned int rreq_id, unsigned int direction, gfp_t gfp); int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp); -ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, - struct readahead_control *ractl, - struct folio_batch *put_batch); +ssize_t rolling_buffer_bulk_load_from_ra(struct rolling_buffer *roll, + struct readahead_control *ractl, + unsigned int rreq_id, gfp_t gfp); ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *folio, unsigned int flags, gfp_t gfp); struct folio_queue *rolling_buffer_delete_spent(struct rolling_buffer *roll); diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 082cb03c6131..9bda9302be90 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -59,6 +59,7 @@ EM(netfs_rreq_trace_free, "FREE ") \ EM(netfs_rreq_trace_intr, "INTR ") \ EM(netfs_rreq_trace_ki_complete, "KI-CMPL") \ + EM(netfs_rreq_trace_ra_put_ref, "RA-PUT ") \ EM(netfs_rreq_trace_recollect, "RECLLCT") \ EM(netfs_rreq_trace_redirty, "REDIRTY") \ EM(netfs_rreq_trace_resubmit, "RESUBMT") \ @@ -70,9 +71,11 @@ EM(netfs_rreq_trace_unpause, "UNPAUSE") \ EM(netfs_rreq_trace_wait_ip, "WAIT-IP") \ EM(netfs_rreq_trace_wait_pause, "--PAUSED--") \ + EM(netfs_rreq_trace_wait_put_ra_refs, "WAIT-P-RA") \ EM(netfs_rreq_trace_wait_quiesce, "WAIT-QUIESCE") \ EM(netfs_rreq_trace_waited_ip, "DONE-IP") \ EM(netfs_rreq_trace_waited_pause, "--UNPAUSED--") \ + EM(netfs_rreq_trace_waited_put_ra_refs, "DONE-P-RA") \ EM(netfs_rreq_trace_waited_quiesce, "DONE-QUIESCE") \ EM(netfs_rreq_trace_wake_ip, "WAKE-IP") \ EM(netfs_rreq_trace_wake_queue, "WAKE-Q ") \ From 533203c4183123dad8ffecd694e7573a0ccd0da0 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:43:01 +0100 Subject: [PATCH 0235/2366] netfs: Mark folios with COPY_TO_CACHE whilst issuing subreqs Mark folios with NETFS_FOLIO_COPY_TO_CACHE whilst issuing subreqs rather than when collecting them. This means that the collector thread doesn't have to try and keep track of which subreqs contribute to which folios - and thus which folios will need to be copied to the cache because at least one byte wasn't in the cache. Instead, this is marked on the folios up front and the collector need only consider the folios. For PG_private_2-using filesystems, PG_private_2 is set instead of NETFS_FOLIO_COPY_TO_CACHE, but otherwise it works the same. The NETFS_RREQ_COPY_TO_CACHE is replaced with NETFS_RREQ_CANCEL_CACHING, which is now set if caching fails somewhere, thereby causing the collection thread to cancel the copy-to-cache marks on the remaining folios. Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-9-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara (Red Hat) cc: Matthew Wilcox cc: netfs@lists.linux.dev cc: linux-mm@kvack.org cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 61 +++++++++++++++++++++++++++++++- fs/netfs/internal.h | 1 + fs/netfs/read_collect.c | 67 ++++++++++++++++++++++-------------- fs/netfs/read_pgpriv2.c | 15 ++++---- fs/netfs/read_retry.c | 6 +++- include/linux/netfs.h | 2 +- include/trace/events/netfs.h | 6 ++-- 7 files changed, 121 insertions(+), 37 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 303fdce54fba..16d4db776f6a 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -211,6 +211,56 @@ static void netfs_issue_read(struct netfs_io_request *rreq, } } +/* + * Mark folios that we want to copy to the cache. For filesystems that use + * netfslib fully, we set folio->private to NETFS_FOLIO_COPY_TO_CACHE; + * otherwise we set the deprecated PG_private_2. + */ +static void netfs_mark_copy_to_cache(struct netfs_io_request *rreq, + struct folio_queue **fq, + unsigned int *offset, + int *slot, + size_t len, + bool copy) +{ + while (len > 0) { + struct folio *folio; + size_t fsize, overlap; + + if (!*fq) + break; + if (*slot >= folioq_count(*fq)) { + *fq = (*fq)->next; + *slot = 0; + *offset = 0; + continue; + } + + /* Determine how much the subreq overlaps the folio, if at all. */ + fsize = folioq_folio_size(*fq, *slot); + overlap = min(len, fsize - *offset); + + if (overlap > 0 && copy) { + folio = folioq_folio(*fq, *slot); + if (unlikely(test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags))) { + if (!folio_test_private_2(folio)) + folio_start_private_2(folio); + } else { + if (!folio_get_private(folio)) + folio_attach_private(folio, NETFS_FOLIO_COPY_TO_CACHE); + } + trace_netfs_folio(folio, netfs_folio_trace_mark_copy); + } + + len -= overlap; + *offset += overlap; + if (*offset >= fsize) { + *slot += 1; + *offset = 0; + } + } +} + /* * Perform a read to the pagecache from a series of sources of different types, * slicing up the region to be read according to available cache blocks and @@ -218,9 +268,11 @@ static void netfs_issue_read(struct netfs_io_request *rreq, */ static void netfs_read_to_pagecache(struct netfs_io_request *rreq) { + struct folio_queue *fq = rreq->buffer.tail; unsigned long long start = rreq->start; + unsigned int offset = 0; ssize_t size = rreq->len; - int ret = 0; + int ret = 0, slot = 0; do { struct netfs_io_subrequest *subreq; @@ -308,6 +360,13 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq) set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); } + if (fq) { + /* See if the cache indicated this should be cached. */ + bool copy = test_bit(NETFS_SREQ_COPY_TO_CACHE, &subreq->flags); + + netfs_mark_copy_to_cache(rreq, &fq, &slot, &offset, slice, copy); + } + netfs_issue_read(rreq, subreq); netfs_maybe_bulk_drop_ra_refs(rreq); diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index bd8b2d633f96..dfe7939f35f3 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -110,6 +110,7 @@ static inline void netfs_see_subrequest(struct netfs_io_subrequest *subreq, /* * read_collect.c */ +void netfs_cancel_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio); bool netfs_read_collection(struct netfs_io_request *rreq); void netfs_read_collection_worker(struct work_struct *work); void netfs_cancel_read(struct netfs_io_subrequest *subreq, int error); diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index edf7cea7e2f9..12a786be1ea2 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -19,7 +19,6 @@ #define MADE_PROGRESS 0x04 /* Made progress cleaning up a stream or the folio set */ #define BUFFERED 0x08 /* The pagecache needs cleaning up */ #define NEED_RETRY 0x10 /* A front op requests retrying */ -#define COPY_TO_CACHE 0x40 /* Need to copy subrequest to cache */ #define ABANDON_SREQ 0x80 /* Need to abandon untransferred part of subrequest */ /* @@ -34,6 +33,30 @@ static void netfs_clear_unread(struct netfs_io_subrequest *subreq) __set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags); } +/* + * Cancel the copy-to-cache mark on a folio. + */ +void netfs_cancel_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio) +{ + if (!test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags)) { + if (folio_get_private(folio) == NETFS_FOLIO_COPY_TO_CACHE) { + folio_detach_private(folio); + trace_netfs_folio(folio, netfs_folio_trace_cancel_copy); + } else if (netfs_folio_group(folio) == NETFS_FOLIO_COPY_TO_CACHE) { + struct netfs_folio *finfo = netfs_folio_info(folio); + + finfo->netfs_group = NULL; + trace_netfs_folio(folio, netfs_folio_trace_cancel_copy); + } + } else { + // TODO: Use of PG_private_2 is deprecated. + if (folio_test_private_2(folio)) { + folio_end_private_2(folio); + trace_netfs_folio(folio, netfs_folio_trace_cancel_copy); + } + } +} + /* * Flush, mark and unlock a folio that's now completely read. If we want to * cache the folio, we set the group to NETFS_FOLIO_COPY_TO_CACHE, mark it @@ -48,37 +71,37 @@ static void netfs_unlock_read_folio(struct netfs_io_request *rreq, if (unlikely(folio_pos(folio) < rreq->abandon_to)) { trace_netfs_folio(folio, netfs_folio_trace_abandon); + netfs_cancel_copy_to_cache(rreq, folio); goto just_unlock; } flush_dcache_folio(folio); folio_mark_uptodate(folio); - if (!test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags)) { - finfo = netfs_folio_info(folio); - if (finfo) { - trace_netfs_folio(folio, netfs_folio_trace_filled_gaps); - if (finfo->netfs_group) - folio_change_private(folio, finfo->netfs_group); - else - folio_detach_private(folio); - kfree(finfo); - } + if (unlikely(test_bit(NETFS_RREQ_CANCEL_CACHING, &rreq->flags))) + netfs_cancel_copy_to_cache(rreq, folio); - if (test_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags)) { - if (!WARN_ON_ONCE(folio_get_private(folio) != NULL)) { - trace_netfs_folio(folio, netfs_folio_trace_copy_to_cache); - folio_attach_private(folio, NETFS_FOLIO_COPY_TO_CACHE); - folio_mark_dirty(folio); - } + if (!test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags)) { + if (netfs_folio_group(folio) == NETFS_FOLIO_COPY_TO_CACHE) { + trace_netfs_folio(folio, netfs_folio_trace_sched_copy); + folio_mark_dirty(folio); } else { + finfo = netfs_folio_info(folio); + if (finfo) { + trace_netfs_folio(folio, netfs_folio_trace_filled_gaps); + if (finfo->netfs_group) + folio_change_private(folio, finfo->netfs_group); + else + folio_detach_private(folio); + kfree(finfo); + } trace_netfs_folio(folio, netfs_folio_trace_read_done); } folioq_clear(folioq, slot); } else { // TODO: Use of PG_private_2 is deprecated. - if (test_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags)) + if (folio_test_private_2(folio)) netfs_pgpriv2_copy_to_cache(rreq, folio); } @@ -131,9 +154,6 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, unsigned int order; size_t fsize; - if (*notes & COPY_TO_CACHE) - set_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags); - folio = folioq_folio(folioq, slot); if (WARN_ONCE(!folio_test_locked(folio), "R=%08x: folio %lx is not locked\n", @@ -156,8 +176,6 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, WRITE_ONCE(rreq->cleaned_to, fpos + fsize); *notes |= MADE_PROGRESS; - clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags); - /* Clean up the head folioq. If we clear an entire folioq, then * we can get rid of it provided it's not also the tail folioq * being filled by the issuer. @@ -255,9 +273,6 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq) stream->collected_to = front->start + transferred; rreq->collected_to = stream->collected_to; - if (test_bit(NETFS_SREQ_COPY_TO_CACHE, &front->flags)) - notes |= COPY_TO_CACHE; - if (test_bit(NETFS_SREQ_FAILED, &front->flags)) { rreq->abandon_to = front->start + front->len; front->transferred = front->len; diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index c31190993b76..a4b7bb88cbdb 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -54,8 +54,8 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct folio /* Attach the folio to the rolling buffer. */ if (rolling_buffer_append(&creq->buffer, folio, 0, creq->gfp) < 0) { + set_bit(NETFS_RREQ_CANCEL_CACHING, &creq->flags); folio_end_private_2(folio); - clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &creq->flags); return; } @@ -122,13 +122,14 @@ static struct netfs_io_request *netfs_pgpriv2_begin_copy_to_cache( netfs_put_failed_request(creq); cancel: rreq->copy_to_cache = ERR_PTR(-ENOBUFS); - clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags); + set_bit(NETFS_RREQ_CANCEL_CACHING, &rreq->flags); return ERR_PTR(-ENOBUFS); } /* * [DEPRECATED] Mark page as requiring copy-to-cache using PG_private_2 and add - * it to the copy write request. + * it to the copy write request. PG_private_2 should already be set on the + * folio. */ void netfs_pgpriv2_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio) { @@ -136,11 +137,13 @@ void netfs_pgpriv2_copy_to_cache(struct netfs_io_request *rreq, struct folio *fo if (!creq) creq = netfs_pgpriv2_begin_copy_to_cache(rreq, folio); - if (IS_ERR(creq)) + if (IS_ERR(creq)) { + set_bit(NETFS_RREQ_CANCEL_CACHING, &rreq->flags); + netfs_cancel_copy_to_cache(rreq, folio); return; + } - trace_netfs_folio(folio, netfs_folio_trace_copy_to_cache); - folio_start_private_2(folio); + trace_netfs_folio(folio, netfs_folio_trace_pgpriv2_copy); netfs_pgpriv2_copy_folio(creq, folio); } diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c index dd463a485139..4f6a36c6e214 100644 --- a/fs/netfs/read_retry.c +++ b/fs/netfs/read_retry.c @@ -303,7 +303,11 @@ void netfs_unlock_abandoned_read_pages(struct netfs_io_request *rreq) for (int slot = 0; slot < folioq_count(p); slot++) { struct folio *folio = folioq_folio(p, slot); - if (folio && !folioq_is_marked2(p, slot)) { + if (!folio) + continue; + netfs_cancel_copy_to_cache(rreq, folio); + + if (!folioq_is_marked2(p, slot)) { if (folio == rreq->no_unlock_folio && test_bit(NETFS_RREQ_NO_UNLOCK_FOLIO, &rreq->flags)) { diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 5c538d0c5d79..9881f4afdc0c 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -275,7 +275,7 @@ struct netfs_io_request { #define NETFS_RREQ_SHORT_TRANSFER 5 /* Set if we have a short transfer */ #define NETFS_RREQ_OFFLOAD_COLLECTION 8 /* Offload collection to workqueue */ #define NETFS_RREQ_NO_UNLOCK_FOLIO 9 /* Don't unlock no_unlock_folio on completion */ -#define NETFS_RREQ_FOLIO_COPY_TO_CACHE 10 /* Copy current folio to cache from read */ +#define NETFS_RREQ_CANCEL_CACHING 10 /* Set to cancel caching */ #define NETFS_RREQ_UPLOAD_TO_SERVER 11 /* Need to write to the server */ #define NETFS_RREQ_USE_IO_ITER 12 /* Use ->io_iter rather than ->i_pages */ #define NETFS_RREQ_NEED_PUT_RA_REFS 17 /* Need to put the folio refs RA gave us */ diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 9bda9302be90..a22084813cb5 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -198,7 +198,6 @@ EM(netfs_folio_trace_clear_cc, "clear-cc") \ EM(netfs_folio_trace_clear_g, "clear-g") \ EM(netfs_folio_trace_clear_s, "clear-s") \ - EM(netfs_folio_trace_copy_to_cache, "mark-copy") \ EM(netfs_folio_trace_end_copy, "end-copy") \ EM(netfs_folio_trace_filled_gaps, "filled-gaps") \ EM(netfs_folio_trace_invalidate_all, "inval-all") \ @@ -209,16 +208,19 @@ EM(netfs_folio_trace_kill_cc, "kill-cc") \ EM(netfs_folio_trace_kill_g, "kill-g") \ EM(netfs_folio_trace_kill_s, "kill-s") \ + EM(netfs_folio_trace_mark_copy, "mark-copy") \ EM(netfs_folio_trace_mkwrite, "mkwrite") \ EM(netfs_folio_trace_mkwrite_plus, "mkwrite+") \ - EM(netfs_folio_trace_not_under_wback, "!wback") \ EM(netfs_folio_trace_not_locked, "!locked") \ + EM(netfs_folio_trace_not_under_wback, "!wback") \ + EM(netfs_folio_trace_pgpriv2_copy, "pgpriv2-copy") \ EM(netfs_folio_trace_put, "put") \ EM(netfs_folio_trace_read, "read") \ EM(netfs_folio_trace_read_done, "read-done") \ EM(netfs_folio_trace_read_gaps, "read-gaps") \ EM(netfs_folio_trace_read_unlock, "read-unlock") \ EM(netfs_folio_trace_redirtied, "redirtied") \ + EM(netfs_folio_trace_sched_copy, "sched-copy") \ EM(netfs_folio_trace_store, "store") \ EM(netfs_folio_trace_store_copy, "store-copy") \ EM(netfs_folio_trace_store_plus, "store+") \ From e00827a4d0cfebf8d78dfd0a9a024237f57c9273 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:43:02 +0100 Subject: [PATCH 0236/2366] netfs: Fix read progress reporting For really big read RPC ops that span multiple folios, netfslib allows the filesystem to give progress notifications to wake up the collector thread to do a collection of folios that have now been fetched, even if the RPC is still ongoing, thereby allowing the application to make progress. This works by taking the current rreq->cleaned_to value (which indicates which folios have been unlocked) and adding the stashed size of the next folio to it. cleaned_to, however, is subject to 64-bit tearing on a 32-bit arch. Fix this by stashing the next progress notification point as a size_t (which won't tear) to be added to rreq->start (which won't change), with the collector thread calculating that from cleaned_to plus the next folio size. Further, however, if the folios are small, the collector thread gets constantly woken up - which has a negative performance impact on the system. Fix that too by setting a minimum trigger of 256KiB or the size of the folio at the front of the queue, whichever is larger. Note that this has an issue that different subreqs have different need-to-be-cached properties; this is solved by a preceding patch that marks the property on the folios whilst issuing subreqs rather than when collecting them. Also, make sure rreq->cleaned_to is initialised up front, along with rreq->collected_to and stream->collected_to. Fixes: e2d46f2ec332 ("netfs: Change the read result collector to only use one work item") Link: https://sashiko.dev/#/patchset/20260804100224.2748935-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-10-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 2 ++ fs/netfs/internal.h | 1 + fs/netfs/objects.c | 32 +++++++++++++-------- fs/netfs/read_collect.c | 54 ++++++++++++++++++++++++++++-------- fs/netfs/read_single.c | 2 ++ include/linux/netfs.h | 2 +- include/trace/events/netfs.h | 21 ++++++++++++++ 7 files changed, 89 insertions(+), 25 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 16d4db776f6a..424df70a5c30 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -442,6 +442,7 @@ void netfs_readahead(struct readahead_control *ractl) rreq->submitted = rreq->start + added; rreq->cleaned_to = rreq->start; + netfs_read_set_unlock_at(rreq); netfs_read_to_pagecache(rreq); netfs_maybe_bulk_drop_ra_refs(rreq); @@ -467,6 +468,7 @@ static int netfs_create_singular_buffer(struct netfs_io_request *rreq, struct fo if (added < 0) return added; rreq->submitted = rreq->start + added; + rreq->progress_at = added; return 0; } diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index dfe7939f35f3..c79c8e69d60c 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -111,6 +111,7 @@ static inline void netfs_see_subrequest(struct netfs_io_subrequest *subreq, * read_collect.c */ void netfs_cancel_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio); +void netfs_read_set_unlock_at(struct netfs_io_request *rreq); bool netfs_read_collection(struct netfs_io_request *rreq); void netfs_read_collection_worker(struct work_struct *work); void netfs_cancel_read(struct netfs_io_subrequest *subreq, int error); diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index 01461a74642d..7f6a3e912602 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -41,24 +41,32 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, memset(rreq, 0, kmem_cache_size(cache)); INIT_WORK(&rreq->cleanup_work, netfs_free_request); - rreq->gfp = gfp; - rreq->start = start; - rreq->len = len; - rreq->origin = origin; - rreq->netfs_ops = ctx->ops; - rreq->mapping = mapping; - rreq->inode = inode; - rreq->i_size = i_size_read(inode); - rreq->debug_id = atomic_inc_return(&debug_ids); - rreq->wsize = INT_MAX; + rreq->gfp = gfp; + rreq->start = start; + rreq->collected_to = start; + rreq->cleaned_to = start; + rreq->len = len; + rreq->progress_at = 0; + rreq->origin = origin; + rreq->netfs_ops = ctx->ops; + rreq->mapping = mapping; + rreq->inode = inode; + rreq->i_size = i_size_read(inode); + rreq->debug_id = atomic_inc_return(&debug_ids); + rreq->wsize = INT_MAX; rreq->io_streams[0].sreq_max_len = ULONG_MAX; rreq->io_streams[0].sreq_max_segs = 0; spin_lock_init(&rreq->lock); - INIT_LIST_HEAD(&rreq->io_streams[0].subrequests); - INIT_LIST_HEAD(&rreq->io_streams[1].subrequests); init_waitqueue_head(&rreq->waitq); refcount_set(&rreq->ref, 2); + for (int s = 0; s < NR_IO_STREAMS; s++) { + struct netfs_io_stream *stream = &rreq->io_streams[s]; + + INIT_LIST_HEAD(&stream->subrequests); + stream->collected_to = rreq->start; + } + if (origin == NETFS_READAHEAD || origin == NETFS_READPAGE || origin == NETFS_READ_GAPS || diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 12a786be1ea2..5cf22087d243 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -117,6 +117,35 @@ static void netfs_unlock_read_folio(struct netfs_io_request *rreq, folioq_clear(folioq, slot); } +/* + * Determine how much to gather before unlocking more folios. + */ +void netfs_read_set_unlock_at(struct netfs_io_request *rreq) +{ + struct folio_queue *folioq = rreq->buffer.tail; + unsigned int slot = rreq->buffer.first_tail_slot; + size_t cleaned_to = rreq->cleaned_to - rreq->start; + size_t progress_at = cleaned_to; + size_t minimum = 256 * 1024; + + while (progress_at < rreq->len) { + if (slot >= folioq_count(folioq)) { + folioq = folioq->next; + if (!folioq) + break; + slot = 0; + } + + progress_at += folioq_folio_size(folioq, slot); + if (progress_at - cleaned_to >= minimum) + break; + slot++; + } + + WRITE_ONCE(rreq->progress_at, progress_at); + trace_netfs_read_progress_at(rreq); +} + /* * Unlock any folios we've finished with. */ @@ -135,7 +164,7 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, if (slot >= folioq_nr_slots(folioq)) { folioq = rolling_buffer_delete_spent(&rreq->buffer); if (!folioq) { - rreq->front_folio_order = 0; + WRITE_ONCE(rreq->progress_at, rreq->len); return; } slot = 0; @@ -151,7 +180,6 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, for (;;) { struct folio *folio; unsigned long long fpos, fend; - unsigned int order; size_t fsize; folio = folioq_folio(folioq, slot); @@ -160,9 +188,7 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, rreq->debug_id, folio->index)) trace_netfs_folio(folio, netfs_folio_trace_not_locked); - order = folioq_folio_order(folioq, slot); - rreq->front_folio_order = order; - fsize = PAGE_SIZE << order; + fsize = folioq_folio_size(folioq, slot); fpos = folio_pos(folio); fend = fpos + fsize; @@ -197,6 +223,8 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, rreq->buffer.tail = folioq; done: rreq->buffer.first_tail_slot = slot; + + netfs_read_set_unlock_at(rreq); } /* @@ -257,7 +285,7 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq) * subreqs. */ if (notes & BUFFERED) { - size_t fsize = PAGE_SIZE << rreq->front_folio_order; + uoff_t unlock_at = rreq->start + rreq->progress_at; /* Clear the tail of a short read. */ if (!(notes & HIT_PENDING) && @@ -279,7 +307,7 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq) transferred = front->len; trace_netfs_rreq(rreq, netfs_rreq_trace_set_abandon); } - if (front->start + transferred >= rreq->cleaned_to + fsize || + if (front->start + transferred >= unlock_at || test_bit(NETFS_SREQ_HIT_EOF, &front->flags)) netfs_read_unlock_folios(rreq, ¬es); } else { @@ -499,20 +527,22 @@ void netfs_read_collection_worker(struct work_struct *work) void netfs_read_subreq_progress(struct netfs_io_subrequest *subreq) { struct netfs_io_request *rreq = subreq->rreq; - struct netfs_io_stream *stream = &rreq->io_streams[0]; - size_t fsize = PAGE_SIZE << rreq->front_folio_order; - - trace_netfs_sreq(subreq, netfs_sreq_trace_progress); + struct netfs_io_stream *stream = &rreq->io_streams[subreq->stream_nr]; + size_t progress_at = READ_ONCE(rreq->progress_at); + uoff_t update_at = rreq->start + progress_at; + uoff_t transferred_to = subreq->start + subreq->transferred; /* If we are at the head of the queue, wake up the collector, * getting a ref to it if we were the ones to do so. */ - if (subreq->start + subreq->transferred > rreq->cleaned_to + fsize && + if (progress_at < rreq->len && + transferred_to >= update_at && (rreq->origin == NETFS_READAHEAD || rreq->origin == NETFS_READPAGE || rreq->origin == NETFS_READ_FOR_WRITE) && list_is_first(&subreq->rreq_link, &stream->subrequests) ) { + trace_netfs_sreq(subreq, netfs_sreq_trace_progress); __set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); netfs_wake_collector(rreq); } diff --git a/fs/netfs/read_single.c b/fs/netfs/read_single.c index 8833550d2eb6..de67ac41548d 100644 --- a/fs/netfs/read_single.c +++ b/fs/netfs/read_single.c @@ -170,6 +170,8 @@ ssize_t netfs_read_single(struct inode *inode, struct file *file, struct iov_ite if (IS_ERR(rreq)) return PTR_ERR(rreq); + rreq->progress_at = rreq->len; + ret = netfs_single_begin_cache_read(rreq, ictx); if (ret == -ENOMEM || ret == -EINTR || ret == -ERESTARTSYS) goto cleanup_free; diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 9881f4afdc0c..b4dd32863dd4 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -246,6 +246,7 @@ struct netfs_io_request { unsigned long long submitted; /* Amount submitted for I/O so far */ unsigned long long len; /* Length of the request */ size_t transferred; /* Amount to be indicated as transferred */ + size_t progress_at; /* Report read progress when hit this much read */ long error; /* 0 or error that occurred */ unsigned long long i_size; /* Size of the file */ unsigned long long start; /* Start position */ @@ -262,7 +263,6 @@ struct netfs_io_request { atomic_t subreq_counter; /* Next subreq->debug_index */ unsigned int nr_group_rel; /* Number of refs to release on ->group */ spinlock_t lock; /* Lock for queuing subreqs */ - unsigned char front_folio_order; /* Order (size) of front folio */ enum netfs_io_origin origin; /* Origin of the request */ bool direct_bv_unpin; /* T if direct_bv[] must be unpinned */ refcount_t ref; diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index a22084813cb5..3fec3e8f91c8 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -791,6 +791,27 @@ TRACE_EVENT(netfs_folioq, __print_symbolic(__entry->trace, netfs_folioq_traces)) ); +TRACE_EVENT(netfs_read_progress_at, + TP_PROTO(const struct netfs_io_request *rreq), + + TP_ARGS(rreq), + + TP_STRUCT__entry( + __field(unsigned int, rreq) + __field(size_t, progress_at) + __field(size_t, cleaned_to) + ), + + TP_fast_assign( + __entry->rreq = rreq->debug_id; + __entry->cleaned_to = rreq->cleaned_to - rreq->start; + __entry->progress_at = rreq->progress_at; + ), + + TP_printk("R=%08x cln=%zx prg=%zx", + __entry->rreq, __entry->cleaned_to, __entry->progress_at) + ); + #undef EM #undef E_ #endif /* _TRACE_NETFS_H */ From a67632c8c2688d6e0091529bcefe54bc5ee80e9b Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 27 Aug 2026 14:43:03 +0100 Subject: [PATCH 0237/2366] cachefiles: Fix potential UAF/KASAN warning Currently, trace_cachefiles_coherency() is being passed a pointer to a __be64 lain over the coherency data in struct cachefiles_xattr so that it can display the first 8 bytes. However, the data is of variable length and could even be 0 bytes. This could lead to a UAF or KASAN warning. Fix this by making sure the buffer has room for at least 8 bytes and that those 8 bytes are pre-cleared. Further, those bytes are not 8-byte aligned, so fix the tracepoint to extract the data as four 2-byte words (they are 2-byte aligned) and reassemble the __be64. The compiler will convert this into a single 8-byte load where the CPU supports it. Fixes: 229105e5cfd9 ("cachefiles: Add auxiliary data trace") Link: https://sashiko.dev/#/patchset/20260810144746.574036-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260827134304.2075713-11-dhowells@redhat.com Acked-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/cachefiles/xattr.c | 16 ++++++++-------- include/trace/events/cachefiles.h | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/fs/cachefiles/xattr.c b/fs/cachefiles/xattr.c index f8ae78b3f7b6..c70bf67e52b0 100644 --- a/fs/cachefiles/xattr.c +++ b/fs/cachefiles/xattr.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "internal.h" #define CACHEFILES_COOKIE_TYPE_DATA 1 @@ -50,7 +51,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object) _enter("%x,#%d", object->debug_id, len); - buf = kmalloc(sizeof(struct cachefiles_xattr) + len, GFP_KERNEL); + buf = kmalloc(sizeof(struct cachefiles_xattr) + max(len, sizeof(__be64)), GFP_KERNEL); if (!buf) return -ENOMEM; @@ -60,6 +61,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object) buf->content = object->content_info; if (test_bit(FSCACHE_COOKIE_LOCAL_WRITE, &object->cookie->flags)) buf->content = CACHEFILES_CONTENT_DIRTY; + put_unaligned_be64(0, (__be64 *)buf->data); if (len > 0) memcpy(buf->data, fscache_get_aux(object->cookie), len); @@ -77,8 +79,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object) trace_cachefiles_vfs_error(object, file_inode(file), ret, cachefiles_trace_setxattr_error); trace_cachefiles_coherency(object, file_inode(file)->i_ino, - be64_to_cpup((__be64 *)buf->data), - buf->content, + buf->data, buf->content, cachefiles_coherency_set_fail); if (ret != -ENOMEM) cachefiles_io_error_obj( @@ -86,8 +87,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object) "Failed to set xattr with error %d", ret); } else { trace_cachefiles_coherency(object, file_inode(file)->i_ino, - be64_to_cpup((__be64 *)buf->data), - buf->content, + buf->data, buf->content, cachefiles_coherency_set_ok); } @@ -110,9 +110,10 @@ int cachefiles_check_auxdata(struct cachefiles_object *object, struct file *file int ret = -ESTALE; tlen = sizeof(struct cachefiles_xattr) + len; - buf = kmalloc(tlen, GFP_KERNEL); + buf = kmalloc(sizeof(struct cachefiles_xattr) + max(len, sizeof(__be64)), GFP_KERNEL); if (!buf) return -ENOMEM; + put_unaligned_be64(0, (__be64 *)buf->data); xlen = cachefiles_inject_read_error(); if (xlen == 0) @@ -148,8 +149,7 @@ int cachefiles_check_auxdata(struct cachefiles_object *object, struct file *file out: trace_cachefiles_coherency(object, file_inode(file)->i_ino, - be64_to_cpup((__be64 *)buf->data), - buf->content, why); + buf->data, buf->content, why); kfree(buf); return ret; } diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cachefiles.h index 9259bc71049e..e3101410e8b2 100644 --- a/include/trace/events/cachefiles.h +++ b/include/trace/events/cachefiles.h @@ -372,7 +372,7 @@ TRACE_EVENT(cachefiles_rename, TRACE_EVENT(cachefiles_coherency, TP_PROTO(struct cachefiles_object *obj, ino_t ino, - u64 disk_aux, + const void *disk_aux, enum cachefiles_content content, enum cachefiles_coherency_trace why), @@ -389,12 +389,27 @@ TRACE_EVENT(cachefiles_coherency, ), TP_fast_assign( + union { + __be16 s[4]; + __be64 ll; + } x; + __entry->obj = obj->debug_id; __entry->why = why; __entry->content = content; __entry->ino = ino; __entry->aux = be64_to_cpup((__be64 *)obj->cookie->inline_aux); - __entry->disk_aux = disk_aux; + + /* cachefiles_xattr::data is 2-byte aligned but not 8-byte aligned. */ + if (disk_aux) { + x.s[0] = ((__be16 *)disk_aux)[0]; + x.s[1] = ((__be16 *)disk_aux)[1]; + x.s[2] = ((__be16 *)disk_aux)[2]; + x.s[3] = ((__be16 *)disk_aux)[3]; + __entry->disk_aux = be64_to_cpu(x.ll); + } else { + __entry->disk_aux = 0; + } ), TP_printk("o=%08x %s B=%llx c=%u aux=%llx dsk=%llx", From 7b9b202f154488d06d8de89bfb17f2b68b1ed4cc Mon Sep 17 00:00:00 2001 From: feng liu Date: Fri, 28 Aug 2026 14:34:56 +0800 Subject: [PATCH 0238/2366] ALSA: hda/conexant:Fix abnormal Mic/Speaker functionality on SN6140 after S3 wake-up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inside cx_codec_ops.set_power_state, implement a 1‑second delay after resuming from the S3 state before sending the power command to the codec AFG, thereby preventing potential transmission failures. Signed-off-by: feng liu Link: https://patch.msgid.link/20260828063456.1368-1-feng.liu@senaryTech.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/conexant.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index 7357dc91ac49..9bfdbf5c9032 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -249,6 +249,25 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ } } +#define SN6140_S3_AFG_D0_DELAY_MS 1000 + +static void cx_set_power_state(struct hda_codec *codec, hda_nid_t fg, + unsigned int power_state) +{ + snd_hda_codec_write_sync(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state); + + /* + * SN6140 may not respond to AFG D0 immediately after S3. + * Wait before the D0 verb so the power-state command itself succeeds. + */ + if (codec->core.vendor_id == 0x14f11f87 && + power_state == AC_PWRST_D0 && + codec->core.dev.power.power_state.event == PM_EVENT_RESUME) + msleep(SN6140_S3_AFG_D0_DELAY_MS); + + snd_hda_codec_set_power_to_all(codec, fg, power_state); +} + static int cx_suspend(struct hda_codec *codec) { cx_auto_shutdown(codec); @@ -1308,6 +1327,7 @@ static const struct hda_codec_ops cx_codec_ops = { .init = cx_init, .unsol_event = snd_hda_jack_unsol_event, .suspend = cx_suspend, + .set_power_state = cx_set_power_state, .check_power_status = snd_hda_gen_check_power_status, .stream_pm = snd_hda_gen_stream_pm, }; From 616fd322e0245f3e62541aa39856f75f1b1aa604 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Fri, 28 Aug 2026 18:17:03 +0800 Subject: [PATCH 0239/2366] ALSA: hda/realtek: Add quirk for Lenovo Yoga Slim 9 14ILL10 The Lenovo Yoga Slim 9 14ILL10 (83CX) uses ALC287 with CS35L56 amplifiers. Without a matching SSID entry the bass speakers stay silent. Add PCI SSID 17aa:380b (codec SSID 17aa:3905) to apply ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221902 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260828101704.354406-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index e4349743a251..ae5464087625 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8100,6 +8100,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3801, "Lenovo Yoga9 14IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), HDA_CODEC_QUIRK(0x17aa, 0x3802, "DuetITL 2021", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3802, "Lenovo Yoga Pro 9 14IRP8", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x380b, "Lenovo Yoga Slim 9 14ILL10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), /* Yoga Pro 9 16IMH9 and Legion 7 16ITHG6 share PCI SSID 17aa:3811 * with Legion S7 15IMH05; use codec SSID to distinguish them */ From 34e08ad3a8da293009a4bc8f2f6738bd66b18e0d Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Fri, 28 Aug 2026 18:17:04 +0800 Subject: [PATCH 0240/2366] ALSA: hda/realtek: Add quirk for Acer Predator PHN16-72 The Acer Predator PHN16-72 (subsystem ID 1025:1731) uses a Realtek ALC245 codec. The PCI SSID and HDA codec SSID are both 0x10251731, as confirmed in the system's ALSA diagnostic report. Apply ALC2XX_FIXUP_HEADSET_MIC to enable the headset microphone. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221811 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260828101704.354406-2-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index ae5464087625..039bb4d6f09a 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7155,6 +7155,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1731, "Acer Predator PHN16-72", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), From acac7b5e07349a9d10d78873afb4b93cd1dc721f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 28 Aug 2026 13:55:39 +0200 Subject: [PATCH 0241/2366] ALSA: pcm: Fix race between non-atomic ops and trigger-start We protect the races of the concurrent state transitions between atomic PCM ops, but the checks between the non-atomic ops (hw_params, hw_free and prepare) and the atomic ops aren't perfect; there is a check of the conflicting PCM state at the beginning of hw_params & co, but the atomic PCM ops can be still issued during the non-atomic PCM operations. An example such scenario is that a thread A re-issues the PREPARE or HW_PARAMS for the already prepared stream, while another thread B triggers the PCM start in the middle of the prepare operation. Although this usually doesn't lead to much serious issues, it can give some inconsistency as reported by syzkaller (such as ODEBUG warning). There are various atomic PCM ops, and basically the only problem is the PCM start as it operates from the PREPARED state. Other trigger commands (stop, etc) are for the running or the other special state, hence they are filtered as pre-condition. This patch is for preventing the PCM trigger-start during the non- atomic operations in order to address the problems above. Fortunately, the hw_params, hw_free and prepare operations call snd_pcm_buffer_access_lock(), and this can be used for checking the concurrent operations at the PCM trigger -- which sets the runtime->buffer_accessing to a negative (if possible), so the PCM trigger just needs to check the runtime->buffer_accessing value; if it's negative, it means the concurrent non-atomic PCM ops is running. Reported-by: syzbot+225231fce6755d40d078@syzkaller.appspotmail.com Closes: https://lore.kernel.org/6a8f0de8.1d9ded08.62e62.00b5.GAE@google.com Cc: Link: https://patch.msgid.link/20260828115542.3999-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 4a5057e7629d..20ae67949e05 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1468,6 +1468,8 @@ static int snd_pcm_pre_start(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; if (runtime->state != SNDRV_PCM_STATE_PREPARED) return -EBADFD; + if (atomic_read(&runtime->buffer_accessing) < 0) + return -EBADFD; /* during hw_params, hw_free or prepare */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !snd_pcm_playback_data(substream)) return -EPIPE; From a565d82392e2240e6b2b25e2118e8617efa9fe74 Mon Sep 17 00:00:00 2001 From: Riku Matsumura Date: Sun, 30 Aug 2026 09:00:08 +0900 Subject: [PATCH 0242/2366] ALSA: hda/realtek: Add quirk for VAIO VJS131 The VAIO VJS131 with an ALC233 codec incorrectly selects the headset microphone when no headset is connected. Add a PCI SSID quirk for 1d19:0006 to apply ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, which configures pin 0x19 as a headset microphone without its own jack detection. Signed-off-by: Riku Matsumura Link: https://patch.msgid.link/20260830000008.22371-1-rick197.3@icloud.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 039bb4d6f09a..ecc9c6e6ab98 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8289,6 +8289,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1d05, 0x3034, "TongFang X6KK45xU", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d05, 0x30ba, "TongFang XxAF5xxx", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1d17, 0x3288, "Haier Boyue G42", ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS), + SND_PCI_QUIRK(0x1d19, 0x0006, "VAIO VJS131", ALC233_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1d72, 0x1602, "RedmiBook", ALC255_FIXUP_XIAOMI_HEADSET_MIC), SND_PCI_QUIRK(0x1d72, 0x1701, "XiaomiNotebook Pro", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC), From 33abb7491e89285a41565670945293dda841afc4 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Sun, 30 Aug 2026 14:34:11 +0800 Subject: [PATCH 0243/2366] ALSA: harmony: initialize locks before requesting IRQ snd_harmony_create() registers the IRQ before initializing h->lock and h->mixer_lock. A pending interrupt can invoke the handler while these locks are uninitialized. Initialize both locks before requesting the IRQ so the handler always sees valid lock state. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260830063411.2215691-1-runyu.xiao@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/parisc/harmony.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/parisc/harmony.c b/sound/parisc/harmony.c index fb40476c6c91..a9625aedf2a4 100644 --- a/sound/parisc/harmony.c +++ b/sound/parisc/harmony.c @@ -868,6 +868,9 @@ snd_harmony_create(struct snd_card *card, goto free_and_ret; } + spin_lock_init(&h->mixer_lock); + spin_lock_init(&h->lock); + err = request_irq(padev->irq, snd_harmony_interrupt, 0, "harmony", h); if (err) { @@ -877,9 +880,6 @@ snd_harmony_create(struct snd_card *card, } h->irq = padev->irq; - spin_lock_init(&h->mixer_lock); - spin_lock_init(&h->lock); - err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, h, &ops); if (err < 0) goto free_and_ret; From 9b110a9dcecc59516c77cb3c0caf1f492f75df2d Mon Sep 17 00:00:00 2001 From: Yilin Zhang Date: Mon, 31 Aug 2026 12:55:06 +0800 Subject: [PATCH 0244/2366] ALSA: pcm: Serialize PCM mmap with buffer reallocation to fix page UAF snd_pcm_hw_params() and snd_pcm_hw_free() guard buffer reallocation with an mmap_count check performed under the PCM stream lock, but the lock is released long before the buffer is actually freed: snd_pcm_sync_stop(), constraint refinement and do_free_pages() all happen in between. snd_pcm_mmap_data(), on the other hand, takes no lock at all: it validates against the old buffer's state and dma_bytes, remaps its pages into the VMA, and only then increments mmap_count. A concurrent mmap() can therefore slip in between the check and the free. remap_pfn_range() installs writable PTEs for the old buffer's pages without taking page references, and the subsequent do_free_pages() returns those pages to the page allocator while the VMA still maps them. This leaves a stale, writable mapping of freed pages: a page-level use-after-free that can be leveraged for local privilege escalation. Make snd_pcm_mmap_data() participate in the buffer-access scheme introduced for hw_params/hw_free: acquire runtime->buffer_accessing before validating and remapping, and release it afterwards. Buffer reallocation already fails with -EBUSY while accessors are active, and the mmap side now fails with -EBUSY while a reallocation is in progress, so the validate/remap sequence and the check/free sequence can no longer interleave. A reproducer that turns this race into a stale writable mapping of the freed DMA buffer pages is available on request. Reported-by: Kimi Security Team Fixes: 92ee3c60ec9f ("ALSA: pcm: Fix races among concurrent hw_params and hw_free calls") Signed-off-by: Yilin Zhang Link: https://patch.msgid.link/20260831045506.889070-1-yilinzhang@moonshot.ai Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 20ae67949e05..62324282fcae 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -4023,20 +4023,33 @@ int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, return -EINVAL; } runtime = substream->runtime; - if (runtime->state == SNDRV_PCM_STATE_OPEN) - return -EBADFD; - if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) - return -ENXIO; + /* don't race with buffer reallocation in hw_params/hw_free */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; + if (runtime->state == SNDRV_PCM_STATE_OPEN) { + err = -EBADFD; + goto out; + } + if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) { + err = -ENXIO; + goto out; + } if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || - runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) - return -EINVAL; + runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) { + err = -EINVAL; + goto out; + } size = area->vm_end - area->vm_start; offset = area->vm_pgoff << PAGE_SHIFT; dma_bytes = PAGE_ALIGN(runtime->dma_bytes); - if ((size_t)size > dma_bytes) - return -EINVAL; - if (offset > dma_bytes - size) - return -EINVAL; + if ((size_t)size > dma_bytes) { + err = -EINVAL; + goto out; + } + if (offset > dma_bytes - size) { + err = -EINVAL; + goto out; + } area->vm_ops = &snd_pcm_vm_ops_data; area->vm_private_data = substream; @@ -4046,6 +4059,8 @@ int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, err = snd_pcm_lib_default_mmap(substream, area); if (!err) atomic_inc(&substream->mmap_count); +out: + atomic_dec(&runtime->buffer_accessing); return err; } EXPORT_SYMBOL(snd_pcm_mmap_data); From 5a88f78df753993469dab4d1831f8fb4256a9468 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft)" Date: Fri, 14 Aug 2026 00:09:44 -0400 Subject: [PATCH 0245/2366] reboot: fix cad_pid use-after-free race cad_pid is a single kernel-wide struct pid pointer. proc_do_cad_pid() reads it and passes it to pid_vnr() without protecting the lifetime of the referenced struct pid. A concurrent writer can replace cad_pid and drop the final reference to the old struct pid after the reader has loaded the pointer but before pid_vnr() has finished dereferencing it, causing a use-after-free. kill_cad_pid() has the same lifetime race when it passes cad_pid to kill_pid(). At the time this issue was reported, an unprivileged user could reach the sysctl through user and PID namespaces because cad_pid was registered in pid_table[]. Moving cad_pid back to the global reboot sysctl table corrected that namespace and permission mismatch, but did not fix the underlying lifetime race. Fix this by treating cad_pid as an RCU-protected pointer at both read sites and by waiting for a grace period before dropping the old reference on the write side. call_rcu(&old_pid->rcu, ...) cannot be used here because free_pid() also queues pid->rcu; queueing the same rcu_head twice can corrupt the RCU callback list. Original KASAN crash stack: kernel/pid.c:545 pid_nr_ns() # reads freed pid->level kernel/pid.c:556 pid_vnr() # calls pid_nr_ns() kernel/pid.c:775 proc_do_cad_pid() # calls pid_vnr(cad_pid) Fixes: 9ec52099e4b8 ("[PATCH] replace cad_pid by a struct pid") Reported-by: AutonomousCodeSecurity@microsoft.com Closes: https://lore.kernel.org/all/20260717210143.4734-1-blbllhy@gmail.com/ Link: https://lore.kernel.org/all/alz5ZYLE4kaq_v2P@redhat.com/ Link: https://lore.kernel.org/all/al4ICz9biJKtdZc4@redhat.com/ Suggested-by: Mateusz Guzik Suggested-by: Bradley Morgan Suggested-by: Oleg Nesterov Suggested-by: Eric W. Biederman Suggested-by: Pavel Tikhomirov Cc: stable@vger.kernel.org Signed-off-by: Cen Zhang (Microsoft) Link: https://patch.msgid.link/20260814040944.16561-1-blbllhy@gmail.com Reviewed-by: Bradley Morgan Reviewed-by: Oleg Nesterov Reviewed-by: Pavel Tikhomirov Signed-off-by: Christian Brauner (Amutable) --- include/linux/sched.h | 2 +- include/linux/sched/signal.h | 5 +---- init/main.c | 2 +- kernel/reboot.c | 19 +++++++++++++++---- kernel/signal.c | 12 ++++++++++++ 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/include/linux/sched.h b/include/linux/sched.h index 3f100d69b053..fdafd164a42d 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1778,7 +1778,7 @@ static inline bool is_lazy_mmu_mode_active(void) } #endif -extern struct pid *cad_pid; +extern struct pid __rcu *cad_pid; /* * Per process flags diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h index 584ae88b435e..d45a5476b97d 100644 --- a/include/linux/sched/signal.h +++ b/include/linux/sched/signal.h @@ -562,10 +562,7 @@ static inline sigset_t *sigmask_to_save(void) return res; } -static inline int kill_cad_pid(int sig, int priv) -{ - return kill_pid(cad_pid, sig, priv); -} +int kill_cad_pid(int sig, int priv); /* These can be the second arg to send_sig_info/send_group_sig_info. */ #define SEND_SIG_NOINFO ((struct kernel_siginfo *) 0) diff --git a/init/main.c b/init/main.c index 92d34e496a33..f46f3a8b3efd 100644 --- a/init/main.c +++ b/init/main.c @@ -1644,7 +1644,7 @@ static noinline void __init kernel_init_freeable(void) */ set_mems_allowed(node_states[N_MEMORY]); - cad_pid = get_pid(task_pid(current)); + rcu_assign_pointer(cad_pid, get_pid(task_pid(current))); smp_prepare_cpus(setup_max_cpus); diff --git a/kernel/reboot.c b/kernel/reboot.c index f070c5c1103a..d177d89fcc33 100644 --- a/kernel/reboot.c +++ b/kernel/reboot.c @@ -13,7 +13,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -24,8 +26,7 @@ */ static int C_A_D = 1; -struct pid *cad_pid; -EXPORT_SYMBOL(cad_pid); +struct pid __rcu *cad_pid; #if defined(CONFIG_ARM) #define DEFAULT_REBOOT_MODE = REBOOT_HARD @@ -1371,10 +1372,14 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe { struct ctl_table tmp_table = *table; struct pid *new_pid; + struct pid *old_pid; pid_t tmp_pid; int r; - tmp_pid = pid_vnr(cad_pid); + rcu_read_lock(); + tmp_pid = pid_vnr(rcu_dereference(cad_pid)); + rcu_read_unlock(); + tmp_table.data = &tmp_pid; r = proc_dointvec(&tmp_table, write, buffer, lenp, ppos); @@ -1385,7 +1390,13 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe if (!new_pid) return -ESRCH; - put_pid(xchg(&cad_pid, new_pid)); + old_pid = unrcu_pointer(xchg(&cad_pid, RCU_INITIALIZER(new_pid))); + /* + * Wait for cad_pid readers before put_pid(). We cannot use + * call_rcu() here because free_pid() already owns pid->rcu. + */ + synchronize_rcu(); + put_pid(old_pid); return 0; } diff --git a/kernel/signal.c b/kernel/signal.c index bbc0fd4cc4d7..2162fad7b940 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -1899,6 +1899,18 @@ int kill_pid(struct pid *pid, int sig, int priv) } EXPORT_SYMBOL(kill_pid); +int kill_cad_pid(int sig, int priv) +{ + int ret; + + rcu_read_lock(); + ret = kill_pid(rcu_dereference(cad_pid), sig, priv); + rcu_read_unlock(); + + return ret; +} +EXPORT_SYMBOL(kill_cad_pid); + #ifdef CONFIG_POSIX_TIMERS /* * These functions handle POSIX timer signals. POSIX timers use From c8329cb590df4a8b3a4e878d289d4b17824db8d1 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 30 Aug 2026 20:19:53 -0700 Subject: [PATCH 0246/2366] dma-buf: fix some kernel-doc warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - drop Excess description of @lock from kernel-doc - add missing function/macro short descriptions WARNING: include/linux/dma-fence-array.h:47 Excess struct member 'lock' description in 'dma_fence_array' WARNING: include/linux/dma-fence-chain.h:48 Excess struct member 'lock' description in 'dma_fence_chain' Warning: include/linux/dma-fence-chain.h:82 missing initial short description on line: * dma_fence_chain_alloc Warning: include/linux/dma-fence-chain.h:94 missing initial short description on line: * dma_fence_chain_free Fixes: 5943243914b9 ("dma-buf: use inline lock for the dma-fence-array") Fixes: a408c0ca0c41 ("dma-buf: use inline lock for the dma-fence-chain") Signed-off-by: Randy Dunlap Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260831031956.3410813-1-rdunlap@infradead.org --- include/linux/dma-fence-array.h | 1 - include/linux/dma-fence-chain.h | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/include/linux/dma-fence-array.h b/include/linux/dma-fence-array.h index 1b1d87579c38..0c49d7ccefb6 100644 --- a/include/linux/dma-fence-array.h +++ b/include/linux/dma-fence-array.h @@ -28,7 +28,6 @@ struct dma_fence_array_cb { /** * struct dma_fence_array - fence to represent an array of fences * @base: fence base class - * @lock: spinlock for fence handling * @num_fences: number of fences in the array * @num_pending: fences in the array still pending * @fences: array of the fences diff --git a/include/linux/dma-fence-chain.h b/include/linux/dma-fence-chain.h index df3beadf1515..705c4394ac0d 100644 --- a/include/linux/dma-fence-chain.h +++ b/include/linux/dma-fence-chain.h @@ -20,7 +20,6 @@ * @prev: previous fence of the chain * @prev_seqno: original previous seqno before garbage collection * @fence: encapsulated fence - * @lock: spinlock for fence handling */ struct dma_fence_chain { struct dma_fence base; @@ -81,9 +80,8 @@ dma_fence_chain_contained(struct dma_fence *fence) } /** - * dma_fence_chain_alloc - * - * Returns a new struct dma_fence_chain object or NULL on failure. + * dma_fence_chain_alloc - Returns a new &struct dma_fence_chain object or + * %NULL on failure. * * This specialized allocator has to be a macro for its allocations to be * accounted separately (to have a separate alloc_tag). The typecast is @@ -93,7 +91,8 @@ dma_fence_chain_contained(struct dma_fence *fence) kmalloc_obj(struct dma_fence_chain) /** - * dma_fence_chain_free + * dma_fence_chain_free - Frees an allocated but not used + * &struct dma_fence_chain object. * @chain: chain node to free * * Frees up an allocated but not used struct dma_fence_chain object. This From 1376afc7660bad2a1a5ee0876898312a486cf8bd Mon Sep 17 00:00:00 2001 From: Kiran Kumar K Date: Tue, 25 Aug 2026 10:47:25 +0530 Subject: [PATCH 0247/2366] octeontx2-af: fix CN20K default MCAM rule removal on port cleanup npc_mcam_free_all_entries() disables every MCAM entry mapped to a port before freeing it. On CN20K, that also disables the default broadcast, multicast, promiscuous, and unicast rules, which causes packet drops when all rules are removed per port. Only disable and free non-default entries. Leave CN20K default rules enabled when freeing the remaining port entries. Fixes: 013717353c03 ("octeontx2-af: npc: cn20k: Tear down default MCAM rules explicitly on free") Signed-off-by: Kiran Kumar K Signed-off-by: Ratheesh Kannoth Signed-off-by: David S. Miller --- drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c index 60922944675b..c34f8d86cc8a 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c @@ -2957,10 +2957,9 @@ static void npc_mcam_free_all_entries(struct rvu *rvu, struct npc_mcam *mcam, } } - /* Disable the entry */ - npc_enable_mcam_entry(rvu, mcam, blkaddr, index, false); - if (!cn20k_dft_rl) { + /* Disable the entry */ + npc_enable_mcam_entry(rvu, mcam, blkaddr, index, false); mcam->entry2pfvf_map[index] = NPC_MCAM_INVALID_MAP; /* Free the entry in bitmap */ npc_mcam_clear_bit(mcam, index); From 6463655ab2946d13d2ec5efe04a5c2bf9d675f01 Mon Sep 17 00:00:00 2001 From: Chaitanya Kumar Borah Date: Thu, 6 Aug 2026 13:18:19 +0530 Subject: [PATCH 0248/2366] drm/i915/dp_mst: Remove duplicate intel_pfit_compute_config() call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mst_stream_compute_config() called intel_pfit_compute_config() twice in a row. commit 5ce9ac1531b8 ("drm/i915/mst: Call intel_pfit_compute_config() for sharpness filter") was erroneously cherry-picked to the fixes tree while commit ca97f5546f19 ("drm/i915/mst: Call intel_pfit_compute_config() for sharpness filter") was already in there. Drop the redundant duplicate call. Cc: Rodrigo Vivi Cc: Ville Syrjälä Cc: Nemesa Garg Cc: Jani Nikula Fixes: 5ce9ac1531b8 ("drm/i915/mst: Call intel_pfit_compute_config() for sharpness filter") Signed-off-by: Chaitanya Kumar Borah Reviewed-by: Nemesa Garg Link: https://patch.msgid.link/20260806074819.2631970-1-chaitanya.kumar.borah@intel.com Signed-off-by: Rodrigo Vivi [Rodrigo: adjusted commit message] (cherry picked from commit ea9f3470d33602fb776ea55443467baacf66f23a) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_dp_mst.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index 3be1643f8d03..57daed0b0b36 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -761,10 +761,6 @@ static int mst_stream_compute_config(struct intel_atomic_state *state, pipe_config->sink_format = INTEL_OUTPUT_FORMAT_RGB; pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; - ret = intel_pfit_compute_config(pipe_config, conn_state); - if (ret) - return ret; - ret = intel_pfit_compute_config(pipe_config, conn_state); if (ret) return ret; From 045b5bef916d1cb1a52cb6aa68f78fd8b1235cef Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Mon, 31 Aug 2026 12:04:46 +0300 Subject: [PATCH 0249/2366] usb: xhci: Fix HCS_ERST_MAX conversion This fixes one broken line in commit 6d45e9556d4a ("usb: xhci: standardize multi bit-field macros") included in 7.3-rc1 kernel HCS_ERST_MAX holds power of 2 value for maximum number of segments. In the culprit commit, this was incorrectly converted to "shift up 2". On hardware where this field is zero, this results in xhci_alloc_erst() calling dma_alloc_coherent() with size = 0, leading to a horrible splat and non-usable XHCI. Revert the shift-up-2 to the BIT() macro. Fixes: 6d45e9556d4a ("usb: xhci: standardize multi bit-field macros") Cc: Niklas Neronin Signed-off-by: Chen-Yu Tsai Signed-off-by: Mathias Nyman Tested-by: Pierre-David Belanger Link: https://patch.msgid.link/20260831090448.95644-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 7a21ac81f9c8..af8d4b74c4ba 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2301,7 +2301,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) if (!segs) segs = ERST_DEFAULT_SEGS; - max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2; + max_segs = BIT(FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2)); segs = min(segs, max_segs); ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); From 05506a76f13a279a204b6f9b89b8352b646e54d3 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Mon, 31 Aug 2026 12:04:47 +0300 Subject: [PATCH 0250/2366] usb: xhci: Fix isochronous scheduling regression An isoc URB without URB_ISO_ASAP should be scheduled immediately after the previous one, unless it's the first submission or prior URBs have completed without resubmitting and the endpoint became idle. An HCD_BH driver must consider URBs pending completion in the BH queue in addition to its own queue. Regrettably, core doesn't provide much information, we can only know if we are being called by completion now. This issue is as old as HCD_BH, affects ehci-hcd too and has no known reproducible impact, as drivers generally resubmit from completion. A recent patch tried to address it by looking at xHCI HW state instead. Obviously, HW has no knowledge of the BH giveback queue either, and the whole solution amounts to testing whether prior URBs have been unlinked instead of completing normally - then a new stream is assumed. This leads to false negatives when a driver simply allows the endpoint to empty out and begins a new stream. New URBs are scheduled into the past and promptly fail with -EXDEV status, causing data loss and worse, because drivers get confused by premature completion, particularly when multiple endpoints are started at once and required to stay in sync. snd-usb-audio underruns the OUT endpoint when userspace fails to supply playback data in time. If this is detected in duplex mode, IN URBs are unlinked and both streams restarted. OUT underruns again before IN even begins, another recovery is attempted and the cycle repeats. Fix this by using the best criteria we can muster, taken from ehci-hcd. This brings false negative rate back to zero and false positive rate to less than ever before in xhci-hcd. Traditional logic was equivalent to: if (list_empty(&ep_ring->td_list) || GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) // consider this URB a new stream While free of false negatives, it had easily avoidable false positives: * no check for completion in progress when the list is empty * the ep_ctx check doesn't make up for it at all, but it adds a race - EP state can remain "stopped" for a while after the first submission [mn: add debug message in possible false positive case where driver might incorrectly assume new stream starts mid stream just because td list is empty (URB enqueue is late), and workqueue isn't processing URB completions for this endpoint at the moment] Link: https://lore.kernel.org/linux-usb/20260813005635.34750f8c.michal.pecio@gmail.com/ Fixes: add8469b3e00 ("xhci: fix frame id calculation and checks for isoc URBs") Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260831090448.95644-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 97a1b53c18ef..9847c5bfc41b 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -4312,11 +4312,16 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, check_interval(urb, ep_ctx); /* - * Check if this starts the isoc data flow. Relies on hw setting ep ctx - * state after doorbell ring. Consider adding list_empty(td_list) check + * Schedule the URB discontiguously if all previous URBs have completed. + * XXX core can't tell if completions are pending but not running yet. */ - if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) + if (list_empty(&ep_ring->td_list) && + !hcd_periodic_completion_in_progress(xhci_to_hcd(xhci), urb->ep)) { + if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) + xhci_dbg(xhci, "Unexpected running ring at isoc stream start, uframe: %d\n", + xep->next_uframe); xep->next_uframe = -1; + } return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); } From ff44dfb03a293bf30e31f98772a1dd316a6071d1 Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Mon, 31 Aug 2026 12:04:48 +0300 Subject: [PATCH 0251/2366] xhci: fix lost bounce buffers on TDs spanning several ring segments When a TD reaches a link TRB with data that is not aligned to the endpoint's wMaxPacketSize, xhci_align_td() stages the unalignable tail through the bounce buffer of the ring segment holding that link TRB. xhci_unmap_td_bounce_buffer() later unmaps it and, for IN transfers, copies the data back into the URB's buffer. The enqueue path records the segment that was bounced in td->bounce_seg, under the assumption that a TD never spans more than two ring segments. That assumption does not hold: a TD large enough to span three or more segments crosses several link TRBs and can be bounced at each of them. Only the last one survives in td->bounce_seg, so every earlier bounce buffer is neither copied back nor DMA unmapped. The URB still completes with actual_length equal to the requested length and no error, so the transfer looks successful while a wMaxPacketSize sized hole in the destination buffer silently keeps its previous contents. It also leaks a DMA mapping per dropped bounce. Any sufficiently large and fragmented bulk transfer can hit this. It was found with a USB mass storage device behind xHCI backing a dm-verity target with 512 byte hash blocks, where the stale data is detected rather than silently consumed. The device enumerates as SuperSpeed, so wMaxPacketSize is 1024, while dm-bufio issues one 512 byte bio per hash block. verity_prefetch_io() makes the block layer merge hundreds of them into a single request of up to 512 scatterlist entries of 512 bytes each. At 256 TRBs per ring segment such a TD spans three segments, and every segment boundary falls on an odd multiple of 512, i.e. unaligned to wMaxPacketSize. dm-bufio then caches a hash block holding stale data and dm-verity declares the metadata block corrupted: device-mapper: verity: 8:2: metadata block 10850 is corrupted A reproducer running this under qemu is available at https://github.com/baloo/xhci-verity The bounce state (bounce_buf, bounce_dma, bounce_len, bounce_offs) already lives on the ring segment, so there is nothing extra to track. Keep recording the last bounced segment in td->bounce_seg and, on completion, walk the segments from td->start_seg up to it, unmapping every segment that still has a pending bounce. Stopping at td->bounce_seg rather than td->end_seg matters: a bounce implies the TD continues past that segment's link TRB, so bounce_seg is always strictly before end_seg, and a later TD may already have started in end_seg and been bounced there. Walking that far would copy a foreign bounce buffer into this URB and unmap it twice. It also keeps the walk correct if a TD ever wraps the whole ring so that end_seg == start_seg. [mn: Add ring->num_segs check to prevent unlikely infinite for loop.] Fixes: f9c589e142d0 ("xhci: TD-fragment, align the unsplittable case with a bounce buffer") Cc: stable@vger.kernel.org Suggested-by: Michal Pecio Signed-off-by: Arthur Gautier Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260831090448.95644-4-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 9847c5bfc41b..ec278a9f9540 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -824,21 +824,18 @@ static void xhci_giveback_urb_in_irq(struct xhci_hcd *xhci, usb_hcd_giveback_urb(hcd, urb, status); } -static void xhci_unmap_td_bounce_buffer(struct xhci_hcd *xhci, - struct xhci_ring *ring, struct xhci_td *td) +static void xhci_unmap_one_bounce_buffer(struct xhci_hcd *xhci, + struct xhci_ring *ring, struct xhci_td *td, + struct xhci_segment *seg) { struct device *dev = xhci_to_hcd(xhci)->self.sysdev; - struct xhci_segment *seg = td->bounce_seg; struct urb *urb = td->urb; size_t len; - if (!ring || !seg || !urb) - return; - if (usb_urb_dir_out(urb)) { dma_unmap_single(dev, seg->bounce_dma, ring->bounce_buf_len, DMA_TO_DEVICE); - return; + goto done; } dma_unmap_single(dev, seg->bounce_dma, ring->bounce_buf_len, @@ -854,10 +851,29 @@ static void xhci_unmap_td_bounce_buffer(struct xhci_hcd *xhci, memcpy(urb->transfer_buffer + seg->bounce_offs, seg->bounce_buf, seg->bounce_len); } +done: seg->bounce_len = 0; seg->bounce_offs = 0; } +static void xhci_unmap_td_bounce_buffer(struct xhci_hcd *xhci, + struct xhci_ring *ring, struct xhci_td *td) +{ + struct xhci_segment *seg; + int i = 0; + + if (!td->bounce_seg || !ring || !td->urb) + return; + + /* td->bounce_seg is the last one bounced, unmap them all */ + for (seg = td->start_seg; i++ < ring->num_segs; seg = seg->next) { + if (seg->bounce_len) + xhci_unmap_one_bounce_buffer(xhci, ring, td, seg); + if (seg == td->bounce_seg) + break; + } +} + static void xhci_td_cleanup(struct xhci_hcd *xhci, struct xhci_td *td, struct xhci_ring *ep_ring, int status) { @@ -3685,7 +3701,7 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, &trb_buff_len, ring->enq_seg)) { send_addr = ring->enq_seg->bounce_dma; - /* assuming TD won't span 2 segs */ + /* TD bounced at least, and last on this seg */ td->bounce_seg = ring->enq_seg; } } From 9fca7779ad18538188d640b1fdcfea924459542c Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Wed, 19 Aug 2026 08:55:08 +0300 Subject: [PATCH 0252/2366] Revert "pmdomain: qcom: rpmhpd: Add missing MXC and MMCX power domains for Eliza" This reverts commit b48a0a0a76ccecec60f0568e2af4d89994b08bec, which wrongfully added the MXC and MMCX power domains on Eliza. Even though they are indeed available in cmd-db, which has been the source of information for adding these two, at hardware level they are not actually wired up. Therefore they need to be dropped. Fixes: b48a0a0a76cc ("pmdomain: qcom: rpmhpd: Add missing MXC and MMCX power domains for Eliza") Signed-off-by: Abel Vesa Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/qcom/rpmhpd.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c index 96e4bd2f5a14..90743275942d 100644 --- a/drivers/pmdomain/qcom/rpmhpd.c +++ b/drivers/pmdomain/qcom/rpmhpd.c @@ -241,13 +241,9 @@ static struct rpmhpd *eliza_rpmhpds[] = { [RPMHPD_GFX] = &gfx, [RPMHPD_LCX] = &lcx, [RPMHPD_LMX] = &lmx, - [RPMHPD_MMCX] = &mmcx, - [RPMHPD_MMCX_AO] = &mmcx_ao, [RPMHPD_MSS] = &mss, [RPMHPD_MX] = &mx, [RPMHPD_MX_AO] = &mx_ao, - [RPMHPD_MXC] = &mxc, - [RPMHPD_MXC_AO] = &mxc_ao, [RPMHPD_NSP] = &nsp, }; From 900f48940abcb5294dac8f1b5335cdc562798734 Mon Sep 17 00:00:00 2001 From: Suraj Kandpal Date: Tue, 11 Aug 2026 23:28:42 +0530 Subject: [PATCH 0253/2366] drm/i915/ddi: add helper to compute DDI clock frequency Add intel_ddi_link_symbol_clock() to return the DDI clock frequency for a given port clock: DP 8b/10b : rate DP 128b/132b (UHBR) : (10 / 32) * rate HDMI FRL : (10 / 18) * rate HDMI TMDS : rate The DP case reuses intel_dp_link_symbol_clock(). This will help in upcoming commits to decide value to be written in DDI_CLK_VALFREQ. Signed-off-by: Suraj Kandpal Reviewed-by: Ankit Nautiyal Link: https://patch.msgid.link/20260811175844.2613721-2-suraj.kandpal@intel.com (cherry picked from commit 5abc20e39dd074e8696387ca6871d6e432baf0cd) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_ddi.c | 11 +++++++++++ drivers/gpu/drm/i915/display/intel_ddi.h | 1 + 2 files changed, 12 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_ddi.c b/drivers/gpu/drm/i915/display/intel_ddi.c index b8985e1e0a81..02a53c9848e1 100644 --- a/drivers/gpu/drm/i915/display/intel_ddi.c +++ b/drivers/gpu/drm/i915/display/intel_ddi.c @@ -1529,6 +1529,17 @@ int intel_ddi_level(struct intel_encoder *encoder, return level; } +int intel_ddi_link_symbol_clock(struct intel_encoder *encoder, int clock) +{ + if (intel_encoder_is_dp(encoder)) + return intel_dp_link_symbol_clock(clock); + + if (intel_hdmi_is_frl(clock)) + return DIV_ROUND_CLOSEST(clock * 10, 18); + + return clock; +} + static void hsw_set_signal_levels(struct intel_encoder *encoder, const struct intel_crtc_state *crtc_state) diff --git a/drivers/gpu/drm/i915/display/intel_ddi.h b/drivers/gpu/drm/i915/display/intel_ddi.h index 580ecb09b8b6..239d5a403f91 100644 --- a/drivers/gpu/drm/i915/display/intel_ddi.h +++ b/drivers/gpu/drm/i915/display/intel_ddi.h @@ -81,6 +81,7 @@ void intel_ddi_sanitize_encoder_pll_mapping(struct intel_encoder *encoder); int intel_ddi_level(struct intel_encoder *encoder, const struct intel_crtc_state *crtc_state, int lane); +int intel_ddi_link_symbol_clock(struct intel_encoder *encoder, int clock); void intel_ddi_update_active_dpll(struct intel_atomic_state *state, struct intel_encoder *encoder, struct intel_crtc *crtc); From 0cd42b346d13486f8f31c0846f9f2a9241e191c2 Mon Sep 17 00:00:00 2001 From: Suraj Kandpal Date: Tue, 11 Aug 2026 23:28:43 +0530 Subject: [PATCH 0254/2366] drm/i915/cx0: program DDI_CLK_VALFREQ with DDI clock frequency DDI_CLK_VALFREQ is programmed with the port clock, which for DP is the symbol clock computed assuming 8b/10b encoding (link_rate / 10). For DP 128b/132b (UHBR) rates and for HDMI FRL the port clock needs to be modfied. DDI_CLK_VALFREQ does not have any functional impact on H/w, it only records the frequency S/w intends to set. Use intel_ddi_link_symbol_clock() to write the correct DDI clock in kHz. Fixes: 51390cc0e00a ("drm/i915/mtl: Add Support for C10 PHY message bus and pll programming") Fixes: 73fc3abcb797 ("drm/i915/mtl: Enabling/disabling sequence Thunderbolt pll") Signed-off-by: Suraj Kandpal Reviewed-by: Ankit Nautiyal Link: https://patch.msgid.link/20260811175844.2613721-3-suraj.kandpal@intel.com (cherry picked from commit 9ac3ee6c0f92cd09893bd442964fb6b0d6813b5e) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_cx0_phy.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cx0_phy.c b/drivers/gpu/drm/i915/display/intel_cx0_phy.c index 452062417ce9..dbebd7210848 100644 --- a/drivers/gpu/drm/i915/display/intel_cx0_phy.c +++ b/drivers/gpu/drm/i915/display/intel_cx0_phy.c @@ -3233,7 +3233,8 @@ static void intel_cx0pll_enable(struct intel_encoder *encoder, * 8. Program DDI_CLK_VALFREQ to match intended DDI * clock frequency. */ - intel_de_write(display, DDI_CLK_VALFREQ(encoder->port), port_clock); + intel_de_write(display, DDI_CLK_VALFREQ(encoder->port), + intel_ddi_link_symbol_clock(encoder, port_clock)); /* * 9. Set PORT_CLOCK_CTL register PCLK PLL Request @@ -3406,7 +3407,7 @@ void intel_mtl_tbt_pll_enable_clock(struct intel_encoder *encoder, int port_cloc * clock frequency. */ intel_de_write(display, DDI_CLK_VALFREQ(encoder->port), - port_clock); + intel_ddi_link_symbol_clock(encoder, port_clock)); } void intel_mtl_pll_enable(struct intel_encoder *encoder, From 1d79c50e2eeae42b8aad7b5d4d0fe57027174e8a Mon Sep 17 00:00:00 2001 From: Suraj Kandpal Date: Tue, 11 Aug 2026 23:28:44 +0530 Subject: [PATCH 0255/2366] drm/i915/lt_phy: program DDI_CLK_VALFREQ with DDI clock frequency DDI_CLK_VALFREQ is programmed with the port clock, which for DP is the symbol clock computed assuming 8b/10b encoding (link_rate / 10). For DP 128b/132b (UHBR) rates and for HDMI FRL the port clock needs to be modified. DDI_CLK_VALFREQ does not have any functional impact on H/w, it only records the frequency S/w intends to set. Use intel_ddi_link_symbol_clock() to write the correct DDI clock in kHz Fixes: 5ec58d714935 ("drm/i915/lt_phy: Add .enable_clock hook on DDI") Signed-off-by: Suraj Kandpal Reviewed-by: Ankit Nautiyal Link: https://patch.msgid.link/20260811175844.2613721-4-suraj.kandpal@intel.com (cherry picked from commit eaed815ca3483c227e4ec80b86d1b3ce5c2508be) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_lt_phy.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_lt_phy.c b/drivers/gpu/drm/i915/display/intel_lt_phy.c index 8fc6d230493f..86492651b01d 100644 --- a/drivers/gpu/drm/i915/display/intel_lt_phy.c +++ b/drivers/gpu/drm/i915/display/intel_lt_phy.c @@ -1976,7 +1976,8 @@ void intel_lt_phy_pll_enable(struct intel_encoder *encoder, * Change. We handle this step in bxt_set_cdclk(). */ /* 10. Program DDI_CLK_VALFREQ to match intended DDI clock frequency. */ - intel_de_write(display, DDI_CLK_VALFREQ(encoder->port), port_clock); + intel_de_write(display, DDI_CLK_VALFREQ(encoder->port), + intel_ddi_link_symbol_clock(encoder, port_clock)); /* 11. Program PORT_CLOCK_CTL[PCLK PLL Request LN0] = 1. */ intel_de_rmw(display, XELPDP_PORT_CLOCK_CTL(display, port), @@ -2023,7 +2024,8 @@ void intel_lt_phy_pll_enable(struct intel_encoder *encoder, lane_phy_pulse_status, lane_phy_pulse_status); } else { - intel_de_write(display, DDI_CLK_VALFREQ(encoder->port), port_clock); + intel_de_write(display, DDI_CLK_VALFREQ(encoder->port), + intel_ddi_link_symbol_clock(encoder, port_clock)); } /* From 7f1172a2ac0d7e50850785e2e65789c8aac8411a Mon Sep 17 00:00:00 2001 From: Nemesa Garg Date: Tue, 18 Aug 2026 15:21:49 +0530 Subject: [PATCH 0256/2366] drm/i915/display: Clear SEL_FETCH_PLANE_CTL on plane disable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit icl_plane_disable_sel_fetch_arm() wrote SEL_FETCH_PLANE_CTL = 0 only when crtc_state->enable_psr2_sel_fetch was set. If a plane was disabled after selective fetch had been turned off, the guard fired early and left the register's enable bit set in hardware. The bit is harmless until selective fetch is re-enabled. When it is, the hardware resumes fetching for the now-disabled plane and keeps its old DDB range reserved. i9xx_cursor_disable_sel_fetch_arm() has the same guard on SEL_FETCH_CUR_CTL and is fixed the same way. v2: Add same check for cursor also. [sashiko] Cc: stable@vger.kernel.org Fixes: b1f5279b5981 ("drm/i915/psr: Move plane sel fetch configuration into plane source files") Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/8739 Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Nemesa Garg Reviewed-by: Jouni Högander Signed-off-by: Animesh Manna Link: https://patch.msgid.link/20260818095149.2172935-1-nemesa.garg@intel.com (cherry picked from commit 600a7c9d40e5e0c5544f42d1c9592c8d15224dc0) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_cursor.c | 15 ++++++++++----- .../gpu/drm/i915/display/skl_universal_plane.c | 15 ++++++++++----- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cursor.c b/drivers/gpu/drm/i915/display/intel_cursor.c index 0673f16f6fd0..86bb96ac449b 100644 --- a/drivers/gpu/drm/i915/display/intel_cursor.c +++ b/drivers/gpu/drm/i915/display/intel_cursor.c @@ -530,13 +530,18 @@ static int i9xx_check_cursor(struct intel_crtc_state *crtc_state, } static void i9xx_cursor_disable_sel_fetch_arm(struct intel_dsb *dsb, - struct intel_plane *plane, - const struct intel_crtc_state *crtc_state) + struct intel_plane *plane) { struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) + /* + * Clear this whenever the hardware has selective fetch, not just when + * the current state uses it. The cursor may have been enabled with + * selective fetch earlier and had its enable bit orphaned when the + * feature was switched off. + */ + if (!HAS_PSR2_SEL_FETCH(display)) return; intel_de_write_dsb(display, dsb, SEL_FETCH_CUR_CTL(pipe), 0); @@ -586,7 +591,7 @@ static void i9xx_cursor_update_sel_fetch_arm(struct intel_dsb *dsb, if (crtc_state->enable_psr2_su_region_et) wa_16021440873(dsb, plane, crtc_state, plane_state); else - i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); + i9xx_cursor_disable_sel_fetch_arm(dsb, plane); } } @@ -695,7 +700,7 @@ static void i9xx_cursor_update_arm(struct intel_dsb *dsb, if (plane_state) i9xx_cursor_update_sel_fetch_arm(dsb, plane, crtc_state, plane_state); else - i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); + i9xx_cursor_disable_sel_fetch_arm(dsb, plane); if (plane->cursor.base != base || plane->cursor.size != fbc_ctl || diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c index 07a683293352..5cda1ab90e40 100644 --- a/drivers/gpu/drm/i915/display/skl_universal_plane.c +++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c @@ -879,13 +879,18 @@ skl_plane_disable_arm(struct intel_dsb *dsb, } static void icl_plane_disable_sel_fetch_arm(struct intel_dsb *dsb, - struct intel_plane *plane, - const struct intel_crtc_state *crtc_state) + struct intel_plane *plane) { struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) + /* + * Clear this whenever the hardware has selective fetch, not just when + * the current state uses it. The plane may have been enabled with + * selective fetch earlier and had its enable bit orphaned when the + * feature was switched off. + */ + if (!HAS_PSR2_SEL_FETCH(display)) return; intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), 0); @@ -921,7 +926,7 @@ icl_plane_disable_arm(struct intel_dsb *dsb, skl_write_plane_wm(dsb, plane, crtc_state); - icl_plane_disable_sel_fetch_arm(dsb, plane, crtc_state); + icl_plane_disable_sel_fetch_arm(dsb, plane); if (plane_has_normalizer(plane)) intel_de_write_dsb(display, dsb, @@ -1641,7 +1646,7 @@ static void icl_plane_update_sel_fetch_arm(struct intel_dsb *dsb, intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), SEL_FETCH_PLANE_CTL_ENABLE); else - icl_plane_disable_sel_fetch_arm(dsb, plane, crtc_state); + icl_plane_disable_sel_fetch_arm(dsb, plane); } static void From aad969968824e97ba8d70dd7a95691f750438ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Fri, 17 Jul 2026 18:51:06 +0300 Subject: [PATCH 0257/2366] drm/i915/cdclk: Avoid spurious cdclk sanitization on PTL+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently PTL+ no longer has the cd2x pipe select field in CDCLK_CTL. Take that into account during CDCLK sanitization. This currently triggers a spurious CDCLK sanitization during driver load on PTL+ which will causes a visible glitch on all active displays. Cc: stable@vger.kernel.org Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/8550 Fixes: 2ee8dbd880b1 ("drm/i915/cdclk: Fix up CDCLK_FREQ_DECIMAL without a full PLL re-enable") Signed-off-by: Ville Syrjälä Link: https://patch.msgid.link/20260717155107.17801-1-ville.syrjala@linux.intel.com Reviewed-by: Suraj Kandpal (cherry picked from commit 1786d26887817a779641d3a093c66ac91382113b) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_cdclk.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c index a53d88727177..9e5e15b0c4d1 100644 --- a/drivers/gpu/drm/i915/display/intel_cdclk.c +++ b/drivers/gpu/drm/i915/display/intel_cdclk.c @@ -2381,8 +2381,10 @@ static void bxt_sanitize_cdclk(struct intel_display *display) * dividers both syncing to an active pipe, or asynchronously * (PIPE_NONE). */ - cdctl &= ~bxt_cdclk_cd2x_pipe_mask(display); - cdctl |= bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); + if (DISPLAY_VER(display) < 30) { + cdctl &= ~bxt_cdclk_cd2x_pipe_mask(display); + cdctl |= bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); + } if (cdctl != expected) { if (DISPLAY_VER(display) < 20) { From a154f2ae8eecbf2a4f97376d29b8d38c198b54e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Wed, 26 Aug 2026 17:31:00 +0300 Subject: [PATCH 0258/2366] drm/i915/cdclk: Fix dg2_power_well_count() return type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dg2_power_well_count() is supposed to return an integer, not a boolean. Make it so. Fixes: 9112ce99c1d7 ("drm/i915/cdclk: Extract dg2_power_well_count()") Signed-off-by: Ville Syrjälä Link: https://patch.msgid.link/20260826143100.19401-1-ville.syrjala@linux.intel.com Reviewed-by: Matt Roper (cherry picked from commit dcf423710d0253d7d729c3992bbae0c6197c9c22) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_cdclk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c index 9e5e15b0c4d1..a1a5720996b7 100644 --- a/drivers/gpu/drm/i915/display/intel_cdclk.c +++ b/drivers/gpu/drm/i915/display/intel_cdclk.c @@ -2715,8 +2715,8 @@ static void intel_set_cdclk(struct intel_display *display, } } -static bool dg2_power_well_count(struct intel_display *display, - const struct intel_cdclk_state *cdclk_state) +static int dg2_power_well_count(struct intel_display *display, + const struct intel_cdclk_state *cdclk_state) { return display->platform.dg2 ? hweight8(cdclk_state->active_pipes) : 0; } From 3785d40831ba5601296283e0197e10e089392757 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Thu, 13 Aug 2026 12:19:02 +0530 Subject: [PATCH 0259/2366] drm/i915: Guard against NULL driver_data in i915_pci_probe() pci_match_device() can return the dummy pci_device_id_any entry when a device is force-bound via sysfs driver_override, in which case ->driver_data is unset (NULL). i915_pci_probe() casts it to struct intel_device_info * unconditionally and dereferences intel_info->require_force_probe, causing a NULL-ptr-deref. Reported-by: syzbot+db96c5ff032f4292a8dc@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=db96c5ff032f4292a8dc Tested-by: syzbot+db96c5ff032f4292a8dc@syzkaller.appspotmail.com Cc: stable@vger.kernel.org Signed-off-by: Deepanshu Kartikey Link: https://patch.msgid.link/20260813064902.367504-1-kartikey406@gmail.com Signed-off-by: Jani Nikula (cherry picked from commit 2727922084672cc274ecea726ea00363c2893731) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/i915_pci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/i915/i915_pci.c b/drivers/gpu/drm/i915/i915_pci.c index 82415af47d54..2f03f95945f1 100644 --- a/drivers/gpu/drm/i915/i915_pci.c +++ b/drivers/gpu/drm/i915/i915_pci.c @@ -958,6 +958,9 @@ static int i915_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) (struct intel_device_info *) ent->driver_data; int err; + if (!intel_info) + return -ENODEV; + if (intel_info->require_force_probe && !id_forced(pdev->device)) { dev_info(&pdev->dev, "Your graphics device %04x is not properly supported by i915 in this\n" From 0606f2114e2dc88fe293858fd991cda2688b8c3a Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 26 Aug 2026 10:45:32 +0200 Subject: [PATCH 0260/2366] cpuidle: psci: Fix support for probe deferral by dropping the faux device At the conversion to the faux driver/device we broke the support for probe deferral. In hindsight, the move to the faux device seems questionable, as it simply makes the code more complicated and for no good reason. To fix the support for the probe deferral let's therefore restore the old code and drop the faux device. Fixes: af5376a77e87 ("cpuidle: psci: Transition to the faux device interface") Fixes: 5836ebeb4a2b ("cpuidle: psci: Avoid initializing faux device if no DT idle states are present") Fixes: 39cdf87a97fd ("cpuidle: psci: Fix uninitialized variable in dt_idle_state_present()") Cc: stable@vger.kernel.org Reviewed-by: Abel Vesa Signed-off-by: Ulf Hansson Signed-off-by: Ulf Hansson --- drivers/cpuidle/cpuidle-psci.c | 42 +++++++++++++--------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/drivers/cpuidle/cpuidle-psci.c b/drivers/cpuidle/cpuidle-psci.c index dcf20ea5ef5e..b250d0dde760 100644 --- a/drivers/cpuidle/cpuidle-psci.c +++ b/drivers/cpuidle/cpuidle-psci.c @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include #include @@ -428,14 +428,14 @@ static int psci_idle_init_cpu(struct device *dev, int cpu) * to register cpuidle driver then rollback to cancel all CPUs * registration. */ -static int psci_cpuidle_probe(struct faux_device *fdev) +static int psci_cpuidle_probe(struct platform_device *pdev) { int cpu, ret; struct cpuidle_driver *drv; struct cpuidle_device *dev; for_each_present_cpu(cpu) { - ret = psci_idle_init_cpu(&fdev->dev, cpu); + ret = psci_idle_init_cpu(&pdev->dev, cpu); if (ret) goto out_fail; } @@ -455,36 +455,26 @@ static int psci_cpuidle_probe(struct faux_device *fdev) return ret; } -static struct faux_device_ops psci_cpuidle_ops = { +static struct platform_driver psci_cpuidle_driver = { .probe = psci_cpuidle_probe, + .driver = { + .name = "psci-cpuidle", + }, }; -static bool __init dt_idle_state_present(void) -{ - struct device_node *cpu_node __free(device_node) = - of_cpu_device_node_get(cpumask_first(cpu_possible_mask)); - if (!cpu_node) - return false; - - struct device_node *state_node __free(device_node) = - of_get_cpu_state_node(cpu_node, 0); - if (!state_node) - return false; - - return !!of_match_node(psci_idle_state_match, state_node); -} - static int __init psci_idle_init(void) { - struct faux_device *fdev; + struct platform_device *pdev; + int ret; - if (!dt_idle_state_present()) - return 0; + ret = platform_driver_register(&psci_cpuidle_driver); + if (ret) + return ret; - fdev = faux_device_create("psci-cpuidle", NULL, &psci_cpuidle_ops); - if (!fdev) { - pr_err("Failed to create psci-cpuidle device\n"); - return -ENODEV; + pdev = platform_device_register_simple("psci-cpuidle", -1, NULL, 0); + if (IS_ERR(pdev)) { + platform_driver_unregister(&psci_cpuidle_driver); + return PTR_ERR(pdev); } return 0; From a518e63c377574784f49653ef5314c70e2463b0c Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 25 Aug 2026 17:23:29 +0200 Subject: [PATCH 0261/2366] ovl: return EINVAL instead of EIO in case of mismatched user_ns The EIO was used to signal an internal error (commit 9efb069de4ba ("ovl: add warning on user_ns mismatch")), which is no longer the case. Fixes: 63981fc786da ("ovl: don't warn when the mount is completed from another user namespace") Signed-off-by: Miklos Szeredi Link: https://patch.msgid.link/20260825152330.850645-1-mszeredi@redhat.com Reviewed-by: Amir Goldstein Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index e487597337e8..bd0a3f9039d2 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1543,7 +1543,7 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc) struct ovl_fs *ofs = sb->s_fs_info; int err; - err = -EIO; + err = -EINVAL; /* The fscontext fd may have been passed to another user namespace. */ if (fc->user_ns != current_user_ns()) goto out_err; From 399aa12450a61a5c73dc77e73f069ece9687c95d Mon Sep 17 00:00:00 2001 From: Aleksandr Khromov Date: Mon, 24 Aug 2026 13:22:46 +0300 Subject: [PATCH 0262/2366] ksmbd: zero the FS_OBJECT_ID_INFORMATION buffer before filling it in smb2_get_info_filesystem() reports 64 bytes for FS_OBJECT_ID_INFORMATION, that is the whole of struct object_id_info, but writes only 46 of them: - objid[] is 16 bytes, and when the volume UUID is not available only sizeof(stfs.f_fsid) (8) bytes are copied into it; - extended_info.version_string[] is STRING_LENGTH (28) bytes, and only strlen("1.1.0") (5) bytes are copied into it. The response buffer is zeroed on allocation (kvzalloc() in smb2_allocate_rsp_buf()), so for a standalone request the remaining 31 bytes are zero. In a compound request they need not be. The offset of the next response is advanced by the length pinned for the previous one, so if a preceding command wrote its reply into the buffer and then failed, smb2_set_err_rsp() pins only the short error response and the next reply lands inside the area that has already been written. Only the header is cleared there: memset((char *)rsp_hdr, 0, sizeof(struct smb2_hdr) + 2); The client then receives up to 31 bytes of a response it was not meant to see, including one that failed with an access denied error. Clear the structure before filling it in. As a side effect version_string is now NUL terminated. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Suggested-by: ChenXiaoSong Cc: stable@vger.kernel.org Signed-off-by: Aleksandr Khromov Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index a8046f477d54..486cd745dd21 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -7444,6 +7444,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, struct object_id_info *info; info = (struct object_id_info *)(rsp->Buffer); + memset(info, 0, sizeof(*info)); if (path.mnt->mnt_sb->s_uuid_len == 16) memcpy(info->objid, path.mnt->mnt_sb->s_uuid.b, From c0cd3fc6824122014da2b3b0cb7ddeaa2946ec8e Mon Sep 17 00:00:00 2001 From: Aleksandr Khromov Date: Mon, 24 Aug 2026 21:23:32 +0900 Subject: [PATCH 0263/2366] ksmbd: initialize FileSystemControlFlags in FS_CONTROL_INFORMATION smb2_get_info_filesystem() reports 48 bytes for FS_CONTROL_INFORMATION, that is the whole of struct smb2_fs_control_info, but never assigns FileSystemControlFlags. Those four bytes go to the client as they are found in the response buffer. The buffer is zeroed on allocation, so a standalone request leaks nothing. A compound request can leak: the offset of the next response is advanced by the length pinned for the previous one, so a reply that was written into the buffer and then dropped in favour of the short error response of smb2_set_err_rsp() stays there, and the next reply is laid over it with only the header cleared. ksmbd does not implement quota tracking, so report no control flags. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Cc: stable@vger.kernel.org Signed-off-by: Aleksandr Khromov Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 486cd745dd21..2fbd9010513e 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -7500,6 +7500,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, info->FreeSpaceStopFiltering = 0; info->DefaultQuotaThreshold = cpu_to_le64(SMB2_NO_FID); info->DefaultQuotaLimit = cpu_to_le64(SMB2_NO_FID); + info->FileSystemControlFlags = 0; info->Padding = 0; rsp->OutputBufferLength = cpu_to_le32(48); fixed_len = 48; From db2267b27c054a6c2151ff7fbb67927e784f31d6 Mon Sep 17 00:00:00 2001 From: Aleksandr Khromov Date: Tue, 25 Aug 2026 10:19:21 +0900 Subject: [PATCH 0264/2366] ksmbd: fill in FileSysIdentifier in FS_POSIX_INFORMATION smb2_get_info_filesystem() reports 56 bytes for FS_POSIX_INFORMATION, that is the whole of FILE_SYSTEM_POSIX_INFO, but never assigns FileSysIdentifier. Those eight bytes go to the client as they are found in the response buffer. The buffer is zeroed on allocation, so a standalone request leaks nothing. A compound request can leak: the offset of the next response is advanced by the length pinned for the previous one, so a reply that was written into the buffer and then dropped in favour of the short error response of smb2_set_err_rsp() stays there, and the next reply is laid over it with only the header cleared. Report the file system id statfs() returned, which is what the field is for. FileSysIdentifier is __le64 and f_fsid is a pair of ints, so assemble the value first, val[0] as the low half, and convert it on the way out. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Cc: stable@vger.kernel.org Signed-off-by: Aleksandr Khromov Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 2fbd9010513e..08cb215c3729 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -7523,6 +7523,9 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, info->UserBlocksAvail = cpu_to_le64(stfs.f_bavail); info->TotalFileNodes = cpu_to_le64(stfs.f_files); info->FreeFileNodes = cpu_to_le64(stfs.f_ffree); + info->FileSysIdentifier = + cpu_to_le64((u64)(u32)stfs.f_fsid.val[1] << 32 | + (u32)stfs.f_fsid.val[0]); rsp->OutputBufferLength = cpu_to_le32(56); fixed_len = 56; } From edcd92df5e1f94e89f8cd410ce41c5cb56e24453 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 26 Aug 2026 23:03:01 +0900 Subject: [PATCH 0265/2366] MAINTAINERS: Add Paulo Alcantara as an SMBDIRECT co-maintainer Steve French passed away recently. He was a long-time maintainer of Linux's SMB support and will be greatly missed. Add Paulo Alcantara as a co-maintainer of SMBDIRECT. Acked-by: Paulo Alcantara Signed-off-by: Paulo Alcantara Acked-by: Stefan Metzmacher Signed-off-by: Namjae Jeon --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index e05570c393c5..7e1d14406619 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25105,10 +25105,9 @@ F: Documentation/admin-guide/LSM/Smack.rst F: security/smack/ SMBDIRECT (RDMA Stream Transport with Read/Write-Offload, MS-SMBD) -M: Steve French -M: Steve French M: Namjae Jeon M: Namjae Jeon +M: Paulo Alcantara R: Stefan Metzmacher R: Tom Talpey L: linux-cifs@vger.kernel.org From 5c944895a94d0317669f1b3409deb0161eaf916b Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 26 Aug 2026 23:36:28 +0900 Subject: [PATCH 0266/2366] MAINTAINERS: Update the KSMBD entry Steve French passed away recently. He was a long-time maintainer of Linux's SMB support and will be greatly missed. Update the KSMBD entry to no longer list Steve French as a maintainer. Signed-off-by: Namjae Jeon --- MAINTAINERS | 2 -- 1 file changed, 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 7e1d14406619..2a81760e697b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14194,8 +14194,6 @@ F: tools/testing/selftests/ KERNEL SMB3 SERVER (KSMBD) M: Namjae Jeon M: Namjae Jeon -M: Steve French -M: Steve French R: Sergey Senozhatsky R: Tom Talpey R: ChenXiaoSong From d12168084c8c1b6d883c8eca5853929ac5136a9e Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 28 Aug 2026 10:46:44 +0900 Subject: [PATCH 0267/2366] ksmbd: safely drain sessions during logoff SMB3 multichannel allows requests for one session to run on multiple connections. Wait for all channels bound to a session before freeing shared session objects. A deferred byte-range lock remains counted as a running request and only wakes when its file closes. Wake blocked locks during the drain without unpublishing or modifying their file objects. Synchronous CANCEL requests must invoke their cancellation callback to wake pending operations, while CHANGE_NOTIFY completion remains specific to the asynchronous path. Serialize session teardown with channel registration and previous-session cleanup, and use atomic work-state transitions so LOGOFF, CANCEL, and connection teardown invoke cancellation callbacks only once. Fixes: 76e98a158b20 ("ksmbd: fix race condition between destroy_previous_session() and smb2 operations()") Reported-by: Cheryl Babcock Signed-off-by: Namjae Jeon --- fs/smb/server/connection.c | 8 ++++-- fs/smb/server/mgmt/user_session.c | 13 ++++++++- fs/smb/server/mgmt/user_session.h | 1 + fs/smb/server/smb2pdu.c | 47 +++++++++++++++++++++++++++---- fs/smb/server/vfs_cache.c | 17 +++++++++-- fs/smb/server/vfs_cache.h | 1 + 6 files changed, 77 insertions(+), 10 deletions(-) diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c index 91fdd1ddc61f..4cb92d6599ee 100644 --- a/fs/smb/server/connection.c +++ b/fs/smb/server/connection.c @@ -13,6 +13,7 @@ #include "mgmt/ksmbd_ida.h" #include "mgmt/user_session.h" #include "connection.h" +#include "vfs_cache.h" #include "compress.h" #include "transport_tcp.h" #include "transport_rdma.h" @@ -384,12 +385,12 @@ static void ksmbd_conn_cancel_async_requests(struct ksmbd_conn *conn) spin_lock(&conn->request_lock); list_for_each_entry_safe(work, tmp, &conn->async_requests, async_request_entry) { - if (work->state != KSMBD_WORK_ACTIVE) + if (cmpxchg(&work->state, KSMBD_WORK_ACTIVE, + KSMBD_WORK_CANCELLED) != KSMBD_WORK_ACTIVE) continue; ksmbd_debug(CONN, "Cancel async request id %d\n", work->async_id); - work->state = KSMBD_WORK_CANCELLED; if (work->cancel_fn) work->cancel_fn(work->cancel_argv); } @@ -473,6 +474,9 @@ int ksmbd_conn_wait_idle_sess(struct ksmbd_conn *curr_conn, if (retry_count >= max_timeout) return -EIO; + /* A blocked byte-range lock cannot drain until teardown wakes it. */ + ksmbd_wake_session_blocked_works(sess); + down_read(&conn_list_lock); hash_for_each(conn_list, bkt, conn, hlist) { if (ksmbd_session_is_bound_to_conn(sess, conn)) { diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c index 7022d5d656b4..2eb8f730e99e 100644 --- a/fs/smb/server/mgmt/user_session.c +++ b/fs/smb/server/mgmt/user_session.c @@ -666,10 +666,21 @@ void destroy_previous_session(struct ksmbd_conn *conn, memcmp(user->passkey, prev_user->passkey, user->passkey_sz)) goto out; + down_write(&prev_sess->chann_lock); + if (prev_sess->tearing_down) { + up_write(&prev_sess->chann_lock); + goto out; + } + prev_sess->tearing_down = true; + up_write(&prev_sess->chann_lock); + ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_RECONNECT); err = ksmbd_conn_wait_idle_sess(conn, prev_sess); if (err) { - ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_SETUP); + down_write(&prev_sess->chann_lock); + prev_sess->tearing_down = false; + up_write(&prev_sess->chann_lock); + ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_GOOD); goto out; } diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h index f8a24c33f7fe..3e52d4cc1324 100644 --- a/fs/smb/server/mgmt/user_session.h +++ b/fs/smb/server/mgmt/user_session.h @@ -42,6 +42,7 @@ struct ksmbd_session { bool sign; bool enc; + bool tearing_down; int state; __u8 *Preauth_HashValue; diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 08cb215c3729..ba0fe25bf366 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -97,6 +97,11 @@ static int register_session_channel(struct ksmbd_session *sess, int rc = 0; down_write(&sess->chann_lock); + if (sess->tearing_down) { + rc = -ESHUTDOWN; + goto out; + } + if (xa_load(&sess->ksmbd_chann_list, (long)conn)) goto out; @@ -3086,17 +3091,41 @@ int smb2_session_logoff(struct ksmbd_work *work) smb2_set_err_rsp(work); return -ENOENT; } + + down_write(&sess->chann_lock); + if (sess->tearing_down) { + up_write(&sess->chann_lock); + ksmbd_conn_unlock(conn); + rsp->hdr.Status = STATUS_USER_SESSION_DELETED; + smb2_set_err_rsp(work); + return -ENOENT; + } + sess->tearing_down = true; + up_write(&sess->chann_lock); + ksmbd_all_conn_set_status(sess, KSMBD_SESS_NEED_RECONNECT); ksmbd_conn_unlock(conn); + err = ksmbd_conn_wait_idle_sess(conn, sess); + if (err) { + down_write(&sess->chann_lock); + sess->tearing_down = false; + up_write(&sess->chann_lock); + ksmbd_all_conn_set_status(sess, KSMBD_SESS_GOOD); + rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR; + smb2_set_err_rsp(work); + return err; + } + ksmbd_close_session_fds(work); - ksmbd_conn_wait_idle(conn); if (ksmbd_tree_conn_session_logoff(sess)) { ksmbd_debug(SMB, "Invalid tid %d\n", req->hdr.Id.SyncId.TreeId); rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED; smb2_set_err_rsp(work); - return -ENOENT; + err = -ENOENT; + } else { + err = 0; } down_write(&conn->session_lock); @@ -3106,6 +3135,9 @@ int smb2_session_logoff(struct ksmbd_work *work) ksmbd_all_conn_set_status(sess, KSMBD_SESS_NEED_SETUP); + if (err) + return err; + rsp->StructureSize = cpu_to_le16(4); err = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_logoff_rsp)); if (err) { @@ -9685,14 +9717,14 @@ int smb2_cancel(struct ksmbd_work *work) * still on conn->async_requests with a live cancel_fn * pointing at the freed file_lock. */ - if (iter->state != KSMBD_WORK_ACTIVE) + if (cmpxchg(&iter->state, KSMBD_WORK_ACTIVE, + KSMBD_WORK_CANCELLED) != KSMBD_WORK_ACTIVE) break; ksmbd_debug(SMB, "smb2 with AsyncId %llu cancelled command = 0x%x\n", le64_to_cpu(hdr->Id.AsyncId), le16_to_cpu(chdr->Command)); - iter->state = KSMBD_WORK_CANCELLED; if (iter->cancel_fn == smb2_notify_cancel_fn) cancelled_notify = smb2_notify_cancel_claim(iter->cancel_argv); @@ -9721,11 +9753,16 @@ int smb2_cancel(struct ksmbd_work *work) iter == work) continue; + if (cmpxchg(&iter->state, KSMBD_WORK_ACTIVE, + KSMBD_WORK_CANCELLED) != KSMBD_WORK_ACTIVE) + break; + ksmbd_debug(SMB, "smb2 with mid %llu cancelled command = 0x%x\n", le64_to_cpu(hdr->MessageId), le16_to_cpu(chdr->Command)); - iter->state = KSMBD_WORK_CANCELLED; + if (iter->cancel_fn) + iter->cancel_fn(iter->cancel_argv); break; } spin_unlock(&conn->request_lock); diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c index 81626d204249..fd2c595f0486 100644 --- a/fs/smb/server/vfs_cache.c +++ b/fs/smb/server/vfs_cache.c @@ -846,12 +846,25 @@ static void set_close_state_blocked_works(struct ksmbd_file *fp) spin_lock(&fp->f_lock); list_for_each_entry(cancel_work, &fp->blocked_works, fp_entry) { - cancel_work->state = KSMBD_WORK_CLOSED; - cancel_work->cancel_fn(cancel_work->cancel_argv); + if (xchg(&cancel_work->state, KSMBD_WORK_CLOSED) == + KSMBD_WORK_ACTIVE) + cancel_work->cancel_fn(cancel_work->cancel_argv); } spin_unlock(&fp->f_lock); } +void ksmbd_wake_session_blocked_works(struct ksmbd_session *sess) +{ + struct ksmbd_file_table *ft = &sess->file_table; + struct ksmbd_file *fp; + unsigned int id; + + read_lock(&ft->lock); + idr_for_each_entry(ft->idr, fp, id) + set_close_state_blocked_works(fp); + read_unlock(&ft->lock); +} + int ksmbd_close_fd(struct ksmbd_work *work, u64 id) { struct ksmbd_file *fp; diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h index 502efb16f05f..1884f6deb9d0 100644 --- a/fs/smb/server/vfs_cache.h +++ b/fs/smb/server/vfs_cache.h @@ -226,6 +226,7 @@ void ksmbd_stop_durable_scavenger(void); bool ksmbd_durable_scavenger_active(void); void ksmbd_close_tree_conn_fds(struct ksmbd_work *work); void ksmbd_close_session_fds(struct ksmbd_work *work); +void ksmbd_wake_session_blocked_works(struct ksmbd_session *sess); int ksmbd_close_inode_fds(struct ksmbd_work *work, struct inode *inode); int ksmbd_init_global_file_table(void); void ksmbd_free_global_file_table(void); From 73f860489e3be2245598d1819226304fc5b87291 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 25 Aug 2026 09:31:35 +0900 Subject: [PATCH 0268/2366] ksmbd: zero pipe read compound padding Compound response handling extends the last response iov to an eight-byte boundary. smb2_read_pipe() allocates only the payload size, so the alignment padding can expose up to seven bytes of uninitialized kernel heap memory. Allocate the aligned size and clear the unused tail before pinning the response buffer. Fixes: e2b76ab8b5c9 ("ksmbd: add support for read compound") Reported-by: Cheryl Babcock Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index ba0fe25bf366..8c589110460a 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -8657,13 +8657,18 @@ static noinline int smb2_read_pipe(struct ksmbd_work *work) } aux_payload_buf = - kvmalloc(rpc_resp->payload_sz, KSMBD_DEFAULT_GFP); + kvmalloc(ALIGN(rpc_resp->payload_sz, 8), + KSMBD_DEFAULT_GFP); if (!aux_payload_buf) { err = -ENOMEM; goto out; } memcpy(aux_payload_buf, rpc_resp->payload, rpc_resp->payload_sz); + if (rpc_resp->payload_sz & 7) + memset(aux_payload_buf + rpc_resp->payload_sz, 0, + ALIGN(rpc_resp->payload_sz, 8) - + rpc_resp->payload_sz); nbytes = rpc_resp->payload_sz; err = ksmbd_iov_pin_rsp_read(work, (void *)rsp, From c61dc7b1b4a3234b4aa3965502908a292238805c Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 25 Aug 2026 09:32:07 +0900 Subject: [PATCH 0269/2366] ksmbd: propagate DACL parsing errors parse_dacl() silently accepts truncated ACEs and allocation failures, allowing set_info_sec() to continue with an incomplete ACL conversion. Return parsing and allocation errors to parse_sec_desc() so malformed security descriptors are rejected before inode attributes or ACL xattrs are updated. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Reported-by: Cheryl Babcock Signed-off-by: Namjae Jeon --- fs/smb/server/smbacl.c | 63 +++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c index 8ad2e5a5cca8..4496098eb559 100644 --- a/fs/smb/server/smbacl.c +++ b/fs/smb/server/smbacl.c @@ -383,10 +383,10 @@ void free_acl_state(struct posix_acl_state *state) kfree(state->groups); } -static void parse_dacl(struct mnt_idmap *idmap, - struct smb_acl *pdacl, char *end_of_acl, - struct smb_sid *pownersid, struct smb_sid *pgrpsid, - struct smb_fattr *fattr) +static int parse_dacl(struct mnt_idmap *idmap, + struct smb_acl *pdacl, char *end_of_acl, + struct smb_sid *pownersid, struct smb_sid *pgrpsid, + struct smb_fattr *fattr) { int i, ret; u16 num_aces = 0; @@ -400,13 +400,13 @@ static void parse_dacl(struct mnt_idmap *idmap, bool owner_found = false, group_found = false, others_found = false; if (!pdacl) - return; + return 0; /* validate that we do not go past end of acl */ if (end_of_acl < (char *)pdacl + sizeof(struct smb_acl) || end_of_acl < (char *)pdacl + le16_to_cpu(pdacl->size)) { pr_err("ACL too small to parse DACL\n"); - return; + return -EINVAL; } ksmbd_debug(SMB, "DACL revision %d size %d num aces %d\n", @@ -418,31 +418,31 @@ static void parse_dacl(struct mnt_idmap *idmap, num_aces = le16_to_cpu(pdacl->num_aces); if (num_aces <= 0) - return; + return 0; dacl_size = le16_to_cpu(pdacl->size); if (dacl_size < sizeof(struct smb_acl)) - return; + return -EINVAL; if (num_aces > (dacl_size - sizeof(struct smb_acl)) / (offsetof(struct smb_ace, sid) + offsetof(struct smb_sid, sub_auth) + sizeof(__le16))) - return; + return -EINVAL; ret = init_acl_state(&acl_state, num_aces); if (ret) - return; + return ret; ret = init_acl_state(&default_acl_state, num_aces); if (ret) { free_acl_state(&acl_state); - return; + return ret; } ppace = kmalloc_objs(struct smb_ace *, num_aces, KSMBD_DEFAULT_GFP); if (!ppace) { free_acl_state(&default_acl_state); free_acl_state(&acl_state); - return; + return -ENOMEM; } /* @@ -451,8 +451,10 @@ static void parse_dacl(struct mnt_idmap *idmap, * user/group/other have no permissions */ for (i = 0; i < num_aces; ++i) { - if (end_of_acl - acl_base < acl_size) - break; + if (end_of_acl - acl_base < acl_size) { + ret = -EINVAL; + goto out; + } ppace[i] = (struct smb_ace *)(acl_base + acl_size); acl_base = (char *)ppace[i]; @@ -465,8 +467,10 @@ static void parse_dacl(struct mnt_idmap *idmap, (end_of_acl - acl_base < acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth) || (le16_to_cpu(ppace[i]->size) < - acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth)) - break; + acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth)) { + ret = -EINVAL; + goto out; + } acl_size = le16_to_cpu(ppace[i]->size); ppace[i]->access_req = @@ -541,7 +545,6 @@ static void parse_dacl(struct mnt_idmap *idmap, ((acl_mode & 0700) >> 6) | 0004; } } - kfree(ppace); if (owner_found) { /* The owner must be set to at least read-only. */ @@ -584,10 +587,12 @@ static void parse_dacl(struct mnt_idmap *idmap, fattr->cf_acls = posix_acl_alloc(acl_state.users->n + acl_state.groups->n + 4, KSMBD_DEFAULT_GFP); - if (fattr->cf_acls) { - cf_pace = fattr->cf_acls->a_entries; - posix_state_to_acl(&acl_state, cf_pace); + if (!fattr->cf_acls) { + ret = -ENOMEM; + goto out; } + cf_pace = fattr->cf_acls->a_entries; + posix_state_to_acl(&acl_state, cf_pace); } } @@ -598,14 +603,20 @@ static void parse_dacl(struct mnt_idmap *idmap, fattr->cf_dacls = posix_acl_alloc(default_acl_state.users->n + default_acl_state.groups->n + 4, KSMBD_DEFAULT_GFP); - if (fattr->cf_dacls) { - cf_pdace = fattr->cf_dacls->a_entries; - posix_state_to_acl(&default_acl_state, cf_pdace); + if (!fattr->cf_dacls) { + ret = -ENOMEM; + goto out; } + cf_pdace = fattr->cf_dacls->a_entries; + posix_state_to_acl(&default_acl_state, cf_pdace); } } + ret = 0; +out: + kfree(ppace); free_acl_state(&acl_state); free_acl_state(&default_acl_state); + return ret; } static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, @@ -966,8 +977,10 @@ int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd, if (dacloffset < sizeof(struct smb_ntsd)) return -EINVAL; - parse_dacl(idmap, dacl_ptr, end_of_acl, - owner_sid_ptr, group_sid_ptr, fattr); + rc = parse_dacl(idmap, dacl_ptr, end_of_acl, + owner_sid_ptr, group_sid_ptr, fattr); + if (rc) + return rc; } return 0; From feca5e70fc963b088377b20879e8cd8237c2fd7d Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 25 Aug 2026 09:32:23 +0900 Subject: [PATCH 0270/2366] ksmbd: rate limit unmapped SID errors A client can include many structurally valid but unmapped SIDs in a DACL. Logging every mapping failure lets one request generate hundreds of kernel error messages. Rate limit the message to prevent an authenticated client from flooding the kernel log. Fixes: e2f34481b24d ("cifsd: add server-side procedures for SMB3") Reported-by: Cheryl Babcock Signed-off-by: Namjae Jeon --- fs/smb/server/smbacl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c index 4496098eb559..1fad6ccf3a72 100644 --- a/fs/smb/server/smbacl.c +++ b/fs/smb/server/smbacl.c @@ -528,8 +528,8 @@ static int parse_dacl(struct mnt_idmap *idmap, temp_fattr.cf_uid = INVALID_UID; ret = sid_to_id(idmap, &ppace[i]->sid, SIDOWNER, &temp_fattr); if (ret || uid_eq(temp_fattr.cf_uid, INVALID_UID)) { - pr_err("%s: Error %d mapping Owner SID to uid\n", - __func__, ret); + pr_err_ratelimited("%s: Error %d mapping Owner SID to uid\n", + __func__, ret); continue; } From f25e93768fcc5d8287e50b1ec52a42e4c276df34 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 28 Aug 2026 08:39:57 +0900 Subject: [PATCH 0271/2366] ksmbd: prevent out-of-bounds reads in share config responses Validate IPC share configuration payload sizes before consuming variable-length fields. Bound veto list parsing and account for the separator byte when deriving the path length. Fixes: a677ebd8ca2f ("ksmbd: validate payload size in ipc response") Reported-by: Kanishka De Silva Reported-by: Farhad Alemi Signed-off-by: Namjae Jeon --- fs/smb/server/mgmt/share_config.c | 40 +++++++++++++++++++++---------- fs/smb/server/transport_ipc.c | 21 ++++++++++------ 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/fs/smb/server/mgmt/share_config.c b/fs/smb/server/mgmt/share_config.c index b2d9580bddc6..cc9f18ede80d 100644 --- a/fs/smb/server/mgmt/share_config.c +++ b/fs/smb/server/mgmt/share_config.c @@ -146,9 +146,9 @@ static struct ksmbd_share_config *__share_lookup(const char *name) static int parse_veto_list(struct ksmbd_share_config *share, char *veto_list, - int veto_list_sz) + size_t veto_list_sz) { - int sz = 0; + size_t sz; if (!veto_list_sz) return 0; @@ -156,7 +156,7 @@ static int parse_veto_list(struct ksmbd_share_config *share, while (veto_list_sz > 0) { struct ksmbd_veto_pattern *p; - sz = strlen(veto_list); + sz = strnlen(veto_list, veto_list_sz); if (!sz) break; @@ -164,7 +164,7 @@ static int parse_veto_list(struct ksmbd_share_config *share, if (!p) return -ENOMEM; - p->pattern = kstrdup(veto_list, KSMBD_DEFAULT_GFP); + p->pattern = kstrndup(veto_list, sz, KSMBD_DEFAULT_GFP); if (!p->pattern) { kfree(p); return -ENOMEM; @@ -172,6 +172,9 @@ static int parse_veto_list(struct ksmbd_share_config *share, list_add(&p->list, &share->veto_list); + if (sz == veto_list_sz) + break; + veto_list += sz + 1; veto_list_sz -= (sz + 1); } @@ -224,17 +227,28 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work, } if (!test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) { - int path_len = PATH_MAX; + size_t path_len; - if (resp->payload_sz) - path_len = resp->payload_sz - resp->veto_list_sz; - - share->path = kstrndup(ksmbd_share_config_path(resp), path_len, - KSMBD_DEFAULT_GFP); - if (!share->path) { - ret = -ENOMEM; + if (resp->payload_sz <= resp->veto_list_sz) { + ret = -EINVAL; } else { - ret = 0; + path_len = resp->payload_sz - resp->veto_list_sz; + if (resp->veto_list_sz) + path_len--; + + if (!path_len) { + ret = -EINVAL; + } else { + share->path = kstrndup( + ksmbd_share_config_path(resp), + path_len, KSMBD_DEFAULT_GFP); + if (!share->path) + ret = -ENOMEM; + else + ret = 0; + } + } + if (share->path) { share->path_sz = strlen(share->path); while (share->path_sz > 1 && share->path[share->path_sz - 1] == '/') diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c index 4b0b572a3e1b..e550aa41ad2c 100644 --- a/fs/smb/server/transport_ipc.c +++ b/fs/smb/server/transport_ipc.c @@ -532,14 +532,21 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry) if (entry->msg_sz < sizeof(struct ksmbd_share_config_response)) return -EINVAL; - if (resp->payload_sz) { - if (resp->payload_sz < resp->veto_list_sz) - return -EINVAL; + if (strnlen(resp->share_name, sizeof(resp->share_name)) == + sizeof(resp->share_name)) + return -EINVAL; - if (check_add_overflow(sizeof(struct ksmbd_share_config_response), - resp->payload_sz, &msg_sz)) - return -EINVAL; - } + if (resp->veto_list_sz > resp->payload_sz) + return -EINVAL; + + if (resp->flags != KSMBD_SHARE_FLAG_INVALID && + !(resp->flags & KSMBD_SHARE_FLAG_PIPE) && + resp->payload_sz <= resp->veto_list_sz) + return -EINVAL; + + if (check_add_overflow(sizeof(struct ksmbd_share_config_response), + resp->payload_sz, &msg_sz)) + return -EINVAL; break; } case KSMBD_EVENT_LOGIN_REQUEST_EXT: From a506290f59e1c6ce9ac0a13158640bb8fee93471 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 28 Aug 2026 09:24:49 +0900 Subject: [PATCH 0272/2366] ksmbd: fix listener task lifetime on netdev events The listener thread exits when its listening socket is shutdown. The netdevice notifier shuts down the socket before calling kthread_stop(), so the task_struct can be freed before kthread_stop() gets its reference. Create the listener in a stopped state and hold an extra task_struct reference until kthread_stop_put() completes. Also stop and release listeners before freeing their interface records during TCP teardown. Fixes: 3316a8fc840d ("ksmbd: server: avoid busy polling in accept loop") Reported-by: Farhad Alemi Signed-off-by: Namjae Jeon --- fs/smb/server/transport_tcp.c | 36 ++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c index 832e93084605..4968cfc1a572 100644 --- a/fs/smb/server/transport_tcp.c +++ b/fs/smb/server/transport_tcp.c @@ -39,6 +39,7 @@ struct tcp_transport { static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops; static void tcp_stop_kthread(struct task_struct *kthread); +static void ksmbd_tcp_stop_listener(struct interface *iface); static struct interface *alloc_iface(char *ifname); static void ksmbd_tcp_disconnect(struct ksmbd_transport *t); @@ -321,13 +322,20 @@ static int ksmbd_tcp_run_kthread(struct interface *iface) int rc; struct task_struct *kthread; - kthread = kthread_run(ksmbd_kthread_fn, (void *)iface, "ksmbd-%s", - iface->name); + kthread = kthread_create(ksmbd_kthread_fn, (void *)iface, "ksmbd-%s", + iface->name); if (IS_ERR(kthread)) { rc = PTR_ERR(kthread); return rc; } + + /* + * The listener can exit after its socket is shutdown, so keep the + * task_struct alive until the caller has stopped it. + */ + get_task_struct(kthread); iface->ksmbd_kthread = kthread; + wake_up_process(kthread); return 0; } @@ -598,12 +606,7 @@ static int ksmbd_netdev_event(struct notifier_block *nb, unsigned long event, if (iface && iface->state == IFACE_STATE_CONFIGURED) { ksmbd_debug(CONN, "netdev-down event: netdev(%s) is going down\n", iface->name); - kernel_sock_shutdown(iface->ksmbd_socket, SHUT_RDWR); - tcp_stop_kthread(iface->ksmbd_kthread); - iface->ksmbd_kthread = NULL; - sock_release(iface->ksmbd_socket); - iface->ksmbd_socket = NULL; - + ksmbd_tcp_stop_listener(iface); iface->state = IFACE_STATE_DOWN; break; } @@ -631,11 +634,25 @@ static void tcp_stop_kthread(struct task_struct *kthread) if (!kthread) return; - ret = kthread_stop(kthread); + ret = kthread_stop_put(kthread); if (ret) pr_err("failed to stop forker thread\n"); } +static void ksmbd_tcp_stop_listener(struct interface *iface) +{ + if (iface->ksmbd_socket) + kernel_sock_shutdown(iface->ksmbd_socket, SHUT_RDWR); + + tcp_stop_kthread(iface->ksmbd_kthread); + iface->ksmbd_kthread = NULL; + + if (iface->ksmbd_socket) { + sock_release(iface->ksmbd_socket); + iface->ksmbd_socket = NULL; + } +} + void ksmbd_tcp_destroy(void) { struct interface *iface, *tmp; @@ -643,6 +660,7 @@ void ksmbd_tcp_destroy(void) unregister_netdevice_notifier(&ksmbd_netdev_notifier); list_for_each_entry_safe(iface, tmp, &iface_list, entry) { + ksmbd_tcp_stop_listener(iface); list_del(&iface->entry); kfree(iface->name); kfree(iface); From ba9572bc43d04d71ba52ae7f20645f1eafe86875 Mon Sep 17 00:00:00 2001 From: Alon Shakevsky Date: Sat, 29 Aug 2026 06:27:46 +0000 Subject: [PATCH 0273/2366] ksmbd: validate normalized name response length FILE_NORMALIZED_NAME_INFORMATION converts the open file path to UTF-16. smb2_allocate_rsp_buf() leaves these responses in the 448-byte small buffer, and get_file_normalized_name_info() converts the path without checking the remaining space. An authenticated client can query a long path and make smbConvertToUTF16() write beyond work->response_buf. Use the large response buffer for normalized-name queries. Before conversion, verify that the response has room for the worst-case UTF-16 output and its terminator. Fixes: 10aeff72ab82 ("ksmbd: support normalized name information") Assisted-by: Antiproof:GPT-5.6-Sol Signed-off-by: Alon Shakevsky Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 8c589110460a..d656832d82ef 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -878,7 +878,8 @@ int smb2_allocate_rsp_buf(struct ksmbd_work *work) req = smb_get_msg(work->request_buf); if ((req->InfoType == SMB2_O_INFO_FILE && (req->FileInfoClass == FILE_FULL_EA_INFORMATION || - req->FileInfoClass == FILE_ALL_INFORMATION)) || + req->FileInfoClass == FILE_ALL_INFORMATION || + req->FileInfoClass == FILE_NORMALIZED_NAME_INFORMATION)) || req->InfoType == SMB2_O_INFO_SECURITY) sz = large_sz; } @@ -6789,7 +6790,7 @@ static int get_file_normalized_name_info(struct ksmbd_work *work, { struct smb2_file_alt_name_info *file_info; char *filename, *normalized, *stream_name; - int conv_len, filename_len; + int buf_free_len, conv_len, filename_len; if (work->conn->dialect < SMB311_PROT_ID) { rsp->hdr.Status = STATUS_NOT_SUPPORTED; @@ -6813,6 +6814,14 @@ static int get_file_normalized_name_info(struct ksmbd_work *work, return -ENOMEM; filename_len = strlen(normalized); + buf_free_len = smb2_resp_buf_len(work, sizeof(*rsp) + + sizeof(*file_info)); + if (buf_free_len < 0 || + (size_t)buf_free_len < (filename_len + 1) * sizeof(__le16)) { + kfree(normalized); + return -EINVAL; + } + file_info = (struct smb2_file_alt_name_info *)rsp->Buffer; conv_len = smbConvertToUTF16((__le16 *)file_info->FileName, normalized, filename_len, From 4dc8f4ee2d46d5d1e749ddd1b94912c72e796162 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Wed, 26 Aug 2026 13:59:53 +0800 Subject: [PATCH 0274/2366] ntfs: handle signal interruption in fallocate The ntfs_attr_fallocate() function checks for pending signals during allocation loops and exits early via 'out' label. However, when a signal interrupts the operation with err == 0, the function returns 0 (success) instead of -EINTR. The signal_pending() checks at the allocation loops jump to 'out' without setting err = -EINTR, so the function returns success even when interrupted by a signal. Set err = -EINTR when jumping to the signal exit path, and only override when no other error is pending. This ensures: - Allocation interrupted by signal returns -EINTR - Allocation that completed successfully before signal arrived returns 0 - Other errors are preserved and not overwritten by -EINTR Fixes: 495e90fa3348 ("ntfs: update attrib operations") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng Reviewed-by: Baolin Liu Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index b3e941423a3f..848a0d338b89 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -5709,7 +5709,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo } if (signal_pending(current)) - goto out; + goto signal_out; vcn += alloc_cnt; try_alloc_cnt -= alloc_cnt; @@ -5730,7 +5730,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo up_write(&ni->runlist.lock); mutex_unlock(&ni->mrec_lock); if (err || signal_pending(current)) - goto out; + goto signal_out; vcn += alloc_cnt; try_alloc_cnt -= alloc_cnt; @@ -5756,4 +5756,8 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo mutex_unlock(&ni->mrec_lock); out: return err >= 0 ? 0 : err; +signal_out: + if (!err) + err = -EINTR; + goto out; } From 04cec690b1fd9d1c4c314b91a10d8c68a3acfe18 Mon Sep 17 00:00:00 2001 From: Jameson Thies Date: Tue, 25 Aug 2026 23:45:45 +0000 Subject: [PATCH 0275/2366] usb: typec: ucsi: displayport: Fix OOB altmode array index The UCSI displayport driver indexes the connector's port altmode array with the GET_CURRENT_CAM response after checking it is not 0xff. The port altmode array is UCSI_MAX_ALTMODES elements long. If the PPM returns an invalid GET_CURRENT_CAM response above UCSI_MAX_ALTMODES and not equal to 0xff, the kernel may crash with an array index OOB error. Update the UCSI displayport driver to verify the current cam is less than UCSI_MAX_ALTMODES before accessing the port altmode array. Fixes: af8622f6a585 ("usb: typec: ucsi: Support for DisplayPort alt mode") Cc: stable@vger.kernel.org Signed-off-by: Jameson Thies Reviewed-by: Benson Leung Link: https://patch.msgid.link/20260825234545.2076049-1-jthies@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/displayport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c index 7067f2561b84..8d2032d0762c 100644 --- a/drivers/usb/typec/ucsi/displayport.c +++ b/drivers/usb/typec/ucsi/displayport.c @@ -74,7 +74,7 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo) cur = 0xff; } - if (cur != 0xff) { + if (cur < UCSI_MAX_ALTMODES) { ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; goto err_unlock; } From 9cc5761b8f28f9cef72061094eb5e37e2cd44d97 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Mon, 31 Aug 2026 16:30:14 +0800 Subject: [PATCH 0276/2366] ntfs: take invalidate_lock in ntfs_setattr_size() ntfs_setattr_size() updates i_size and resizes the on-disk attribute without holding mapping->invalidate_lock. Page faults take the lock shared, so a fault racing the resize can resolve a VCN against the transient runlist state of ntfs_non_resident_attr_expand() and fail with a spurious SIGBUS, and can interleave with the size-change epilogue (truncate_pagecache(), i_size_write(), pagecache_isize_extended()). Take invalidate_lock exclusively around the whole resize after inode_dio_wait(), matching the fallocate path and other filesystems such as xfs, which wraps truncate in its mmaplock (= invalidate_lock). Fixes: 9c87959601e8 ("ntfs: update file operations") Cc: stable@vger.kernel.org Reviewed-by: Hyunchul Lee Reviewed-by: Baolin Liu Signed-off-by: Hongling Zeng Signed-off-by: Namjae Jeon --- fs/ntfs/file.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 1969e4f444f7..585ab2145797 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -270,18 +270,25 @@ static int ntfs_setattr_size(struct inode *vi, struct iattr *attr) return err; inode_dio_wait(vi); + + /* + * Serialize with page faults and pagecache instantiation so that + * readers cannot observe the size change until the attribute + * updates below have completed. + */ + filemap_invalidate_lock(vi->i_mapping); if (attr->ia_size > old_size) { truncate_pagecache(vi, old_size); i_size_write(vi, attr->ia_size); pagecache_isize_extended(vi, old_size, attr->ia_size); - } else + } else { truncate_setsize(vi, attr->ia_size); + } err = ntfs_truncate_vfs(vi, attr->ia_size, old_size); - if (err) { + if (err) i_size_write(vi, old_size); - return err; - } + filemap_invalidate_unlock(vi->i_mapping); return err; } From 0fecc393f2060e6bc25138df32cb923ec7071c6b Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Mon, 31 Aug 2026 16:32:52 +0800 Subject: [PATCH 0277/2366] ntfs: take invalidate_lock in ntfs_filemap_page_mkwrite() ntfs_filemap_page_mkwrite() calls iomap_page_mkwrite() without holding mapping->invalidate_lock, so a concurrent truncate or fallocate can be in the middle of invalidating pagecache and rewriting the runlist while the write fault maps blocks and dirties the folio. This races with ntfs_attr_fallocate(), which merges clusters into the in-memory runlist, drops the runlist lock, and only afterwards zeroes the newly allocated clusters on disk; and with the punch-hole/insert/collapse paths that free clusters after truncating the cache. Per Documentation/filesystems/locking.rst, ->page_mkwrite() must ensure there are no truncate/invalidate races, "usually mapping->invalidate_lock is suitable for proper serialization". xfs takes its mmaplock (= the invalidate_lock rwsem) shared in exactly this path. Take invalidate_lock shared around iomap_page_mkwrite(). The read-only fault path is already covered because filemap_fault() itself grabs invalidate_lock shared on instantiation/read paths; only page_mkwrite was bypassing it in this driver. Fixes: 9c87959601e8 ("ntfs: update file operations") Cc: stable@vger.kernel.org Reviewed-by: Hyunchul Lee Reviewed-by: Baolin Liu Signed-off-by: Hongling Zeng Co-developed-by: Namjae Jeon Signed-off-by: Namjae Jeon --- fs/ntfs/file.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 585ab2145797..8164326b7812 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -676,6 +676,7 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) static vm_fault_t ntfs_filemap_page_mkwrite(struct vm_fault *vmf) { struct inode *inode = file_inode(vmf->vma->vm_file); + struct address_space *mapping = inode->i_mapping; vm_fault_t ret; if (NInoWofCompressed(NTFS_I(inode))) @@ -684,7 +685,14 @@ static vm_fault_t ntfs_filemap_page_mkwrite(struct vm_fault *vmf) sb_start_pagefault(inode->i_sb); file_update_time(vmf->vma->vm_file); + /* + * Serialize against truncate/fallocate which hold the lock + * exclusively while invalidating pagecache and changing extents. + */ + filemap_invalidate_lock_shared(mapping); ret = iomap_page_mkwrite(vmf, &ntfs_page_mkwrite_iomap_ops, NULL); + filemap_invalidate_unlock_shared(mapping); + sb_end_pagefault(inode->i_sb); return ret; } @@ -1185,13 +1193,15 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le err = file_modified(file); out: + if (!err && mode == 0 && NInoNonResident(ni) && + offset > old_size) { + truncate_pagecache(vi, old_size); + pagecache_isize_extended(vi, old_size, offset); + } + filemap_invalidate_unlock(vi->i_mapping); + if (!err) { - if (mode == 0 && NInoNonResident(ni) && - offset > old_size) { - truncate_pagecache(vi, old_size); - pagecache_isize_extended(vi, old_size, offset); - } NInoSetFileNameDirty(ni); inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi)); mark_inode_dirty(vi); From 04405aeef4f8d7bcac6dcb1947acafdb4420c2c3 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Sun, 30 Aug 2026 22:20:26 +0800 Subject: [PATCH 0278/2366] ASoC: sti: initialize IRQ lock before requesting IRQ uni_reader_init() registers the shared IRQ before initializing reader->irq_lock. A pending interrupt can invoke the handler while the lock is still uninitialized. Initialize the lock before registering the IRQ so the interrupt path always sees valid lock state. Fixes: d05d862ead8e ("ASoC: STI: Fix null ptr deference in IRQ handler") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260830142026.2666914-1-runyu.xiao@seu.edu.cn Signed-off-by: Mark Brown --- sound/soc/sti/uniperif_reader.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/sti/uniperif_reader.c b/sound/soc/sti/uniperif_reader.c index 45d7613f595c..5347f9620f25 100644 --- a/sound/soc/sti/uniperif_reader.c +++ b/sound/soc/sti/uniperif_reader.c @@ -416,6 +416,8 @@ int uni_reader_init(struct platform_device *pdev, else reader->hw = &uni_reader_pcm_hw; + spin_lock_init(&reader->irq_lock); + ret = devm_request_irq(&pdev->dev, reader->irq, uni_reader_irq_handler, IRQF_SHARED, dev_name(&pdev->dev), reader); @@ -424,8 +426,6 @@ int uni_reader_init(struct platform_device *pdev, return -EBUSY; } - spin_lock_init(&reader->irq_lock); - return 0; } EXPORT_SYMBOL_GPL(uni_reader_init); From 882de800bf583dd15969836088789edbf035a944 Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sun, 23 Aug 2026 12:53:34 +0100 Subject: [PATCH 0279/2366] arm64: dts: amlogic: t7: use the real UART pclk uart_a listed the 24MHz crystal for all three of its clocks because the T7 clock controller driver did not exist when these boards were added. That leaves the real UART bus clock without a user, so the kernel turns it off when it disables unused clocks at the end of boot, and the board hangs. Update the board DTS files to point uart_a's pclk to CLKID_SYS_UART_A instead of the dummy crystal clock. Fixes: 4fef056588f5 ("arm64: dts: amlogic-t7-a311d2-khadas-vim4: add initial device-tree") Fixes: 6f048cc7a635 ("arm64: dts: add board AN400") Signed-off-by: Lucas Tanure Assisted-by: Claude:claude-fable-5 Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260823115335.102219-2-tanure@linux.com Signed-off-by: Neil Armstrong --- arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts | 2 +- arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts index cab2ee9ea0d3..ca7536f772ff 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-an400.dts @@ -33,7 +33,7 @@ xtal: xtal-clk { }; &uart_a { - clocks = <&xtal>, <&xtal>, <&xtal>; + clocks = <&xtal>, <&clkc_periphs CLKID_SYS_UART_A>, <&xtal>; clock-names = "xtal", "pclk", "baud"; status = "okay"; }; diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index c41525a34b72..75d81ad6830d 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -266,6 +266,6 @@ &sd_emmc_c { &uart_a { status = "okay"; - clocks = <&xtal>, <&xtal>, <&xtal>; + clocks = <&xtal>, <&clkc_periphs CLKID_SYS_UART_A>, <&xtal>; clock-names = "xtal", "pclk", "baud"; }; From c793a084ab3088744516aef437b4239d0d98623f Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sun, 23 Aug 2026 12:53:35 +0100 Subject: [PATCH 0280/2366] arm64: dts: amlogic: t7: khadas-vim4: allow the SD card to be power cycled SD cards start at 3.3V and switch to 1.8V to reach UHS-I speeds. Some cards refuse that switch, and the SD specification says the only way to recover is to power the card off and start again. SD_3V3 is marked regulator-always-on, so the supply never goes off, the card stays stuck half way through the switch, and the MMC core retries forever: mmc1: error -95 whilst initialising SD card Drop regulator-always-on. regulator-boot-on still turns the supply on at boot, and a card that refuses the switch now falls back to high speed instead of failing to initialise. Fixes: 8c45bf9ae4ef ("arm64: dts: amlogic: t7: khadas-vim4: Add power regulators") Signed-off-by: Lucas Tanure Assisted-by: Claude:claude-fable-5 Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260823115335.102219-3-tanure@linux.com Signed-off-by: Neil Armstrong --- arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index 75d81ad6830d..c94afaaf8826 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -71,7 +71,6 @@ sd_3v3: regulator-sdcard-3v3 { vin-supply = <&vddao_3v3>; gpio = <&gpio GPIOD_11 GPIO_ACTIVE_LOW>; regulator-boot-on; - regulator-always-on; }; sdio_pwrseq: sdio-pwrseq { From ae7be5c58a819259e5eb98a3abfa60842478f2ae Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 29 Aug 2026 10:47:56 +0100 Subject: [PATCH 0281/2366] arm64: dts: amlogic: t7: fix the pin groups of two PWM outputs Two of the PWM outputs can each appear on more than one pin, but the description named a single group that does not exist, so anything using it refused to start. Name the real groups instead, one entry per pin, the same way the other multi-pin PWM output is already described. Fixes: 2a2a7b9701a7 ("arm64: dts: amlogic: t7: Add PWM pinctrl nodes") Assisted-by: Claude:claude-opus-5 Signed-off-by: Lucas Tanure Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260829094758.23248-3-tanure@linux.com Signed-off-by: Neil Armstrong --- arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 32 ++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi index c3dc479b137d..8c4bd683cce0 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi @@ -458,9 +458,25 @@ mux { }; }; - pwm_ao_g_pins: pwm-ao-g { + pwm_ao_g_d11_pins: pwm-ao-g-d11 { mux { - groups = "pwm_ao_g"; + groups = "pwm_ao_g_d11"; + function = "pwm_ao_g"; + bias-disable; + }; + }; + + pwm_ao_g_d7_pins: pwm-ao-g-d7 { + mux { + groups = "pwm_ao_g_d7"; + function = "pwm_ao_g"; + bias-disable; + }; + }; + + pwm_ao_g_e_pins: pwm-ao-g-e { + mux { + groups = "pwm_ao_g_e"; function = "pwm_ao_g"; bias-disable; }; @@ -474,9 +490,17 @@ mux { }; }; - pwm_ao_h_pins: pwm-ao-h { + pwm_ao_h_d5_pins: pwm-ao-h-d5 { mux { - groups = "pwm_ao_h"; + groups = "pwm_ao_h_d5"; + function = "pwm_ao_h"; + bias-disable; + }; + }; + + pwm_ao_h_d10_pins: pwm-ao-h-d10 { + mux { + groups = "pwm_ao_h_d10"; function = "pwm_ao_h"; bias-disable; }; From 1e5a53bd16ac501e68463e84023d1bff543cc696 Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 29 Aug 2026 10:47:57 +0100 Subject: [PATCH 0282/2366] arm64: dts: amlogic: t7: khadas-vim4: add the PWM-driven supplies The board powers its two CPU clusters, the GPU, the NPU, the memory and the always-on domain from regulators steered by PWM outputs. None of them were described, so Linux treated those outputs as unused and switched them off part way through boot. The supplies then drifted away from the levels the bootloader had set, which showed up as random hangs and memory corruption. Describe each supply so it has an owner and is left alone. The voltage ranges are read off the feedback networks on the board schematic. VDDNPU is deliberately not the range in Amlogic's own device tree: this board fits a different feedback resistor, which puts it about 40mV higher at both ends. Fixes: 8c45bf9ae4ef ("arm64: dts: amlogic: t7: khadas-vim4: Add power regulators") Assisted-by: Claude:claude-opus-5 Signed-off-by: Lucas Tanure Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260829094758.23248-4-tanure@linux.com Signed-off-by: Neil Armstrong --- .../amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 110 +++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index c94afaaf8826..77abfd555ec5 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -104,6 +104,66 @@ vcc5v0_usb: regulator-vcc-usb { enable-active-high; }; + vdd_ddr: regulator-vddddr { + /* + * SY8003ADFC Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDDDR"; + regulator-min-microvolt = <690000>; + regulator-max-microvolt = <890000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_gh 0 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + + vdd_ee: regulator-vddee { + /* + * MP8756GD Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDEE"; + regulator-min-microvolt = <700000>; + regulator-max-microvolt = <922000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_ab 0 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + + vdd_gpu: regulator-vddgpu { + /* + * SY8003ADFC Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDGPU"; + regulator-min-microvolt = <700000>; + regulator-max-microvolt = <922000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_ef 0 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + + vdd_npu: regulator-vddnpu { + /* + * SY8003ADFC Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDNPU"; + regulator-min-microvolt = <733000>; + regulator-max-microvolt = <933000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_ef 1 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + vddao_1v8: regulator-vddao-1v8 { compatible = "regulator-fixed"; regulator-name = "VDDAO_1V8"; @@ -122,6 +182,36 @@ vddao_3v3: regulator-vddao-3v3 { regulator-always-on; }; + vddcpu_a: regulator-vddcpu-a { + /* + * MP8756GD Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDCPU_A"; + regulator-min-microvolt = <689000>; + regulator-max-microvolt = <1049000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_cd 1 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + + vddcpu_b: regulator-vddcpu-b { + /* + * MP8756GD Regulator. + */ + compatible = "pwm-regulator"; + regulator-name = "VDDCPU_B"; + regulator-min-microvolt = <689000>; + regulator-max-microvolt = <1049000>; + pwm-supply = <&dc_in>; + pwms = <&pwm_ao_ab 1 1500 0>; + pwm-dutycycle-range = <100 0>; + regulator-boot-on; + regulator-always-on; + }; + vddio_1v8: regulator-vddio-1v8 { compatible = "regulator-fixed"; regulator-name = "VDDIO_1V8"; @@ -172,9 +262,27 @@ &pwm_ab { pinctrl-names = "default"; }; +&pwm_ao_ab { + status = "okay"; + pinctrl-0 = <&pwm_ao_a_pins>, <&pwm_ao_b_pins>; + pinctrl-names = "default"; +}; + &pwm_ao_cd { status = "okay"; - pinctrl-0 = <&pwm_ao_c_d_pins>; + pinctrl-0 = <&pwm_ao_c_d_pins>, <&pwm_ao_d_pins>; + pinctrl-names = "default"; +}; + +&pwm_ao_ef { + status = "okay"; + pinctrl-0 = <&pwm_ao_e_pins>, <&pwm_ao_f_pins>; + pinctrl-names = "default"; +}; + +&pwm_ao_gh { + status = "okay"; + pinctrl-0 = <&pwm_ao_g_e_pins>; pinctrl-names = "default"; }; From 406292fd75f95aa3010fec95b5beb5a8b7e3ba3a Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sat, 29 Aug 2026 10:47:58 +0100 Subject: [PATCH 0283/2366] arm64: dts: amlogic: t7: fix the pin groups of the vsync PWM The vsync PWM output can appear on either of two pins, but the description named a single group that does not exist, so anything using it would refuse to start. Name the real groups instead, one entry per pin. No board describes this output yet, so nothing changes today. Fixes: 2a2a7b9701a7 ("arm64: dts: amlogic: t7: Add PWM pinctrl nodes") Assisted-by: Claude:claude-opus-5 Signed-off-by: Lucas Tanure Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/20260829094758.23248-5-tanure@linux.com Signed-off-by: Neil Armstrong --- arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi index 8c4bd683cce0..719e111bc3dd 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi @@ -546,9 +546,17 @@ mux { }; }; - pwm_vs_pins: pwm-vs { + pwm_vs_y_pins: pwm-vs-y { mux { - groups = "pwm_vs"; + groups = "pwm_vs_y"; + function = "pwm_vs"; + bias-disable; + }; + }; + + pwm_vs_h_pins: pwm-vs-h { + mux { + groups = "pwm_vs_h"; function = "pwm_vs"; bias-disable; }; From fbf2c660bac863f0ac372b677ee55bf775c94594 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 31 Aug 2026 10:45:06 +0100 Subject: [PATCH 0284/2366] ASoC: cs35l56: Fix pm_runtime imbalance if suspending before first attach Remove the check for init_done in cs35l56_sdw_system_suspend(). Instead, protect the call to cs35l56_mask_soundwire_interrupts() to only be done if the amp is currently enumerated. This fixes a runtime imbalance if cs35l56_sdw_system_suspend() is called before the first SoundWire attach. This would skip the call to pm_runtime_force_suspend() in cs35l56_system_suspend(). But resume unconditionally called pm_runtime_force_resume() leading to an imbalance. cs35l56_system_suspend() doesn't have any dependency on completion of cs35l56_init(), so there is no need for the skip on !init_done in cs35l56_sdw_system_suspend(). Fixes: f9dc6b875ec0a ("ASoC: cs35l56: Add basic system suspend handling") Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260831094506.57467-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56-sdw.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c index 4fba59e80c37..98bb4542b914 100644 --- a/sound/soc/codecs/cs35l56-sdw.c +++ b/sound/soc/codecs/cs35l56-sdw.c @@ -386,11 +386,8 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) { struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); - if (!cs35l56->base.init_done) - return 0; - - /* runtime_resume unmasks the interrupt */ - cs35l56_mask_soundwire_interrupts(cs35l56); + if (cs35l56->sdw_attached) + cs35l56_mask_soundwire_interrupts(cs35l56); return cs35l56_system_suspend(dev); } From ca1f4a5ecab084af7f405baa902edbed171b57e6 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 13 Aug 2026 15:25:05 +0200 Subject: [PATCH 0285/2366] s390/time: Use jiffies instead of jiffies_64 Christoph Schlameuss and Alexander Egorenkov reported a data-race reported by KCSAN when jiffies_64 is read: ================================================================== BUG: KCSAN: data-race in do_account_vtime / tick_do_update_jiffies64 write to 0x0000016599ea8600 of 8 bytes by interrupt on cpu 6: tick_do_update_jiffies64+0x140/0x250 =============================================================> BUG: KCSAN: data-race in do_account_vtime / tick_do_update_ji> write to 0x0000016599ea8600 of 8 bytes by interrupt on cpu 6: tick_do_update_jiffies64+0x140/0x250 tick_nohz_handler+0x2e6/0x300 __run_hrtimer+0x156/0x4d0 __hrtimer_run_queues+0xd2/0x150 ... system_call+0x72/0x90 read to 0x0000016599ea8600 of 8 bytes by interrupt on cpu 12: do_account_vtime+0x7d6/0x860 vtime_flush+0x26/0xe0 update_process_times+0x32/0x160 tick_nohz_handler+0x12a/0x300 ... system_call+0x72/0x90 value changed: 0x00000000ffffaa6c -> 0x00000000ffffaa6d ... =============================================================> Problem is that jiffies_64 instead of jiffies is used. Both are at the same address, but only jiffies is of volatile type, which prevents this warning. Change the vtime code so jiffies instead of jiffies_64 is used everywhere. This addresses also the inconsistency that both jiffies and jiffies_64 were used in the original patch which introduced this. Fixes: f341b8dff982 ("s390/vtime: limit MT scaling value updates") Reported-by: Christoph Schlameuss Reported-by: Alexander Egorenkov Reviewed-by: Alexander Egorenkov Tested-by: Alexander Egorenkov Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/kernel/vtime.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/s390/kernel/vtime.c b/arch/s390/kernel/vtime.c index d804e1140c2e..efcbf406f03e 100644 --- a/arch/s390/kernel/vtime.c +++ b/arch/s390/kernel/vtime.c @@ -32,7 +32,7 @@ static atomic64_t virt_timer_elapsed; DEFINE_PER_CPU(u64, mt_cycles[8]); static DEFINE_PER_CPU(u64, mt_scaling_mult) = { 1 }; static DEFINE_PER_CPU(u64, mt_scaling_div) = { 1 }; -static DEFINE_PER_CPU(u64, mt_scaling_jiffies); +static DEFINE_PER_CPU(unsigned long, mt_scaling_jiffies); static inline void set_vtimer(u64 expires) { @@ -81,7 +81,7 @@ static void update_mt_scaling(void) memcpy(cycles_old, cycles_new, sizeof(u64) * (smp_cpu_mtid + 1)); } - __this_cpu_write(mt_scaling_jiffies, jiffies_64); + __this_cpu_write(mt_scaling_jiffies, jiffies); } static inline u64 update_tsk_timer(unsigned long *tsk_vtime, u64 new) @@ -144,7 +144,7 @@ static int do_account_vtime(struct task_struct *tsk) lc->system_timer += timer; /* Update MT utilization calculation */ - if (smp_cpu_mtid && time_after64(jiffies_64, __this_cpu_read(mt_scaling_jiffies))) + if (smp_cpu_mtid && time_after(jiffies, __this_cpu_read(mt_scaling_jiffies))) update_mt_scaling(); /* Calculate cputime delta */ From b00c10948fa4c9b1f3e2814b97f299ad970f94c8 Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Fri, 14 Aug 2026 14:15:25 +0200 Subject: [PATCH 0286/2366] s390/cpacf: Unpoison instruction results Stop KMSAN from complaining about CPACF outputs being uninitialized. Do not unpoison variable-length parameter blocks: mapping function codes (like CPACF_KIMD_SHA_256) to lengths will be ugly. So let the callers do this once the need arises. Also do not touch cpacf_kma(): this wrapper does not handle cc 1 and cc2 at the moment and has to be reworked. Reviewed-by: Harald Freudenberger Signed-off-by: Ilya Leoshkevich Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/include/asm/cpacf.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/s390/include/asm/cpacf.h b/arch/s390/include/asm/cpacf.h index a83683169d98..6174552d856d 100644 --- a/arch/s390/include/asm/cpacf.h +++ b/arch/s390/include/asm/cpacf.h @@ -301,6 +301,7 @@ static __always_inline void __cpacf_query(unsigned int opcode, cpacf_mask_t *mask) { __cpacf_query_insn(opcode, mask, CPACF_FC_QUERY); + kmsan_unpoison_memory(mask, sizeof(*mask)); } static __always_inline int __cpacf_check_opcode(unsigned int opcode) @@ -370,6 +371,7 @@ static __always_inline int cpacf_query_func(unsigned int opcode, static __always_inline void __cpacf_qai(unsigned int opcode, cpacf_qai_t *qai) { __cpacf_query_insn(opcode, qai, CPACF_FC_QUERY_AUTH_INFO); + kmsan_unpoison_memory(qai, sizeof(*qai)); } /** @@ -422,6 +424,7 @@ static inline int cpacf_km(unsigned long func, void *param, [opc] "i" (CPACF_KM) : "cc", "memory", "0", "1"); + kmsan_unpoison_memory(dest, src_len - s.odd); return src_len - s.odd; } @@ -454,6 +457,7 @@ static inline int cpacf_kmc(unsigned long func, void *param, [opc] "i" (CPACF_KMC) : "cc", "memory", "0", "1"); + kmsan_unpoison_memory(dest, src_len - s.odd); return src_len - s.odd; } @@ -587,6 +591,7 @@ static inline int cpacf_kmctr(unsigned long func, void *param, u8 *dest, [opc] "i" (CPACF_KMCTR) : "cc", "memory", "0", "1"); + kmsan_unpoison_memory(dest, src_len - s.odd); return src_len - s.odd; } @@ -619,6 +624,7 @@ static inline void cpacf_prno(unsigned long func, void *param, : [fc] "d" (func), [pba] "d" ((unsigned long)param), [seed] "d" (s.pair), [opc] "i" (CPACF_PRNO) : "cc", "memory", "0", "1"); + kmsan_unpoison_memory(dest, dest_len); } /** From f3c63b8cabbb121866347fe164c23635965b31d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Thu, 6 Aug 2026 18:21:19 +0200 Subject: [PATCH 0287/2366] s390/ap: Drop unused member from ap_device_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ap_device_id::driver_info is not used in the kernel. The structure is also not part of API/ABI, so the unused member can just be dropped. Signed-off-by: Uwe Kleine-König (The Capable Hub) Acked-by: Holger Dengler Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- include/linux/device-id/ap.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/linux/device-id/ap.h b/include/linux/device-id/ap.h index 0992333a34db..e050abebbf3d 100644 --- a/include/linux/device-id/ap.h +++ b/include/linux/device-id/ap.h @@ -4,7 +4,6 @@ #ifdef __KERNEL__ #include -typedef unsigned long kernel_ulong_t; #endif #define AP_DEVICE_ID_MATCH_CARD_TYPE 0x01 @@ -14,7 +13,6 @@ typedef unsigned long kernel_ulong_t; struct ap_device_id { __u16 match_flags; /* which fields to match against */ __u8 dev_type; /* device type */ - kernel_ulong_t driver_info; }; #endif /* ifndef LINUX_DEVICE_ID_AP_H */ From 7f918871112e8e7c581e99eb8e545af4e59c8367 Mon Sep 17 00:00:00 2001 From: Vasily Gorbik Date: Thu, 13 Aug 2026 13:06:54 +0200 Subject: [PATCH 0288/2366] s390/ipl: Fix NULL deref in kdump without re-IPL parm block Some IPL types, like HMC FTP boot or QEMU direct kernel boot, might not provide an IPL parameter block. In this case, reipl_type_init() selects IPL_TYPE_UNKNOWN, and reipl_block_actual remains NULL. kdump passes the re-IPL parameter block to the dump kernel through os_info. Before commit 3b9678472bab ("s390/ipl: correct kdump reipl block checksum calculation"), the os_info entry was added only for IPL types which initialized reipl_block_actual. That commit moved the os_info update to machine_crash_shutdown(), making it unconditional. As a result, set_os_info_reipl_block() dereferences reipl_block_actual for IPL_TYPE_UNKNOWN. This may happen to work by chance when address zero contains readable lowcore data and the resulting empty os_info entry is ignored by the dump kernel. Skip the os_info update when no re-IPL parameter block is available. Kdump then collect the dump and reboot without setting re-IPL parameter block. Fixes: 3b9678472bab ("s390/ipl: correct kdump reipl block checksum calculation") Reviewed-by: Heiko Carstens Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/kernel/ipl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/s390/kernel/ipl.c b/arch/s390/kernel/ipl.c index d74ef30155aa..8c672e36b397 100644 --- a/arch/s390/kernel/ipl.c +++ b/arch/s390/kernel/ipl.c @@ -1157,6 +1157,8 @@ static struct attribute_group reipl_nss_attr_group = { void set_os_info_reipl_block(void) { + if (!reipl_block_actual) + return; os_info_entry_add_data(OS_INFO_REIPL_BLOCK, reipl_block_actual, reipl_block_actual->hdr.len); } From 37f61b71cbc0caefc01022a19ee56fc2510e2e6e Mon Sep 17 00:00:00 2001 From: Vasily Gorbik Date: Thu, 13 Aug 2026 13:06:55 +0200 Subject: [PATCH 0289/2366] s390/ipl: Fix NULL deref in dump_reipl without re-IPL parm block Unlike kdump, which passes the re-IPL parameter block through os_info, the stand-alone dump passes it through the IPL parm block address and checksum in lowcore. Some IPL types, like HMC FTP boot or QEMU direct kernel boot, might not provide an IPL parameter block. In this case reipl_type_init() selects IPL_TYPE_UNKNOWN and reipl_block_actual remains NULL. Nevertheless, dump_reipl_run() unconditionally dereferences it when preparing the lowcore fields. This may happen to work by chance when address zero contains readable lowcore data. A zero IPL parameter block address is then stored in lowcore, causing the stand-alone dumper to enter disabled wait after completing the dump. Explicitly store a zero IPL parameter block address and checksum when no re-IPL parameter block is available. This does not change the behavior: the stand-alone dumper completes the dump and halts, while valid re-IPL parameter blocks continue to be handled as before. Fixes: 099b76513992 ("[S390] Automatic IPL after dump") Reviewed-by: Mikhail Zaslonko Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/kernel/ipl.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/arch/s390/kernel/ipl.c b/arch/s390/kernel/ipl.c index 8c672e36b397..b1e798f8e1dd 100644 --- a/arch/s390/kernel/ipl.c +++ b/arch/s390/kernel/ipl.c @@ -1929,7 +1929,8 @@ static struct shutdown_action __refdata dump_action = { static void dump_reipl_run(struct shutdown_trigger *trigger) { struct lowcore *abs_lc; - unsigned int csum; + unsigned long ipib = 0; + unsigned int csum = 0; /* * Set REIPL_CLEAR flag in os_info flags entry indicating @@ -1945,9 +1946,12 @@ static void dump_reipl_run(struct shutdown_trigger *trigger) reipl_type == IPL_TYPE_UNKNOWN) os_info_flags |= OS_INFO_FLAG_REIPL_CLEAR; os_info_entry_add_data(OS_INFO_FLAGS_ENTRY, &os_info_flags, sizeof(os_info_flags)); - csum = (__force unsigned int)cksm(reipl_block_actual, reipl_block_actual->hdr.len, 0); + if (reipl_block_actual) { + ipib = __pa(reipl_block_actual); + csum = (__force unsigned int)cksm(reipl_block_actual, reipl_block_actual->hdr.len, 0); + } abs_lc = get_abs_lowcore(); - abs_lc->ipib = __pa(reipl_block_actual); + abs_lc->ipib = ipib; abs_lc->ipib_checksum = csum; put_abs_lowcore(abs_lc); dump_run(trigger); From 8ac60ae2a307a50b599bf5d300b448d638f3ba29 Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Thu, 6 Aug 2026 11:43:39 +0200 Subject: [PATCH 0290/2366] s390/pci: Fix leak of uninitialized kernel data in SCLP report While report_error_write() checks that the provided buffer is at least as large as the header struct, but not that it is large enough to contain the report with the length claimed by report->length. If user-space provides a short buffer, meaning a larger report->length than the actually written payload, up to around 4K of kernel data from past the kmalloc(len + 1) sized buffer allocated in kernfs_fop_write_iter() will leak into the SCLP report. However, as the entity processing the SCLP is privileged and able to access at least the page including the report, this does not leak data that entity could not access but it is still an out of bounds read and a malformed error report that should be rejected. Fixes: 368704a65be8 ("s390/pci: add report_error attribute") Cc: stable@vger.kernel.org Signed-off-by: Niklas Schnelle Reviewed-by: Benjamin Block Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_sysfs.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/s390/pci/pci_sysfs.c b/arch/s390/pci/pci_sysfs.c index d98d97df792a..bbb76113a4d0 100644 --- a/arch/s390/pci/pci_sysfs.c +++ b/arch/s390/pci/pci_sysfs.c @@ -153,6 +153,9 @@ static ssize_t report_error_write(struct file *filp, struct kobject *kobj, if (off || (count < sizeof(*report))) return -EINVAL; + if (count < (report->length + sizeof(*report))) + return -EINVAL; + ret = sclp_pci_report(report, zdev->fh, zdev->fid); return ret ? ret : count; From a91a5c25a2c3f652178b591facc2395a7dbb59af Mon Sep 17 00:00:00 2001 From: Holger Dengler Date: Thu, 20 Aug 2026 17:50:03 +0200 Subject: [PATCH 0291/2366] s390/zcrypt: Validate length in reply before using it The length information in the reply is used to copy the key token to the target buffer. An invalid information in t->len of the reply may cause an over-read of the target buffer and also a over-write of the target buffer. To prevent that, check t->len before using it. As the available space in destination and source buffer is always larger than the valid length value in the parameter block in the reply, compare t->len with this (already validated) length information. As a side effect, this check also prevents buffer over-read and over-write. Reviewed-by: Harald Freudenberger Signed-off-by: Holger Dengler Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- drivers/s390/crypto/zcrypt_ccamisc.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/s390/crypto/zcrypt_ccamisc.c b/drivers/s390/crypto/zcrypt_ccamisc.c index d4ce6352b5b2..19909bf43dc9 100644 --- a/drivers/s390/crypto/zcrypt_ccamisc.c +++ b/drivers/s390/crypto/zcrypt_ccamisc.c @@ -1158,8 +1158,21 @@ static int _ip_cprb_helper(u16 cardnr, u16 domain, /* do not check the key here, it may be incomplete */ - /* copy the vlsc key token back */ + /* + * Copy the vlsc key token back. + * The available space in the destination (key_token) and the source + * (t) buffer is always larger as the valid range of prepparm->kb.len. + * Validate t->len by comparing it with the length information in the + * param block of the request (prepparm->kb.len) + * The value range of prepparm->kb.len has been checked above. + */ t = (struct cipherkeytoken *)prepparm->kb.tlv1.key_token; + if (t->len != prepparm->kb.len - 3 * sizeof(uint16_t)) { + ZCRYPT_DBF_ERR("%s reply with invalid key_token length %u\n", + __func__, t->len); + rc = -EIO; + goto out; + } memcpy(key_token, t, t->len); *key_token_size = t->len; From 439077c39d8f7108aea4dd8d4d819b9b864fe84c Mon Sep 17 00:00:00 2001 From: Sumanth Korikkar Date: Tue, 11 Aug 2026 16:23:06 +0200 Subject: [PATCH 0292/2366] s390/diag324: Preserve -EBUSY return code When diag324 reports -EBUSY, the error code is overwritten by the result of copy_to_user() and put_user(). As a result, the ioctl may incorrectly return success instead of -EBUSY. Preserve the original diag324 return code and only return -EFAULT when copying data to userspace fails. Fixes: 90e6f191e1ee ("s390/diag324: Retrieve power readings via diag 0x324") Signed-off-by: Sumanth Korikkar Reviewed-by: Heiko Carstens Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/kernel/diag/diag324.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/s390/kernel/diag/diag324.c b/arch/s390/kernel/diag/diag324.c index fe325c2a2d0d..3eec0cc8fb9e 100644 --- a/arch/s390/kernel/diag/diag324.c +++ b/arch/s390/kernel/diag/diag324.c @@ -182,8 +182,7 @@ long diag324_pibbuf(unsigned long arg) goto out; rc = copy_to_user((void __user *)address, data->pib, data->pib->len); rc |= put_user(data->sequence, &udata->sequence); - if (rc) - rc = -EFAULT; + rc = rc ? -EFAULT : data->rc; out: mutex_unlock(&pibmutex); return rc; From f3110e969ad226ffbb2d9b4bf5387e68d0d9ef40 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Wed, 19 Aug 2026 07:58:55 +0200 Subject: [PATCH 0293/2366] s390/pai: Handle multiple PMU stop callback invocations Handle the following scenario: The kernel protects itself against a very high sampling load and throttles the sampling using: perf_event_throttle() --> PMU->stop() Shortly later the scheduler may terminate the task and removes it from the CPU. It again calls PMU->stop() which results in two invocations of PMU->stop() called back to back. Protect against this and check the PERF_HES_STOPPED bit on function entry. If it is already set return. Clear bit PERF_HES_STOPPED in PMU->start(). Prohibit ioctl(fd, PERF_EVENT_IOC_PERIOD, ...) call for this event. It sets perf_event::event_limit to a positive value and causes perf_event_overflow() to invoke pai_stop() call back function when perf_event::event_limit hits zero. This is not supported because the sample events CRYPTO_ALL and NNPA_ALL are only taken at schedule out of a task. Use list_for_each_entry_safe() for safe iteration over syswide_list in pai_have_samples(). Fixes: 9f66572f2889 ("s390/pai_crypto: Enable per-task and system-wide sampling event") Fixes: 582cc1b28e8c ("s390/pai_ext: Enable per-task and system-wide sampling event") Cc: stable@vger.kernel.org # v6.19+ Signed-off-by: Thomas Richter Reviewed-by: Sumanth Korikkar Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/kernel/perf_pai.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/arch/s390/kernel/perf_pai.c b/arch/s390/kernel/perf_pai.c index cdb8006220ca..05f74d74fad1 100644 --- a/arch/s390/kernel/perf_pai.c +++ b/arch/s390/kernel/perf_pai.c @@ -464,6 +464,7 @@ static void pai_start(struct perf_event *event, int flags, cpump->event = event; } } + event->hw.state &= ~PERF_HES_STOPPED; } static void paicrypt_start(struct perf_event *event, int flags) @@ -510,6 +511,13 @@ static void pai_stop(struct perf_event *event, int flags) struct pai_mapptr *mp = this_cpu_ptr(pai_root[idx].mapptr); struct pai_map *cpump = mp->mapptr; + /* Cope with multiple invocations: + * 1. perf_event_throttle() --> PMU->stop() + * 2. task schedules out --> PMU->stop() + * Check for event already stopped. + */ + if (event->hw.state & PERF_HES_STOPPED) + return; if (!event->attr.sample_period) { /* Counting */ pai_pmu[idx].pmu->read(event); } else { /* Sampling */ @@ -672,9 +680,9 @@ static void pai_have_samples(int idx) { struct pai_mapptr *mp = this_cpu_ptr(pai_root[idx].mapptr); struct pai_map *cpump = mp->mapptr; - struct perf_event *event; + struct perf_event *event, *e2; - list_for_each_entry(event, &cpump->syswide_list, hw.tp_list) + list_for_each_entry_safe(event, e2, &cpump->syswide_list, hw.tp_list) pai_have_sample(event, cpump); } @@ -691,6 +699,17 @@ static void paicrypt_sched_task(struct perf_event_pmu_context *pmu_ctx, pai_have_samples(PAI_PMU_CRYPTO); } +/* Prevent ioctl(fd, PERF_EVENT_IOC_PERIOD, ...) call. + * It sets perf_event::event_limit to a positive value and causes + * perf_event_overflow() to invoke pai_stop() call back function when + * perf_event::event_limit hits zero. This is not supported because the + * sample events CRYPTO_ALL and NNPA_ALL are always taken at schedule out + * of a task. + */ +static int pai_check_period(struct perf_event *event, u64 value) +{ + return -EINVAL; +} /* ============================= paiext ====================================*/ static void paiext_event_destroy(struct perf_event *event) @@ -804,6 +823,7 @@ static struct pmu paicrypt = { .stop = paicrypt_stop, .read = paicrypt_read, .sched_task = paicrypt_sched_task, + .check_period = pai_check_period, .attr_groups = paicrypt_attr_groups }; @@ -1015,6 +1035,7 @@ static struct pmu paiext = { .stop = paiext_stop, .read = paiext_read, .sched_task = paiext_sched_task, + .check_period = pai_check_period, .attr_groups = paiext_attr_groups, }; From 8cff0ac21658fedd4598e9904dd0c518bdaf5856 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Tue, 25 Aug 2026 11:49:25 +0200 Subject: [PATCH 0294/2366] s390/pai: Reduce excessive debug feature size The pai debug feature is registered with 256 areas, where each area contains 32 pages. This sums up to a total of 32MiB. The code does not use any debug exceptions, which means that 255 of those areas are never used. In addition all existing debug feature calls have a lower level (5) than the default level (3). This in turn means that without user interaction the debug feature is unused. Reduce the number of areas to 1, and also reduce the number of pages for the remaining area to 1. Since user interaction is required, the user can also increase the size of the remaining area, instead of wasting memory by default. This reduces the total size of the debug feature to 4KiB. Fixes: a3f8423622ef ("s390/pai_crypto: Add PAI crypto characteristics table for parameters") Reviewed-by: Thomas Richter Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/kernel/perf_pai.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/kernel/perf_pai.c b/arch/s390/kernel/perf_pai.c index 05f74d74fad1..5c18c8b82ab7 100644 --- a/arch/s390/kernel/perf_pai.c +++ b/arch/s390/kernel/perf_pai.c @@ -1242,7 +1242,7 @@ static int __init paipmu_setup(void) static int __init pai_init(void) { /* Setup s390dbf facility */ - paidbg = debug_register("pai", 32, 256, 128); + paidbg = debug_register("pai", 1, 1, 128); if (!paidbg) { pr_err("Registration of s390dbf pai failed\n"); return -ENOMEM; From bb06e5a2a031c89b1f1f60ff45ce80f8e4f6ee56 Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Tue, 25 Aug 2026 14:58:20 +0200 Subject: [PATCH 0295/2366] s390/topology: Switch to common cpu capacity code s390 implementation of cpu capacity management infrastructure code does not do anything different than its common code counterpart. Switch to common code functions and remove the smp_cpu_*_capacity() functions. Make s390 code better align with other architectures which utilize cpu_capacity. No functional changes. Allow cpu_capacity attributes inside sysfs to accurately reflect cpu capacity. ex: $ cat /sys/devices/system/cpu/cpu0/polarization vertical:high $ cat /sys/devices/system/cpu/cpu0/cpu_capacity 1024 $ cat /sys/devices/system/cpu/cpu40/polarization vertical:low $ cat /sys/devices/system/cpu/cpu40/cpu_capacity 128 Prior to commit 6bceea7a1e07 ("arch_topology: Relocate cpu_scale to topology.[h|c]") cpu_capacity attribute was only available to the common arch_topology driver's users. Reflect the correct values to the newly made available attributes. Signed-off-by: Mete Durlu Reviewed-by: Heiko Carstens Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/include/asm/processor.h | 1 - arch/s390/include/asm/smp.h | 4 +--- arch/s390/kernel/smp.c | 16 +++------------- arch/s390/kernel/topology.c | 2 +- 4 files changed, 5 insertions(+), 18 deletions(-) diff --git a/arch/s390/include/asm/processor.h b/arch/s390/include/asm/processor.h index be8369115f6d..9434c76c25b8 100644 --- a/arch/s390/include/asm/processor.h +++ b/arch/s390/include/asm/processor.h @@ -46,7 +46,6 @@ struct pcpu { unsigned long ec_mask; /* bit mask for ec_xxx functions */ unsigned long ec_clk; /* sigp timestamp for ec_xxx */ unsigned long flags; /* per CPU flags */ - unsigned long capacity; /* cpu capacity for scheduler */ signed char state; /* physical cpu state */ signed char polarization; /* physical polarization */ u16 address; /* physical cpu address */ diff --git a/arch/s390/include/asm/smp.h b/arch/s390/include/asm/smp.h index fb2bdbf35da5..a6c621e0491c 100644 --- a/arch/s390/include/asm/smp.h +++ b/arch/s390/include/asm/smp.h @@ -30,7 +30,7 @@ static __always_inline unsigned int raw_smp_processor_id(void) return cpu; } -#define arch_scale_cpu_capacity smp_cpu_get_capacity +#define arch_scale_cpu_capacity topology_get_cpu_scale extern struct mutex smp_cpu_state_mutex; extern unsigned int smp_cpu_mt_shift; @@ -53,9 +53,7 @@ extern void smp_save_dump_secondary_cpus(void); extern void smp_yield_cpu(int cpu); extern void smp_cpu_set_polarization(int cpu, int val); extern int smp_cpu_get_polarization(int cpu); -extern void smp_cpu_set_capacity(int cpu, unsigned long val); extern void smp_set_core_capacity(int cpu, unsigned long val); -extern unsigned long smp_cpu_get_capacity(int cpu); extern int smp_cpu_get_cpu_address(int cpu); extern void smp_fill_possible_mask(void); extern void smp_detect_cpus(void); diff --git a/arch/s390/kernel/smp.c b/arch/s390/kernel/smp.c index 167c72803ccf..32499cad86f0 100644 --- a/arch/s390/kernel/smp.c +++ b/arch/s390/kernel/smp.c @@ -659,23 +659,13 @@ int smp_cpu_get_polarization(int cpu) return per_cpu(pcpu_devices, cpu).polarization; } -void smp_cpu_set_capacity(int cpu, unsigned long val) -{ - per_cpu(pcpu_devices, cpu).capacity = val; -} - -unsigned long smp_cpu_get_capacity(int cpu) -{ - return per_cpu(pcpu_devices, cpu).capacity; -} - void smp_set_core_capacity(int cpu, unsigned long val) { int i; cpu = smp_get_base_cpu(cpu); for (i = cpu; (i <= cpu + smp_cpu_mtid) && (i < nr_cpu_ids); i++) - smp_cpu_set_capacity(i, val); + topology_set_cpu_scale(i, val); } int smp_cpu_get_cpu_address(int cpu) @@ -727,7 +717,7 @@ static int smp_add_core(struct sclp_core_entry *core, cpumask_t *avail, else pcpu->state = CPU_STATE_STANDBY; smp_cpu_set_polarization(cpu, POLARIZATION_UNKNOWN); - smp_cpu_set_capacity(cpu, CPU_CAPACITY_HIGH); + topology_set_cpu_scale(cpu, CPU_CAPACITY_HIGH); set_cpu_present(cpu, true); if (!early && arch_register_cpu(cpu)) set_cpu_present(cpu, false); @@ -967,7 +957,7 @@ void __init smp_prepare_boot_cpu(void) ipl_pcpu->state = CPU_STATE_CONFIGURED; lc->pcpu = (unsigned long)ipl_pcpu; smp_cpu_set_polarization(0, POLARIZATION_UNKNOWN); - smp_cpu_set_capacity(0, CPU_CAPACITY_HIGH); + topology_set_cpu_scale(0, CPU_CAPACITY_HIGH); } void __init smp_setup_processor_id(void) diff --git a/arch/s390/kernel/topology.c b/arch/s390/kernel/topology.c index 1377c6f3f670..42fc0294f543 100644 --- a/arch/s390/kernel/topology.c +++ b/arch/s390/kernel/topology.c @@ -147,7 +147,7 @@ static void add_cpus_to_mask(struct topology_core *tl_core, cpumask_set_cpu(cpu, &book->mask); cpumask_set_cpu(cpu, &socket->mask); smp_cpu_set_polarization(cpu, tl_core->pp); - smp_cpu_set_capacity(cpu, CPU_CAPACITY_HIGH); + topology_set_cpu_scale(cpu, CPU_CAPACITY_HIGH); } } } From a8603b52b39f520ea8a34def74c23fba87396d3e Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Fri, 28 Aug 2026 19:08:09 -0300 Subject: [PATCH 0296/2366] smb: client: fix data corruption with concurrent writes and O_TRUNC cifs_do_truncate() flushes dirty pages with filemap_write_and_wait() and truncates the file on the server, but in the old code both operations ran without holding i_rwsem or invalidate_lock. A concurrent buffered write via netfs_perform_write() -- which only needs i_rwsem shared -- could dirty new pages after the flush but before the local truncation, and those pages would be silently discarded by cifs_setsize() -> truncate_pagecache(). Fix by acquiring inode_lock (exclusive i_rwsem) and filemap_invalidate_lock at the top of cifs_do_truncate(), so the entire flush-truncate-resize sequence is atomic with respect to: - buffered writes (blocked by exclusive i_rwsem, since netfs_start_io_write takes i_rwsem shared), - read page faults (blocked by exclusive invalidate_lock, since filemap_fault takes it shared), - writeback collection (blocked by netfs_wb_begin/netfs_wb_end around the server truncate and local resize, since netfs_writepages also acquires the wb lock). Fixes: 110fee6b9bb5 ("smb: client: fix missing timestamp updates with O_TRUNC") Signed-off-by: Paulo Alcantara Reviewed-by: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/file.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 100acc76e9be..61f9c6ccc6be 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -999,42 +999,50 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry) struct cifs_tcon *tcon; int rc; - rc = filemap_write_and_wait(inode->i_mapping); - if (is_interrupt_error(rc)) + rc = inode_lock_killable(inode); + if (rc) return -ERESTARTSYS; + + filemap_invalidate_lock(inode->i_mapping); + + rc = filemap_write_and_wait(inode->i_mapping); + if (is_interrupt_error(rc)) { + rc = -ERESTARTSYS; + goto out; + } mapping_set_error(inode->i_mapping, rc); cfile = find_writable_file(cinode, FIND_FSUID_ONLY); rc = cifs_file_flush(xid, inode, cfile); if (!rc) { if (cfile) { + struct netfs_inode *ictx = netfs_inode(inode); + tcon = tlink_tcon(cfile->tlink); server = tcon->ses->server; + netfs_wb_begin(ictx, false); rc = server->ops->set_file_size(xid, tcon, cfile, 0, false); if (!rc) { - inode_lock(inode); - filemap_invalidate_lock(inode->i_mapping); netfs_resize_file(&cinode->netfs, 0, true); cifs_setsize(inode, 0); - filemap_invalidate_unlock(inode->i_mapping); - inode_unlock(inode); cifs_invalidate_cache(inode, 0); } + netfs_wb_end(ictx); } else { /* * No cached handle; evict stale pages so they can't * be served after the file is later extended; let * the server's O_TRUNC open response set the i_size */ - inode_lock(inode); - filemap_invalidate_lock(inode->i_mapping); truncate_inode_pages(inode->i_mapping, 0); - filemap_invalidate_unlock(inode->i_mapping); - inode_unlock(inode); cifs_invalidate_cache(inode, 0); } } + +out: + filemap_invalidate_unlock(inode->i_mapping); + inode_unlock(inode); if (cfile) cifsFileInfo_put(cfile); return rc; From 4aa2c106aef4bf3dfd97c30842db0767b26e8428 Mon Sep 17 00:00:00 2001 From: Yunpeng Tian Date: Sun, 30 Aug 2026 18:46:56 -0700 Subject: [PATCH 0297/2366] smb: client: reject SetEA requests that do not fit the request buffer CIFSSMBSetEA() copies the caller's extended attribute value into the SMB request buffer without checking that it fits. The requirement is stated in the source but was never implemented: /*BB add length check to see if it would fit in negotiated SMB buffer size BB */ /* if (ea_value_len > buffer_size - 512 (enough for header)) */ if (ea_value_len) memcpy(parm_data->list.name + name_len + 1, ea_value, ea_value_len); The only bound applied on the way in is in cifs_xattr_set(): #define MAX_EA_VALUE_SIZE CIFSMaxBufSize ... if (size > MAX_EA_VALUE_SIZE) CIFSMaxBufSize is the full payload capacity of the buffer, so a value of exactly that size leaves no room for the SMB header, the TRANS2 parameter block, the fealist header and the EA name that are written ahead of it in the same object. SendReceive() already enforces the correct limit on this very length: if (in_len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) but it is called after the copy has taken place. An unprivileged setxattr(2) on an SMB1 mount with a 250-byte name and a 16384-byte value writes 16384 bytes starting 345 bytes into a 16588-byte cifs_request object, ending 141 bytes past it: BUG: KASAN: slab-out-of-bounds in CIFSSMBSetEA+0xabc/0xde0 Write of size 16384 at addr ffff888003aa0159 by task init/68 __asan_memcpy+0x3c/0x60 CIFSSMBSetEA+0xabc/0xde0 cifs_xattr_set+0xd3a/0xff0 __vfs_setxattr+0x13e/0x1a0 The buggy address is located 345 bytes inside of allocated 16588-byte region Apply SendReceive()'s limit to the assembled request before the copy rather than after it, and widen the byte counters so the sum cannot wrap before it is tested. byte_count is also tested against U16_MAX, because it is stored in the 16-bit pSMB->ByteCount. That becomes reachable when CIFSMaxBufSize is raised at module load, where it may be set as high as 1024*127: with a 5-byte EA name and a 65521-byte value, count is exactly U16_MAX while byte_count is 65556, and cpu_to_le16() would truncate it to 20 and transmit a frame whose ByteCount does not match its length. Testing byte_count covers count as well, since byte_count is the larger of the two and count's only 16-bit consumer is written after this point. check_add_overflow() is evaluated first so that total_len is assigned before it is reported. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Yunpeng Tian Reported-by: Mingda Zhang Reported-by: Gongming Wang Reported-by: Qinrun Dai Cc: stable@vger.kernel.org Signed-off-by: Yunpeng Tian Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 230af243247c..f8aa9e7b4bc6 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -6334,8 +6334,10 @@ CIFSSMBSetEA(const unsigned int xid, struct cifs_tcon *tcon, int name_len; int rc = 0; int bytes_returned = 0; - __u16 params, param_offset, byte_count, offset, count; + __u16 params, param_offset; + unsigned int byte_count, offset, count; int remap = cifs_remap(cifs_sb); + unsigned int total_len; cifs_dbg(FYI, "In SetEA\n"); SetEARetry: @@ -6387,6 +6389,13 @@ CIFSSMBSetEA(const unsigned int xid, struct cifs_tcon *tcon, pSMB->Reserved3 = 0; pSMB->SubCommand = cpu_to_le16(TRANS2_SET_PATH_INFORMATION); byte_count = 3 /* pad */ + params + count; + if (check_add_overflow(in_len, byte_count, &total_len) || + byte_count > U16_MAX || + total_len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) { + cifs_dbg(VFS, "EA request too large: %u bytes\n", total_len); + cifs_buf_release(pSMB); + return -E2BIG; + } pSMB->DataCount = cpu_to_le16(count); parm_data->list_len = cpu_to_le32(count); parm_data->list.EA_flags = 0; From cfc1e9a543e3589ba200795b6e7fd8ef4314efdf Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 22:22:44 +0800 Subject: [PATCH 0298/2366] ARM: socfpga: select the PL310 erratum 753970 workaround ARCH_INTEL_SOCFPGA selects CACHE_L2X0 and several PL310 erratum workarounds. The 753970 workaround is still conditioned on PL310, but that Kconfig symbol no longer exists, so this one selection is always disabled. Select PL310_ERRATA_753970 directly, consistently with the other PL310 workarounds required by the platform. Fixes: fbc125afdc50 ("ARM: socfpga: Turn on ARM errata for L2 cache") Signed-off-by: Pengpeng Hou Signed-off-by: Dinh Nguyen --- arch/arm/mach-socfpga/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/mach-socfpga/Kconfig b/arch/arm/mach-socfpga/Kconfig index eb72c240c248..528c5c1368c3 100644 --- a/arch/arm/mach-socfpga/Kconfig +++ b/arch/arm/mach-socfpga/Kconfig @@ -16,7 +16,7 @@ menuconfig ARCH_INTEL_SOCFPGA select ARM_ERRATA_775420 select PL310_ERRATA_588369 select PL310_ERRATA_727915 - select PL310_ERRATA_753970 if PL310 + select PL310_ERRATA_753970 select PL310_ERRATA_769419 select RESET_CONTROLLER From 34b8b2d78b6276dc2dc4ebc06625a39956f266e4 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Sat, 1 Aug 2026 09:17:31 +0800 Subject: [PATCH 0299/2366] remoteproc: qcom: q6v5_pas: Don't enable handover IRQ on attach qcom_pas_attach() unmasks the handover IRQ and marks handover_issued even though this driver instance never runs qcom_q6v5_prepare() for the boot it is attaching to. This was believed necessary to flush a stale interrupt latched at the interrupt controller while masked, but the handover IRQ is a Qualcomm SMP2P soft IRQ, not a real edge-latched hardware interrupt. The Linux SMP2P driver updates its cached value unconditionally on every notification and only delivers the nested IRQ for bits currently enabled in its own software bitmap, so a transition that happens while masked is simply dropped, never replayed on a later unmask. Since there is nothing to flush, and this driver instance never takes the proxy power-domain/clock/regulator votes that the handover callback would tear down, there is no need to unmask the IRQ in attach() at all. Drop the enable_irq()/disable_irq() pair; setting handover_issued = true is sufficient to keep the flag consistent for the eventual qcom_q6v5_unprepare()/qcom_q6v5_prepare() cycle. It fixes the following unbalanced runtime PM usage and IRQ enable warnings seen on Nord ADSP (probed as attached), after commit bb7c5d6f5b41 ("remoteproc: qcom: q6v5: Make handover IRQ one-shot") comes in place. root@iq10-rrd:~# cat /sys/class/remoteproc/remoteproc0/state attached root@iq10-rrd:~# echo stop > /sys/class/remoteproc/remoteproc0/state [ 40.004874] genpd genpd:0:4c00000.remoteproc: Runtime PM usage count underflow! [ 40.012409] genpd genpd:1:4c00000.remoteproc: Runtime PM usage count underflow! [ 40.050074] remoteproc remoteproc0: stopped remote processor adsp root@iq10-rrd:~# echo start > /sys/class/remoteproc/remoteproc0/state [ 44.350298] remoteproc remoteproc0: powering up adsp [ 44.375769] remoteproc remoteproc0: Booting fw image qcom/nord/adsp.mbn, size 8241816 [ 44.389850] PDM: no support for the platform, userspace daemon might be required. [ 44.397864] ------------[ cut here ]------------ [ 44.402633] Unbalanced enable for IRQ 363 [ 44.406779] WARNING: kernel/irq/manage.c:775 at __enable_irq+0x4c/0x7c, CPU#9: sh/791 ... Suggested-by: Stephan Gerhold Signed-off-by: Shawn Guo Reviewed-by: Abel Vesa Link: https://lore.kernel.org/r/20260801011731.1084591-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_pas.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index ca8e61254c44..8cff0684fe2f 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -524,7 +524,6 @@ static int qcom_pas_attach(struct rproc *rproc) int ret; pas->q6v5.handover_issued = true; - enable_irq(pas->q6v5.handover_irq); pas->q6v5.running = true; ret = irq_get_irqchip_state(pas->q6v5.fatal_irq, @@ -570,7 +569,6 @@ static int qcom_pas_attach(struct rproc *rproc) pas->rproc->state = RPROC_OFFLINE; ret = -EINVAL; disable_running: - disable_irq(pas->q6v5.handover_irq); pas->q6v5.running = false; return ret; From 8e359920216689b3b79e0fe8961a77fe312a511f Mon Sep 17 00:00:00 2001 From: Etienne Perot Date: Fri, 28 Aug 2026 21:52:51 +0000 Subject: [PATCH 0300/2366] cgroup: fix spurious SIGKILL of CLONE_INTO_CGROUP children Since commit b69bb476dee9 ("cgroup: fix race between fork and cgroup.kill"), the fork path snapshots the kill_seq of the child's future cgroup into kargs->kill_seq, and cgroup_post_fork() SIGKILLs the child if that cgroup's kill_seq has changed in the meantime, to catch forks racing with a cgroup.kill sweep. For CLONE_INTO_CGROUP, however, the snapshot in cgroup_css_set_fork() is taken before the target cgroup has been resolved: kargs->cgrp is always NULL at this point (it is only set at the end of the function). So the "if (kargs->cgrp)" branch is dead code and the snapshot always records the kill_seq of the parent's cgroup. cgroup_post_fork() then compares it with the kill_seq of the target cgroup, so the child gets SIGKILLed whenever the two cgroups have been killed a different number of times. As a result, once cgroup.kill has been written to a cgroup, every child subsequently cloned into it with clone3(CLONE_INTO_CGROUP) is killed on the spot, for as long as the cgroup exists: kill_seq is not exposed to userspace and never resets. Re-snapshot kill_seq from the target cgroup once it has been resolved, and drop the dead branch at the early snapshot site. This does not reopen the race fixed by b69bb476dee9. For CLONE_INTO_CGROUP, everything from the snapshot to the check in cgroup_post_fork() runs with cgroup_mutex held, and kill_seq is only ever incremented under cgroup_mutex. tj: Updated the comment above kill_seq to reflect the new serialization rules as suggested by Shakeel Butt. Fixes: b69bb476dee9 ("cgroup: fix race between fork and cgroup.kill") Cc: stable@vger.kernel.org Cc: Shakeel Butt Assisted-by: LLM Signed-off-by: Etienne Perot Signed-off-by: Tejun Heo --- include/linux/cgroup-defs.h | 5 ++++- kernel/cgroup/cgroup.c | 6 ++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/linux/cgroup-defs.h b/include/linux/cgroup-defs.h index 7a631a257613..3754d697854b 100644 --- a/include/linux/cgroup-defs.h +++ b/include/linux/cgroup-defs.h @@ -527,7 +527,10 @@ struct cgroup { int nr_threaded_children; /* # of live threaded child cgroups */ - /* sequence number for cgroup.kill, serialized by css_set_lock. */ + /* + * Sequence number for cgroup.kill. Incremented with both cgroup_mutex + * and css_set_lock held. Readers hold either one. + */ unsigned int kill_seq; struct kernfs_node *kn; /* cgroup kernfs entry */ diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index f87fc4550081..353c8f83439a 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -6777,10 +6777,7 @@ static int cgroup_css_set_fork(struct kernel_clone_args *kargs) spin_lock_irq(&css_set_lock); cset = task_css_set(current); get_css_set(cset); - if (kargs->cgrp) - kargs->kill_seq = kargs->cgrp->kill_seq; - else - kargs->kill_seq = cset->dfl_cgrp->kill_seq; + kargs->kill_seq = cset->dfl_cgrp->kill_seq; spin_unlock_irq(&css_set_lock); if (!(kargs->flags & CLONE_INTO_CGROUP)) { @@ -6844,6 +6841,7 @@ static int cgroup_css_set_fork(struct kernel_clone_args *kargs) put_css_set(cset); kargs->cgrp = dst_cgrp; + kargs->kill_seq = dst_cgrp->kill_seq; return ret; err: From 3f4b7d1a49c5c826f3be9b684313eea5b83ac232 Mon Sep 17 00:00:00 2001 From: Etienne Perot Date: Fri, 28 Aug 2026 21:52:52 +0000 Subject: [PATCH 0301/2366] selftests/cgroup: test clone3() into a previously killed cgroup Once cgroup.kill had been written to a cgroup, a stale kill_seq snapshot (taken in cgroup_css_set_fork() before the target cgroup was resolved) caused every child subsequently cloned into that cgroup with clone3(CLONE_INTO_CGROUP) to be SIGKILLed on the spot. Add a regression test: create a cgroup, kill it while it is empty, then clone a child into it and check that the child runs and exits cleanly. On a kernel without the fix, the test fails: not ok 4 test_cgkill_clone_into_killed The test is skipped on kernels without clone3() or without CLONE_INTO_CGROUP. Cc: Shakeel Butt Assisted-by: LLM Signed-off-by: Etienne Perot Signed-off-by: Tejun Heo --- tools/testing/selftests/cgroup/test_kill.c | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tools/testing/selftests/cgroup/test_kill.c b/tools/testing/selftests/cgroup/test_kill.c index 99cafd9dc013..bac1ddd8cb94 100644 --- a/tools/testing/selftests/cgroup/test_kill.c +++ b/tools/testing/selftests/cgroup/test_kill.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "kselftest.h" @@ -261,6 +262,59 @@ static int test_cgkill_forkbomb(const char *root) return ret; } +/* + * Test that a cgroup that was killed in the past can still be the target + * of clone3(CLONE_INTO_CGROUP): writing cgroup.kill must only kill the + * tasks in the cgroup at the time of the write, not tasks cloned into + * it afterwards. + */ +static int test_cgkill_clone_into_killed(const char *root) +{ + pid_t pid; + int cgroup_fd = -EBADF; + int ret = KSFT_FAIL; + char *cgroup = NULL; + + cgroup = cg_name(root, "cg_test_clone_into_killed"); + if (!cgroup) + goto cleanup; + + if (cg_create(cgroup)) + goto cleanup; + + /* Kill the cgroup while it is still empty. */ + if (cg_write(cgroup, "cgroup.kill", "1")) + goto cleanup; + + cgroup_fd = dirfd_open_opath(cgroup); + if (cgroup_fd < 0) + goto cleanup; + + pid = clone_into_cgroup(cgroup_fd); + if (pid < 0) { + if (errno == ENOSYS) + ret = KSFT_SKIP; + goto cleanup; + } + + if (pid == 0) + exit(EXIT_SUCCESS); + + /* The child must not be SIGKILLed; it has to exit cleanly. */ + if (clone_reap(pid, WEXITED) != EXIT_SUCCESS) + goto cleanup; + + ret = KSFT_PASS; + +cleanup: + if (cgroup_fd >= 0) + close(cgroup_fd); + if (cgroup) + cg_destroy(cgroup); + free(cgroup); + return ret; +} + #define T(x) { x, #x } struct cgkill_test { int (*fn)(const char *root); @@ -269,6 +323,7 @@ struct cgkill_test { T(test_cgkill_simple), T(test_cgkill_tree), T(test_cgkill_forkbomb), + T(test_cgkill_clone_into_killed), }; #undef T From b853857293584dd1f52183f70a2bdeca692a1e71 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Fri, 21 Aug 2026 01:15:40 -0700 Subject: [PATCH 0302/2366] remoteproc: qcom_q6v5_mss: Don't require PAS for memory protection Commit f3b1357673dd ("remoteproc: qcom_q6v5_mss: Switch to generic PAS TZ APIs") changed the probe-time gate for need_mem_protection platforms from qcom_scm_is_available() to qcom_pas_is_available(). Memory protection in this driver is implemented with qcom_scm_assign_mem(), which is a TZ service distinct from PAS. The only PAS call in the driver is qcom_pas_mem_setup(), and it is already guarded by need_pas_mem_setup. No descriptor sets both flags: sc7180, sc7280, sdm660, sdm845, msm8996 and msm8998 set need_mem_protection only, while msm8937, msm8940 and msm8953 set need_pas_mem_setup only. On TrustZone firmware that does not implement PAS - for example SC7180 Chromebooks, where call-availability queries report every QCOM_SCM_SVC_PIL command as unavailable - the modem consequently never probes: platform 4080000.remoteproc: deferred probe pending: (reason unknown) On those machines the modem is also what loads the WLAN firmware, so ath10k never receives QMI and wifi does not come up either. Gate memory protection on SCM availability as it was before, and require PAS only where a PAS call is actually issued. Keeping the SCM check matters: qcom_scm_assign_mem() passes __scm->mempool to qcom_tzmem_alloc() without testing __scm, so dropping the gate entirely would allow a NULL dereference when qcom_scm has not yet probed. Fixes: f3b1357673dd ("remoteproc: qcom_q6v5_mss: Switch to generic PAS TZ APIs") Link: https://lore.kernel.org/r/20260808034716.58888-1-phollinsky@holtechnik.com Signed-off-by: Paul Hollinsky Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260821081540.89137-1-phollinsky@holtechnik.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_mss.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/remoteproc/qcom_q6v5_mss.c b/drivers/remoteproc/qcom_q6v5_mss.c index bef198b9ee63..2f71ed2feff6 100644 --- a/drivers/remoteproc/qcom_q6v5_mss.c +++ b/drivers/remoteproc/qcom_q6v5_mss.c @@ -2079,7 +2079,16 @@ static int q6v5_probe(struct platform_device *pdev) if (!desc) return -EINVAL; - if (desc->need_mem_protection && !qcom_pas_is_available()) + /* + * Memory protection is done through qcom_scm_assign_mem(), which needs + * SCM but not PAS. Only the memory setup path issues PAS calls, so + * requiring PAS for every need_mem_protection platform prevents the + * modem from probing at all on TZ firmware that offers no PAS. + */ + if (desc->need_mem_protection && !qcom_scm_is_available()) + return -EPROBE_DEFER; + + if (desc->need_pas_mem_setup && !qcom_pas_is_available()) return -EPROBE_DEFER; mba_image = desc->hexagon_mba_image; From 0d8e2195bce6f08c1c53c5ef4d7347fe46418101 Mon Sep 17 00:00:00 2001 From: Mostafa Saleh Date: Thu, 27 Aug 2026 20:30:55 +0000 Subject: [PATCH 0303/2366] remoteproc: qcom_q6v5_adsp: Fix iommu_unmap() usage During adsp_map_carveout, the IOVA is computed by combining the physical address and the SID: iova = adsp->mem_phys | (sid << 32); However, adsp_unmap_carveout() uses the physical address and not the IOVA in iommu_unmap(), causing the unmap to fail or leak mappings because the address doesn't match the original IOVA. Cache the constructed IOVA within the qcom_adsp device struct during mapping and use it during unmapping. Fixes: f22eedff28af ("remoteproc: qcom: Add support for memory sandbox") Signed-off-by: Mostafa Saleh Link: https://lore.kernel.org/r/20260827203055.640116-1-smostafa@google.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_adsp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_adsp.c b/drivers/remoteproc/qcom_q6v5_adsp.c index 39654206781d..2e5fb5954fa9 100644 --- a/drivers/remoteproc/qcom_q6v5_adsp.c +++ b/drivers/remoteproc/qcom_q6v5_adsp.c @@ -104,6 +104,7 @@ struct qcom_adsp { struct completion stop_done; phys_addr_t mem_phys; + unsigned long iova; phys_addr_t mem_reloc; void __iomem *mem_region; size_t mem_size; @@ -333,7 +334,7 @@ static void adsp_unmap_carveout(struct rproc *rproc) struct qcom_adsp *adsp = rproc->priv; if (adsp->has_iommu) - iommu_unmap(rproc->domain, adsp->mem_phys, adsp->mem_size); + iommu_unmap(rproc->domain, adsp->iova, adsp->mem_size); } static int adsp_map_carveout(struct rproc *rproc) @@ -341,7 +342,6 @@ static int adsp_map_carveout(struct rproc *rproc) struct qcom_adsp *adsp = rproc->priv; struct of_phandle_args args; long long sid; - unsigned long iova; int ret; if (!adsp->has_iommu) @@ -358,9 +358,9 @@ static int adsp_map_carveout(struct rproc *rproc) of_node_put(args.np); /* Add SID configuration for ADSP Firmware to SMMU */ - iova = adsp->mem_phys | (sid << 32); + adsp->iova = adsp->mem_phys | (sid << 32); - ret = iommu_map(rproc->domain, iova, adsp->mem_phys, + ret = iommu_map(rproc->domain, adsp->iova, adsp->mem_phys, adsp->mem_size, IOMMU_READ | IOMMU_WRITE, GFP_KERNEL); if (ret) { From cd3b9cea675bbfebc223f007dc2f4e79524fa54c Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Thu, 27 Aug 2026 21:16:17 +0000 Subject: [PATCH 0304/2366] usb: typec: tcpm: constrain TCPM_SOURCING_VBUS event handling When a sink detach occurs while waiting for TX send status, the old TCPM_SOURCING_VBUS event along with TCPM_VBUS_EVENT and TCPM_CC_EVENT can be queued in port->pd_events. Because TCPM_SOURCING_VBUS is evaluated after TCPM_VBUS_EVENT and TCPM_CC_EVENT in tcpm_pd_event_handler(), a stale TCPM_SOURCING_VBUS event can override the detach handling and incorrectly set port->vbus_source and port->vbus_present to true. Add a state guard to check that the port is either operating as a Source (tcpm_port_is_source(port)) or in a Fast Role Swap (FRS) state up to FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED before processing TCPM_SOURCING_VBUS. Otherwise, discard and log the event. Log snippet for error condition before fix: [72792.204955] state change SRC_ATTACHED -> SRC_STARTUP [rev3 NONE_AMS] [72792.204960] sourcing vbus [72792.204962] VBUS on [72792.204970] AMS POWER_NEGOTIATION start [72792.204974] cc:=4 [72792.205319] state change SRC_STARTUP -> AMS_START [rev3 POWER_NEGOTIATION] [72792.205325] state change AMS_START -> SRC_SEND_CAPABILITIES [rev3 POWER_NEGOTIATION] [72792.205332] PD TX, header: 0x11a1 [72792.216911] PD TX complete, status: 2 [72792.216957] pending state change SRC_SEND_CAPABILITIES -> SRC_SEND_CAPABILITIES @ 150 ms [rev3 POWER_NEGOTIATION] [72792.218005] VBUS off [72792.218013] pending state change SRC_SEND_CAPABILITIES -> SNK_UNATTACHED @ 650 ms [rev3 POWER_NEGOTIATION] [72792.218020] VBUS VSAFE0V [72792.218024] state change SRC_SEND_CAPABILITIES -> SNK_UNATTACHED [rev3 POWER_NEGOTIATION] [72792.218458] CC1: 2 -> 0, CC2: 0 -> 0 [state SNK_UNATTACHED, polarity 0, disconnected] [72792.218467] VBUS on --> VBUS left on [72792.218980] disable vbus discharge ret:0 [72792.235193] Start toggling After fix: [ 1195.291691] state change SRC_ATTACHED -> SRC_STARTUP [rev3 NONE_AMS] [ 1195.291698] sourcing vbus [ 1195.291700] VBUS on [ 1195.291707] AMS POWER_NEGOTIATION start [ 1195.291710] cc:=4 [ 1195.291758] state change SRC_STARTUP -> AMS_START [rev3 POWER_NEGOTIATION] [ 1195.291794] state change AMS_START -> SRC_SEND_CAPABILITIES [rev3 POWER_NEGOTIATION] [ 1195.291798] PD TX, header: 0x11a1 [ 1195.297056] PD TX complete, status: 2 [ 1195.297092] pending state change SRC_SEND_CAPABILITIES -> SRC_SEND_CAPABILITIES @ 150 ms [rev3 POWER_NEGOTIATION] [ 1195.297177] VBUS off [ 1195.297184] pending state change SRC_SEND_CAPABILITIES -> SNK_UNATTACHED @ 650 ms [rev3 POWER_NEGOTIATION] [ 1195.297227] CC1: 2 -> 0, CC2: 0 -> 0 [state SRC_SEND_CAPABILITIES, polarity 0, disconnected] [ 1195.307469] cc:=2 [ 1195.307544] pending state change SRC_SEND_CAPABILITIES -> SNK_UNATTACHED @ 650 ms [rev3 POWER_NEGOTIATION] [ 1195.307555] Discarding sourcing vbus! Invalid state SRC_SEND_CAPABILITIES [ 1195.957636] state change SRC_SEND_CAPABILITIES -> SNK_UNATTACHED [delayed 650 ms] [ 1195.957732] disable vbus discharge ret:0 [ 1195.970196] Start toggling [ 1195.970468] VBUS off [ 1196.051637] VBUS off [ 1196.051642] VBUS VSAFE0V Fixes: 8dc4bd073663 ("usb: typec: tcpm: Add support for Sink Fast Role SWAP(FRS)") Cc: stable Assisted-by: Gemini:gemini-3.1-pro Signed-off-by: Amit Sunil Dhamne Reviewed-by: Badhri Jagan Sridharan Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260827-sourcing-vbus-v1-1-9be1aca991a0@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index a8cd1959c426..2d6b14aa2085 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -7119,16 +7119,32 @@ static void tcpm_pd_event_handler(struct kthread_work *work) } } if (events & TCPM_SOURCING_VBUS) { - tcpm_log(port, "sourcing vbus"); /* * In fast role swap case TCPC autonomously sources vbus. Set vbus_source - * true as TCPM wouldn't have called tcpm_set_vbus. + * true conditionally as TCPM wouldn't have called tcpm_set_vbus. + * If TCPM calls tcpm_set_vbus to source vbus, vbus_source would already + * be true. * - * When vbus is sourced on the command on TCPM i.e. TCPM called - * tcpm_set_vbus to source vbus, vbus_source would already be true. + * When TCPM_FRS_EVENT and TCPM_SOURCING_VBUS arrive simultaneously, + * handling TCPM_FRS_EVENT above transitions the state to AMS_START + * with upcoming_state FR_SWAP_SEND. */ - port->vbus_source = true; - _tcpm_pd_vbus_on(port); + + if (tcpm_port_is_source(port) || + tcpm_port_is_debug_source(port) || + (port->state == AMS_START && port->upcoming_state == FR_SWAP_SEND) || + port->state == FR_SWAP_SEND || + port->state == FR_SWAP_SEND_TIMEOUT || + port->state == FR_SWAP_SNK_SRC_TRANSITION_TO_OFF || + port->state == FR_SWAP_SNK_SRC_NEW_SINK_READY || + port->state == FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED) { + tcpm_log(port, "sourcing vbus"); + port->vbus_source = true; + _tcpm_pd_vbus_on(port); + } else { + tcpm_log(port, "Discarding sourcing vbus! Invalid state %s", + tcpm_states[port->state]); + } } if (events & TCPM_PORT_CLEAN) { tcpm_log(port, "port clean"); From 9db31edf92dde1f3c98008bd5f88e859d14324cc Mon Sep 17 00:00:00 2001 From: Vignesh Viswanathan Date: Wed, 19 Aug 2026 11:00:30 +0530 Subject: [PATCH 0305/2366] remoteproc: qcom_q6v5_pas: Fix error masking in qcom_pas_stop() In qcom_pas_stop function, return value of qcom_pas_shutdown for pas_id is overwritten by the return value of qcom_pas_shutdown for dtb_pas_id. This causes errors seen on qcom_pas_shutdown failures for pas_id to be masked to the caller. This might lead to issues where the memory regions locked by PAS, as part of qcom_pas_auth_and_reset, are not released for access by linux and rproc_coredump flow will end up accessing the locked memory, leading to an access violation. Fix this by using a separate variable for the dtb_pas_id shutdown call and only overriding the main return value if the pas_id shutdown succeeded but dtb_pas_id shutdown failed. Fixes: 29814986b82e ("remoteproc: qcom_q6v5_pas: add support for dtb co-firmware loading") Signed-off-by: Vignesh Viswanathan Reviewed-by: Konrad Dybcio Reviewed-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260819-rproc_dtb_fix-v1-1-cb6be7f93bc9@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_pas.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index 8cff0684fe2f..a005546c265d 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -405,6 +405,7 @@ static int qcom_pas_stop(struct rproc *rproc) { struct qcom_pas *pas = rproc->priv; int handover; + int dtb_ret; int ret; ret = qcom_q6v5_request_stop(&pas->q6v5, pas->sysmon); @@ -419,9 +420,12 @@ static int qcom_pas_stop(struct rproc *rproc) dev_err(pas->dev, "failed to shutdown: %d\n", ret); if (pas->dtb_pas_id) { - ret = qcom_pas_shutdown(pas->dtb_pas_id); - if (ret) - dev_err(pas->dev, "failed to shutdown dtb: %d\n", ret); + dtb_ret = qcom_pas_shutdown(pas->dtb_pas_id); + if (dtb_ret) + dev_err(pas->dev, "failed to shutdown dtb: %d\n", dtb_ret); + + if (!ret && dtb_ret) + ret = dtb_ret; qcom_pas_unmap_carveout(rproc, pas->dtb_mem_phys, pas->dtb_mem_size); } From 23761359861ca4bb087540937dfea8b0716914c2 Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Thu, 27 Aug 2026 16:07:36 +0800 Subject: [PATCH 0306/2366] sched_ext: Fix timer pinning and return value in scx_central central_timerfn() re-arms the timer with a hardcoded BPF_F_TIMER_CPU_PIN flag and ignores the return value, defeating start_central_timer()'s -EINVAL fallback for kernels without the flag (<6.7): on such kernels the first tick kills the timer permanently with no diagnostic. Honor timer_pinned and check the return like the initial arm does. Fixes: 22a920209ab6 ("sched_ext: Implement tickless support") Signed-off-by: Wanwu Li Signed-off-by: Tejun Heo --- tools/sched_ext/scx_central.bpf.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/sched_ext/scx_central.bpf.c b/tools/sched_ext/scx_central.bpf.c index 64dd60b3e922..65dae9e45400 100644 --- a/tools/sched_ext/scx_central.bpf.c +++ b/tools/sched_ext/scx_central.bpf.c @@ -299,6 +299,7 @@ static int central_timerfn(void *map, int *key, struct bpf_timer *timer) u64 now = scx_bpf_now(); u64 nr_to_kick = nr_queued; s32 i, curr_cpu; + int ret; curr_cpu = bpf_get_smp_processor_id(); if (timer_pinned && (curr_cpu != central_cpu)) { @@ -332,7 +333,10 @@ static int central_timerfn(void *map, int *key, struct bpf_timer *timer) scx_bpf_kick_cpu(cpu, SCX_KICK_PREEMPT); } - bpf_timer_start(timer, TIMER_INTERVAL_NS, BPF_F_TIMER_CPU_PIN); + ret = bpf_timer_start(timer, TIMER_INTERVAL_NS, + timer_pinned ? BPF_F_TIMER_CPU_PIN : 0); + if (ret) + scx_bpf_error("bpf_timer_start failed (%d)", ret); __sync_fetch_and_add(&nr_timers, 1); return 0; } From b6ee92d7f7f0498d1f776d0b125a2f6bcedf0891 Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Thu, 27 Aug 2026 16:07:37 +0800 Subject: [PATCH 0307/2366] sched_ext: Fix vtime delta loss in scx_flatcg cgroup migration fcg_cgroup_move() lost the signed vtime offset across cgroup migration in the mechanical conversion to time helpers: time_delta() clamps negative deltas to 0, so a queued task (whose dsq_vtime is normally behind the source frontier) loses its accumulated vtime credit and lands exactly at the destination frontier instead of keeping its relative position. Restore the wrapping signed subtraction. Fixes: 62addc6dbf36 ("sched_ext: Use time helpers in BPF schedulers") Signed-off-by: Wanwu Li Signed-off-by: Tejun Heo --- tools/sched_ext/scx_flatcg.bpf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sched_ext/scx_flatcg.bpf.c b/tools/sched_ext/scx_flatcg.bpf.c index 64cf4dd964d6..454ebb820c5e 100644 --- a/tools/sched_ext/scx_flatcg.bpf.c +++ b/tools/sched_ext/scx_flatcg.bpf.c @@ -937,7 +937,7 @@ void BPF_STRUCT_OPS(fcg_cgroup_move, struct task_struct *p, if (!(from_cgc = find_cgrp_ctx(from)) || !(to_cgc = find_cgrp_ctx(to))) return; - delta = time_delta(p->scx.dsq_vtime, from_cgc->tvtime_now); + delta = (s64)(p->scx.dsq_vtime - from_cgc->tvtime_now); scx_bpf_task_set_dsq_vtime(p, to_cgc->tvtime_now + delta); } From 84590dbb9f3519e865ee8396494ac7186b625fef Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Thu, 27 Aug 2026 16:07:38 +0800 Subject: [PATCH 0308/2366] sched_ext: Check bpf_timer_start return values in scx_qmap monitor_timerfn(), lowpri_timerfn() and round_robin_timerfn() ignore bpf_timer_start()'s return value: a failed re-arm silently stops the periodic heartbeat, starving every task parked in LOWPRI_DSQ (lowpri) or freezing cid rotation (round-robin). Check the returns and raise scx_bpf_error(), matching the init paths. Signed-off-by: Wanwu Li Signed-off-by: Tejun Heo --- tools/sched_ext/scx_qmap.bpf.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index 5bb8b90a275a..9f6e61d7ca07 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -1246,7 +1246,8 @@ static int monitor_timerfn(void *map, int *key, struct bpf_timer *timer) scx_read_event(&events, SCX_EV_BYPASS_ACTIVATE)); } - bpf_timer_start(timer, ONE_SEC_IN_NS, 0); + if (bpf_timer_start(timer, ONE_SEC_IN_NS, 0)) + scx_bpf_error("failed to re-arm stats timer"); return 0; } @@ -1268,7 +1269,8 @@ struct { static int lowpri_timerfn(void *map, int *key, struct bpf_timer *timer) { scx_bpf_dsq_reenq(LOWPRI_DSQ, 0); - bpf_timer_start(timer, LOWPRI_INTV_NS, 0); + if (bpf_timer_start(timer, LOWPRI_INTV_NS, 0)) + scx_bpf_error("failed to re-arm lowpri timer"); return 0; } @@ -1747,7 +1749,8 @@ static void rr_advance(void) static int round_robin_timerfn(void *map, int *key, struct bpf_timer *timer) { rr_advance(); - bpf_timer_start(timer, round_robin_ns, 0); + if (bpf_timer_start(timer, round_robin_ns, 0)) + scx_bpf_error("failed to re-arm round-robin timer"); return 0; } From 4881a13521886076e8d6d677f274320dcf66fea4 Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Thu, 27 Aug 2026 17:14:11 +0800 Subject: [PATCH 0309/2366] sched_ext: Fix several comment issues Fix several comment issues found during review: __setschduler_prio() -> __setscheduler_class() scx_iter_scx_dsq_new() -> bpf_iter_scx_dsq_new() scx_next_task_scx() -> set_next_task_scx() Signed-off-by: Wanwu Li Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 10 +++++----- kernel/sched/ext/internal.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index c539d15cda63..76a3f4ea237c 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -876,9 +876,9 @@ struct task_struct *scx_task_iter_next_locked(struct scx_task_iter *iter) * unloading. The init_tasks ("swappers") should be excluded * from the iteration because: * - * - It's unsafe to use __setschduler_prio() on an init_task to - * determine the sched_class to use as it won't preserve its - * idle_sched_class. + * - It's unsafe to use __setscheduler_class() on an init_task + * to determine the sched_class to use as it won't preserve + * its idle_sched_class. * * - ops.init/exit_task() can easily be confused if called with * init_tasks as they, e.g., share PID 0. @@ -5514,7 +5514,7 @@ static const struct kset_uevent_ops scx_uevent_ops = { }; /* - * Used by sched_fork() and __setscheduler_prio() to pick the matching + * Used by sched_fork() and __setscheduler_class() to pick the matching * sched_class. dl/rt are already handled. */ bool task_should_scx(int policy) @@ -9771,7 +9771,7 @@ __bpf_kfunc struct task_struct *bpf_iter_scx_dsq_next(struct bpf_iter_scx_dsq *i * bpf_iter_scx_dsq_destroy - Destroy a DSQ iterator * @it: iterator to destroy * - * Undo scx_iter_scx_dsq_new(). + * Undo bpf_iter_scx_dsq_new(). */ __bpf_kfunc void bpf_iter_scx_dsq_destroy(struct bpf_iter_scx_dsq *it) { diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 53e136a47924..0967b99a4948 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -442,7 +442,7 @@ struct sched_ext_ops { * * Note that this callback may be called from a CPU other than the * one the task is going to run on. This can happen when a task - * property is changed (i.e., affinity), since scx_next_task_scx(), + * property is changed (i.e., affinity), since set_next_task_scx(), * which triggers this callback, may run on a CPU different from * the task's assigned CPU. * From ea2ee8b222306208d2b094d1a11894da6c106d42 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Tue, 25 Aug 2026 22:53:00 +0530 Subject: [PATCH 0310/2366] Bluetooth: btintel_pcie: Clear automask on spurious interrupts On spurious interrupt where the TX and RX causes are not set, driver was not clearing the auto mask which can block all the interrupts. Driver needs to clear the automask even if no causes are set. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Signed-off-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 005c77a4f5eb..eec95e5f3dbb 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1696,6 +1696,9 @@ static irqreturn_t btintel_pcie_irq_msix_handler(int irq, void *dev_id) if (unlikely(!(intr_fh | intr_hw))) { /* Ignore interrupt, inta == 0 */ + bt_warn_ratelimited("Bluetooth: btintel_pcie: Received spurious interrupt\n"); + btintel_pcie_wr_reg32(data, BTINTEL_PCIE_CSR_MSIX_AUTOMASK_ST, + BIT(entry->entry)); return IRQ_NONE; } From 068e5a0bc57e57d24cbf38def29cc5fb4db9a0df Mon Sep 17 00:00:00 2001 From: Liang Luo Date: Tue, 25 Aug 2026 13:50:53 +0800 Subject: [PATCH 0311/2366] sched_ext: Fix missing @slice and @vtime descriptions in finish_dispatch() kernel-doc Commit 13f1eae3b662 ("sched_ext: Synchronize slice and dsq_vtime writes") added the slice and vtime parameters to finish_dispatch() but did not update its kernel-doc, which produces warnings: Warning: function parameter 'slice' not described in 'finish_dispatch' Warning: function parameter 'vtime' not described in 'finish_dispatch' Describe both parameters using the same wording as dispatch_to_local_dsq(), which receives the same values. Signed-off-by: Liang Luo Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 76a3f4ea237c..713aa26b2828 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2806,6 +2806,8 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq, * @p: task to finish dispatching * @qseq_at_dispatch: qseq when @p started getting dispatched * @dsq_id: destination DSQ ID + * @slice: slice carried by the insert verdict, 0 keeps the current value + * @vtime: vtime carried by the insert verdict, committed on PRIQ inserts * @enq_flags: %SCX_ENQ_* * * Dispatching to local DSQs may need to wait for queueing to complete or From 068c35b5d0546c8625b3d7c61910f73775cf1216 Mon Sep 17 00:00:00 2001 From: Jiacheng Xu Date: Tue, 25 Aug 2026 15:03:15 +0800 Subject: [PATCH 0312/2366] workqueue: reject watchdog thresholds that overflow jiffies The watchdog threshold is supplied in seconds but is multiplied by HZ before being used as a jiffies interval. Reject values that exceed MAX_JIFFY_OFFSET / HZ so the multiplication cannot wrap and the time_after() comparisons remain within their supported range. The check is performed before changing the threshold or watchdog timer. Zero remains the value used to disable the watchdog. Fixes: 82607adcf9cdf ("workqueue: implement lockup detector") Signed-off-by: Jiacheng Xu Signed-off-by: Tejun Heo --- kernel/workqueue.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 0ee73dcd4a14..b8bec1689b7a 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -8035,6 +8035,9 @@ static int wq_watchdog_param_set_thresh(const char *val, if (ret) return ret; + if (thresh > MAX_JIFFY_OFFSET / HZ) + return -ERANGE; + if (system_percpu_wq) wq_watchdog_set_thresh(thresh); else From a086c0892969bf8a0151b0f12bd14a68827c88b2 Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Mon, 31 Aug 2026 15:44:21 +0545 Subject: [PATCH 0313/2366] Bluetooth: btintel: validate version TLV value lengths btintel_parse_version_tlv() verifies that a complete TLV is present in the response, but it does not ensure that the value is long enough for the specific TLV type. A short value can therefore cause an out-of-bounds read through get_unaligned_le16(), get_unaligned_le32(), or memcpy(). Reject values shorter than the minimum required by each known TLV type. Also reject responses that do not contain the Command Complete Status field. Fixes: 57375beef71a ("Bluetooth: btintel: Add infrastructure to read controller information") Reviewed-by: Ali Ahmet Memis Signed-off-by: Laxman Acharya Padhya Tested-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index bcb2514b7bc0..352a1c83cd08 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -571,12 +571,44 @@ int btintel_version_info_tlv(struct hci_dev *hdev, } EXPORT_SYMBOL_GPL(btintel_version_info_tlv); +static u8 btintel_version_tlv_min_len(u8 type) +{ + switch (type) { + case INTEL_TLV_CNVI_TOP: + case INTEL_TLV_CNVR_TOP: + case INTEL_TLV_CNVI_BT: + case INTEL_TLV_CNVR_BT: + case INTEL_TLV_BUILD_NUM: + case INTEL_TLV_GIT_SHA1: + return sizeof(u32); + case INTEL_TLV_DEV_REV_ID: + case INTEL_TLV_TIME_STAMP: + return sizeof(u16); + case INTEL_TLV_IMAGE_TYPE: + case INTEL_TLV_BUILD_TYPE: + case INTEL_TLV_SECURE_BOOT: + case INTEL_TLV_OTP_LOCK: + case INTEL_TLV_API_LOCK: + case INTEL_TLV_DEBUG_LOCK: + case INTEL_TLV_LIMITED_CCE: + case INTEL_TLV_SBE_TYPE: + return sizeof(u8); + case INTEL_TLV_MIN_FW: + return 3; + case INTEL_TLV_OTP_BDADDR: + return sizeof(bdaddr_t); + default: + return 0; + } +} + int btintel_parse_version_tlv(struct hci_dev *hdev, struct intel_version_tlv *version, struct sk_buff *skb) { /* Consume Command Complete Status field */ - skb_pull(skb, 1); + if (!skb_pull(skb, 1)) + return -EINVAL; /* Event parameters contain multiple TLVs. Read each of them * and only keep the required data. Also, it use existing legacy @@ -596,6 +628,9 @@ int btintel_parse_version_tlv(struct hci_dev *hdev, if (skb->len < tlv->len + sizeof(*tlv)) return -EINVAL; + if (tlv->len < btintel_version_tlv_min_len(tlv->type)) + return -EINVAL; + switch (tlv->type) { case INTEL_TLV_CNVI_TOP: version->cnvi_top = get_unaligned_le32(tlv->val); From ac8aa9e0ec93a12a60230066f199f49c3b9aac3d Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Mon, 31 Aug 2026 15:44:22 +0545 Subject: [PATCH 0314/2366] Bluetooth: btintel: bound firmware ID by TLV length The firmware ID is treated as a NUL-terminated string even though the TLV length is its only boundary. If the value does not contain a NUL terminator, snprintf() can read beyond the received response. Limit the conversion to the advertised TLV value length. Fixes: 164c62f958f8 ("Bluetooth: btintel: Add firmware ID to firmware name") Reviewed-by: Ali Ahmet Memis Signed-off-by: Laxman Acharya Padhya Tested-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 352a1c83cd08..2f87047168d7 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -702,7 +702,7 @@ int btintel_parse_version_tlv(struct hci_dev *hdev, break; case INTEL_TLV_FW_ID: snprintf(version->fw_id, sizeof(version->fw_id), - "%s", tlv->val); + "%.*s", tlv->len, tlv->val); break; default: /* Ignore rest of information */ From 3a74624b5deae7f5e2b98e638687fbf9594a9781 Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Mon, 31 Aug 2026 15:44:23 +0545 Subject: [PATCH 0315/2366] Bluetooth: btintel: propagate version TLV parsing errors btintel_read_version_tlv() ignores the parser return value, so setup continues with partially initialized version data after a malformed TLV causes parsing to stop. Return the parser error to the caller so an invalid response fails setup instead of being treated as successful. Keep this behavioral change separate from the bounds checks so it can be reverted independently if an existing controller sends malformed data. Signed-off-by: Laxman Acharya Padhya Reviewed-by: Ali Ahmet Memis Tested-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 2f87047168d7..909a265fd906 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -721,6 +721,7 @@ static int btintel_read_version_tlv(struct hci_dev *hdev, { struct sk_buff *skb; const u8 param[1] = { 0xFF }; + int err; if (!version) return -EINVAL; @@ -739,10 +740,10 @@ static int btintel_read_version_tlv(struct hci_dev *hdev, return -EIO; } - btintel_parse_version_tlv(hdev, version, skb); + err = btintel_parse_version_tlv(hdev, version, skb); kfree_skb(skb); - return 0; + return err; } /* ------- REGMAP IBT SUPPORT ------- */ From 57938bbdb9bf7fd41cbd5cd509ec10c4b22bec18 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Fri, 28 Aug 2026 08:55:09 +0000 Subject: [PATCH 0316/2366] Bluetooth: hci_core: Fix race condition during device registration In hci_register_dev(), the power_on work item is queued to hdev->req_workqueue before initializing hdev->adv_monitors_idr and registering the MSFT extension via msft_register(). For devices marked with quirks such as HCI_QUIRK_RAW_DEVICE, the HCI_UNCONFIGURED flag is set on the device. When the power_on work item runs concurrently on another CPU, hci_power_on() detects that the device is unconfigured and immediately invokes hci_dev_do_close(), which calls msft_do_close(). Concurrently, msft_register() allocates the msft structure and exposes it to hdev->msft_data prior to calling mutex_init(&msft->filter_lock). If msft_do_close() executes while hdev->msft_data is already assigned but the mutex has not yet been initialized, mutex_lock(&msft->filter_lock) operates on an uninitialized mutex, triggering a DEBUG_LOCKS warning: DEBUG_LOCKS_WARN_ON(lock->magic != lock) WARNING: kernel/locking/mutex.c:625 at __mutex_lock_common kernel/locking/mutex.c:625 [inline] WARNING: kernel/locking/mutex.c:625 at __mutex_lock+0x12d8/0x1550 kernel/locking/mutex.c:821 ... Call Trace: msft_do_close+0x308/0x7b0 net/bluetooth/msft.c:693 hci_dev_close_sync+0x86b/0x10a0 net/bluetooth/hci_sync.c:5522 hci_dev_do_close net/bluetooth/hci_core.c:499 [inline] hci_power_on+0x32c/0x750 net/bluetooth/hci_core.c:937 process_one_work kernel/workqueue.c:3322 [inline] process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405 worker_thread+0x92d/0xe10 kernel/workqueue.c:3486 kthread+0x388/0x470 kernel/kthread.c:436 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Fix this by moving the queue_work() call in hci_register_dev() to after idr_init(&hdev->adv_monitors_idr) and msft_register(hdev) so that device structures and extensions are fully initialized before asynchronous tasks can access them. Additionally, assign hdev->msft_data in msft_register() only after mutex_init(&msft->filter_lock) has completed. Fixes: 9e14606d8f38 ("Bluetooth: msft: Extended monitor tracking by address filter") Assisted-by: Gemini:gemini-3.7-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+14ce1b05b7d5a989abbe@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=14ce1b05b7d5a989abbe Link: https://syzkaller.appspot.com/ai_job?id=2bc9e8aa-ca6d-43e2-be2c-fd5d9f649d7e Signed-off-by: Aleksandr Nogikh Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 4 ++-- net/bluetooth/msft.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 35a1be57e386..d7355c73f93e 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2632,11 +2632,11 @@ int hci_register_dev(struct hci_dev *hdev) if (error) BT_WARN("register suspend notifier failed error:%d\n", error); - queue_work(hdev->req_workqueue, &hdev->power_on); - idr_init(&hdev->adv_monitors_idr); msft_register(hdev); + queue_work(hdev->req_workqueue, &hdev->power_on); + return id; err_wqueue: diff --git a/net/bluetooth/msft.c b/net/bluetooth/msft.c index ded68568e6c9..d9dd722db3eb 100644 --- a/net/bluetooth/msft.c +++ b/net/bluetooth/msft.c @@ -769,8 +769,8 @@ void msft_register(struct hci_dev *hdev) INIT_LIST_HEAD(&msft->handle_map); INIT_LIST_HEAD(&msft->address_filters); - hdev->msft_data = msft; mutex_init(&msft->filter_lock); + hdev->msft_data = msft; } void msft_release(struct hci_dev *hdev) From 4ef05db5b08b176a551b4a6287372045998806b0 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 30 Aug 2026 20:11:36 +0300 Subject: [PATCH 0317/2366] Bluetooth: L2CAP: fix chan mode for LE_CONN_REQ + EXT_FLOWCTL pchan l2cap_new_connection() sets default value of channel mode to match the parent channel. l2cap_le_connect_req() left this at the default, and created L2CAP_MODE_EXT_FLOWCTL channels if listening pchan has that mode. This causes FLAG_DEFER_SETUP channels to reply to L2CAP_LE_CONN_REQ with L2CAP_ECRED_CONN_RSP, which is incorrect. It can also result to stack OOB write (of l2cap_alloc_cid determined values) in l2cap_ecred_rsp_defer(), as l2cap_le_connect_req() does not limit maximum number of deferred channels or check for duplicate ident. Fix by setting chan->mode correctly in l2cap_le_connect_req(). Also check channel mode in l2cap_ecred_rsp_defer(), and do WARN_ON_ONCE instead of OOB write to make it less brittle. Fixes: 15f02b910562 ("Bluetooth: L2CAP: Add initial code for Enhanced Credit Based Mode") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_core.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index ee459dd411f5..1c0b7884dc27 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -3894,6 +3894,9 @@ static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data) struct l2cap_ecred_conn_rsp *rsp_flex = container_of(&rsp->pdu.rsp, struct l2cap_ecred_conn_rsp, hdr); + if (chan->mode != L2CAP_MODE_EXT_FLOWCTL) + return; + /* Check if channel for outgoing connection or if it wasn't deferred * since in those cases it must be skipped. */ @@ -3904,6 +3907,10 @@ static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data) /* Reset ident so only one response is sent */ chan->ident = 0; + /* Unreachable, check in l2cap_ecred_conn_req. If reached, drop rest */ + if (WARN_ON_ONCE(rsp->count >= ARRAY_SIZE(rsp->pdu.scid))) + rsp->pdu.rsp.result = cpu_to_le16(L2CAP_CR_LE_NO_MEM); + /* Include all channels pending with the same ident */ if (!rsp->pdu.rsp.result) rsp_flex->dcid[rsp->count++] = cpu_to_le16(chan->scid); @@ -5063,6 +5070,7 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn, __set_chan_timer(chan, chan->ops->get_sndtimeo(chan)); chan->ident = cmd->ident; + chan->mode = L2CAP_MODE_LE_FLOWCTL; if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) { l2cap_state_change(chan, BT_CONNECT2); From 56c2b5831d39dc84aad2573dc3e197af1a872a05 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 30 Aug 2026 15:04:01 +0300 Subject: [PATCH 0318/2366] Bluetooth: L2CAP: fix out-of-bounds write in l2cap_ecred_connect l2cap_chan_connect() tries to ensure there are no more than L2CAP_ECRED_CONN_SCID_MAX pending ECRED channels, so they fit in the same L2CAP_ECRED_CONN_REQ that l2cap_ecred_connect() constructs. However, the check only counts deferred channels. If 6 L2CAP sockets are connected at the same time in order DDDDND (D=deferred, N=non-deferred), the last can bump the total to max+1. It results to one __le16 written out of bounds of the scid array, and an invalid ECRED_CONN_REQ being sent. Fix by leaving room for the non-deferred pending ECRED channels in the counting in l2cap_chan_connect(), so the limit can't be exceeded. Move counting under same critical section where the channel is added. Although race conditions involving this appear unreachable, it's easier to see. Also add WARN_ON_ONCE check in l2cap_ecred_defer_connect() to make this less brittle. Fixes: da49b602f7f7 ("Bluetooth: L2CAP: Use DEFER_SETUP to group ECRED connections") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_core.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 1c0b7884dc27..60833fa2835b 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -1337,7 +1337,7 @@ static void l2cap_le_connect(struct l2cap_chan *chan) struct l2cap_ecred_conn_data { struct { struct l2cap_ecred_conn_req_hdr req; - __le16 scid[5]; + __le16 scid[L2CAP_ECRED_CONN_SCID_MAX]; } __packed pdu; struct l2cap_chan *chan; struct pid *pid; @@ -1365,6 +1365,10 @@ static void l2cap_ecred_defer_connect(struct l2cap_chan *chan, void *data) if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags)) return; + /* Unreachable, checked in l2cap_connect (+timer drops it if reached) */ + if (WARN_ON_ONCE(conn->count >= ARRAY_SIZE(conn->pdu.scid))) + return; + l2cap_ecred_init(chan, 0); /* Set the same ident so we can match on the rsp */ @@ -7377,6 +7381,9 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid, goto done; } + mutex_lock(&conn->lock); + l2cap_chan_lock(chan); + if (chan->mode == L2CAP_MODE_EXT_FLOWCTL) { struct l2cap_chan_data data; @@ -7384,19 +7391,20 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid, data.pid = chan->ops->get_peer_pid(chan); data.count = 1; - l2cap_chan_list(conn, l2cap_chan_by_pid, &data); + __l2cap_chan_list(conn, l2cap_chan_by_pid, &data); + + /* Leave room for non-deferred channel that ends the group. */ + if (test_bit(FLAG_DEFER_SETUP, &chan->flags)) + data.count += 1; /* Check if there isn't too many channels being connected */ if (data.count > L2CAP_ECRED_CONN_SCID_MAX) { hci_conn_drop(hcon); err = -EPROTO; - goto done; + goto chan_unlock; } } - mutex_lock(&conn->lock); - l2cap_chan_lock(chan); - if (cid && __l2cap_get_chan_by_dcid(conn, cid)) { hci_conn_drop(hcon); err = -EBUSY; From 0d77683237270702fa93489ca759c89b4e970554 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 30 Aug 2026 15:04:02 +0300 Subject: [PATCH 0319/2366] Bluetooth: L2CAP: clear FLAG_DEFER_SETUP only for same PID/PSM l2cap_ecred_defer_connect() clears FLAG_DEFER_SETUP also for channels with different PID/PSM, which will not be added to the same ECRED_CONN_REQ in any case. Consequently, only one ECRED connection group can work at a time although it appears intended they would be separate for each PID/PSM combination. Fix by clearing FLAG_DEFER_SETUP only for the connections that could be added in the request. Retain test_bit(FLAG_DEFER_SETUP) before calling get_peer_pid as it may be NULL otherwise. Fixes: da49b602f7f7 ("Bluetooth: L2CAP: Use DEFER_SETUP to group ECRED connections") Signed-off-by: Pauli Virtanen Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 60833fa2835b..644e31160d55 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -1352,7 +1352,7 @@ static void l2cap_ecred_defer_connect(struct l2cap_chan *chan, void *data) if (chan == conn->chan) return; - if (!test_and_clear_bit(FLAG_DEFER_SETUP, &chan->flags)) + if (!test_bit(FLAG_DEFER_SETUP, &chan->flags)) return; pid = chan->ops->get_peer_pid(chan); @@ -1362,6 +1362,9 @@ static void l2cap_ecred_defer_connect(struct l2cap_chan *chan, void *data) chan->mode != L2CAP_MODE_EXT_FLOWCTL || chan->state != BT_CONNECT) return; + if (!test_and_clear_bit(FLAG_DEFER_SETUP, &chan->flags)) + return; + if (test_and_set_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags)) return; From 2deb76c21b81e42b3282224f7dd2046fe73fd1e0 Mon Sep 17 00:00:00 2001 From: Gongwei Li Date: Tue, 25 Aug 2026 10:01:45 +0800 Subject: [PATCH 0320/2366] Bluetooth: hci_mrvl: Fix wrong return value check of wait_on_bit_timeout() wait_on_bit_timeout() returns 0 if the bit was cleared, -EINTR if the process received a signal and the mode permitted wake up on that signal, or -EAGAIN if the timeout elapsed. It never returns 1. Hence the check "err == 1" in mrvl_load_firmware() is dead code: when the waiting task is interrupted by a signal (-EINTR), the code falls into the "else if (err)" branch and misreports it as "Firmware request timeout" with -ETIMEDOUT instead of propagating -EINTR. Fix this by testing for -EINTR so that an interrupted firmware load is properly detected and reported. Fixes: 162f812f23ba ("Bluetooth: hci_uart: Add Marvell support") Signed-off-by: Gongwei Li Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_mrvl.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/bluetooth/hci_mrvl.c b/drivers/bluetooth/hci_mrvl.c index 516b8f74c434..5798a8db016e 100644 --- a/drivers/bluetooth/hci_mrvl.c +++ b/drivers/bluetooth/hci_mrvl.c @@ -307,9 +307,8 @@ static int mrvl_load_firmware(struct hci_dev *hdev, const char *name) err = wait_on_bit_timeout(&mrvl->flags, STATE_FW_REQ_PENDING, TASK_INTERRUPTIBLE, msecs_to_jiffies(2000)); - if (err == 1) { + if (err == -EINTR) { bt_dev_err(hdev, "Firmware load interrupted"); - err = -EINTR; break; } else if (err) { bt_dev_err(hdev, "Firmware request timeout"); From 77d499e61d36e883a6ad1f10afe05f556aa7e0cc Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Fri, 14 Aug 2026 16:35:18 -0400 Subject: [PATCH 0321/2366] selinux: fix BPF token permission checks Avoid multiple lookups of the bpffs creator SID using the token's file descriptor when the same information can be found via the resolved path/dentry (in selinux_bpf_token_create()) or the token itself (in selinux_bpf_map_create() and selinux_bpf_prog_load()). Not only does this simplify the code, it avoids potential TOCTOU issues if the user changes the token file descriptor passed into the kernel. Cc: stable@vger.kernel.org Fixes: 5473a722f782 ("selinux: add support for BPF token access control") Reviewed-by: Stephen Smalley Tested-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 36 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 035aaf113d1d..e5e17f100aae 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -7267,24 +7267,6 @@ static int selinux_bpf_prog(struct bpf_prog *prog) BPF__PROG_RUN, NULL); } -static u32 selinux_bpffs_creator_sid(u32 fd) -{ - struct path path; - struct super_block *sb; - struct superblock_security_struct *sbsec; - - CLASS(fd, f)(fd); - - if (fd_empty(f)) - return SECSID_NULL; - - path = fd_file(f)->f_path; - sb = path.dentry->d_sb; - sbsec = selinux_superblock(sb); - - return sbsec->creator_sid; -} - static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, struct bpf_token *token, bool kernel) { @@ -7297,7 +7279,7 @@ static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, if (!token) ssid = bpfsec->sid; else - ssid = selinux_bpffs_creator_sid(attr->map_token_fd); + ssid = selinux_bpf_token_security(token)->grantor_sid; return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__MAP_CREATE, NULL); @@ -7315,7 +7297,7 @@ static int selinux_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr, if (!token) ssid = bpfsec->sid; else - ssid = selinux_bpffs_creator_sid(attr->prog_token_fd); + ssid = selinux_bpf_token_security(token)->grantor_sid; return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__PROG_LOAD, NULL); @@ -7329,12 +7311,14 @@ static int selinux_bpf_token_create(struct bpf_token *token, const struct path *path) { struct bpf_security_struct *bpfsec; - u32 sid = selinux_bpffs_creator_sid(attr->token_create.bpffs_fd); + struct superblock_security_struct *sbsec; int err; + sbsec = selinux_superblock(path->dentry->d_sb); + bpfsec = selinux_bpf_token_security(token); bpfsec->sid = current_sid(); - bpfsec->grantor_sid = sid; + bpfsec->grantor_sid = sbsec->creator_sid; bpfsec->perms = 0; /** @@ -7343,15 +7327,15 @@ static int selinux_bpf_token_create(struct bpf_token *token, * in the allowed_cmds bitmap. */ if (bpf_token_cmd(token, BPF_MAP_CREATE)) { - err = avc_has_perm(bpfsec->sid, sid, SECCLASS_BPF, - BPF__MAP_CREATE_AS, NULL); + err = avc_has_perm(bpfsec->sid, bpfsec->grantor_sid, + SECCLASS_BPF, BPF__MAP_CREATE_AS, NULL); if (err) return err; bpfsec->perms |= BPF__MAP_CREATE; } if (bpf_token_cmd(token, BPF_PROG_LOAD)) { - err = avc_has_perm(bpfsec->sid, sid, SECCLASS_BPF, - BPF__PROG_LOAD_AS, NULL); + err = avc_has_perm(bpfsec->sid, bpfsec->grantor_sid, + SECCLASS_BPF, BPF__PROG_LOAD_AS, NULL); if (err) return err; bpfsec->perms |= BPF__PROG_LOAD; From ee02ed6308fbbd851c4e5c1f642d029617049a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20Do=C4=9Fu=20Ari?= Date: Fri, 28 Aug 2026 02:51:39 +0300 Subject: [PATCH 0322/2366] platform/x86: hp-wmi: Fix board_params typo for 8DD6 board MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When adding support for board 8DD6, &omen_v1_no_ec_thermal_params was passed as driver_data instead of &omen_v1_no_ec_board_params. Because active_board_params expects a pointer to struct hp_wmi_board_params, dereferencing active_board_params->thermal_profile results in a type confusion bug and invalid memory access. Update the entry to point to omen_v1_no_ec_board_params. Fixes: a7320d6eb9c42 ("platform/x86: hp-wmi: Add support for OMEN MAX 16-ak0xxx (8DD6)") Cc: stable@vger.kernel.org Signed-off-by: Arda Doğu Ari Reviewed-by: Krishna Chomal Link: https://patch.msgid.link/20260827235139.154462-1-arfeliousheres@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index b2773fc1aca4..615b4cf6fc45 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -337,7 +337,7 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") }, - .driver_data = (void *)&omen_v1_no_ec_thermal_params, + .driver_data = (void *)&omen_v1_no_ec_board_params, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") }, From 6bb4fb72c00dc2a9cb663e2d16adce15e4170cdf Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 31 Aug 2026 01:50:58 +0200 Subject: [PATCH 0323/2366] platform/x86: asus-laptop: Fix ACPI event handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The event codes inside asus_keymap[] span a wide range from 0x02 till 0xC5, but using ACPI_DEVICE_NOTIFY prevents us from receiving event codes below 0x80. Fix this by using ACPI_ALL_NOTIFY instead. Fixes: 378500dc1313 ("platform/x86: asus-laptop: Register ACPI notify handler directly") Reported-by: Mo Jun Closes: https://bugs.debian.org/1146124 Tested-by: Mo Jun Signed-off-by: Armin Wolf Reviewed-by: Rafael J. Wysocki Link: https://patch.msgid.link/20260830235058.324140-1-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-laptop.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 449addd1ac7a..79a575d0f5b4 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -1882,7 +1882,7 @@ static int asus_acpi_probe(struct platform_device *pdev) if (result && result != -ENODEV) goto fail_pega_rfkill; - result = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + result = acpi_dev_install_notify_handler(device, ACPI_ALL_NOTIFY, asus_acpi_notify, asus); if (result) goto fail_pega_rfkill; @@ -1912,7 +1912,7 @@ static void asus_acpi_remove(struct platform_device *pdev) { struct asus_laptop *asus = platform_get_drvdata(pdev); - acpi_dev_remove_notify_handler(asus->device, ACPI_DEVICE_NOTIFY, + acpi_dev_remove_notify_handler(asus->device, ACPI_ALL_NOTIFY, asus_acpi_notify); asus_backlight_exit(asus); asus_rfkill_exit(asus); From 9ffed84a24d60ec506d8961fe138f0baa92fdbd0 Mon Sep 17 00:00:00 2001 From: Pedro Falcato Date: Mon, 31 Aug 2026 12:43:46 +0100 Subject: [PATCH 0324/2366] platform/x86/amd/pmf: fix build on !CONFIG_AMD_PMF_DEBUG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amd_pmf_get_ta_custom_bios_inputs() is used by non-debug features. Fix the build on !CONFIG_AMD_PMF_DEBUG by moving amd_pmf_get_ta_custom_bios_inputs() outside the ifdef CONFIG_AMD_PMF_DEBUG. Fixes: 5bda82c797c9 ("platform/x86/amd/pmf: Implement util layer ioctl handler") Reported-by: Oleksandr Natalenko Link: https://lore.kernel.org/all/fS7s9V_xTaedaqEAaxwKnQ@natalenko.name/ Signed-off-by: Pedro Falcato Reviewed-by: Mario Limonciello (AMD) > --- Tested-by: Oleksandr Natalenko Link: https://patch.msgid.link/20260831114346.2041361-1-pfalcato@suse.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/spc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index 94355b435a66..592ba4de4c7f 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -17,7 +17,6 @@ #include #include "pmf.h" -#ifdef CONFIG_AMD_PMF_DEBUG u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) { switch (index) { @@ -31,6 +30,7 @@ u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) } EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); +#ifdef CONFIG_AMD_PMF_DEBUG void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) { int i; From d83b7502bb087fa54daf0fdd419d2910c34bc97d Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Mon, 31 Aug 2026 17:36:46 +0200 Subject: [PATCH 0325/2366] MAINTAINERS: Update URI for watchdog tree The old git repository git://www.linux-watchdog.org/linux-watchdog.git is not accessible anymore, while the development of the watchdog framework is currently handled in git.kernel.org by Guenter's linux-staging repository. Update the URI. Signed-off-by: Antonio Borneo Link: https://patch.msgid.link/20260831153646.396038-1-antonio.borneo@foss.st.com Signed-off-by: Guenter Roeck --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..5e0468e53257 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29357,7 +29357,7 @@ M: Guenter Roeck L: linux-watchdog@vger.kernel.org S: Maintained W: http://www.linux-watchdog.org/ -T: git git://www.linux-watchdog.org/linux-watchdog.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git F: Documentation/devicetree/bindings/watchdog/ F: Documentation/watchdog/ F: drivers/watchdog/ From 93e257938aa67a6c957217db94091d9d9e5d403f Mon Sep 17 00:00:00 2001 From: Aaron Tomlin Date: Mon, 31 Aug 2026 14:15:53 -0400 Subject: [PATCH 0326/2366] tools/workqueue/wq_dump.py: Support backward compatibility for wq->attrs rename Commit 464e454e1cb4 ("workqueue: rename wq->unbound_attrs to wq->attrs") renamed wq->unbound_attrs to wq->attrs. When running wq_dump.py against older running kernels or vmcores where struct workqueue_struct still contains unbound_attrs, drgn raises an AttributeError. Add a wq_attrs() helper to allow wq_dump.py to inspect both older and newer kernel versions seamlessly. Fixes: 464e454e1cb4 ("workqueue: rename wq->unbound_attrs to wq->attrs") Signed-off-by: Aaron Tomlin Signed-off-by: Tejun Heo --- tools/workqueue/wq_dump.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/workqueue/wq_dump.py b/tools/workqueue/wq_dump.py index 31afc24ef17b..9313ebe0c525 100644 --- a/tools/workqueue/wq_dump.py +++ b/tools/workqueue/wq_dump.py @@ -78,6 +78,12 @@ def cpumask_str(cpumask): wq_type_len = 9 +def wq_attrs(wq): + try: + return wq.attrs + except AttributeError: + return wq.unbound_attrs + def wq_type_str(wq): if wq.flags & WQ_BH: return f'{"bh":{wq_type_len}}' @@ -85,7 +91,7 @@ def wq_type_str(wq): if wq.flags & WQ_ORDERED: return f'{"ordered":{wq_type_len}}' else: - if wq.attrs.affn_strict: + if wq_attrs(wq).affn_strict: return f'{"unbound,S":{wq_type_len}}' else: return f'{"unbound":{wq_type_len}}' @@ -206,7 +212,7 @@ for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_( print(f'{wq.name.string_().decode():{WQ_NAME_LEN}}', end='') if wq.flags & WQ_UNBOUND: - print(f' {cpumask_str(wq.attrs.cpumask):{ucpus_len}}', end='') + print(f' {cpumask_str(wq_attrs(wq).cpumask):{ucpus_len}}', end='') else: print(f' {"":{ucpus_len}}', end='') From 8e839bca7793a0b03c005f4b2b0825464290d425 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 31 Aug 2026 22:29:06 +0200 Subject: [PATCH 0327/2366] ASoC: ab8500: Reset the audio block before configuring it ResetAudn is active low, but the codec probe only deasserts it. It also clears Clk32kOut2Dis despite claiming to disable that output, and writes codec registers before releasing reset. Pulse ResetAudn before the first audio-bank access and leave the unused 32 kHz output disabled. Fixes: 679d7abdc754 ("ASoC: codecs: Add AB8500 codec-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260831-ab8500-codec-fixes-v1-1-f85024e717e3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 2cf96cbdd294..714ff0a16f44 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -1638,18 +1638,18 @@ static struct snd_kcontrol_new ab8500_ctrls[] = { static int ab8500_audio_init_audioblock(struct snd_soc_component *component) { int status; + u8 mask = AB8500_STW4500CTRL3_CLK32KOUT2DIS | + AB8500_STW4500CTRL3_RESETAUDN; dev_dbg(component->dev, "%s: Enter.\n", __func__); - /* Reset audio-registers and disable 32kHz-clock output 2 */ - status = ab8500_sysctrl_write(AB8500_STW4500CTRL3, - AB8500_STW4500CTRL3_CLK32KOUT2DIS | - AB8500_STW4500CTRL3_RESETAUDN, - AB8500_STW4500CTRL3_RESETAUDN); + /* Reset the audio registers and disable the unused 32 kHz output. */ + status = ab8500_sysctrl_write(AB8500_STW4500CTRL3, mask, + AB8500_STW4500CTRL3_CLK32KOUT2DIS); if (status < 0) return status; - return 0; + return ab8500_sysctrl_write(AB8500_STW4500CTRL3, mask, mask); } static int ab8500_audio_setup_mics(struct snd_soc_component *component, @@ -2181,6 +2181,13 @@ static int ab8500_codec_probe(struct snd_soc_component *component) ab8500_codec_of_probe(dev, np, &codec_pdata); + status = ab8500_audio_init_audioblock(component); + if (status < 0) { + dev_err(dev, "%s: failed to init audio-block (%d)!\n", + __func__, status); + return status; + } + status = ab8500_audio_setup_mics(component, &codec_pdata.amics); if (status < 0) { pr_err("%s: Failed to setup mics (%d)!\n", __func__, status); @@ -2193,13 +2200,6 @@ static int ab8500_codec_probe(struct snd_soc_component *component) return status; } - status = ab8500_audio_init_audioblock(component); - if (status < 0) { - dev_err(dev, "%s: failed to init audio-block (%d)!\n", - __func__, status); - return status; - } - /* Override HW-defaults */ snd_soc_component_write(component, AB8500_ANACONF5, BIT(AB8500_ANACONF5_HSAUTOEN)); From 103fe1a37f040ef6ac9ed1cf33be786149d2bb15 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 31 Aug 2026 22:29:07 +0200 Subject: [PATCH 0328/2366] ASoC: ab8500: Repair the DAPM capture graph The capture stream routes point away from the stream widget. Digital microphone mux routes are unconditional and bypass their enable bits, and several widgets independently own shared AD path enable bits. The dummy ADC and DAC widgets hide the resulting power graph errors. Connect each real AIF widget to the stream and main supply, use the mux item names on digital microphone routes, and model shared AD enables as supplies. Also make the ANC DAPM switch writable. Fixes: 679d7abdc754 ("ASoC: codecs: Add AB8500 codec-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260831-ab8500-codec-fixes-v1-2-f85024e717e3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 120 +++++++++++++++----------------- 1 file changed, 56 insertions(+), 64 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 714ff0a16f44..5172154f4139 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -259,7 +259,7 @@ static const struct snd_kcontrol_new dapm_anc_in_select[] = { /* ANC - Enable/Disable */ static const struct snd_kcontrol_new dapm_anc_enable[] = { SOC_DAPM_SINGLE("Switch", AB8500_ANCCONF1, - AB8500_ANCCONF1_ENANC, 0, 0), + AB8500_ANCCONF1_ENANC, 1, 0), }; /* ANC to Earpiece - Mute */ @@ -341,12 +341,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { /* DA/AD */ - SND_SOC_DAPM_INPUT("ADC Input"), - SND_SOC_DAPM_ADC("ADC", "ab8500_0c", SND_SOC_NOPM, 0, 0), - - SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_OUTPUT("DAC Output"), - SND_SOC_DAPM_AIF_IN("DA_IN1", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("DA_IN2", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("DA_IN3", NULL, 0, SND_SOC_NOPM, 0, 0), @@ -538,9 +532,8 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_MIXER("AD3 Channel Volume", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_MIXER("AD3 Enable", - AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD34, 0, - NULL, 0), + SND_SOC_DAPM_SUPPLY("AD34 Enable", AB8500_ADPATHENA, + AB8500_ADPATHENA_ENAD34, 0, NULL, 0), /* Mic 2 */ @@ -599,9 +592,8 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_MIXER("AD12 Enable", - AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD12, 0, - NULL, 0), + SND_SOC_DAPM_SUPPLY("AD12 Enable", AB8500_ADPATHENA, + AB8500_ADPATHENA_ENAD12, 0, NULL, 0), /* HD Capture path */ @@ -615,12 +607,8 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_MIXER("AD6 Channel Volume", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_MIXER("AD57 Enable", - AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD5768, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("AD68 Enable", - AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD5768, 0, - NULL, 0), + SND_SOC_DAPM_SUPPLY("AD5768 Enable", AB8500_ADPATHENA, + AB8500_ADPATHENA_ENAD5768, 0, NULL, 0), /* Digital Microphone path */ @@ -652,10 +640,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_MIXER("AD4 Channel Volume", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_MIXER("AD4 Enable", - AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD34, - 0, NULL, 0), - /* Acoustical Noise Cancellation path */ SND_SOC_DAPM_INPUT("ANC Configure Input"), @@ -703,24 +687,17 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"Main Supply", NULL, "Audio Power"}, {"Main Supply", NULL, "Audio Analog Power"}, - {"DAC", NULL, "ab8500_0p"}, - {"DAC", NULL, "Main Supply"}, - {"ADC", NULL, "ab8500_0c"}, - {"ADC", NULL, "Main Supply"}, - /* ANC Configure */ {"ANC Configure Input", NULL, "Main Supply"}, {"ANC Configure Output", NULL, "ANC Configure Input"}, - /* AD/DA */ - {"ADC", NULL, "ADC Input"}, - {"DAC Output", NULL, "DAC"}, - /* Powerup charge pump if DA1/2 is in use */ {"DA_IN1", NULL, "ab8500_0p"}, + {"DA_IN1", NULL, "Main Supply"}, {"DA_IN1", NULL, "Charge Pump"}, {"DA_IN2", NULL, "ab8500_0p"}, + {"DA_IN2", NULL, "Main Supply"}, {"DA_IN2", NULL, "Charge Pump"}, /* Headset path */ @@ -755,8 +732,10 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { /* HF or LineOut path */ {"DA_IN3", NULL, "ab8500_0p"}, + {"DA_IN3", NULL, "Main Supply"}, {"DA3 Channel Volume", NULL, "DA_IN3"}, {"DA_IN4", NULL, "ab8500_0p"}, + {"DA_IN4", NULL, "Main Supply"}, {"DA4 Channel Volume", NULL, "DA_IN4"}, {"Speaker Left Source", "Audio Path", "DA3 Channel Volume"}, @@ -814,8 +793,10 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { /* Vibrator path */ {"DA_IN5", NULL, "ab8500_0p"}, + {"DA_IN5", NULL, "Main Supply"}, {"DA5 Channel Volume", NULL, "DA_IN5"}, {"DA_IN6", NULL, "ab8500_0p"}, + {"DA_IN6", NULL, "Main Supply"}, {"DA6 Channel Volume", NULL, "DA_IN6"}, {"VIB1 DAC", NULL, "DA5 Channel Volume"}, @@ -857,13 +838,15 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"AD1 Channel Volume", NULL, "AD1 Source Select"}, {"AD2 Channel Volume", NULL, "AD2 Source Select"}, - {"AD12 Enable", NULL, "AD1 Channel Volume"}, - {"AD12 Enable", NULL, "AD2 Channel Volume"}, + {"AD1 Channel Volume", NULL, "AD12 Enable"}, + {"AD2 Channel Volume", NULL, "AD12 Enable"}, - {"AD_OUT1", NULL, "ab8500_0c"}, - {"AD_OUT1", NULL, "AD12 Enable"}, - {"AD_OUT2", NULL, "ab8500_0c"}, - {"AD_OUT2", NULL, "AD12 Enable"}, + {"ab8500_0c", NULL, "AD_OUT1"}, + {"AD_OUT1", NULL, "Main Supply"}, + {"AD_OUT1", NULL, "AD1 Channel Volume"}, + {"ab8500_0c", NULL, "AD_OUT2"}, + {"AD_OUT2", NULL, "Main Supply"}, + {"AD_OUT2", NULL, "AD2 Channel Volume"}, /* Mic 1 */ @@ -880,11 +863,11 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"AD3 Source Select", "Mic 1", "MIC1 ADC"}, {"AD3 Channel Volume", NULL, "AD3 Source Select"}, + {"AD3 Channel Volume", NULL, "AD34 Enable"}, - {"AD3 Enable", NULL, "AD3 Channel Volume"}, - - {"AD_OUT3", NULL, "ab8500_0c"}, - {"AD_OUT3", NULL, "AD3 Enable"}, + {"ab8500_0c", NULL, "AD_OUT3"}, + {"AD_OUT3", NULL, "Main Supply"}, + {"AD_OUT3", NULL, "AD3 Channel Volume"}, /* HD Capture path */ @@ -893,14 +876,15 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"AD5 Channel Volume", NULL, "AD5 Source Select"}, {"AD6 Channel Volume", NULL, "AD6 Source Select"}, + {"AD5 Channel Volume", NULL, "AD5768 Enable"}, + {"AD6 Channel Volume", NULL, "AD5768 Enable"}, - {"AD57 Enable", NULL, "AD5 Channel Volume"}, - {"AD68 Enable", NULL, "AD6 Channel Volume"}, - - {"AD_OUT57", NULL, "ab8500_0c"}, - {"AD_OUT57", NULL, "AD57 Enable"}, - {"AD_OUT68", NULL, "ab8500_0c"}, - {"AD_OUT68", NULL, "AD68 Enable"}, + {"ab8500_0c", NULL, "AD_OUT57"}, + {"AD_OUT57", NULL, "Main Supply"}, + {"AD_OUT57", NULL, "AD5 Channel Volume"}, + {"ab8500_0c", NULL, "AD_OUT68"}, + {"AD_OUT68", NULL, "Main Supply"}, + {"AD_OUT68", NULL, "AD6 Channel Volume"}, /* Digital Microphone path */ @@ -911,17 +895,25 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"DMic 5", NULL, "V-DMIC"}, {"DMic 6", NULL, "V-DMIC"}, - {"AD1 Source Select", NULL, "DMic 1"}, - {"AD2 Source Select", NULL, "DMic 2"}, - {"AD3 Source Select", NULL, "DMic 3"}, - {"AD5 Source Select", NULL, "DMic 5"}, - {"AD6 Source Select", NULL, "DMic 6"}, + {"DMIC1", NULL, "DMic 1"}, + {"DMIC2", NULL, "DMic 2"}, + {"DMIC3", NULL, "DMic 3"}, + {"DMIC4", NULL, "DMic 4"}, + {"DMIC5", NULL, "DMic 5"}, + {"DMIC6", NULL, "DMic 6"}, - {"AD4 Channel Volume", NULL, "DMic 4"}, - {"AD4 Enable", NULL, "AD4 Channel Volume"}, + {"AD1 Source Select", "DMic 1", "DMIC1"}, + {"AD2 Source Select", "DMic 2", "DMIC2"}, + {"AD3 Source Select", "DMic 3", "DMIC3"}, + {"AD5 Source Select", "DMic 5", "DMIC5"}, + {"AD6 Source Select", "DMic 6", "DMIC6"}, - {"AD_OUT4", NULL, "ab8500_0c"}, - {"AD_OUT4", NULL, "AD4 Enable"}, + {"AD4 Channel Volume", NULL, "DMIC4"}, + {"AD4 Channel Volume", NULL, "AD34 Enable"}, + + {"ab8500_0c", NULL, "AD_OUT4"}, + {"AD_OUT4", NULL, "Main Supply"}, + {"AD_OUT4", NULL, "AD4 Channel Volume"}, /* LineIn Bypass path */ @@ -946,13 +938,13 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { /* Sidetone Filter path */ - {"Sidetone Left Source", "LineIn Left", "AD12 Enable"}, - {"Sidetone Left Source", "LineIn Right", "AD12 Enable"}, - {"Sidetone Left Source", "Mic 1", "AD3 Enable"}, + {"Sidetone Left Source", "LineIn Left", "AD1 Channel Volume"}, + {"Sidetone Left Source", "LineIn Right", "AD2 Channel Volume"}, + {"Sidetone Left Source", "Mic 1", "AD3 Channel Volume"}, {"Sidetone Left Source", "Headset Left", "DA_IN1"}, - {"Sidetone Right Source", "LineIn Right", "AD12 Enable"}, - {"Sidetone Right Source", "Mic 1", "AD3 Enable"}, - {"Sidetone Right Source", "DMic 4", "AD4 Enable"}, + {"Sidetone Right Source", "LineIn Right", "AD2 Channel Volume"}, + {"Sidetone Right Source", "Mic 1", "AD3 Channel Volume"}, + {"Sidetone Right Source", "DMic 4", "AD4 Channel Volume"}, {"Sidetone Right Source", "Headset Right", "DA_IN2"}, {"STFIR1 Control", NULL, "Sidetone Left Source"}, From f98785adf004db6b1c9f4cea9dadae7b800db72f Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 31 Aug 2026 22:29:08 +0200 Subject: [PATCH 0329/2366] ASoC: ab8500: Correct digital interface format setup The codec programs I2S as an undelayed left-aligned format, although the hardware manual defines delayed left-aligned as I2S compatible. It also enables the master generator when the codec is a clock consumer, changes registers before the complete format has been validated, and discards register I/O errors. Build all three interface register values before writing them, use the required one-bit I2S delay, only run the master generator for a provider configuration, and propagate write failures. Fixes: 679d7abdc754 ("ASoC: codecs: Add AB8500 codec-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260831-ab8500-codec-fixes-v1-3-f85024e717e3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 175 ++++++++++++-------------------- 1 file changed, 64 insertions(+), 111 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 5172154f4139..4fbe486de74a 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -1737,149 +1737,91 @@ static int ab8500_audio_set_ear_cmv(struct snd_soc_component *component, return 0; } -static int ab8500_audio_set_bit_delay(struct snd_soc_dai *dai, - unsigned int delay) -{ - unsigned int mask, val; - struct snd_soc_component *component = dai->component; - - mask = BIT(AB8500_DIGIFCONF2_IF0DEL); - val = 0; - - switch (delay) { - case 0: - break; - case 1: - val |= BIT(AB8500_DIGIFCONF2_IF0DEL); - break; - default: - dev_err(dai->component->dev, - "%s: ERROR: Unsupported bit-delay (0x%x)!\n", - __func__, delay); - return -EINVAL; - } - - dev_dbg(dai->component->dev, "%s: IF0 Bit-delay: %d bits.\n", - __func__, delay); - snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, mask, val); - - return 0; -} - -/* Gates clocking according format mask */ -static int ab8500_codec_set_dai_clock_gate(struct snd_soc_component *component, - unsigned int fmt) -{ - unsigned int mask; - unsigned int val; - - mask = BIT(AB8500_DIGIFCONF1_ENMASTGEN) | - BIT(AB8500_DIGIFCONF1_ENFSBITCLK0); - - val = BIT(AB8500_DIGIFCONF1_ENMASTGEN); - - switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { - case SND_SOC_DAIFMT_CONT: /* continuous clock */ - dev_dbg(component->dev, "%s: IF0 Clock is continuous.\n", - __func__); - val |= BIT(AB8500_DIGIFCONF1_ENFSBITCLK0); - break; - case SND_SOC_DAIFMT_GATED: /* clock is gated */ - dev_dbg(component->dev, "%s: IF0 Clock is gated.\n", - __func__); - break; - default: - dev_err(component->dev, - "%s: ERROR: Unsupported clock mask (0x%x)!\n", - __func__, fmt & SND_SOC_DAIFMT_CLOCK_MASK); - return -EINVAL; - } - - snd_soc_component_update_bits(component, AB8500_DIGIFCONF1, mask, val); - - return 0; -} - static int ab8500_codec_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { - unsigned int mask; - unsigned int val; struct snd_soc_component *component = dai->component; - int status; + unsigned int conf1_mask, conf1_val = 0; + unsigned int conf2_mask, conf2_val = 0; + unsigned int conf3_mask, conf3_val = 0; + bool provider = false; + int ret; dev_dbg(component->dev, "%s: Enter (fmt = 0x%x)\n", __func__, fmt); - mask = BIT(AB8500_DIGIFCONF3_IF1DATOIF0AD) | + conf3_mask = BIT(AB8500_DIGIFCONF3_IF1DATOIF0AD) | BIT(AB8500_DIGIFCONF3_IF1CLKTOIF0CLK) | BIT(AB8500_DIGIFCONF3_IF0BFIFOEN) | BIT(AB8500_DIGIFCONF3_IF0MASTER); - val = 0; switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { case SND_SOC_DAIFMT_CBP_CFP: - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0 Master-mode: AB8500 provider.\n", __func__); - val |= BIT(AB8500_DIGIFCONF3_IF0MASTER); + conf3_val |= BIT(AB8500_DIGIFCONF3_IF0MASTER); + provider = true; break; case SND_SOC_DAIFMT_CBC_CFC: - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0 Master-mode: AB8500 consumer.\n", __func__); break; case SND_SOC_DAIFMT_CBC_CFP: case SND_SOC_DAIFMT_CBP_CFC: - dev_err(dai->component->dev, + dev_err(component->dev, "%s: ERROR: The device is either a provider or a consumer.\n", __func__); fallthrough; default: - dev_err(dai->component->dev, - "%s: ERROR: Unsupporter clocking mask 0x%x\n", + dev_err(component->dev, + "%s: ERROR: Unsupported clocking mask 0x%x\n", __func__, fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK); return -EINVAL; } - snd_soc_component_update_bits(component, AB8500_DIGIFCONF3, mask, val); - - /* Set clock gating */ - status = ab8500_codec_set_dai_clock_gate(component, fmt); - if (status) { - dev_err(dai->component->dev, - "%s: ERROR: Failed to set clock gate (%d).\n", - __func__, status); - return status; + conf1_mask = BIT(AB8500_DIGIFCONF1_ENMASTGEN) | + BIT(AB8500_DIGIFCONF1_ENFSBITCLK0); + switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { + case SND_SOC_DAIFMT_CONT: + if (provider) + conf1_val = conf1_mask; + break; + case SND_SOC_DAIFMT_GATED: + if (provider) + conf1_val = BIT(AB8500_DIGIFCONF1_ENMASTGEN); + break; + default: + dev_err(component->dev, "%s: Unsupported clock mask 0x%x\n", + __func__, fmt & SND_SOC_DAIFMT_CLOCK_MASK); + return -EINVAL; } - /* Setting data transfer format */ - - mask = BIT(AB8500_DIGIFCONF2_IF0FORMAT0) | - BIT(AB8500_DIGIFCONF2_IF0FORMAT1) | - BIT(AB8500_DIGIFCONF2_FSYNC0P) | - BIT(AB8500_DIGIFCONF2_BITCLK0P); - val = 0; + conf2_mask = BIT(AB8500_DIGIFCONF2_IF0FORMAT0) | + BIT(AB8500_DIGIFCONF2_IF0FORMAT1) | + BIT(AB8500_DIGIFCONF2_IF0DEL) | + BIT(AB8500_DIGIFCONF2_FSYNC0P) | + BIT(AB8500_DIGIFCONF2_BITCLK0P); switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: /* I2S mode */ - dev_dbg(dai->component->dev, "%s: IF0 Protocol: I2S\n", __func__); - val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT1); - ab8500_audio_set_bit_delay(dai, 0); + dev_dbg(component->dev, "%s: IF0 Protocol: I2S\n", __func__); + conf2_val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT1) | + BIT(AB8500_DIGIFCONF2_IF0DEL); break; case SND_SOC_DAIFMT_DSP_A: /* L data MSB after FRM LRC */ - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0 Protocol: DSP A (TDM)\n", __func__); - val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT0); - ab8500_audio_set_bit_delay(dai, 1); + conf2_val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT0) | + BIT(AB8500_DIGIFCONF2_IF0DEL); break; case SND_SOC_DAIFMT_DSP_B: /* L data MSB during FRM LRC */ - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0 Protocol: DSP B (TDM)\n", __func__); - val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT0); - ab8500_audio_set_bit_delay(dai, 0); + conf2_val |= BIT(AB8500_DIGIFCONF2_IF0FORMAT0); break; default: - dev_err(dai->component->dev, + dev_err(component->dev, "%s: ERROR: Unsupported format (0x%x)!\n", __func__, fmt & SND_SOC_DAIFMT_FORMAT_MASK); return -EINVAL; @@ -1887,39 +1829,50 @@ static int ab8500_codec_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */ - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0: Normal bit clock, normal frame\n", __func__); break; case SND_SOC_DAIFMT_NB_IF: /* normal BCLK + inv FRM */ - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0: Normal bit clock, inverted frame\n", __func__); - val |= BIT(AB8500_DIGIFCONF2_FSYNC0P); + conf2_val |= BIT(AB8500_DIGIFCONF2_FSYNC0P); break; case SND_SOC_DAIFMT_IB_NF: /* invert BCLK + nor FRM */ - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0: Inverted bit clock, normal frame\n", __func__); - val |= BIT(AB8500_DIGIFCONF2_BITCLK0P); + conf2_val |= BIT(AB8500_DIGIFCONF2_BITCLK0P); break; case SND_SOC_DAIFMT_IB_IF: /* invert BCLK + FRM */ - dev_dbg(dai->component->dev, + dev_dbg(component->dev, "%s: IF0: Inverted bit clock, inverted frame\n", __func__); - val |= BIT(AB8500_DIGIFCONF2_FSYNC0P); - val |= BIT(AB8500_DIGIFCONF2_BITCLK0P); + conf2_val |= BIT(AB8500_DIGIFCONF2_FSYNC0P) | + BIT(AB8500_DIGIFCONF2_BITCLK0P); break; default: - dev_err(dai->component->dev, + dev_err(component->dev, "%s: ERROR: Unsupported INV mask 0x%x\n", __func__, fmt & SND_SOC_DAIFMT_INV_MASK); return -EINVAL; } - snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, mask, val); + ret = snd_soc_component_update_bits(component, AB8500_DIGIFCONF3, + conf3_mask, conf3_val); + if (ret < 0) + return ret; - return 0; + ret = snd_soc_component_update_bits(component, AB8500_DIGIFCONF1, + conf1_mask, conf1_val); + if (ret < 0) + return ret; + + ret = snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, + conf2_mask, conf2_val); + + return ret < 0 ? ret : 0; } static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai, From 85cef7e2004ef5c1a715feaddb55d3f3d27bac0a Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 31 Aug 2026 22:29:09 +0200 Subject: [PATCH 0330/2366] ASoC: ab8500: Validate and program TDM slots correctly The interface clock ratio is selected from the slot count alone, ffs() and fls() produce one-based hardware slot numbers, eight-channel mode does not program any mappings, and all register errors are ignored. Invalid masks can also leave a partially programmed interface. Validate the complete configuration first, derive the supported BCLK ratio from slots times slot width, use zero-based slot indices, program deterministic eight-channel maps, and propagate register failures. Fixes: 679d7abdc754 ("ASoC: codecs: Add AB8500 codec-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260831-ab8500-codec-fixes-v1-4-f85024e717e3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 222 ++++++++++++++++++-------------- 1 file changed, 126 insertions(+), 96 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 4fbe486de74a..167f021c7ecb 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -1880,23 +1880,27 @@ static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai, int slots, int slot_width) { struct snd_soc_component *component = dai->component; - unsigned int val, mask, slot, slots_active; + unsigned int active_mask, clock_ratio, slot, value, ad_out, reg; + unsigned int tx_active, rx_active; + unsigned int conf1_val, conf2_val; + unsigned int mask; + int channel, ret; mask = BIT(AB8500_DIGIFCONF2_IF0WL0) | BIT(AB8500_DIGIFCONF2_IF0WL1); - val = 0; + conf2_val = 0; switch (slot_width) { case 16: break; case 20: - val |= BIT(AB8500_DIGIFCONF2_IF0WL0); + conf2_val |= BIT(AB8500_DIGIFCONF2_IF0WL0); break; case 24: - val |= BIT(AB8500_DIGIFCONF2_IF0WL1); + conf2_val |= BIT(AB8500_DIGIFCONF2_IF0WL1); break; case 32: - val |= BIT(AB8500_DIGIFCONF2_IF0WL1) | + conf2_val |= BIT(AB8500_DIGIFCONF2_IF0WL1) | BIT(AB8500_DIGIFCONF2_IF0WL0); break; default: @@ -1905,27 +1909,11 @@ static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai, return -EINVAL; } - dev_dbg(dai->component->dev, "%s: IF0 slot-width: %d bits.\n", - __func__, slot_width); - snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, mask, val); - - /* Setup TDM clocking according to slot count */ - dev_dbg(dai->component->dev, "%s: Slots, total: %d\n", __func__, slots); - mask = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0) | - BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); switch (slots) { case 2: - val = AB8500_MASK_NONE; - break; case 4: - val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0); - break; case 8: - val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); - break; case 16: - val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0) | - BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); break; default: dev_err(dai->component->dev, @@ -1933,94 +1921,136 @@ static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai, __func__, slots); return -EINVAL; } - snd_soc_component_update_bits(component, AB8500_DIGIFCONF1, mask, val); - /* Setup TDM DA according to active tx slots */ - - if (tx_mask & ~0xff) - return -EINVAL; - - mask = AB8500_DASLOTCONFX_SLTODAX_MASK; - tx_mask = tx_mask << AB8500_DA_DATA0_OFFSET; - slots_active = hweight32(tx_mask); - - dev_dbg(dai->component->dev, "%s: Slots, active, TX: %d\n", __func__, - slots_active); - - switch (slots_active) { - case 0: + clock_ratio = slots * slot_width; + switch (clock_ratio) { + case 32: + conf1_val = 0; break; - case 1: - slot = ffs(tx_mask); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF1, mask, slot); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF3, mask, slot); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF2, mask, slot); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF4, mask, slot); + case 64: + conf1_val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0); break; - case 2: - slot = ffs(tx_mask); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF1, mask, slot); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF3, mask, slot); - slot = fls(tx_mask); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF2, mask, slot); - snd_soc_component_update_bits(component, AB8500_DASLOTCONF4, mask, slot); + case 128: + conf1_val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); break; - case 8: - dev_dbg(dai->component->dev, - "%s: In 8-channel mode DA-from-slot mapping is set manually.", - __func__); + case 256: + conf1_val = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0) | + BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); break; default: - dev_err(dai->component->dev, - "%s: Unsupported number of active TX-slots (%d)!\n", - __func__, slots_active); + dev_err(component->dev, "%s: Unsupported BCLK ratio (%u)!\n", + __func__, clock_ratio); return -EINVAL; } - /* Setup TDM AD according to active RX-slots */ - - if (rx_mask & ~0xff) - return -EINVAL; - - rx_mask = rx_mask << AB8500_AD_DATA0_OFFSET; - slots_active = hweight32(rx_mask); - - dev_dbg(dai->component->dev, "%s: Slots, active, RX: %d\n", __func__, - slots_active); - - switch (slots_active) { - case 0: - break; - case 1: - slot = ffs(rx_mask); - snd_soc_component_update_bits(component, AB8500_ADSLOTSEL(slot), - AB8500_MASK_SLOT(slot), - AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT3, slot)); - break; - case 2: - slot = ffs(rx_mask); - snd_soc_component_update_bits(component, - AB8500_ADSLOTSEL(slot), - AB8500_MASK_SLOT(slot), - AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT3, slot)); - slot = fls(rx_mask); - snd_soc_component_update_bits(component, - AB8500_ADSLOTSEL(slot), - AB8500_MASK_SLOT(slot), - AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT2, slot)); - break; - case 8: - dev_dbg(dai->component->dev, - "%s: In 8-channel mode AD-to-slot mapping is set manually.", + active_mask = GENMASK(min(slots, 8) - 1, 0); + if ((tx_mask | rx_mask) & ~active_mask) { + dev_err(component->dev, "%s: Slot mask exceeds slot count\n", __func__); - break; - default: - dev_err(dai->component->dev, - "%s: Unsupported number of active RX-slots (%d)!\n", - __func__, slots_active); return -EINVAL; } + tx_active = hweight32(tx_mask); + rx_active = hweight32(rx_mask); + if (tx_active != 0 && tx_active != 1 && tx_active != 2 && + tx_active != 8) { + dev_err(component->dev, "%s: Unsupported active TX slots (%u)!\n", + __func__, tx_active); + return -EINVAL; + } + if (rx_active != 0 && rx_active != 1 && rx_active != 2 && + rx_active != 8) { + dev_err(component->dev, "%s: Unsupported active RX slots (%u)!\n", + __func__, rx_active); + return -EINVAL; + } + + dev_dbg(component->dev, + "%s: %d slots of %d bits, TX active: %u, RX active: %u\n", + __func__, slots, slot_width, tx_active, rx_active); + + ret = snd_soc_component_update_bits(component, AB8500_DIGIFCONF2, + mask, conf2_val); + if (ret < 0) + return ret; + + mask = BIT(AB8500_DIGIFCONF1_IF0BITCLKOS0) | + BIT(AB8500_DIGIFCONF1_IF0BITCLKOS1); + ret = snd_soc_component_update_bits(component, AB8500_DIGIFCONF1, + mask, conf1_val); + if (ret < 0) + return ret; + + mask = AB8500_DASLOTCONFX_SLTODAX_MASK; + if (tx_active == 1 || tx_active == 2) { + slot = __ffs(tx_mask) + AB8500_DA_DATA0_OFFSET; + reg = AB8500_DASLOTCONF1; + ret = snd_soc_component_update_bits(component, reg, mask, slot); + if (ret < 0) + return ret; + reg = AB8500_DASLOTCONF3; + ret = snd_soc_component_update_bits(component, reg, mask, slot); + if (ret < 0) + return ret; + + if (tx_active == 2) + slot = __fls(tx_mask) + AB8500_DA_DATA0_OFFSET; + reg = AB8500_DASLOTCONF2; + ret = snd_soc_component_update_bits(component, reg, mask, slot); + if (ret < 0) + return ret; + reg = AB8500_DASLOTCONF4; + ret = snd_soc_component_update_bits(component, reg, mask, slot); + if (ret < 0) + return ret; + } else if (tx_active == 8) { + channel = 0; + for (slot = 0; slot < 8; slot++) { + if (!(tx_mask & BIT(slot))) + continue; + reg = AB8500_DASLOTCONF1 + channel++; + value = slot + AB8500_DA_DATA0_OFFSET; + ret = snd_soc_component_update_bits(component, reg, mask, value); + if (ret < 0) + return ret; + } + } + + if (rx_active == 1 || rx_active == 2) { + slot = __ffs(rx_mask) + AB8500_AD_DATA0_OFFSET; + value = AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT3, + slot); + reg = AB8500_ADSLOTSEL(slot); + mask = AB8500_MASK_SLOT(slot); + ret = snd_soc_component_update_bits(component, reg, mask, value); + if (ret < 0) + return ret; + + if (rx_active == 2) { + slot = __fls(rx_mask) + AB8500_AD_DATA0_OFFSET; + value = AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(AB8500_AD_OUT2, + slot); + reg = AB8500_ADSLOTSEL(slot); + mask = AB8500_MASK_SLOT(slot); + ret = snd_soc_component_update_bits(component, reg, mask, value); + if (ret < 0) + return ret; + } + } else if (rx_active == 8) { + channel = 0; + for (slot = 0; slot < 8; slot++) { + if (!(rx_mask & BIT(slot))) + continue; + ad_out = AB8500_AD_OUT1 + channel++; + value = AB8500_ADSLOTSELX_AD_OUT_TO_SLOT(ad_out, slot); + reg = AB8500_ADSLOTSEL(slot); + mask = AB8500_MASK_SLOT(slot); + ret = snd_soc_component_update_bits(component, reg, mask, value); + if (ret < 0) + return ret; + } + } + return 0; } From ec75e653b70ce26ea68187c2069722baa80efadb Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 31 Aug 2026 22:29:10 +0200 Subject: [PATCH 0331/2366] ASoC: ab8500: Remove the nonfunctional sidetone apply control After the coefficient controls were removed, writing "Apply FIR" programs 128 zero coefficients and reports that the sidetone filter is configured. The associated ANC configuration DAPM pins are also now unreachable dead infrastructure. Remove the misleading status/apply control, its private state, and the obsolete configuration-only DAPM pins. Keep the direct sidetone reset and remaining hardware controls. Fixes: e366ce8b22ec ("ASoC: codecs: ab8500: Remove suspicious code") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260831-ab8500-codec-fixes-v1-5-f85024e717e3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 114 -------------------------------- 1 file changed, 114 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 167f021c7ecb..11142d15df45 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -14,17 +14,14 @@ * for ST-Ericsson. */ -#include #include #include #include #include #include #include -#include #include #include -#include #include #include #include @@ -54,32 +51,9 @@ /* Macrocell register definitions */ #define AB8500_GPIO_DIR4_REG 0x13 /* Bank AB8500_MISC */ -/* Nr of FIR/IIR-coeff banks in ANC-block */ -#define AB8500_NR_OF_ANC_COEFF_BANKS 2 - -/* Minimum duration to keep ANC IIR Init bit high or -low before proceeding with the configuration sequence */ -#define AB8500_ANC_SM_DELAY 2000 - -/* Sidetone states */ -static const char * const enum_sid_state[] = { - "Unconfigured", - "Apply FIR", - "FIR is configured", -}; -enum sid_state { - SID_UNCONFIGURED = 0, - SID_APPLY_FIR = 1, - SID_FIR_CONFIGURED = 2, -}; - /* Private data for AB8500 device-driver */ struct ab8500_codec_drvdata { struct regmap *regmap; - struct mutex ctrl_lock; - - /* Sidetone */ - enum sid_state sid_status; }; static inline const char *amic_micbias_str(enum amic_micbias micbias) @@ -642,9 +616,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { NULL, 0), /* Acoustical Noise Cancellation path */ - SND_SOC_DAPM_INPUT("ANC Configure Input"), - SND_SOC_DAPM_OUTPUT("ANC Configure Output"), - SND_SOC_DAPM_MUX("ANC Source", SND_SOC_NOPM, 0, 0, dapm_anc_in_select), @@ -687,10 +658,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"Main Supply", NULL, "Audio Power"}, {"Main Supply", NULL, "Audio Analog Power"}, - /* ANC Configure */ - {"ANC Configure Input", NULL, "Main Supply"}, - {"ANC Configure Output", NULL, "ANC Configure Input"}, - /* Powerup charge pump if DA1/2 is in use */ {"DA_IN1", NULL, "ab8500_0p"}, @@ -972,75 +939,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes_mic2_vamicx[] = { {"MIC2 V-AMICx Enable", NULL, "V-AMIC2"}, }; -/* - * Control-events - */ - -static int sid_status_control_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); - - guard(mutex)(&drvdata->ctrl_lock); - ucontrol->value.enumerated.item[0] = drvdata->sid_status; - - return 0; -} - -/* Write sidetone FIR-coefficients configuration sequence */ -static int sid_status_control_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); - unsigned int param, sidconf; - int status = 1; - - dev_dbg(component->dev, "%s: Enter\n", __func__); - - if (ucontrol->value.enumerated.item[0] != SID_APPLY_FIR) { - dev_err(component->dev, - "%s: ERROR: This control supports '%s' only!\n", - __func__, enum_sid_state[SID_APPLY_FIR]); - return -EIO; - } - - guard(mutex)(&drvdata->ctrl_lock); - - sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); - if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { - if ((sidconf & BIT(AB8500_SIDFIRCONF_ENFIRSIDS)) == 0) { - dev_err(component->dev, "%s: Sidetone busy while off!\n", - __func__); - status = -EPERM; - } else { - status = -EBUSY; - } - dev_dbg(component->dev, "%s: Exit\n", __func__); - return status; - } - - snd_soc_component_write(component, AB8500_SIDFIRADR, 0); - - for (param = 0; param < AB8500_SID_FIR_COEFFS; param++) { - snd_soc_component_write(component, AB8500_SIDFIRCOEF1, 0); - snd_soc_component_write(component, AB8500_SIDFIRCOEF2, 0); - } - - snd_soc_component_update_bits(component, AB8500_SIDFIRADR, - BIT(AB8500_SIDFIRADR_FIRSIDSET), - BIT(AB8500_SIDFIRADR_FIRSIDSET)); - snd_soc_component_update_bits(component, AB8500_SIDFIRADR, - BIT(AB8500_SIDFIRADR_FIRSIDSET), 0); - - drvdata->sid_status = SID_FIR_CONFIGURED; - - dev_dbg(component->dev, "%s: Exit\n", __func__); - - return status; -} - /* * Controls - Non-DAPM ASoC */ @@ -1324,9 +1222,6 @@ static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast, AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOMAST_SHIFT, enum_slavemaster); -/* Sidetone */ -static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_sidstate, enum_sid_state); - /* ANC */ static struct snd_kcontrol_new ab8500_ctrls[] = { @@ -1617,8 +1512,6 @@ static struct snd_kcontrol_new ab8500_ctrls[] = { AB8500_ANC_WARP_DELAY_MIN, AB8500_ANC_WARP_DELAY_MAX, 0), /* Sidetone */ - SOC_ENUM_EXT("Sidetone Status", soc_enum_sidstate, - sid_status_control_get, sid_status_control_put), SOC_SINGLE_STROBE("Sidetone Reset", AB8500_SIDFIRADR, AB8500_SIDFIRADR_FIRSIDSET, 0), }; @@ -2145,10 +2038,8 @@ static void ab8500_codec_of_probe(struct device *dev, struct device_node *np, static int ab8500_codec_probe(struct snd_soc_component *component) { - struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct device *dev = component->dev; struct device_node *np = dev->of_node; - struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); struct ab8500_codec_platform_data codec_pdata; int status; @@ -2181,10 +2072,6 @@ static int ab8500_codec_probe(struct snd_soc_component *component) snd_soc_component_write(component, AB8500_SHORTCIRCONF, BIT(AB8500_SHORTCIRCONF_HSZCDDIS)); - snd_soc_dapm_disable_pin(dapm, "ANC Configure Input"); - - mutex_init(&drvdata->ctrl_lock); - return status; } @@ -2213,7 +2100,6 @@ static int ab8500_codec_driver_probe(struct platform_device *pdev) GFP_KERNEL); if (!drvdata) return -ENOMEM; - drvdata->sid_status = SID_UNCONFIGURED; dev_set_drvdata(&pdev->dev, drvdata); drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev, From 711178754287db3fd0f7accff3c2a7575f8873b7 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 31 Aug 2026 22:29:11 +0200 Subject: [PATCH 0332/2366] ASoC: ab8500: Skip missing DMIC GPIOs on AB8505 GPIO27, GPIO29 and GPIO31 provide the digital microphone clock outputs on AB8500, but these GPIOs do not exist on AB8505. The shared codec driver nevertheless accesses their direction register while setting up every AB8505 codec. Identify the parent MFD device and leave the nonexistent GPIOs untouched on AB8505. Fixes: 679d7abdc754 ("ASoC: codecs: Add AB8500 codec-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260831-ab8500-codec-fixes-v1-6-f85024e717e3@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/ab8500-codec.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index 11142d15df45..e1a0e35836e6 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -1540,6 +1540,8 @@ static int ab8500_audio_init_audioblock(struct snd_soc_component *component) static int ab8500_audio_setup_mics(struct snd_soc_component *component, struct amic_settings *amics) { + struct device *dev = component->dev; + struct ab8500 *ab8500 = dev_get_drvdata(dev->parent); struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); u8 value8; unsigned int value; @@ -1548,20 +1550,21 @@ static int ab8500_audio_setup_mics(struct snd_soc_component *component, dev_dbg(component->dev, "%s: Enter.\n", __func__); - /* Set DMic-clocks to outputs */ - status = abx500_get_register_interruptible(component->dev, AB8500_MISC, - AB8500_GPIO_DIR4_REG, - &value8); - if (status < 0) - return status; - value = value8 | GPIO27_DIR_OUTPUT | GPIO29_DIR_OUTPUT | - GPIO31_DIR_OUTPUT; - status = abx500_set_register_interruptible(component->dev, - AB8500_MISC, - AB8500_GPIO_DIR4_REG, - value); - if (status < 0) - return status; + /* Set DMic-clocks to outputs; these GPIOs do not exist on AB8505. */ + if (!is_ab8505(ab8500)) { + status = abx500_get_register_interruptible(dev, AB8500_MISC, + AB8500_GPIO_DIR4_REG, + &value8); + if (status < 0) + return status; + value = value8 | GPIO27_DIR_OUTPUT | GPIO29_DIR_OUTPUT | + GPIO31_DIR_OUTPUT; + status = abx500_set_register_interruptible(dev, AB8500_MISC, + AB8500_GPIO_DIR4_REG, + value); + if (status < 0) + return status; + } /* Attach regulators to AMic DAPM-paths */ dev_dbg(component->dev, "%s: Mic 1a regulator: %s\n", __func__, From 7a4ce92d150b9e7ecf1a710a34d8cdeb590d3751 Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Mon, 31 Aug 2026 15:13:36 +0000 Subject: [PATCH 0333/2366] ASoC: sprd: validate compress buffer sizes against fixed allocations sprd_platform_compr_open() allocates the stage 0 IRAM buffer (32K data area) and the stage 1 DDR buffer (2M data area) with fixed sizes, but sprd_platform_compr_copy() derives all copy lengths from the user controlled runtime->fragment_size and the write() count, never comparing them against the physical buffer sizes. The compress core only checks fragment_size * fragments for an u32 overflow in snd_compress_check_input(), so a local user can configure a logical buffer of up to ~4GB via SNDRV_COMPRESS_SET_PARAMS, far exceeding the fixed allocations. A fragment_size larger than the 32K IRAM data area makes the stage 0 copy_from_user() overflow past the IRAM allocation, and a buffer_size larger than the 2M DDR buffer makes the wrapping copy at the end of sprd_platform_compr_copy() write fully user controlled data past the buffer. No SNDRV_PCM_TRIGGER_START is needed, a write() in SETUP state reaches the copy callback directly. Reject parameters that do not fit into the fixed buffers in set_params(), and fix the advertised max fragment size: 128K never fitted into the 32K IRAM buffer. The caps values may have been carried over from the qdsp6 driver, which allocates its buffers according to the advertised maxima, unlike this driver. With 32K as max fragment size the advertised limits are self-consistent: 32K * 64 = 2M equals the DDR buffer size. Discovered by Atuin - Automated Vulnerability Discovery Engine. Fixes: cce1396936ef ("ASoC: sprd: Add Spreadtrum audio compress offload support") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Tianchu Chen Link: https://patch.msgid.link/4386bc53631b052c1866a91061715b009d98b04f@linux.dev Signed-off-by: Mark Brown --- sound/soc/sprd/sprd-pcm-compress.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sound/soc/sprd/sprd-pcm-compress.c b/sound/soc/sprd/sprd-pcm-compress.c index a7d437b49fbf..e5249924b54d 100644 --- a/sound/soc/sprd/sprd-pcm-compress.c +++ b/sound/soc/sprd/sprd-pcm-compress.c @@ -17,7 +17,7 @@ /* Default values if userspace does not set */ #define SPRD_COMPR_MIN_FRAGMENT_SIZE SZ_8K -#define SPRD_COMPR_MAX_FRAGMENT_SIZE SZ_128K +#define SPRD_COMPR_MAX_FRAGMENT_SIZE SZ_32K #define SPRD_COMPR_MIN_NUM_FRAGMENTS 4 #define SPRD_COMPR_MAX_NUM_FRAGMENTS 64 @@ -271,6 +271,19 @@ static int sprd_platform_compr_set_params(struct snd_soc_component *component, struct sprd_compr_params compr_params = { }; int ret; + /* + * The stage 0 IRAM buffer and the stage 1 DDR buffer are allocated + * with fixed sizes at open time, so the requested fragment size and + * fragments must fit into them, otherwise sprd_platform_compr_copy() + * would overflow the buffers. Note the compress core only checks the + * fragment size and fragments against an u32 overflow, not against + * the buffer sizes advertised by get_caps. + */ + if (params->buffer.fragment_size > SPRD_COMPR_IRAM_BUF_SIZE || + (u64)params->buffer.fragment_size * params->buffer.fragments > + SPRD_COMPR_AREA_BUF_SIZE) + return -EINVAL; + /* * Configure the DMA engine 2-stage transfer mode. Channel 1 set as the * destination channel, and channel 0 set as the source channel, that From 93d88ac4a448f200d18d1d71de0074b8e716bbee Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Tue, 25 Aug 2026 10:35:56 +0800 Subject: [PATCH 0334/2366] sched_ext: Pass the initial cpu.idle state in scx_cgroup_init_args scx_cgroup_init_args carries the initial weight and bandwidth control parameters of a cgroup to ops.cgroup_init(), but not its cpu.idle state. A cgroup that was already configured idle before the scheduler was loaded (or before it was onlined under it) is presented as non-idle, and the BPF scheduler only learns about it if cpu.idle is written again later. Add the sched_idle state to scx_cgroup_init_args and fill it in all four places that build the args: scx_tg_online() for cgroups onlined under the scheduler, scx_cgroup_init() for cgroups that already exist when the scheduler is loaded, and the sub-scheduler handover paths scx_cgroup_claim_subtree() and scx_cgroup_return_subtree(). Verified in a VM with a probe scheduler printing the init args: a cgroup configured cpu.idle=1 before loading shows sched_idle=1 in ops.cgroup_init(), the default shows 0, and later cpu.idle writes still come through ops.cgroup_set_idle(). The sub-scheduler paths are compile-tested only. Fixes: 347ed2d566da ("sched/ext: Implement cgroup_set_idle() callback") Signed-off-by: Tao Cui Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 4 +++- kernel/sched/ext/internal.h | 3 +++ kernel/sched/ext/sub.c | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 713aa26b2828..901fb0f8b976 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -4766,7 +4766,8 @@ int scx_tg_online(struct task_group *tg) { .weight = tg->scx.weight, .bw_period_us = tg->scx.bw_period_us, .bw_quota_us = tg->scx.bw_quota_us, - .bw_burst_us = tg->scx.bw_burst_us }; + .bw_burst_us = tg->scx.bw_burst_us, + .sched_idle = tg->scx.idle }; ret = SCX_CALL_OP_RET(sch, cgroup_init, NULL, tg->css.cgroup, &args); @@ -5187,6 +5188,7 @@ static int scx_cgroup_init(struct scx_sched *sch) .bw_period_us = tg->scx.bw_period_us, .bw_quota_us = tg->scx.bw_quota_us, .bw_burst_us = tg->scx.bw_burst_us, + .sched_idle = tg->scx.idle, }; ret = SCX_CALL_OP_RET(sch, cgroup_init, NULL, css->cgroup, &args); diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 0967b99a4948..076a351bb3f2 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -259,6 +259,9 @@ struct scx_cgroup_init_args { u64 bw_period_us; u64 bw_quota_us; u64 bw_burst_us; + + /* whether the cgroup is configured SCHED_IDLE via cpu.idle */ + bool sched_idle; }; enum scx_cpu_preempt_reason { diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index 0554448835bd..385302d19914 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -1361,6 +1361,7 @@ static s32 scx_cgroup_claim_subtree(struct scx_sched *sch) .bw_period_us = tg->scx.bw_period_us, .bw_quota_us = tg->scx.bw_quota_us, .bw_burst_us = tg->scx.bw_burst_us, + .sched_idle = tg->scx.idle, }; if (tg->scx.sched != parent || @@ -1464,6 +1465,7 @@ static void scx_cgroup_return_subtree(struct scx_sched *sch) .bw_period_us = tg->scx.bw_period_us, .bw_quota_us = tg->scx.bw_quota_us, .bw_burst_us = tg->scx.bw_burst_us, + .sched_idle = tg->scx.idle, }; /* the first pass must have transferred everything */ From c4126f1db36e6b2e1c79b0e30a8a2de91c568f4c Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Mon, 10 Aug 2026 14:58:45 +0530 Subject: [PATCH 0335/2366] drm/pagemap: Prevent double migration of device pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A device-private folio migrated to system memory by a CPU fault can remain reachable through the raw-PFN eviction path until migration finalization drops the source reference. If eviction selects the same device-private folio during this window, it can attempt to migrate the folio again. The second migration can leave an uncharged folio on an LRU list, causing folio_lruvec_lock_irqsave() to retry indefinitely and resulting in a soft lockup and RCU stall. Mark successfully migrated device-private folios using a low bit of their zone_device_data before migration finalization. Make both CPU-fault and raw-PFN migration paths skip device-private folios carrying this flag. Mask the flag when retrieving the drm_pagemap_zdd pointer and preserve it when a device-private folio is split. Keeping the state on the physical folio also avoids depending on a virtual address that may change before a fault occurs. v2: - Replace the retired-PFN XArray with an embedded bitmap. (Matthew Brost) - Mark every base page covered by a migrated folio so retirement remains valid if the folio is later split. v3: - Store the migrated state in a low bit of zone_device_data instead of adding virtual-range and bitmap tracking to the ZDD. (Matthew Brost) - Mask the flag when retrieving the ZDD and preserve it when splitting a folio. - Drop the pre-existing fixes already covered by Matthew Brost's series: https://patchwork.freedesktop.org/series/171651/ v4: - Advance by the folio size only for migration entries marked with MIGRATE_PFN_COMPOUND. (Sashiko) v5: - Simplify ZDD flag updates and folio iteration. (Matthew Brost) - Skip retired device-private folios in the CPU-fault path. (Matthew Brost) - Preserve flag bits while taking a new ZDD reference for split folios. v6: - Restore MIGRATE_PFN_COMPOUND-aware stepping so non-compound migration entries are processed one at a time. (Sashiko) - Drop the pre-existing fixes already covered by Matthew Brost's series: https://patchwork.freedesktop.org/series/171651/ The lockup was observed as: [10109.860465] watchdog: BUG: soft lockup - CPU#9 stuck for 26s! [kworker/u65:5:6557] [10109.860524] Tainted: [S]=CPU_OUT_OF_SPEC, [O]=OOT_MODULE [10109.860524] Hardware name: ASUS System Product Name/PRIME Z790-P WIFI, BIOS 0812 02/24/2023 [10109.860525] Workqueue: xe_page_fault_work_queue xe_pagefault_queue_work [xe] [10109.860644] RIP: 0010:_raw_spin_unlock_irqrestore+0x57/0x80 [10109.860655] Call Trace: [10109.860655] [10109.860657] folio_lruvec_lock_irqsave+0x216/0x220 [10109.860661] ? __pfx_lru_add+0x10/0x10 [10109.860665] folio_batch_move_lru+0xc8/0x450 [10109.860670] ? lock_acquire+0xc4/0x2d0 [10109.860674] ? __folio_batch_add_and_move+0x60/0x2e0 [10109.860677] ? folio_migrate_mapping+0xa6/0x110 [10109.860679] ? folio_migrate_flags+0x13b/0x1b0 [10109.860681] ? __pfx_lru_add+0x10/0x10 [10109.860683] __folio_batch_add_and_move+0xe7/0x2e0 [10109.860685] ? dma_iova_try_alloc+0xb0/0x140 [10109.860689] folio_add_lru+0x64/0x80 [10109.860691] __migrate_device_finalize+0x12c/0x270 [10109.860695] migrate_device_finalize+0x10/0x20 [10109.860698] drm_pagemap_evict_to_ram+0x185/0x370 [drm_gpusvm_helper] [10109.860704] ? drm_pagemap_evict_to_ram+0x96/0x370 [drm_gpusvm_helper] [10109.860709] xe_svm_bo_evict+0x15/0x20 [xe] [10109.860819] ? xe_svm_bo_evict+0x15/0x20 [xe] [10109.860921] xe_bo_move+0x107e/0x1570 [xe] [10109.860992] ? xe_ttm_tt_create+0x168/0x340 [xe] [10109.861059] ? __up_read+0x98/0x2b0 [10109.861061] ? lock_is_held_type+0xa3/0x130 [10109.861067] ttm_bo_handle_move_mem+0xe8/0x1e0 [ttm] [10109.861075] ttm_bo_evict+0x141/0x1c0 [ttm] [10109.861081] ttm_bo_evict_cb+0x9f/0x100 [ttm] [10109.861086] ttm_lru_walk_for_evict+0x84/0x190 [ttm] [10109.861091] ? xe_ttm_vram_mgr_new+0x258/0x3a0 [xe] [10109.861198] ttm_bo_alloc_resource+0x219/0x750 [ttm] [10109.861203] ? ttm_bo_alloc_resource+0xa9/0x750 [ttm] [10109.861208] ? lock_acquire+0xc4/0x2d0 [10109.861214] ttm_bo_validate+0x94/0x1c0 [ttm] [10109.861218] ? ww_mutex_trylock+0x19d/0x3d0 [10109.861219] ? _raw_write_unlock+0x22/0x50 [10109.861223] ttm_bo_init_reserved+0x17d/0x1f0 [ttm] [10109.861228] xe_bo_init_locked+0x20a/0x620 [xe] [10109.861294] ? __pfx_xe_ttm_bo_destroy+0x10/0x10 [xe] [10109.861359] ? mark_held_locks+0x46/0x90 [10109.861361] ? __create_object+0x68/0xc0 [10109.861366] __xe_bo_create_locked+0x384/0xa20 [xe] [10109.861432] ? lock_acquire+0xc4/0x2d0 [10109.861434] ? xe_drm_pagemap_populate_mm+0xd3/0x340 [xe] [10109.861542] xe_bo_create_locked+0x23/0x40 [xe] [10109.861609] xe_drm_pagemap_populate_mm+0x12e/0x340 [xe] [10109.861707] ? __lock_acquire+0x43e/0x2930 [10109.861716] drm_pagemap_populate_mm+0x74/0xe0 [drm_gpusvm_helper] [10109.861720] xe_svm_alloc_vram+0xb5/0x2c0 [xe] [10109.861817] ? seqcount_lockdep_reader_access.constprop.0+0x9f/0xc0 [10109.861819] ? ktime_get+0x23/0x130 [10109.861821] ? trace_hardirqs_on+0x22/0xe0 [10109.861823] ? seqcount_lockdep_reader_access.constprop.0+0x9f/0xc0 [10109.861826] __xe_svm_handle_pagefault+0x77d/0xbf0 [xe] [10109.861924] ? rwsem_down_write_slowpath+0x43a/0x9a0 [10109.861926] ? _raw_spin_unlock_irq+0x27/0x70 [10109.861928] ? rwsem_down_write_slowpath+0x43a/0x9a0 [10109.861929] ? trace_hardirqs_on+0x22/0xe0 [10109.861931] ? _raw_spin_unlock_irq+0x27/0x70 [10109.861933] ? rwsem_down_write_slowpath+0x459/0x9a0 [10109.861937] xe_svm_handle_pagefault+0x3d/0xb0 [xe] [10109.862030] xe_pagefault_queue_work+0x1a9/0x520 [xe] [10109.862122] process_one_work+0x239/0x730 [10109.862127] worker_thread+0x200/0x3f0 [10109.862130] ? __pfx_worker_thread+0x10/0x10 [10109.862132] kthread+0x10d/0x150 [10109.862133] ? __pfx_kthread+0x10/0x10 [10109.862135] ret_from_fork+0x3bd/0x470 [10109.862138] ? __pfx_kthread+0x10/0x10 [10109.862140] ret_from_fork_asm+0x1a/0x30 [10109.862146] Fixes: 99624bdff867 ("drm/gpusvm: Add support for GPU Shared Virtual Memory") Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: Matthew Brost Cc: Thomas Zimmermann Cc: David Airlie Cc: Simona Vetter Cc: Thomas Hellström Cc: Himal Prasad Ghimiray Assisted-by: Claude:claude-opus-4-8 Suggested-by: Matthew Brost Signed-off-by: Arvind Yadav Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260810092845.2776097-1-arvind.yadav@intel.com --- drivers/gpu/drm/drm_pagemap.c | 127 ++++++++++++++++++++++++++++++++-- include/drm/drm_pagemap.h | 8 ++- 2 files changed, 129 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index 892b325fa99b..c5906f153ada 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -1102,12 +1102,117 @@ void drm_pagemap_put(struct drm_pagemap *dpagemap) } EXPORT_SYMBOL(drm_pagemap_put); +/** + * drm_pagemap_page_get_flags() - Read flags from a device-private folio + * @page: Pointer to a page of the device-private folio + * + * Return: The DRM_PAGEMAP_ZDD_FLAG_* bits encoded in zone_device_data. + */ +static unsigned long drm_pagemap_page_get_flags(struct page *page) +{ + struct folio *folio = page_folio(page); + + return (unsigned long)folio_zone_device_data(folio) & + DRM_PAGEMAP_ZDD_FLAG_MASK; +} + +/** + * drm_pagemap_page_set_flags() - Set flags on a device-private folio + * @page: Pointer to a page of the device-private folio + * @flags: DRM_PAGEMAP_ZDD_FLAG_* bits to set + * + * Preserve any flags already encoded alongside the ZDD pointer. + */ +static void drm_pagemap_page_set_flags(struct page *page, + unsigned long flags) +{ + struct folio *folio = page_folio(page); + unsigned long old; + + if (WARN_ON_ONCE(flags & ~DRM_PAGEMAP_ZDD_FLAG_MASK)) + return; + + old = (unsigned long)folio_zone_device_data(folio); + folio_set_zone_device_data(folio, (void *)(old | flags)); +} + +/** + * drm_pagemap_retire_migrated_pages() - Record migrated device-private folios + * @src_pfns: source array after migrate_vma_pages() or migrate_device_pages() + * @npages: number of entries in @src_pfns + * + * Flag device-private folios successfully migrated to RAM before finalize + * unlocks the sources. The migrated state is stored in the physical folio, so + * it survives later folio splits and subsequent migrations can skip it. + */ +static void drm_pagemap_retire_migrated_pages(unsigned long *src_pfns, + unsigned long npages) +{ + unsigned long i = 0; + + while (i < npages) { + struct page *page = migrate_pfn_to_page(src_pfns[i]); + unsigned long nr = 1; + + if (!page) { + i++; + continue; + } + + if (src_pfns[i] & MIGRATE_PFN_COMPOUND) + nr = folio_nr_pages(page_folio(page)); + + if ((src_pfns[i] & MIGRATE_PFN_MIGRATE) && + is_device_private_page(page)) + drm_pagemap_page_set_flags(page, + DRM_PAGEMAP_ZDD_FLAG_MIGRATED); + + i += nr; + } +} + +/** + * drm_pagemap_skip_retired_pages() - Skip retired device-private folios + * @src_pfns: MIGRATE_PFN-encoded source array + * @npages: number of entries in @src_pfns + * + * Skip source folios already migrated to RAM, identified by the migrated flag + * stored in the physical folio's zone_device_data. + */ +static void drm_pagemap_skip_retired_pages(unsigned long *src_pfns, + unsigned long npages) +{ + unsigned long i = 0; + + while (i < npages) { + struct page *page = migrate_pfn_to_page(src_pfns[i]); + unsigned long nr = 1; + + if (!page) { + i++; + continue; + } + + if (src_pfns[i] & MIGRATE_PFN_COMPOUND) + nr = folio_nr_pages(page_folio(page)); + + if ((src_pfns[i] & MIGRATE_PFN_MIGRATE) && + is_device_private_page(page) && + (drm_pagemap_page_get_flags(page) & + DRM_PAGEMAP_ZDD_FLAG_MIGRATED)) + src_pfns[i] &= ~MIGRATE_PFN_MIGRATE; + + i += nr; + } +} + /** * drm_pagemap_evict_to_ram() - Evict GPU SVM range to RAM * @devmem_allocation: Pointer to the device memory allocation * - * Similar to __drm_pagemap_migrate_to_ram but does not require mmap lock and - * migration done via migrate_device_* functions. + * Similar to __drm_pagemap_migrate_to_ram(), but uses the + * migrate_device_* helpers and does not require the mmap lock. + * Device-private PFNs already migrated to RAM by either path are skipped. * * Return: 0 on success, negative error code on failure. */ @@ -1148,6 +1253,8 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) if (err) goto err_free; + drm_pagemap_skip_retired_pages(src, npages); + err = drm_pagemap_migrate_populate_ram_pfn(NULL, NULL, npages, &mpages, src, dst, 0); if (err || !mpages) @@ -1178,6 +1285,7 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) if (err) drm_pagemap_migration_unlock_put_pages(npages, dst); migrate_device_pages(src, dst, npages); + drm_pagemap_retire_migrated_pages(src, npages); migrate_device_finalize(src, dst, npages); drm_pagemap_migrate_unmap_pages(devmem_allocation->dev, pagemap_addr, dst, npages, DMA_FROM_DEVICE, &state); @@ -1275,13 +1383,15 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas, if (!migrate.cpages) goto err_free; + drm_pagemap_skip_retired_pages(migrate.src, npages); + ops = zdd->devmem_allocation->ops; dev = zdd->devmem_allocation->dev; err = drm_pagemap_migrate_populate_ram_pfn(vas, page, npages, &mpages, migrate.src, migrate.dst, start); - if (err) + if (err || !mpages) goto err_finalize; err = drm_pagemap_migrate_map_system_pages(dev, pagemap_addr, @@ -1308,6 +1418,7 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas, if (err) drm_pagemap_migration_unlock_put_pages(npages, migrate.dst); migrate_vma_pages(&migrate); + drm_pagemap_retire_migrated_pages(migrate.src, npages); migrate_vma_finalize(&migrate); if (dev) drm_pagemap_migrate_unmap_pages(dev, pagemap_addr, migrate.dst, @@ -1360,13 +1471,19 @@ static vm_fault_t drm_pagemap_migrate_to_ram(struct vm_fault *vmf) static void drm_pagemap_folio_split(struct folio *orig_folio, struct folio *new_folio) { struct drm_pagemap_zdd *zdd; + unsigned long orig_data, new_data; if (!new_folio) return; new_folio->pgmap = orig_folio->pgmap; - zdd = folio_zone_device_data(orig_folio); - folio_set_zone_device_data(new_folio, drm_pagemap_zdd_get(zdd)); + + orig_data = (unsigned long)folio_zone_device_data(orig_folio); + zdd = (struct drm_pagemap_zdd *)(orig_data & ~DRM_PAGEMAP_ZDD_FLAG_MASK); + + new_data = (unsigned long)drm_pagemap_zdd_get(zdd); + new_data |= orig_data & DRM_PAGEMAP_ZDD_FLAG_MASK; + folio_set_zone_device_data(new_folio, (void *)new_data); } static const struct dev_pagemap_ops drm_pagemap_pagemap_ops = { diff --git a/include/drm/drm_pagemap.h b/include/drm/drm_pagemap.h index 95eb4b66b057..ebbd3b0ddf36 100644 --- a/include/drm/drm_pagemap.h +++ b/include/drm/drm_pagemap.h @@ -2,6 +2,7 @@ #ifndef _DRM_PAGEMAP_H_ #define _DRM_PAGEMAP_H_ +#include #include #include #include @@ -339,6 +340,9 @@ struct drm_pagemap_migrate_details { #if IS_ENABLED(CONFIG_ZONE_DEVICE) +#define DRM_PAGEMAP_ZDD_FLAG_MIGRATED BIT(0) +#define DRM_PAGEMAP_ZDD_FLAG_MASK DRM_PAGEMAP_ZDD_FLAG_MIGRATED + int drm_pagemap_migrate_to_devmem(struct drm_pagemap_devmem *devmem_allocation, struct mm_struct *mm, unsigned long start, unsigned long end, @@ -373,7 +377,9 @@ static inline struct drm_pagemap_zdd *drm_pagemap_page_zone_device_data(struct p { struct folio *folio = page_folio(page); - return folio_zone_device_data(folio); + return (struct drm_pagemap_zdd *) + ((unsigned long)folio_zone_device_data(folio) & + ~DRM_PAGEMAP_ZDD_FLAG_MASK); } #else From 8eae39cd0adf28ba81a46090b10484cf402c0ac8 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Tue, 28 Jul 2026 14:33:04 +0530 Subject: [PATCH 0336/2366] drm/pagemap: Reset migration page count on eviction retry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_pagemap_evict_to_ram() may retry eviction, but mpages retains the count from the previous attempt. A retry can therefore continue to the copy path even when no RAM pages were populated. Reset mpages at the retry label so it reflects only the current attempt. Fixes: 99624bdff867 ("drm/gpusvm: Add support for GPU Shared Virtual Memory") Cc: Matthew Brost Cc: Thomas Hellström Cc: Himal Prasad Ghimiray Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: Thomas Zimmermann Cc: David Airlie Cc: Simona Vetter Signed-off-by: Arvind Yadav Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260728090304.1264759-1-arvind.yadav@intel.com --- drivers/gpu/drm/drm_pagemap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index c5906f153ada..097a900cf55d 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -1220,7 +1220,7 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) { const struct drm_pagemap_devmem_ops *ops = devmem_allocation->ops; struct drm_pagemap_iova_state state = {}; - unsigned long npages, mpages = 0; + unsigned long npages, mpages; struct page **pages; unsigned long *src, *dst; struct drm_pagemap_addr *pagemap_addr; @@ -1231,6 +1231,7 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) npages = devmem_allocation->size >> PAGE_SHIFT; retry: + mpages = 0; if (!mmget_not_zero(devmem_allocation->mm)) return -EFAULT; From ca12149896ed040dafef92eacd2af3f903afb177 Mon Sep 17 00:00:00 2001 From: Rudi Heitbaum Date: Mon, 24 Aug 2026 02:27:29 +0000 Subject: [PATCH 0337/2366] regulator: dt-bindings: fan53555: add tcs,tcs4526 The driver has accepted tcs,tcs4526 since commit 5eee5eced95f ("regulator: fan53555: add tcs4526"), which added the compatible to both the of_device_id and i2c_device_id tables for the TCS4526, a chip that reports id 0 rather than the TCS4525's id 12. The binding was converted to DT schema afterwards, in commit 6cea468b680e ("regulator: dt-bindings: Convert Fairchild FAN53555 to DT schema"), and only picked up tcs,tcs4525. No in-tree DTS used the 4526 string at the time, so nothing flagged the omission. RK3399Pro boards use the TCS4526 for vdd_gpu and vdd_cpu_b, so a DTS describing them fails dtbs_check today even though the driver binds correctly. Add the missing compatible. Signed-off-by: Rudi Heitbaum Acked-by: Conor Dooley Link: https://patch.msgid.link/aousEdwMni9ScZBn@0d3a7a881997 Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/regulator/fcs,fan53555.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/regulator/fcs,fan53555.yaml b/Documentation/devicetree/bindings/regulator/fcs,fan53555.yaml index 9a18891f721e..b35b8f365b0f 100644 --- a/Documentation/devicetree/bindings/regulator/fcs,fan53555.yaml +++ b/Documentation/devicetree/bindings/regulator/fcs,fan53555.yaml @@ -23,6 +23,7 @@ properties: - silergy,syr827 - silergy,syr828 - tcs,tcs4525 + - tcs,tcs4526 - items: - const: rockchip,rk8601 - const: rockchip,rk8600 From fa5acd038ea657ad5033713d6916214cbd349151 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 28 Aug 2026 14:07:47 -0500 Subject: [PATCH 0338/2366] net/iucv: fix the recvmsg window update iucv_sock_recvmsg() sends the HiperSockets-only AF_IUCV_FLAG_WIN without testing the transport, so on a classic z/VM socket iucv_send_ctrl() sizes the skb through a NULL iucv->hs_dev. SO_MSGLIMIT accepts 1, so msglimit / 2 is zero and one recvmsg() on its own socket is enough for an unprivileged process to take a spurious disconnect. It also calls iucv_send_ctrl() under spin_lock_bh(&message_q.lock), which allocates GFP_KERNEL inside a section the code treats as atomic. Sending outside that lock lets two recvmsg() reach afiucv_hs_send() at once, where msg_recv is sampled for the advertised window and subtracted after dev_queue_xmit() -- and sendmsg reaches that counter under lock_sock() while recvmsg holds no socket lock, so both can subtract the same value, the counter goes negative and the credit reaches the peer twice. Test the transport, claim the credit with atomic_xchg() after the last error exit and hand it back if the transmit fails, and send once the lock is dropped. Fixes: 3881ac441f64 ("af_iucv: add HiperSockets transport") Fixes: 238965b71b96 ("net/af_iucv: build proper skbs for HiperTransport") Cc: stable@vger.kernel.org Tested-by: Aswin Karuvally Signed-off-by: Bryam Vargas Reviewed-by: Alexandra Winter Link: https://patch.msgid.link/20260828-b4-disp-33fac0ed-v3-1-e6d061880ee0@proton.me Signed-off-by: Jakub Kicinski --- net/iucv/af_iucv.c | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c index 4e5cc9da6e06..db261ecd19af 100644 --- a/net/iucv/af_iucv.c +++ b/net/iucv/af_iucv.c @@ -210,12 +210,6 @@ static int afiucv_hs_send(struct iucv_message *imsg, struct sock *sock, phs_hdr->flags = flags; if (flags == AF_IUCV_FLAG_SYN) phs_hdr->window = iucv->msglimit; - else if ((flags == AF_IUCV_FLAG_WIN) || !flags) { - confirm_recv = atomic_read(&iucv->msg_recv); - phs_hdr->window = confirm_recv; - if (confirm_recv) - phs_hdr->flags = phs_hdr->flags | AF_IUCV_FLAG_WIN; - } memcpy(phs_hdr->destUserID, iucv->dst_user_id, 8); memcpy(phs_hdr->destAppName, iucv->dst_name, 8); memcpy(phs_hdr->srcUserID, iucv->src_user_id, 8); @@ -250,13 +244,22 @@ static int afiucv_hs_send(struct iucv_message *imsg, struct sock *sock, } skb->protocol = cpu_to_be16(ETH_P_AF_IUCV); + /* Claim the receive credit here, not while building the header: every + * way this frame can be dropped has now been ruled out, so the window + * is zeroed only for as long as the transmit itself takes. + */ + if (flags == AF_IUCV_FLAG_WIN || !flags) { + confirm_recv = atomic_xchg(&iucv->msg_recv, 0); + phs_hdr->window = confirm_recv; + if (confirm_recv) + phs_hdr->flags = phs_hdr->flags | AF_IUCV_FLAG_WIN; + } + atomic_inc(&iucv->skbs_in_xmit); err = dev_queue_xmit(skb); if (net_xmit_eval(err)) { atomic_dec(&iucv->skbs_in_xmit); - } else { - atomic_sub(confirm_recv, &iucv->msg_recv); - WARN_ON(atomic_read(&iucv->msg_recv) < 0); + atomic_add(confirm_recv, &iucv->msg_recv); } return net_xmit_eval(err); @@ -1241,6 +1244,7 @@ static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg, struct iucv_sock *iucv = iucv_sk(sk); unsigned int copied, rlen; struct sk_buff *skb, *rskb, *cskb; + bool send_win = false; int err = 0; u32 offset; @@ -1331,16 +1335,20 @@ static int iucv_sock_recvmsg(struct socket *sock, struct msghdr *msg, if (skb_queue_empty(&iucv->backlog_skb_q)) { if (!list_empty(&iucv->message_q.list)) iucv_process_message_q(sk); - if (atomic_read(&iucv->msg_recv) >= - iucv->msglimit / 2) { - err = iucv_send_ctrl(sk, AF_IUCV_FLAG_WIN); - if (err) { - sk->sk_state = IUCV_DISCONN; - sk->sk_state_change(sk); - } - } + if (iucv->transport == AF_IUCV_TRANS_HIPER && + atomic_read(&iucv->msg_recv) >= + iucv->msglimit / 2) + send_win = true; } spin_unlock_bh(&iucv->message_q.lock); + + if (send_win) { + err = iucv_send_ctrl(sk, AF_IUCV_FLAG_WIN); + if (err) { + sk->sk_state = IUCV_DISCONN; + sk->sk_state_change(sk); + } + } } done: From 5443d9c4f55d46634b95432e1e8a40b824019bbb Mon Sep 17 00:00:00 2001 From: Selvamani Rajagopal Date: Mon, 24 Aug 2026 14:57:58 -0700 Subject: [PATCH 0339/2366] net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances Threaded IRQ uses waiting_tx_skb. Transmit path also uses this pointer without any mutual exclusion protection. As a result, it might leak skb buffer, particularly if threaded IRQ sets disable_traffic true after start_xmit already checked and found that disable_traffic being false, if they happen to run on different cores. On fatal error, where disable_traffic is set, transmit function drops the packet and return NETDEV_TX_OK. Due to this change, skb_linearize call is moved up to the beginning of the transmit function. Since skb buffer may be freed from different contexts, dev_kfree_skb_any is used to free skb buffer now, replacing one of the kfree_skb call. oa_tc6_exit disables the irq before setting disable_traffic true. Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.") Signed-off-by: Selvamani Rajagopal Link: https://patch.msgid.link/20260824-fix-race-condition-and-crash-v7-1-4323279b18f2@onsemi.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/oa_tc6.c | 111 +++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 34 deletions(-) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 417c15d1ff42..2f45001be0f5 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -693,6 +693,26 @@ static int oa_tc6_enable_data_transfer(struct oa_tc6 *tc6) return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, value); } +/* Called when a frame that is meant to be transmitted, is dropped. */ +static void oa_tc6_drop_tx_skb(struct oa_tc6 *tc6, struct sk_buff *skb) +{ + if (skb) { + tc6->netdev->stats.tx_dropped++; + dev_kfree_skb_any(skb); + } +} + +static struct sk_buff *oa_tc6_detach_waiting_tx_skb(struct oa_tc6 *tc6) +{ + struct sk_buff *skb; + + lockdep_assert_held(&tc6->tx_skb_lock); + skb = tc6->waiting_tx_skb; + tc6->waiting_tx_skb = NULL; + + return skb; +} + static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6) { if (tc6->rx_skb) { @@ -704,26 +724,30 @@ static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6) static void oa_tc6_cleanup_ongoing_tx_skb(struct oa_tc6 *tc6) { - if (tc6->ongoing_tx_skb) { - tc6->netdev->stats.tx_dropped++; - kfree_skb(tc6->ongoing_tx_skb); - tc6->ongoing_tx_skb = NULL; - } + oa_tc6_drop_tx_skb(tc6, tc6->ongoing_tx_skb); + tc6->ongoing_tx_skb = NULL; } static void oa_tc6_cleanup_waiting_tx_skb(struct oa_tc6 *tc6) { - if (tc6->waiting_tx_skb) { - tc6->netdev->stats.tx_dropped++; - kfree_skb(tc6->waiting_tx_skb); - tc6->waiting_tx_skb = NULL; - } + struct sk_buff *skb; + + spin_lock_bh(&tc6->tx_skb_lock); + skb = oa_tc6_detach_waiting_tx_skb(tc6); + spin_unlock_bh(&tc6->tx_skb_lock); + + oa_tc6_drop_tx_skb(tc6, skb); +} + +static void oa_tc6_free_ongoing_skbs(struct oa_tc6 *tc6) +{ + oa_tc6_cleanup_ongoing_tx_skb(tc6); + oa_tc6_cleanup_ongoing_rx_skb(tc6); } static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6) { - oa_tc6_cleanup_ongoing_tx_skb(tc6); - oa_tc6_cleanup_ongoing_rx_skb(tc6); + oa_tc6_free_ongoing_skbs(tc6); oa_tc6_cleanup_waiting_tx_skb(tc6); } @@ -734,9 +758,15 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6) static void oa_tc6_disable_traffic(struct oa_tc6 *tc6) { u32 regval = OA_TC6_INT_MASK0_ALL_INTERRUPTS; + struct sk_buff *skb; + spin_lock_bh(&tc6->tx_skb_lock); tc6->disable_traffic = true; - oa_tc6_free_pending_skbs(tc6); + skb = oa_tc6_detach_waiting_tx_skb(tc6); + spin_unlock_bh(&tc6->tx_skb_lock); + + oa_tc6_drop_tx_skb(tc6, skb); + oa_tc6_free_ongoing_skbs(tc6); oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval); oa_tc6_read_register(tc6, OA_TC6_REG_STATUS0, ®val); oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, regval); @@ -1177,8 +1207,7 @@ static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6) if (ret == -EAGAIN) continue; - oa_tc6_cleanup_ongoing_tx_skb(tc6); - oa_tc6_cleanup_ongoing_rx_skb(tc6); + oa_tc6_free_ongoing_skbs(tc6); netdev_err(tc6->netdev, "Device error: %d\n", ret); return ret; } @@ -1200,15 +1229,20 @@ static irqreturn_t oa_tc6_macphy_threaded_irq(int irq, void *data) * no need to attempt spi transfer, once it fails. Pending skbs * are already freed. */ - if (!tc6->disable_traffic) { - while (tc6->int_flag || - (tc6->waiting_tx_skb && tc6->tx_credits)) { - ret = oa_tc6_try_spi_transfer(tc6); - if (ret) { - disable_irq_nosync(tc6->spi->irq); - oa_tc6_disable_traffic(tc6); - break; - } + spin_lock_bh(&tc6->tx_skb_lock); + if (tc6->disable_traffic) { + spin_unlock_bh(&tc6->tx_skb_lock); + return IRQ_HANDLED; + } + spin_unlock_bh(&tc6->tx_skb_lock); + + while (tc6->int_flag || + (tc6->waiting_tx_skb && tc6->tx_credits)) { + ret = oa_tc6_try_spi_transfer(tc6); + if (ret) { + disable_irq_nosync(tc6->spi->irq); + oa_tc6_disable_traffic(tc6); + break; } } @@ -1287,23 +1321,30 @@ EXPORT_SYMBOL_GPL(oa_tc6_zero_align_receive_frame_enable); * @tc6: oa_tc6 struct. * @skb: socket buffer in which the ethernet frame is stored. * - * Return: NETDEV_TX_OK if the transmit ethernet frame skb added in the tx_skb_q - * otherwise returns NETDEV_TX_BUSY. + * Return: NETDEV_TX_OK either on successful queueing of the packet for + * transmission, or on packet getting dropped. Packet can be dropped due to + * failure in linearizing the buffer or disable_traffic is set due to + * earlier fatal error. Returns NETDEV_TX_BUSY when there is no room + * to queue the packet. */ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb) { - if (tc6->disable_traffic || tc6->waiting_tx_skb) { - netif_stop_queue(tc6->netdev); - return NETDEV_TX_BUSY; - } - if (skb_linearize(skb)) { - dev_kfree_skb_any(skb); - tc6->netdev->stats.tx_dropped++; + oa_tc6_drop_tx_skb(tc6, skb); return NETDEV_TX_OK; } spin_lock_bh(&tc6->tx_skb_lock); + if (tc6->waiting_tx_skb) { + netif_stop_queue(tc6->netdev); + spin_unlock_bh(&tc6->tx_skb_lock); + return NETDEV_TX_BUSY; + } + if (tc6->disable_traffic) { + spin_unlock_bh(&tc6->tx_skb_lock); + oa_tc6_drop_tx_skb(tc6, skb); + return NETDEV_TX_OK; + } tc6->waiting_tx_skb = skb; spin_unlock_bh(&tc6->tx_skb_lock); @@ -1462,8 +1503,10 @@ EXPORT_SYMBOL_GPL(oa_tc6_init); */ void oa_tc6_exit(struct oa_tc6 *tc6) { - tc6->disable_traffic = true; disable_irq(tc6->spi->irq); + spin_lock_bh(&tc6->tx_skb_lock); + tc6->disable_traffic = true; + spin_unlock_bh(&tc6->tx_skb_lock); oa_tc6_phy_exit(tc6); oa_tc6_free_pending_skbs(tc6); } From 172c974113bffe5723b80b1acac17593bb50513c Mon Sep 17 00:00:00 2001 From: Selvamani Rajagopal Date: Mon, 24 Aug 2026 14:57:59 -0700 Subject: [PATCH 0340/2366] net: ethernet: oa_tc6: Improve the error recovery When oversubscribed traffic causes lot of buffer overflow errors, probably due to loss of data chunks, driver fails to find a data chunk with end_valid bit set, before it runs out of sk buffer space. As a result, assert is seen during skb_put. Now, check is made if skb buffer has enough tailroom for the incoming data before accepting. If there is no room, current frame is abandoned and it will start looking for a data chunk with start_valid bit, that is a new frame. SK buffer allocation error is considered as recoverable error. rx_buf_overflow flag is too specific and no longer the only condition this flag is used for. Therefore it is renamed as wait_until_start_valid. This is more appropriate as this flag is used to look for the next data chunk with SV bit set, after failures like buffer overflow, buffer allocation failure, skb pointer validity besides buffer overflow error. Not writing to status0 if it reads 0. Fixes: d70a0d8f2f2d ("net: ethernet: oa_tc6: implement receive path to receive rx ethernet frames") Signed-off-by: Selvamani Rajagopal Link: https://patch.msgid.link/20260824-fix-race-condition-and-crash-v7-2-4323279b18f2@onsemi.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/oa_tc6.c | 143 +++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 35 deletions(-) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 2f45001be0f5..657b1c6119da 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -83,7 +83,7 @@ struct oa_tc6 { u16 spi_data_tx_buf_offset; u16 tx_credits; u8 rx_chunks_available; - bool rx_buf_overflow; + bool wait_until_start_valid; bool int_flag; bool disable_traffic; bool prot_ctrl; @@ -751,6 +751,12 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6) oa_tc6_cleanup_waiting_tx_skb(tc6); } +static void oa_tc6_look_for_new_frame(struct oa_tc6 *tc6) +{ + tc6->wait_until_start_valid = true; + oa_tc6_cleanup_ongoing_rx_skb(tc6); +} + /* If the failure is at SPI interface level, masking and clearing * the interrupt of the device won't work. Since SPI interrupt is * disabled, it should stop the repeated interrupts. @@ -785,6 +791,13 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6) return ret; } + /* This function is called for each chunk received in a given SPI + * transaction. In case, extended status bit is set in more than + * one chunk, skip the write, if status0 is already cleared. + */ + if (!value) + return 0; + /* Clear the error interrupts status */ ret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, value); if (ret) { @@ -794,8 +807,7 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6) } if (FIELD_GET(OA_TC6_STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) { - tc6->rx_buf_overflow = true; - oa_tc6_cleanup_ongoing_rx_skb(tc6); + oa_tc6_look_for_new_frame(tc6); net_err_ratelimited("%s: Receive buffer overflow error\n", tc6->netdev->name); return -EAGAIN; @@ -821,6 +833,8 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6) static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer) { + int ret = 0; + /* Process rx chunk footer for the following, * 1. tx credits * 2. errors if any from MAC-PHY @@ -831,9 +845,11 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer) footer); if (FIELD_GET(OA_TC6_DATA_FOOTER_EXTENDED_STS, footer)) { - int ret = oa_tc6_process_extended_status(tc6); - - if (ret) + ret = oa_tc6_process_extended_status(tc6); + /* EAGAIN error is recoverable. Move on to check + * HEADER and SYNC errors before returning. + */ + if (ret && ret != -EAGAIN) return ret; } @@ -851,7 +867,7 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer) return -ENODEV; } - return 0; + return ret; } static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6) @@ -876,13 +892,35 @@ static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6) tc6->rx_skb = NULL; } -static void oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length) +/* On oversubscribed traffic condition, particularly with overwhelming rx + * buffer overflow errors, there could be data chunk loss. If tail + length + * goes beyond end pointer, that is an indication that the data chunk with + * end_valid bit is lost. Time to look for a data chunk with start_valid bit. + * + * If rx_skb is NULL, it is time to start looking for data chunk with + * start_bit. + */ +static int oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length) { + if (!tc6->rx_skb || + skb_tailroom(tc6->rx_skb) < length) { + oa_tc6_look_for_new_frame(tc6); + return -EAGAIN; + } + memcpy(skb_put(tc6->rx_skb, length), payload, length); + return 0; } +/* On overwhelming rx buffer overflow errors, due to data chunk loss, it is + * possible that we get two data chunks with start_valid bit set, without + * end_valid bit set in between. In this case, rx_skb would have a valid + * buffer pointer. We should release, if a valid pointer is found before + * allocating a new one. + */ static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6) { + oa_tc6_cleanup_ongoing_rx_skb(tc6); tc6->rx_skb = netdev_alloc_skb_ip_align(tc6->netdev, tc6->netdev->mtu + ETH_HLEN + ETH_FCS_LEN); if (!tc6->rx_skb) { @@ -902,7 +940,9 @@ static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload, if (ret) return ret; - oa_tc6_update_rx_skb(tc6, payload, size); + ret = oa_tc6_update_rx_skb(tc6, payload, size); + if (ret) + return ret; oa_tc6_submit_rx_skb(tc6); @@ -917,22 +957,24 @@ static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size) if (ret) return ret; - oa_tc6_update_rx_skb(tc6, payload, size); - - return 0; + return oa_tc6_update_rx_skb(tc6, payload, size); } -static void oa_tc6_prcs_rx_frame_end(struct oa_tc6 *tc6, u8 *payload, u16 size) +static int oa_tc6_prcs_rx_frame_end(struct oa_tc6 *tc6, u8 *payload, u16 size) { - oa_tc6_update_rx_skb(tc6, payload, size); + int ret; - oa_tc6_submit_rx_skb(tc6); + ret = oa_tc6_update_rx_skb(tc6, payload, size); + if (!ret) + oa_tc6_submit_rx_skb(tc6); + return ret; } -static void oa_tc6_prcs_ongoing_rx_frame(struct oa_tc6 *tc6, u8 *payload, - u32 footer) +static int oa_tc6_prcs_ongoing_rx_frame(struct oa_tc6 *tc6, u8 *payload, + u32 footer) { - oa_tc6_update_rx_skb(tc6, payload, OA_TC6_CHUNK_PAYLOAD_SIZE); + return oa_tc6_update_rx_skb(tc6, payload, + OA_TC6_CHUNK_PAYLOAD_SIZE); } static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, @@ -947,10 +989,10 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, u16 size; /* Restart the new rx frame after receiving rx buffer overflow error */ - if (start_valid && tc6->rx_buf_overflow) - tc6->rx_buf_overflow = false; + if (start_valid && tc6->wait_until_start_valid) + tc6->wait_until_start_valid = false; - if (tc6->rx_buf_overflow) + if (tc6->wait_until_start_valid) return 0; /* Process the chunk with complete rx frame */ @@ -972,8 +1014,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, /* Process the chunk with only rx frame end */ if (end_valid && !start_valid) { size = end_byte_offset + 1; - oa_tc6_prcs_rx_frame_end(tc6, data, size); - return 0; + return oa_tc6_prcs_rx_frame_end(tc6, data, size); } /* Process the chunk with previous rx frame end and next rx frame @@ -987,6 +1028,15 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, if (tc6->rx_skb) { size = end_byte_offset + 1; oa_tc6_prcs_rx_frame_end(tc6, data, size); + + /* Return value from oa_tc6_prcs_rx_frame_end is not + * checked. If it returned an error, it is to make + * the code to look for new frame. At this stage, + * code below is going to process a new frame. So, + * error condition is set to false, in case it is + * set before proceeding. + */ + tc6->wait_until_start_valid = false; } size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset; return oa_tc6_prcs_rx_frame_start(tc6, @@ -995,9 +1045,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, } /* Process the chunk with ongoing rx frame data */ - oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer); - - return 0; + return oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer); } static u32 oa_tc6_get_rx_chunk_footer(struct oa_tc6 *tc6, u16 footer_offset) @@ -1013,8 +1061,9 @@ static u32 oa_tc6_get_rx_chunk_footer(struct oa_tc6 *tc6, u16 footer_offset) static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length) { u16 no_of_rx_chunks = length / OA_TC6_CHUNK_SIZE; + bool retry = false; + int ret = 0; u32 footer; - int ret; /* All the rx chunks in the receive SPI data buffer are examined here */ for (int i = 0; i < no_of_rx_chunks; i++) { @@ -1023,8 +1072,11 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length) OA_TC6_CHUNK_PAYLOAD_SIZE); ret = oa_tc6_process_rx_chunk_footer(tc6, footer); - if (ret) - return ret; + if (ret) { + if (ret != -EAGAIN) + return ret; + retry = true; + } /* If there is a data valid chunks then process it for the * information needed to determine the validity and the location @@ -1036,12 +1088,35 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length) ret = oa_tc6_prcs_rx_chunk_payload(tc6, payload, footer); - if (ret) - return ret; + if (ret) { + if (ret != -ENOMEM && ret != -EAGAIN) + return ret; + retry = true; + } } } - return 0; + /* Not bailing out on recoverable error codes, -EAGAIN and + * -ENOMEM. If subsequent loop iterations, if any, succeeds, + * error code would be overwritten. retry flag helps to + * make the caller to continue and retry. Since recovery + * action for -ENOMEM and -EAGAIN are same, we are returning + * one of the error codes, that is -EAGAIN. + * + * Successful recovery depends on how small the frames are, + * how many chunks, among the received chunks triggered the + * error, whether data is intact even with error conditions. + * As a result, there is no single, best method to recover + * most data when error conditions hit. We do our best by + * processing all the chunks with good "footer header" and + * "data valid" bit set. + */ + if (retry) { + ret = -EAGAIN; + oa_tc6_look_for_new_frame(tc6); + } + + return ret; } static __be32 oa_tc6_prepare_data_header(bool data_valid, bool start_valid, @@ -1203,10 +1278,8 @@ static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6) } ret = oa_tc6_process_spi_data_rx_buf(tc6, spi_len); - if (ret) { - if (ret == -EAGAIN) - continue; + if (ret && ret != -EAGAIN) { oa_tc6_free_ongoing_skbs(tc6); netdev_err(tc6->netdev, "Device error: %d\n", ret); return ret; From 349c366365876b7f67120827a0deb44899f59303 Mon Sep 17 00:00:00 2001 From: Selvamani Rajagopal Date: Mon, 24 Aug 2026 14:58:00 -0700 Subject: [PATCH 0341/2366] net: ethernet: oa_tc6: Disable tx queues on fatal error Previously, TX queue interface was stopped when disable_traffic flag was set, which would indicate fatal error. It is more appropriate to disable the queue as, unless driver is unloaded and reloaded, there is no recovery after disable_traffic is set. Queues may be re-enabled inadvertently by other layers. Intention of disable_traffic is only to stop the traffic from flowing on fatal error. Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.") Signed-off-by: Selvamani Rajagopal Link: https://patch.msgid.link/20260824-fix-race-condition-and-crash-v7-3-4323279b18f2@onsemi.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/oa_tc6.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 657b1c6119da..eea00b41fb8d 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -771,6 +771,10 @@ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6) skb = oa_tc6_detach_waiting_tx_skb(tc6); spin_unlock_bh(&tc6->tx_skb_lock); + /* disable_traffic, when set, is a point of no return to + * working state. Keeping the TX queues disabled. + */ + netif_tx_disable(tc6->netdev); oa_tc6_drop_tx_skb(tc6, skb); oa_tc6_free_ongoing_skbs(tc6); oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval); From 3cc2aa96b97184abd6fc106aac626ddf14389813 Mon Sep 17 00:00:00 2001 From: Selvamani Rajagopal Date: Mon, 24 Aug 2026 14:58:01 -0700 Subject: [PATCH 0342/2366] net: ethernet: oa_tc6: Fix for the wrong data type Inadvertently bool data type is used where int is supposed to be used. This might turn a negative error code into true or false and sign of the return code would be lost. Fixes: 8f9bf857e43b ("net: ethernet: oa_tc6: implement internal PHY initialization") Signed-off-by: Selvamani Rajagopal Link: https://patch.msgid.link/20260824-fix-race-condition-and-crash-v7-4-4323279b18f2@onsemi.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/oa_tc6.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index eea00b41fb8d..6fcc5f561d56 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -455,7 +455,7 @@ static int oa_tc6_mdiobus_read(struct mii_bus *bus, int addr, int regnum) { struct oa_tc6 *tc6 = bus->priv; u32 regval; - bool ret; + int ret; ret = oa_tc6_read_register(tc6, OA_TC6_PHY_STD_REG_ADDR_BASE | (regnum & OA_TC6_PHY_STD_REG_ADDR_MASK), From d7e7e98d23f42a92d9ab7e36302bd96bd9b33b5f Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 25 Aug 2026 04:10:51 -0400 Subject: [PATCH 0343/2366] net/sched: cls_u32: fix duplicate handle when node ID pool is exhausted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gen_new_kid() falls back to returning max (htid | 0xFFF) when both idr_alloc_u32() ranges are full, instead of reporting an error. u32_change() trusts that value and inserts a new knode with a handle that is already live in the hash table, breaking handle uniqueness within the table's node ID space. The handle was never reserved in ht->handle_idr, so every later error path that does idr_remove(&ht->handle_idr, handle) removes the reservation of a different, live knode, which is then reused — one failed add compounds into further duplicates. The 4095 limit is per (table, bucket) — ht->handle_idr is per hash table and the range is derived from htid (bucketid), so a table with divisor 256 can legitimately hold 256*4095 knodes. The sibling helper gen_new_htid() has the same silent in-band failure: it returns 0 when the tp_c handle pool (1..0x7FF) is full, and u32_init() publishes the root hash table with handle 0 without checking. Two root tables with handle 0 alias in u32_lookup_ht(), allowing cross-tcf_proto knode add/lookup/delete. Add the same exhaustion check that the divisor path already has. Return an error so u32_change() fails with ENOSPC/ENOMEM when the node ID space is exhausted, and so u32_init() fails with -ENOMEM when the hash table ID space is exhausted. The extack message distinguishes pool exhaustion (-ENOSPC) from a transient allocation failure (-ENOMEM). Conditions to recreate the bug: - CONFIG_NET_SCHED=y, CONFIG_CLS_U32=y (or =m with module loaded) - Create a clsact qdisc on a device, then add 4095 u32 filters with auto-generated handles to fill the node ID space for the root hash table (single bucket). The 4096th auto-handle filter add triggers the duplicate handle (fh 800::fff reused). Reachable at Level 2 (unshare -Urn, namespace-local CAP_NET_ADMIN). - For gen_new_htid: create 2047 u32 proto entries on the same block to fill the tp_c handle pool, then create one more. The root table gets handle 0 and aliases with other handle-0 root tables. Fixes: 7801db8aec95 ("net_sched: avoid generating same handle for u32 filters") Reported-by: vega@nebusec.ai Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260825081052.133898-1-jhs@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/cls_u32.c | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c index ac6d0fa5a40e..a3e65c8cf29e 100644 --- a/net/sched/cls_u32.c +++ b/net/sched/cls_u32.c @@ -370,6 +370,10 @@ static int u32_init(struct tcf_proto *tp) refcount_set(&root_ht->refcnt, 1); root_ht->handle = tp_c ? gen_new_htid(tp_c, root_ht) : id2handle(0); + if (root_ht->handle == 0) { + kfree(root_ht); + return -ENOMEM; + } root_ht->prio = tp->prio; root_ht->is_root = true; idr_init(&root_ht->handle_idr); @@ -695,21 +699,33 @@ static int u32_delete(struct tcf_proto *tp, void *arg, bool *last, return ret; } -static u32 gen_new_kid(struct tc_u_hnode *ht, u32 htid) +static u32 gen_new_kid(struct tc_u_hnode *ht, u32 htid, int *err) { u32 index = htid | 0x800; u32 max = htid | 0xFFF; + *err = 0; + if (idr_alloc_u32(&ht->handle_idr, NULL, &index, max, GFP_KERNEL)) { index = htid + 1; - if (idr_alloc_u32(&ht->handle_idr, NULL, &index, max, - GFP_KERNEL)) - index = max; + *err = idr_alloc_u32(&ht->handle_idr, NULL, &index, max, + GFP_KERNEL); + if (*err) + return 0; } return index; } +static int u32_kid_extack(int err, struct netlink_ext_ack *extack) +{ + if (err == -ENOSPC) + NL_SET_ERR_MSG_MOD(extack, "Hash table node ID pool exhausted"); + else + NL_SET_ERR_MSG_MOD(extack, "Failed to allocate node ID"); + return err; +} + static const struct nla_policy u32_policy[TCA_U32_MAX + 1] = { [TCA_U32_CLASSID] = { .type = NLA_U32 }, [TCA_U32_HASH] = { .type = NLA_U32 }, @@ -1079,7 +1095,9 @@ static int u32_change(struct net *net, struct sk_buff *in_skb, * handle which is used to uniquely identify the match entry. */ if (!TC_U32_NODE(handle)) { - handle = gen_new_kid(ht, htid); + handle = gen_new_kid(ht, htid, &err); + if (err) + return u32_kid_extack(err, extack); } else { handle = htid | TC_U32_NODE(handle); err = idr_alloc_u32(&ht->handle_idr, NULL, &handle, @@ -1091,7 +1109,9 @@ static int u32_change(struct net *net, struct sk_buff *in_skb, /* The user did not give us a handle; lets just generate one * from the table's pool of nodeids. */ - handle = gen_new_kid(ht, htid); + handle = gen_new_kid(ht, htid, &err); + if (err) + return u32_kid_extack(err, extack); } if (tb[TCA_U32_SEL] == NULL) { From 7b120a771943ffc3cbce787daecdd23eccb0505f Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 25 Aug 2026 04:10:52 -0400 Subject: [PATCH 0344/2366] selftests: tc-testing: add u32 node ID pool exhaustion test Add a tdc test case that fills the u32 node ID space with 4095 auto-generated handles, then attempts to add a 4096th. On the fixed kernel the 4096th filter is rejected with ENOSPC (exit 2). On the unfixed kernel it silently succeeds with a duplicate handle. The setup pipes the 4095 add commands directly into `tc -b -` inside a single bash -c (matching the existing test id 1234 pattern), avoiding any temp file. Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260825081052.133898-2-jhs@mojatatu.com Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/filters/u32.json | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json b/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json index b2ca9d4e991b..e2b03f2b5e89 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json +++ b/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json @@ -353,5 +353,28 @@ "teardown": [ "$TC qdisc del dev $DEV1 parent root drr" ] + }, + { + "id": "70fd", + "name": "Add u32 filter when node ID pool is exhausted (4096th filter rejected)", + "category": [ + "filter", + "u32" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY clsact", + "bash -c 'for i in {1..4095}; do echo filter add dev $DUMMY ingress prio 1 protocol ip u32 match u8 0 0 at 0; done | $TC -b -'" + ], + "cmdUnderTest": "$TC filter add dev $DUMMY ingress prio 1 protocol ip u32 match u8 0 0 at 0", + "expExitCode": "2", + "verifyCmd": "$TC -d filter show dev $DUMMY ingress", + "matchPattern": "fh 800::", + "matchCount": "4095", + "teardown": [ + "$TC qdisc del dev $DUMMY clsact" + ] } ] From 5f8cb07d7a8573753c5d31dc76b51cab8e5e9460 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 28 Aug 2026 10:31:29 +0800 Subject: [PATCH 0345/2366] arm64: dts: socfpga: change access permission from 755 to 644 These files have an incorrect access permission of 755 instead of 644. Change them to the correct access permission of 644. Fixes: 4bc04eb90b7c ("arm64: dts: socfpga: stratix10: Add emmc support") Cc: # v7.1+ Signed-off-by: Dinh Nguyen --- arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dtsi | 0 arch/arm64/boot/dts/altera/socfpga_stratix10_socdk_emmc.dts | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dtsi mode change 100755 => 100644 arch/arm64/boot/dts/altera/socfpga_stratix10_socdk_emmc.dts diff --git a/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dtsi b/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dtsi old mode 100755 new mode 100644 diff --git a/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk_emmc.dts b/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk_emmc.dts old mode 100755 new mode 100644 From dc0df5a0c62ccea1d0e08d39a4dc9064de81d411 Mon Sep 17 00:00:00 2001 From: Florian Schauer Date: Fri, 28 Aug 2026 08:08:22 +0200 Subject: [PATCH 0346/2366] page_pool: keep frag_offset aligned for odd-sized requests page_pool_alloc_frag_netmem() rounds the requested fragment size with size = ALIGN(size, dma_get_cache_alignment()); dma_get_cache_alignment() returns 1 unless the architecture defines ARCH_DMA_MINALIGN, which DMA-coherent architectures such as x86 do not. There the ALIGN() is a no-op and pool->frag_offset advances by the raw, unrounded size. A single caller asking for an odd size then leaves frag_offset misaligned for every fragment carved out of that page afterwards. The pool is shared, so the damage is not confined to the caller that caused it. The per-cpu system_page_pool used by generic XDP hits this. skb_pp_cow_data() allocates its fragments with the raw packet length: size = min_t(u32, len, PAGE_SIZE); truesize = size; page = page_pool_dev_alloc(pool, &page_off, &truesize); leaving frag_offset odd for whatever is carved out of that page next. Its own head allocation is already aligned -- SKB_HEAD_ALIGN(size) plus the XDP_PACKET_HEADROOM its callers pass -- so it is a later user of the shared pool that pays: page_pool_dev_alloc_va() returns a misaligned buffer, napi_build_skb() installs it as skb->head, and skb_shinfo(skb) == skb->head + skb->end is misaligned with it. skb_shinfo()->dataref is a 4-byte atomic_t at offset 0x20, so the atomic_inc() in __skb_clone() straddles a cache line. On x86 with split lock detection -- fatal for kernel split locks by default -- this panics the machine: Oops: Split lock detected RIP: 0010:skb_clone+0x154/0x1e0 Call Trace: raw_local_deliver+0x1ed/0x2c0 ip_protocol_deliver_rcu+0x54/0x1c0 ip_local_deliver_finish+0x85/0x100 ip_local_deliver+0x67/0x100 __netif_receive_skb_one_core+0x85/0xa0 process_backlog+0x87/0x130 Reproduced by attaching any generic-mode XDP program to loopback and opening a RAW IPPROTO_UDP socket, which makes raw_local_deliver() clone every locally delivered UDP packet; ordinary DNS traffic then triggers it, roughly once per 2500 clones. Observed on 6.12.101 and 7.1.8. Tracing page_pool_alloc_frag_netmem() over one such run shows the amplification -- two odd-sized requests, nine misaligned offsets: requested size & 7: 0: 17035 5: 1 7: 1 frag_offset & 7: 0: 17028 3: 1 4: 1 5: 1 6: 1 7: 5 and skb_pp_cow_data() returning heads that were aligned on entry: head 0xffff8f4c86aeac00 -> 0xffff8f4c53a9a9c4 (&7=4) head 0xffff8f4d6a8a42c0 -> 0xffff8f4c4f7b7a45 (&7=5) Round the fragment size up to at least the alignment struct skb_shared_info requires, so fragments are always suitably aligned for the objects callers build on them. Architectures needing a larger DMA alignment keep it. This also makes the remainder computed in page_pool_alloc_netmem(), *size = max_size - *offset; aligned, since max_size is a power of two -- which fixes the matching misalignment of skb->end. Verified with a controlled A/B under QEMU/KVM: same tree, same config, same compiler, same rootfs and identical traffic, differing only by this patch. A SEC("xdp.frags") XDP_PASS program on lo plus UDP datagrams larger than max_head_size drives skb_pp_cow_data()'s fragment loop, which passes raw packet lengths to the pool. Measured at the return of skb_pp_cow_data(): unpatched patched skb_pp_cow_data calls 40800 40800 misaligned skb->head 1120 0 dataref at line offset >60 80 0 The last row counts the accesses that actually fault: skb_shinfo()->dataref sits at head+end+0x20 and is a 4-byte atomic, so `lock incl` splits a 64-byte cache line only when that address lands at offset 61..63. All 80 occurrences were at offset 61; the panic reported above was at offset 62. Eliminating the misalignment removes every one of them. Same class of bug as commit 3bed3cc4156e ("net: Do not allocate page fragments that are not skb aligned"), which fixed the older netdev_alloc_frag()/napi_alloc_frag() allocators. Fixes: 53e0961da1c7 ("page_pool: add frag page recycling support in page pool") Cc: stable@vger.kernel.org Signed-off-by: Florian Schauer Acked-by: Jesper Dangaard Brouer Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260828060822.2628276-1-florian@schauer.to Signed-off-by: Jakub Kicinski --- net/core/page_pool.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/core/page_pool.c b/net/core/page_pool.c index 8f8956fb061b..08d7f35cf608 100644 --- a/net/core/page_pool.c +++ b/net/core/page_pool.c @@ -1073,7 +1073,8 @@ netmem_ref page_pool_alloc_frag_netmem(struct page_pool *pool, if (WARN_ON(size > max_size)) return 0; - size = ALIGN(size, dma_get_cache_alignment()); + size = ALIGN(size, max_t(unsigned int, dma_get_cache_alignment(), + __alignof__(struct skb_shared_info))); *offset = pool->frag_offset; if (netmem && *offset + size > max_size) { From b6b9e6d4abe87b16ab55990b887c6fad8e7a01af Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 28 Aug 2026 16:50:33 +0100 Subject: [PATCH 0347/2366] rust: pin-init: use irrefutable pattern for `stack_pin_init` In Rust 1.100.0, `Infallible` will become an alias of `!`. The let binding in `stack_pin_init` will thus become unreachable and produce an "unreachable expression" warning for the subsequent match, and thus will fail a `-Dwarnings` build. For this macro, all we need to know is that the error type is uninhabited, so replace this with an irrefutable pattern instead. [ The error looks like (dummy reproducer): error: unreachable expression --> rust/kernel/sync.rs:177:5 | 177 | pin_init::stack_pin_init!(let num = 42u32); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | unreachable expression | any code following this expression is unreachable | = note: `-D unreachable-code` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(unreachable_code)]` = note: this error originates in the macro `pin_init::stack_pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) - Miguel ] Reported-by: Mohamad Alsadhan Closes: https://github.com/Rust-for-Linux/pin-init/pull/171 Signed-off-by: Gary Guo Cc: stable@vger.kernel.org # Needed in 7.1.y and later (for 6.12.y and 6.18.y a custom one is needed). Link: https://patch.msgid.link/20260828155033.2101924-1-gary@kernel.org [ Reworded for typos. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/pin-init/src/lib.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index 7600cdbbbf98..f1463be9479d 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -490,13 +490,7 @@ macro_rules! stack_pin_init { (let $var:ident $(: $t:ty)? = $val:expr) => { let val = $val; let mut $var = ::core::pin::pin!($crate::__internal::StackInit$(::<$t>)?::uninit()); - let mut $var = match $crate::__internal::StackInit::init($var, val) { - Ok(res) => res, - Err(x) => { - let x: ::core::convert::Infallible = x; - match x {} - } - }; + let Ok(mut $var) = $crate::__internal::StackInit::init($var, val); }; } From dee44f41f206becb41c492899c1996cfd7f82a1b Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Wed, 26 Aug 2026 09:39:27 +0900 Subject: [PATCH 0348/2366] vsock/virtio: validate packet source for connected sockets virtio_transport_recv_pkt() looks up sockets first by the full source and destination tuple, then by destination only in the bound table. The fallback is needed for listening and connecting sockets, but sockets remain in the bound table after connect(), so it can also return a non-listening socket. The fallback does not validate the source address. In TCP_SYN_SENT, a RESPONSE from an unrelated source can transition the victim socket to TCP_ESTABLISHED while its stored remote address remains unchanged. Subsequent RW packets from that source are delivered through the same destination-only fallback. This was reproduced with capability-empty processes under different UIDs. The attacker discovered the target tuple through unprivileged AF_VSOCK sock_diag and caused the victim socket to read 16 attacker-chosen bytes; the intended peer-side socket read 0 of those 16 bytes. Add vsock_check_source() to validate the transport, source port and source CID against the peer stored in a non-listening socket. The local transport is the CID exception because its packets are generated internally with VMADDR_CID_LOCAL as their source, including connections using CID aliases. Use the helper after lock_sock() in the virtio receive path. Fixes: 06a8fc78367d ("VSOCK: Introduce virtio_vsock_common.ko") Closes: https://lore.kernel.org/netdev/20260813121236.2328599-1-4ncienth@gmail.com/ Cc: stable@vger.kernel.org Suggested-by: Stefano Garzarella Reviewed-by: Bobby Eshleman Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/20260826003929.966160-2-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- include/net/af_vsock.h | 3 +++ net/vmw_vsock/af_vsock.c | 32 +++++++++++++++++++++++++ net/vmw_vsock/virtio_transport_common.c | 3 ++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h index 3357ee62d10b..5549298c1ec6 100644 --- a/include/net/af_vsock.h +++ b/include/net/af_vsock.h @@ -229,6 +229,9 @@ struct sock *vsock_find_bound_socket_net(struct sockaddr_vm *addr, struct sock *vsock_find_connected_socket_net(struct sockaddr_vm *src, struct sockaddr_vm *dst, struct net *net); +bool vsock_check_source(const struct vsock_sock *vsk, + const struct vsock_transport *transport, + const struct sockaddr_vm *src); void vsock_remove_sock(struct vsock_sock *vsk); void vsock_for_each_connected_socket(struct vsock_transport *transport, void (*fn)(struct sock *sk)); diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index a33b2a2d381d..f840498b58af 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -438,6 +438,38 @@ struct sock *vsock_find_connected_socket(struct sockaddr_vm *src, } EXPORT_SYMBOL_GPL(vsock_find_connected_socket); +/** + * vsock_check_source - validate a packet source against a socket peer + * @vsk: socket receiving the packet + * @transport: transport receiving the packet + * @src: source address from the packet + * + * Return: true if the packet arrived on the socket's assigned transport and + * its source matches the stored peer. Loopback packets are generated + * internally and always use the local CID as their source, including + * connections using a valid CID alias. + * + * The caller must hold the socket lock and must not call this for listening + * sockets, which accept packets from any source and have no assigned + * transport. + */ +bool vsock_check_source(const struct vsock_sock *vsk, + const struct vsock_transport *transport, + const struct sockaddr_vm *src) +{ + if (vsk->transport != transport) + return false; + + if (src->svm_port != vsk->remote_addr.svm_port) + return false; + + if (src->svm_cid == vsk->remote_addr.svm_cid) + return true; + + return transport->get_local_cid() == VMADDR_CID_LOCAL; +} +EXPORT_SYMBOL_GPL(vsock_check_source); + void vsock_remove_sock(struct vsock_sock *vsk) { /* Transport reassignment must not remove the binding. */ diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index 88df82364f77..f225f53ed4ba 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -1836,7 +1836,8 @@ void virtio_transport_recv_pkt(struct virtio_transport *t, * lock_sock (note: listener sockets are not assigned to any transport) */ if (sock_flag(sk, SOCK_DONE) || - (sk->sk_state != TCP_LISTEN && vsk->transport != &t->transport)) { + (sk->sk_state != TCP_LISTEN && + !vsock_check_source(vsk, &t->transport, &src))) { (void)virtio_transport_reset_no_sock(t, skb, net); release_sock(sk); sock_put(sk); From ad9a7da3fa39c2d616ec0dd3cf6e30531d032fe7 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Wed, 26 Aug 2026 09:39:28 +0900 Subject: [PATCH 0349/2366] vsock/vmci: validate packet source for connected sockets vmci_transport_recv_stream_cb() looks up sockets first by the full source and destination tuple, then by destination only in the bound table. The fallback can select a non-listening socket without checking whether the packet came from its stored peer. This was reproduced with two VMCI contexts. A RST from the context not stored in a TCP_SYN_SENT socket reset that socket after it was selected by the destination-only lookup. VMCI can process notification packets in bottom-half context when the socket is not owned by user context, or defer packets to a workqueue. Use vsock_check_source() after taking the socket lock in the bottom-half path, and recheck after lock_sock() in the workqueue path. Listening sockets continue to accept packets from any source. Reply with a RST addressed from the received packet before dropping a source that fails validation. This preserves the existing reset behavior for bound non-listening and concurrently closed sockets without directing the reset to a connected socket's stored peer. Fixes: d021c344051a ("VSOCK: Introduce VM Sockets") Reported-by: Sashiko Closes: https://lore.kernel.org/netdev/20260814121255.6B5001F000E9@smtp.kernel.org/ Cc: stable@vger.kernel.org Suggested-by: Stefano Garzarella Suggested-by: Paolo Abeni Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Vishnu Dasa Link: https://patch.msgid.link/20260826003929.966160-3-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/vmw_vsock/vmci_transport.c | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/net/vmw_vsock/vmci_transport.c b/net/vmw_vsock/vmci_transport.c index 1c4ee039c166..1f186e8f8364 100644 --- a/net/vmw_vsock/vmci_transport.c +++ b/net/vmw_vsock/vmci_transport.c @@ -680,11 +680,13 @@ static int vmci_transport_recv_stream_cb(void *data, struct vmci_datagram *dg) struct vmci_transport_packet *pkt; struct vsock_sock *vsk; bool bh_process_pkt; + bool drop_pkt; int err; sk = NULL; err = VMCI_SUCCESS; bh_process_pkt = false; + drop_pkt = false; /* Ignore incoming packets from resources that aren't vsock * implementations. @@ -765,17 +767,29 @@ static int vmci_transport_recv_stream_cb(void *data, struct vmci_datagram *dg) bh_lock_sock(sk); if (!sock_owned_by_user(sk)) { - /* The local context ID may be out of date, update it. */ - vsk->local_addr.svm_cid = dst.svm_cid; + if (sk->sk_state != TCP_LISTEN && + !vsock_check_source(vsk, &vmci_transport, &src)) { + drop_pkt = true; + err = VMCI_ERROR_NO_ACCESS; + } else { + /* The local context ID may be out of date, update it. */ + vsk->local_addr.svm_cid = dst.svm_cid; - if (sk->sk_state == TCP_ESTABLISHED) - vmci_trans(vsk)->notify_ops->handle_notify_pkt( - sk, pkt, true, &dst, &src, - &bh_process_pkt); + if (sk->sk_state == TCP_ESTABLISHED) + vmci_trans(vsk)->notify_ops->handle_notify_pkt(sk, pkt, true, + &dst, &src, + &bh_process_pkt); + } } bh_unlock_sock(sk); + if (drop_pkt) { + if (vmci_transport_send_reset_bh(&dst, &src, pkt) < 0) + pr_err("unable to send reset\n"); + goto out; + } + if (!bh_process_pkt) { struct vmci_transport_recv_pkt_info *recv_pkt_info; @@ -900,6 +914,7 @@ static void vmci_transport_recv_pkt_work(struct work_struct *work) { struct vmci_transport_recv_pkt_info *recv_pkt_info; struct vmci_transport_packet *pkt; + struct sockaddr_vm src; struct sock *sk; recv_pkt_info = @@ -908,6 +923,12 @@ static void vmci_transport_recv_pkt_work(struct work_struct *work) pkt = &recv_pkt_info->pkt; lock_sock(sk); + vsock_addr_init(&src, pkt->dg.src.context, pkt->src_port); + if (sk->sk_state != TCP_LISTEN && + !vsock_check_source(vsock_sk(sk), &vmci_transport, &src)) { + vmci_transport_reply_reset(pkt); + goto out; + } /* The local context ID may be out of date. */ vsock_sk(sk)->local_addr.svm_cid = pkt->dg.dst.context; @@ -937,6 +958,7 @@ static void vmci_transport_recv_pkt_work(struct work_struct *work) break; } +out: release_sock(sk); kfree(recv_pkt_info); /* Release reference obtained in the stream callback when we fetched From fee10655709c5c597e8e9f722f3035d9ea31ff3a Mon Sep 17 00:00:00 2001 From: Aohan Mei Date: Wed, 26 Aug 2026 10:51:20 +0800 Subject: [PATCH 0350/2366] net/sched: cls_flower: validate mask pointer after nla_next() fl_set_enc_opt() iterates the key's nested tunnel-option attributes with nla_for_each_attr() while advancing a single mask pointer via nla_next() at the bottom of each loop, so the mask cursor is driven by the number of key attributes rather than by the mask's own attributes. The nla_ok() added by commit c96adff956191 ("cls_flower: call nla_ok() before nla_next()") only validates the mask pointer that was just consumed; the pointer produced by nla_next() is used by the next iteration (fl_set_geneve_opt() and siblings) without any validation. The mask's nested attributes are validated with NL_VALIDATE_LIBERAL, which merely warns on trailing bytes that do not form a complete attribute. A mask carrying one valid attribute plus 1-3 residue bytes (or a non-aligned attribute length making msk_depth negative) therefore reaches the next iteration with msk_depth != 0, so neither the !msk_depth check in fl_set_enc_opt() nor the !depth check in the per-type helpers fires. nla_type() then reads past the mask payload and nla_parse_nested_deprecated() iterates with an nla_len taken from those bytes, reading well beyond the mask attribute (KASAN: slab-out-of-bounds read in __nla_validate_parse from fl_change()). Validate the advanced mask pointer as well: when the mask is not legitimately exhausted (msk_depth != 0) and the new pointer fails nla_ok(), reject the filter with -EINVAL. An exactly exhausted mask still skips the check, preserving exact-match behaviour for the remaining key attributes. Fixes: c96adff95619 ("cls_flower: call nla_ok() before nla_next()") Reported-by: TencentOS Corvus AI Cc: stable@vger.kernel.org Signed-off-by: Aohan Mei Link: https://patch.msgid.link/20260826025123.62758-1-ljp1205831794@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/cls_flower.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/sched/cls_flower.c b/net/sched/cls_flower.c index 0e275b58151c..1cefea571efd 100644 --- a/net/sched/cls_flower.c +++ b/net/sched/cls_flower.c @@ -1703,6 +1703,11 @@ static int fl_set_enc_opt(struct nlattr **tb, struct fl_flow_key *key, return -EINVAL; } nla_opt_msk = nla_next(nla_opt_msk, &msk_depth); + + if (msk_depth && !nla_ok(nla_opt_msk, msk_depth)) { + NL_SET_ERR_MSG(extack, "A mask attribute is invalid"); + return -EINVAL; + } } return 0; From e510334fbaeaa016ac76d80b4c5f47611c5f7860 Mon Sep 17 00:00:00 2001 From: Mehmet Koseoglu Date: Fri, 28 Aug 2026 04:50:20 +0300 Subject: [PATCH 0351/2366] rust: samples: add missing newlines in rust_print_main Calls to `pr_info!` in `arc_print` are missing trailing newlines, which are expected as the `pr_*!` documentation shows. Add the missing `\n` to all four formatting strings. Fixes: f431c5c581fa ("samples: rust: print: Add sample code for Arc printing") Fixes: 47cb6bf7860c ("rust: use derive(CoercePointee) on rustc >= 1.84.0") Suggested-by: Miguel Ojeda Link: https://github.com/Rust-for-Linux/linux/issues/1139 Signed-off-by: Mehmet Koseoglu Link: https://patch.msgid.link/20260828015148.221737-2-mehmet.mkoseoglu@gmail.com [ Reworded to fix the description of the missing-newline behavior. - Miguel ] Signed-off-by: Miguel Ojeda --- samples/rust/rust_print_main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/rust/rust_print_main.rs b/samples/rust/rust_print_main.rs index 682207c81fc2..01729e87d6b5 100644 --- a/samples/rust/rust_print_main.rs +++ b/samples/rust/rust_print_main.rs @@ -23,10 +23,10 @@ fn arc_print() -> Result { let b = UniqueArc::new("hello, world", GFP_KERNEL)?; // Prints the value of data in `a`. - pr_info!("{}", a); + pr_info!("{}\n", a); // Uses ":?" to print debug fmt of `b`. - pr_info!("{:?}", b); + pr_info!("{:?}\n", b); let a: Arc<&str> = b.into(); let c = a.clone(); @@ -42,7 +42,7 @@ fn arc_print() -> Result { use kernel::fmt::Display; fn arc_dyn_print(arc: &Arc) { - pr_info!("Arc says {arc}"); + pr_info!("Arc says {arc}\n"); } let a_i32_display: Arc = Arc::new(42i32, GFP_KERNEL)?; @@ -53,7 +53,7 @@ fn arc_dyn_print(arc: &Arc) { } // Pretty-prints the debug formatting with lower-case hexadecimal integers. - pr_info!("{:#x?}", a); + pr_info!("{:#x?}\n", a); Ok(()) } From 2987ee196c88dbde0463dc87d5fb209c684e34a2 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 27 Aug 2026 16:06:56 +0000 Subject: [PATCH 0352/2366] igmp: convert struct ip_sf_list to RCU Commit 23d2b94043ca ("igmp: Add ip_mc_list lock in ip_check_mc_rcu") added spin_lock_bh(&im->lock) to ip_check_mc_rcu() to prevent a use-after-free while iterating im->sources during concurrent deletions. However, ip_check_mc_rcu() is called from RCU read-side critical sections in packet receive and route lookup fast paths (e.g. __mkroute_output(), ip_route_input_rcu(), and __udp4_lib_rcv()). When igmpv3_send_cr() or igmpv3_send_report() holds &pmc->lock and calls add_grec() -> igmpv3_newpack() -> ip_route_output_ports(), an XFRM policy matching a multicast destination triggers xfrm_tmpl_resolve_one() -> xfrm4_get_saddr() -> __mkroute_output() -> ip_check_mc_rcu(). This attempts to acquire &im->lock while &pmc->lock is already held on the same CPU, triggering a lockdep recursive locking warning / deadlock. Fix this by converting IPv4 struct ip_sf_list to RCU, mirroring the IPv6 implementation in net/ipv6/mcast.c: 1. Add struct rcu_head to struct ip_sf_list and annotate sf_next, sources, and tomb as __rcu pointers. 2. Use rcu_assign_pointer() and kfree_rcu() for list updates and deletions. 3. Remove spin_lock_bh(&im->lock) from ip_check_mc_rcu() and traverse im->sources locklessly with for_each_psf_rcu(), reading and writing counter fields with READ_ONCE() and WRITE_ONCE(). Note: RCU conversion of /proc/net/mcfilter will be done in a separate patch. Fixes: 23d2b94043ca ("igmp: Add ip_mc_list lock in ip_check_mc_rcu") Reported-by: syzbot+3d99fb01bcd740f2fc1e@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3d99fb01bcd740f2fc1e Signed-off-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260827160656.903003-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/linux/igmp.h | 7 +- net/ipv4/igmp.c | 210 +++++++++++++++++++++++++++---------------- 2 files changed, 135 insertions(+), 82 deletions(-) diff --git a/include/linux/igmp.h b/include/linux/igmp.h index 3a2d35a9f307..a0cf0398519f 100644 --- a/include/linux/igmp.h +++ b/include/linux/igmp.h @@ -57,20 +57,21 @@ struct ip_mc_socklist { }; struct ip_sf_list { - struct ip_sf_list *sf_next; + struct ip_sf_list __rcu *sf_next; unsigned long sf_count[2]; /* include/exclude counts */ __be32 sf_inaddr; unsigned char sf_gsresp; /* include in g & s response? */ unsigned char sf_oldin; /* change state */ unsigned char sf_crcount; /* retrans. left to send */ + struct rcu_head rcu; }; struct ip_mc_list { struct in_device *interface; __be32 multiaddr; unsigned int sfmode; - struct ip_sf_list *sources; - struct ip_sf_list *tomb; + struct ip_sf_list __rcu *sources; + struct ip_sf_list __rcu *tomb; unsigned long sfcount[2]; union { struct ip_mc_list *next; diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index b80b8a92f46e..d56355aca797 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -188,6 +188,10 @@ static void ip_ma_put(struct ip_mc_list *im) } } +#define pmc_dereference(e, pmc) \ + rcu_dereference_protected(e, lockdep_is_held(&(pmc)->lock) || \ + lockdep_is_held(&(pmc)->interface->mc_tomb_lock)) + #define for_each_pmc_rcu(in_dev, pmc) \ for (pmc = rcu_dereference(in_dev->mc_list); \ pmc != NULL; \ @@ -198,13 +202,28 @@ static void ip_ma_put(struct ip_mc_list *im) pmc != NULL; \ pmc = rtnl_dereference(pmc->next_rcu)) +#define for_each_psf_mclock(pmc, psf) \ + for (psf = pmc_dereference((pmc)->sources, pmc); \ + psf; \ + psf = pmc_dereference(psf->sf_next, pmc)) + +#define for_each_psf_rcu(im, psf) \ + for (psf = rcu_dereference((im)->sources); \ + psf; \ + psf = rcu_dereference(psf->sf_next)) + +#define for_each_psf_tomb(pmc, psf) \ + for (psf = pmc_dereference((pmc)->tomb, pmc); \ + psf; \ + psf = pmc_dereference(psf->sf_next, pmc)) + static void ip_sf_list_clear_all(struct ip_sf_list *psf) { struct ip_sf_list *next; while (psf) { - next = psf->sf_next; - kfree(psf); + next = rcu_dereference_protected(psf->sf_next, 1); + kfree_rcu(psf, rcu); psf = next; } } @@ -349,7 +368,7 @@ igmp_scount(struct ip_mc_list *pmc, int type, int gdeleted, int sdeleted) struct ip_sf_list *psf; int scount = 0; - for (psf = pmc->sources; psf; psf = psf->sf_next) { + for_each_psf_mclock(pmc, psf) { if (!is_in(pmc, psf, type, gdeleted, sdeleted)) continue; scount++; @@ -494,7 +513,8 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, struct net *net = dev_net(dev); struct igmpv3_report *pih; struct igmpv3_grec *pgr = NULL; - struct ip_sf_list *psf, *psf_next, *psf_prev, **psf_list; + struct ip_sf_list *psf, *psf_next, *psf_prev; + struct ip_sf_list __rcu **psf_list; int scount, stotal, first, isquery, truncate; unsigned int mtu; @@ -517,7 +537,7 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, psf_list = sdeleted ? &pmc->tomb : &pmc->sources; - if (!*psf_list) + if (!rcu_access_pointer(*psf_list)) goto empty_source; pih = skb ? igmpv3_report_hdr(skb) : NULL; @@ -533,10 +553,12 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, } first = 1; psf_prev = NULL; - for (psf = *psf_list; psf; psf = psf_next) { + for (psf = pmc_dereference(*psf_list, pmc); + psf; + psf = psf_next) { __be32 *psrc; - psf_next = psf->sf_next; + psf_next = pmc_dereference(psf->sf_next, pmc); if (!is_in(pmc, psf, type, gdeleted, sdeleted)) { psf_prev = psf; @@ -583,10 +605,12 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, psf->sf_crcount--; if ((sdeleted || gdeleted) && psf->sf_crcount == 0) { if (psf_prev) - psf_prev->sf_next = psf->sf_next; + rcu_assign_pointer(psf_prev->sf_next, + psf_next); else - *psf_list = psf->sf_next; - kfree(psf); + rcu_assign_pointer(*psf_list, + psf_next); + kfree_rcu(psf, rcu); continue; } } @@ -655,28 +679,29 @@ static int igmpv3_send_report(struct in_device *in_dev, struct ip_mc_list *pmc) /* * remove zero-count source records from a source filter list */ -static void igmpv3_clear_zeros(struct ip_sf_list **ppsf) +static void igmpv3_clear_zeros(struct ip_sf_list __rcu **ppsf) { struct ip_sf_list *psf_prev, *psf_next, *psf; psf_prev = NULL; - for (psf = *ppsf; psf; psf = psf_next) { - psf_next = psf->sf_next; + for (psf = rcu_dereference_protected(*ppsf, 1); psf; psf = psf_next) { + psf_next = rcu_dereference_protected(psf->sf_next, 1); if (psf->sf_crcount == 0) { if (psf_prev) - psf_prev->sf_next = psf->sf_next; + rcu_assign_pointer(psf_prev->sf_next, psf_next); else - *ppsf = psf->sf_next; - kfree(psf); - } else + rcu_assign_pointer(*ppsf, psf_next); + kfree_rcu(psf, rcu); + } else { psf_prev = psf; + } } } static void kfree_pmc(struct ip_mc_list *pmc) { - ip_sf_list_clear_all(pmc->sources); - ip_sf_list_clear_all(pmc->tomb); + ip_sf_list_clear_all(rcu_dereference_protected(pmc->sources, 1)); + ip_sf_list_clear_all(rcu_dereference_protected(pmc->tomb, 1)); kfree(pmc); } @@ -710,7 +735,8 @@ static void igmpv3_send_cr(struct in_device *in_dev) igmpv3_clear_zeros(&pmc->sources); } } - if (pmc->crcount == 0 && !pmc->tomb && !pmc->sources) { + if (pmc->crcount == 0 && !rcu_access_pointer(pmc->tomb) && + !rcu_access_pointer(pmc->sources)) { if (pmc_prev) pmc_prev->next = pmc_next; else @@ -896,7 +922,7 @@ static int igmp_xmarksources(struct ip_mc_list *pmc, int nsrcs, __be32 *srcs) int i, scount; scount = 0; - for (psf = pmc->sources; psf; psf = psf->sf_next) { + for_each_psf_mclock(pmc, psf) { if (scount == nsrcs) break; for (i = 0; i < nsrcs; i++) { @@ -927,7 +953,7 @@ static int igmp_marksources(struct ip_mc_list *pmc, int nsrcs, __be32 *srcs) /* mark INCLUDE-mode sources */ scount = 0; - for (psf = pmc->sources; psf; psf = psf->sf_next) { + for_each_psf_mclock(pmc, psf) { if (scount == nsrcs) break; for (i = 0; i < nsrcs; i++) @@ -1228,11 +1254,12 @@ static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im, if (pmc->sfmode == MCAST_INCLUDE) { struct ip_sf_list *psf; + for_each_psf_mclock(im, psf) + psf->sf_crcount = pmc->crcount; pmc->tomb = im->tomb; pmc->sources = im->sources; - im->tomb = im->sources = NULL; - for (psf = pmc->sources; psf; psf = psf->sf_next) - psf->sf_crcount = pmc->crcount; + RCU_INIT_POINTER(im->tomb, NULL); + RCU_INIT_POINTER(im->sources, NULL); } spin_unlock_bh(&im->lock); @@ -1271,9 +1298,18 @@ static void igmpv3_del_delrec(struct in_device *in_dev, struct ip_mc_list *im) if (pmc) { im->interface = pmc->interface; if (im->sfmode == MCAST_INCLUDE) { - swap(im->tomb, pmc->tomb); - swap(im->sources, pmc->sources); - for (psf = im->sources; psf; psf = psf->sf_next) + struct ip_sf_list *sources, *tomb; + + tomb = rcu_replace_pointer(im->tomb, + rcu_dereference_protected(pmc->tomb, 1), + lockdep_is_held(&im->lock)); + rcu_assign_pointer(pmc->tomb, tomb); + + sources = rcu_replace_pointer(im->sources, + rcu_dereference_protected(pmc->sources, 1), + lockdep_is_held(&im->lock)); + rcu_assign_pointer(pmc->sources, sources); + for_each_psf_mclock(im, psf) psf->sf_crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv); } else { @@ -1310,8 +1346,8 @@ static void igmpv3_clear_delrec(struct in_device *in_dev) struct ip_sf_list *psf; spin_lock_bh(&pmc->lock); - psf = pmc->tomb; - pmc->tomb = NULL; + psf = pmc_dereference(pmc->tomb, pmc); + RCU_INIT_POINTER(pmc->tomb, NULL); spin_unlock_bh(&pmc->lock); ip_sf_list_clear_all(psf); } @@ -1990,7 +2026,7 @@ static int ip_mc_del1_src(struct ip_mc_list *pmc, int sfmode, int rv = 0; psf_prev = NULL; - for (psf = pmc->sources; psf; psf = psf->sf_next) { + for_each_psf_mclock(pmc, psf) { if (psf->sf_inaddr == *psfsrc) break; psf_prev = psf; @@ -1999,7 +2035,7 @@ static int ip_mc_del1_src(struct ip_mc_list *pmc, int sfmode, /* source filter not found, or count wrong => bug */ return -ESRCH; } - psf->sf_count[sfmode]--; + WRITE_ONCE(psf->sf_count[sfmode], psf->sf_count[sfmode] - 1); if (psf->sf_count[sfmode] == 0) { ip_rt_multicast_event(pmc->interface); } @@ -2011,19 +2047,28 @@ static int ip_mc_del1_src(struct ip_mc_list *pmc, int sfmode, /* no more filters for this source */ if (psf_prev) - psf_prev->sf_next = psf->sf_next; + rcu_assign_pointer(psf_prev->sf_next, + pmc_dereference(psf->sf_next, pmc)); else - pmc->sources = psf->sf_next; + rcu_assign_pointer(pmc->sources, + pmc_dereference(psf->sf_next, pmc)); #ifdef CONFIG_IP_MULTICAST if (psf->sf_oldin && !IGMP_V1_SEEN(in_dev) && !IGMP_V2_SEEN(in_dev)) { - psf->sf_crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv); - psf->sf_next = pmc->tomb; - pmc->tomb = psf; - rv = 1; - } else + struct ip_sf_list *dpsf = kmalloc_obj(*dpsf, GFP_ATOMIC); + + if (dpsf) { + *dpsf = *psf; + dpsf->sf_crcount = in_dev->mr_qrv ?: + READ_ONCE(net->ipv4.sysctl_igmp_qrv); + rcu_assign_pointer(dpsf->sf_next, + pmc_dereference(pmc->tomb, pmc)); + rcu_assign_pointer(pmc->tomb, dpsf); + rv = 1; + } + } #endif - kfree(psf); + kfree_rcu(psf, rcu); } return rv; } @@ -2060,7 +2105,7 @@ static int ip_mc_del_src(struct in_device *in_dev, __be32 *pmca, int sfmode, err = -EINVAL; if (!pmc->sfcount[sfmode]) goto out_unlock; - pmc->sfcount[sfmode]--; + WRITE_ONCE(pmc->sfcount[sfmode], pmc->sfcount[sfmode] - 1); } err = 0; for (i = 0; i < sfcount; i++) { @@ -2083,7 +2128,7 @@ static int ip_mc_del_src(struct in_device *in_dev, __be32 *pmca, int sfmode, #ifdef CONFIG_IP_MULTICAST pmc->crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv); WRITE_ONCE(in_dev->mr_ifc_count, pmc->crcount); - for (psf = pmc->sources; psf; psf = psf->sf_next) + for_each_psf_mclock(pmc, psf) psf->sf_crcount = 0; igmp_ifc_event(pmc->interface); } else if (sf_setstate(pmc) || changerec) { @@ -2104,7 +2149,7 @@ static int ip_mc_add1_src(struct ip_mc_list *pmc, int sfmode, struct ip_sf_list *psf, *psf_prev; psf_prev = NULL; - for (psf = pmc->sources; psf; psf = psf->sf_next) { + for_each_psf_mclock(pmc, psf) { if (psf->sf_inaddr == *psfsrc) break; psf_prev = psf; @@ -2114,12 +2159,12 @@ static int ip_mc_add1_src(struct ip_mc_list *pmc, int sfmode, if (!psf) return -ENOBUFS; psf->sf_inaddr = *psfsrc; - if (psf_prev) { - psf_prev->sf_next = psf; - } else - pmc->sources = psf; + if (psf_prev) + rcu_assign_pointer(psf_prev->sf_next, psf); + else + rcu_assign_pointer(pmc->sources, psf); } - psf->sf_count[sfmode]++; + WRITE_ONCE(psf->sf_count[sfmode], psf->sf_count[sfmode] + 1); if (psf->sf_count[sfmode] == 1) { ip_rt_multicast_event(pmc->interface); } @@ -2132,13 +2177,15 @@ static void sf_markstate(struct ip_mc_list *pmc) struct ip_sf_list *psf; int mca_xcount = pmc->sfcount[MCAST_EXCLUDE]; - for (psf = pmc->sources; psf; psf = psf->sf_next) + for_each_psf_mclock(pmc, psf) { if (pmc->sfcount[MCAST_EXCLUDE]) { psf->sf_oldin = mca_xcount == psf->sf_count[MCAST_EXCLUDE] && !psf->sf_count[MCAST_INCLUDE]; - } else + } else { psf->sf_oldin = psf->sf_count[MCAST_INCLUDE] != 0; + } + } } static int sf_setstate(struct ip_mc_list *pmc) @@ -2149,27 +2196,31 @@ static int sf_setstate(struct ip_mc_list *pmc) int new_in, rv; rv = 0; - for (psf = pmc->sources; psf; psf = psf->sf_next) { + for_each_psf_mclock(pmc, psf) { if (pmc->sfcount[MCAST_EXCLUDE]) { new_in = mca_xcount == psf->sf_count[MCAST_EXCLUDE] && !psf->sf_count[MCAST_INCLUDE]; - } else + } else { new_in = psf->sf_count[MCAST_INCLUDE] != 0; + } if (new_in) { if (!psf->sf_oldin) { struct ip_sf_list *prev = NULL; - for (dpsf = pmc->tomb; dpsf; dpsf = dpsf->sf_next) { + for_each_psf_tomb(pmc, dpsf) { if (dpsf->sf_inaddr == psf->sf_inaddr) break; prev = dpsf; } if (dpsf) { + struct ip_sf_list *dpsf_next; + + dpsf_next = pmc_dereference(dpsf->sf_next, pmc); if (prev) - prev->sf_next = dpsf->sf_next; + rcu_assign_pointer(prev->sf_next, dpsf_next); else - pmc->tomb = dpsf->sf_next; - kfree(dpsf); + rcu_assign_pointer(pmc->tomb, dpsf_next); + kfree_rcu(dpsf, rcu); } psf->sf_crcount = qrv; rv++; @@ -2181,17 +2232,19 @@ static int sf_setstate(struct ip_mc_list *pmc) * add or update "delete" records if an active filter * is now inactive */ - for (dpsf = pmc->tomb; dpsf; dpsf = dpsf->sf_next) + for_each_psf_tomb(pmc, dpsf) { if (dpsf->sf_inaddr == psf->sf_inaddr) break; + } if (!dpsf) { dpsf = kmalloc_obj(*dpsf, GFP_ATOMIC); if (!dpsf) continue; *dpsf = *psf; /* pmc->lock held by callers */ - dpsf->sf_next = pmc->tomb; - pmc->tomb = dpsf; + rcu_assign_pointer(dpsf->sf_next, + pmc_dereference(pmc->tomb, pmc)); + rcu_assign_pointer(pmc->tomb, dpsf); } dpsf->sf_crcount = qrv; rv++; @@ -2231,7 +2284,7 @@ static int ip_mc_add_src(struct in_device *in_dev, __be32 *pmca, int sfmode, #endif isexclude = pmc->sfmode == MCAST_EXCLUDE; if (!delta) - pmc->sfcount[sfmode]++; + WRITE_ONCE(pmc->sfcount[sfmode], pmc->sfcount[sfmode] + 1); err = 0; for (i = 0; i < sfcount; i++) { err = ip_mc_add1_src(pmc, sfmode, &psfsrc[i]); @@ -2242,7 +2295,7 @@ static int ip_mc_add_src(struct in_device *in_dev, __be32 *pmca, int sfmode, int j; if (!delta) - pmc->sfcount[sfmode]--; + WRITE_ONCE(pmc->sfcount[sfmode], pmc->sfcount[sfmode] - 1); for (j = 0; j < i; j++) (void) ip_mc_del1_src(pmc, sfmode, &psfsrc[j]); } else if (isexclude != (pmc->sfcount[MCAST_EXCLUDE] != 0)) { @@ -2262,7 +2315,7 @@ static int ip_mc_add_src(struct in_device *in_dev, __be32 *pmca, int sfmode, pmc->crcount = in_dev->mr_qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv); WRITE_ONCE(in_dev->mr_ifc_count, pmc->crcount); - for (psf = pmc->sources; psf; psf = psf->sf_next) + for_each_psf_mclock(pmc, psf) psf->sf_crcount = 0; igmp_ifc_event(in_dev); } else if (sf_setstate(pmc)) { @@ -2278,13 +2331,13 @@ static void ip_mc_clear_src(struct ip_mc_list *pmc) struct ip_sf_list *tomb, *sources; spin_lock_bh(&pmc->lock); - tomb = pmc->tomb; - pmc->tomb = NULL; - sources = pmc->sources; - pmc->sources = NULL; + tomb = pmc_dereference(pmc->tomb, pmc); + RCU_INIT_POINTER(pmc->tomb, NULL); + sources = pmc_dereference(pmc->sources, pmc); + RCU_INIT_POINTER(pmc->sources, NULL); pmc->sfmode = MCAST_EXCLUDE; - pmc->sfcount[MCAST_INCLUDE] = 0; - pmc->sfcount[MCAST_EXCLUDE] = 1; + WRITE_ONCE(pmc->sfcount[MCAST_INCLUDE], 0); + WRITE_ONCE(pmc->sfcount[MCAST_EXCLUDE], 1); spin_unlock_bh(&pmc->lock); ip_sf_list_clear_all(tomb); @@ -2866,20 +2919,19 @@ int ip_check_mc_rcu(struct in_device *in_dev, __be32 mc_addr, __be32 src_addr, u rv = 1; } else if (im) { if (src_addr) { - spin_lock_bh(&im->lock); - for (psf = im->sources; psf; psf = psf->sf_next) { + for_each_psf_rcu(im, psf) { if (psf->sf_inaddr == src_addr) break; } if (psf) - rv = psf->sf_count[MCAST_INCLUDE] || - psf->sf_count[MCAST_EXCLUDE] != - im->sfcount[MCAST_EXCLUDE]; + rv = READ_ONCE(psf->sf_count[MCAST_INCLUDE]) || + READ_ONCE(psf->sf_count[MCAST_EXCLUDE]) != + READ_ONCE(im->sfcount[MCAST_EXCLUDE]); else - rv = im->sfcount[MCAST_EXCLUDE] != 0; - spin_unlock_bh(&im->lock); - } else + rv = READ_ONCE(im->sfcount[MCAST_EXCLUDE]) != 0; + } else { rv = 1; /* unspecified source; tentatively allow */ + } } return rv; } @@ -3043,7 +3095,7 @@ static inline struct ip_sf_list *igmp_mcf_get_first(struct seq_file *seq) im = rcu_dereference(idev->mc_list); if (likely(im)) { spin_lock_bh(&im->lock); - psf = im->sources; + psf = pmc_dereference(im->sources, im); if (likely(psf)) { state->im = im; state->idev = idev; @@ -3059,7 +3111,7 @@ static struct ip_sf_list *igmp_mcf_get_next(struct seq_file *seq, struct ip_sf_l { struct igmp_mcf_iter_state *state = igmp_mcf_seq_private(seq); - psf = psf->sf_next; + psf = pmc_dereference(psf->sf_next, state->im); while (!psf) { spin_unlock_bh(&state->im->lock); state->im = state->im->next; @@ -3075,7 +3127,7 @@ static struct ip_sf_list *igmp_mcf_get_next(struct seq_file *seq, struct ip_sf_l state->im = rcu_dereference(state->idev->mc_list); } spin_lock_bh(&state->im->lock); - psf = state->im->sources; + psf = pmc_dereference(state->im->sources, state->im); } out: return psf; From 9feb069e5ed03582fbf6272539f1caa2a17dc6d5 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Fri, 28 Aug 2026 15:32:36 +0800 Subject: [PATCH 0353/2366] ppp: ppp_async: simplify tty disc_data access tty_ldisc_hangup() invokes the hangup callback while holding only a read lock on tty->ldisc_sem, so it can run concurrently with other line discipline callbacks. This currently forces async PPP to maintain separate lifetime protection around tty->disc_data. Line discipline close is called under the write lock during hangup processing. Remove the hangup callback and rely on close for teardown, as done for SLIP by commit 23c53269f2ba ("slip: remove slip_hangup() to fix use-after-free in slip_receive_buf()"). This serializes teardown with all other line discipline operations. disc_data_lock, refcount and completion are redundant with that serialization. Remove them and access tty->disc_data directly. This also eliminates a lockdep warning reported by syzbot. The warning does not indicate a real deadlock because the write side runs only in process context with hardirqs disabled. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: syzbot+8e808eb853386f575d86@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/0000000000002fbad30611e25849@google.com/ Signed-off-by: Qingfang Deng Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260828073245.126804-1-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_async.c | 82 ++++--------------------------------- 1 file changed, 7 insertions(+), 75 deletions(-) diff --git a/drivers/net/ppp/ppp_async.c b/drivers/net/ppp/ppp_async.c index 583426d06381..ea7fe9608ffd 100644 --- a/drivers/net/ppp/ppp_async.c +++ b/drivers/net/ppp/ppp_async.c @@ -63,8 +63,6 @@ struct asyncppp { struct tasklet_struct tsk; - refcount_t refcnt; - struct completion dead; struct ppp_channel chan; /* interface to generic ppp layer */ unsigned char obuf[OBUFSIZE]; }; @@ -114,38 +112,6 @@ static const struct ppp_channel_ops async_ops = { * Routines implementing the PPP line discipline. */ -/* - * We have a potential race on dereferencing tty->disc_data, - * because the tty layer provides no locking at all - thus one - * cpu could be running ppp_asynctty_receive while another - * calls ppp_asynctty_close, which zeroes tty->disc_data and - * frees the memory that ppp_asynctty_receive is using. The best - * way to fix this is to use a rwlock in the tty struct, but for now - * we use a single global rwlock for all ttys in ppp line discipline. - * - * FIXME: this is no longer true. The _close path for the ldisc is - * now guaranteed to be sane. - */ -static DEFINE_RWLOCK(disc_data_lock); - -static struct asyncppp *ap_get(struct tty_struct *tty) -{ - struct asyncppp *ap; - - read_lock(&disc_data_lock); - ap = tty->disc_data; - if (ap != NULL) - refcount_inc(&ap->refcnt); - read_unlock(&disc_data_lock); - return ap; -} - -static void ap_put(struct asyncppp *ap) -{ - if (refcount_dec_and_test(&ap->refcnt)) - complete(&ap->dead); -} - /* * Called when a tty is put into PPP line discipline. Called in process * context. @@ -180,9 +146,6 @@ ppp_asynctty_open(struct tty_struct *tty) skb_queue_head_init(&ap->rqueue); tasklet_setup(&ap->tsk, ppp_async_process); - refcount_set(&ap->refcnt, 1); - init_completion(&ap->dead); - ap->chan.private = ap; ap->chan.ops = &async_ops; ap->chan.mtu = PPP_MRU; @@ -203,34 +166,18 @@ ppp_asynctty_open(struct tty_struct *tty) } /* - * Called when the tty is put into another line discipline - * or it hangs up. We have to wait for any cpu currently - * executing in any of the other ppp_asynctty_* routines to - * finish before we can call ppp_unregister_channel and free - * the asyncppp struct. This routine must be called from - * process context, not interrupt or softirq context. + * Called when the tty is put into another line discipline or it hangs up. + * This call is serialized against other ldisc functions. */ static void ppp_asynctty_close(struct tty_struct *tty) { - struct asyncppp *ap; + struct asyncppp *ap = tty->disc_data; - write_lock_irq(&disc_data_lock); - ap = tty->disc_data; - tty->disc_data = NULL; - write_unlock_irq(&disc_data_lock); if (!ap) return; - /* - * We have now ensured that nobody can start using ap from now - * on, but we have to wait for all existing users to finish. - * Note that ppp_unregister_channel ensures that no calls to - * our channel ops (i.e. ppp_async_send/ioctl) are in progress - * by the time it returns. - */ - if (!refcount_dec_and_test(&ap->refcnt)) - wait_for_completion(&ap->dead); + tty->disc_data = NULL; tasklet_kill(&ap->tsk); ppp_unregister_channel(&ap->chan); @@ -240,17 +187,6 @@ ppp_asynctty_close(struct tty_struct *tty) kfree(ap); } -/* - * Called on tty hangup in process context. - * - * Wait for I/O to driver to complete and unregister PPP channel. - * This is already done by the close routine, so just call that. - */ -static void ppp_asynctty_hangup(struct tty_struct *tty) -{ - ppp_asynctty_close(tty); -} - /* * Read does nothing - no data is ever available this way. * Pppd reads and writes packets via /dev/ppp instead. @@ -281,7 +217,7 @@ ppp_asynctty_write(struct tty_struct *tty, struct file *file, const u8 *buf, static int ppp_asynctty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) { - struct asyncppp *ap = ap_get(tty); + struct asyncppp *ap = tty->disc_data; int err, val; int __user *p = (int __user *)arg; @@ -322,7 +258,6 @@ ppp_asynctty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) err = tty_mode_ioctl(tty, cmd, arg); } - ap_put(ap); return err; } @@ -331,7 +266,7 @@ static void ppp_asynctty_receive(struct tty_struct *tty, const u8 *buf, const u8 *cflags, size_t count) { - struct asyncppp *ap = ap_get(tty); + struct asyncppp *ap = tty->disc_data; unsigned long flags; if (!ap) @@ -341,21 +276,19 @@ ppp_asynctty_receive(struct tty_struct *tty, const u8 *buf, const u8 *cflags, spin_unlock_irqrestore(&ap->recv_lock, flags); if (!skb_queue_empty(&ap->rqueue)) tasklet_schedule(&ap->tsk); - ap_put(ap); tty_unthrottle(tty); } static void ppp_asynctty_wakeup(struct tty_struct *tty) { - struct asyncppp *ap = ap_get(tty); + struct asyncppp *ap = tty->disc_data; clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); if (!ap) return; set_bit(XMIT_WAKEUP, &ap->xmit_flags); tasklet_schedule(&ap->tsk); - ap_put(ap); } @@ -365,7 +298,6 @@ static struct tty_ldisc_ops ppp_ldisc = { .name = "ppp", .open = ppp_asynctty_open, .close = ppp_asynctty_close, - .hangup = ppp_asynctty_hangup, .read = ppp_asynctty_read, .write = ppp_asynctty_write, .ioctl = ppp_asynctty_ioctl, From d8d4d1cf40d541a5d7cc3b15d57e42d0815c7d53 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Fri, 28 Aug 2026 15:32:37 +0800 Subject: [PATCH 0354/2366] ppp: ppp_synctty: simplify tty disc_data access Apply the same simplification as the preceding ppp_async change. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: syzbot+b503105c2410c3433459@syzkaller.appspotmail.com Closes: https://syzbot.org/bug?extid=b503105c2410c3433459 Signed-off-by: Qingfang Deng Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260828073245.126804-2-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_synctty.c | 83 +++-------------------------------- 1 file changed, 7 insertions(+), 76 deletions(-) diff --git a/drivers/net/ppp/ppp_synctty.c b/drivers/net/ppp/ppp_synctty.c index 0b1bd1635c39..f87d43faeeab 100644 --- a/drivers/net/ppp/ppp_synctty.c +++ b/drivers/net/ppp/ppp_synctty.c @@ -38,11 +38,9 @@ #include #include #include -#include #include #include #include -#include #include #include @@ -67,8 +65,6 @@ struct syncppp { struct tasklet_struct tsk; - refcount_t refcnt; - struct completion dead_cmp; struct ppp_channel chan; /* interface to generic ppp layer */ }; @@ -116,37 +112,6 @@ ppp_print_buffer (const char *name, const __u8 *buf, int count) * Routines implementing the synchronous PPP line discipline. */ -/* - * We have a potential race on dereferencing tty->disc_data, - * because the tty layer provides no locking at all - thus one - * cpu could be running ppp_synctty_receive while another - * calls ppp_synctty_close, which zeroes tty->disc_data and - * frees the memory that ppp_synctty_receive is using. The best - * way to fix this is to use a rwlock in the tty struct, but for now - * we use a single global rwlock for all ttys in ppp line discipline. - * - * FIXME: Fixed in tty_io nowadays. - */ -static DEFINE_RWLOCK(disc_data_lock); - -static struct syncppp *sp_get(struct tty_struct *tty) -{ - struct syncppp *ap; - - read_lock(&disc_data_lock); - ap = tty->disc_data; - if (ap != NULL) - refcount_inc(&ap->refcnt); - read_unlock(&disc_data_lock); - return ap; -} - -static void sp_put(struct syncppp *ap) -{ - if (refcount_dec_and_test(&ap->refcnt)) - complete(&ap->dead_cmp); -} - /* * Called when a tty is put into sync-PPP line discipline. */ @@ -177,9 +142,6 @@ ppp_sync_open(struct tty_struct *tty) skb_queue_head_init(&ap->rqueue); tasklet_setup(&ap->tsk, ppp_sync_process); - refcount_set(&ap->refcnt, 1); - init_completion(&ap->dead_cmp); - ap->chan.private = ap; ap->chan.ops = &sync_ops; ap->chan.mtu = PPP_MRU; @@ -201,34 +163,18 @@ ppp_sync_open(struct tty_struct *tty) } /* - * Called when the tty is put into another line discipline - * or it hangs up. We have to wait for any cpu currently - * executing in any of the other ppp_synctty_* routines to - * finish before we can call ppp_unregister_channel and free - * the syncppp struct. This routine must be called from - * process context, not interrupt or softirq context. + * Called when the tty is put into another line discipline or it hangs up. + * This call is serialized against other ldisc functions. */ static void ppp_sync_close(struct tty_struct *tty) { - struct syncppp *ap; + struct syncppp *ap = tty->disc_data; - write_lock_irq(&disc_data_lock); - ap = tty->disc_data; - tty->disc_data = NULL; - write_unlock_irq(&disc_data_lock); if (!ap) return; - /* - * We have now ensured that nobody can start using ap from now - * on, but we have to wait for all existing users to finish. - * Note that ppp_unregister_channel ensures that no calls to - * our channel ops (i.e. ppp_sync_send/ioctl) are in progress - * by the time it returns. - */ - if (!refcount_dec_and_test(&ap->refcnt)) - wait_for_completion(&ap->dead_cmp); + tty->disc_data = NULL; tasklet_kill(&ap->tsk); ppp_unregister_channel(&ap->chan); @@ -237,17 +183,6 @@ ppp_sync_close(struct tty_struct *tty) kfree(ap); } -/* - * Called on tty hangup in process context. - * - * Wait for I/O to driver to complete and unregister PPP channel. - * This is already done by the close routine, so just call that. - */ -static void ppp_sync_hangup(struct tty_struct *tty) -{ - ppp_sync_close(tty); -} - /* * Read does nothing - no data is ever available this way. * Pppd reads and writes packets via /dev/ppp instead. @@ -273,7 +208,7 @@ ppp_sync_write(struct tty_struct *tty, struct file *file, const u8 *buf, static int ppp_synctty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) { - struct syncppp *ap = sp_get(tty); + struct syncppp *ap = tty->disc_data; int __user *p = (int __user *)arg; int err, val; @@ -314,7 +249,6 @@ ppp_synctty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) break; } - sp_put(ap); return err; } @@ -323,7 +257,7 @@ static void ppp_sync_receive(struct tty_struct *tty, const u8 *buf, const u8 *cflags, size_t count) { - struct syncppp *ap = sp_get(tty); + struct syncppp *ap = tty->disc_data; unsigned long flags; if (!ap) @@ -333,21 +267,19 @@ ppp_sync_receive(struct tty_struct *tty, const u8 *buf, const u8 *cflags, spin_unlock_irqrestore(&ap->recv_lock, flags); if (!skb_queue_empty(&ap->rqueue)) tasklet_schedule(&ap->tsk); - sp_put(ap); tty_unthrottle(tty); } static void ppp_sync_wakeup(struct tty_struct *tty) { - struct syncppp *ap = sp_get(tty); + struct syncppp *ap = tty->disc_data; clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); if (!ap) return; set_bit(XMIT_WAKEUP, &ap->xmit_flags); tasklet_schedule(&ap->tsk); - sp_put(ap); } @@ -357,7 +289,6 @@ static struct tty_ldisc_ops ppp_sync_ldisc = { .name = "pppsync", .open = ppp_sync_open, .close = ppp_sync_close, - .hangup = ppp_sync_hangup, .read = ppp_sync_read, .write = ppp_sync_write, .ioctl = ppp_synctty_ioctl, From 4825ef699cda4c6f2f0586b17a5e225560481da6 Mon Sep 17 00:00:00 2001 From: Yafang Shao Date: Sun, 16 Aug 2026 17:04:41 +0800 Subject: [PATCH 0355/2366] klp-build: Fix wrong index in funcs cleanup error path In the object allocation loop, when kzalloc() for funcs fails, the cleanup loop uses `objs[i].funcs` instead of `objs[j].funcs`. Since `objs[i].funcs` is still NULL at that point, it repeatedly calls kfree(NULL) and leaks all previously allocated funcs arrays. Fixes: 59adee07b568 ("livepatch/klp-build: Add stub init code for livepatch modules") Signed-off-by: Yafang Shao Acked-by: Song Liu Reviewed-by: Petr Mladek Acked-by: Miroslav Benes Link: https://patch.msgid.link/20260816090442.18128-2-laoar.shao@gmail.com Signed-off-by: Josh Poimboeuf --- scripts/livepatch/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/livepatch/init.c b/scripts/livepatch/init.c index f14d8c8fb35f..16aff8f736eb 100644 --- a/scripts/livepatch/init.c +++ b/scripts/livepatch/init.c @@ -51,7 +51,7 @@ static int __init livepatch_mod_init(void) if (!funcs) { ret = -ENOMEM; for (int j = 0; j < i; j++) - kfree(objs[i].funcs); + kfree(objs[j].funcs); goto err_free_objs; } From 738ef4cd82818b13f492fd5ddec7e00f177ffe60 Mon Sep 17 00:00:00 2001 From: Andi Kleen Date: Mon, 31 Aug 2026 08:04:37 -0700 Subject: [PATCH 0356/2366] uprobes: guard trace cleanup against error pointers Sashiko pointed out the some of the scope cleanups for free_uprobe could get an error pointer. Handle this case in free_uprobe to prevent a crash. On the other hand the macro doesn't need the guard because free_uprobe itself already does the check. Link: https://lore.kernel.org/all/20260831150651.1134594-2-ak@kernel.org/ Assisted-by: omp:gpt-5.6-luna sashiko Signed-off-by: Andi Kleen Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_uprobe.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/trace/trace_uprobe.c b/kernel/trace/trace_uprobe.c index 861d857adadb..22cc3c8181b8 100644 --- a/kernel/trace/trace_uprobe.c +++ b/kernel/trace/trace_uprobe.c @@ -368,7 +368,7 @@ alloc_trace_uprobe(const char *group, const char *event, int nargs, bool is_ret) static void free_trace_uprobe(struct trace_uprobe *tu) { - if (!tu) + if (IS_ERR_OR_NULL(tu)) return; path_put(&tu->path); @@ -533,7 +533,7 @@ static int register_trace_uprobe(struct trace_uprobe *tu) return ret; } -DEFINE_FREE(free_trace_uprobe, struct trace_uprobe *, if (_T) free_trace_uprobe(_T)) +DEFINE_FREE(free_trace_uprobe, struct trace_uprobe *, free_trace_uprobe(_T)) /* * Argument syntax: From ac323c9467092479dc1e5bc138c9abbe015b0069 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 28 Aug 2026 10:48:50 -0700 Subject: [PATCH 0357/2366] objtool/klp: Fix checksums for constant pool references Adding a line of code to __link_shadow_page() with a literal string causes a false positive changed function with GCC: arch/x86/kvm/kvm.ko.o: changed function: kvm_tdp_mmu_map_private_pfn While the patch only touched __link_shadow_page(), the string addition triggered a rename of .LC64 -> .LC65 in kvm_tdp_mmu_map_private_pfn() even though the underlying referenced constant data didn't change. So for .LC* symbols, the suffix is arbitrary but the data isn't. Add the underlying data to the checksum calculation rather than the symbol name. Clang also uses .LC* symbols, but also uses anonymous data. Both compilers put this data in .rodata.cst sections. Fixes: 0d83da43b1e1 ("objtool/klp: Add --checksum option to generate per-function checksums") Link: https://patch.msgid.link/f3a9e74ceebc6475ce94bcfe985401140857814a.1787939301.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-checksum.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tools/objtool/klp-checksum.c b/tools/objtool/klp-checksum.c index b8e47f28997e..ebe25f9c5260 100644 --- a/tools/objtool/klp-checksum.c +++ b/tools/objtool/klp-checksum.c @@ -54,6 +54,19 @@ static int checksum_debug_init(struct objtool_file *file) return 0; } +/* + * Detect a reference to anonymous constant pool data which the compiler places + * in .rodata.cst and which either has an .LC symbol associated with + * it or (with Clang) no symbol at all. These are typically initializers for + * local function stack data, so they're considered part of the function rather + * than data per se. + */ +static bool is_anonymous_const_data(struct symbol *sym) +{ + return strstarts(sym->sec->name, ".rodata.cst") && + (is_sec_sym(sym) || strstarts(sym->name, ".LC")); +} + static void checksum_update_insn(struct objtool_file *file, struct symbol *func, struct instruction *insn) { @@ -129,6 +142,14 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func, goto alts; } + if (is_anonymous_const_data(sym)) { + void *cst; + + cst = sym->sec->data->d_buf + sym->offset + offset; + __checksum_update_insn(func, insn, cst, sym->sec->sh.sh_entsize); + goto alts; + } + if (is_sec_sym(sym)) { sym = find_symbol_containing(reloc->sym->sec, offset); if (!sym) From 93b49239840b91313adbd77b8b52993eff2d08c1 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 08:45:27 +0000 Subject: [PATCH 0358/2366] ipv6: mcast: fix RCU list diversion in ip6_mc_del1_src() When removing a source filter whose count reaches zero, ip6_mc_del1_src() unlinks psf from pmc->mca_sources. If the filter was previously active, the code moved psf directly into pmc->mca_tomb by updating psf->sf_next. Because pmc->mca_sources is traversed locklessly under RCU (e.g. by ipv6_chk_mcast_addr()), mutating psf->sf_next before a grace period elapses diverts concurrent readers to the tombstone list. Consequently, readers miss remaining active sources in pmc->mca_sources and improperly examine deleted tombstone entries. Fix this by allocating a new tombstone node for pmc->mca_tomb (as done in sf_setstate()) and retiring the original psf via kfree_rcu(). Fixes: 4b200e398953 ("mld: convert ip6_sf_list to RCU") Signed-off-by: Eric Dumazet Cc: Taehee Yoo Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260828084531.1826790-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/mcast.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index aaba4c2aae23..ec7fac511c8d 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -2351,14 +2351,18 @@ static int ip6_mc_del1_src(struct ifmcaddr6 *pmc, int sfmode, if (psf->sf_oldin && !(pmc->mca_flags & MAF_NOREPORT) && !mld_in_v1_mode(idev)) { - psf->sf_crcount = idev->mc_qrv; - rcu_assign_pointer(psf->sf_next, - mc_dereference(pmc->mca_tomb, idev)); - rcu_assign_pointer(pmc->mca_tomb, psf); - rv = 1; - } else { - kfree_rcu(psf, rcu); + struct ip6_sf_list *dpsf = kmalloc_obj(*dpsf); + + if (dpsf) { + *dpsf = *psf; + dpsf->sf_crcount = idev->mc_qrv; + rcu_assign_pointer(dpsf->sf_next, + mc_dereference(pmc->mca_tomb, idev)); + rcu_assign_pointer(pmc->mca_tomb, dpsf); + rv = 1; + } } + kfree_rcu(psf, rcu); } return rv; } From c073d1b070f171d206b19c98d71739a97f15b3f1 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 08:45:28 +0000 Subject: [PATCH 0359/2366] ipv6: mcast: use copy-on-write RCU updates in ip6_mc_source() pmc->sflist is read locklessly under rcu_read_lock() by inet6_mc_check() during packet reception in the UDP and RAW multicast receive paths. ip6_mc_source() mutated psl->sl_addr and psl->sl_count in-place when adding or removing a source filter. Additionally, when expanding the filter buffer, newpsl was published via rcu_assign_pointer() before writing the new source into the array. Because 16-byte struct in6_addr writes are not atomic and array shifting is not synchronized with RCU readers, concurrent readers in inet6_mc_check() could read torn IPv6 addresses or observe duplicated/missed source entries. Fix this by switching ip6_mc_source() to copy-on-write RCU updates: allocate and fully populate newpsl before publishing it via rcu_assign_pointer(), and reclaim the old filter via kfree_rcu(), matching ip6_mc_msfilter(). Also remove the now unused IP6_SFBLOCK macro. Fixes: 882ba1f73c06 ("mld: convert ipv6_mc_socklist->sflist to RCU") Signed-off-by: Eric Dumazet Cc: Taehee Yoo Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260828084531.1826790-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/if_inet6.h | 2 - net/ipv6/mcast.c | 98 ++++++++++++++++++++++++------------------ 2 files changed, 56 insertions(+), 44 deletions(-) diff --git a/include/net/if_inet6.h b/include/net/if_inet6.h index 238ad3349456..795fb41b45f5 100644 --- a/include/net/if_inet6.h +++ b/include/net/if_inet6.h @@ -88,8 +88,6 @@ struct ip6_sf_socklist { struct in6_addr sl_addr[] __counted_by(sl_max); }; -#define IP6_SFBLOCK 10 /* allocate this many at once */ - struct ipv6_mc_socklist { struct in6_addr addr; int ifindex; diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index ec7fac511c8d..66f5858e5fea 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -355,12 +355,12 @@ int ip6_mc_source(int add, int omode, struct sock *sk, { struct ipv6_pinfo *inet6 = inet6_sk(sk); struct in6_addr *source, *group; + struct ip6_sf_socklist *newpsl, *psl; struct net *net = sock_net(sk); struct ipv6_mc_socklist *pmc; - struct ip6_sf_socklist *psl; struct inet6_dev *idev; int leavegroup = 0; - int i, j, rv; + int i, j; int err; source = &((struct sockaddr_in6 *)&pgsr->gsr_source)->sin6_addr; @@ -409,13 +409,11 @@ int ip6_mc_source(int add, int omode, struct sock *sk, if (!add) { if (!psl) goto done; /* err = -EADDRNOTAVAIL */ - rv = !0; for (i = 0; i < psl->sl_count; i++) { - rv = !ipv6_addr_equal(&psl->sl_addr[i], source); - if (rv == 0) + if (ipv6_addr_equal(&psl->sl_addr[i], source)) break; } - if (rv) /* source not found */ + if (i == psl->sl_count) /* source not found */ goto done; /* err = -EADDRNOTAVAIL */ /* special case - (INCLUDE, empty) == LEAVE_GROUP */ @@ -424,58 +422,74 @@ int ip6_mc_source(int add, int omode, struct sock *sk, goto done; } + atomic_sub(struct_size(psl, sl_addr, psl->sl_max), + &sk->sk_omem_alloc); + + if (psl->sl_count == 1) { + newpsl = NULL; + } else { + newpsl = sock_kmalloc(sk, struct_size(newpsl, sl_addr, + psl->sl_count - 1), + GFP_KERNEL); + if (!newpsl) { + atomic_add(struct_size(psl, sl_addr, psl->sl_max), + &sk->sk_omem_alloc); + err = -ENOBUFS; + goto done; + } + newpsl->sl_max = psl->sl_count - 1; + newpsl->sl_count = psl->sl_count - 1; + for (j = 0; j < i; j++) + newpsl->sl_addr[j] = psl->sl_addr[j]; + for (j = i + 1; j < psl->sl_count; j++) + newpsl->sl_addr[j - 1] = psl->sl_addr[j]; + } + /* update the interface filter */ ip6_mc_del_src(idev, group, omode, 1, source, 1); - for (j = i+1; j < psl->sl_count; j++) - psl->sl_addr[j-1] = psl->sl_addr[j]; - psl->sl_count--; + rcu_assign_pointer(pmc->sflist, newpsl); + kfree_rcu(psl, rcu); err = 0; goto done; } /* else, add a new source to the filter */ - if (psl && psl->sl_count >= sysctl_mld_max_msf) { + if (psl && psl->sl_count >= READ_ONCE(sysctl_mld_max_msf)) { err = -ENOBUFS; goto done; } - if (!psl || psl->sl_count == psl->sl_max) { - struct ip6_sf_socklist *newpsl; - int count = IP6_SFBLOCK; + if (psl) { + for (i = 0; i < psl->sl_count; i++) { + if (ipv6_addr_equal(&psl->sl_addr[i], source)) + goto done; /* err = -EADDRNOTAVAIL */ + } + } - if (psl) - count += psl->sl_max; - newpsl = sock_kmalloc(sk, struct_size(newpsl, sl_addr, count), - GFP_KERNEL); - if (!newpsl) { - err = -ENOBUFS; - goto done; - } - newpsl->sl_max = count; - newpsl->sl_count = count - IP6_SFBLOCK; - if (psl) { - for (i = 0; i < psl->sl_count; i++) - newpsl->sl_addr[i] = psl->sl_addr[i]; - atomic_sub(struct_size(psl, sl_addr, psl->sl_max), - &sk->sk_omem_alloc); - } - rcu_assign_pointer(pmc->sflist, newpsl); - kfree_rcu(psl, rcu); - psl = newpsl; + i = psl ? psl->sl_count + 1 : 1; + newpsl = sock_kmalloc(sk, struct_size(newpsl, sl_addr, i), + GFP_KERNEL); + if (!newpsl) { + err = -ENOBUFS; + goto done; } - rv = 1; /* > 0 for insert logic below if sl_count is 0 */ - for (i = 0; i < psl->sl_count; i++) { - rv = !ipv6_addr_equal(&psl->sl_addr[i], source); - if (rv == 0) /* There is an error in the address. */ - goto done; + newpsl->sl_max = i; + newpsl->sl_count = i; + if (psl) { + for (j = 0; j < psl->sl_count; j++) + newpsl->sl_addr[j] = psl->sl_addr[j]; } - for (j = psl->sl_count-1; j >= i; j--) - psl->sl_addr[j+1] = psl->sl_addr[j]; - psl->sl_addr[i] = *source; - psl->sl_count++; - err = 0; + newpsl->sl_addr[i - 1] = *source; + /* update the interface list */ ip6_mc_add_src(idev, group, omode, 1, source, 1); + + if (psl) + atomic_sub(struct_size(psl, sl_addr, psl->sl_max), + &sk->sk_omem_alloc); + rcu_assign_pointer(pmc->sflist, newpsl); + kfree_rcu(psl, rcu); + err = 0; done: mutex_unlock(&idev->mc_lock); in6_dev_put(idev); From 75fa9caeb8aaba19c2463dee0b0a1e09d39c04af Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 08:45:29 +0000 Subject: [PATCH 0360/2366] ipv6: mcast: fix delay calculation in igmp6_join_group() When joining a multicast group, if a report work is already pending (e.g. scheduled by a query or a previous join), igmp6_join_group() cancels the delayed work and recalculates the delay: if (cancel_delayed_work(&ma->mca_work)) { refcount_dec(&ma->mca_refcnt); delay = ma->mca_work.timer.expires - jiffies; } Unlike igmp6_group_queried(), igmp6_join_group() did not check if delay >= interval. This leads to two issues: 1. If the timer has already expired (timer.expires <= jiffies), the stale expiry is reused by mod_delayed_work(), causing the second unsolicited report to fire on the very next tick without a randomized delay. 2. If the timer was originally armed by a query with a large maximum response delay, delay could exceed unsolicited_report_interval(ma->idev). Fix this by initializing delay to unsolicited_report_interval(ma->idev) and re-randomizing it with get_random_u32_below(interval) when delay >= interval, mirroring the logic in igmp6_group_queried(). Fixes: 2d9a93b4902b ("mld: convert from timer to delayed work") Signed-off-by: Eric Dumazet Cc: Taehee Yoo Reviewed-by: Ido Schimmel Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://patch.msgid.link/20260828084531.1826790-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/mcast.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 66f5858e5fea..4423b90dc9ab 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -2639,7 +2639,7 @@ static void ip6_mc_clear_src(struct ifmcaddr6 *pmc) static void igmp6_join_group(struct ifmcaddr6 *ma) { - unsigned long delay; + unsigned long delay, interval; mc_assert_locked(ma->idev); @@ -2648,13 +2648,17 @@ static void igmp6_join_group(struct ifmcaddr6 *ma) igmp6_send(&ma->mca_addr, ma->idev->dev, ICMPV6_MGM_REPORT); - delay = get_random_u32_below(unsolicited_report_interval(ma->idev)); + interval = unsolicited_report_interval(ma->idev); + delay = interval; if (cancel_delayed_work(&ma->mca_work)) { refcount_dec(&ma->mca_refcnt); delay = ma->mca_work.timer.expires - jiffies; } + if (delay >= interval) + delay = get_random_u32_below(interval); + if (!mod_delayed_work(mld_wq, &ma->mca_work, delay)) refcount_inc(&ma->mca_refcnt); WRITE_ONCE(ma->mca_flags, ma->mca_flags | From 0c8f56c583c3250408367880c98e4d6fbc929315 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 08:45:30 +0000 Subject: [PATCH 0361/2366] ipv6: mcast: use rcu_assign_pointer() for __rcu list updates Several places in net/ipv6/mcast.c update RCU-protected lists (np->ipv6_mc_list, idev->mc_list, idev->mc_tomb) using direct pointer assignments instead of rcu_assign_pointer(): 1. In __ipv6_dev_mc_dec(), unlinking a group from idev->mc_list did: *map = ma->next; without rcu_assign_pointer() while concurrent readers traverse idev->mc_list locklessly under rcu_read_lock(). 2. In ipv6_sock_mc_drop() and __ipv6_sock_mc_close(), unlinking a group from np->ipv6_mc_list directly assigned *lnk = mc_lst->next and np->ipv6_mc_list = mc_lst->next without rcu_assign_pointer(), racing with lockless readers in inet6_mc_check(). 3. In __ipv6_sock_mc_join(), mc_lst->next was initialized to np->ipv6_mc_list via raw assignment before publishing mc_lst. 4. In mld_del_delrec() and __ipv6_dev_mc_inc(), __rcu source pointers passed into rcu_assign_pointer() lacked explicit dereference helpers. Fix these by consistently using rcu_assign_pointer() along with mc_dereference() / sock_dereference(). Fixes: 456b61bca8ee ("ipv6: mcast: RCU conversion") Fixes: 88e2ca308094 ("mld: convert ifmcaddr6 to RCU") Signed-off-by: Eric Dumazet Cc: Taehee Yoo Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260828084531.1826790-5-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/mcast.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 4423b90dc9ab..2290457eb8d3 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -240,7 +240,8 @@ static int __ipv6_sock_mc_join(struct sock *sk, int ifindex, return err; } - mc_lst->next = np->ipv6_mc_list; + rcu_assign_pointer(mc_lst->next, + sock_dereference(np->ipv6_mc_list, sk)); rcu_assign_pointer(np->ipv6_mc_list, mc_lst); return 0; @@ -300,7 +301,8 @@ int ipv6_sock_mc_drop(struct sock *sk, int ifindex, const struct in6_addr *addr) lnk = &mc_lst->next) { if ((ifindex == 0 || mc_lst->ifindex == ifindex) && ipv6_addr_equal(&mc_lst->addr, addr)) { - *lnk = mc_lst->next; + rcu_assign_pointer(*lnk, + sock_dereference(mc_lst->next, sk)); __ipv6_sock_mc_drop(sk, mc_lst); return 0; } @@ -333,7 +335,8 @@ void __ipv6_sock_mc_close(struct sock *sk) struct ipv6_mc_socklist *mc_lst; while ((mc_lst = sock_dereference(np->ipv6_mc_list, sk)) != NULL) { - np->ipv6_mc_list = mc_lst->next; + rcu_assign_pointer(np->ipv6_mc_list, + sock_dereference(mc_lst->next, sk)); __ipv6_sock_mc_drop(sk, mc_lst); } } @@ -798,9 +801,11 @@ static void mld_del_delrec(struct inet6_dev *idev, struct ifmcaddr6 *im) if (!pmc) return; if (pmc_prev) - rcu_assign_pointer(pmc_prev->next, pmc->next); + rcu_assign_pointer(pmc_prev->next, + mc_dereference(pmc->next, idev)); else - rcu_assign_pointer(idev->mc_tomb, pmc->next); + rcu_assign_pointer(idev->mc_tomb, + mc_dereference(pmc->next, idev)); im->idev = pmc->idev; if (im->mca_sfmode == MCAST_INCLUDE) { @@ -980,7 +985,7 @@ static int __ipv6_dev_mc_inc(struct net_device *dev, return -ENOMEM; } - rcu_assign_pointer(mc->next, idev->mc_list); + rcu_assign_pointer(mc->next, mc_dereference(idev->mc_list, idev)); rcu_assign_pointer(idev->mc_list, mc); mld_del_delrec(idev, mc); @@ -1014,7 +1019,8 @@ int __ipv6_dev_mc_dec(struct inet6_dev *idev, const struct in6_addr *addr) WRITE_ONCE(ma->mca_users, new_users); if (new_users == 0) { - *map = ma->next; + rcu_assign_pointer(*map, + mc_dereference(ma->next, idev)); igmp6_group_dropped(ma); inet6_ifmcaddr_notify(idev->dev, ma, From b4cf4a092a7bdaa62acca39c28f386b6d1674968 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 08:45:31 +0000 Subject: [PATCH 0362/2366] ipv6: mcast: use jiffies_delta_to_clock_t() in igmp6_mc_seq_show() If a multicast group timer has expired but the delayed work has not yet run to clear MAF_TIMER_RUNNING, expires - jiffies produces a negative value. Because unsigned arithmetic was used with jiffies_to_clock_t(), expires - jiffies underflows to a huge value and reports invalid timer durations in /proc/net/igmp6. Use jiffies_delta_to_clock_t() with a signed long delta to properly cap expired deltas to 0, matching IPv4 igmp_mc_seq_show() and commit a399a8053164 ("time: jiffies_delta_to_clock_t() helper to the rescue"). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260828084531.1826790-6-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/mcast.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 2290457eb8d3..ecef55f26189 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -3029,7 +3029,7 @@ static int igmp6_mc_seq_show(struct seq_file *seq, void *v) struct ifmcaddr6 *im = (struct ifmcaddr6 *)v; struct igmp6_mc_iter_state *state = igmp6_mc_seq_private(seq); unsigned int mca_flags = READ_ONCE(im->mca_flags); - unsigned long expires = READ_ONCE(im->mca_work.timer.expires); + long delta = READ_ONCE(im->mca_work.timer.expires) - jiffies; seq_printf(seq, "%-4d %-15s %pi6 %5d %08X %ld\n", @@ -3037,7 +3037,7 @@ static int igmp6_mc_seq_show(struct seq_file *seq, void *v) &im->mca_addr, READ_ONCE(im->mca_users), mca_flags, (mca_flags & MAF_TIMER_RUNNING) ? - jiffies_to_clock_t(expires - jiffies) : 0); + jiffies_delta_to_clock_t(delta) : 0); return 0; } From 97cc84dad1d7f68a36b71b69b361d88482707673 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 10:37:31 +0000 Subject: [PATCH 0363/2366] ip6_gre: check tunnel info before xmit in ip6gre_tunnel_xmit Shuangpeng Bai reported a KASAN slab-use-after-free in ip6gre_tunnel_xmit(). The precise KASAN bug was caused by ip6_tnl_xmit() consuming the skb during headroom expansion and returning an error, while ip6gre_tunnel_xmit() still held the stale pointer and called skb_tunnel_info_txcheck(skb) at tx_err. That specific bug was fixed by commit 87f21b59ddc6 ("ip6_tunnel: use skb_cow_head() in ip6_tnl_xmit()"). However, calling skb_tunnel_info_txcheck(skb) at the tx_err label after the transmission attempt remains problematic: Downstream helpers like ip6_tnl_xmit() call skb_scrub_packet(), which drops the skb's metadata_dst before transmission. If an error occurs later during transmit, inspecting skb at tx_err sees a scrubbed dst and misclassifies tx_errors vs tx_dropped. Commit e5f7e211b6aa ("ip6gre: avoid tx_error when sending MLD/DAD on external tunnels") already handled this correctly in ip6erspan_tunnel_xmit() by checking and caching tun_info before transmit. Align ip6gre_tunnel_xmit() with ip6erspan_tunnel_xmit() by caching tun_info before xmit and checking it at tx_err. Fixes: e5f7e211b6aa ("ip6gre: avoid tx_error when sending MLD/DAD on external tunnels") Reported-by: Shuangpeng Bai Closes: https://lore.kernel.org/netdev/20260819062224.3197349-1-shuangpeng.kernel@gmail.com/ Cc: Davide Caratti Signed-off-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260828103731.1951815-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_gre.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index 69c51f1a5bf0..8ebda0b6a78b 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -878,6 +878,7 @@ static int ip6gre_xmit_other(struct sk_buff *skb, struct net_device *dev) static netdev_tx_t ip6gre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev) { + struct ip_tunnel_info *tun_info = NULL; struct ip6_tnl *t = netdev_priv(dev); __be16 payload_protocol; int ret; @@ -888,6 +889,9 @@ static netdev_tx_t ip6gre_tunnel_xmit(struct sk_buff *skb, if (!ip6_tnl_xmit_ctl(t, &t->parms.laddr, &t->parms.raddr)) goto tx_err; + if (t->parms.collect_md) + tun_info = skb_tunnel_info_txcheck(skb); + payload_protocol = skb_protocol(skb, true); switch (payload_protocol) { case htons(ETH_P_IP): @@ -907,7 +911,7 @@ static netdev_tx_t ip6gre_tunnel_xmit(struct sk_buff *skb, return NETDEV_TX_OK; tx_err: - if (!t->parms.collect_md || !IS_ERR(skb_tunnel_info_txcheck(skb))) + if (!IS_ERR(tun_info)) DEV_STATS_INC(dev, tx_errors); DEV_STATS_INC(dev, tx_dropped); kfree_skb(skb); From bdc46e507b59ac44c8e1dab505121f18c2add1ab Mon Sep 17 00:00:00 2001 From: Xiaofeng Yuan Date: Mon, 31 Aug 2026 19:11:22 -0600 Subject: [PATCH 0364/2366] riscv: mm: make EXECMEM_KPROBES writable without CONFIG_STRICT_MODULE_RWX When CONFIG_STRICT_MODULE_RWX is not set, execmem cannot create temporary writable mappings for read-only executable pages. In this case, the execmem ranges must already have writable permissions. Currently EXECMEM_KPROBES unconditionally uses PAGE_KERNEL_READ_EXEC, which causes kprobe instruction slot writes to trigger page faults on systems where CONFIG_STRICT_MODULE_RWX is not enabled. Fix this by using PAGE_KERNEL_EXEC when CONFIG_STRICT_MODULE_RWX is not available. Signed-off-by: Xiaofeng Yuan Tested-by: Lad Prabhakar Reviewed-by: Nam Cao Link: https://patch.msgid.link/20260814082742.148403-2-xiaofengmian@163.com Signed-off-by: Paul Walmsley --- arch/riscv/mm/init.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/riscv/mm/init.c b/arch/riscv/mm/init.c index f8994caefc70..fb37b0b67efe 100644 --- a/arch/riscv/mm/init.c +++ b/arch/riscv/mm/init.c @@ -1465,7 +1465,9 @@ struct execmem_info __init *execmem_arch_setup(void) [EXECMEM_KPROBES] = { .start = VMALLOC_START, .end = VMALLOC_END, - .pgprot = PAGE_KERNEL_READ_EXEC, + .pgprot = IS_ENABLED(CONFIG_STRICT_MODULE_RWX) ? + PAGE_KERNEL_READ_EXEC : + PAGE_KERNEL_EXEC, .alignment = 1, }, [EXECMEM_BPF] = { From 8718e5a3090bbfd759801d088b700aab21e8e989 Mon Sep 17 00:00:00 2001 From: Xiaofeng Yuan Date: Mon, 31 Aug 2026 19:11:23 -0600 Subject: [PATCH 0365/2366] riscv: patch: skip fixmap mapping when kernel text is already writable patch_map() always creates a temporary writable mapping via fixmap for kernel text addresses, even when CONFIG_STRICT_KERNEL_RWX is disabled and the kernel text is already mapped with _PAGE_WRITE. This is unnecessary overhead at best, and on minimal configurations it can cause page faults. Skip the fixmap path for kernel text when CONFIG_STRICT_KERNEL_RWX is not enabled, since the text pages are already writable in that case. The module text path is already gated on CONFIG_STRICT_MODULE_RWX and is kept unchanged. Reported-by: Klara Modin Closes: https://lore.kernel.org/all/ant_8TaBbov_GS4i@soda.int.kasm.eu/ Reported-by: Lad Prabhakar Closes: https://lore.kernel.org/all/CA+V-a8tQK8rih9SGGTyqrEBGpNkx4H0eX2YccCRrgkVAPr+EBg@mail.gmail.com/ Tested-by: Klara Modin Tested-by: Lad Prabhakar Link: https://patch.msgid.link/20260814082742.148403-3-xiaofengmian@163.com Signed-off-by: Paul Walmsley --- arch/riscv/kernel/patch.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/riscv/kernel/patch.c b/arch/riscv/kernel/patch.c index 16b243376f36..2239c28981bc 100644 --- a/arch/riscv/kernel/patch.c +++ b/arch/riscv/kernel/patch.c @@ -45,6 +45,8 @@ static __always_inline void *patch_map(void *addr, const unsigned int fixmap) phys_addr_t phys; if (core_kernel_text(uintaddr) || is_kernel_exittext(uintaddr)) { + if (!IS_ENABLED(CONFIG_STRICT_KERNEL_RWX)) + return addr; phys = __pa_symbol(addr); } else if (IS_ENABLED(CONFIG_STRICT_MODULE_RWX)) { struct page *page = vmalloc_to_page(addr); From 2d2184ac90365a4af3274e23c98d469f09f91749 Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Mon, 31 Aug 2026 19:11:23 -0600 Subject: [PATCH 0366/2366] Revert "riscv: Reset pmm when PR_TAGGED_ADDR_ENABLE is not set" This reverts commit 3033b2b1e3949274f33a140e2a97571b5a307298. The reverted patch is userspace-visible behavior change, not a bug fix. The two variables here (pmm and pmlen) control two independent features: pmm is the _hardware_ pointer masking mode that applies while executing in userspace. pmlen is the shift amount that the _kernel_ uses when untagging addresses; PMLEN_0 means no untagging occurs, so the kernel does not accept tagged addresses in syscall arguments. It is valid (as documented and tested by the self test) to enable pointer masking without enabling the tagged address ABI. This separation is necessary to allow userspace to create an execution environment similar to what the kernel supports on arm64 by default, where TBI is enabled but the tagged address ABI is not. (On arm64, there is no equivalent to PR_PMLEN_MASK because TBI is always enabled.) Signed-off-by: Samuel Holland Link: https://patch.msgid.link/20260820014551.1979772-1-samuel.holland@sifive.com Cc: stable@vger.kernel.org Fixes: 3033b2b1e394 ("riscv: Reset pmm when PR_TAGGED_ADDR_ENABLE is not set") Signed-off-by: Paul Walmsley --- arch/riscv/kernel/process.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arch/riscv/kernel/process.c b/arch/riscv/kernel/process.c index b2df7f72241a..7cc5a6a5c020 100644 --- a/arch/riscv/kernel/process.c +++ b/arch/riscv/kernel/process.c @@ -349,10 +349,8 @@ long set_tagged_addr_ctrl(struct task_struct *task, unsigned long arg) if (arg & PR_TAGGED_ADDR_ENABLE && (tagged_addr_disabled || !pmlen)) return -EINVAL; - if (!(arg & PR_TAGGED_ADDR_ENABLE)) { + if (!(arg & PR_TAGGED_ADDR_ENABLE)) pmlen = PMLEN_0; - pmm = ENVCFG_PMM_PMLEN_0; - } if (mmap_write_lock_killable(mm)) return -EINTR; From 12381af01024f4a59cd3f673fb3644bbe2ca3aad Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Mon, 31 Aug 2026 19:11:23 -0600 Subject: [PATCH 0367/2366] riscv: use string helper in setup_global_riscv_enable() Prefer the convenient string choice 'str_disabled_enabled()' helper over hardcoded strings in 'setup_global_riscv_enable()'. Signed-off-by: Dmitry Antipov Link: https://patch.msgid.link/20260819160546.3219942-1-dmantipov@yandex.ru Signed-off-by: Paul Walmsley --- arch/riscv/kernel/usercfi.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/riscv/kernel/usercfi.c b/arch/riscv/kernel/usercfi.c index f027e6e05251..dec0ba5eff5e 100644 --- a/arch/riscv/kernel/usercfi.c +++ b/arch/riscv/kernel/usercfi.c @@ -525,9 +525,8 @@ static int __init setup_global_riscv_enable(char *str) if (riscv_nousercfi) pr_info("RISC-V user CFI disabled via cmdline - shadow stack status : %s, landing pad status : %s\n", - (riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_BCFI) ? "disabled" : - "enabled", (riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_FCFI) ? - "disabled" : "enabled"); + str_disabled_enabled(riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_BCFI), + str_disabled_enabled(riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_FCFI)); return 1; } From d0fc6fab20460add1f27402cd8b945d094a56b21 Mon Sep 17 00:00:00 2001 From: Andy Chiu Date: Mon, 31 Aug 2026 19:11:23 -0600 Subject: [PATCH 0368/2366] riscv: hwprobe: initialize pair->value in hwprobe_one_pair() The vendor-extension handlers reached from hwprobe_one_pair() (hwprobe_isa_vendor_ext_thead_0() and friends) only OR the present bits into pair->value via VENDOR_EXTENSION_SUPPORTED() and clear their own missing bits; they assume the caller has already zeroed pair->value. That holds for hwprobe_get_values() (it zeroes each pair) and hwprobe_get_cpus() (it re-initializes its scratch pair per key), but not for complete_hwprobe_vdso_data(), which reuses a single pair across all keys without re-zeroing. A vendor key therefore inherits stale bits from the previously probed key, and the wrong value is cached in the vDSO all_cpu_hwprobe_values[] and handed to userspace on the fast patih. Zero pair->value once at the top of hwprobe_one_pair() so every handler starts from a clean value regardless of the caller, and drop the now redundant zeroing in the *_BLOCK_SIZE cases. hwprobe_isa_ext0() keeps its own zeroing because hwprobe_ext0_has() calls it directly, bypassing hwprobe_one_pair(). Fixes: a5ea53da65c5 ("riscv: hwprobe: Add thead vendor extension probing") Signed-off-by: Andy Chiu Reviewed-by: Jesse Taube Link: https://patch.msgid.link/20260725001614.2578617-2-tchiu@tenstorrent.com Cc: stable@vger.kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/kernel/sys_hwprobe.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c index bd6ca7d769da..7818e1d32622 100644 --- a/arch/riscv/kernel/sys_hwprobe.c +++ b/arch/riscv/kernel/sys_hwprobe.c @@ -297,6 +297,8 @@ static u64 hwprobe_vec_misaligned(const struct cpumask *cpus) static void hwprobe_one_pair(struct riscv_hwprobe *pair, const struct cpumask *cpus) { + pair->value = 0; + switch (pair->key) { case RISCV_HWPROBE_KEY_MVENDORID: case RISCV_HWPROBE_KEY_MARCHID: @@ -331,17 +333,14 @@ static void hwprobe_one_pair(struct riscv_hwprobe *pair, break; case RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE: - pair->value = 0; if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOZ)) pair->value = riscv_cboz_block_size; break; case RISCV_HWPROBE_KEY_ZICBOM_BLOCK_SIZE: - pair->value = 0; if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOM)) pair->value = riscv_cbom_block_size; break; case RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE: - pair->value = 0; if (hwprobe_ext0_has(cpus, RISCV_HWPROBE_EXT_ZICBOP)) pair->value = riscv_cbop_block_size; break; From ddeaa39406c4cf680643412cd1f75bb98a641f6c Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Mon, 31 Aug 2026 19:11:23 -0600 Subject: [PATCH 0369/2366] riscv: bug: Make RV32 use GENERIC_BUG_RELATIVE_POINTERS x86 did this in commit b0a848f4a47a ("x86/bugs: Make i386 use GENERIC_BUG_RELATIVE_POINTERS") powerpc did this in commit 1baa1f70ef77 ("powerpc: Allow relative pointers in bug table entries") Similar as x86 and powerpc does, make RV32 use GENERIC_BUG_RELATIVE_POINTERS for "there is only one code path." and "less #ifdef is more better". Signed-off-by: Jisheng Zhang Link: https://patch.msgid.link/20260221024255.3552-1-jszhang@kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/Kconfig | 2 +- arch/riscv/include/asm/bug.h | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index f8e26c4bed2b..d6c2dbf8455c 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -326,7 +326,7 @@ config STACKTRACE_SUPPORT config GENERIC_BUG def_bool y depends on BUG - select GENERIC_BUG_RELATIVE_POINTERS if 64BIT + select GENERIC_BUG_RELATIVE_POINTERS config GENERIC_BUG_RELATIVE_POINTERS bool diff --git a/arch/riscv/include/asm/bug.h b/arch/riscv/include/asm/bug.h index 6f581b84d8fc..699c0cf3e4ef 100644 --- a/arch/riscv/include/asm/bug.h +++ b/arch/riscv/include/asm/bug.h @@ -29,13 +29,8 @@ typedef u32 bug_insn_t; -#ifdef CONFIG_GENERIC_BUG_RELATIVE_POINTERS #define __BUG_ENTRY_ADDR RISCV_INT " 1b - ." #define __BUG_ENTRY_FILE(file) RISCV_INT " " file " - ." -#else -#define __BUG_ENTRY_ADDR RISCV_PTR " 1b" -#define __BUG_ENTRY_FILE(file) RISCV_PTR " " file -#endif #ifdef CONFIG_DEBUG_BUGVERBOSE #define __BUG_ENTRY(file, line, flags) \ From 6693171c8c540b3a54e671992f0561613076fc3b Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 18 Aug 2026 17:10:00 +0800 Subject: [PATCH 0370/2366] perf: RISC-V: use BIT_ULL for u64 overflow masks Overflow status and restart masks are u64, but bits were built with BIT(). On RV32 that is an unsigned long shift, so indices >= 32 truncate or wrap and corrupt the mask. Use BIT_ULL() for those u64 bitops. Fixes: a8625217a054 ("drivers/perf: riscv: Implement SBI PMU snapshot function") Assisted-by: DeepSeek:deepseek-v3 Signed-off-by: Xixin Liu Link: https://patch.msgid.link/prpmask01bitul.v2.1786434000.git.liuxixin@kylinos.cn Cc: stable@vger.kernel.org [pjw@kernel.org: updated to apply] Signed-off-by: Paul Walmsley --- drivers/perf/riscv_pmu_sbi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c index 50220f7b46d9..8ea5ae617347 100644 --- a/drivers/perf/riscv_pmu_sbi.c +++ b/drivers/perf/riscv_pmu_sbi.c @@ -1002,7 +1002,7 @@ static inline void pmu_sbi_start_ovf_ctrs_snapshot(struct cpu_hw_events *cpu_hw_ struct riscv_pmu_snapshot_data *sdata = cpu_hw_evt->snapshot_addr; for_each_set_bit(idx, cpu_hw_evt->used_hw_ctrs, RISCV_MAX_COUNTERS) { - if (ctr_ovf_mask & BIT(idx)) { + if (ctr_ovf_mask & BIT_ULL(idx)) { event = cpu_hw_evt->events[idx]; hwc = &event->hw; max_period = riscv_pmu_ctr_get_width_mask(event); @@ -1109,14 +1109,14 @@ static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev) hidx = info->csr - CSR_CYCLE; /* check if the corresponding bit is set in scountovf or overflow mask in shmem */ - if (!(overflow & BIT(hidx))) + if (!(overflow & BIT_ULL(hidx))) continue; /* * Keep a track of overflowed counters so that they can be started * with updated initial value. */ - overflowed_ctrs |= BIT(lidx); + overflowed_ctrs |= BIT_ULL(lidx); hw_evt = &event->hw; /* Update the event states here so that we know the state while reading */ hw_evt->state |= PERF_HES_STOPPED; From cd51b74bdd0b75aedf255dc67306a16dd057f7ee Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Fri, 28 Aug 2026 22:23:41 +0300 Subject: [PATCH 0371/2366] ipv6: Fix redirect exception creation for UDP/RAW sockets When an ICMP Redirect Message is matched to a socket, both IPv4 and IPv6 verify that the source IP of the ICMP packet is the current gateway for the quoted packet. Both also pass the socket's bound device as the expected nexthop device. The difference is that IPv4 treats "oif=0" as "any", whereas IPv6 always requires an exact match (see ip6_redirect_nh_match()), since the gateway address is usually a link-local address. Therefore, when an IPv6 UDP/RAW socket is not bound to a device, the above verification fails and an exception is not created. This also happens when the socket is bound to a VRF, as l3mdev_update_flow() resets the oif to 0. Fix this by passing the ifindex of the ingress device as the expected nexthop device. This is consistent with the existing callers of ip6_redirect(). Note that for ICMPv6 Redirect Message packets the VRF driver does not reset skb->dev to the VRF device, so skb->dev is correct, even when it is a VRF port. Fixes: b55b76b22144 ("ipv6:introduce function to find route for redirect") Cc: stable@vger.kernel.org Reviewed-by: Eric Dumazet Reviewed-by: David Ahern Signed-off-by: Ido Schimmel Link: https://patch.msgid.link/20260828192344.2596928-2-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 6a40c5074543..9658939511e0 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -3255,7 +3255,7 @@ void ip6_redirect_no_header(struct sk_buff *skb, struct net *net, int oif) void ip6_sk_redirect(struct sk_buff *skb, struct sock *sk) { - ip6_redirect(skb, sock_net(sk), sk->sk_bound_dev_if, + ip6_redirect(skb, sock_net(sk), skb->dev->ifindex, READ_ONCE(sk->sk_mark), sk_uid(sk)); } From 4c3499f79f8c7e8561266bcc220a18538cec0458 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Fri, 28 Aug 2026 22:23:42 +0300 Subject: [PATCH 0372/2366] ipv4: udp: Create exceptions before socket matching Currently, when ICMP Fragmentation Needed and Redirect Message packets are locally delivered and quote a UDP packet, a FIB nexthop exception (FNHE) is only created if the kernel can match the UDP packet to an existing socket. This behavior allows off-path attackers to conduct a side-channel attack on the FNHE cache in order to discover the ephemeral port used by a connected UDP socket. Commit 6457378fe796 ("ipv4: use siphash instead of Jenkins in fnhe_hashfun()") and commit 67d6d681e15b ("ipv4: make exception cache less predictible") tried to mitigate such attacks by making it harder for attackers to discover hash collisions in the FNHE cache and by randomizing the number of exceptions a hash bucket can hold, respectively. Unfortunately, both of the mitigations can be bypassed. Instead, mitigate such attacks by always creating a FNHE, even before trying to find a matching socket. Do that by calling ipv4_update_pmtu() and ipv4_redirect(), the helpers used when the quoted packet did not originate from a socket. This means that guesses (right or wrong) from an off-path attacker will always result in a FNHE being created or updated in the cache that the attacker can observe. Pass an oif of 0, in a similar fashion to icmp_err(). This is also the oif used by the socket path for sockets that are not bound to a device. Note that this does not allow attackers to create FNHEs that they could not create before, as both helpers can already be reached with little to no validation. For example, by sending an ICMP error that quotes an ICMP Echo Reply or one that quotes a UDP source port that matches a wildcard socket. Also note that in the good case (matched socket) the above scheme comes at the cost of an extra route lookup, as the no socket helpers perform their own lookup before the one performed by ipv4_sk_update_pmtu() / ipv4_sk_redirect(). When the two resolve to different nexthops, it also results in two exceptions being created for the same destination IP. One in the FNHE cache of the nexthop resolved by the no socket helpers and another in the FNHE cache of the nexthop used by the socket. Fixes: 4895c771c7f0 ("ipv4: Add FIB nexthop exceptions.") Cc: stable@vger.kernel.org Reported-by: Amit Klein Reported-by: Noam Caspi Signed-off-by: Ido Schimmel Reviewed-by: David Ahern Link: https://patch.msgid.link/20260828192344.2596928-3-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv4/udp.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 6ff5670bf6ed..bb8cfc62cb00 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -900,6 +900,15 @@ static struct sock *__udp4_lib_err_encap(struct net *net, return sk; } +static void udp_err_update_exception(struct net *net, struct sk_buff *skb, + int type, int code, u32 info) +{ + if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) + ipv4_update_pmtu(skb, net, info, 0, IPPROTO_UDP); + else if (type == ICMP_REDIRECT) + ipv4_redirect(skb, net, 0, IPPROTO_UDP); +} + /* * This routine is called by the ICMP module when it gets some * sort of error condition. If err < 0 then the socket should @@ -923,6 +932,8 @@ int udp_err(struct sk_buff *skb, u32 info) int harderr; int err; + udp_err_update_exception(net, skb, type, code, info); + uh = (struct udphdr *)(skb->data + (iph->ihl << 2)); sk = __udp4_lib_lookup(net, iph->daddr, uh->dest, iph->saddr, uh->source, skb->dev->ifindex, From ac76cab50e899a7346408b8d3c3a4192c2eefb9f Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Fri, 28 Aug 2026 22:23:43 +0300 Subject: [PATCH 0373/2366] ipv6: udp: Create exceptions before socket matching Currently, when ICMPv6 Packet Too Big and Redirect Message packets are locally delivered and quote a UDP packet, an exception is only created in the IPv6 exception cache if the kernel can match the UDP packet to an existing socket. This behavior allows off-path attackers to conduct a side-channel attack on the exception cache in order to discover the ephemeral port used by a connected UDP socket. Commit 4785305c05b2 ("ipv6: use siphash in rt6_exception_hash()") and commit a00df2caffed ("ipv6: make exception cache less predictible") tried to mitigate such attacks by making it harder for attackers to discover hash collisions in the exception cache and by randomizing the number of exceptions a hash bucket can hold, respectively. Unfortunately, both of the mitigations can be bypassed. Instead, mitigate such attacks by always creating an exception, even before trying to find a matching socket. Do that by calling ip6_update_pmtu() and ip6_redirect(), the helpers used when the quoted packet did not originate from a socket. This means that guesses (right or wrong) from an off-path attacker will always result in an exception being created or updated in the cache that the attacker can observe. Pass the ifindex of the ingress device and the default uid, in a similar fashion to icmpv6_err(). Unlike IPv4, an oif of 0 would not match any nexthop in ip6_redirect_nh_match() and no exception would be created in response to a Redirect Message. Note that this does not allow attackers to create exceptions that they could not create before, as both helpers can already be reached with little to no validation. For example, by sending an ICMPv6 error that quotes an ICMPv6 Echo Reply or one that quotes a UDP source port that matches a wildcard socket. Also note that in the good case (matched socket) the above scheme comes at the cost of an extra route lookup, as the no socket helpers perform their own lookup before the one performed by ip6_sk_update_pmtu() / ip6_sk_redirect(). When the two resolve to different nexthops, it also results in two exceptions being created for the same destination IP. One in the exception cache of the nexthop resolved by the no socket helpers and another in the exception cache of the nexthop used by the socket. Fixes: 2b760fcf5cfb ("ipv6: hook up exception table to store dst cache") Cc: stable@vger.kernel.org Reported-by: Amit Klein Reported-by: Noam Caspi Signed-off-by: Ido Schimmel Reviewed-by: David Ahern Link: https://patch.msgid.link/20260828192344.2596928-4-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv6/udp.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index fd875908ac0c..93478d1ad576 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c @@ -690,6 +690,17 @@ static struct sock *__udp6_lib_err_encap(struct net *net, return sk; } +static void udpv6_err_update_exception(struct net *net, struct sk_buff *skb, + u8 type, __be32 info) +{ + if (type == ICMPV6_PKT_TOOBIG) + ip6_update_pmtu(skb, net, info, skb->dev->ifindex, 0, + sock_net_uid(net, NULL)); + else if (type == NDISC_REDIRECT) + ip6_redirect(skb, net, skb->dev->ifindex, 0, + sock_net_uid(net, NULL)); +} + static int udpv6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, u8 type, u8 code, int offset, __be32 info) { @@ -703,6 +714,8 @@ static int udpv6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, int harderr; int err; + udpv6_err_update_exception(net, skb, type, info); + daddr = seg6_get_daddr(skb, opt) ? : &hdr->daddr; saddr = &hdr->saddr; sk = __udp6_lib_lookup(net, daddr, uh->dest, saddr, uh->source, From c923c14942b164cfc2c1efa4e6324214f2fc248a Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Fri, 28 Aug 2026 22:23:44 +0300 Subject: [PATCH 0374/2366] selftests: net: Add exception cache tests Add a test for the IPv4 and IPv6 exception caches, covering the exceptions that are created in response to ICMP errors quoting a UDP packet. The topology consists of a host (h1) that reaches a remote host (h2) via a router (r1), with a second router (r2) attached to the segment shared by h1 and r1. UDP packets are injected using a packet socket, so that an ICMP error quoting them is only matched to a socket when one was opened separately with the same source port. PMTU errors are provoked by lowering the MTU of the far end of the path and redirects by pointing r1's route towards h2 back over the segment it received the packet from. The following is tested for both address families and for both PMTU and redirect exceptions: * An error that is not matched to a socket creates an exception that carries the new MTU or gateway. * An error that is matched to a socket creates the same exception. The PMTU tests further verify that a lower PMTU replaces the one stored in the exception whereas a higher one does not, and that a socket which disabled PMTU discovery using IP{,V6}_PMTUDISC_OMIT gets the same exception as the other cases. Without "ipv4: udp: Create exceptions before socket matching" and "ipv6: udp: Create exceptions before socket matching", the tests that do not open a socket fail: # ./exception_cache.sh TEST: IPv4: PMTU: exception without a matching socket [FAIL] No socket: exception does not carry an MTU of 1400 TEST: IPv6: PMTU: exception without a matching socket [FAIL] No socket: exception does not carry an MTU of 1400 TEST: IPv4: PMTU: exception with a matching socket [ OK ] TEST: IPv6: PMTU: exception with a matching socket [ OK ] TEST: IPv4: PMTU: exception with a socket ignoring it [FAIL] PMTU discovery disabled: exception does not carry an MTU of 1400 TEST: IPv6: PMTU: exception with a socket ignoring it [FAIL] PMTU discovery disabled: exception does not carry an MTU of 1400 TEST: IPv4: Redirect: exception without a matching socket [FAIL] No socket: exception does not carry the new gateway TEST: IPv6: Redirect: exception without a matching socket [FAIL] No socket: exception does not carry the new gateway TEST: IPv4: Redirect: exception with a matching socket [ OK ] TEST: IPv6: Redirect: exception with a matching socket [ OK ] Signed-off-by: Ido Schimmel Link: https://patch.msgid.link/20260828192344.2596928-5-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/Makefile | 1 + .../testing/selftests/net/exception_cache.sh | 521 ++++++++++++++++++ 2 files changed, 522 insertions(+) create mode 100755 tools/testing/selftests/net/exception_cache.sh diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 0f5c178bc224..517c09d60bef 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -28,6 +28,7 @@ TEST_PROGS := \ double_udp_encap.sh \ drop_monitor_tests.sh \ ecmp_rehash.sh \ + exception_cache.sh \ fcnal-ipv4.sh \ fcnal-ipv6.sh \ fcnal-other.sh \ diff --git a/tools/testing/selftests/net/exception_cache.sh b/tools/testing/selftests/net/exception_cache.sh new file mode 100755 index 000000000000..8d3eed5c532a --- /dev/null +++ b/tools/testing/selftests/net/exception_cache.sh @@ -0,0 +1,521 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Test that the state of the route exception cache after an ICMP error is +# processed does not depend on whether the quoted packet was matched to a +# socket. Otherwise, an off-path attacker can probe the cache to discover the +# ephemeral port used by a connected UDP socket. +# +# When the quoted packet is not matched to a socket, the same exception is +# created as when it is matched, so that neither its presence nor its contents +# reveal the result of socket matching. +# +# +----+ +# +---------| r1 | +# | +----+ +# +----+ +--------+ | .1 +# | h1 |---| bridge | | 198.51.100.0/30 +# +----+ +--------+ | 2001:db8:2::/64 +# .1 | | .2 +# | +----+ +----+ +# +---------| r2 |-----------| h2 | +# .2 .3 +----+ .1 .2 +----+ +# 203.0.113.0/24 +# 2001:db8:3::/64 +# 192.0.2.0/24 +# 2001:db8:1::/64 +# +# Traffic from h1 to h2 is routed via r1, which reaches h2's network via r2 +# over the point-to-point link. The MTU of the r2 - h2 link is lowered so that +# r2 emits ICMP errors towards h1. +# +# For the redirect tests r1's route to h2's network is replaced with one via r2 +# on the shared segment, so that r1 forwards the packet back to the segment it +# arrived from and emits a redirect towards h1. +# +# The packets that provoke the ICMP errors are injected with a packet socket so +# that no socket is ever associated with them. A socket is created separately, +# with socat, when a test needs the ICMP error to be matched. + +# shellcheck disable=SC1091,SC2034,SC2154,SC2329 +source lib.sh + +require_command jq +require_command mausezahn +require_command nstat +require_command socat + +ALL_TESTS=" + pmtu_no_socket_ipv4 + pmtu_no_socket_ipv6 + pmtu_socket_ipv4 + pmtu_socket_ipv6 + pmtu_omit_ipv4 + pmtu_omit_ipv6 + redirect_no_socket_ipv4 + redirect_no_socket_ipv6 + redirect_socket_ipv4 + redirect_socket_ipv6 +" + +# Shared segment. +H1_ADDR4=192.0.2.1 +R1_ADDR4=192.0.2.2 +R2_ADDR4=192.0.2.3 +H1_ADDR6=2001:db8:1::1 +R1_ADDR6=2001:db8:1::2 +R2_ADDR6=2001:db8:1::3 + +# r1 - r2 link. +R2_R1_ADDR4=198.51.100.2 +R2_R1_ADDR6=2001:db8:2::2 + +# r2 - h2 link. +H2_ADDR4=203.0.113.2 +H2_NET4=203.0.113.0/24 +H2_ADDR6=2001:db8:3::2 +H2_NET6=2001:db8:3::/64 + +SPORT=12345 +DPORT=54321 + +# The MTU of the shared segment and of the r1 - r2 link. Large enough for the +# injected packets to reach r2 intact. +SEGMENT_MTU=2000 +# Size of the injected packets. The PMTU tests need a size that exceeds every +# MTU used for the r2 - h2 link, so that r2 responds with an ICMP error. The +# redirect tests need a size that does not, otherwise r2 would respond with an +# ICMP error in addition to the redirect emitted by r1. +PMTU_PACKET_SIZE=1800 +REDIRECT_PACKET_SIZE=100 + +# The MTUs used for the r2 - h2 link. All of them must be at least +# IPV6_MIN_MTU, otherwise IPv6 silently ignores the error instead of creating +# an exception. +MTU_MID=1400 +MTU_LOW=1300 + +# Values for the IP{,V6}_MTU_DISCOVER socket option. +PMTUDISC_DONT=0 +PMTUDISC_OMIT=5 + +SOCAT_PID= + +linklocal_get() +{ + local ns=$1; shift + local dev=$1; shift + + ip -n "$ns" -j -6 addr show dev "$dev" | \ + jq -r '.[]["addr_info"][] | select(.scope == "link") | .local' +} + +linklocal_exists() +{ + local ns=$1; shift + local dev=$1; shift + + [ -n "$(linklocal_get "$ns" "$dev")" ] +} + +family_vars_set() +{ + local family=$1; shift + + FAMILY=$family + + if [ "$family" -eq 4 ]; then + H1_ADDR=$H1_ADDR4 + H2_ADDR=$H2_ADDR4 + MZ_FAMILY_OPT=() + # Without the Don't Fragment bit set r2 fragments the packet + # instead of reporting the MTU of the next hop. + MZ_IP_OPTS="df," + SOCAT_DST="UDP4-CONNECT:$H2_ADDR4:$DPORT" + SOCAT_BIND="bind=$H1_ADDR4:$SPORT" + SOCAT_PMTUDISC="ip-mtu-discover" + else + H1_ADDR=$H1_ADDR6 + H2_ADDR=$H2_ADDR6 + MZ_FAMILY_OPT=(-6) + MZ_IP_OPTS= + SOCAT_DST="UDP6-CONNECT:[$H2_ADDR6]:$DPORT" + SOCAT_BIND="bind=[$H1_ADDR6]:$SPORT" + SOCAT_PMTUDISC="ipv6-mtu-discover" + fi +} + +topology_setup() +{ + local ns + + setup_ns h1 r1 r2 h2 sw + defer cleanup_all_ns + + # Link-local addresses are generated from the MAC address and read + # back during setup, so request that generation mode explicitly and + # make the addresses available as soon as the devices are brought up. + for ns in "$h1" "$r1" "$r2" "$h2" "$sw"; do + ip netns exec "$ns" sysctl -qw \ + net.ipv6.conf.default.addr_gen_mode=0 \ + net.ipv6.conf.default.accept_dad=0 \ + net.ipv6.conf.all.accept_dad=0 + done + + ip -n "$sw" link add name br0 type bridge + ip -n "$sw" link set dev br0 mtu "$SEGMENT_MTU" up + + ip -n "$h1" link add name eth0 mtu "$SEGMENT_MTU" type veth \ + peer name swp1 mtu "$SEGMENT_MTU" netns "$sw" + ip -n "$r1" link add name eth0 mtu "$SEGMENT_MTU" type veth \ + peer name swp2 mtu "$SEGMENT_MTU" netns "$sw" + ip -n "$r2" link add name eth0 mtu "$SEGMENT_MTU" type veth \ + peer name swp3 mtu "$SEGMENT_MTU" netns "$sw" + ip -n "$r1" link add name eth1 mtu "$SEGMENT_MTU" type veth \ + peer name eth1 mtu "$SEGMENT_MTU" netns "$r2" + ip -n "$r2" link add name eth2 type veth peer name eth0 netns "$h2" + + ip -n "$sw" link set dev swp1 master br0 up + ip -n "$sw" link set dev swp2 master br0 up + ip -n "$sw" link set dev swp3 master br0 up + + ip -n "$h1" link set dev eth0 up + ip -n "$r1" link set dev eth0 up + ip -n "$r1" link set dev eth1 up + ip -n "$r2" link set dev eth0 up + ip -n "$r2" link set dev eth1 up + ip -n "$r2" link set dev eth2 up + ip -n "$h2" link set dev eth0 up + + ip -n "$h1" address add "$H1_ADDR4/24" dev eth0 + ip -n "$r1" address add "$R1_ADDR4/24" dev eth0 + ip -n "$r2" address add "$R2_ADDR4/24" dev eth0 + ip -n "$r1" address add 198.51.100.1/30 dev eth1 + ip -n "$r2" address add "$R2_R1_ADDR4/30" dev eth1 + ip -n "$r2" address add 203.0.113.1/24 dev eth2 + ip -n "$h2" address add "$H2_ADDR4/24" dev eth0 + + ip -n "$h1" -6 address add "$H1_ADDR6/64" dev eth0 nodad + ip -n "$r1" -6 address add "$R1_ADDR6/64" dev eth0 nodad + ip -n "$r2" -6 address add "$R2_ADDR6/64" dev eth0 nodad + ip -n "$r1" -6 address add 2001:db8:2::1/64 dev eth1 nodad + ip -n "$r2" -6 address add "$R2_R1_ADDR6/64" dev eth1 nodad + ip -n "$r2" -6 address add 2001:db8:3::1/64 dev eth2 nodad + ip -n "$h2" -6 address add "$H2_ADDR6/64" dev eth0 nodad + + ip netns exec "$r1" sysctl -qw net.ipv4.ip_forward=1 + ip netns exec "$r1" sysctl -qw net.ipv4.conf.all.send_redirects=1 + ip netns exec "$r1" sysctl -qw net.ipv6.conf.all.forwarding=1 + ip netns exec "$r2" sysctl -qw net.ipv4.ip_forward=1 + ip netns exec "$r2" sysctl -qw net.ipv6.conf.all.forwarding=1 + + ip netns exec "$h1" sysctl -qw net.ipv4.conf.all.accept_redirects=1 + ip netns exec "$h1" sysctl -qw net.ipv4.conf.eth0.accept_redirects=1 + ip netns exec "$h1" sysctl -qw net.ipv6.conf.all.accept_redirects=1 + ip netns exec "$h1" sysctl -qw net.ipv6.conf.eth0.accept_redirects=1 + + slowwait 5 linklocal_exists "$r1" eth0 + check_err $? "r1: link-local address was not generated" + slowwait 5 linklocal_exists "$r2" eth0 + check_err $? "r2: link-local address was not generated" + + R1_LLADDR=$(linklocal_get "$r1" eth0) + R2_LLADDR=$(linklocal_get "$r2" eth0) + R1_MAC=$(ip -n "$r1" -j link show dev eth0 | jq -r '.[]["address"]') + R2_MAC=$(ip -n "$r2" -j link show dev eth0 | jq -r '.[]["address"]') + + ip -n "$h1" route add "$H2_NET4" via "$R1_ADDR4" dev eth0 + ip -n "$h1" -6 route add "$H2_NET6" via "$R1_LLADDR" dev eth0 + ip -n "$r1" route add "$H2_NET4" via "$R2_R1_ADDR4" dev eth1 + ip -n "$r1" -6 route add "$H2_NET6" via "$R2_R1_ADDR6" dev eth1 + ip -n "$h2" route add default via 203.0.113.1 dev eth0 + ip -n "$h2" -6 route add default via 2001:db8:3::1 dev eth0 + + far_mtu_set "$MTU_MID" +} + +# Make r1 forward towards h2's network over the segment it receives the packet +# from, so that it emits a redirect towards h1. +redirect_route_set() +{ + ip -n "$r1" route replace "$H2_NET4" via "$R2_ADDR4" dev eth0 + ip -n "$r1" -6 route replace "$H2_NET6" via "$R2_LLADDR" dev eth0 + + # __ip_do_redirect() only creates an exception if the new gateway is + # already a valid neighbour. Otherwise it merely triggers address + # resolution. IPv6 resolves the target itself, in rt6_do_redirect(). + ip -n "$h1" neigh replace "$R2_ADDR4" lladdr "$R2_MAC" dev eth0 \ + nud permanent +} + +far_mtu_set() +{ + local mtu=$1; shift + + ip -n "$r2" link set dev eth2 mtu "$mtu" + ip -n "$h2" link set dev eth0 mtu "$mtu" +} + +socket_is_open() +{ + ip netns exec "$h1" ss -uHn "sport = :$SPORT" | grep -q . +} + +socket_start() +{ + # Disable PMTU discovery by default so that ICMP errors are not + # reported to the socket. Otherwise socat would exit when the first one + # arrives and later packets in the same test would not be matched to a + # socket. The exception is still created, as ip{,6}_sk_accept_pmtu() + # only rejects IP{,V6}_PMTUDISC_{INTERFACE,OMIT}. + local pmtudisc=${1:-$PMTUDISC_DONT} + + # Send socat's diagnostics to /dev/null. It reports the ICMP errors + # that reach the socket, which is exactly what the tests provoke. + ip netns exec "$h1" socat -u -lf/dev/null \ + "$SOCAT_DST,$SOCAT_BIND,$SOCAT_PMTUDISC=$pmtudisc" \ + OPEN:/dev/null,wronly=1 & + SOCAT_PID=$! + defer socket_stop + + slowwait 5 socket_is_open + check_err $? "socket did not open" +} + +socket_stop() +{ + [ -z "$SOCAT_PID" ] && return 0 + + kill "$SOCAT_PID" &> /dev/null + wait "$SOCAT_PID" 2> /dev/null + SOCAT_PID= +} + +# Inject a packet towards h2 with a packet socket. No socket is associated with +# it, so an ICMP error quoting it is matched to a socket only if one was +# created separately with the same source port. +packet_send() +{ + local size=$1; shift + + ip netns exec "$h1" mausezahn "${MZ_FAMILY_OPT[@]}" eth0 \ + -a own -b "$R1_MAC" -A "$H1_ADDR" -B "$H2_ADDR" \ + -t udp "${MZ_IP_OPTS}sp=$SPORT,dp=$DPORT" \ + -p "$size" -c 1 -q +} + +exception_show() +{ + if [ "$FAMILY" -eq 4 ]; then + # IPv4 exceptions without a bound route are not dumped, but + # "route get" reports the exception and binds a route to it. + ip -n "$h1" route get "$H2_ADDR" + else + # IPv6 does not report a cache indication in "route get" + # output, so dump the exceptions instead. + ip -n "$h1" -6 route show cache | grep -F "$H2_ADDR" || true + fi +} + +exception_mtu_get() +{ + exception_show | grep -o "mtu [0-9]*" | cut -d ' ' -f 2 +} + +exception_gw_get() +{ + exception_show | grep -o "via [0-9a-f.:]*" | cut -d ' ' -f 2 +} + +exception_mtu_check() +{ + local expected=$1; shift + + [ "$(exception_mtu_get)" = "$expected" ] +} + +icmp_errors_get() +{ + local ctr=IcmpInDestUnreachs + + [ "$FAMILY" -eq 6 ] && ctr=Icmp6InPktTooBigs + + ip netns exec "$h1" nstat -asz "$ctr" | \ + awk -v ctr="$ctr" '$1 == ctr { print $2 }' +} + +exception_pmtu_check() +{ + local mtu=$1; shift + local desc=$1; shift + + busywait "$BUSYWAIT_TIMEOUT" exception_mtu_check "$mtu" + check_err $? "$desc: exception does not carry an MTU of $mtu" +} + +pmtu_no_socket() +{ + local family=$1; shift + + RET=0 + family_vars_set "$family" + topology_setup + + packet_send "$PMTU_PACKET_SIZE" + exception_pmtu_check "$MTU_MID" "No socket" + + log_test "IPv$family: PMTU: exception without a matching socket" +} + +pmtu_no_socket_ipv4() +{ + pmtu_no_socket 4 +} + +pmtu_no_socket_ipv6() +{ + pmtu_no_socket 6 +} + +pmtu_socket() +{ + local family=$1; shift + local t0 + + RET=0 + family_vars_set "$family" + topology_setup + socket_start + + packet_send "$PMTU_PACKET_SIZE" + exception_pmtu_check "$MTU_MID" "Matching socket" + + # A lower PMTU replaces the one currently stored in the exception. + far_mtu_set "$MTU_LOW" + packet_send "$PMTU_PACKET_SIZE" + exception_pmtu_check "$MTU_LOW" "Lower PMTU" + + # A higher PMTU is ignored, so the exception is left as it is. Wait + # for the error to be received, as otherwise the check below would + # pass even if it never was. + far_mtu_set "$MTU_MID" + t0=$(icmp_errors_get) + packet_send "$PMTU_PACKET_SIZE" + busywait "$BUSYWAIT_TIMEOUT" until_counter_is ">= $((t0 + 1))" \ + icmp_errors_get > /dev/null + check_err $? "Higher PMTU: ICMP error was not received" + + exception_mtu_check "$MTU_LOW" + check_err $? "Higher PMTU: exception does not carry an MTU of $MTU_LOW" + + log_test "IPv$family: PMTU: exception with a matching socket" +} + +pmtu_socket_ipv4() +{ + pmtu_socket 4 +} + +pmtu_socket_ipv6() +{ + pmtu_socket 6 +} + +pmtu_omit() +{ + local family=$1; shift + + RET=0 + family_vars_set "$family" + topology_setup + socket_start "$PMTUDISC_OMIT" + + packet_send "$PMTU_PACKET_SIZE" + exception_pmtu_check "$MTU_MID" "PMTU discovery disabled" + + log_test "IPv$family: PMTU: exception with a socket ignoring it" +} + +pmtu_omit_ipv4() +{ + pmtu_omit 4 +} + +pmtu_omit_ipv6() +{ + pmtu_omit 6 +} + +exception_gw_check() +{ + local expected=$1; shift + + [ -n "$expected" ] && [ "$(exception_gw_get)" = "$expected" ] +} + +redirect_gw_new() +{ + if [ "$FAMILY" -eq 4 ]; then + echo "$R2_ADDR4" + else + echo "$R2_LLADDR" + fi +} + +redirect_no_socket() +{ + local family=$1; shift + + RET=0 + family_vars_set "$family" + topology_setup + redirect_route_set + + packet_send "$REDIRECT_PACKET_SIZE" + busywait "$BUSYWAIT_TIMEOUT" exception_gw_check "$(redirect_gw_new)" + check_err $? "No socket: exception does not carry the new gateway" + + log_test "IPv$family: Redirect: exception without a matching socket" +} + +redirect_no_socket_ipv4() +{ + redirect_no_socket 4 +} + +redirect_no_socket_ipv6() +{ + redirect_no_socket 6 +} + +redirect_socket() +{ + local family=$1; shift + + RET=0 + family_vars_set "$family" + topology_setup + redirect_route_set + socket_start + + packet_send "$REDIRECT_PACKET_SIZE" + busywait "$BUSYWAIT_TIMEOUT" exception_gw_check "$(redirect_gw_new)" + check_err $? "Matching socket: exception does not carry the new gateway" + + log_test "IPv$family: Redirect: exception with a matching socket" +} + +redirect_socket_ipv4() +{ + redirect_socket 4 +} + +redirect_socket_ipv6() +{ + redirect_socket 6 +} + +trap defer_scopes_cleanup EXIT +tests_run + +exit "$EXIT_STATUS" From b3b76e9f4f2476f1135b2ba7743a821db4a0df4b Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Thu, 27 Aug 2026 18:13:46 +0700 Subject: [PATCH 0375/2366] tipc: fix NULL deref in tipc_named_node_up() on empty publication list User-space applications can bind a large number of service addresses to one or more sockets. Each binding of a local-scope service address inserts one entry (publication) into the TIPC name table. If the number of these publications exceeds TIPC_MAX_PUBL (65535), protocol service types (such as node state and link state) are no longer inserted into the name table. This causes two issues: 1. User-space applications subscribing to node or link up/down events stop receiving notifications. 2. A NULL pointer dereference can occur: BUG: kernel NULL pointer dereference, address: 00000000000000d0 ... CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.2.0-rc4-default+ #5 PREEMPT(full) ... RIP: 0010:tipc_named_node_up (./include/linux/skbuff.h:2251 net/tipc/name_distr.c:195 net/tipc/name_distr.c:221) ... Call Trace: tipc_node_write_unlock (net/tipc/node.c:428) tipc_rcv (net/tipc/node.c:934 net/tipc/node.c:2189) tipc_udp_recv (net/tipc/udp_media.c:389) Thread 1 (tipc_net_finalize) | Thread 2 (named_distribute) -----------------------------|----------------------------- | ... | list_for_each_entry(publ, pls, binding_node) { | ... | __skb_queue_tail(list, skb); | ... | } | ... | hdr = buf_msg(skb_peek_tail(list)); ... | tipc_nametbl_publish(); | If 'tipc_nametbl_publish()' (Thread 1) fails because the number of local publications reaches TIPC_MAX_PUBL, list (Thread 2) will be empty. As a result, NULL is passed to 'buf_msg()', leading to a NULL pointer dereference. Fix these issues by allowing protocol service types (node state, link state, and topology server) to be inserted into the name table unconditionally. This ensures that users subscribing to these types always receive notifications. In addition, the maximum number of local user publications is reduced to (TIPC_MAX_PUBL - 1). This ensures that the maximum bulk size calculated in tipc_link_set_queue_limits() remains valid. Fixes: a5e7ac5ce134 ("tipc: fix regression bug where node events are not being generated") Reported-by: Xiang Mei Tested-by: Weiming Shi Signed-off-by: Tung Nguyen Link: https://patch.msgid.link/20260827111418.164957-1-tung.quang.nguyen@est.tech Signed-off-by: Jakub Kicinski --- net/tipc/name_table.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/net/tipc/name_table.c b/net/tipc/name_table.c index 253c72d1366e..6fda36ab1766 100644 --- a/net/tipc/name_table.c +++ b/net/tipc/name_table.c @@ -763,21 +763,40 @@ struct publication *tipc_nametbl_publish(struct net *net, struct tipc_uaddr *ua, struct tipc_socket_addr *sk, u32 key) { struct name_table *nt = tipc_name_table(net); + u32 max_user_pub = TIPC_MAX_PUBL - 1; struct tipc_net *tn = tipc_net(net); struct publication *p = NULL; struct sk_buff *skb = NULL; + bool protocol_type = false; u32 rc_dests; - spin_lock_bh(&tn->nametbl_lock); + if (ua->sr.type == TIPC_NODE_STATE || ua->sr.type == TIPC_LINK_STATE || + ua->sr.type == TIPC_TOP_SRV) + protocol_type = true; - if (nt->local_publ_count >= TIPC_MAX_PUBL) { - pr_warn("Bind failed, max limit %u reached\n", TIPC_MAX_PUBL); + spin_lock_bh(&tn->nametbl_lock); + if (protocol_type) + goto insert; + + /* Reserve one entry for node state service type because it has cluster + * scope and it is distributed in bulk. So, the maximum number of user's + * publications is (TIPC_MAX_PUBL - 1). + */ + if (nt->local_publ_count >= max_user_pub) { + pr_warn("Bind failed, max limit %u reached\n", max_user_pub); goto exit; } +insert: p = tipc_nametbl_insert_publ(net, ua, sk, key); if (p) { - nt->local_publ_count++; + /* Not count node state, link state and topology server types + * so that maximum nt->local_publ_count does not prevent + * protocol service types from being inserted into the name + * table. + */ + if (!protocol_type) + nt->local_publ_count++; skb = tipc_named_publish(net, p); } rc_dests = nt->rc_dests; @@ -810,7 +829,10 @@ void tipc_nametbl_withdraw(struct net *net, struct tipc_uaddr *ua, p = tipc_nametbl_remove_publ(net, ua, sk, key); if (p) { - nt->local_publ_count--; + if (p->sr.type != TIPC_NODE_STATE && + p->sr.type != TIPC_LINK_STATE && + p->sr.type != TIPC_TOP_SRV) + nt->local_publ_count--; skb = tipc_named_withdraw(net, p); list_del_init(&p->binding_sock); kfree_rcu(p, rcu); From 81c600c26302a27852ed8b19c5f2f647ea3555c9 Mon Sep 17 00:00:00 2001 From: David Laight Date: Sat, 29 Aug 2026 12:58:12 +0100 Subject: [PATCH 0376/2366] tipc: Dont send random pad bytes in RESET/ACTIVATE messages The interface name is passed in a fixed length (TIPC_MAX_IF_NAME) buffer. Replace the strcpy(data, l->if_name) with memcpy() so that the pad bytes are actually written (l->if_name[] is zero padded) rather than sending random bytes from the skb to the remote system. Replace two other strcpy() with strscpy(). Fixes: e74a386d70c7 ("tipc: remove pre-allocated message header in link struct") Signed-off-by: David Laight Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/20260829115813.188600-1-david.laight.linux@gmail.com Signed-off-by: Jakub Kicinski --- net/tipc/link.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/tipc/link.c b/net/tipc/link.c index 49dfc098d89b..6427c69f8929 100644 --- a/net/tipc/link.c +++ b/net/tipc/link.c @@ -504,7 +504,7 @@ bool tipc_link_create(struct net *net, char *if_name, int bearer_id, snprintf(l->name, sizeof(l->name), "%s:%s-%s:unknown", self_str, if_name, peer_str); - strcpy(l->if_name, if_name); + strscpy(l->if_name, if_name); l->addr = peer; l->peer_caps = peer_caps; l->net = net; @@ -574,7 +574,7 @@ bool tipc_link_bc_create(struct net *net, u32 ownnode, u32 peer, u8 *peer_id, snprintf(l->name, sizeof(l->name), "%s:%s", tipc_bclink_name, peer_str); } else { - strcpy(l->name, tipc_bclink_name); + strscpy(l->name, tipc_bclink_name); } trace_tipc_link_reset(l, TIPC_DUMP_ALL, "bclink created!"); tipc_link_reset(l); @@ -1898,7 +1898,7 @@ static void tipc_link_build_proto_msg(struct tipc_link *l, int mtyp, bool probe, msg_set_dest_session(hdr, l->peer_session); } msg_set_max_pkt(hdr, l->advertised_mtu); - strcpy(data, l->if_name); + memcpy(data, l->if_name, TIPC_MAX_IF_NAME); msg_set_size(hdr, INT_H_SIZE + TIPC_MAX_IF_NAME); skb_trim(skb, INT_H_SIZE + TIPC_MAX_IF_NAME); } From 975b5b067f525a1b1338c4a3bee1c46545801518 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 14:17:27 +0000 Subject: [PATCH 0377/2366] ipv6: sr: restore network header before routing and forwarding ipv6_srh_rcv() runs with skb->data at the Segment Routing Header (SRH) while skb_network_header() points at the IPv6 header. When segments_left > 0, ipv6_srh_rcv() previously restored the skb->data position by pushing sizeof(struct ipv6hdr), assuming the SRH immediately followed the fixed IPv6 header. If another extension header (such as a Hop-by-Hop options header) precedes the SRH, skb_network_offset() remained negative. This led to two problems: 1. During ip6_route_input(), fib6_rules_early_flow_dissect() invokes __skb_flow_dissect() which passes the negative skb_network_offset() to flow dissection, breaking BPF and C flow dissector logic. 2. If forwarded via ip6_forward() or redirected via act_mirred, downstream handlers (like sch_fragment() or neighbour output) pass the negative offset as an unsigned length, triggering OOB memcpy or buffer overflows. Fix this by pushing -skb_network_offset(skb) before routing, ensuring skb_network_offset(skb) is 0 for route lookup / flow dissection as well as downstream forwarding. On the loopback path, pull skb_transport_offset(skb) to restore skb->data to the SRH before looping back. Fixes: 1ababeba4a21 ("ipv6: implement dataplane support for rthdr type 4 (Segment Routing Header)") Reported-by: TencentOS Corvus AI Reported-by: Jun Yang Reported-by: Fourie Zhang Closes: https://lore.kernel.org/netdev/20260817104128.22681-1-juny24602@gmail.com/ Closes: https://lore.kernel.org/netdev/20260827092345.2301937-1-fouriezhang@tencent.com/ Signed-off-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260828141727.2372570-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/exthdrs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 51941ad656a3..09a4552f7f08 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -445,7 +445,7 @@ static int ipv6_srh_rcv(struct sk_buff *skb, struct inet6_dev *idev) hdr->segments_left--; addr = hdr->segments + hdr->segments_left; - skb_push(skb, sizeof(struct ipv6hdr)); + skb_push(skb, -skb_network_offset(skb)); if (skb->ip_summed == CHECKSUM_COMPLETE) seg6_update_csum(skb); @@ -469,7 +469,7 @@ static int ipv6_srh_rcv(struct sk_buff *skb, struct inet6_dev *idev) } ipv6_hdr(skb)->hop_limit--; - skb_pull(skb, sizeof(struct ipv6hdr)); + skb_pull(skb, skb_transport_offset(skb)); goto looped_back; } From 545b63503c696c4ce0663b3fcd37f41169aec1eb Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 31 Aug 2026 00:16:17 +0900 Subject: [PATCH 0378/2366] net: ntb_netdev: Fix statistics races ntb_netdev updates shared net_device stats from per-QP RX and TX callbacks. Once multiple queues are enabled, concurrent updates can be lost. Use per-CPU tstats for packet and byte counters and DEV_STATS_INC() for less frequent drop and error counters. Callbacks can run synchronously in the xmit path or asynchronously from a tasklet or the memcpy kthread. Pin TX updates against migration in the kthread path. Use the IRQ-safe u64_stats helpers because netpoll can invoke the synchronous path with IRQs disabled. Let the core manage tstats while keeping transport teardown after unregister_netdev(), outside RTNL. RCU lets unregister wait for TX completions already updating stats, while later completions only consume the skb and skip accounting and queue wake. Fixes: 24d9e73c7e00 ("net: ntb_netdev: Support ethtool channels for multi-queue") Cc: stable@vger.kernel.org Suggested-by: Jakub Kicinski Signed-off-by: Koichiro Den Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260830151617.3546585-1-den@valinux.co.jp Signed-off-by: Jakub Kicinski --- drivers/net/ntb_netdev.c | 47 +++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c index 9c171697e762..2c04be6d61a8 100644 --- a/drivers/net/ntb_netdev.c +++ b/drivers/net/ntb_netdev.c @@ -127,8 +127,10 @@ static void ntb_netdev_rx_handler(struct ntb_transport_qp *qp, void *qp_data, { struct ntb_netdev_queue *q = qp_data; struct ntb_netdev *dev = q->ntdev; + struct pcpu_sw_netstats *tstats; struct sk_buff *skb, *new_skb; struct net_device *ndev; + unsigned long flags; int rc; ndev = dev->ndev; @@ -139,17 +141,20 @@ static void ntb_netdev_rx_handler(struct ntb_transport_qp *qp, void *qp_data, netdev_dbg(ndev, "%s: %d byte payload received\n", __func__, len); if (len < 0) { - ndev->stats.rx_errors++; - ndev->stats.rx_length_errors++; + DEV_STATS_INC(ndev, rx_errors); + DEV_STATS_INC(ndev, rx_length_errors); goto enqueue_again; } - ndev->stats.rx_packets++; - ndev->stats.rx_bytes += len; + tstats = this_cpu_ptr(ndev->tstats); + flags = u64_stats_update_begin_irqsave(&tstats->syncp); + u64_stats_inc(&tstats->rx_packets); + u64_stats_add(&tstats->rx_bytes, len); + u64_stats_update_end_irqrestore(&tstats->syncp, flags); new_skb = netdev_alloc_skb(ndev, ndev->mtu + ETH_HLEN); if (!new_skb) { - ndev->stats.rx_dropped++; + DEV_STATS_INC(ndev, rx_dropped); goto enqueue_again; } @@ -166,8 +171,8 @@ static void ntb_netdev_rx_handler(struct ntb_transport_qp *qp, void *qp_data, rc = ntb_transport_rx_enqueue(qp, skb, skb->data, ndev->mtu + ETH_HLEN); if (rc) { dev_kfree_skb_any(skb); - ndev->stats.rx_errors++; - ndev->stats.rx_fifo_errors++; + DEV_STATS_INC(ndev, rx_errors); + DEV_STATS_INC(ndev, rx_fifo_errors); } } @@ -210,25 +215,39 @@ static void ntb_netdev_tx_handler(struct ntb_transport_qp *qp, void *qp_data, { struct ntb_netdev_queue *q = qp_data; struct ntb_netdev *dev = q->ntdev; + struct pcpu_sw_netstats *tstats; struct net_device *ndev; struct sk_buff *skb; + unsigned long flags; + bool registered; ndev = dev->ndev; skb = data; if (!skb || !ndev) return; + rcu_read_lock(); + registered = READ_ONCE(ndev->reg_state) == NETREG_REGISTERED; + if (!registered) + goto free_skb; + if (len > 0) { - ndev->stats.tx_packets++; - ndev->stats.tx_bytes += skb->len; + /* The memcpy kthread can migrate, so pin the per-CPU update. */ + tstats = get_cpu_ptr(ndev->tstats); + flags = u64_stats_update_begin_irqsave(&tstats->syncp); + u64_stats_inc(&tstats->tx_packets); + u64_stats_add(&tstats->tx_bytes, skb->len); + u64_stats_update_end_irqrestore(&tstats->syncp, flags); + put_cpu_ptr(ndev->tstats); } else { - ndev->stats.tx_errors++; - ndev->stats.tx_aborted_errors++; + DEV_STATS_INC(ndev, tx_errors); + DEV_STATS_INC(ndev, tx_aborted_errors); } +free_skb: dev_kfree_skb_any(skb); - if (ntb_transport_tx_free_entry(qp) >= tx_start) { + if (registered && ntb_transport_tx_free_entry(qp) >= tx_start) { /* Make sure anybody stopping the queue after this sees the new * value of ntb_transport_tx_free_entry() */ @@ -237,6 +256,7 @@ static void ntb_netdev_tx_handler(struct ntb_transport_qp *qp, void *qp_data, ntb_transport_link_query(q->qp)) netif_wake_subqueue(ndev, q->qid); } + rcu_read_unlock(); } static const struct ntb_queue_handlers ntb_netdev_handlers = { @@ -277,7 +297,7 @@ static netdev_tx_t ntb_netdev_start_xmit(struct sk_buff *skb, drop: dev_kfree_skb_any(skb); - ndev->stats.tx_dropped++; + DEV_STATS_INC(ndev, tx_dropped); return NETDEV_TX_OK; } @@ -647,6 +667,7 @@ static int ntb_netdev_probe(struct device *client_dev) } ndev->features = NETIF_F_HIGHDMA; + ndev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS; ndev->priv_flags |= IFF_LIVE_ADDR_CHANGE; From 3f9c7a108c0e8f14425384912017071b71341e3b Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Mon, 31 Aug 2026 11:50:50 +0900 Subject: [PATCH 0379/2366] block: flag zoned disks with GENHD_FL_NO_PART Zoned block devices do not support partitions. However, the partition table is nevertheless still inspected, and any partition found ignored with a warning in add_partition(). While this is generally not a problem, and in fact beneficial to the user as it indicates an invalid use of a zoned block device, scanning for a partition table on the device may result in issuing read operations to offline zones (e.g. after a disk head is depopulated for disks that support head management operations). Since partitions are ignored anyway, completely disable partition scanning for zoned gendisks by setting the flag GENHD_FL_NO_PART in __add_disk(). The existing check in add_partition() is left as-is to ensure that we still get a warning if for whatever reason, despite GENHD_FL_NO_PART, we still endup trying to add partitions. Flagging zoned disks with GENHD_FL_NO_PART also has the benefit to expose through sysfs the ext_range attribute with the value of 1 instead of the default DISK_MAX_PARTS, thus correctly advertizing the fact that zoned disks do not support partitions. Fixes: 5eac3eb30c9a ("block: Remove partition support for zoned block devices") Cc: stable@vger.kernel.org Signed-off-by: Damien Le Moal Reviewed-by: Bart Van Assche Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Link: https://patch.msgid.link/20260831025050.667758-1-dlemoal@kernel.org Signed-off-by: Jens Axboe --- block/genhd.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/block/genhd.c b/block/genhd.c index f1990c7cdfb9..10ca8b4d6eea 100644 --- a/block/genhd.c +++ b/block/genhd.c @@ -447,6 +447,13 @@ static int __add_disk(struct device *parent, struct gendisk *disk, bdev_set_flag(disk->part0, BD_HAS_SUBMIT_BIO); } + /* + * We do not support partitions with zoned block devices, so do not try + * to scan the partitions table. + */ + if (blk_queue_is_zoned(disk->queue)) + disk->flags |= GENHD_FL_NO_PART; + /* * If the driver provides an explicit major number it also must provide * the number of minors numbers supported, and those will be used to From 32cd87f54dd1070020e664ccb0312a9f0fea79b4 Mon Sep 17 00:00:00 2001 From: Guoqing Jiang Date: Thu, 27 Aug 2026 20:55:53 +0800 Subject: [PATCH 0380/2366] RDMA/siw: Clear association under lock if siw_qp_modify fails in siw_accept We need to clear cep before release state_lock as siw_qp_llp_close and siw_qp_modify->siw_qp_llp_close did. Otherwise if siw_qp_modify() fails in siw_accept(), the QP's state_lock is released before the error path cleanup. A concurrent ibv_modify_qp() transitioning the QP to ERROR can race in this window: siw_accept() ibv_modify_qp(ERROR) ---------------------- ---------------------- siw_qp_modify() fails up_write(&qp->state_lock) down_write(&qp->state_lock) nextstate_from_idle(): if (qp->cep) siw_cep_put(qp->cep) <- frees cep qp->cep = NULL goto error cep->qp = NULL <- UAF Clear qp->cep and drop the association reference taken by siw_cep_get(), all under the write lock held from the initial down_write(&qp->state_lock). Thread B therefore sees qp->cep == NULL, skips its own put, and cannot free the cep before siw_accept() is done with it. Fixes: 6c52fdc244b5 ("rdma/siw: connection management") Reported-by: Shuangpeng Bai Link: https://lore.kernel.org/linux-rdma/d6fbe475-a5c2-f975-99b0-a0bd6b6d10e8@linux.dev/T/#m5876c1ff2de8686a9a1173b8f1aa0ff5363a785c Signed-off-by: Guoqing Jiang Link: https://patch.msgid.link/20260827125553.12831-1-guoqing.jiang@linux.dev Acked-by: Bernard Metzler Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/siw/siw_cm.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/sw/siw/siw_cm.c b/drivers/infiniband/sw/siw/siw_cm.c index 0245b25e7271..ed49818793dd 100644 --- a/drivers/infiniband/sw/siw/siw_cm.c +++ b/drivers/infiniband/sw/siw/siw_cm.c @@ -1719,9 +1719,12 @@ int siw_accept(struct iw_cm_id *id, struct iw_cm_conn_param *params) SIW_QP_ATTR_STATE | SIW_QP_ATTR_LLP_HANDLE | SIW_QP_ATTR_ORD | SIW_QP_ATTR_IRD | SIW_QP_ATTR_MPA); + if (rv) { + qp->cep = NULL; + siw_cep_put(cep); + goto error_unlock; + } up_write(&qp->state_lock); - if (rv) - goto error; siw_dbg_cep(cep, "[QP %u]: send mpa reply, %d byte pdata\n", qp_id(qp), params->private_data_len); From 412a6ceb56d501ef2f8202e26ab4b5d4dfbca566 Mon Sep 17 00:00:00 2001 From: Zhenhao Wan Date: Tue, 11 Aug 2026 16:46:28 +0800 Subject: [PATCH 0381/2366] drm/nouveau/uvmm: fix NULL deref unwinding an OP_MAP_SPARSE op Each bind_job_op is zeroed by kzalloc_obj() in bind_job_op_from_uop(), and the OP_MAP_SPARSE case in nouveau_uvmm_bind_job_submit() only creates a region, so op->ops stays NULL for a successfully processed sparse map. If a later op in the same job fails, the reverse unwind loop revisits that op and calls drm_gpuva_ops_free(&uvmm->base, op->ops) unconditionally. drm_gpuva_ops_free() dereferences its argument right away (list_for_each_entry_safe on &ops->list), so a NULL op->ops oopses. The path is reachable by any render-node fd holder, since NOUVEAU_VM_BIND is DRM_RENDER_ALLOW. Guard the free with IS_ERR_OR_NULL(), as nouveau_uvmm_bind_job_cleanup() already does for the identical free. Fixes: b88baab82871 ("drm/nouveau: implement new VM_BIND uAPI") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Zhenhao Wan Reviewed-by: Lyude Paul Link: https://patch.msgid.link/20260811-nouveau-uvmm-vmbind-fixes-v2-1-aaee4b395d04@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_uvmm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c index f5e4756b4de4..19e758a20c24 100644 --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c @@ -1489,7 +1489,8 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job, break; } - drm_gpuva_ops_free(&uvmm->base, op->ops); + if (!IS_ERR_OR_NULL(op->ops)) + drm_gpuva_ops_free(&uvmm->base, op->ops); op->ops = NULL; op->reg = NULL; } From ccf930812f23b8259ef64fd3394d53b093e4651a Mon Sep 17 00:00:00 2001 From: Zhenhao Wan Date: Tue, 11 Aug 2026 16:46:29 +0800 Subject: [PATCH 0382/2366] drm/nouveau/uvmm: fix premature region free on failed OP_UNMAP_SPARSE In nouveau_uvmm_bind_job_submit()'s OP_UNMAP_SPARSE arm, op->reg is set from nouveau_uvma_region_find(), which only looks the region up and takes no reference; a region's sole reference is its membership in uvmm->region_mt. Two failure paths leave op->reg set: the -ENOENT check when the region is busy, and the drm_gpuvm_sm_unmap_ops_create() failure. The sibling nouveau_uvmm_sm_unmap_prepare() failure just below clears op->reg; these two do not. unwind_continue steps back one op, so the failing op is skipped by the unwind loop and its op->reg stays set. nouveau_uvmm_bind_job_cleanup() then enters its if (op->reg) branch and calls nouveau_uvma_region_remove() and nouveau_uvma_region_put() on it, dropping the tree's sole reference and freeing a region this job never created. The comment above the cleanup loop documents the broken invariant: op->reg must be NULL on submit failure. This frees a live region on an unrelated failure, reachable single-job when drm_gpuvm_sm_unmap_ops_create() returns -ENOMEM; if another job owns the same region, its cleanup then removes and puts the freed region, a use-after-free. Clear op->reg on both failure paths. Fixes: b88baab82871 ("drm/nouveau: implement new VM_BIND uAPI") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Zhenhao Wan Reviewed-by: Lyude Paul Link: https://patch.msgid.link/20260811-nouveau-uvmm-vmbind-fixes-v2-2-aaee4b395d04@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_uvmm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c index 19e758a20c24..d30ec3709e79 100644 --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c @@ -1319,6 +1319,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job, op->va.range); if (!op->reg || op->reg->dirty) { ret = -ENOENT; + op->reg = NULL; goto unwind_continue; } @@ -1327,6 +1328,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job, op->va.range); if (IS_ERR(op->ops)) { ret = PTR_ERR(op->ops); + op->reg = NULL; goto unwind_continue; } From 38a62306c4266bcb3cd89e33c7111ee33096ebb3 Mon Sep 17 00:00:00 2001 From: Zhenhao Wan Date: Tue, 11 Aug 2026 16:46:30 +0800 Subject: [PATCH 0383/2366] drm/nouveau/uvmm: clear the dirty flag when unwinding an OP_UNMAP_SPARSE A successful OP_UNMAP_SPARSE marks its region dirty with nouveau_uvma_region_dirty() and defers the teardown to nouveau_uvmm_bind_job_cleanup(); it does not remove the region from uvmm->region_mt. If a later op in the job fails, the unwind path never clears reg->dirty (set in one place, cleared nowhere) and sets op->reg = NULL, so cleanup skips the teardown. The region is left in the tree with dirty set and its completion never signalled. Later binds over that range then fail permanently -- -ENOENT or -EINVAL from the dirty checks, or an unkillable wait_for_completion() in bind_validate_region() -- for the lifetime of the uvmm. Clear reg->dirty when the unwind reverts the sparse unmap, restoring the region to the state it was found in. Fixes: b88baab82871 ("drm/nouveau: implement new VM_BIND uAPI") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Zhenhao Wan Reviewed-by: Lyude Paul Link: https://patch.msgid.link/20260811-nouveau-uvmm-vmbind-fixes-v2-3-aaee4b395d04@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_uvmm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c index d30ec3709e79..fc125fd44a9b 100644 --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c @@ -1475,6 +1475,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job, op->va.range); break; case OP_UNMAP_SPARSE: + op->reg->dirty = false; __nouveau_uvma_region_insert(uvmm, op->reg); nouveau_uvmm_sm_unmap_prepare_unwind(uvmm, &op->new, op->ops); From 73e594c19b4f815d8343461cec7074c4713bbde7 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sun, 30 Aug 2026 18:09:12 +0000 Subject: [PATCH 0384/2366] af_packet: Don't cast tpacket_hdr.tp_len to int in tpacket_parse_header(). syzbot reported BUG() in sock_sendmsg_nosec(). [0] The problem is that tpacket_parse_header() casts user-provided tpacket_hdr.tp_len, which is u32, to int. If the length is larger than INT_MAX, the following condition in tpacket_parse_header() passes, if (unlikely(tp_len > size_max)) and any negative value can be returned to the caller, up to sock_sendmsg_nosec(). The repro set tpacket_hdr.tp_len to 0xfffffdef, which is cast to -EIOCBQUEUED (-529), triggering BUG() in sock_sendmsg_nosec(). *(uint64_t*)0x200000000008 = 0xfffffdef; ... syscall(__NR_write, /*fd=*/r[0], /*buf=*/0x200000000000ul, /*count=*/1ul); Let's define the local tp_len as u32 in tpacket_parse_header(). [0]: kernel BUG at net/socket.c:803! Oops: invalid opcode: 0000 [#1] SMP KASAN PTI CPU: 0 UID: 0 PID: 5628 Comm: syz-executor176 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026 RIP: 0010:sock_sendmsg_nosec+0x145/0x180 net/socket.c:803 Code: 06 67 48 0f b9 3a eb 95 e8 e8 3a 22 f8 48 89 df 4c 89 f6 4c 89 e2 4d 89 fb 2e e8 32 a5 5c 16 e9 51 ff ff ff e8 cc 3a 22 f8 90 <0f> 0b e8 c4 3a 22 f8 48 83 c3 18 48 89 d8 48 c1 e8 03 42 80 3c 28 RSP: 0018:ffffc90003aefb48 EFLAGS: 00010293 RAX: ffffffff89a578d4 RBX: ffff8880764c67c0 RCX: ffff88807fb23e80 RDX: 0000000000000000 RSI: 00000000fffffdef RDI: 00000000fffffdef RBP: 00000000fffffdef R08: ffffc90003aef747 R09: 1ffff9200075dee8 R10: dffffc0000000000 R11: fffff5200075dee9 R12: 0000000000000001 R13: dffffc0000000000 R14: ffffc90003aefbc0 R15: ffffffff8aac4310 FS: 000055559101b400(0000) GS:ffff888124ce0000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000200000000210 CR3: 0000000073dca000 CR4: 00000000003526f0 Call Trace: __sock_sendmsg net/socket.c:815 [inline] sock_write_iter+0x2de/0x3e0 net/socket.c:1266 new_sync_write fs/read_write.c:595 [inline] vfs_write+0x612/0xba0 fs/read_write.c:687 ksys_write+0x150/0x270 fs/read_write.c:739 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline] do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f173130ecb9 Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007ffd67e44248 EFLAGS: 00000246 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 0000200000000000 RCX: 00007f173130ecb9 RDX: 0000000000000001 RSI: 0000200000000000 RDI: 0000000000000003 RBP: 0000000000000001 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffd67e44388 R13: 0000000000000002 R14: 00002000000000c0 R15: 0000000000000002 Fixes: 69e3c75f4d54 ("net: TX_RING and packet mmap") Reported-by: syzbot+73df3f89e1e13089e466@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a946ffa.1d9ded08.62e62.0123.GAE@google.com/ Signed-off-by: Kuniyuki Iwashima Reviewed-by: Eric Dumazet Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260830180915.260225-1-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/packet/af_packet.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index b22cda322136..76bde7906d49 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -2675,7 +2675,8 @@ static int tpacket_parse_header(struct packet_sock *po, void *frame, int size_max, void **data) { union tpacket_uhdr ph; - int tp_len, off; + u32 tp_len; + int off; ph.raw = frame; @@ -2695,7 +2696,7 @@ static int tpacket_parse_header(struct packet_sock *po, void *frame, break; } if (unlikely(tp_len > size_max)) { - pr_err("packet size is too long (%d > %d)\n", tp_len, size_max); + pr_err("packet size is too long (%u > %d)\n", tp_len, size_max); return -EMSGSIZE; } From deced5fa01c5e9813384b6c176379e5baaf5ec10 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Tue, 25 Aug 2026 13:06:15 +1000 Subject: [PATCH 0385/2366] nouveau/instmem: handle iomapping already existing Turns out sashiko was right, and I should protect this properly Fixes: 34e27b90552a ("nouveau/instmem: use iomapping interface for instmem handling") Signed-off-by: Dave Airlie Link: https://patch.msgid.link/20260825030615.3464436-1-airlied@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c index f4489efc94a7..22b0fde6ba34 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c @@ -195,6 +195,9 @@ check_io_mapping(struct nv50_instmem *imem) { struct nvkm_device *device = imem->base.subdev.device; + if (imem->iomap.size) + return true; + return io_mapping_init_wc(&imem->iomap, device->func->resource_addr(device, NVKM_BAR2_INST), device->func->resource_size(device, NVKM_BAR2_INST)) != NULL; From caa1bc2a0a6ca19dcb90bbf88208b0fe2decd66f Mon Sep 17 00:00:00 2001 From: Zhenhao Wan Date: Tue, 11 Aug 2026 22:28:50 +0800 Subject: [PATCH 0386/2366] drm/nouveau/dmem: fix mismatched DMA unmap size for large folios Device-private THP migration maps migration buffers with page_size() and records that length in dma_info->size. For a compound folio page_size() is PAGE_SIZE << order, but two teardown sites still pass a literal PAGE_SIZE to dma_unmap_page(): - nouveau_dmem_migrate_to_ram() on the success path, and - nouveau_dmem_migrate_copy_one() on the copy-error path. For an order > 0 folio this unmaps less than was mapped, leaking the remainder of the IOMMU/IOVA mapping. The other unmap sites, in nouveau_dmem_migrate_chunk() and nouveau_dmem_evict_chunk(), already use the saved size; use it here too. Fixes: c32287471077 ("gpu/drm/nouveau: enable THP support for GPU memory migration") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Zhenhao Wan Link: https://patch.msgid.link/20260811-b4-nouveau-dmem-thp-fixes-v1-1-2cdf9860af2a@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_dmem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_dmem.c b/drivers/gpu/drm/nouveau/nouveau_dmem.c index 9442ec6e1f6c..d2abee3efb9a 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dmem.c +++ b/drivers/gpu/drm/nouveau/nouveau_dmem.c @@ -267,7 +267,7 @@ static vm_fault_t nouveau_dmem_migrate_to_ram(struct vm_fault *vmf) nouveau_fence_new(&fence, dmem->migrate.chan); migrate_vma_pages(&args); nouveau_dmem_fence_done(&fence); - dma_unmap_page(drm->dev->dev, dma_info.dma_addr, PAGE_SIZE, + dma_unmap_page(drm->dev->dev, dma_info.dma_addr, dma_info.size, DMA_BIDIRECTIONAL); done: migrate_vma_finalize(&args); @@ -772,7 +772,7 @@ static unsigned long nouveau_dmem_migrate_copy_one(struct nouveau_drm *drm, return mpfn; out_dma_unmap: - dma_unmap_page(dev, dma_info->dma_addr, PAGE_SIZE, DMA_BIDIRECTIONAL); + dma_unmap_page(dev, dma_info->dma_addr, dma_info->size, DMA_BIDIRECTIONAL); out_free_page: nouveau_dmem_page_free_locked(drm, dpage); out: From c2256c044a1df39c8aad4dd2d6f709b2533e2d7a Mon Sep 17 00:00:00 2001 From: Zhenhao Wan Date: Tue, 11 Aug 2026 22:28:51 +0800 Subject: [PATCH 0387/2366] drm/nouveau/dmem: fix callocated underflow on large folio split nouveau_dmem_folio_free() drops chunk->callocated once per freed folio, while a large (compound) device-private folio is only counted once when it is allocated. When such a folio is split, the mm core invokes ->folio_split() (nouveau_dmem_folio_split()) once for each new sub-folio, but the hook only fixes up the sub-folio metadata and leaves chunk->callocated unchanged. Each resulting sub-folio is later freed separately, so after a split the single allocation (+1) is met by N frees (-N), leaving chunk->callocated short by N-1. On the first split/free cycle it underflows: WARN_ON(!chunk->callocated) fires, the unsigned counter wraps and never returns to zero, so the chunk can no longer be reclaimed (nouveau_dmem_fini() also warns on the leaked count). Account for the new sub-folio in the split hook, under the same lock as nouveau_dmem_folio_free(), so the count stays balanced. Fixes: c32287471077 ("gpu/drm/nouveau: enable THP support for GPU memory migration") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Signed-off-by: Zhenhao Wan Reviewed-by: Lyude Paul Link: https://patch.msgid.link/20260811-b4-nouveau-dmem-thp-fixes-v1-2-2cdf9860af2a@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_dmem.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_dmem.c b/drivers/gpu/drm/nouveau/nouveau_dmem.c index d2abee3efb9a..ad4570c50be7 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dmem.c +++ b/drivers/gpu/drm/nouveau/nouveau_dmem.c @@ -279,11 +279,25 @@ static vm_fault_t nouveau_dmem_migrate_to_ram(struct vm_fault *vmf) static void nouveau_dmem_folio_split(struct folio *head, struct folio *tail) { + struct nouveau_dmem_chunk *chunk; + struct nouveau_dmem *dmem; + if (tail == NULL) return; tail->pgmap = head->pgmap; tail->mapping = head->mapping; folio_set_zone_device_data(tail, folio_zone_device_data(head)); + + /* + * The split hands out a new independently-freeable folio that will + * later be released via nouveau_dmem_folio_free(); account for it so + * chunk->callocated stays balanced. + */ + chunk = nouveau_page_to_chunk(&head->page); + dmem = chunk->drm->dmem; + spin_lock(&dmem->lock); + chunk->callocated++; + spin_unlock(&dmem->lock); } static const struct dev_pagemap_ops nouveau_dmem_pagemap_ops = { From c037915f80c4db47f7d061d68e703ffd551b1a34 Mon Sep 17 00:00:00 2001 From: Kaiwen Shi Date: Sun, 30 Aug 2026 07:05:51 +0800 Subject: [PATCH 0388/2366] mac802154: fix data race and NULL deref on local->assoc_dev local->assoc_dev is shared between the association path and the association-response worker without common synchronization. mac802154_perform_association() stores the coordinator pointer and waits for a response. Its timeout and error paths clear the pointer and return to mac802154_associate(), which may then free the coordinator object. Meanwhile, mac802154_rx_mac_cmd_worker() may observe the associating bit and enter mac802154_process_association_resp(), which dereferences assoc_dev. The worker's bit test and the handler's pointer dereference are not atomic with respect to cleanup. Cleanup can clear assoc_dev between them, causing a NULL dereference, or free the coordinator while the response handler still uses the pointer. The recorded result is exposed to the same window. assoc_status and assoc_addr are written by the handler but read by the association path while the associating bit is still set, so a second response for the same request - a malicious one, for instance - can replace them between those reads and leave the caller with an incoherent status and address pair. The response handler only needs the coordinator extended address. Replace assoc_dev with a cached address, removing the pointer lifetime dependency. Protect the cached address and the associating bit with a dedicated spinlock. A READ_ONCE()/WRITE_ONCE() pair would not guarantee an atomic __le64 access on all 32-bit architectures. wpan_dev->association_lock cannot be reused here: nl802154_associate() holds it across rdev_associate(), hence for the whole of mac802154_perform_association() including the wait for the response. A response handler taking that lock would only get it once the association has already given up. Reset the completion, publish the cached address, and set the associating bit while holding the lock. The response handler takes the lock, rechecks the bit and the cached address, records the response, clears the bit, and only then completes the waiter. Thus cleanup cannot pass the handler between its state check and completion, and the cached 64-bit value cannot tear. The handler clears the bit before completing, not the woken waiter: otherwise complete() is issued under the lock and a second (e.g. malicious) response can reacquire it before the waiter and replace the result. So a wait that returns success implies the bit is already clear, and the success and negative-response paths return directly. The transmit-error and timeout paths still clear it under assoc_lock, which serializes any racing response against the cleanup while the call returns the error it already selected. Both paths snapshot assoc_status and assoc_addr under the same lock. Both users run in process context, so a plain spinlock is sufficient. The lock is not held while waiting for the completion. Suggested-by: Miquel Raynal Suggested-by: Xuanqiang Luo Fixes: fefd19807fe9 ("mac802154: Handle associating") Cc: stable@vger.kernel.org Signed-off-by: Kaiwen Shi Reviewed-by: Xuanqiang Luo Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260829230551.1787432-1-skwkevin@mail.ustc.edu.cn Signed-off-by: Paolo Abeni --- net/mac802154/ieee802154_i.h | 7 ++++- net/mac802154/main.c | 1 + net/mac802154/scan.c | 51 +++++++++++++++++++++++++++--------- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/net/mac802154/ieee802154_i.h b/net/mac802154/ieee802154_i.h index 8f2bff268392..c53aa293a222 100644 --- a/net/mac802154/ieee802154_i.h +++ b/net/mac802154/ieee802154_i.h @@ -76,7 +76,12 @@ struct ieee802154_local { struct work_struct rx_mac_cmd_work; /* Association */ - struct ieee802154_pan_device *assoc_dev; + /* assoc_lock protects assoc_dev_extended_addr, assoc_addr, + * assoc_status, the assoc_done reinit/complete pairing and the + * IEEE802154_IS_ASSOCIATING bit in @ongoing. + */ + spinlock_t assoc_lock; + __le64 assoc_dev_extended_addr; struct completion assoc_done; __le16 assoc_addr; u8 assoc_status; diff --git a/net/mac802154/main.c b/net/mac802154/main.c index ea1efef3572a..63e89bd586e3 100644 --- a/net/mac802154/main.c +++ b/net/mac802154/main.c @@ -104,6 +104,7 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops) INIT_WORK(&local->rx_mac_cmd_work, mac802154_rx_mac_cmd_worker); init_completion(&local->assoc_done); + spin_lock_init(&local->assoc_lock); /* init supported flags with 802.15.4 default ranges */ phy->supported.max_minbe = 8; diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c index 005338f89b75..dd156c01ac49 100644 --- a/net/mac802154/scan.c +++ b/net/mac802154/scan.c @@ -536,7 +536,9 @@ int mac802154_perform_association(struct ieee802154_sub_if_data *sdata, struct ieee802154_association_req_frame frame = {}; struct ieee802154_local *local = sdata->local; struct wpan_dev *wpan_dev = &sdata->wpan_dev; + __le16 resp_short_addr; struct sk_buff *skb; + u8 resp_status; int ret; frame.mhr.fc.type = IEEE802154_FC_TYPE_MAC_CMD; @@ -578,9 +580,11 @@ int mac802154_perform_association(struct ieee802154_sub_if_data *sdata, return ret; } - local->assoc_dev = coord; + spin_lock(&local->assoc_lock); reinit_completion(&local->assoc_done); + local->assoc_dev_extended_addr = coord->extended_addr; set_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing); + spin_unlock(&local->assoc_lock); ret = ieee802154_mlme_tx_one_locked(local, sdata, skb); if (ret) { @@ -599,25 +603,37 @@ int mac802154_perform_association(struct ieee802154_sub_if_data *sdata, goto clear_assoc; } - if (local->assoc_status != IEEE802154_ASSOCIATION_SUCCESSFUL) { - if (local->assoc_status == IEEE802154_PAN_AT_CAPACITY) + /* The association is complete: mac802154_process_association_resp() + * cleared the associating bit before waking us, so a second (e.g. + * malicious) ASSOC RESP can no longer pass the recheck and overwrite + * the result. Snapshot assoc_status/assoc_addr under the lock. + */ + spin_lock(&local->assoc_lock); + resp_status = local->assoc_status; + resp_short_addr = local->assoc_addr; + spin_unlock(&local->assoc_lock); + + if (resp_status != IEEE802154_ASSOCIATION_SUCCESSFUL) { + if (resp_status == IEEE802154_PAN_AT_CAPACITY) ret = -ERANGE; else ret = -EPERM; dev_warn(&sdata->dev->dev, "Negative ASSOC RESP received from %8phC: %s\n", &ceaddr, - local->assoc_status == IEEE802154_PAN_AT_CAPACITY ? + resp_status == IEEE802154_PAN_AT_CAPACITY ? "PAN at capacity" : "access denied"); - goto clear_assoc; + return ret; } - ret = 0; - *short_addr = local->assoc_addr; + *short_addr = resp_short_addr; + + return 0; clear_assoc: + spin_lock(&local->assoc_lock); clear_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing); - local->assoc_dev = NULL; + spin_unlock(&local->assoc_lock); return ret; } @@ -639,19 +655,28 @@ int mac802154_process_association_resp(struct ieee802154_sub_if_data *sdata, dest->mode != IEEE802154_EXTENDED_ADDRESSING)) return -EINVAL; - if (unlikely(dest->extended_addr != wpan_dev->extended_addr || - src->extended_addr != local->assoc_dev->extended_addr)) + spin_lock(&local->assoc_lock); + if (unlikely(!test_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing) || + dest->extended_addr != wpan_dev->extended_addr || + src->extended_addr != local->assoc_dev_extended_addr)) { + spin_unlock(&local->assoc_lock); return -ENODEV; + } memcpy(&resp_pl, skb->data, sizeof(resp_pl)); local->assoc_addr = resp_pl.short_addr; local->assoc_status = resp_pl.status; + /* Clear the associating bit before waking the waiter: once the result + * is saved, any subsequent (e.g. malicious) ASSOC RESP must fail the + * test_bit() recheck above and can no longer overwrite the result. + */ + clear_bit(IEEE802154_IS_ASSOCIATING, &local->ongoing); + complete(&local->assoc_done); + spin_unlock(&local->assoc_lock); dev_dbg(&skb->dev->dev, "ASSOC RESP 0x%x received from %8phC, getting short address %04x\n", - local->assoc_status, &deaddr, local->assoc_addr); - - complete(&local->assoc_done); + resp_pl.status, &deaddr, resp_pl.short_addr); return 0; } From ae36a5b609ae79f4de966328b78d2584be9719a4 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Thu, 27 Aug 2026 19:18:07 +0200 Subject: [PATCH 0389/2366] RDMA/rxe: validate access flags before swapping the MR's PD rxe_rereg_user_mr() reassigns mr->ibmr.pd first and only then validates the IB_MR_REREG_ACCESS argument: if (flags & IB_MR_REREG_PD) { rxe_put(old_pd); rxe_get(pd); mr->ibmr.pd = ibpd; } if (flags & IB_MR_REREG_ACCESS) { if (access & ~RXE_ACCESS_SUPPORTED_MR) return ERR_PTR(-EOPNOTSUPP); mr->access = access; } Both flags pass the entry check because RXE_MR_REREG_SUPPORTED is IB_MR_REREG_PD | IB_MR_REREG_ACCESS, so a caller can reach the access check with mr->ibmr.pd already reassigned. mr->ibmr.pd is owned by the core, which adjusts pd->usecnt only on the success path: ib_uverbs_rereg_mr() jumps to put_new_uobj on a driver error without undoing the reassignment, so mr->pd == new_pd while the usecnts still charge the MR to orig_pd. ib_dereg_mr_user() then decrements new_pd, whose count can reach zero while a memory window still references it; uverbs_free_pd() frees the PD on that count alone and rxe_mw_cleanup() writes to freed memory: BUG: KASAN: slab-use-after-free in __rxe_put+0x31/0xa0 Write of size 4 at addr ffff8881301dd690 by task rxe_poc/591 __rxe_put+0x31/0xa0 rxe_mw_cleanup+0x42/0x200 __rxe_cleanup+0x115/0x370 rxe_dealloc_mw+0x4c/0x80 Allocated by task 591: ib_uverbs_alloc_pd+0x258/0x540 Freed by task 591: ib_dealloc_pd_user+0x174/0x210 uverbs_free_pd+0x8d/0xc0 ib_uverbs_dealloc_pd+0x18e/0x1d0 Validate the access flags before mutating any state so the callback either applies every requested change or none. Fixes: 544c7f62cf32 ("RDMA/rxe: Implement rereg_user_mr") Signed-off-by: Norbert Szetei Link: https://patch.msgid.link/46E1D5C0-24BE-4D01-BDB3-634FE09B22C5@doyensec.com Reviewed-by: Zhu Yanjun Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/rxe/rxe_verbs.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_verbs.c b/drivers/infiniband/sw/rxe/rxe_verbs.c index 96c7716057fe..3864284522eb 100644 --- a/drivers/infiniband/sw/rxe/rxe_verbs.c +++ b/drivers/infiniband/sw/rxe/rxe_verbs.c @@ -1331,19 +1331,20 @@ static struct ib_mr *rxe_rereg_user_mr(struct ib_mr *ibmr, int flags, if (err) return ERR_PTR(err); + if ((flags & IB_MR_REREG_ACCESS) && + (access & ~RXE_ACCESS_SUPPORTED_MR)) { + rxe_err_mr(mr, "access = %#x not supported\n", access); + return ERR_PTR(-EOPNOTSUPP); + } + if (flags & IB_MR_REREG_PD) { rxe_put(old_pd); rxe_get(pd); mr->ibmr.pd = ibpd; } - if (flags & IB_MR_REREG_ACCESS) { - if (access & ~RXE_ACCESS_SUPPORTED_MR) { - rxe_err_mr(mr, "access = %#x not supported\n", access); - return ERR_PTR(-EOPNOTSUPP); - } + if (flags & IB_MR_REREG_ACCESS) mr->access = access; - } return NULL; } From f576944a59f31bcffff121117ebf452c5dd162b7 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Fri, 7 Aug 2026 23:09:55 +0800 Subject: [PATCH 0390/2366] staging: fbtft: make dirty_lock IRQ-safe fbtft_mkdirty() can be reached from the fbcon rendering path while processing printk() in hardirq context. Meanwhile, dirty_lock is also taken by fbtft_deferred_io() in workqueue context with local interrupts enabled. Lockdep reports a possible IRQ lock inversion involving dirty_lock and console_owner. A hardirq can interrupt a CPU holding dirty_lock and enter the console rendering path, which can attempt to acquire dirty_lock again. The following lockdep report was observed on an RK3566 system with CONFIG_PROVE_LOCKING enabled: WARNING: possible irq lock inversion dependency detected swapper/2/0 just changed the state of lock: (console_owner){-...}-{0:0} but this lock took another, HARDIRQ-unsafe lock in the past: (&par->dirty_lock){+.+.}-{2:2} CPU0 CPU1 ---- ---- lock(&par->dirty_lock); local_irq_disable(); lock(console_owner); lock(&par->dirty_lock); lock(console_owner); *** DEADLOCK *** Use spin_lock_irqsave() for fbtft_mkdirty() and spin_lock_irq() for fbtft_deferred_io(). They only access the dirty line range, so the IRQ-off regions remain short. Fixes: c296d5f9957c ("staging: fbtft: core support") Signed-off-by: Hui Su Link: https://lore.kernel.org/lkml/20260804173712.176017-1-sh_def@163.com/ Reviewed-by: Nam Cao Link: https://patch.msgid.link/20260807150953.2811933-3-sh_def@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/staging/fbtft/fbtft-core.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/fbtft/fbtft-core.c index ca0c38221c16..7925d974de80 100644 --- a/drivers/staging/fbtft/fbtft-core.c +++ b/drivers/staging/fbtft/fbtft-core.c @@ -298,14 +298,15 @@ static void fbtft_mkdirty(struct fb_info *info, int y, int height) { struct fbtft_par *par = info->par; struct fb_deferred_io *fbdefio = info->fbdefio; + unsigned long flags; /* Mark display lines/area as dirty */ - spin_lock(&par->dirty_lock); + spin_lock_irqsave(&par->dirty_lock, flags); if (y < par->dirty_lines_start) par->dirty_lines_start = y; if (y + height - 1 > par->dirty_lines_end) par->dirty_lines_end = y + height - 1; - spin_unlock(&par->dirty_lock); + spin_unlock_irqrestore(&par->dirty_lock, flags); /* Schedule deferred_io to update display (no-op if already on queue)*/ schedule_delayed_work(&info->deferred_work, fbdefio->delay); @@ -318,13 +319,13 @@ static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagereflis struct fb_deferred_io_pageref *pageref; unsigned int y_low = 0, y_high = 0; - spin_lock(&par->dirty_lock); + spin_lock_irq(&par->dirty_lock); dirty_lines_start = par->dirty_lines_start; dirty_lines_end = par->dirty_lines_end; /* set display line markers as clean */ par->dirty_lines_start = par->info->var.yres - 1; par->dirty_lines_end = 0; - spin_unlock(&par->dirty_lock); + spin_unlock_irq(&par->dirty_lock); /* Mark display lines as dirty */ list_for_each_entry(pageref, pagereflist, list) { From 99aa998dec83ba180822f70e6d48a514fc81c20d Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Tue, 28 Jul 2026 17:54:54 +0500 Subject: [PATCH 0391/2366] staging: rtl8723bs: fix OOB read / stack overflow in rtw_get_wps_attr() rtw_get_wps_attr() walks WPS attributes inside a WPS IE taken from a wireless management frame. For each candidate attribute it only checks that the fixed 4-byte attribute header (2-byte ID + 2-byte length) fits inside the IE: 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; attr_data_len (and therefore attr_len) is read directly from the wire and is never checked against the remaining bytes in the IE before being used as the size of: memcpy(buf_attr, attr_ptr, attr_len); Since attr_len is fully attacker controlled (0 to 65535+4), this is both a heap OOB read of wps_ie, and, more seriously, a stack buffer overflow at several call sites where buf_attr is a single-byte stack variable, e.g. rtw_get_wps_attr_content()'s callers passing WPS_ATTR_SELECTED_REGISTRAR into a stack "u8 sr"/"u8 selected_registrar" (drivers/staging/rtl8723bs/os_dep/ioctl_cfg80211.c, drivers/staging/rtl8723bs/core/rtw_mlme_ext.c). A crafted WPS IE in a beacon or probe response processed during scanning can therefore smash the stack of the parsing thread. rtw_get_wps_attr_content() itself has no independent length check and simply trusts the attr_len it gets back from rtw_get_wps_attr(), so fixing the bound here also fixes that caller. The "attr_ptr + 4 > wps_ie + wps_ielen" header check above was added by commit 1463ca3ec6601 ("staging: rtl8723bs: fix OOB reads in rtw_get_sec_ie(), rtw_get_wapi_ie(), and rtw_get_wps_attr()"), which bounded the fixed header but never extended the check to cover the variable-length attribute data that follows it. Add that missing check before attr_len is used as a memcpy() length or accepted as a match. Fixes: 554c0a3abf216 ("staging: Add rtl8723bs sdio wifi driver") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260728125456.32359-2-meatuni001@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c index 66f476a46aad..2a58a5cdd9f5 100644 --- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c @@ -741,6 +741,10 @@ u8 *rtw_get_wps_attr(u8 *wps_ie, uint wps_ielen, u16 target_attr_id, u8 *buf_att u16 attr_data_len = get_unaligned_be16(attr_ptr + 2); u16 attr_len = attr_data_len + 4; + /* Reject attributes whose claimed length runs past the IE */ + if (attr_ptr + attr_len > wps_ie + wps_ielen) + break; + if (attr_id == target_attr_id) { target_attr_ptr = attr_ptr; From ff917923f4fb9c83717ba135ee47d7e4c1567bb7 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Tue, 28 Jul 2026 17:54:55 +0500 Subject: [PATCH 0392/2366] staging: rtl8723bs: fix OOB read in rtw_action_frame_parse() rtw_action_frame_parse() takes a frame_len parameter but never actually checks it before indexing into the frame body: const u8 *frame_body = frame + sizeof(struct ieee80211_hdr_3addr); ... c = frame_body[0]; ... a = frame_body[1]; frame_body already points 24 bytes (sizeof(struct ieee80211_hdr_3addr)) into frame, so reading frame_body[0] and frame_body[1] requires frame_len >= 26. A management action frame shorter than that (e.g. exactly 24 bytes, the minimum a malicious peer can send) causes a 1-2 byte out-of-bounds read. This is reachable from rtw_cfg80211_monitor_if_xmit_entry() and cfg80211_rtw_mgmt_tx() in ioctl_cfg80211.c, both of which pass attacker/user-influenced frame buffers and lengths straight through. Add the missing length check before frame_body is dereferenced. Fixes: 554c0a3abf216 ("staging: Add rtl8723bs sdio wifi driver") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260728125456.32359-3-meatuni001@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/staging/rtl8723bs/core/rtw_ieee80211.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c index 2a58a5cdd9f5..4d211711f2ba 100644 --- a/drivers/staging/rtl8723bs/core/rtw_ieee80211.c +++ b/drivers/staging/rtl8723bs/core/rtw_ieee80211.c @@ -1153,6 +1153,9 @@ int rtw_action_frame_parse(const u8 *frame, u32 frame_len, u8 *category, u8 *act u8 c; u8 a = ACT_PUBLIC_MAX; + if (frame_len < sizeof(struct ieee80211_hdr_3addr) + 2) + return false; + fc = le16_to_cpu(((struct ieee80211_hdr_3addr *)frame)->frame_control); if ((fc & (IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) != From 28a289beaf226b30b1e6e7d7b1a2946fe2d6e852 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Tue, 28 Jul 2026 17:54:56 +0500 Subject: [PATCH 0393/2366] staging: rtl8723bs: fix OOB read in rtw_restruct_wmm_ie() rtw_restruct_wmm_ie() scans in_ie for a WMM IE with: while (i < in_len) { ... if (i + 5 < in_len && in_ie[i] == 0xDD && ...) { ... break; } i += (in_ie[i + 1] + 2); /* to the next IE element */ } When the "i + 5 < in_len" match check fails simply because i is within 5 bytes of the end of the buffer (i.e. no WMM IE was found near the tail of in_ie), execution falls through to "i += (in_ie[i + 1] + 2)", which reads in_ie[i + 1]. If i == in_len - 1 at that point, this is a 1-byte out-of-bounds read of an attacker-influenced IE buffer built from association/scan data. Commit a75281626fc8f ("staging: rtl8723bs: fix potential out-of-bounds read in rtw_restruct_wmm_ie") added the "i + 5 < in_len" guard to the match condition itself, but did not add an equivalent guard before the fallthrough advance, so the same class of OOB read remained reachable through the non-matching path. Add an explicit bounds check before advancing to the next IE. Fixes: 554c0a3abf216 ("staging: Add rtl8723bs sdio wifi driver") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260728125456.32359-4-meatuni001@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/staging/rtl8723bs/core/rtw_mlme.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/staging/rtl8723bs/core/rtw_mlme.c b/drivers/staging/rtl8723bs/core/rtw_mlme.c index fc46b33b836a..d18768a51b19 100644 --- a/drivers/staging/rtl8723bs/core/rtw_mlme.c +++ b/drivers/staging/rtl8723bs/core/rtw_mlme.c @@ -1975,6 +1975,9 @@ int rtw_restruct_wmm_ie(struct adapter *adapter, u8 *in_ie, u8 *out_ie, uint in_ break; } + if (i + 1 >= in_len) + break; + i += (in_ie[i + 1] + 2); /* to the next IE element */ } From d10e2a08799e858d3e71ea4169bcd018f216d444 Mon Sep 17 00:00:00 2001 From: Gang Yan Date: Fri, 14 Aug 2026 17:37:40 +0800 Subject: [PATCH 0394/2366] RDMA/rxe: Fix integer overflow in mr_check_range() leading to OOB access mr_check_range() validates that [iova, iova+length) falls within the registered MR range using wraparound-prone arithmetic: if (iova < mr->ibmr.iova || iova + length > mr->ibmr.iova + mr->ibmr.length) A remote peer can craft an RDMA-Write/Read RETH so that iova + length wraps to 0 (e.g. iova=0xfffffffffffffff8, length=8), bypassing the check. rxe_mr_iova_to_index() then computes a huge index (int idx, only guarded by WARN_ON) and rxe_mr_copy_xarray() dereferences mr->page_info[huge], causing an out-of-bounds read/write and a kernel oops that is triggerable by an unauthenticated remote peer. Rewrite the check in overflow-safe form; the first two clauses guarantee that the subsequent subtractions do not underflow: if (iova < mr->ibmr.iova || length > mr->ibmr.length || iova - mr->ibmr.iova > mr->ibmr.length - length) With the fix, mr_check_range() returns -EINVAL for the crafted iova and the responder reports REMOTE_ACCESS_ERROR instead of triggering the OOB. Fixes: 8700e3e7c485 ("Soft RoCE driver") Signed-off-by: Gang Yan Link: https://patch.msgid.link/20260814093740.292954-1-gang.yan@linux.dev Reviewed-by: Zhu Yanjun Reviewed-by: Shukai Ni Tested-by: Shukai Ni Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/rxe/rxe_mr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/sw/rxe/rxe_mr.c b/drivers/infiniband/sw/rxe/rxe_mr.c index 875eceb55fdf..71d9ea477289 100644 --- a/drivers/infiniband/sw/rxe/rxe_mr.c +++ b/drivers/infiniband/sw/rxe/rxe_mr.c @@ -33,7 +33,8 @@ int mr_check_range(struct rxe_mr *mr, u64 iova, size_t length) case IB_MR_TYPE_USER: case IB_MR_TYPE_MEM_REG: if (iova < mr->ibmr.iova || - iova + length > mr->ibmr.iova + mr->ibmr.length) { + length > mr->ibmr.length || + iova - mr->ibmr.iova > mr->ibmr.length - length) { rxe_dbg_mr(mr, "iova/length out of range\n"); return -EINVAL; } From 9fa903b24b1f46b4ff5443bcd4aca23e5c57f9c1 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft Security FORGE Labs)" Date: Wed, 26 Aug 2026 16:17:45 -0400 Subject: [PATCH 0395/2366] xfrm: hold net_device reference under RCU in bundle creation xfrm_bundle_create() and xfrm_create_dummy_bundle() read dst->dev into a local pointer without taking a device reference, then pass it to xfrm_fill_dst(). A concurrent RTM_DELLINK replaces dst->dev via dst_dev_put() and frees the old net_device, causing a use-after-free when xfrm6_fill_dst() later dereferences the stale dev pointer. BUG: KASAN: slab-use-after-free in xfrm6_fill_dst+0x82c/0x860 (net/ipv6/xfrm6_policy.c:86 netdev_hold()) Read of size 8 at addr ffff8880142fe588 by task exploit/153 Call Trace: xfrm6_fill_dst+0x82c/0x860 xfrm_resolve_and_create_bundle+0x21d4/0x2bd0 xfrm_lookup_with_ifid+0x485/0x1640 ip6_dst_lookup_flow+0x19b/0x1e0 udpv6_sendmsg+0x1443/0x2dd0 Fix this by reading dst->dev via dst_dev_rcu() and keeping the RCU read-side critical section active until xfrm_fill_dst() has taken the required device references. Fixes: 25ee3286dcbc ("[IPSEC]: Merge common code into xfrm_bundle_create") Fixes: a0073fe18e71 ("xfrm: Add a state resolution packet queue") Suggested-by: Steffen Klassert Reported-by: Xiang Mei (Microsoft) Link: https://lore.kernel.org/all/20260820200245.44312-1-blbllhy@gmail.com/ Cc: AutonomousCodeSecurity@microsoft.com Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Cen Zhang (Microsoft Security FORGE Labs) Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_policy.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c index 932a313b9460..513c9f228334 100644 --- a/net/xfrm/xfrm_policy.c +++ b/net/xfrm/xfrm_policy.c @@ -2770,9 +2770,12 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy, xdst0->path = dst; err = -ENODEV; - dev = dst->dev; - if (!dev) + rcu_read_lock(); + dev = dst_dev_rcu(dst); + if (!dev) { + rcu_read_unlock(); goto free_dst; + } xfrm_init_path(xdst0, dst, nfheader_len); xfrm_init_pmtu(bundle, nx); @@ -2780,8 +2783,10 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy, for (xdst_prev = xdst0; xdst_prev != (struct xfrm_dst *)dst; xdst_prev = (struct xfrm_dst *) xfrm_dst_child(&xdst_prev->u.dst)) { err = xfrm_fill_dst(xdst_prev, dev, fl); - if (err) + if (err) { + rcu_read_unlock(); goto free_dst; + } xdst_prev->u.dst.header_len = header_len; xdst_prev->u.dst.trailer_len = trailer_len; @@ -2789,6 +2794,7 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy, trailer_len -= xdst_prev->u.dst.xfrm->props.trailer_len; } + rcu_read_unlock(); return &xdst0->u.dst; put_states: @@ -3058,11 +3064,15 @@ static struct xfrm_dst *xfrm_create_dummy_bundle(struct net *net, xfrm_init_path((struct xfrm_dst *)dst1, dst, 0); err = -ENODEV; - dev = dst->dev; - if (!dev) + rcu_read_lock(); + dev = dst_dev_rcu(dst); + if (!dev) { + rcu_read_unlock(); goto free_dst; + } err = xfrm_fill_dst(xdst, dev, fl); + rcu_read_unlock(); if (err) goto free_dst; From 3cf5cdecd99c9c186a5ea518d93bbf3045b6e3aa Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 29 Aug 2026 17:24:24 +0800 Subject: [PATCH 0396/2366] xfrm: save input state data before secpath resets xfrm_input() stores the current xfrm_state in the skb secpath while it continues receive-side processing. Some input paths can reset that secpath before xfrm_input() has finished dereferencing the state. Receive callback users such as VTI and XFRM interfaces can reset the secpath. The VTI receive path does so before checking whether the packet crosses network namespaces, while the XFRM interface path does so only for cross-network-namespace packets. The XFRM_MAX_DEPTH error path can also reset the secpath before the final drop callback reports the current state's protocol. If secpath_reset() drops the last state reference while the state is concurrently deleted, xfrm_input() can still dereference the freed state when selecting transport_finish() or reporting the drop callback protocol. Save the state protocol on the stack while the state is still valid, and use the already saved address family for transport_finish(). A larval XFRM_STATE_ACQ state has no type, so retain nexthdr as its protocol. This preserves the existing drop-path fallback while avoiding the post-reset state dereferences without adding an extra state reference to every received packet. Fixes: df3893c176e9 ("vti: Update the ipv4 side to use it's own receive hook.") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Steffen Klassert --- net/xfrm/xfrm_input.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c index 8f6109eada7e..5ed87d51392a 100644 --- a/net/xfrm/xfrm_input.c +++ b/net/xfrm/xfrm_input.c @@ -474,6 +474,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) struct xfrm_state *x = NULL; xfrm_address_t *daddr; u32 mark = skb->mark; + u8 xfrm_proto = nexthdr; unsigned int family = AF_UNSPEC; int decaps = 0; int async = 0; @@ -485,6 +486,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) if (encap_type < 0 || (xo && (xo->flags & XFRM_GRO || encap_type == 0 || encap_type == UDP_ENCAP_ESPINUDP))) { x = xfrm_input_state(skb); + xfrm_proto = x->type ? x->type->proto : nexthdr; if (unlikely(x->km.state != XFRM_STATE_VALID)) { if (x->km.state == XFRM_STATE_ACQ) @@ -592,11 +594,13 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) x = xfrm_input_state_lookup(net, mark, daddr, spi, nexthdr, family); if (x == NULL) { + xfrm_proto = nexthdr; secpath_reset(skb); XFRM_INC_STATS(net, LINUX_MIB_XFRMINNOSTATES); xfrm_audit_state_notfound(skb, family, spi, seq); goto drop; } + xfrm_proto = x->type ? x->type->proto : nexthdr; if (unlikely(x->dir && x->dir != XFRM_SA_DIR_IN)) { secpath_reset(skb); @@ -604,6 +608,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) xfrm_audit_state_notfound(skb, family, spi, seq); xfrm_state_put(x); x = NULL; + xfrm_proto = nexthdr; goto drop; } @@ -728,7 +733,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) } while (!err); rcu_read_lock(); - err = xfrm_rcv_cb(skb, family, x->type->proto, 0); + err = xfrm_rcv_cb(skb, family, xfrm_proto, 0); if (err) { rcu_read_unlock(); goto drop; @@ -753,7 +758,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) xfrm_gro = xo->flags & XFRM_GRO; err = -EAFNOSUPPORT; - afinfo = xfrm_state_afinfo_get_rcu(x->props.family); + afinfo = xfrm_state_afinfo_get_rcu(family); if (likely(afinfo)) err = afinfo->transport_finish(skb, xfrm_gro || async); if (xfrm_gro) { @@ -776,7 +781,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type) drop: if (async) dev_put(dev); - xfrm_rcv_cb(skb, family, x && x->type ? x->type->proto : nexthdr, -1); + xfrm_rcv_cb(skb, family, xfrm_proto, -1); kfree_skb(skb); return 0; } From 96f01b53c2d05e003b040892256de54a586e8529 Mon Sep 17 00:00:00 2001 From: Wyatt Feng Date: Sat, 29 Aug 2026 23:44:32 +0800 Subject: [PATCH 0397/2366] net: xfrm: reject unrepresentable espintcp transport headers ESP-in-TCP can hand xfrm packets whose transport header offset no longer fits after the stream parser trims the TCP envelope. The plain transport header reset truncates that offset and triggers the skb warning path. Use the careful transport-header helper and drop the skb through the existing XFRM error path when the offset cannot be represented. Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: Codex:GPT-5.4 Signed-off-by: Wyatt Feng Signed-off-by: Ren Wei Signed-off-by: Steffen Klassert --- net/xfrm/espintcp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/xfrm/espintcp.c b/net/xfrm/espintcp.c index 674aedc5af5a..3e72b9f067b9 100644 --- a/net/xfrm/espintcp.c +++ b/net/xfrm/espintcp.c @@ -30,7 +30,11 @@ static void handle_esp(struct sk_buff *skb, struct sock *sk) { struct tcp_skb_cb *tcp_cb = (struct tcp_skb_cb *)skb->cb; - skb_reset_transport_header(skb); + if (!skb_reset_transport_header_careful(skb)) { + XFRM_INC_STATS(sock_net(sk), LINUX_MIB_XFRMINERROR); + kfree_skb(skb); + return; + } /* restore IP CB, we need at least IP6CB->nhoff */ memmove(skb->cb, &tcp_cb->header, sizeof(tcp_cb->header)); From bc93419130bb70fabf6561e197054caae85c160c Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 31 Aug 2026 08:10:27 +0000 Subject: [PATCH 0398/2366] net: bonding: annotate lockless writes with WRITE_ONCE() Several fields in bonding are read locklessly using READ_ONCE() (or ACCESS_ONCE() previously) but have corresponding writes that do not use WRITE_ONCE(). Add WRITE_ONCE() annotations to: - bond->send_peer_notif decrements in bond_peer_notify_may_events() and reset in bond_close(). - bond->slave_cnt increments and decrements in bond_enslave() and __bond_release_one(). - bond->recv_probe updates in bond_open(), bond_option_arp_interval_set() and rlb_initialize(). - slaves->count decrement in bond_skip_slave(). Fixes: 4d97480b1806 ("bonding: use local function pointer of bond->recv_probe in bond_handle_frame") Fixes: 9a72c2da690d ("bonding: fix div by zero while enslaving and transmitting") Fixes: ee6377147409 ("bonding: Simplify the xmit function for modes that use xmit_hash") Fixes: 429208aab9db ("net: bonding: add the READ_ONCE/WRITE_ONCE for outside lock accessing") Signed-off-by: Eric Dumazet Cc: Jay Vosburgh Reviewed-by: Xuanqiang Luo Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260831081027.3209554-1-edumazet@google.com Signed-off-by: Paolo Abeni --- drivers/net/bonding/bond_alb.c | 2 +- drivers/net/bonding/bond_main.c | 14 +++++++------- drivers/net/bonding/bond_options.c | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c index 839f7482dc18..d2fb67a47cf9 100644 --- a/drivers/net/bonding/bond_alb.c +++ b/drivers/net/bonding/bond_alb.c @@ -875,7 +875,7 @@ static int rlb_initialize(struct bonding *bond) spin_unlock_bh(&bond->mode_lock); /* register to receive ARPs */ - bond->recv_probe = rlb_arp_recv; + WRITE_ONCE(bond->recv_probe, rlb_arp_recv); return 0; } diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index ef9eb0c53c66..947d92a669b6 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -1245,7 +1245,7 @@ static void bond_peer_notify_may_events(struct bonding *bond, bool force) } if (notified || force) - bond->send_peer_notif--; + WRITE_ONCE(bond->send_peer_notif, bond->send_peer_notif - 1); } /** @@ -2284,7 +2284,7 @@ int bond_enslave(struct net_device *bond_dev, struct net_device *slave_dev, } } - bond->slave_cnt++; + WRITE_ONCE(bond->slave_cnt, bond->slave_cnt + 1); netdev_compute_master_upper_features(bond->dev, true); bond_set_carrier(bond); @@ -2533,7 +2533,7 @@ static int __bond_release_one(struct net_device *bond_dev, unblock_netpoll_tx(); synchronize_rcu(); - bond->slave_cnt--; + WRITE_ONCE(bond->slave_cnt, bond->slave_cnt - 1); if (!bond_has_slaves(bond)) { call_netdevice_notifiers(NETDEV_CHANGEADDR, bond->dev); @@ -4385,13 +4385,13 @@ static int bond_open(struct net_device *bond_dev) if (bond->params.arp_interval) { /* arp interval, in milliseconds. */ queue_delayed_work(bond->wq, &bond->arp_work, 0); - bond->recv_probe = bond_rcv_validate; + WRITE_ONCE(bond->recv_probe, bond_rcv_validate); } if (BOND_MODE(bond) == BOND_MODE_8023AD) { queue_delayed_work(bond->wq, &bond->ad_work, 0); /* register to receive LACPDUs */ - bond->recv_probe = bond_3ad_lacpdu_recv; + WRITE_ONCE(bond->recv_probe, bond_3ad_lacpdu_recv); bond_3ad_initiate_agg_selection(bond, 1); bond_for_each_slave(bond, slave, iter) @@ -4413,7 +4413,7 @@ static int bond_close(struct net_device *bond_dev) struct slave *slave; bond_work_cancel_all(bond); - bond->send_peer_notif = 0; + WRITE_ONCE(bond->send_peer_notif, 0); WRITE_ONCE(bond->recv_probe, NULL); /* Wait for any in-flight RX handlers */ @@ -5118,7 +5118,7 @@ static void bond_skip_slave(struct bond_up_slave *slaves, if (skipslave == slaves->arr[idx]) { slaves->arr[idx] = slaves->arr[slaves->count - 1]; - slaves->count--; + WRITE_ONCE(slaves->count, slaves->count - 1); break; } } diff --git a/drivers/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c index 36b8d89387ee..9efadeff6a22 100644 --- a/drivers/net/bonding/bond_options.c +++ b/drivers/net/bonding/bond_options.c @@ -1147,11 +1147,11 @@ static int bond_option_arp_interval_set(struct bonding *bond, */ if (!newval->value) { if (bond->params.arp_validate) - bond->recv_probe = NULL; + WRITE_ONCE(bond->recv_probe, NULL); cancel_delayed_work_sync(&bond->arp_work); } else { /* arp_validate can be set only in active-backup mode */ - bond->recv_probe = bond_rcv_validate; + WRITE_ONCE(bond->recv_probe, bond_rcv_validate); cancel_delayed_work_sync(&bond->mii_work); queue_delayed_work(bond->wq, &bond->arp_work, 0); } From e4637ce34607f1733a34a57294966d26b263e626 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 1 Sep 2026 18:04:09 +0900 Subject: [PATCH 0399/2366] ALSA: usb-audio: fix OOB write in snd_usbmidi_us122l_output() The snd_usbmidi_us122l_output() picks a count of 2 on anything slower than high speed and never relates it to ep->max_transfer. The URB buffer holds exactly max_transfer bytes, so a device declaring a one byte bulk endpoint takes two bytes from snd_rawmidi_transmit(), and the memset that pads the rest computes 1 - 2 in int and wraps to SIZE_MAX. Only 0x800e and 0x800f are pinned to nine bytes. The US-122MKII at 0x0644:0x8021 falls to the default and takes usb_maxpacket(), which the USB core only clamps downward. The akai and novation output ops in this file were given the same guard recently. Do the same here. Fixes: 030a07e44129 ("ALSA: Add USB US122L driver") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260901090409.1478573-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/midi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/midi.c b/sound/usb/midi.c index 8a9bc37f0b6e..7e5b1b13360f 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -971,6 +971,8 @@ static void snd_usbmidi_us122l_output(struct snd_usb_midi_out_endpoint *ep, default: count = 2; } + if (ep->max_transfer < count) + return; count = snd_rawmidi_transmit(ep->ports[0].substream, urb->transfer_buffer, count); From 32d7226e6105c257ef7b3d0ec819f11a81f53b6d Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Tue, 1 Sep 2026 11:40:24 +0800 Subject: [PATCH 0400/2366] ALSA: hda: restore MFG widget enumeration after core split Before commit 7639a06c23c7 ("ALSA: hda - Move a part of hda_codec stuff into hdac_device"), widget enumeration selected the function group with codec->afg ? codec->afg : codec->mfg and read subordinate nodes from that group. The core split moved this logic into snd_hdac_refresh_widgets(), but hard-coded codec->afg there. For an MFG-only codec, codec->afg is zero, so the Root Node is queried and codec->start_nid/num_nodes are populated from the function-group range instead of the MFG's subordinate nodes. Restore the pre-split AFG-or-MFG selection. Fixes: 7639a06c23c7 ("ALSA: hda - Move a part of hda_codec stuff into hdac_device") Signed-off-by: Xu Rao Link: https://patch.msgid.link/44809B8FF80DCCA2+20260901034024.2407783-1-raoxu@uniontech.com Signed-off-by: Takashi Iwai --- sound/hda/core/device.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c index 776d629ba252..a45f61e12c1a 100644 --- a/sound/hda/core/device.c +++ b/sound/hda/core/device.c @@ -404,6 +404,7 @@ static void setup_fg_nodes(struct hdac_device *codec) */ int snd_hdac_refresh_widgets(struct hdac_device *codec) { + hda_nid_t fg = codec->afg ? codec->afg : codec->mfg; hda_nid_t start_nid; int nums, err = 0; @@ -412,10 +413,10 @@ int snd_hdac_refresh_widgets(struct hdac_device *codec) * widgets array. */ guard(mutex)(&codec->widget_lock); - nums = snd_hdac_get_sub_nodes(codec, codec->afg, &start_nid); + nums = snd_hdac_get_sub_nodes(codec, fg, &start_nid); if (!start_nid || nums <= 0 || nums >= 0xff) { dev_err(&codec->dev, "cannot read sub nodes for FG 0x%02x\n", - codec->afg); + fg); return -EINVAL; } From c53f5bfc370046e098fe04dc045e9e3cf19df609 Mon Sep 17 00:00:00 2001 From: Peter Drabik Date: Mon, 31 Aug 2026 21:58:55 +0200 Subject: [PATCH 0401/2366] ALSA: usb-audio: Add mixer map quirk for Audient iD24 The Audient iD24 (2708:000d) exposes feature unit 12 as a 4-channel "Speaker Playback Volume" control (cmask 0xf, -127..0 dB). The device does not actually apply this volume to all of its output channels: the left main output ignores it and stays at 0 dB, while the right main output honors it. When userspace (PulseAudio / PipeWire in a stereo profile) adopts this control as the master playback volume, any setting below maximum produces a stereo imbalance on the main outputs. This was verified against the device's internal meters: with the control set to 107/127 (-20 dB) on all four channels and a digitally identical L/R sine played back, both DAW return meters read the same level while the right main output metered exactly 20 dB below the left. Restoring the control to 127 (0 dB) restored the balance. Rename the control to "Monitor Mix Playback" so that it is not picked up as the stream's master volume control, in line with similar quirks for other devices. The control remains accessible for manual use. Signed-off-by: Peter Drabik Link: https://patch.msgid.link/20260831195855.1836617-1-drabik.p@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/mixer_maps.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index ace4ccad8f51..69093c666282 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -518,6 +518,19 @@ static const struct usbmix_name_map audient_id14_map[] = { {} }; +/* + * Audient iD24: feature unit 12 ("Speaker Playback Volume") sits in the + * monitor-mixer branch and does not apply volume to all of its channels; + * when userspace adopts it as the master playback volume, the left main + * output stays at 0 dB while the right one is attenuated, producing a + * stereo imbalance. Rename it so that it is not picked up as the + * stream's master volume control. + */ +static const struct usbmix_name_map audient_id24_map[] = { + { 12, "Monitor Mix Playback" }, /* FU, partial channel coverage */ + {} +}; + /* * Control map entries */ @@ -611,6 +624,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x2708, 0x0008), .map = audient_id14_map, }, + { + /* Audient iD24 */ + .id = USB_ID(0x2708, 0x000d), + .map = audient_id24_map, + }, { /* KEF X300A */ .id = USB_ID(0x27ac, 0x1000), From a0c798ed4103316c23938bdf625af364fbd38016 Mon Sep 17 00:00:00 2001 From: Vasily Gorbik Date: Wed, 19 Aug 2026 12:30:05 +0200 Subject: [PATCH 0402/2366] s390/boot: Fix physical memory search range search_mem_end() calculates the number of 1MB blocks with a signed int literal. CONFIG_MAX_PHYSMEM_BITS values of 51 and above either overflow the signed int or shift beyond its width. This produces an invalid search range when the binary-search memory detection fallback is used. Use an unsigned long literal so the full supported physical address range is represented. Fixes: 54c57795e848 ("s390/mem_detect: replace tprot loop with binary search") Reviewed-by: Heiko Carstens Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/boot/physmem_info.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/boot/physmem_info.c b/arch/s390/boot/physmem_info.c index 1f2ca5435838..0ebb2174713f 100644 --- a/arch/s390/boot/physmem_info.c +++ b/arch/s390/boot/physmem_info.c @@ -141,7 +141,7 @@ static int tprot(unsigned long addr) static unsigned long search_mem_end(void) { - unsigned long range = 1 << (MAX_PHYSMEM_BITS - 20); /* in 1MB blocks */ + unsigned long range = 1UL << (MAX_PHYSMEM_BITS - 20); /* in 1MB blocks */ unsigned long offset = 0; unsigned long pivot; From d76181dfabdaa720703167393704efacba343442 Mon Sep 17 00:00:00 2001 From: Vasily Gorbik Date: Wed, 19 Aug 2026 12:30:33 +0200 Subject: [PATCH 0403/2366] s390/boot: Avoid IPL parameter append past command line A command line may occupy all but the terminating byte of COMMAND_LINE_SIZE. In that case append_ipl_block_parm() passes a zero size to the IPL parameter conversion helpers and points the destination one byte past early_command_line. The helpers subtract one from the unsigned size and write the converted parameter outside the command line buffer. Convert the IPL parameter in the command line parsing buffer first. A parameter beginning with '=' can then replace the existing command line regardless of its length, while other parameters are appended only when space remains. Fixes: 5ecb2da660ab ("s390: support command lines longer than 896 bytes") Reviewed-by: Heiko Carstens Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/boot/ipl_parm.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/arch/s390/boot/ipl_parm.c b/arch/s390/boot/ipl_parm.c index 6bc950b92be7..59eabf4a2de0 100644 --- a/arch/s390/boot/ipl_parm.c +++ b/arch/s390/boot/ipl_parm.c @@ -23,6 +23,7 @@ struct parmarea parmarea __section(".parmarea") = { }; char __bootdata(early_command_line)[COMMAND_LINE_SIZE]; +static char command_line_buf[COMMAND_LINE_SIZE]; unsigned int __bootdata_preserved(zlib_dfltcc_support) = ZLIB_DFLTCC_FULL; struct ipl_parameter_block __bootdata_preserved(ipl_block); @@ -135,31 +136,29 @@ static size_t ipl_block_get_ascii_scpdata(char *dest, size_t size, static void append_ipl_block_parm(void) { - char *parm, *delim; - size_t len, rc = 0; + size_t len, extra = 0; + char *delim; len = strlen(early_command_line); - - delim = early_command_line + len; /* '\0' character position */ - parm = early_command_line + len + 1; /* append right after '\0' */ + delim = early_command_line + len; /* '\0' character position */ switch (ipl_block.pb0_hdr.pbt) { case IPL_PBT_CCW: - rc = ipl_block_get_ascii_vmparm( - parm, COMMAND_LINE_SIZE - len - 1, &ipl_block); + extra = ipl_block_get_ascii_vmparm(command_line_buf, sizeof(command_line_buf), &ipl_block); break; case IPL_PBT_FCP: case IPL_PBT_NVME: case IPL_PBT_ECKD: - rc = ipl_block_get_ascii_scpdata( - parm, COMMAND_LINE_SIZE - len - 1, &ipl_block); + extra = ipl_block_get_ascii_scpdata(command_line_buf, sizeof(command_line_buf), &ipl_block); break; } - if (rc) { - if (*parm == '=') - memmove(early_command_line, parm + 1, rc); - else + if (extra) { + if (command_line_buf[0] == '=') { + memmove(early_command_line, command_line_buf + 1, extra); + } else if (len < COMMAND_LINE_SIZE - 2) { *delim = ' '; /* replace '\0' with space */ + sized_strscpy(delim + 1, command_line_buf, COMMAND_LINE_SIZE - len - 1); + } } } @@ -245,7 +244,6 @@ static void modify_fac_list(char *str) check_cleared_facilities(); } -static char command_line_buf[COMMAND_LINE_SIZE]; void parse_boot_command_line(void) { char *param, *val; From 12373ea918a0e72483662095686556eea21d67bc Mon Sep 17 00:00:00 2001 From: Vasily Gorbik Date: Wed, 19 Aug 2026 12:31:10 +0200 Subject: [PATCH 0404/2366] s390/boot: Bound command line facility ranges The facilities and debug-alternative command line parsers iterate over inclusive numeric ranges. If a range ends at ULONG_MAX, incrementing the current value wraps to zero and the loop never terminates. Large finite out-of-range values also cause unnecessary early boot iterations even though the bitmap helpers ignore them. Stop each loop at the size of the bitmap it modifies. This preserves all meaningful range values while guaranteeing termination. Reviewed-by: Heiko Carstens Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/boot/alternative.c | 5 +++-- arch/s390/boot/ipl_parm.c | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/s390/boot/alternative.c b/arch/s390/boot/alternative.c index 19ea7934b918..77e8bad560c5 100644 --- a/arch/s390/boot/alternative.c +++ b/arch/s390/boot/alternative.c @@ -45,11 +45,12 @@ static void alt_debug_modify(int type, unsigned int nr, bool clear) static char *alt_debug_parse(int type, char *str) { - unsigned long val, endval; + unsigned long val, endval, limit; char *endp; bool clear; int i; + limit = type == ALT_TYPE_FACILITY ? MAX_FACILITY_BIT : MAX_MFEATURE_BIT; if (*str == ':') { str++; } else { @@ -73,7 +74,7 @@ static char *alt_debug_parse(int type, char *str) if (str == endp) break; str = endp; - while (val <= endval) { + while (val <= endval && val < limit) { alt_debug_modify(type, val, clear); val++; } diff --git a/arch/s390/boot/ipl_parm.c b/arch/s390/boot/ipl_parm.c index 59eabf4a2de0..c1b43e5e688a 100644 --- a/arch/s390/boot/ipl_parm.c +++ b/arch/s390/boot/ipl_parm.c @@ -230,7 +230,7 @@ static void modify_fac_list(char *str) if (str == endp) break; str = endp; - while (val <= endval) { + while (val <= endval && val < MAX_FACILITY_BIT) { modify_facility(val, clear); val++; } From 33123ff9cbcb35640f56efb8ede1d6f0d97376fd Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 27 Aug 2026 12:28:32 +0200 Subject: [PATCH 0405/2366] s390/mm: Simplify crst_table_upgrade() In case of an upgrade from four to five level page tables, and a failing pgd allocation, the exit path of crst_table_upgrade() would incorrectly dereference the p4d NULL pointer via pagetable_dtor(). Address this by reworking crst_table_upgrade(), which basically is a revert of [1]. Take into account that GFP_KERNEL order-2 allocation failures are very unlikely. Therefore keep the code as simple as possible: In case of an upgrade from three to five levels, and an allocation failure of the fifth page table level, keep the upgrade to four levels instead of reverting back to three levels. This allows to keep error handling minimal. [1] commit 31932757c612 ("s390/mm: optimize page table upgrade routine") Reviewed-by: Alexander Gordeev Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- arch/s390/mm/pgalloc.c | 89 +++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 53 deletions(-) diff --git a/arch/s390/mm/pgalloc.c b/arch/s390/mm/pgalloc.c index 9610770fcf6d..4b160eedc5a0 100644 --- a/arch/s390/mm/pgalloc.c +++ b/arch/s390/mm/pgalloc.c @@ -55,63 +55,46 @@ static void __crst_table_upgrade(void *arg) int crst_table_upgrade(struct mm_struct *mm, unsigned long end) { - unsigned long *pgd = NULL, *p4d = NULL, *__pgd; - unsigned long asce_limit = mm->context.asce_limit; + unsigned long *table, *pgd; + int rc, notify; mmap_assert_write_locked(mm); - /* upgrade should only happen from 3 to 4, 3 to 5, or 4 to 5 levels */ - VM_BUG_ON(asce_limit < _REGION2_SIZE); - - if (end <= asce_limit) - return 0; - - if (asce_limit == _REGION2_SIZE) { - p4d = crst_table_alloc(mm); - if (unlikely(!p4d)) - goto err_p4d; - crst_table_init(p4d, _REGION2_ENTRY_EMPTY); - pagetable_p4d_ctor(virt_to_ptdesc(p4d)); + VM_BUG_ON(mm->context.asce_limit < _REGION2_SIZE); + rc = 0; + notify = 0; + while (mm->context.asce_limit < end) { + table = crst_table_alloc(mm); + if (!table) { + rc = -ENOMEM; + break; + } + spin_lock_bh(&mm->page_table_lock); + pgd = (unsigned long *)mm->pgd; + if (mm->context.asce_limit == _REGION2_SIZE) { + crst_table_init(table, _REGION2_ENTRY_EMPTY); + p4d_populate(mm, (p4d_t *)table, (pud_t *)pgd); + pagetable_p4d_ctor(virt_to_ptdesc(table)); + mm->pgd = (pgd_t *)table; + mm->context.asce_limit = _REGION1_SIZE; + mm->context.asce = __pa(mm->pgd) | _ASCE_TABLE_LENGTH | + _ASCE_USER_BITS | _ASCE_TYPE_REGION2; + mm_inc_nr_puds(mm); + } else { + crst_table_init(table, _REGION1_ENTRY_EMPTY); + pgd_populate(mm, (pgd_t *)table, (p4d_t *)pgd); + pagetable_pgd_ctor(virt_to_ptdesc(table)); + mm->pgd = (pgd_t *)table; + mm->context.asce_limit = TASK_SIZE_MAX; + mm->context.asce = __pa(mm->pgd) | _ASCE_TABLE_LENGTH | + _ASCE_USER_BITS | _ASCE_TYPE_REGION1; + } + notify = 1; + spin_unlock_bh(&mm->page_table_lock); } - if (end > _REGION1_SIZE) { - pgd = crst_table_alloc(mm); - if (unlikely(!pgd)) - goto err_pgd; - crst_table_init(pgd, _REGION1_ENTRY_EMPTY); - pagetable_pgd_ctor(virt_to_ptdesc(pgd)); - } - - spin_lock_bh(&mm->page_table_lock); - - if (p4d) { - __pgd = (unsigned long *) mm->pgd; - p4d_populate(mm, (p4d_t *) p4d, (pud_t *) __pgd); - mm->pgd = (pgd_t *) p4d; - mm->context.asce_limit = _REGION1_SIZE; - mm->context.asce = __pa(mm->pgd) | _ASCE_TABLE_LENGTH | - _ASCE_USER_BITS | _ASCE_TYPE_REGION2; - mm_inc_nr_puds(mm); - } - if (pgd) { - __pgd = (unsigned long *) mm->pgd; - pgd_populate(mm, (pgd_t *) pgd, (p4d_t *) __pgd); - mm->pgd = (pgd_t *) pgd; - mm->context.asce_limit = TASK_SIZE_MAX; - mm->context.asce = __pa(mm->pgd) | _ASCE_TABLE_LENGTH | - _ASCE_USER_BITS | _ASCE_TYPE_REGION1; - } - - spin_unlock_bh(&mm->page_table_lock); - - on_each_cpu(__crst_table_upgrade, mm, 0); - - return 0; - -err_pgd: - pagetable_dtor(virt_to_ptdesc(p4d)); - crst_table_free(mm, p4d); -err_p4d: - return -ENOMEM; + if (notify) + on_each_cpu(__crst_table_upgrade, mm, 0); + return rc; } unsigned long *page_table_alloc_noprof(struct mm_struct *mm) From 98d23edcd41432286cf03672252507a841323c8c Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Tue, 25 Aug 2026 18:01:54 +0200 Subject: [PATCH 0406/2366] s390/zcrypt: Fix uninitialized padding in CRT key structure The zcrypt_type6_crt_key() function leaves padding bytes uninitialized between key components and the modulus in the CCA CRT key token. These padding bytes are sent to the crypto card, potentially leaking kernel memory contents. The initial memset() only zeros fixed structure fields, not the flexible array member key_parts[] where the padding resides. While key components are properly copied from userspace, the calculated pad_len bytes between them remain uninitialized. Fix by explicitly zeroing the padding bytes after copying the CRT key components. Signed-off-by: Harald Freudenberger Reviewed-by: Finn Callies Signed-off-by: Vasily Gorbik Signed-off-by: Heiko Carstens --- drivers/s390/crypto/zcrypt_cca_key.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/s390/crypto/zcrypt_cca_key.h b/drivers/s390/crypto/zcrypt_cca_key.h index f5907b67db29..8a69eed75040 100644 --- a/drivers/s390/crypto/zcrypt_cca_key.h +++ b/drivers/s390/crypto/zcrypt_cca_key.h @@ -219,6 +219,7 @@ static inline int zcrypt_type6_crt_key(struct ica_rsa_modexpo_crt *crt, void *p) copy_from_user(key->key_parts + 2 * long_len + 2 * short_len, crt->u_mult_inv, long_len)) return -EFAULT; + memset(key->key_parts + 3 * long_len + 2 * short_len, 0, pad_len); memset(key->key_parts + 3 * long_len + 2 * short_len + pad_len, 0xff, crt->inputdatalength); pub = (struct cca_public_sec *)(key->key_parts + key_len); From 65320b642025c53063372bd34a376bec7bf15598 Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 08:50:00 +0800 Subject: [PATCH 0407/2366] firmware: arm_scpi: fix device_node leak in scpi_dev_domain_id of_parse_phandle_with_args() takes a reference on clkspec.np that must be released with of_node_put(). scpi_dev_domain_id() returned clkspec.args[0] without dropping that reference, so every domain lookup leaked a device node. Paths such as scpi_dvfs_info() / cpufreq init call this per CPU, so the leak accumulates over time. Save the domain id, of_node_put(clkspec.np), then return the saved value. Signed-off-by: Xixin Liu Link: https://patch.msgid.link/84fdd490495b.v2.1785200642.git.liuxixin@kylinos.cn Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index 2acad5fa5a28..43d6d9bbc7a9 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -661,12 +661,15 @@ static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) static int scpi_dev_domain_id(struct device *dev) { struct of_phandle_args clkspec; + int domain; if (of_parse_phandle_with_args(dev->of_node, "clocks", "#clock-cells", 0, &clkspec)) return -EINVAL; - return clkspec.args[0]; + domain = clkspec.args[0]; + of_node_put(clkspec.np); + return domain; } static struct scpi_dvfs_info *scpi_dvfs_info(struct device *dev) From 32471d84a487c7fd74532bc96be56f8028cf4a3f Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 08:50:00 +0800 Subject: [PATCH 0408/2366] firmware: arm_scpi: reject DVFS OPP count above MAX_DVFS_OPPS scpi_dvfs_get_info() already rejected a zero opp_count, but still trusted any larger value from the SCP firmware. The shared-memory reply only holds MAX_DVFS_OPPS entries in buf.opps[]; a bigger count over-reads that array and then sizes the allocated OPP table incorrectly (garbage OPPs / OOB). The missing upper bound dates back to the original SCPI DVFS support. Reject zero and out-of-range counts in one check and return -EINVAL. Fixes: 8cb7cf56c9fe ("firmware: add support for ARM System Control and Power Interface(SCPI) protocol") Signed-off-by: Xixin Liu Link: https://patch.msgid.link/022802f0b38f.v2.1785200642.git.liuxixin@kylinos.cn Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index 43d6d9bbc7a9..68a730d22037 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -631,8 +631,8 @@ static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) if (ret) return ERR_PTR(ret); - if (!buf.opp_count) - return ERR_PTR(-ENOENT); + if (!buf.opp_count || buf.opp_count > MAX_DVFS_OPPS) + return ERR_PTR(-EINVAL); info = kmalloc_obj(*info); if (!info) From 70f4b78d560e592cbf3325b162424737d032fc1d Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 08:50:00 +0800 Subject: [PATCH 0409/2366] clk: scpi: bound-check DVFS index in scpi_dvfs_recalc_rate dvfs_get_idx() may return an out-of-range index if the SCP firmware is buggy or returns a stale value. Only negative indexes were rejected, so a large index walked past info->opps and could treat garbage as a clock rate (KASAN OOB / wrong frequency to consumers). The missing upper bound dates back to the original SCPI clock driver. Treat indexes >= opp count as invalid and return 0, same as idx < 0. Fixes: cd52c2a4b5c4 ("clk: add support for clocks provided by SCP(System Control Processor)") Signed-off-by: Xixin Liu Link: https://patch.msgid.link/04f9ab766e07.v2.1785200642.git.liuxixin@kylinos.cn Signed-off-by: Sudeep Holla --- drivers/clk/clk-scpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index 2328d2abf6d8..fa4349c8178a 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -73,7 +73,7 @@ static unsigned long scpi_dvfs_recalc_rate(struct clk_hw *hw, int idx = clk->scpi_ops->dvfs_get_idx(clk->id); const struct scpi_opp *opp; - if (idx < 0) + if (idx < 0 || idx >= clk->info->count) return 0; opp = clk->info->opps + idx; From ab06cf8152dace327cd873188e4a036c4e0b5944 Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 08:50:00 +0800 Subject: [PATCH 0410/2366] clk: scpi: register scpi-cpufreq once and clear on failure scpi_clocks_probe() walks clock children and, for each DVFS provider, calls platform_device_register_simple("scpi-cpufreq", -1, ...). Two related bugs: Since all DVFS providers register the fixed scpi-cpufreq device using PLATFORM_DEVID_NONE, a second registration fails with -EEXIST and overwrites the pointer to the successfully registered device. The first device can then no longer be unregistered. Register the virtual device only once. If registration fails, reset the pointer to NULL so a subsequent DVFS provider can retry and the global pointer only represents a successfully registered device. Fixes: 9490f01e2471 ("clk: scpi: add support for cpufreq virtual device") Fixes: 67bcc2c5f1da ("clk: scpi: don't add cpufreq device if the scpi dvfs node is disabled") Signed-off-by: Xixin Liu Link: https://patch.msgid.link/fd1b9199a9c3.v2.1785200642.git.liuxixin@kylinos.cn (sudeep.holla: reworded the commit message to improve readability) Signed-off-by: Sudeep Holla --- drivers/clk/clk-scpi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index fa4349c8178a..f8182175b483 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -272,10 +272,14 @@ static int scpi_clocks_probe(struct platform_device *pdev) if (match->data != &scpi_dvfs_ops) continue; /* Add the virtual cpufreq device if it's DVFS clock provider */ + if (cpufreq_dev) + continue; cpufreq_dev = platform_device_register_simple("scpi-cpufreq", -1, NULL, 0); - if (IS_ERR(cpufreq_dev)) + if (IS_ERR(cpufreq_dev)) { pr_warn("unable to register cpufreq device"); + cpufreq_dev = NULL; + } } return 0; } From 86d923a882f48b047b079a293abb6ebca3eaf23f Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 28 Jul 2026 09:27:31 +0800 Subject: [PATCH 0411/2366] clk: scpi: use PLATFORM_DEVID_NONE for scpi-cpufreq Replace the magic -1 passed to platform_device_register_simple() with PLATFORM_DEVID_NONE. This is a readability cleanup only and does not change behavior. Signed-off-by: Xixin Liu Link: https://patch.msgid.link/fb51cfbfbb41.v2.1785200642.git.liuxixin@kylinos.cn Signed-off-by: Sudeep Holla --- drivers/clk/clk-scpi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index f8182175b483..b2d412ca1d22 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -275,7 +275,8 @@ static int scpi_clocks_probe(struct platform_device *pdev) if (cpufreq_dev) continue; cpufreq_dev = platform_device_register_simple("scpi-cpufreq", - -1, NULL, 0); + PLATFORM_DEVID_NONE, + NULL, 0); if (IS_ERR(cpufreq_dev)) { pr_warn("unable to register cpufreq device"); cpufreq_dev = NULL; From 148845aa1921d95ef5dc851c76e6a284f6657df6 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Wed, 26 Aug 2026 00:25:33 +0000 Subject: [PATCH 0412/2366] dm-crypt: fix a tiny race condition in crypt_dec_pending crypt_dec_pending reads io->error before calling atomic_dec_and_test. Another context, for example crypt_endio called from an interrupt, may set io->error and drop its reference between the read and the decrement. crypt_dec_pending then drops the last reference and completes the bio with the stale status - so a read that failed and was never decrypted, or a write that failed, is reported as successful. The read was placed before the decrement by commit b35f8caa0890 ("dm crypt: wait for endio to complete before destruction"), because that commit freed dm_crypt_io before calling bio_endio. This is no longer the case, dm_crypt_io lives in the per-bio data now. Read io->error after atomic_dec_and_test instead. atomic_dec_and_test is fully ordered, so no additional barrier is needed. Fixes: b35f8caa0890 ("dm crypt: wait for endio to complete before destruction") Cc: stable@vger.kernel.org Reviewed-by: Jose Fernandez (Anthropic) Signed-off-by: Ben Cressey Assisted-by: Claude:unspecified Signed-off-by: Mikulas Patocka --- drivers/md/dm-crypt.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 608b617fb817..9e170de50ad3 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -1745,7 +1745,6 @@ static void crypt_dec_pending(struct dm_crypt_io *io) { struct crypt_config *cc = io->cc; struct bio *base_bio = io->base_bio; - blk_status_t error = io->error; if (!atomic_dec_and_test(&io->io_pending)) return; @@ -1767,7 +1766,7 @@ static void crypt_dec_pending(struct dm_crypt_io *io) else kfree(io->integrity_metadata); - base_bio->bi_status = error; + base_bio->bi_status = io->error; bio_endio(base_bio); } From bc9781c0247de107876f32929f1637db93a42b34 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Tue, 25 Aug 2026 15:22:26 -0400 Subject: [PATCH 0413/2366] dm cache: fix issue with background work locking dm cache used a rw_semaphore for background_work_lock. Write locks on rw_semaphores have strict owner semantics, but there was no guarantee that the process that locked background_work_lock was the same process that unlocked it. This can be easily seen using a kernel compiled with CONFIG_DEBUG_RWSEMS. Given a dm cache device , run: 'dmsetup suspend && dmsetup resume '. This will trigger a kernel warning: DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && !rwsem_test_oflags(sem, RWSEM_NONSPINNABLE)) triggered by cache_resume(). To fix this, switch from a rw_semaphore to a spinlock and a wait queue. dm cache already has a wait queue and associated counter, migration_wait and nr_allocated_migrations, that was getting woken up when background work was getting completed, but wasn't actually used by anything. This is replaced by the background_work queue and counter. Fixes: b29d4986d0da ("dm cache: significant rework to leverage dm-bio-prison-v2") Cc: stable@vger.kernel.org Signed-off-by: Benjamin Marzinski Reviewed-by: Matthew Sakai Reviewed-by: Ming-Hung Tsai Signed-off-by: Mikulas Patocka --- drivers/md/dm-cache-target.c | 55 ++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c index 33dbc71b730f..d8d9c63d2a67 100644 --- a/drivers/md/dm-cache-target.c +++ b/drivers/md/dm-cache-target.c @@ -340,8 +340,6 @@ struct cache { struct list_head invalidation_requests; sector_t migration_threshold; - wait_queue_head_t migration_wait; - atomic_t nr_allocated_migrations; /* * The number of in flight migrations that are performing @@ -397,7 +395,11 @@ struct cache { bool loaded_mappings:1; bool loaded_discards:1; - struct rw_semaphore background_work_lock; + /* background work management */ + bool background_work_allowed; + unsigned background_work_nr; + spinlock_t background_work_lock; + wait_queue_head_t background_work_wait; struct batcher committer; struct work_struct commit_ws; @@ -488,19 +490,13 @@ static struct dm_cache_migration *alloc_migration(struct cache *cache) memset(mg, 0, sizeof(*mg)); mg->cache = cache; - atomic_inc(&cache->nr_allocated_migrations); return mg; } static void free_migration(struct dm_cache_migration *mg) { - struct cache *cache = mg->cache; - - if (atomic_dec_and_test(&cache->nr_allocated_migrations)) - wake_up(&cache->migration_wait); - - mempool_free(mg, &cache->migration_pool); + mempool_free(mg, &mg->cache->migration_pool); } /*----------------------------------------------------------------*/ @@ -1030,34 +1026,39 @@ static void calc_discard_block_range(struct cache *cache, struct bio *bio, static void prevent_background_work(struct cache *cache) { - lockdep_off(); - down_write(&cache->background_work_lock); - lockdep_on(); + spin_lock_irq(&cache->background_work_lock); + cache->background_work_allowed = false; + wait_event_lock_irq(cache->background_work_wait, + cache->background_work_nr == 0, + cache->background_work_lock); + spin_unlock_irq(&cache->background_work_lock); } static void allow_background_work(struct cache *cache) { - lockdep_off(); - up_write(&cache->background_work_lock); - lockdep_on(); + spin_lock_irq(&cache->background_work_lock); + cache->background_work_allowed = true; + spin_unlock_irq(&cache->background_work_lock); } static bool background_work_begin(struct cache *cache) { bool r; - lockdep_off(); - r = down_read_trylock(&cache->background_work_lock); - lockdep_on(); - + spin_lock_irq(&cache->background_work_lock); + r = cache->background_work_allowed; + if (r) + cache->background_work_nr++; + spin_unlock_irq(&cache->background_work_lock); return r; } static void background_work_end(struct cache *cache) { - lockdep_off(); - up_read(&cache->background_work_lock); - lockdep_on(); + spin_lock_irq(&cache->background_work_lock); + if (--cache->background_work_nr == 0) + wake_up(&cache->background_work_wait); + spin_unlock_irq(&cache->background_work_lock); } /*----------------------------------------------------------------*/ @@ -2507,9 +2508,7 @@ static int cache_create(struct cache_args *ca, struct cache **result) spin_lock_init(&cache->lock); bio_list_init(&cache->deferred_bios); - atomic_set(&cache->nr_allocated_migrations, 0); atomic_set(&cache->nr_io_migrations, 0); - init_waitqueue_head(&cache->migration_wait); r = -ENOMEM; atomic_set(&cache->nr_dirty, 0); @@ -2592,8 +2591,10 @@ static int cache_create(struct cache_args *ca, struct cache **result) issue_op, cache, cache->wq); dm_iot_init(&cache->tracker); - init_rwsem(&cache->background_work_lock); - prevent_background_work(cache); + init_waitqueue_head(&cache->background_work_wait); + spin_lock_init(&cache->background_work_lock); + cache->background_work_allowed = false; + cache->background_work_nr = 0; *result = cache; return 0; From b2fd92f016e9d692fd3c8c08d0ee014e9212279d Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Mon, 24 Aug 2026 19:34:49 +0800 Subject: [PATCH 0414/2366] dm-integrity: require stable writes for internal hash modes dm-integrity direct, bitmap and inline internal-hash modes compute integrity tags from the pages carried by the write bio. The lower data write also uses those pages, so the tag and the data write depend on the same memory contents staying unchanged while writeback is in flight. Without stable writes, a buffered writer can modify a writeback folio after dm-integrity has submitted the data bio and before the lower device has consumed the data. After a crash, this can leave data from the later contents with a tag calculated from the earlier contents, causing permanent checksum failures on read. Set BLK_FEAT_STABLE_WRITES for internal-hash D, B and I modes so filesystems wait for writeback folios to become stable before modifying them again. Journal mode is left unchanged because it copies data into the journal before computing and persisting the tag. Tested using dm-delay over a virtio-blk test disk, dm-integrity internal_hash:crc32c and no-journal ext4. The D and B reproducers both failed with checksum errors before this change and completed with READ_RC=0 and zero mismatches after it. Fixes: 7eada909bfd7 ("dm: add integrity target") Cc: stable@vger.kernel.org Reported-by: Sun Yangkai Link: https://github.com/chencheng-fnnas/reproducer/blob/main/dm-integrity-writeback-race.py Signed-off-by: Chen Cheng Signed-off-by: Mikulas Patocka --- drivers/md/dm-integrity.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index c50feaa98bf9..0370d7d7ce72 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -4130,6 +4130,10 @@ static void dm_integrity_io_hints(struct dm_target *ti, struct queue_limits *lim limits->dma_alignment = limits->logical_block_size - 1; limits->discard_granularity = ic->sectors_per_block << SECTOR_SHIFT; + if (ic->internal_hash && + (ic->mode == 'D' || ic->mode == 'B' || ic->mode == 'I')) + limits->features |= BLK_FEAT_STABLE_WRITES; + if (!ic->internal_hash) { struct blk_integrity *bi = &limits->integrity; From 59e6f919d77d72ec79cbf171256f2f7819737580 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Thu, 20 Aug 2026 21:44:57 +0000 Subject: [PATCH 0415/2366] dm-integrity: fix buffer overflow with keyed discard Since commit 68c5c42567bc ("dm-integrity: replace forgeable discard filler with a keyed sector marker"), integrity_metadata computes a checksum for every discarded block into the "checksums" buffer. integrity_sector_checksum always writes the whole digest. So if the tag size is smaller than the digest size, the checksum of the last block that fits into the buffer is written past the end of it. For example, with hmac(sha256) and tag size 16, a 4MiB discard writes 16 bytes past the kmalloc'ed page. Fix this by subtracting extra_space from the buffer size when computing max_blocks, like we do for writes. Fixes: 68c5c42567bc ("dm-integrity: replace forgeable discard filler with a keyed sector marker") Reviewed-by: Jose Fernandez (Anthropic) Signed-off-by: Ben Cressey Assisted-by: Claude:unspecified Signed-off-by: Mikulas Patocka --- drivers/md/dm-integrity.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 0370d7d7ce72..d4fe85d61d33 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -1980,7 +1980,7 @@ static void integrity_metadata(struct work_struct *w) if (unlikely(dio->op == REQ_OP_DISCARD)) { unsigned int bi_size = dio->bio_details.bi_iter.bi_size; unsigned int max_size = likely(checksums != checksums_onstack) ? PAGE_SIZE : HASH_MAX_DIGESTSIZE; - unsigned int max_blocks = max_size / ic->tag_size; + unsigned int max_blocks = (max_size - extra_space) / ic->tag_size; sector_t sector = dio->range.logical_sector; if (!ic->discard_keyed) From 18d80c77b4c7dd20699e81cedfbbff4e9d198f28 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Thu, 20 Aug 2026 21:44:58 +0000 Subject: [PATCH 0416/2366] dm-integrity: fix infinite loop on discard with large tag size When integrity_metadata handles a discard, it fills a buffer with DISCARD_FILLER and writes it over the tags, max_blocks blocks at a time. If the kmalloc fails, the buffer is the on-stack array checksums_onstack and max_size is set to HASH_MAX_DIGESTSIZE. So if the tag size is larger than HASH_MAX_DIGESTSIZE, max_blocks is zero, bi_size is never decremented and the loop never terminates. Fix this by using sizeof(checksums_onstack) as max_size. The array has MAX_TAG_SIZE bytes since commit b93b6643e9b5 ("dm integrity: fix a crash with unusually large tag size"), so max_blocks is at least 1. Fixes: 84597a44a9d8 ("dm integrity: add optional discard support") Cc: stable@vger.kernel.org Reviewed-by: Jose Fernandez (Anthropic) Signed-off-by: Ben Cressey Assisted-by: Claude:unspecified Signed-off-by: Mikulas Patocka --- drivers/md/dm-integrity.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index d4fe85d61d33..5327d7c6a71c 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -1979,7 +1979,7 @@ static void integrity_metadata(struct work_struct *w) if (unlikely(dio->op == REQ_OP_DISCARD)) { unsigned int bi_size = dio->bio_details.bi_iter.bi_size; - unsigned int max_size = likely(checksums != checksums_onstack) ? PAGE_SIZE : HASH_MAX_DIGESTSIZE; + unsigned int max_size = likely(checksums != checksums_onstack) ? PAGE_SIZE : sizeof(checksums_onstack); unsigned int max_blocks = (max_size - extra_space) / ic->tag_size; sector_t sector = dio->range.logical_sector; From 769001ce838d907ecaa95f1d0a4e8fc86f761f9f Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 26 Jul 2026 19:15:30 +0800 Subject: [PATCH 0417/2366] RDMA/rxe: Restore HMM_PFN_WRITE check in ODP write paths Commit 0b261d7c1cd3 ("RDMA/rxe: Break endless pagefault loop for RO pages") dropped the access permission test from rxe_check_pagefault() and left only HMM_PFN_VALID. A page faulted in read-only, for example a page-cache folio behind a PROT_READ file mapping, then satisfies the check and ODP write operations (RDMA WRITE, RDMA READ response, SEND payload, atomics) modify it through kmap without ever breaking CoW. An unprivileged user can register an ODP MR over such a mapping and have incoming RDMA traffic overwrite the page cache of a file it only holds O_RDONLY, including /etc/passwd or setuid binaries. This is the same primitive class as Dirty COW and CVE-2022-2590. mlx5 has the missing invariant: its ODP path sets the device write bit only for pfns that carry HMM_PFN_WRITE. Restore it in rxe by requiring HMM_PFN_WRITE in rxe_check_pagefault() for every operation except RXE_PAGEFAULT_RDONLY. A write to a non-writable VMA now fails the one fault attempt with -EPERM from hmm_vma_fault() instead of re-faulting forever. For a writable VMA the fault breaks CoW and the write lands in the private page. Keep pmem flushes on the read-only check. arch_wb_cache_pmem() never modifies memory, and the FLUSH access bits do not make the umem writable, so classifying flushes as writes would make every flush against a flush-only MR fail. Fixes: 0b261d7c1cd3 ("RDMA/rxe: Break endless pagefault loop for RO pages") Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20260726111533.1037819-1-bestswngs@gmail.com Reviewed-by: Zhu Yanjun Tested-by: Hongqiang Luo Tested-by: Xinyu Ma Tested-by: Zhanbo Ye Reported-by: Weiming Shi Reported-by: Shaomin Chen Reported-by: Rui Ding Reported-by: Miao Zhao Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/rxe/rxe_odp.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_odp.c b/drivers/infiniband/sw/rxe/rxe_odp.c index e870efa7a0a3..ab21b620e94c 100644 --- a/drivers/infiniband/sw/rxe/rxe_odp.c +++ b/drivers/infiniband/sw/rxe/rxe_odp.c @@ -120,19 +120,23 @@ int rxe_odp_mr_init_user(struct rxe_dev *rxe, u64 start, u64 length, } static inline bool rxe_check_pagefault(struct ib_umem_odp *umem_odp, u64 iova, - int length) + int length, bool write) { bool need_fault = false; + u64 access = HMM_PFN_VALID; u64 addr; int idx; + if (write) + access |= HMM_PFN_WRITE; + addr = iova & (~(BIT(umem_odp->page_shift) - 1)); /* Skim through all pages that are to be accessed. */ while (addr < iova + length) { idx = (addr - ib_umem_start(umem_odp)) >> umem_odp->page_shift; - if (!(umem_odp->map.pfn_list[idx] & HMM_PFN_VALID)) { + if ((umem_odp->map.pfn_list[idx] & access) != access) { need_fault = true; break; } @@ -155,6 +159,7 @@ static unsigned long rxe_odp_iova_to_page_offset(struct ib_umem_odp *umem_odp, u static int rxe_odp_map_range_and_lock(struct rxe_mr *mr, u64 iova, int length, u32 flags) { struct ib_umem_odp *umem_odp = to_ib_umem_odp(mr->umem); + bool write = !(flags & RXE_PAGEFAULT_RDONLY); bool need_fault; int err; @@ -163,7 +168,7 @@ static int rxe_odp_map_range_and_lock(struct rxe_mr *mr, u64 iova, int length, u mutex_lock(&umem_odp->umem_mutex); - need_fault = rxe_check_pagefault(umem_odp, iova, length); + need_fault = rxe_check_pagefault(umem_odp, iova, length, write); if (need_fault) { mutex_unlock(&umem_odp->umem_mutex); @@ -173,7 +178,7 @@ static int rxe_odp_map_range_and_lock(struct rxe_mr *mr, u64 iova, int length, u if (err < 0) return err; - need_fault = rxe_check_pagefault(umem_odp, iova, length); + need_fault = rxe_check_pagefault(umem_odp, iova, length, write); if (need_fault) { mutex_unlock(&umem_odp->umem_mutex); return -EFAULT; @@ -335,8 +340,9 @@ int rxe_odp_flush_pmem_iova(struct rxe_mr *mr, u64 iova, int err; u8 *va; + /* A flush never modifies memory; read-only access suffices. */ err = rxe_odp_map_range_and_lock(mr, iova, length, - RXE_PAGEFAULT_DEFAULT); + RXE_PAGEFAULT_RDONLY); if (err) return err; From d3dbccfe6afa7b9a6a7ed65cfaa76a47fa050a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan=20Ghe=C8=9Bu?= Date: Sun, 30 Aug 2026 23:51:06 +0300 Subject: [PATCH 0418/2366] ASoC: fsl_micfil: balance mclk enable/disable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hw_params() enables mclk unconditionally and hw_free() disables it unconditionally, but the PCM core does not guarantee 1:1 pairing: hw_free() can run without hw_params(), and hw_params() can be called multiple times from the SETUP state. This triggers an "already disabled" WARN() in the first case and leaks an enable reference in the second, leaving the clock ungateable. Guard both sides with the existing mclk_flag, as fsl_sai.c does with mclk_streams. Fixes: b47024dc624b ("ASoC: fsl_micfil: Add mclk enable flag") Signed-off-by: Ștefan Ghețu Reviewed-by: Chancel Liu Link: https://patch.msgid.link/20260830205106.11267-1-stefanghetu9@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_micfil.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index 60ac8eabab9d..5d8f0f76ab46 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -953,12 +953,17 @@ static int fsl_micfil_reparent_rootclk(struct fsl_micfil *micfil, unsigned int s /* Get root clock */ clk = micfil->mclk; - /* Disable clock first, for it was enabled by pm_runtime */ + /* Reparent root clock to the PLL matching this sample rate */ fsl_asoc_reparent_pll_clocks(dev, clk, micfil->pll8k_clk, micfil->pll11k_clk, ratio); - ret = clk_prepare_enable(clk); - if (ret) - return ret; + + /* Enable only once; hw_params can be called multiple times */ + if (!micfil->mclk_flag) { + ret = clk_prepare_enable(clk); + if (ret) + return ret; + micfil->mclk_flag = true; + } return 0; } @@ -991,8 +996,6 @@ static int fsl_micfil_hw_params(struct snd_pcm_substream *substream, if (ret) return ret; - micfil->mclk_flag = true; - /* floor(K * CLKDIV) */ switch (micfil->quality) { case QUALITY_HIGH: @@ -1068,8 +1071,10 @@ static int fsl_micfil_hw_free(struct snd_pcm_substream *substream, { struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); - clk_disable_unprepare(micfil->mclk); - micfil->mclk_flag = false; + if (micfil->mclk_flag) { + clk_disable_unprepare(micfil->mclk); + micfil->mclk_flag = false; + } return 0; } From 1caceeb2d74bbe88223aea55eb8626b4c5f076fd Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:27:28 -0400 Subject: [PATCH 0419/2366] RDMA/rxe: insert mcg into mcg_tree only after rxe_mcast_add() succeeds rxe_get_mcg() publishes a newly allocated multicast group in rxe->mcg_tree before programming the backing Ethernet multicast address with rxe_mcast_add(), which runs outside mcg_lock. A local userspace RDMA client reaches this path with ATTACH_MCAST on a UD QP; if rxe_mcast_add() then returns an error (for example -ENODEV when the backing netdev has been removed, or a propagated dev_mc_add() error), the unwind frees the published group without removing it from the tree. A later lookup of the same MGID dereferences the freed struct rxe_mcg from __rxe_lookup_mcg(). Fix this by keeping the new mcg private until rxe_mcast_add() succeeds. Split the tree publication into __rxe_publish_mcg(), call rxe_mcast_add() before taking the tree reference, and free the still-private mcg on failure. Because the group is never visible in mcg_tree until the multicast address is programmed, no concurrent caller can look it up or attach a QP to a group that is about to be torn down, so the error path needs no conditional unwind. If another caller publishes the same MGID while the address is being programmed, the post-add re-check under mcg_lock finds the winner; this caller then drops its private object and balances its own rxe_mcast_add() with rxe_mcast_del() before returning the winner. Reproduced by forcing the rxe_mcast_add() error return under KASAN: without the change the next attach to the same MGID reports a slab-use-after-free in __rxe_lookup_mcg(); with it the forced failure returns cleanly. A no-injection attach/detach regression, including a two-QP shared join/leave and re-attach, stays KASAN- and leak-clean. Fixes: a926a903b7dc ("RDMA/rxe: Do not call dev_mc_add/del() under a spinlock") Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/20260617022728.2770116-1-michael.bommarito@gmail.com Reviewed-by: Zhu Yanjun Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/rxe/rxe_mcast.c | 50 +++++++++++++++++++-------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_mcast.c b/drivers/infiniband/sw/rxe/rxe_mcast.c index acd03bd87794..5ca9211ab33b 100644 --- a/drivers/infiniband/sw/rxe/rxe_mcast.c +++ b/drivers/infiniband/sw/rxe/rxe_mcast.c @@ -175,7 +175,9 @@ struct rxe_mcg *rxe_lookup_mcg(struct rxe_dev *rxe, union ib_gid *mgid) * @mgid: multicast address as a gid * @mcg: new mcg object * - * Context: caller should hold rxe->mcg lock + * Initializes the mcg fields. The mcg is private and not yet visible in + * mcg_tree, so this may run without rxe->mcg_lock; __rxe_publish_mcg() + * makes it visible under the lock once it is ready. */ static void __rxe_init_mcg(struct rxe_dev *rxe, union ib_gid *mgid, struct rxe_mcg *mcg) @@ -184,13 +186,22 @@ static void __rxe_init_mcg(struct rxe_dev *rxe, union ib_gid *mgid, memcpy(&mcg->mgid, mgid, sizeof(mcg->mgid)); INIT_LIST_HEAD(&mcg->qp_list); mcg->rxe = rxe; +} - /* caller holds a ref on mcg but that will be - * dropped when mcg goes out of scope. We need to take a ref - * on the pointer that will be saved in the red-black tree - * by __rxe_insert_mcg and used to lookup mcg from mgid later. - * Inserting mcg makes it visible to outside so this should - * be done last after the object is ready. +/** + * __rxe_publish_mcg - make a fully initialized mcg visible in mcg_tree + * @mcg: the mcg object + * + * Context: caller must hold rxe->mcg_lock and a reference on mcg + */ +static void __rxe_publish_mcg(struct rxe_mcg *mcg) +{ + /* caller holds a ref on mcg but that will be dropped when mcg goes + * out of scope. We need to take a ref on the pointer that will be + * saved in the red-black tree by __rxe_insert_mcg and used to lookup + * mcg from mgid later. Inserting mcg makes it visible to outside so + * this is done last after the object is ready and the multicast + * address has been programmed. */ kref_get(&mcg->ref_cnt); __rxe_insert_mcg(mcg); @@ -228,26 +239,37 @@ static struct rxe_mcg *rxe_get_mcg(struct rxe_dev *rxe, union ib_gid *mgid) err = -ENOMEM; goto err_dec; } + __rxe_init_mcg(rxe, mgid, mcg); + + /* program the multicast address while mcg is still private, before + * it is inserted into mcg_tree. dev_mc_add() may sleep so this must + * run outside mcg_lock. On failure mcg was never published, so a + * plain free is correct and the tree is untouched. + */ + err = rxe_mcast_add(rxe, mgid); + if (err) { + kfree(mcg); + goto err_dec; + } spin_lock_bh(&rxe->mcg_lock); - /* re-check to see if someone else just added it */ + /* re-check to see if someone else just added it while we were adding + * the multicast address; if so use theirs and drop ours + */ tmp = __rxe_lookup_mcg(rxe, mgid); if (tmp) { spin_unlock_bh(&rxe->mcg_lock); + rxe_mcast_del(rxe, mgid); atomic_dec(&rxe->mcg_num); kfree(mcg); return tmp; } - __rxe_init_mcg(rxe, mgid, mcg); + __rxe_publish_mcg(mcg); spin_unlock_bh(&rxe->mcg_lock); - /* add mcast address outside of lock */ - err = rxe_mcast_add(rxe, mgid); - if (!err) - return mcg; + return mcg; - kfree(mcg); err_dec: atomic_dec(&rxe->mcg_num); return ERR_PTR(err); From 4ed5bfc41071a48d609a6eaf41d68b2fd09e69d6 Mon Sep 17 00:00:00 2001 From: Jairaj Arava Date: Tue, 1 Sep 2026 09:19:51 +0800 Subject: [PATCH 0420/2366] ASoC: Intel: sof_rt5682: Add support for nvl_max98360a_rt5682 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds the driver data for rt5682 codec on SSP0 and max98360a speaker amplifiers on SSP1 for NVL platform. The existing one is not aligned with PTL. Hence, changed it to align with the working changes in PTL. Signed-off-by: Jairaj Arava Reviewed-by: Péter Ujfalusi Signed-off-by: Bard Liao Link: https://patch.msgid.link/20260901011951.230315-1-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_rt5682.c | 8 ++++++++ sound/soc/intel/common/soc-acpi-intel-nvl-match.c | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c index 7899f7ffd99b..88cf5c0ab2e3 100644 --- a/sound/soc/intel/boards/sof_rt5682.c +++ b/sound/soc/intel/boards/sof_rt5682.c @@ -908,6 +908,14 @@ static const struct platform_device_id board_ids[] = { SOF_SSP_PORT_BT_OFFLOAD(2) | SOF_BT_OFFLOAD_PRESENT), }, + { + .name = "nvl_rt5682_def", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_SSP_PORT_CODEC(0) | + SOF_SSP_PORT_AMP(1) | + SOF_SSP_PORT_BT_OFFLOAD(2) | + SOF_BT_OFFLOAD_PRESENT), + }, { .name = "ptl_rt5682_c1_h02", .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | diff --git a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c index 8f6e987c791e..4a67f6b72fa5 100644 --- a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c @@ -51,10 +51,10 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_nvl_machines[] = { }, { .comp_ids = &nvl_rt5682_rt5682s_hp, - .drv_name = "sof_rt5682", - .sof_tplg_filename = "sof-nvl-rt5682", /* the tplg suffix is added at run time */ - .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | - SND_SOC_ACPI_TPLG_INTEL_SSP_MSB, + .drv_name = "nvl_rt5682_def", + .sof_tplg_filename = "sof-nvl", /* the tplg suffix is added at run time */ + .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_AMP_NAME | + SND_SOC_ACPI_TPLG_INTEL_CODEC_NAME, }, /* place amp/hdmi-in only boards in the end of table */ { From 1d2929d0850fff683b8aff051275945e65f082c8 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Sat, 29 Aug 2026 18:56:18 +0200 Subject: [PATCH 0421/2366] net: psp: do not inherit the Rx association on clone sk->psp_assoc sits past sk_dontcopy_end, so sock_copy() copies it into every socket accepted from a listener without taking a reference, while inet_sock_destruct() puts for every inet socket. psp_twsk_init() does refcount_inc() for the timewait socket, so a child closing through TIME_WAIT cancels its own put and leaves the association with one reference and N timewait sockets holding the same pointer. Closing the listener frees it, and the timewait timers then put freed memory. Rejecting the association on a listening socket is not sufficient: a socket can acquire one while established and then be turned back into a listener, because tcp_disconnect() leaves sk->psp_assoc in place. BUG: KASAN: slab-use-after-free in psp_twsk_assoc_free+0x6f/0xf0 Write of size 4 at addr ffff888110f9255c by task swapper/7/0 psp_twsk_assoc_free+0x6f/0xf0 inet_twsk_put+0xda/0x1b0 call_timer_fn+0x53/0x2e0 __run_timers+0x764/0xa80 Freed by task 99: kfree+0x1a7/0x500 process_one_work+0x7ec/0x1100 An association carries a per-connection SPI and key, so a child must not inherit the parent's. Clear it on clone. Fixes: 6b46ca260e22 ("net: psp: add socket security association code") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Norbert Szetei Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/BC10EB92-ABB3-41B2-AB16-266BEEBE18C0@doyensec.com Signed-off-by: Paolo Abeni --- net/core/sock.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/core/sock.c b/net/core/sock.c index 1ad41904db25..fa60b7494c58 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -2494,6 +2494,9 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority, #ifdef CONFIG_BPF_SYSCALL RCU_INIT_POINTER(newsk->sk_bpf_storage, NULL); #endif +#if IS_ENABLED(CONFIG_INET_PSP) + RCU_INIT_POINTER(newsk->psp_assoc, NULL); +#endif /* SANITY */ if (likely(newsk->sk_net_refcnt)) { From 2ccb8878c149443c6acf628b438c9c942c20abb2 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Tue, 18 Aug 2026 18:05:47 +0800 Subject: [PATCH 0422/2366] dm cache: fix demotion stats in passthrough mode The demotion counter is incremented per incoming write bio before the invalidation begins, causing the demotion count to exceed the actual number of cached blocks when multiple bios target the same cached block. Additionally, the counter is incremented unconditionally regardless of invalidation failure. Reproduce steps: 1. Create a cache device consisting of 512 cache entries modprobe brd rd_size=262144 dmsetup create cmeta --table "0 8192 linear /dev/ram0 0" dmsetup create cdata --table "0 65536 linear /dev/ram0 8192" dmsetup create corig --table "0 65536 linear /dev/ram0 262144" dd if=/dev/zero of=/dev/mapper/cmeta bs=4k count=1 oflag=direct dmsetup create cache --table "0 65536 cache /dev/mapper/cmeta \ /dev/mapper/cdata /dev/mapper/corig 128 2 metadata2 writethrough smq 0" 2. Populate the cache, and record the number of cached blocks fio --name=populate --filename=/dev/mapper/cache --rw=randwrite --bs=4k \ --direct=1 --ioengine=libaio --iodepth=32 --io_size=2048m nr_cached=$(dmsetup status cache | awk '{split($7, a, "/"); print a[1]}') 3. Reload the cache into passthrough mode dmsetup suspend cache dmsetup reload cache --table "0 65536 cache /dev/mapper/cmeta \ /dev/mapper/cdata /dev/mapper/corig 128 2 metadata2 passthrough smq 0" dmsetup resume cache 4. Write to the passthrough cache with multiple jobs to trigger multiple bios hitting the same cached block. fio --filename=/dev/mapper/cache --name=test --rw=write --bs=4k \ --direct=1 --ioengine=libaio --iodepth=32 --numjobs=4 5. Check if demoted matches cached block count. These numbers should match but may differ due to overcounting per bio. nr_demoted=$(dmsetup status cache | awk '{print $12}') echo "$nr_cached, $nr_demoted" Fix by moving the demotion counter increment into invalidate_complete(), gated on the success flag. Reported-by: Ben Marzinski Fixes: b29d4986d0da ("dm cache: significant rework to leverage dm-bio-prison-v2") Cc: stable@vger.kernel.org Signed-off-by: Ming-Hung Tsai Reviewed-by: Benjamin Marzinski Signed-off-by: Mikulas Patocka --- drivers/md/dm-cache-target.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c index d8d9c63d2a67..1a5072425c4a 100644 --- a/drivers/md/dm-cache-target.c +++ b/drivers/md/dm-cache-target.c @@ -1463,6 +1463,9 @@ static void invalidate_complete(struct dm_cache_migration *mg, bool success) struct bio_list bios; struct cache *cache = mg->cache; + if (success) + atomic_inc(&cache->stats.demotion); + bio_list_init(&bios); if (mg->cell) { if (dm_cell_unlock_v2(cache->prison, mg->cell, &bios)) @@ -1734,7 +1737,6 @@ static int map_bio(struct cache *cache, struct bio *bio, dm_oblock_t block, if (passthrough_mode(cache)) { if (bio_data_dir(bio) == WRITE) { bio_drop_shared_lock(cache, bio); - atomic_inc(&cache->stats.demotion); invalidate_start(cache, cblock, block, bio); return DM_MAPIO_SUBMITTED; } else From 975396b9e5a4028e649f4b9a6a5ca5dfb76a824b Mon Sep 17 00:00:00 2001 From: Shuhei Takeshita Date: Sun, 9 Aug 2026 12:27:42 +0900 Subject: [PATCH 0423/2366] IB/hfi1: Resolve the credit-return buffer through the send context's node hfi1_file_mmap()'s PIO_CRED case derives this context's credit-return page offset, and the DMA handle for it, from dd->cr_base[uctxt->numa_id]. uctxt->numa_id is the node of whichever CPU the process happened to be running on, but the entry itself lives in the credit-return allocation of the send context's own node: sc->hw_free = &sc->dd->cr_base[sc->node].va[gc].cr[index]; and user send contexts are allocated with sc_alloc(dd, SC_USER, ..., dd->node), the HFI-local node. On a multi-socket host with the process running off that node the two allocations differ, so the subtraction produces an offset into an unrelated buffer and the DMA handle belongs to the wrong allocation. Use the send context's own node for all three references. The continuation lines are reindented at the same time; they mixed spaces and tabs. Fixes: 7724105686e7 ("IB/hfi1: add driver files") Cc: stable@vger.kernel.org Signed-off-by: Shuhei Takeshita Link: https://patch.msgid.link/20260809032743.2671579-2-jyohuku.alterego@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/hfi1/file_ops.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/hw/hfi1/file_ops.c b/drivers/infiniband/hw/hfi1/file_ops.c index dc548e6802e2..72c21d7455fd 100644 --- a/drivers/infiniband/hw/hfi1/file_ops.c +++ b/drivers/infiniband/hw/hfi1/file_ops.c @@ -382,10 +382,10 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) * of enabled contexts > 64 and 128 respectively). */ cr_page_offset = ((u64)uctxt->sc->hw_free - - (u64)dd->cr_base[uctxt->numa_id].va) & - PAGE_MASK; - memvirt = dd->cr_base[uctxt->numa_id].va + cr_page_offset; - memdma = dd->cr_base[uctxt->numa_id].dma + cr_page_offset; + (u64)dd->cr_base[uctxt->sc->node].va) & + PAGE_MASK; + memvirt = dd->cr_base[uctxt->sc->node].va + cr_page_offset; + memdma = dd->cr_base[uctxt->sc->node].dma + cr_page_offset; memlen = PAGE_SIZE; flags &= ~VM_MAYWRITE; flags |= VM_DONTCOPY | VM_DONTEXPAND; From 62f0f34fbd2b2d5653d33d3b9d42fdcabb1c0101 Mon Sep 17 00:00:00 2001 From: Shuhei Takeshita Date: Sun, 9 Aug 2026 12:27:43 +0900 Subject: [PATCH 0424/2366] IB/hfi1: Fix the PIO_CRED credit-return mmap hfi1_file_mmap()'s PIO_CRED case must hand user space the single credit-return page that holds this context's entry. That page is the second or third page of the per-node credit-return allocation once the hardware send context index reaches 64 or 128, so the failure below is intermittent: when the entry lands on the first page the offset is zero and everything works. Two things are wrong. First, cr_page_offset is a byte offset but .va is a struct credit_return *, so adding it is pointer arithmetic and scales the offset by sizeof(struct credit_return) == 64. memvirt then lands 256 KiB or 512 KiB past a 10240-byte allocation. With an IOMMU translating, that address is inside the vmalloc range but in no vm_area, so dma_mmap_coherent() -> iommu_dma_mmap() finds no pages, vmalloc_to_pfn() returns page_to_pfn(NULL), and remap_pfn_range() installs a frame above MAXPHYADDR. The first user read then takes: psm2_ep_open_pr: Corrupted page table at address 7a14d007e000 PGD 800000013886a067 P4D 800000013886a067 PUD 13886b067 PMD 13886c067 PTE 800049168e911235 Oops: Bad pagetable: 000d [#1] SMP PTI Second, and still wrong once the arithmetic is corrected, dma_mmap_coherent() describes a whole coherent buffer and selects the page within it with vma->vm_pgoff. Offsetting cpu_addr has no effect: for a vmap'd allocation iommu_dma_mmap() uses cpu_addr only to locate the vm_area and then maps pages[vm_pgoff], which hfi1_file_mmap() has just set to 0. User space therefore always receives the first credit-return page, every credit read is for the wrong context, and send PIO stalls forever. Use the DMA API as intended: pass the base of the allocation with its full length and select the page with vm_pgoff. A separate length is needed because memlen must keep describing the VMA for the existing size check. The dma-direct path stays correct as well, since dma_direct_mmap() adds the same vm_pgoff to the base pfn. Tested on a Dell T7610 (Xeon E5-2650 v2, Intel IOMMU in DMA-FQ mode) against a Threadripper PRO 3995WX peer, both Omni-Path 100. Before this change psm2_ep_open() Oopses the kernel; with only the arithmetic corrected psm2_ep_open() succeeds but any transfer that uses send PIO hangs, PSM2_SDMA=2 (send PIO disabled) completing normally while PSM2_SDMA=0 (send PIO only) hangs every time. With this change send PIO, send DMA and the default mixed mode all work. Fixes: 1ec82317a1da ("IB/hfi1: Use dma_mmap_coherent for matching buffers") Cc: stable@vger.kernel.org Signed-off-by: Shuhei Takeshita Link: https://patch.msgid.link/20260809032743.2671579-3-jyohuku.alterego@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/hfi1/file_ops.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/hw/hfi1/file_ops.c b/drivers/infiniband/hw/hfi1/file_ops.c index 72c21d7455fd..1a36f995c4f6 100644 --- a/drivers/infiniband/hw/hfi1/file_ops.c +++ b/drivers/infiniband/hw/hfi1/file_ops.c @@ -326,6 +326,7 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) void *memvirt = NULL; dma_addr_t memdma = 0; u8 subctxt, mapio = 0, vmf = 0, type; + size_t memdmalen = 0; ssize_t memlen = 0; int ret = 0; u16 ctxt; @@ -371,7 +372,9 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) mapio = 1; break; case PIO_CRED: { + struct credit_return_base *cr = &dd->cr_base[uctxt->sc->node]; u64 cr_page_offset; + if (flags & VM_WRITE) { ret = -EPERM; goto done; @@ -381,11 +384,18 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) * second or third page allocated for credit returns (if number * of enabled contexts > 64 and 128 respectively). */ - cr_page_offset = ((u64)uctxt->sc->hw_free - - (u64)dd->cr_base[uctxt->sc->node].va) & + cr_page_offset = ((u64)uctxt->sc->hw_free - (u64)cr->va) & PAGE_MASK; - memvirt = dd->cr_base[uctxt->sc->node].va + cr_page_offset; - memdma = dd->cr_base[uctxt->sc->node].dma + cr_page_offset; + /* + * dma_mmap_coherent() describes the whole coherent buffer and + * selects the page within it with vma->vm_pgoff, so pass the + * base of the allocation and its length and let vm_pgoff pick + * the page. + */ + vma->vm_pgoff = cr_page_offset >> PAGE_SHIFT; + memvirt = cr->va; + memdma = cr->dma; + memdmalen = TXE_NUM_CONTEXTS * sizeof(struct credit_return); memlen = PAGE_SIZE; flags &= ~VM_MAYWRITE; flags |= VM_DONTCOPY | VM_DONTEXPAND; @@ -567,7 +577,8 @@ static int hfi1_file_mmap(struct file *fp, struct vm_area_struct *vma) ret = 0; } else if (memdma) { ret = dma_mmap_coherent(&dd->pcidev->dev, vma, - memvirt, memdma, memlen); + memvirt, memdma, + memdmalen ? memdmalen : memlen); } else if (mapio) { ret = io_remap_pfn_range(vma, vma->vm_start, PFN_DOWN(memaddr), From 2be77295316c2dff0a33c0dc3a65abdab4ccf796 Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Tue, 11 Aug 2026 19:25:57 +0300 Subject: [PATCH 0425/2366] RDMA/mlx5: Remove warn on missing representor in query_port_speed The representor ib_device's phys_port_cnt is set to the total vport count when the uplink vport rep loads. Individual port[i].rep entries are populated only as each VF/SF vport rep registers. A NULL .rep for a given port index is therefore expected while VF reps are still loading or haven't been enabled yet. Tools like ibstat and ibv_devinfo iterate over all ports of all RDMA devices. Some ports may not have an eswitch representor, causing repeated dmesg warnings when these tools run without a device argument. This causes dmesg to be flooded with this message on every ibstat invocation. Remove the warning and return -ENODEV when no representor exists for the queried port. Fixes: aaecff5e13cd ("RDMA/mlx5: Implement query_port_speed callback") Signed-off-by: Or Har-Toov Reviewed-by: Shay Drory Signed-off-by: Edward Srouji Link: https://patch.msgid.link/20260811-remove-warn-on-miss-rep-v1-1-eccf399bc6af@nvidia.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/mlx5/main.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/hw/mlx5/main.c b/drivers/infiniband/hw/mlx5/main.c index 373ee1f42d4a..a457647edacd 100644 --- a/drivers/infiniband/hw/mlx5/main.c +++ b/drivers/infiniband/hw/mlx5/main.c @@ -1683,11 +1683,8 @@ static int mlx5_ib_query_port_speed_rep(struct mlx5_ib_dev *dev, u32 port_num, struct mlx5_core_dev *mdev; u16 op_mod; - if (!dev->port[port_num - 1].rep) { - mlx5_ib_warn(dev, "Representor doesn't exist for port %u\n", - port_num); - return -EINVAL; - } + if (!dev->port[port_num - 1].rep) + return -ENODEV; rep = dev->port[port_num - 1].rep; mdev = mlx5_eswitch_get_core_dev(rep->esw); From 0c06c4ce0206290c9a934a1e7196aaa86adfe018 Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Mon, 24 Aug 2026 14:35:06 +0800 Subject: [PATCH 0426/2366] ASoC: amd: renoir: fix disable_pdm_interrupts() to clear mask bits disable_pdm_interrupts() uses |= ~PDM_DMA_INTR_MASK which sets all bits except the PDM DMA interrupt bit instead of clearing only the PDM DMA interrupt bit. Use &= ~PDM_DMA_INTR_MASK to clear only the target bit. Fixes: f621a3676d3f ("ASoC: amd: add ACP3x PDM platform driver") Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260824063507.483784-1-wangdich9700@163.com Signed-off-by: Mark Brown --- sound/soc/amd/renoir/acp3x-pdm-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/renoir/acp3x-pdm-dma.c b/sound/soc/amd/renoir/acp3x-pdm-dma.c index e60e3821703c..3f59b753243d 100644 --- a/sound/soc/amd/renoir/acp3x-pdm-dma.c +++ b/sound/soc/amd/renoir/acp3x-pdm-dma.c @@ -104,7 +104,7 @@ static void disable_pdm_interrupts(void __iomem *acp_base) u32 ext_int_ctrl; ext_int_ctrl = rn_readl(acp_base + ACP_EXTERNAL_INTR_CNTL); - ext_int_ctrl |= ~PDM_DMA_INTR_MASK; + ext_int_ctrl &= ~PDM_DMA_INTR_MASK; rn_writel(ext_int_ctrl, acp_base + ACP_EXTERNAL_INTR_CNTL); } From 1b67e0d3b9691d7b6b74e18960ddd2be24f9dc9d Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Mon, 24 Aug 2026 14:35:07 +0800 Subject: [PATCH 0427/2366] ASoC: amd: yc: fix memory leak in acp6x_pdm_dma_close() acp6x_pdm_dma_close() does not free the runtime->private_data buffer allocated in acp6x_pdm_dma_open(). Add the missing kfree. Fixes: 7610174a5bfe ("ASoC: amd: add acp6x pdm platform driver") Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260824063507.483784-2-wangdich9700@163.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-pdm-dma.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-pdm-dma.c b/sound/soc/amd/yc/acp6x-pdm-dma.c index 710db721ffa4..40c4d833f4ed 100644 --- a/sound/soc/amd/yc/acp6x-pdm-dma.c +++ b/sound/soc/amd/yc/acp6x-pdm-dma.c @@ -275,9 +275,11 @@ static int acp6x_pdm_dma_close(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct pdm_dev_data *adata = dev_get_drvdata(component->dev); + struct snd_pcm_runtime *runtime = substream->runtime; acp6x_disable_pdm_interrupts(adata->acp6x_base); adata->capture_stream = NULL; + kfree(runtime->private_data); return 0; } From d215f014b3526a9898d87bfb4b866287f0864cc9 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:32 +0200 Subject: [PATCH 0428/2366] KVM: s390: Fix dirty marking in adapter_indicators_set*() When the indicator and/or summary bits are set in the guest, the accessed page was only marked dirty in KVM if the access was performed using the slow path; accesses through the new kvm_arch_set_irq_inatomic fast inject path would not mark the page as dirty. Fix by adding/moving the missing calls to mark_page_dirty(). Note that for the inatomic path set_page_dirty{,_lock}() is not needed as the page stays pinned; the unpin path correctly marks it as dirty. Opportunistically reorder the local variables to be in reverse Christmas tree order and refactor to use guard(). Fixes: 1e95e3bc6b05 ("KVM: s390: Enable adapter_indicators_set to use mapped pages") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-2-imbrenda@linux.ibm.com> --- arch/s390/kvm/s390/interrupt.c | 77 +++++++++++++++++----------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/arch/s390/kvm/s390/interrupt.c b/arch/s390/kvm/s390/interrupt.c index 0381ae981703..49b4e233e791 100644 --- a/arch/s390/kvm/s390/interrupt.c +++ b/arch/s390/kvm/s390/interrupt.c @@ -2984,61 +2984,58 @@ static int adapter_indicators_set(struct kvm *kvm, struct s390_io_adapter *adapter, struct kvm_s390_adapter_int *adapter_int) { - unsigned long bit; - int summary_set, idx; struct s390_map_info *ind_info, *summary_info; - void *map; struct page *ind_page, *summary_page; - unsigned long flags; + unsigned long bit; + int summary_set; + void *map; ind_page = NULL; - spin_lock_irqsave(&adapter->maps_lock, flags); - ind_info = get_map_info(adapter, adapter_int->ind_addr); + scoped_guard(spinlock_irqsave, &adapter->maps_lock) { + ind_info = get_map_info(adapter, adapter_int->ind_addr); + if (ind_info) { + map = page_address(ind_info->page); + bit = get_ind_bit(ind_info->addr, adapter_int->ind_offset, adapter->swap); + set_bit(bit, map); + } + } if (!ind_info) { - spin_unlock_irqrestore(&adapter->maps_lock, flags); ind_page = pin_map_page(kvm, adapter_int->ind_addr, 0); if (!ind_page) return -1; - idx = srcu_read_lock(&kvm->srcu); map = page_address(ind_page); bit = get_ind_bit(adapter_int->ind_addr, adapter_int->ind_offset, adapter->swap); set_bit(bit, map); - mark_page_dirty(kvm, adapter_int->ind_gaddr >> PAGE_SHIFT); set_page_dirty_lock(ind_page); - srcu_read_unlock(&kvm->srcu, idx); unpin_user_page(ind_page); - } else { - map = page_address(ind_info->page); - bit = get_ind_bit(ind_info->addr, adapter_int->ind_offset, adapter->swap); - set_bit(bit, map); - spin_unlock_irqrestore(&adapter->maps_lock, flags); } + scoped_guard(srcu, &kvm->srcu) + mark_page_dirty(kvm, gpa_to_gfn(adapter_int->ind_gaddr)); - spin_lock_irqsave(&adapter->maps_lock, flags); - summary_info = get_map_info(adapter, adapter_int->summary_addr); + scoped_guard(spinlock_irqsave, &adapter->maps_lock) { + summary_info = get_map_info(adapter, adapter_int->summary_addr); + if (summary_info) { + map = page_address(summary_info->page); + bit = get_ind_bit(summary_info->addr, adapter_int->summary_offset, + adapter->swap); + summary_set = test_and_set_bit(bit, map); + } + } if (!summary_info) { - spin_unlock_irqrestore(&adapter->maps_lock, flags); summary_page = pin_map_page(kvm, adapter_int->summary_addr, 0); if (!summary_page) return -1; - idx = srcu_read_lock(&kvm->srcu); map = page_address(summary_page); bit = get_ind_bit(adapter_int->summary_addr, adapter_int->summary_offset, adapter->swap); summary_set = test_and_set_bit(bit, map); - mark_page_dirty(kvm, adapter_int->summary_gaddr >> PAGE_SHIFT); set_page_dirty_lock(summary_page); - srcu_read_unlock(&kvm->srcu, idx); unpin_user_page(summary_page); - } else { - map = page_address(summary_info->page); - bit = get_ind_bit(summary_info->addr, adapter_int->summary_offset, - adapter->swap); - summary_set = test_and_set_bit(bit, map); - spin_unlock_irqrestore(&adapter->maps_lock, flags); } + scoped_guard(srcu, &kvm->srcu) + mark_page_dirty(kvm, gpa_to_gfn(adapter_int->summary_gaddr)); return summary_set ? 0 : 1; } @@ -3048,26 +3045,29 @@ static int adapter_indicators_set_fast(struct kvm *kvm, struct kvm_s390_adapter_int *adapter_int, int setbit) { + struct s390_map_info *ind_info, *summary_info; unsigned long bit; int summary_set; - struct s390_map_info *ind_info, *summary_info; void *map; - spin_lock(&adapter->maps_lock); + guard(srcu)(&kvm->srcu); + guard(spinlock)(&adapter->maps_lock); + ind_info = get_map_info(adapter, adapter_int->ind_addr); - if (!ind_info) { - spin_unlock(&adapter->maps_lock); + if (!ind_info) return -EWOULDBLOCK; - } + map = page_address(ind_info->page); bit = get_ind_bit(ind_info->addr, adapter_int->ind_offset, adapter->swap); - if (setbit) + if (setbit) { set_bit(bit, map); - summary_info = get_map_info(adapter, adapter_int->summary_addr); - if (!summary_info) { - spin_unlock(&adapter->maps_lock); - return -EWOULDBLOCK; + mark_page_dirty(kvm, gpa_to_gfn(adapter_int->ind_gaddr)); } + + summary_info = get_map_info(adapter, adapter_int->summary_addr); + if (!summary_info) + return -EWOULDBLOCK; + map = page_address(summary_info->page); bit = get_ind_bit(summary_info->addr, adapter_int->summary_offset, adapter->swap); @@ -3077,7 +3077,8 @@ static int adapter_indicators_set_fast(struct kvm *kvm, summary_set = test_and_set_bit(bit, map); else summary_set = test_and_clear_bit(bit, map); - spin_unlock(&adapter->maps_lock); + mark_page_dirty(kvm, gpa_to_gfn(adapter_int->summary_gaddr)); + return summary_set ? 0 : 1; } From ae12d2f9c119639a142d92c4a37c30277e8576da Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:33 +0200 Subject: [PATCH 0429/2366] KVM: s390: Fix compile warning for kvm_s390_update_cmma_dirty() The parameter "old" should be marked as const, to prevent compile-time warnings. Fixes: d487a24041c2 ("KVM: s390: Prepare gmap for a second KVM implementation") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-3-imbrenda@linux.ibm.com> --- arch/s390/kvm/s390/s390.c | 2 +- arch/s390/kvm/s390/s390.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c index b0839e887221..8f7e09d7d049 100644 --- a/arch/s390/kvm/s390/s390.c +++ b/arch/s390/kvm/s390/s390.c @@ -5766,7 +5766,7 @@ static long cmma_d_count_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_ return 0; } -void kvm_s390_update_cmma_dirty(struct kvm *kvm, struct kvm_memory_slot *old) +void kvm_s390_update_cmma_dirty(struct kvm *kvm, const struct kvm_memory_slot *old) { const struct dat_walk_ops ops = { .pte_entry = cmma_d_count_pte, }; diff --git a/arch/s390/kvm/s390/s390.h b/arch/s390/kvm/s390/s390.h index d284a263ba70..aa0d1d062f8d 100644 --- a/arch/s390/kvm/s390/s390.h +++ b/arch/s390/kvm/s390/s390.h @@ -472,7 +472,7 @@ int __kvm_s390_mprotect_many(struct gmap *gmap, gpa_t gpa, u8 npages, unsigned i unsigned long bits); bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu); -void kvm_s390_update_cmma_dirty(struct kvm *kvm, struct kvm_memory_slot *old); +void kvm_s390_update_cmma_dirty(struct kvm *kvm, const struct kvm_memory_slot *old); int kvm_s390_vm_stop_migration(struct kvm *kvm); From faff4c8ff3dbec6d71b89dd281a0d17c4478fa44 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:34 +0200 Subject: [PATCH 0430/2366] KVM: s390: Fix _gaccess_shadow_fault() In some circumstances, it is possible that the page of nested guest memory that is being shadowed is not present at all in the parent guest gmap. dat_entry_walk() will not find any leaf entry and return with -ENOENT, which will erroneously be propagated all the way to userspace. Fix by manually calling gmap_link() on the memory of the nested guest that is being shadowed if the mapping was not already present. Fixes: e38c884df921 ("KVM: s390: Switch to new gmap") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-4-imbrenda@linux.ibm.com> --- arch/s390/kvm/s390/gaccess.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/arch/s390/kvm/s390/gaccess.c b/arch/s390/kvm/s390/gaccess.c index e5c064f263df..405345ccc4f1 100644 --- a/arch/s390/kvm/s390/gaccess.c +++ b/arch/s390/kvm/s390/gaccess.c @@ -1589,12 +1589,25 @@ static inline int ___gaccess_shadow_fault(struct kvm_vcpu *vcpu, struct gmap *sg parent = READ_ONCE(sg->parent); if (!parent) return -EAGAIN; +retry: scoped_guard(spinlock, &parent->children_lock) { if (READ_ONCE(sg->parent) != parent) return -EAGAIN; sg->invalidated = false; rc = _gaccess_do_shadow(vcpu->arch.mc, sg, saddr, walk); } + if (rc == -ENOENT) { + struct kvm_memory_slot *slot; + struct guest_fault *entries; + + entries = get_entries(walk); + slot = kvm_vcpu_gfn_to_memslot(vcpu, entries[LEVEL_MEM].gfn); + if (!slot) + return PGM_ADDRESSING; + rc = gmap_link(vcpu->arch.mc, parent, entries + LEVEL_MEM, slot); + if (!rc) + goto retry; + } if (!rc) kvm_s390_release_faultin_array(vcpu->kvm, walk->raw_entries, false); return rc; From 00c0ae5e438615bde828a3b9f33912960f4e6511 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:35 +0200 Subject: [PATCH 0431/2366] KVM: s390: Refactor dat_set_slot() Refactor dat_set_slot(), _dat_slot_pte(), _dat_slot_crste(). Now they only take a struct kvm_s390_mmu_cache as priv. For dat_delete_slot(), mc is NULL, as no allocations should take place. This is needed as a prerequisite to move gmap DAT table setup from kvm_arch_commit_memory_region() to kvm_arch_prepare_memory_region(). Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-5-imbrenda@linux.ibm.com> --- arch/s390/kvm/gmap/dat.c | 29 +++++++++-------------------- arch/s390/kvm/gmap/dat.h | 10 ++++------ arch/s390/kvm/gmap/kvm_mmu.c | 4 ++-- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/arch/s390/kvm/gmap/dat.c b/arch/s390/kvm/gmap/dat.c index 24547e39fab2..dcedd5479d82 100644 --- a/arch/s390/kvm/gmap/dat.c +++ b/arch/s390/kvm/gmap/dat.c @@ -846,19 +846,12 @@ long dat_reset_skeys(union asce asce, gfn_t start) } #endif /* KVM_S390_MANAGES_S390_GUEST */ -struct slot_priv { - unsigned long token; - struct kvm_s390_mmu_cache *mc; -}; - static long _dat_slot_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_walk *walk) { - struct slot_priv *p = walk->priv; - union crste dummy = { .val = p->token }; union pte new_pte, pte = READ_ONCE(*ptep); union pgste pgste; - new_pte = _PTE_TOK(dummy.tok.type, dummy.tok.par); + new_pte = walk->priv ? _PTE_EMPTY : _PTE_TOK(_DAT_TOKEN_PIC, PGM_ADDRESSING); /* Table entry already in the desired state. */ if (pte.val == new_pte.val) @@ -875,10 +868,9 @@ static long _dat_slot_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_wal static long _dat_slot_crste(union crste *crstep, gfn_t gfn, gfn_t next, struct dat_walk *walk) { union crste new_crste, crste = READ_ONCE(*crstep); - struct slot_priv *p = walk->priv; + struct kvm_s390_mmu_cache *mc = walk->priv; - new_crste.val = p->token; - new_crste.h.tt = crste.h.tt; + new_crste = mc ? _CRSTE_EMPTY(crste.h.tt) : _CRSTE_HOLE(crste.h.tt); /* Table entry already in the desired state. */ if (crste.val == new_crste.val) @@ -902,7 +894,10 @@ static long _dat_slot_crste(union crste *crstep, gfn_t gfn, gfn_t next, struct d if (!crste.h.fc && !crste.h.i) return 0; /* Split (install a lower level table), and handle things there. */ - return dat_split_crste(p->mc, crstep, gfn, walk->asce, false); + if (mc) + return dat_split_crste(mc, crstep, gfn, walk->asce, false); + /* A large page should never cross memslots boundaries */ + return -EINVAL; } static const struct dat_walk_ops dat_slot_ops = { @@ -910,16 +905,10 @@ static const struct dat_walk_ops dat_slot_ops = { .crste_ops = { _dat_slot_crste, _dat_slot_crste, _dat_slot_crste, _dat_slot_crste, }, }; -int dat_set_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, gfn_t end, - u16 type, u16 param) +int dat_set_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, gfn_t end) { - struct slot_priv priv = { - .token = _CRSTE_TOK(0, type, param).val, - .mc = mc, - }; - return _dat_walk_gfn_range(start, end, asce, &dat_slot_ops, - DAT_WALK_IGN_HOLES | DAT_WALK_ANY, &priv); + DAT_WALK_IGN_HOLES | DAT_WALK_ANY, mc); } static void pgste_set_unlock_multiple(union pte *first, int n, union pgste *pgstes) diff --git a/arch/s390/kvm/gmap/dat.h b/arch/s390/kvm/gmap/dat.h index e452c141b841..90389d47ba4e 100644 --- a/arch/s390/kvm/gmap/dat.h +++ b/arch/s390/kvm/gmap/dat.h @@ -547,8 +547,7 @@ long dat_reset_skeys(union asce asce, gfn_t start); unsigned long dat_get_ptval(struct page_table *table, struct ptval_param param); void dat_set_ptval(struct page_table *table, struct ptval_param param, unsigned long val); -int dat_set_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, gfn_t end, - u16 type, u16 param); +int dat_set_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, gfn_t end); #if KVM_S390_MANAGES_S390_GUEST int dat_set_prefix_notif_bit(union asce asce, gfn_t gfn); @@ -973,16 +972,15 @@ static inline int get_level(union crste *crstep, union pte *ptep) return ptep ? TABLE_TYPE_PAGE_TABLE : crstep->h.tt; } -static inline int dat_delete_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, - unsigned long npages) +static inline int dat_delete_slot(union asce asce, gfn_t start, unsigned long npages) { - return dat_set_slot(mc, asce, start, start + npages, _DAT_TOKEN_PIC, PGM_ADDRESSING); + return dat_set_slot(NULL, asce, start, start + npages); } static inline int dat_create_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, unsigned long npages) { - return dat_set_slot(mc, asce, start, start + npages, _DAT_TOKEN_NONE, 0); + return dat_set_slot(mc, asce, start, start + npages); } static inline bool crste_is_ucas(union crste crste) diff --git a/arch/s390/kvm/gmap/kvm_mmu.c b/arch/s390/kvm/gmap/kvm_mmu.c index b08b8229bb6f..4c8054e18490 100644 --- a/arch/s390/kvm/gmap/kvm_mmu.c +++ b/arch/s390/kvm/gmap/kvm_mmu.c @@ -111,10 +111,10 @@ void s390_kvm_mmu_commit_memory_region(struct kvm *kvm, kvm_s390_update_cmma_dirty(kvm, old); switch (change) { case KVM_MR_DELETE: - rc = dat_delete_slot(mc, kvm->arch.gmap->asce, old->base_gfn, old->npages); + rc = dat_delete_slot(kvm->arch.gmap->asce, old->base_gfn, old->npages); break; case KVM_MR_MOVE: - rc = dat_delete_slot(mc, kvm->arch.gmap->asce, old->base_gfn, old->npages); + rc = dat_delete_slot(kvm->arch.gmap->asce, old->base_gfn, old->npages); if (rc) break; fallthrough; From 19192a4043277af380f83d550a55b92eeeaac7c6 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:36 +0200 Subject: [PATCH 0432/2366] KVM: s390: Move all code into s390_kvm_mmu_prepare_memory_region() Move all code from s390_kvm_mmu_commit_memory_region() into s390_kvm_mmu_prepare_memory_region(). This allows the function to fail gracefully if needed. The previous behaviour was to print a warning and continue execution with page tables inconsistent with the memslots. Fixes: e38c884df921 ("KVM: s390: Switch to new gmap") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-6-imbrenda@linux.ibm.com> --- arch/s390/kvm/gmap/kvm_mmu.c | 89 +++++++++++++++--------------------- arch/s390/kvm/gmap/kvm_mmu.h | 4 -- arch/s390/kvm/s390/s390.c | 1 - 3 files changed, 36 insertions(+), 58 deletions(-) diff --git a/arch/s390/kvm/gmap/kvm_mmu.c b/arch/s390/kvm/gmap/kvm_mmu.c index 4c8054e18490..c2ffb5e59ec6 100644 --- a/arch/s390/kvm/gmap/kvm_mmu.c +++ b/arch/s390/kvm/gmap/kvm_mmu.c @@ -47,6 +47,9 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *new, enum kvm_mr_change change) { + struct kvm_s390_mmu_cache *mc __free(kvm_s390_mmu_cache) = NULL; + int rc = 0; + if (kvm_is_ucontrol(kvm) && new && new->id < KVM_USER_MEM_SLOTS) return -EINVAL; @@ -61,6 +64,10 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, * and munmap() stuff in this slot after doing this call at any * time. */ + if (change != KVM_MR_MOVE && change != KVM_MR_CREATE) { + WARN(1, "Unknown KVM MR CHANGE: %d\n", change); + return -EINVAL; + } if (new->userspace_addr & ~PAGE_MASK) return -EINVAL; if ((new->base_gfn + new->npages) * PAGE_SIZE > kvm->arch.mem_limit) @@ -69,65 +76,41 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, return -EINVAL; } - if (!kvm_s390_is_migration_mode(kvm)) - return 0; - - /* - * Turn off migration mode when: - * - userspace creates a new memslot with dirty logging off, - * - userspace modifies an existing memslot (MOVE or FLAGS_ONLY) and - * dirty logging is turned off. - * Migration mode expects dirty page logging being enabled to store - * its dirty bitmap. - */ - if (change != KVM_MR_DELETE && - !(new->flags & KVM_MEM_LOG_DIRTY_PAGES)) - WARN(kvm_s390_vm_stop_migration(kvm), - "Failed to stop migration mode"); - - return 0; -} - -void s390_kvm_mmu_commit_memory_region(struct kvm *kvm, - struct kvm_memory_slot *old, - const struct kvm_memory_slot *new, - enum kvm_mr_change change) -{ - struct kvm_s390_mmu_cache *mc __free(kvm_s390_mmu_cache) = NULL; - int rc = 0; - - guard(mutex)(&kvm->slots_arch_lock); + if (kvm->arch.migration_mode) { + /* + * Turn off migration mode when: + * - userspace creates a new memslot with dirty logging off, + * - userspace modifies an existing memslot (MOVE or FLAGS_ONLY) + * and dirty logging is turned off. + * Migration mode expects dirty page logging being enabled to + * store its dirty bitmap. + */ + if (change != KVM_MR_DELETE && + !(new->flags & KVM_MEM_LOG_DIRTY_PAGES)) + WARN(kvm_s390_vm_stop_migration(kvm), + "Failed to stop migration mode"); + } if (change == KVM_MR_FLAGS_ONLY) - return; - - mc = kvm_s390_new_mmu_cache(); - if (!mc) { - rc = -ENOMEM; - goto out; + return 0; + if (change != KVM_MR_DELETE) { + /* Enough capacity to add a new memslot */ + mc = kvm_s390_new_mmu_cache(); + if (!mc) + return -ENOMEM; } - scoped_guard(write_lock, &kvm->mmu_lock) { kvm_s390_update_cmma_dirty(kvm, old); - switch (change) { - case KVM_MR_DELETE: + if (change == KVM_MR_DELETE || change == KVM_MR_MOVE) rc = dat_delete_slot(kvm->arch.gmap->asce, old->base_gfn, old->npages); - break; - case KVM_MR_MOVE: - rc = dat_delete_slot(kvm->arch.gmap->asce, old->base_gfn, old->npages); - if (rc) - break; - fallthrough; - case KVM_MR_CREATE: + if (!rc && (change == KVM_MR_MOVE || change == KVM_MR_CREATE)) rc = dat_create_slot(mc, kvm->arch.gmap->asce, new->base_gfn, new->npages); - break; - case KVM_MR_FLAGS_ONLY: - break; - default: - WARN(1, "Unknown KVM MR CHANGE: %d\n", change); - } } -out: - if (rc) - pr_warn("failed to commit memory region\n"); + /* + * Can only be triggered if dat_{create,delete}_slot() found an + * internal inconsistency or if the mmu cache ran out of memory; + * both should be impossible. + */ + KVM_BUG_ON(rc, kvm); + return rc; } diff --git a/arch/s390/kvm/gmap/kvm_mmu.h b/arch/s390/kvm/gmap/kvm_mmu.h index cdbd390bd33c..43cde61bae03 100644 --- a/arch/s390/kvm/gmap/kvm_mmu.h +++ b/arch/s390/kvm/gmap/kvm_mmu.h @@ -10,9 +10,5 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, const struct kvm_memory_slot *old, struct kvm_memory_slot *new, enum kvm_mr_change change); -void s390_kvm_mmu_commit_memory_region(struct kvm *kvm, - struct kvm_memory_slot *old, - const struct kvm_memory_slot *new, - enum kvm_mr_change change); #endif /* ARCH_KVM_GMAP_KVM_MMU_H */ diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c index 8f7e09d7d049..eca4a4359ab2 100644 --- a/arch/s390/kvm/s390/s390.c +++ b/arch/s390/kvm/s390/s390.c @@ -5781,7 +5781,6 @@ void kvm_arch_commit_memory_region(struct kvm *kvm, struct kvm_memory_slot *old, const struct kvm_memory_slot *new, enum kvm_mr_change change) { - s390_kvm_mmu_commit_memory_region(kvm, old, new, change); } /** From f3a557067d57ce6ae98d485c8009b223e16f5f36 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:37 +0200 Subject: [PATCH 0433/2366] KVM: s390: Add missing srcu in kvm_s390_set_irq_state() Like kvm_s390_inject_vcpu(), kvm_s390_set_irq_state() also needs the kvm->srcu or the slots lock when performing the Store status operation. Fix by taking kvm->srcu in kvm_s390_set_irq_state(). Fixes: ba5c1e9b6cee ("KVM: s390: interrupt subsystem, cpu timer, waitpsw") Fixes: 062e44a9319f ("KVM: s390: Use srcu in kvm_arch_vcpu_unlocked_ioctl()") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-7-imbrenda@linux.ibm.com> --- arch/s390/kvm/s390/interrupt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/s390/kvm/s390/interrupt.c b/arch/s390/kvm/s390/interrupt.c index 49b4e233e791..f892f4307883 100644 --- a/arch/s390/kvm/s390/interrupt.c +++ b/arch/s390/kvm/s390/interrupt.c @@ -3229,9 +3229,9 @@ int kvm_s390_set_irq_state(struct kvm_vcpu *vcpu, void __user *irqstate, int len break; } } - if (storestatus) { - n = kvm_s390_store_status_unloaded(vcpu, KVM_S390_STORE_STATUS_NOADDR); + scoped_guard(srcu, &vcpu->kvm->srcu) + n = kvm_s390_store_status_unloaded(vcpu, KVM_S390_STORE_STATUS_NOADDR); return r ? r : n; } From 27554b9505ddfc0aeab466aeb60929dfa17284c7 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:38 +0200 Subject: [PATCH 0434/2366] KVM: s390: Fix potential races in dat skey functions When dat_cond_set_storage_key() finds a large page, it will conditionally set the storage key in absolute memory using large_crste_to_phys() to get the absolute address. There is a race window between dat_entry_walk() and large_crste_to_phys(): the large page could have been split concurrently, and large_crste_to_phys() might be called with a crste that does not designate a large page, leading to crashes. Similar issues were also present in dat_set_storage_key(). dat_get_storage_key() and dat_reset_reference_bit() did instead check for a potential concurrent splitting of the large page, but then handled it incorrectly. Fix by performing a READ_ONCE on the crste pointer, checking and using the result, instead of dereferencing the pointer again. In case a race is detacted, try dat_entry_walk() again. Fixes: 8e03e8316eb2 ("KVM: s390: KVM page table management functions: storage keys") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-8-imbrenda@linux.ibm.com> --- arch/s390/kvm/gmap/dat.c | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/arch/s390/kvm/gmap/dat.c b/arch/s390/kvm/gmap/dat.c index dcedd5479d82..ff80d02c9f56 100644 --- a/arch/s390/kvm/gmap/dat.c +++ b/arch/s390/kvm/gmap/dat.c @@ -621,17 +621,20 @@ int dat_get_storage_key(union asce asce, gfn_t gfn, union skey *skey) union pte *ptep; int rc; +again: skey->skey = 0; rc = dat_entry_walk(NULL, gfn, asce, DAT_WALK_ANY, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; if (!ptep) { - union crste crste; + union crste crste = READ_ONCE(*crstep); - crste = READ_ONCE(*crstep); - if (!crste.h.fc || !crste.s.fc1.pr) + if (!crste_leaf(crste) && !crste.h.i) + goto again; + if (!crste.s.fc1.pr) return 0; + skey->skey = page_get_storage_key(large_crste_to_phys(crste, gfn)); return 0; } @@ -662,13 +665,20 @@ int dat_set_storage_key(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t gf union pte *ptep; int rc; +again: rc = dat_entry_walk(mc, gfn, asce, DAT_WALK_LEAF_ALLOC, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; if (!ptep) { - page_set_storage_key(large_crste_to_phys(*crstep, gfn), skey.skey, !nq); + union crste crste = READ_ONCE(*crstep); + + /* A large page has been split concurrently, try again */ + if (!crste_leaf(crste)) + goto again; + + page_set_storage_key(large_crste_to_phys(crste, gfn), skey.skey, !nq); return 0; } @@ -718,15 +728,22 @@ int dat_cond_set_storage_key(struct kvm_s390_mmu_cache *mmc, union asce asce, gf union pte *ptep; int rc; +again: rc = dat_entry_walk(mmc, gfn, asce, DAT_WALK_LEAF_ALLOC, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; if (!ptep) { + union crste crste = READ_ONCE(*crstep); + + /* A large page has been split concurrently, try again */ + if (!crste_leaf(crste)) + goto again; if (!oldkey) oldkey = &prev; - return page_cond_set_storage_key(large_crste_to_phys(*crstep, gfn), skey, oldkey, + + return page_cond_set_storage_key(large_crste_to_phys(crste, gfn), skey, oldkey, nq, mr, mc); } @@ -768,7 +785,7 @@ int dat_reset_reference_bit(union asce asce, gfn_t gfn, union skey *skey) int rc; skey->skey = 0; - +again: rc = dat_entry_walk(NULL, gfn, asce, DAT_WALK_ANY, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; @@ -776,9 +793,12 @@ int dat_reset_reference_bit(union asce asce, gfn_t gfn, union skey *skey) if (!ptep) { union crste crste = READ_ONCE(*crstep); - if (!crste.h.fc || !crste.s.fc1.pr) + /* A large page has been split concurrently, try again */ + if (!crste_leaf(crste) && !crste.h.i) + goto again; + if (!crste.s.fc1.pr) return 0; - skey->skey = page_reset_referenced(large_crste_to_phys(*crstep, gfn)) << 1; + skey->skey = page_reset_referenced(large_crste_to_phys(crste, gfn)) << 1; return 0; } old = pgste_get_lock(ptep); From 4ca00a9154f998116fba9a32cce5bd938d228065 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Fri, 28 Aug 2026 13:54:39 +0200 Subject: [PATCH 0435/2366] KVM: s390: Fix race in _destroy_pages_crste() Use READ_ONCE() in _destroy_pages_crste() to read the crste, avoid dereferencing the pointer multiple times. Fixes: a2c17f9270cc ("KVM: s390: New gmap code") Signed-off-by: Claudio Imbrenda Message-ID: <20260828115439.145885-9-imbrenda@linux.ibm.com> --- arch/s390/kvm/gmap/gmap.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/s390/kvm/gmap/gmap.c b/arch/s390/kvm/gmap/gmap.c index 4968330e9553..3f3fa864cc36 100644 --- a/arch/s390/kvm/gmap/gmap.c +++ b/arch/s390/kvm/gmap/gmap.c @@ -994,11 +994,13 @@ static long _destroy_pages_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct da static long _destroy_pages_crste(union crste *crstep, gfn_t gfn, gfn_t next, struct dat_walk *walk) { phys_addr_t origin, cur, end; + union crste crste; - if (!crstep->h.fc || !crstep->s.fc1.pr) + crste = READ_ONCE(*crstep); + if (!crste.h.fc || !crste.s.fc1.pr) return 0; - origin = crste_origin_large(*crstep); + origin = crste_origin_large(crste); cur = ((max(gfn, walk->start) - gfn) << PAGE_SHIFT) + origin; end = ((min(next, walk->end) - gfn) << PAGE_SHIFT) + origin; for ( ; cur < end; cur += PAGE_SIZE) From ef9fbe1b93f3b617b96e86d5cd76b3fa44514cb5 Mon Sep 17 00:00:00 2001 From: Krystian Kaniewski Date: Wed, 12 Aug 2026 10:16:41 +0200 Subject: [PATCH 0436/2366] RDMA/core: Reject unregistering netdevs in ib_get_eth_speed ib_device_get_netdev() intentionally returns a referenced net_device even when it is unregistering, so matching and cleanup callers can still find the association. The reference keeps struct net_device allocated, but does not guarantee that the device remains operational. ib_get_eth_speed() uses the returned device operationally by invoking its ethtool callback. Although that call is made under RTNL, the function does not verify the registration state first. An asynchronous RDMA port query can therefore call into a netdev after NETDEV_UNREGISTER and ndo_uninit have completed. Check for NETREG_REGISTERED while holding RTNL and return -ENODEV for a device which is being unregistered. Keeping RTNL across the check and the ethtool operation prevents unregister from starting between them. Keep the speed fallback and warning under RTNL as well, so the warning can safely read netdev->name. Drop the netdev reference before releasing RTNL once all accesses to the device are complete. Fixes: d41861942fc5 ("IB/core: Add generic function to extract IB speed from netdev") Reported-by: syzbot+5fe14f2ff4ccbace9a26@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=5fe14f2ff4ccbace9a26 Signed-off-by: Krystian Kaniewski Link: https://patch.msgid.link/20260812081708.32468-1-krystianmkaniewski@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/verbs.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/core/verbs.c b/drivers/infiniband/core/verbs.c index 04abc80c1327..c43e25d37266 100644 --- a/drivers/infiniband/core/verbs.c +++ b/drivers/infiniband/core/verbs.c @@ -2058,11 +2058,13 @@ int ib_get_eth_speed(struct ib_device *dev, u32 port_num, u16 *speed, u8 *width) return -ENODEV; rtnl_lock(); + if (READ_ONCE(netdev->reg_state) != NETREG_REGISTERED) { + dev_put(netdev); + rtnl_unlock(); + return -ENODEV; + } + rc = __ethtool_get_link_ksettings(netdev, &lksettings); - rtnl_unlock(); - - dev_put(netdev); - if (!rc && lksettings.base.speed != (u32)SPEED_UNKNOWN) { netdev_speed = lksettings.base.speed; } else { @@ -2071,6 +2073,8 @@ int ib_get_eth_speed(struct ib_device *dev, u32 port_num, u16 *speed, u8 *width) pr_warn("%s speed is unknown, defaulting to %u\n", netdev->name, netdev_speed); } + dev_put(netdev); + rtnl_unlock(); ib_get_width_and_speed(netdev_speed, lksettings.lanes, speed, width); From cc7cd2a9228175c975f62ad56ed7c767701cb4fa Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Tue, 1 Sep 2026 16:30:31 +0500 Subject: [PATCH 0437/2366] staging: sm750fb: fix mono image source stride mismatch in lynxfb_ops_imageblit() sm750_hw_imageblit() advances its monochrome source pointer by src_delta per scanline, and computes the correct rounded-up stride internally as: bytes_per_scan = (width + start_bit + 7) / 8; Its only caller, lynxfb_ops_imageblit(), instead passed src_delta as image->width >> 3. For widths not a multiple of 8 this under-counted the stride, so the source pointer fell further behind the real per-scanline layout on every line, corrupting the rendered image. Rather than just fixing the caller's calculation, remove src_delta as a parameter entirely and have sm750_hw_imageblit() advance by the bytes_per_scan it already computes for itself. There has only ever been one caller, and that caller was passing an out-of-sync derivative of the same width/start_bit values sm750_hw_imageblit() already has, so keeping stride as a separate parameter served no purpose beyond letting the two calculations drift apart, which is exactly what happened here. Rounding up, rather than down, is the direction consistent with the rest of the fbdev core: struct fb_image mono bitmap data (the same image->data this driver receives) is walked elsewhere with byte strides derived from a ceiling division of width by 8. The generic mono bit iterator in drivers/video/fbdev/core/fb_imageblit.h advances scanlines with "iter->data += BITS_TO_BYTES(iter->width)", and BITS_TO_BYTES() (include/linux/bitops.h) is a ceiling division. sm750_hw_imageblit()'s own "(width + start_bit + 7) / 8" is that same ceiling division with an added start_bit offset, so the caller's ">> 3" (floor) was the one calculation out of step with how this data layout is handled everywhere else. Found by code review of sm750_hw_imageblit()'s internal stride calculation against what its only caller was passing in, and confirmed with a clean -Werror build. I do not have this hardware, so this has not been exercised at runtime on real sm750 silicon. Fixes: 81dee67e215b2 ("staging: sm750fb: add sm750 to staging") Cc: stable@vger.kernel.org Reviewed-by: Dan Carpenter Signed-off-by: Muhammad Bilal Link: https://patch.msgid.link/20260901113031.161610-1-meatuni001@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/staging/sm750fb/sm750.c | 2 +- drivers/staging/sm750fb/sm750.h | 2 +- drivers/staging/sm750fb/sm750_accel.c | 6 ++---- drivers/staging/sm750fb/sm750_accel.h | 4 +--- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/staging/sm750fb/sm750.c b/drivers/staging/sm750fb/sm750.c index 039e2033f84e..8b93bfeb217b 100644 --- a/drivers/staging/sm750fb/sm750.c +++ b/drivers/staging/sm750fb/sm750.c @@ -252,7 +252,7 @@ static void lynxfb_ops_imageblit(struct fb_info *info, spin_lock(&sm750_dev->slock); sm750_dev->accel.de_imageblit(&sm750_dev->accel, - image->data, image->width >> 3, 0, + image->data, 0, base, pitch, bpp, image->dx, image->dy, image->width, image->height, diff --git a/drivers/staging/sm750fb/sm750.h b/drivers/staging/sm750fb/sm750.h index 89a61bf80779..fd1cf9eb5797 100644 --- a/drivers/staging/sm750fb/sm750.h +++ b/drivers/staging/sm750fb/sm750.h @@ -64,7 +64,7 @@ struct lynx_accel { u32 rop2); int (*de_imageblit)(struct lynx_accel *accel, const char *p_srcbuf, - u32 src_delta, u32 start_bit, u32 d_base, u32 d_pitch, + u32 start_bit, u32 d_base, u32 d_pitch, u32 byte_per_pixel, u32 dx, u32 dy, u32 width, u32 height, u32 f_color, u32 b_color, u32 rop2); diff --git a/drivers/staging/sm750fb/sm750_accel.c b/drivers/staging/sm750fb/sm750_accel.c index 0316ea69d009..bac9a209899c 100644 --- a/drivers/staging/sm750fb/sm750_accel.c +++ b/drivers/staging/sm750fb/sm750_accel.c @@ -288,8 +288,6 @@ static unsigned int de_get_transparency(struct lynx_accel *accel) * sm750_hw_imageblit * @accel: Acceleration device data * @src_buf: pointer to start of source buffer in system memory - * @src_delta: Pitch value (in bytes) of the source buffer, +ive means top down - * and -ive mean button up * @start_bit: Mono data can start at any bit in a byte, this value should be * 0 to 7 * @dest_base: Address of destination: offset in frame buffer @@ -304,7 +302,7 @@ static unsigned int de_get_transparency(struct lynx_accel *accel) * @rop2: ROP value */ int sm750_hw_imageblit(struct lynx_accel *accel, const char *src_buf, - u32 src_delta, u32 start_bit, u32 dest_base, u32 dest_pitch, + u32 start_bit, u32 dest_base, u32 dest_pitch, u32 byte_per_pixel, u32 dx, u32 dy, u32 width, u32 height, u32 fg_color, u32 bg_color, u32 rop2) { @@ -395,7 +393,7 @@ int sm750_hw_imageblit(struct lynx_accel *accel, const char *src_buf, write_dp_port(accel, *(unsigned int *)remain); } - src_buf += src_delta; + src_buf += bytes_per_scan; } return 0; diff --git a/drivers/staging/sm750fb/sm750_accel.h b/drivers/staging/sm750fb/sm750_accel.h index 617885431661..efceefaaafb0 100644 --- a/drivers/staging/sm750fb/sm750_accel.h +++ b/drivers/staging/sm750fb/sm750_accel.h @@ -220,8 +220,6 @@ int sm750_hw_copyarea(struct lynx_accel *accel, /** * sm750_hw_imageblit * @src_buf: pointer to start of source buffer in system memory - * @src_delta: Pitch value (in bytes) of the source buffer, +ive means top down - *>----- and -ive mean button up * @start_bit: Mono data can start at any bit in a byte, this value should be *>----- 0 to 7 * @dest_base: Address of destination: offset in frame buffer @@ -236,7 +234,7 @@ int sm750_hw_copyarea(struct lynx_accel *accel, * @rop2: ROP value */ int sm750_hw_imageblit(struct lynx_accel *accel, const char *src_buf, - u32 src_delta, u32 start_bit, u32 dest_base, u32 dest_pitch, + u32 start_bit, u32 dest_base, u32 dest_pitch, u32 byte_per_pixel, u32 dx, u32 dy, u32 width, u32 height, u32 fg_color, u32 bg_color, u32 rop2); From 2dc65035eb8d9f4b4495a8bc044f10f44974a9c6 Mon Sep 17 00:00:00 2001 From: Niranjan H Y Date: Tue, 1 Sep 2026 10:11:32 +0800 Subject: [PATCH 0438/2366] ASoC: Intel: sof_sdw: add tac5xx2-sdw family Add Texas Instrument's tac5xx2-sdw family to include support for soundwire codecs tac5572, tac5682 Signed-off-by: Niranjan H Y Signed-off-by: Bard Liao Link: https://patch.msgid.link/20260901021132.231908-1-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/Kconfig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index cddbd2aa424e..b795bcade8e9 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -533,12 +533,14 @@ config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH select SND_SOC_CS35L56_SPI select SND_SOC_CS35L56_SDW select SND_SOC_ES9356 + imply SND_SOC_TAC5XX2_SDW select SND_SOC_DMIC select SND_SOC_INTEL_HDA_DSP_COMMON imply SND_SOC_SDW_MOCKUP help Add support for Intel SoundWire-based platforms connected to - MAX98373, RT700, RT711, RT1308 and RT715 + MAX98373, RT700, RT711, RT1308, RT715, TAC5XX2_SDW family (including + TAC5572, TAC5682). If unsure select "N". endif From a44a3f175eaee7e5aeb6a8fed381c4a0d5f49236 Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Tue, 11 Aug 2026 19:19:16 +0300 Subject: [PATCH 0439/2366] RDMA/uverbs: Fix mmap_lock/disassociation_lock circular dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 51976c6cd786 ("RDMA/core: Provide rdma_user_mmap_disassociate() to disassociate mmap pages") introduced disassociation_lock to protect new mmap registrations against uverbs_user_mmap_disassociate(), but created an ABBA deadlock: Thread A (mmap / fork): mmap_lock -> disassociation_lock Thread B (disassociate): disassociation_lock -> mmap_lock Fix by removing disassociation_lock entirely and using the pre-existing hw_destroy_rwsem instead. hw_destroy_rwsem already provides the same protection: rdma_umap_open() and ib_uverbs_mmap() both use down_read_trylock() before registering a new VMA, so holding hw_destroy_rwsem in uverbs_user_mmap_disassociate() is sufficient to block new registrations. trylock is used in both mmap paths (not blocking down_read) because mmap_lock is already held on entry, and uverbs_user_mmap_disassociate() acquires mmap_lock internally — a blocking read would recreate the same deadlock. The only caller that was not taking hw_destroy_rwsem for write was rdma_user_mmap_disassociate(). Fix it to take the rwsem per-ufile while iterating under lists_mutex. This is safe because ib_uverbs_close() releases hw_destroy_rwsem entirely before acquiring lists_mutex, so the two locks are never held simultaneously. lockdep warning: [ 776.654252] ====================================================== [ 776.655214] WARNING: possible circular locking dependency detected [ 776.656167] 6.18.0for-upstream_debug_94e244d9ccab #1 Not tainted [ 776.657114] ------------------------------------------------------ [ 776.658087] devlink/14824 is trying to acquire lock: [ 776.658879] ffff88811170c800 (&mm->mmap_lock){++++}-{4:4}, at: uverbs_user_mmap_disassociate+0x168/0x780 [ib_uverbs] [ 776.660479] [ 776.660479] but task is already holding lock: [ 776.661460] ffff888142d92b08 (&file->disassociation_lock){+.+.}-{4:4}, at: uverbs_user_mmap_disassociate+0x39/0x780 [ib_uverbs] [ 776.663177] [ 776.663177] which lock already depends on the new lock. [ 776.663177] [ 776.664525] [ 776.664525] the existing dependency chain (in reverse order) is: [ 776.665724] [ 776.665724] -> #2 (&file->disassociation_lock){+.+.}-{4:4}: [ 776.666887] __mutex_lock+0x16d/0x2330 [ 776.667633] rdma_umap_open+0x129/0x280 [ib_uverbs] [ 776.668489] dup_mmap+0xa40/0x1790 [ 776.669170] copy_process+0x5dd2/0x6170 [ 776.669933] kernel_clone+0xb6/0x610 [ 776.670636] __do_sys_clone+0xb5/0xf0 [ 776.671354] do_syscall_64+0x70/0x12e0 [ 776.672083] entry_SYSCALL_64_after_hwframe+0x4b/0x53 [ 776.672940] [ 776.672940] -> #1 (&mm->mmap_lock/1){+.+.}-{4:4}: [ 776.673985] down_write_nested+0x90/0x1e0 [ 776.674751] dup_mmap+0x201/0x1790 [ 776.675448] copy_process+0x5dd2/0x6170 [ 776.676180] kernel_clone+0xb6/0x610 [ 776.676904] __do_sys_clone+0xb5/0xf0 [ 776.677615] do_syscall_64+0x70/0x12e0 [ 776.678351] entry_SYSCALL_64_after_hwframe+0x4b/0x53 [ 776.679239] [ 776.679239] -> #0 (&mm->mmap_lock){++++}-{4:4}: [ 776.680253] __lock_acquire+0x18c6/0x2ec0 [ 776.681018] lock_acquire+0x10e/0x2e0 [ 776.681742] down_read+0x95/0x430 [ 776.682395] uverbs_user_mmap_disassociate+0x168/0x780 [ib_uverbs] [ 776.683436] uverbs_destroy_ufile_hw+0x1ae/0x270 [ib_uverbs] [ 776.684416] ib_uverbs_remove_one+0x22b/0x420 [ib_uverbs] [ 776.685371] remove_client_context+0xa6/0xf0 [ib_core] [ 776.686342] disable_device+0x12b/0x240 [ib_core] [ 776.687249] __ib_unregister_device+0x269/0x460 [ib_core] [ 776.688233] ib_unregister_device+0x21/0x30 [ib_core] [ 776.689140] mlx5r_remove+0xd0/0x170 [mlx5_ib] [ 776.689999] device_release_driver_internal+0x3b2/0x560 [ 776.694876] bus_remove_device+0x1f5/0x3e0 [ 776.695638] device_del+0x3b9/0x990 [ 776.696329] mlx5_detach_device+0x17e/0x350 [mlx5_core] [ 776.697429] mlx5_unload_one_devl_locked+0x3f/0xb0 [mlx5_core] [ 776.698578] mlx5_devlink_reload_down+0x1f9/0x550 [mlx5_core] [ 776.699712] devlink_reload+0x13e/0x680 [ 776.700456] devlink_nl_reload_doit+0xc29/0x1160 [ 776.701293] genl_family_rcv_msg_doit+0x1c9/0x2a0 [ 776.702135] genl_rcv_msg+0x3f0/0x6b0 [ 776.702854] netlink_rcv_skb+0x11d/0x370 [ 776.703605] genl_rcv+0x24/0x40 [ 776.704236] netlink_unicast+0x5b4/0x970 [ 776.704984] netlink_sendmsg+0x730/0xbf0 [ 776.705748] __sock_sendmsg+0xc5/0x190 [ 776.706461] __sys_sendto+0x201/0x2f0 [ 776.707188] __x64_sys_sendto+0xdc/0x1b0 [ 776.707931] do_syscall_64+0x70/0x12e0 [ 776.708643] entry_SYSCALL_64_after_hwframe+0x4b/0x53 [ 776.709546] [ 776.709546] other info that might help us debug this: [ 776.709546] [ 776.710910] Chain exists of: [ 776.710910] &mm->mmap_lock --> &mm->mmap_lock/1 --> &file->disassociation_lock [ 776.710910] [ 776.712805] Possible unsafe locking scenario: [ 776.712805] [ 776.713828] CPU0 CPU1 [ 776.714589] ---- ---- [ 776.715347] lock(&file->disassociation_lock); [ 776.716097] lock(&mm->mmap_lock/1); [ 776.717067] lock(&file->disassociation_lock); [ 776.718199] rlock(&mm->mmap_lock); [ 776.718857] [ 776.718857] *** DEADLOCK *** Fixes: 51976c6cd786 ("RDMA/core: Provide rdma_user_mmap_disassociate() to disassociate mmap pages") Signed-off-by: Or Har-Toov Signed-off-by: Leon Romanovsky Signed-off-by: Edward Srouji Link: https://patch.msgid.link/20260811-fix-mmap-lockdep-v1-1-1151b41063b4@nvidia.com Acked-by: Junxian Huang Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/rdma_core.c | 1 - drivers/infiniband/core/uverbs_main.c | 25 +++++++++++-------------- include/rdma/uverbs_types.h | 2 -- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/drivers/infiniband/core/rdma_core.c b/drivers/infiniband/core/rdma_core.c index fd5651c003ae..a7cbe643e33c 100644 --- a/drivers/infiniband/core/rdma_core.c +++ b/drivers/infiniband/core/rdma_core.c @@ -69,7 +69,6 @@ void ib_uverbs_release_file(struct kref *ref) if (file->disassociate_page) __free_pages(file->disassociate_page, 0); - mutex_destroy(&file->disassociation_lock); mutex_destroy(&file->umap_lock); mutex_destroy(&file->ucontext_lock); kfree(file); diff --git a/drivers/infiniband/core/uverbs_main.c b/drivers/infiniband/core/uverbs_main.c index 0d88b2ee68ff..2a046c888dfa 100644 --- a/drivers/infiniband/core/uverbs_main.c +++ b/drivers/infiniband/core/uverbs_main.c @@ -644,12 +644,15 @@ static int ib_uverbs_mmap(struct file *filp, struct vm_area_struct *vma) goto out; } - mutex_lock(&file->disassociation_lock); + if (!down_read_trylock(&file->hw_destroy_rwsem)) { + ret = -EIO; + goto out; + } vma->vm_ops = &rdma_umap_ops; ret = ucontext->device->ops.mmap(ucontext, vma); - mutex_unlock(&file->disassociation_lock); + up_read(&file->hw_destroy_rwsem); out: srcu_read_unlock(&file->device->disassociate_srcu, srcu_key); return ret; @@ -671,7 +674,6 @@ static void rdma_umap_open(struct vm_area_struct *vma) /* We are racing with disassociation */ if (!down_read_trylock(&ufile->hw_destroy_rwsem)) goto out_zap; - mutex_lock(&ufile->disassociation_lock); /* * Disassociation already completed, the VMA should already be zapped. @@ -684,12 +686,10 @@ static void rdma_umap_open(struct vm_area_struct *vma) goto out_unlock; rdma_umap_priv_init(priv, vma, opriv->entry); - mutex_unlock(&ufile->disassociation_lock); up_read(&ufile->hw_destroy_rwsem); return; out_unlock: - mutex_unlock(&ufile->disassociation_lock); up_read(&ufile->hw_destroy_rwsem); out_zap: /* @@ -773,7 +773,7 @@ void uverbs_user_mmap_disassociate(struct ib_uverbs_file *ufile) { struct rdma_umap_priv *priv, *next_priv; - mutex_lock(&ufile->disassociation_lock); + lockdep_assert_held_write(&ufile->hw_destroy_rwsem); while (1) { struct mm_struct *mm = NULL; @@ -799,10 +799,8 @@ void uverbs_user_mmap_disassociate(struct ib_uverbs_file *ufile) break; } mutex_unlock(&ufile->umap_lock); - if (!mm) { - mutex_unlock(&ufile->disassociation_lock); + if (!mm) return; - } /* * The umap_lock is nested under mmap_lock since it used within @@ -832,8 +830,6 @@ void uverbs_user_mmap_disassociate(struct ib_uverbs_file *ufile) mmap_read_unlock(mm); mmput(mm); } - - mutex_unlock(&ufile->disassociation_lock); } /** @@ -851,8 +847,11 @@ void rdma_user_mmap_disassociate(struct ib_device *device) mutex_lock(&uverbs_dev->lists_mutex); list_for_each_entry(ufile, &uverbs_dev->uverbs_file_list, list) { - if (ufile->ucontext) + if (ufile->ucontext) { + down_write(&ufile->hw_destroy_rwsem); uverbs_user_mmap_disassociate(ufile); + up_write(&ufile->hw_destroy_rwsem); + } } mutex_unlock(&uverbs_dev->lists_mutex); } @@ -927,8 +926,6 @@ static int ib_uverbs_open(struct inode *inode, struct file *filp) mutex_init(&file->umap_lock); INIT_LIST_HEAD(&file->umaps); - mutex_init(&file->disassociation_lock); - filp->private_data = file; list_add_tail(&file->list, &dev->uverbs_file_list); mutex_unlock(&dev->lists_mutex); diff --git a/include/rdma/uverbs_types.h b/include/rdma/uverbs_types.h index 5a07f9a6dcd1..6f3622892c0c 100644 --- a/include/rdma/uverbs_types.h +++ b/include/rdma/uverbs_types.h @@ -180,8 +180,6 @@ struct ib_uverbs_file { struct page *disassociate_page; struct xarray idr; - - struct mutex disassociation_lock; }; extern const struct uverbs_obj_type_class uverbs_idr_class; From 1af874e9f4ce22ccf8b10ab5462f32c70d3be21a Mon Sep 17 00:00:00 2001 From: Shuangpeng Bai Date: Sun, 16 Aug 2026 00:45:10 -0400 Subject: [PATCH 0440/2366] IB/mlx4: Fix use-after-free on pkey sysfs registration failure register_pkey_tree() ignores errors from register_one_pkey_tree() and continues registering the remaining slaves. The per-slave error path has already released the pkey parent kobjects, but their pointers remain stored in the device. A later device cleanup therefore passes the stale pointers to kobject_put(), causing a use-after-free. Clear the parent pointers after releasing a failed slave tree and skip unregistered trees during device cleanup. This preserves the existing best-effort registration behavior while preventing a second cleanup of the failed tree. Fixes: c1e7e466120b ("IB/mlx4: Add iov directory in sysfs under the ib device") Cc: stable@vger.kernel.org Signed-off-by: Shuangpeng Bai Link: https://patch.msgid.link/20260816044510.3848996-1-shuangpeng.kernel@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/mlx4/sysfs.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/infiniband/hw/mlx4/sysfs.c b/drivers/infiniband/hw/mlx4/sysfs.c index e688ad66a895..5438224bf325 100644 --- a/drivers/infiniband/hw/mlx4/sysfs.c +++ b/drivers/infiniband/hw/mlx4/sysfs.c @@ -751,11 +751,13 @@ static int register_one_pkey_tree(struct mlx4_ib_dev *dev, int slave) kobject_put(p); } kobject_put(dev->dev_ports_parent[slave]); + dev->dev_ports_parent[slave] = NULL; err_ports: kobject_put(dev->pkeys.device_parent[slave]); /* extra put for the device_parent create_and_add */ kobject_put(dev->pkeys.device_parent[slave]); + dev->pkeys.device_parent[slave] = NULL; fail_dev: kobject_put(dev->iov_parent); @@ -785,6 +787,8 @@ static void unregister_pkey_tree(struct mlx4_ib_dev *device) return; for (slave = device->dev->persist->num_vfs; slave >= 0; --slave) { + if (!device->pkeys.device_parent[slave]) + continue; list_for_each_entry_safe(p, t, &device->pkeys.pkey_port_list[slave], entry) { From 67bfe48a29fbddfff77e13d4d327e49fca2c2be5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 25 Aug 2026 11:55:11 +0200 Subject: [PATCH 0441/2366] HID: bpf: mark struct hid_device as safe BPF pointer Commit ee9ad135b208 ("bpf: Reject a store through a fault prone pointer") in the BPF tree makes the verifier reject any writes to hid_device->{name,uniq,phys}. A simple solution is to mark the struct hid_device as safe from a BPF point of view. Suggested-by: Daniel Borkmann Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/hid_bpf_struct_ops.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/hid/bpf/hid_bpf_struct_ops.c b/drivers/hid/bpf/hid_bpf_struct_ops.c index 702c22fae136..56c53aca4511 100644 --- a/drivers/hid/bpf/hid_bpf_struct_ops.c +++ b/drivers/hid/bpf/hid_bpf_struct_ops.c @@ -62,6 +62,10 @@ struct hid_bpf_offset_write_range { u32 end; }; +struct hid_bpf_ctx__safe_trusted { + struct hid_device *hid; +}; + static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log, const struct bpf_reg_state *reg, int off, int size) @@ -86,6 +90,8 @@ static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log, const char *cur = NULL; int i; + BTF_TYPE_EMIT(struct hid_bpf_ctx__safe_trusted); + t = btf_type_by_id(reg->btf, reg->btf_id); for (i = 0; i < ARRAY_SIZE(write_ranges); i++) { From 1fb68c2e76386ac663819036c2c75cd476e433c1 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 25 Aug 2026 11:55:12 +0200 Subject: [PATCH 0442/2366] selftests/hid: Add a test to ensure we can write fields in hid_device hid_device->{name,uniq,phys} are all writeable fields, we need to have tests for them in case the verifier becomes too much strict. Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hid_bpf.c | 26 +++++++++++++++++++ tools/testing/selftests/hid/progs/hid.c | 26 +++++++++++++++++++ .../selftests/hid/progs/hid_bpf_helpers.h | 3 +++ 3 files changed, 55 insertions(+) diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index b851339308c2..069ebdbb4d1c 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -909,6 +909,32 @@ TEST_F(hid_bpf, test_rdesc_fixup_get_data_overflow) ASSERT_EQ(self->skel->bss->get_data_overflow_check, 1); } +TEST_F(hid_bpf, test_rdesc_fixup_change_uniq_name_phys) +{ + const struct test_program progs[] = { + { .name = "hid_rdesc_fixup_change_uniq_name_phys" }, + }; + char expected[256], buf[256] = {}; + int err; + + LOAD_PROGRAMS(progs); + + err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(buf)), buf); + ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWNAME"); + ASSERT_STREQ("name coming from bpf", buf); + + snprintf(expected, sizeof(expected), "%d phys:coming:from:bpf", self->hid.dev_id); + + err = ioctl(self->hidraw_fd, HIDIOCGRAWPHYS(sizeof(buf)), buf); + ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWPHYS"); + ASSERT_STREQ(expected, buf); + + err = ioctl(self->hidraw_fd, HIDIOCGRAWUNIQ(sizeof(buf)), buf); + ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWUNIQ"); + ASSERT_STREQ("uniq:coming:from:bpf", buf); + +} + static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) { diff --git a/tools/testing/selftests/hid/progs/hid.c b/tools/testing/selftests/hid/progs/hid.c index b21fbb13c926..361dc7eaad22 100644 --- a/tools/testing/selftests/hid/progs/hid.c +++ b/tools/testing/selftests/hid/progs/hid.c @@ -255,6 +255,32 @@ struct hid_bpf_ops rdesc_fixup_get_data_overflow = { .hid_rdesc_fixup = (void *)hid_rdesc_fixup_get_data_overflow, }; +SEC("?struct_ops.s/hid_rdesc_fixup") +int BPF_PROG(hid_rdesc_fixup_change_uniq_name_phys, struct hid_bpf_ctx *hid_ctx) +{ +#define HID_BPF_MEMCPY(target, str) \ + __builtin_memcpy(target, str, sizeof(str)) + + HID_BPF_MEMCPY(hid_ctx->hid->name, "name coming from bpf"); + HID_BPF_MEMCPY(hid_ctx->hid->uniq, "uniq:coming:from:bpf"); + /* hid_bpf relies on a phys being a rand % 1024 */ + for (int i = 0; i < 5; i++) { + if (!hid_ctx->hid->phys[i]) { + HID_BPF_MEMCPY(hid_ctx->hid->phys + i, " phys:coming:from:bpf"); + break; + } + } + +#undef HID_BPF_MEMCPY + + return 0; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops rdesc_fixup_change_uniq_name_phys = { + .hid_rdesc_fixup = (void *)hid_rdesc_fixup_change_uniq_name_phys, +}; + SEC("?struct_ops/hid_device_event") int BPF_PROG(hid_test_insert1, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) { diff --git a/tools/testing/selftests/hid/progs/hid_bpf_helpers.h b/tools/testing/selftests/hid/progs/hid_bpf_helpers.h index cdca912f3afd..05698793762a 100644 --- a/tools/testing/selftests/hid/progs/hid_bpf_helpers.h +++ b/tools/testing/selftests/hid/progs/hid_bpf_helpers.h @@ -61,6 +61,9 @@ enum hid_report_type { struct hid_device { unsigned int id; + char name[128]; + char phys[64]; + char uniq[64]; } __attribute__((preserve_access_index)); struct bpf_wq { From ce58f5a1843235d800ad724e86a3cf5c9c6f08ab Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 25 Aug 2026 11:55:13 +0200 Subject: [PATCH 0443/2366] selftests/hid: prepare test_rdesc_fixup_get_data_overflow for the new verifier The new verifier in the bpf-next branch is now capable of detecting the overflow that was triggered by test_rdesc_fixup_get_data_overflow. This is better in terms of UI, but now the test is failing and should be marked as expected to fail. Add a new parameter to load_programs() when we expect the test to fail, and dynamically validate the test by checkcing if it loads (it should fail to load with new verifier), but if it still loads, HID-BPF should detect the overflow itself and return an error in hid_bpf_get_data(). Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hid_bpf.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index 069ebdbb4d1c..7ab86296ff23 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -67,14 +67,17 @@ struct test_program { int insert_head; }; #define LOAD_PROGRAMS(progs) \ - load_programs(progs, ARRAY_SIZE(progs), _metadata, self, variant) + load_programs(progs, ARRAY_SIZE(progs), false, _metadata, self, variant) +#define LOAD_PROGRAMS_MAY_FAIL(progs) \ + load_programs(progs, ARRAY_SIZE(progs), true, _metadata, self, variant) #define LOAD_BPF \ - load_programs(NULL, 0, _metadata, self, variant) -static void load_programs(const struct test_program programs[], - const size_t progs_count, - struct __test_metadata *_metadata, - FIXTURE_DATA(hid_bpf) * self, - const FIXTURE_VARIANT(hid_bpf) * variant) + load_programs(NULL, 0, false, _metadata, self, variant) +static int load_programs(const struct test_program programs[], + const size_t progs_count, + bool load_may_fail, + struct __test_metadata *_metadata, + FIXTURE_DATA(hid_bpf) * self, + const FIXTURE_VARIANT(hid_bpf) * variant) { struct bpf_map *iter_map; int err = -EINVAL; @@ -128,6 +131,9 @@ static void load_programs(const struct test_program programs[], } err = hid__load(self->skel); + if (err && load_may_fail) + return err; + ASSERT_OK(err) TH_LOG("hid_skel_load failed: %d", err); for (int i = 0; i < progs_count; i++) { @@ -147,6 +153,7 @@ static void load_programs(const struct test_program programs[], self->hidraw_fd = open_hidraw(&self->hid); ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw"); + return 0; } /* @@ -904,7 +911,9 @@ TEST_F(hid_bpf, test_rdesc_fixup_get_data_overflow) { .name = "hid_rdesc_fixup_get_data_overflow" }, }; - LOAD_PROGRAMS(progs); + /* newer verifier can detect the overflow at load time */ + if (LOAD_PROGRAMS_MAY_FAIL(progs)) + return; ASSERT_EQ(self->skel->bss->get_data_overflow_check, 1); } From 2430eb81e44111b30eeb5273bbcf8b24ca517ef9 Mon Sep 17 00:00:00 2001 From: Griffin Kroah-Hartman Date: Wed, 19 Aug 2026 12:04:25 +0200 Subject: [PATCH 0444/2366] usb: image: mdc800: change kmalloc() to kzalloc() Change the kmalloc() calls in usb_mdc800_init() for irq_urb_buffer and download_urb_buffer to kzalloc(), avoiding potential stack leaks if a shorter message is received in mdc800_usb_irq() and mdc800_usb_download_notify() Assisted-by: gkh_clanker_t1000 Cc: stable Signed-off-by: Griffin Kroah-Hartman Link: https://patch.msgid.link/20260819-usb_misc_random-v1-1-43a0dcee3a32@kroah.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/mdc800.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/image/mdc800.c b/drivers/usb/image/mdc800.c index ca287b770e8c..f7caa1c5cbb7 100644 --- a/drivers/usb/image/mdc800.c +++ b/drivers/usb/image/mdc800.c @@ -1000,13 +1000,13 @@ static int __init usb_mdc800_init (void) mdc800->downloaded = 0; mdc800->written = 0; - mdc800->irq_urb_buffer=kmalloc (8, GFP_KERNEL); + mdc800->irq_urb_buffer=kzalloc (8, GFP_KERNEL); if (!mdc800->irq_urb_buffer) goto cleanup_on_fail; mdc800->write_urb_buffer=kmalloc (8, GFP_KERNEL); if (!mdc800->write_urb_buffer) goto cleanup_on_fail; - mdc800->download_urb_buffer=kmalloc (64, GFP_KERNEL); + mdc800->download_urb_buffer=kzalloc (64, GFP_KERNEL); if (!mdc800->download_urb_buffer) goto cleanup_on_fail; From dea99705bc8fcda12590cfeeae6d2ba47a7ef572 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 17 Aug 2026 20:22:39 +0200 Subject: [PATCH 0445/2366] usb: typec: mux: Fix typec_switch_match() The fwnode_typec_switch_get() sporadically returns NULL instead of an -EPROBE_DEFER for orientation-switch described in DT. This makes it impossible to discern whether the DT does describe an orientation-switch which did not probe yet, or whether the DT does not describe the switch. This happens with gpio-sbu-mux connected to an I2C GPIO expander. The class_find_device() on typec_switch_match() may return NULL in case the mux did not probe just yet early on boot. The sw_devs[] array can be empty on boot as well. If these two conditions occur, then the conditional if (to_typec_switch_dev(dev) == sw_devs[i]) evaluates to true and the match function returns NULL, which propagates to fwnode_typec_switch_get() which makes it look as if the orientation-switch was not described in DT. This is incorrect, because the mux driver will probe a bit later on, but at that point, the caller of fwnode_typec_switch_get() already got the NULL return value. The NULL return value also does not trigger IS_ERR(), therefore the caller driver interprets this as if the orientation-switch is not described in DT, and does not return -EPROBE_DEFER to try again, even if it should. Fix this by checking the class_find_device() return value, and return -EPROBE_DEFER if it is NULL right away. If the return value is not NULL, perform the deduplication test, and if that test passes, consider the return value to be already non-NULL. Fixes: a53b4f9c51a9 ("usb: typec: mux: avoid duplicated orientation switches") Cc: stable Signed-off-by: Marek Vasut Reviewed-by: Sebastian Reichel Tested-by: Jens Glathe Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260817182302.146546-1-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index 9b908c46bd7d..2bc7e8edb3cb 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -57,6 +57,8 @@ static void *typec_switch_match(const struct fwnode_handle *fwnode, */ dev = class_find_device(&typec_mux_class, NULL, fwnode, switch_fwnode_match); + if (!dev) + return ERR_PTR(-EPROBE_DEFER); /* Skip duplicates */ for (i = 0; i < TYPEC_MUX_MAX_DEVS; i++) @@ -65,7 +67,7 @@ static void *typec_switch_match(const struct fwnode_handle *fwnode, return NULL; } - return dev ? to_typec_switch_dev(dev) : ERR_PTR(-EPROBE_DEFER); + return to_typec_switch_dev(dev); } /** From d50b6442bef66abbe4694f918f8ad013f81d75cf Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Sat, 22 Aug 2026 09:24:58 +0200 Subject: [PATCH 0446/2366] usb: typec: mux: avoid duplicated mux switches Some devices use combo PHYs (i.e. USB3 + DisplayPort), which also handle the lane muxing. These PHYs are referenced twice from the USB-C connector (USB super-speed lines and SBU/AUX lines) resulting in the mux being configured twice. Avoid this by dropping duplicates. This is a re-application of b145c3f29d62 ("usb: typec: mux: avoid duplicated mux switches"), with fix derived from usb: typec: mux: Fix typec_switch_match() . Fixes: f576c75f95a5 ("Revert "usb: typec: mux: avoid duplicated mux switches"") Cc: stable Signed-off-by: Sebastian Reichel Co-developed-by: Sebastian Reichel Signed-off-by: Marek Vasut Tested-by: Jens Glathe Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260822072556.490594-1-marex@nabladev.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index 2bc7e8edb3cb..afa6fc181397 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -277,7 +277,9 @@ static int mux_fwnode_match(struct device *dev, const void *fwnode) static void *typec_mux_match(const struct fwnode_handle *fwnode, const char *id, void *data) { + struct typec_mux_dev **mux_devs = data; struct device *dev; + int i; /* * Device graph (OF graph) does not give any means to identify the @@ -292,8 +294,18 @@ static void *typec_mux_match(const struct fwnode_handle *fwnode, dev = class_find_device(&typec_mux_class, NULL, fwnode, mux_fwnode_match); + if (!dev) + return ERR_PTR(-EPROBE_DEFER); - return dev ? to_typec_mux_dev(dev) : ERR_PTR(-EPROBE_DEFER); + /* Skip duplicates */ + for (i = 0; i < TYPEC_MUX_MAX_DEVS; i++) + if (to_typec_mux_dev(dev) == mux_devs[i]) { + put_device(dev); + return NULL; + } + + + return to_typec_mux_dev(dev); } /** @@ -318,7 +330,8 @@ struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode) return ERR_PTR(-ENOMEM); count = fwnode_connection_find_matches(fwnode, "mode-switch", - NULL, typec_mux_match, + (void **)mux_devs, + typec_mux_match, (void **)mux_devs, ARRAY_SIZE(mux_devs)); if (count <= 0) { From 6b2a674fcc953378e5e750d47a888bbe51229de5 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Wed, 19 Aug 2026 23:51:58 +0530 Subject: [PATCH 0447/2366] usb: dwc3: google: Initialise probe properties with DWC3_DEFAULT_PROPERTIES dwc3_google_probe() zero initialises struct dwc3_probe_data and never assigns its properties member. The unspecified state of gsbuscfg0_reqinfo is encoded as DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED (0xffffffff), not as zero, so dwc3_get_software_properties() reads the zeroed field as a value the glue explicitly requested: if (properties->gsbuscfg0_reqinfo != DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED) { dwc->gsbuscfg0_reqinfo = properties->gsbuscfg0_reqinfo; return; } Two things follow. dwc3_config_soc_bus() programs GSBUSCFG0.REQINFO with zero on hardware that never asked for it, and the early return skips the walk over the parent devices, so a swnode or device tree supplied snps,gsbuscfg0-reqinfo would be ignored. Assign DWC3_DEFAULT_PROPERTIES so the unset fields carry their unspecified sentinels and the controller is left alone. Fixes: 8995a37371bf ("usb: dwc3: Add Google Tensor SoC DWC3 glue driver") Cc: stable Signed-off-by: Radhey Shyam Pandey Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260819182158.1351869-1-radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-google.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/dwc3/dwc3-google.c b/drivers/usb/dwc3/dwc3-google.c index 60ee4cc99b28..a01ca23cb6a8 100644 --- a/drivers/usb/dwc3/dwc3-google.c +++ b/drivers/usb/dwc3/dwc3-google.c @@ -442,6 +442,7 @@ static int dwc3_google_probe(struct platform_device *pdev) probe_data.dwc = &google->dwc; probe_data.res = res; probe_data.ignore_clocks_and_resets = true; + probe_data.properties = DWC3_DEFAULT_PROPERTIES; ret = dwc3_core_probe(&probe_data); if (ret) { ret = dev_err_probe(dev, ret, "failed to register DWC3 Core\n"); From b58e6200450d350314db0ecda7d6d1bde3281e80 Mon Sep 17 00:00:00 2001 From: Elson Serrao Date: Thu, 13 Aug 2026 08:14:56 -0700 Subject: [PATCH 0448/2366] usb: dwc3: clear forceRM when issuing EndTransfer The forceRM bit of the DEPCMD register controls the behavior of the EndTransfer command used to stop an active transfer. Older DWC3 programming guide revisions recommended setting forceRM=1 when issuing EndTransfer. Newer programming guide revisions recommend issuing EndTransfer with forceRM cleared. With forceRM=1 on DWC_usb31 v2.00a and v2.10a controllers, a transfer aborted through the ep_dequeue path was observed to remain active after EndTransfer completion. A subsequent StartTransfer issued on the same endpoint triggered writes associated with the aborted transfer. This resulted in an SMMU fault because the transfer buffer had already been unmapped during EndTransfer command-completion cleanup. Using forceRM=0 eliminates the issue. Although older DWC3 programming guide revisions recommended setting forceRM=1, no issues are known from using forceRM=0. Clear forceRM when issuing EndTransfer to provide consistent EndTransfer behavior and align with newer programming guide recommendations. Fixes: 1e43c86d84fb ("usb: dwc3: core: Add DWC31 version 2.00a controller") Cc: stable Signed-off-by: Elson Serrao Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260813151456.867008-1-elson.serrao@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/ep0.c | 2 +- drivers/usb/dwc3/gadget.c | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index bfe616194dfa..310b5ffb236a 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -304,7 +304,7 @@ void dwc3_ep0_out_start(struct dwc3 *dwc) dwc3_ep->flags &= ~DWC3_EP_DELAY_STOP; if (dwc->connected) - dwc3_stop_active_transfer(dwc3_ep, true, true); + dwc3_stop_active_transfer(dwc3_ep, false, true); else dwc3_remove_requests(dwc, dwc3_ep, -ESHUTDOWN); } diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index fa944856f956..f245e66cd13d 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1004,7 +1004,7 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action) * controller to generate an ERDY to initiate the * stream. */ - dwc3_stop_active_transfer(dep, true, true); + dwc3_stop_active_transfer(dep, false, true); /* * All stream eps will reinitiate stream on NoStream @@ -1032,7 +1032,7 @@ void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep, int status) { struct dwc3_request *req; - dwc3_stop_active_transfer(dep, true, false); + dwc3_stop_active_transfer(dep, false, false); /* If endxfer is delayed, avoid unmapping requests */ if (dep->flags & DWC3_EP_DELAY_STOP) @@ -1720,7 +1720,7 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep) if (ret == -EAGAIN) return ret; - dwc3_stop_active_transfer(dep, true, true); + dwc3_stop_active_transfer(dep, false, true); list_for_each_entry_safe(req, tmp, &dep->started_list, list) dwc3_gadget_move_cancelled_request(req, DWC3_REQUEST_STATUS_DEQUEUED); @@ -1757,6 +1757,11 @@ static int __dwc3_gadget_get_frame(struct dwc3 *dwc) * the controller won't update the TRB progress on command * completion. It also won't clear the HWO bit in the TRB. * The command will also not complete immediately in that case. + * + * Older programming guide revisions recommended setting ForceRM to 1 + * when ending a transfer. Newer programming guide revisions now + * recommend keeping ForceRM cleared, and TRBs are properly updated + * on command completion. */ static int __dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool interrupt) { @@ -1882,7 +1887,7 @@ static int dwc3_gadget_start_isoc_quirk(struct dwc3_ep *dep) * to wait for the next XferNotReady to test the command again */ if (cmd_status == 0) { - dwc3_stop_active_transfer(dep, true, true); + dwc3_stop_active_transfer(dep, false, true); return 0; } } @@ -2165,7 +2170,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, struct dwc3_request *t; /* wait until it is processed */ - dwc3_stop_active_transfer(dep, true, true); + dwc3_stop_active_transfer(dep, false, true); /* * Remove any started request if the transfer is @@ -2242,7 +2247,7 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol) return 0; } - dwc3_stop_active_transfer(dep, true, true); + dwc3_stop_active_transfer(dep, false, true); list_for_each_entry_safe(req, tmp, &dep->started_list, list) dwc3_gadget_move_cancelled_request(req, DWC3_REQUEST_STATUS_STALLED); @@ -3368,7 +3373,7 @@ static void dwc3_nostream_work(struct work_struct *work) dwc3_send_gadget_generic_command(dwc, cmd, dep->number); } else { dep->flags |= DWC3_EP_DELAY_START; - dwc3_stop_active_transfer(dep, true, true); + dwc3_stop_active_transfer(dep, false, true); spin_unlock_irqrestore(&dwc->lock, flags); return; } @@ -3726,7 +3731,7 @@ static bool dwc3_gadget_endpoint_trbs_complete(struct dwc3_ep *dep, if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && list_empty(&dep->started_list) && (list_empty(&dep->pending_list) || status == -EXDEV)) - dwc3_stop_active_transfer(dep, true, true); + dwc3_stop_active_transfer(dep, false, true); else if (dwc3_gadget_ep_should_continue(dep)) if (__dwc3_gadget_kick_transfer(dep) == 0) no_started_trb = false; From c9a48db776d7184981630ecc01a3ad30a8f7dc24 Mon Sep 17 00:00:00 2001 From: Chang Wu Date: Wed, 19 Aug 2026 23:20:27 +0800 Subject: [PATCH 0449/2366] usb: typec: hd3ss3220: track VBUS enable state per consumer regulator_is_enabled() reports the aggregate regulator state, not whether this consumer holds an enable reference. If another consumer enables VBUS first, the driver can skip its own regulator_enable() call and later attempt to drop a reference it never acquired, triggering an unbalanced regulator disable warning. Track successful enable and disable calls locally. Keep the state unchanged when an operation fails so a later role or ID notification retries the operation while this consumer keeps balanced references. Fixes: b3f9d6e491fd ("usb: typec: hd3ss3220: Check if regulator needs to be switched") Cc: stable Link: https://github.com/qualcomm-linux/kernel/issues/472 Signed-off-by: Chang Wu Reviewed-by: Heikki Krogerus Tested-by: Jan Remmet Reviewed-by: Krishna Kurapati Link: https://patch.msgid.link/20260819152027.90994-1-kunjinkao.jp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/hd3ss3220.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/hd3ss3220.c b/drivers/usb/typec/hd3ss3220.c index d0de5a2488f9..4eec90c82bae 100644 --- a/drivers/usb/typec/hd3ss3220.c +++ b/drivers/usb/typec/hd3ss3220.c @@ -62,6 +62,7 @@ struct hd3ss3220 { int id_irq; struct regulator *vbus; + bool vbus_enabled; }; static int hd3ss3220_set_power_opmode(struct hd3ss3220 *hd3ss3220, int power_opmode) @@ -208,7 +209,7 @@ static void hd3ss3220_regulator_control(struct hd3ss3220 *hd3ss3220, bool on) { int ret; - if (regulator_is_enabled(hd3ss3220->vbus) == on) + if (hd3ss3220->vbus_enabled == on) return; if (on) @@ -216,9 +217,13 @@ static void hd3ss3220_regulator_control(struct hd3ss3220 *hd3ss3220, bool on) else ret = regulator_disable(hd3ss3220->vbus); - if (ret) + if (ret) { dev_err(hd3ss3220->dev, "vbus regulator %s failed: %d\n", on ? "enable" : "disable", ret); + return; + } + + hd3ss3220->vbus_enabled = on; } static void hd3ss3220_set_role(struct hd3ss3220 *hd3ss3220) From 8b852965b8eaf910c314dc346967ed82c8d4f235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Tue, 1 Sep 2026 10:06:27 -0300 Subject: [PATCH 0450/2366] Input: evdev - zero absinfo before partial copy in EVIOCSABS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EVIOCSABS handler copies at most the user supplied ioctl size into an uninitialized on-stack struct input_absinfo: if (copy_from_user(&abs, p, min_t(size_t, size, sizeof(struct input_absinfo)))) The size comes from _IOC_SIZE() of the ioctl command and is therefore fully controlled by userspace. A short size leaves the trailing part of the structure holding whatever was on the kernel stack, and the whole structure is then stored into the device: dev->absinfo[t] = abs; EVIOCGABS hands that back to userspace, disclosing the stale stack bytes. Only the resolution field is currently cleared, which covers the legacy struct layout but not an arbitrarily short size. Zero the structure before the copy so any part not supplied by the caller reads back as zero. The existing resolution fixup is kept, since it also handles a size that partially overlaps that field. Fixes: 448cd1664a57 ("Input: evdev - rearrange ioctl handling") Cc: stable@vger.kernel.org Signed-off-by: Iván Ezequiel Rodriguez Link: https://patch.msgid.link/20260901130629.24078-2-ivanrwcm25@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/evdev.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index 3a718d600006..8bfaaa45e0b9 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -1229,6 +1229,8 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd, t = _IOC_NR(cmd) & ABS_MAX; + memset(&abs, 0, sizeof(abs)); + if (copy_from_user(&abs, p, min_t(size_t, size, sizeof(struct input_absinfo)))) return -EFAULT; From f84819ef8d66931ee3998fee3c4f03230f4cb6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Tue, 1 Sep 2026 10:06:28 -0300 Subject: [PATCH 0451/2366] Input: zero ff_effect before compat copy in input_ff_effect_from_user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the compat path input_ff_effect_from_user() aliases the caller's native struct ff_effect with the smaller struct ff_effect_compat and copies only the compat sized prefix: compat_effect = (struct ff_effect_compat *)effect; if (copy_from_user(compat_effect, buffer, sizeof(struct ff_effect_compat))) The tail of the native structure is never written. Callers pass an uninitialized on-stack object, for example evdev_do_ioctl() for EVIOCSFF, so those bytes keep their previous stack contents. input_ff_upload() then stores the full native structure in ff->effects[id], from where a uinput based force feedback daemon can read it back via UI_BEGIN_FF_UPLOAD, disclosing kernel stack memory to userspace. Zero the effect before the compat copy. Fixes: 2d56f3a32c0e ("Input: refactor evdev 32bit compat to be shareable with uinput") Cc: stable@vger.kernel.org Signed-off-by: Iván Ezequiel Rodriguez Link: https://patch.msgid.link/20260901130629.24078-3-ivanrwcm25@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/input-compat.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/input/input-compat.c b/drivers/input/input-compat.c index a5043193ead8..8860a0730294 100644 --- a/drivers/input/input-compat.c +++ b/drivers/input/input-compat.c @@ -76,6 +76,8 @@ int input_ff_effect_from_user(const char __user *buffer, size_t size, */ compat_effect = (struct ff_effect_compat *)effect; + memset(effect, 0, sizeof(*effect)); + if (copy_from_user(compat_effect, buffer, sizeof(struct ff_effect_compat))) return -EFAULT; From f0efaf1872949e96d213c8e910fd9517f7d7c406 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Wed, 29 Jul 2026 09:04:54 +0000 Subject: [PATCH 0452/2366] usb: gadget: midi2: Fix null-pointer dereference in f_midi2_free_ep_reqs A null-pointer dereference occurs in f_midi2_free_ep_reqs() when attempting to clean up an endpoint that was never initialized. When configuring the MIDI 2.0 gadget via configfs and setting the block direction to SNDRV_UMP_DIR_INPUT, the initialization of the midi1_ep_out endpoint is explicitly skipped during the gadget bind phase (f_midi2_bind()). As a result, the usb_ep->card field remains NULL. Later, when the host sets the alternate setting, f_midi2_set_alt() unconditionally stops both the IN and OUT endpoints by calling f_midi2_stop_eps(), which in turn calls f_midi2_free_ep_reqs() for both endpoints. When f_midi2_free_ep_reqs() is called for the uninitialized midi1_ep_out, it attempts to dereference usb_ep->card to determine the number of requests to free, leading to a crash. Fix this by using usb_ep->num_reqs instead of usb_ep->card->info.num_reqs in f_midi2_free_ep_reqs(). usb_ep->num_reqs is correctly set during f_midi2_init_ep() and remains 0 if the endpoint was never initialized, safely avoiding the loop. For consistency, apply the same change to f_midi2_alloc_ep_reqs(). Oops: general protection fault, probably for non-canonical address 0xdffffc00000000ee: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000770-0x0000000000000777] ... RIP: 0010:f_midi2_free_ep_reqs drivers/usb/gadget/function/f_midi2.c:1166 [inline] RIP: 0010:f_midi2_stop_eps+0x28e/0x4d0 drivers/usb/gadget/function/f_midi2.c:1246 ... Call Trace: f_midi2_set_alt+0x11c/0xf00 drivers/usb/gadget/function/f_midi2.c:1296 composite_setup+0x1ffd/0x3480 drivers/usb/gadget/composite.c:1933 configfs_composite_setup+0xbd/0x100 drivers/usb/gadget/configfs.c:1877 Fixes: 8b645922b223 ("usb: gadget: Add support for USB MIDI 2.0 function driver") Cc: stable Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+bbb6dad313f4aaa8da6b@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=bbb6dad313f4aaa8da6b Link: https://syzkaller.appspot.com/ai_job?id=8ce30b1a-8cf7-4e38-bcf7-1f69e6f6313f Signed-off-by: Aleksandr Nogikh Reviewed-by: Takashi Iwai Closes: https://syzkaller.appspot.com/bug?extid=01a17afb30637396955e Link: https://patch.msgid.link/cafe65f4-e1bb-46a3-901d-732814b861b2@mail.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_midi2.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index a4b72a6fad8a..e0b743dfaba5 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -1145,7 +1145,7 @@ static int f_midi2_alloc_ep_reqs(struct f_midi2_usb_ep *usb_ep) if (!usb_ep->reqs) return -EINVAL; - for (i = 0; i < midi2->info.num_reqs; i++) { + for (i = 0; i < usb_ep->num_reqs; i++) { if (usb_ep->reqs[i].req) continue; usb_ep->reqs[i].req = alloc_ep_req(usb_ep->usb_ep, @@ -1160,10 +1160,9 @@ static int f_midi2_alloc_ep_reqs(struct f_midi2_usb_ep *usb_ep) /* Free allocated requests */ static void f_midi2_free_ep_reqs(struct f_midi2_usb_ep *usb_ep) { - struct f_midi2 *midi2 = usb_ep->card; int i; - for (i = 0; i < midi2->info.num_reqs; i++) { + for (i = 0; i < usb_ep->num_reqs; i++) { if (!usb_ep->reqs[i].req) continue; free_ep_req(usb_ep->usb_ep, usb_ep->reqs[i].req); From 85f080fb87ed5cd3e46121be677f52c82f26a0ab Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Tue, 1 Sep 2026 20:26:49 +0800 Subject: [PATCH 0453/2366] Input: cyttsp5 - clamp the HID report size before memcpy The size field comes from the device and is used as the memcpy() length into response_buf, which is CY_MAX_INPUT bytes. Fixes: 5b0c03e24a06 ("Input: Add driver for Cypress Generation 5 touchscreen") Signed-off-by: Linkai Gong Link: https://patch.msgid.link/20260901122649.1173066-1-gonglinkai@kylinos.cn Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/cyttsp5.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/touchscreen/cyttsp5.c b/drivers/input/touchscreen/cyttsp5.c index 9266c07314be..e878a02dc9b7 100644 --- a/drivers/input/touchscreen/cyttsp5.c +++ b/drivers/input/touchscreen/cyttsp5.c @@ -710,6 +710,7 @@ static irqreturn_t cyttsp5_handle_irq(int irq, void *handle) size = 2; } else { report_id = ts->input_buf[2]; + size = min(size, CY_MAX_INPUT); } switch (report_id) { From e24e3370356bddb65d667985a332b5f8aeeb5f97 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Thu, 13 Aug 2026 20:16:15 +0200 Subject: [PATCH 0454/2366] usb: typec: tipd: Fix Thunderbolt altmode VDOs for cd321x The Intel VID status register is actually 9 bytes long and doesn't contain the raw VDOs but only the upper 16bits for device mode and enter mode. Shift those two fields into place and reconstruct the cable discover mode VDO from the data status register instead since it's not directly accessible. With this fixed now the correct VDOs are forwarded to the PHY and the to-be-submitted Thunderbolt/USB4 native host interface so that the right mode can be negotiated and the link actually comes up. Link: https://www.ti.com/lit/ug/slvubh2b/slvubh2b.pdf Fixes: 0b31c978935f ("usb: typec: tipd: Read USB4, Thunderbolt and DisplayPort status for cd321x") Fixes: 82432bbfb9e8 ("usb: typec: tipd: Handle mode transitions for CD321x") Cc: stable Signed-off-by: Sven Peter Tested-by: Rafay Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260813-b4-tipd-vdo-fix-v1-1-70317f2cd554@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 17 +++++++++++++---- drivers/usb/typec/tipd/tps6598x.h | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 522f56742aa9..f76f563dc42b 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -114,7 +114,6 @@ struct tps6598x_intel_vid_status_reg { __le32 attention_vdo; __le16 enter_vdo; __le16 device_mode; - __le16 cable_mode; } __packed; /* Standard Task return codes */ @@ -731,9 +730,19 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.mode == TYPEC_TBT_MODE) return; - tbt_data.cable_mode = le16_to_cpu(st->intel_vid_status.cable_mode); - tbt_data.device_mode = le16_to_cpu(st->intel_vid_status.device_mode); - tbt_data.enter_vdo = le16_to_cpu(st->intel_vid_status.enter_vdo); + tbt_data.cable_mode = TBT_MODE | + TBT_SET_CABLE_SPEED(TPS_DATA_STATUS_TBT_CABLE_SPEED(st->data_status)) | + TBT_SET_CABLE_ROUNDED(TPS_DATA_STATUS_TBT_CABLE_GEN(st->data_status)); + if (st->data_status & TPS_DATA_STATUS_OPTICAL_CABLE) + tbt_data.cable_mode |= TBT_CABLE_OPTICAL; + if (st->data_status & TPS_DATA_STATUS_ACTIVE_LINK_TRAIN) + tbt_data.cable_mode |= TBT_CABLE_LINK_TRAINING; + if (st->data_status & TPS_DATA_STATUS_ACTIVE_CABLE) + tbt_data.cable_mode |= TBT_CABLE_ACTIVE_PASSIVE; + tbt_data.device_mode = TBT_MODE | + (u32)le16_to_cpu(st->intel_vid_status.device_mode) << 16; + tbt_data.enter_vdo = + (u32)le16_to_cpu(st->intel_vid_status.enter_vdo) << 16; cd321x->state.alt = cd321x->port_altmode_tbt; cd321x->state.mode = TYPEC_TBT_MODE; cd321x->state.data = &tbt_data; diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index d4140f4da5bb..11ab58ba9a18 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -210,10 +210,10 @@ #define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT(x) \ TPS_FIELD_GET(TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK, (x)) #define TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK GENMASK(27, 25) -#define TPS_DATA_STATUS_TBT_CABLE_SPEED \ +#define TPS_DATA_STATUS_TBT_CABLE_SPEED(x) \ TPS_FIELD_GET(TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK, (x)) #define TPS_DATA_STATUS_TBT_CABLE_GEN_MASK GENMASK(29, 28) -#define TPS_DATA_STATUS_TBT_CABLE_GEN \ +#define TPS_DATA_STATUS_TBT_CABLE_GEN(x) \ TPS_FIELD_GET(TPS_DATA_STATUS_TBT_CABLE_GEN_MASK, (x)) /* Map data status to DP spec assignments */ From fed0aa7c6eaedc6c0d4e362fc91724aa47be4a7b Mon Sep 17 00:00:00 2001 From: Ivy Lopez Date: Sat, 15 Aug 2026 18:54:33 -0600 Subject: [PATCH 0455/2366] usb: gadget: f_midi2: fix use-after-free in string attribute show path f_midi2_opts_str_show() takes the string lock internally, but its callers dereference the opts->info. pointer before calling it, outside the lock. This races with f_midi2_opts_str_store(), which frees the old string under opts->lock when the attribute is written concurrently, the show path can read a pointer that gets freed before the lock inside str_show() is even taken. Change f_midi2_opts_str_show() to take a pointer to the string field, matching the existing pattern in f_midi2_opts_str_store(), and dereference it only after the lock is held. Update all three callers (iface_name, block name, and the EP string option macro) accordingly. Reported-by: syzbot+2280f1cca5e6b0c353e4@syzkaller.appspotmail.com Cc: stable Closes: https://syzkaller.appspot.com/bug?extid=2280f1cca5e6b0c353e4 Signed-off-by: Ivy Lopez Reviewed-by: Takashi Iwai Link: https://patch.msgid.link/20260816005434.34018-1-skunkolee@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_midi2.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index e0b743dfaba5..5b8b18281989 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -2177,13 +2177,13 @@ static ssize_t f_midi2_opts_bool_store(struct f_midi2_opts *opts, /* generic show/store for string */ static ssize_t f_midi2_opts_str_show(struct f_midi2_opts *opts, - const char *str, char *page) + const char **strp, char *page) { int result = 0; mutex_lock(&opts->lock); - if (str) - result = scnprintf(page, PAGE_SIZE, "%s\n", str); + if (*strp) + result = scnprintf(page, PAGE_SIZE, "%s\n", *strp); mutex_unlock(&opts->lock); return result; } @@ -2277,7 +2277,7 @@ static ssize_t f_midi2_block_opts_name_show(struct config_item *item, { struct f_midi2_block_opts *opts = to_f_midi2_block_opts(item); - return f_midi2_opts_str_show(opts->ep->opts, opts->info.name, page); + return f_midi2_opts_str_show(opts->ep->opts, &opts->info.name, page); } static ssize_t f_midi2_block_opts_name_store(struct config_item *item, @@ -2434,7 +2434,7 @@ static ssize_t f_midi2_ep_opts_##name##_show(struct config_item *item, \ char *page) \ { \ struct f_midi2_ep_opts *opts = to_f_midi2_ep_opts(item); \ - return f_midi2_opts_str_show(opts->opts, opts->info.name, page);\ + return f_midi2_opts_str_show(opts->opts, &opts->info.name, page);\ } \ \ static ssize_t f_midi2_ep_opts_##name##_store(struct config_item *item, \ @@ -2589,7 +2589,7 @@ static ssize_t f_midi2_opts_iface_name_show(struct config_item *item, { struct f_midi2_opts *opts = to_f_midi2_opts(item); - return f_midi2_opts_str_show(opts, opts->info.iface_name, page); + return f_midi2_opts_str_show(opts, &opts->info.iface_name, page); } static ssize_t f_midi2_opts_iface_name_store(struct config_item *item, From 7e07d3e4c389217d7d7171d80edf2e23ac70f1ea Mon Sep 17 00:00:00 2001 From: Jeffin Philip Date: Sat, 15 Aug 2026 11:10:06 +0530 Subject: [PATCH 0456/2366] usb: gadget: f_midi: initialize work in f_midi_alloc() f_midi_alloc initializes free_ref to 1 and it can only be incremented when a sound card is registered via f_midi_register_card(). f_midi_register_card() is only called in f_midi_bind() which actually performs INIT_WORK. If f_midi_bind() is never run, work is not initialized and the if condition in f_midi_free becomes true, this results in a warning later in __flush_work as work->func = 0. Fix this by moving INIT_WORK from f_midi_bind() to f_midi_alloc(). Reported-by: syzbot+d5fa3d224505c8610702@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d5fa3d224505c8610702 Fixes: 8653d71ce376 ("usb/gadget: f_midi: Replace tasklet with work") Cc: stable Signed-off-by: Jeffin Philip Reviewed-by: Takashi Iwai Link: https://patch.msgid.link/20260815054006.102325-1-jeffinphilip14@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_midi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index fba8cf787d6c..63fb6ee70a3d 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -879,7 +879,6 @@ static int f_midi_bind(struct usb_configuration *c, struct usb_function *f) int status, n, jack = 1, i = 0, endpoint_descriptor_index = 0; midi->gadget = cdev->gadget; - INIT_WORK(&midi->work, f_midi_in_work); status = f_midi_register_card(midi); if (status < 0) goto fail_register; @@ -1377,6 +1376,7 @@ static struct usb_function *f_midi_alloc(struct usb_function_instance *fi) status = -ENOMEM; goto midi_free; } + INIT_WORK(&midi->work, f_midi_in_work); midi->out_ports = opts->out_ports; midi->index = opts->index; midi->buflen = opts->buflen; From dd0eed9e165b1a6292f49e622e3dd0b7d99b106d Mon Sep 17 00:00:00 2001 From: Lovekesh Solanki Date: Tue, 25 Aug 2026 22:43:43 +0530 Subject: [PATCH 0457/2366] USB: gadget: fix NULL pointer dereference in gadget_dev_ioctl() gadget_dev_ioctl() reads dev->gadget before acquiring dev->lock, but dev->state is checked after acquiring the lock. Therefore a concurrent bind can change the device state between these operations, which can leave ioctl with a stale NULL gadget pointer and causing a NULL pointer dereference at gadget->ops->ioctl. Read dev->gadget while holding dev->lock so that the gadget pointer and device state are sampled consistently. Cc: stable Reported-by: Eulgyu Kim Link: https://lore.kernel.org/all/20260824113510.1141236-1-jjy600901@snu.ac.kr/ Reported-by: Jaeyoung Chung Link: https://lore.kernel.org/all/20260824113510.1141236-1-jjy600901@snu.ac.kr/ Signed-off-by: Lovekesh Solanki Reviewed-by: Alan Stern Link: https://patch.msgid.link/20260825171343.459630-1-lovekeshsolanki00@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index db961aaa3740..67c6ffaf4f72 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -1260,10 +1260,11 @@ ep0_poll (struct file *fd, poll_table *wait) static long gadget_dev_ioctl (struct file *fd, unsigned code, unsigned long value) { struct dev_data *dev = fd->private_data; - struct usb_gadget *gadget = dev->gadget; + struct usb_gadget *gadget; long ret = -ENOTTY; spin_lock_irq(&dev->lock); + gadget = dev->gadget; if (dev->state == STATE_DEV_OPENED || dev->state == STATE_DEV_UNBOUND) { /* Not bound to a UDC */ From 96c8ea3c5add7920b3c43840d1ea76b3354c8d2d Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Wed, 19 Aug 2026 08:49:37 -0700 Subject: [PATCH 0458/2366] block: save page offset gaps in cloned bio The cloned bio needs to inherit the accumulated gaps between vectors so that we can know if this bio can subscribe to the iova coalescing optimization. When cloning for a split, the gap only applies to the front bio since that's as far as has been processed. The remaining bio can reset its gaps to 0 since it advanced past the checked vectors, and will start its accounting from there on the next split check. Fixes: 2f6b2565d43c ("block: accumulate memory segment gaps per bio") Reported-by: Eric Auger Tested-by: Eric Auger Signed-off-by: Keith Busch Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260819154937.3903312-1-kbusch@meta.com Signed-off-by: Jens Axboe --- block/bio.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/block/bio.c b/block/bio.c index 898b2f5ef8c8..f95b63c0604a 100644 --- a/block/bio.c +++ b/block/bio.c @@ -859,6 +859,7 @@ static int __bio_clone(struct bio *bio, struct bio *bio_src, gfp_t gfp) bio->bi_ioprio = bio_src->bi_ioprio; bio->bi_write_hint = bio_src->bi_write_hint; bio->bi_write_stream = bio_src->bi_write_stream; + bio->bi_bvec_gap_bit = bio_src->bi_bvec_gap_bit; bio->bi_iter = bio_src->bi_iter; bio->bi_io_vec = bio_src->bi_io_vec; @@ -1972,6 +1973,14 @@ struct bio *bio_split(struct bio *bio, int sectors, bio_advance(bio, split->bi_iter.bi_size); + /* + * The gap bit is set when splitting to limits and only applies to the + * front bio that was split off. The remaining bio will calcualte its + * gap value when it is subsequently split to limits, so it is safe to + * re-initialize the value back to 0. + */ + bio->bi_bvec_gap_bit = 0; + if (bio_flagged(bio, BIO_TRACE_COMPLETION)) bio_set_flag(split, BIO_TRACE_COMPLETION); From e52349a5ea6a74d46dfb703fcb64e08b5af28e8c Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Mon, 10 Aug 2026 09:42:17 -0700 Subject: [PATCH 0459/2366] loop, zloop: fix dma_alignment for large or unreported limits A file system sets STATX_DIOALIGN with zeroed alignments when the file can't be used for direct I/O. The zero underflowed to UINT_MAX and triggered a queue limits validation warning. Fall back to the block device's limits when dio_mem_align isn't reported. A file system with a block size larger than PAGE_SIZE may also report a memory alignment that can't be expressed as a queue limit. File systems fall back to buffered I/O for requests that don't meet their alignment, so cap the reported limit to the largest possible value. Fixes: 6c8dec275ccc ("loop: set dma_alignment from the backing file for direct I/O") Fixes: c5059c1af2bd ("zloop: set dma_alignment from the backing files for direct I/O") Reported-by: syzbot+ac00e7bf7ac8c91af921@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ac00e7bf7ac8c91af921 Signed-off-by: Keith Busch Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260810164218.3721636-1-kbusch@meta.com Signed-off-by: Jens Axboe --- drivers/block/loop.c | 8 +++++--- drivers/block/zloop.c | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/block/loop.c b/drivers/block/loop.c index 6f12976035b0..758c20678bf6 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -458,12 +458,14 @@ static void loop_update_dio_alignment(struct loop_device *lo) * Use the dio alignment of the file system if provided. The incomoing * request's bio_vec is forwarded to the backing file unchanged, so its * required memory alignment becomes the device's dma_alignment when - * used for direct-io. + * used for direct-io. The file system reports zeroed alignments if the + * file can't be used for direct-io at all, so fall back to the block + * device limits in that case. */ if (!vfs_getattr(&file->f_path, &st, STATX_DIOALIGN, 0) && - (st.result_mask & STATX_DIOALIGN)) { + (st.result_mask & STATX_DIOALIGN) && st.dio_mem_align) { lo->lo_min_dio_size = st.dio_offset_align; - lo->lo_dio_mem_align = st.dio_mem_align - 1; + lo->lo_dio_mem_align = min(st.dio_mem_align - 1, PAGE_SIZE - 1); return; } diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c index 4323ac108cae..f0ca221524db 100644 --- a/drivers/block/zloop.c +++ b/drivers/block/zloop.c @@ -1042,12 +1042,14 @@ static int zloop_get_block_size(struct zloop_device *zlo, * Use the dio alignment of the file system if provided. The incoming * request's bio_vec is forwarded to the backing file unchanged, so its * required memory alignment becomes the device's dma_alignment when - * used for direct-io. + * used for direct-io. The file system reports zeroed alignments if the + * file can't be used for direct-io at all, so fall back to the block + * device limits in that case. */ if (!vfs_getattr(&zone->file->f_path, &st, STATX_DIOALIGN, 0) && - (st.result_mask & STATX_DIOALIGN)) { + (st.result_mask & STATX_DIOALIGN) && st.dio_mem_align) { zlo->block_size = st.dio_offset_align; - zlo->dio_mem_align = st.dio_mem_align - 1; + zlo->dio_mem_align = min(st.dio_mem_align - 1, PAGE_SIZE - 1); } else if (sb_bdev) { zlo->block_size = bdev_physical_block_size(sb_bdev); zlo->dio_mem_align = bdev_dma_alignment(sb_bdev); From 445fc368c6bc73eff0aeb3818cf5f355facfbb16 Mon Sep 17 00:00:00 2001 From: Liu Qi Date: Fri, 21 Aug 2026 17:04:16 +0800 Subject: [PATCH 0460/2366] usb-storage: ene_ub6250: fix race between scan work and probe ene_ub6250_probe() calls usb_stor_probe2(), which starts the usb-storage infrastructure and schedules the delayed scan work. The driver then calls ene_get_card_type(), which sends an ENE command through ene_send_scsi_cmd() and the usb-storage bulk transfer helpers. Both the delayed scan work, through usb_stor_Bulk_max_lun(), and ene_get_card_type() use us->current_urb. The scan work serializes this access with us->dev_mutex, but the ENE card-type probe does not. If the scan work runs while ene_get_card_type() is still using us->current_urb, usb_submit_urb() warns that the URB is already active. Serialize ene_get_card_type() with us->dev_mutex, matching the locking used by the scan path. Reported-by: syzbot+22ea20ef3afb6785b122@syzkaller.appspotmail.com Cc: stable Closes: https://syzkaller.appspot.com/bug?extid=22ea20ef3afb6785b122 Assisted-by: Qwen:Qwen3.6 Signed-off-by: Liu Qi Acked-by: Alan Stern Link: https://patch.msgid.link/20260821090416.1247127-1-liuqi@longcheer.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/ene_ub6250.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/storage/ene_ub6250.c b/drivers/usb/storage/ene_ub6250.c index ed49a3bc859c..895f90c7a3fa 100644 --- a/drivers/usb/storage/ene_ub6250.c +++ b/drivers/usb/storage/ene_ub6250.c @@ -2357,7 +2357,9 @@ static int ene_ub6250_probe(struct usb_interface *intf, return result; /* probe card type */ + mutex_lock(&us->dev_mutex); result = ene_get_card_type(us, REG_CARD_STATUS, info->bbuf); + mutex_unlock(&us->dev_mutex); if (result != USB_STOR_XFER_GOOD) { usb_stor_disconnect(intf); return USB_STOR_TRANSPORT_ERROR; From eae6460f617382044c5afe5ef202f4d8b2c099b5 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Thu, 20 Aug 2026 12:45:31 +0200 Subject: [PATCH 0461/2366] usb: cdnsp: fix wakeup from S3 after controller context loss CDNSP controller may lose its runtime register programming across S3 suspend/resume, depending on SoC power domain configuration. After resume the operational and interrupter registers may contain reset values, which prevents the gadget side from recovering correctly and breaks wakeup from S3. Fix this by detecting whether the controller lost its register context after resume and handling both cases: - If context was lost (CFG_3XPORT_U1_PIPE_CLK_GATE_EN set or power lost): reset the controller and reprogram the state required for normal operation, including the command ring, DCBAA pointer, doorbell base, event ring, ERST base/size and event ring dequeue pointer. - If context was retained: restart the controller directly without reprogramming registers. Issue a wakeup if the link was in U3 before suspend. Move the basic controller register programming out of the one-time memory initialization path and make it reusable from the resume path. Also separate ring allocation from ring initialization so that rings can be reinitialized without reallocating DMA memory. Always perform the full suspend sequence regardless of the current link state. Previously, if the device was already in U3, the suspend callback returned early without stopping the controller, which could lead to commands being issued on a disabled slot during resume. Fixes: 3d82904559f4 ("usb: cdnsp: cdns3 Add main part of Cadence USBSSP DRD Driver") Cc: stable Signed-off-by: Pawel Laszczak Acked-by: Peter Chen Link: https://patch.msgid.link/20260820-suspend_resume_fix-v3-1-5a713098b977@cadence.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdnsp-gadget.c | 111 +++++++++++++++++++++++++++++-- drivers/usb/cdns3/cdnsp-gadget.h | 1 + drivers/usb/cdns3/cdnsp-mem.c | 98 ++++++++++----------------- 3 files changed, 142 insertions(+), 68 deletions(-) diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index 7a516e509198..e84405352caa 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -1338,7 +1338,6 @@ static int cdnsp_run(struct cdnsp_device *pdev, cdnsp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); - ret = cdnsp_start(pdev); if (ret) { ret = -ENODEV; @@ -1837,6 +1836,82 @@ static void cdnsp_get_rev_cap(struct cdnsp_device *pdev) readl(&pdev->rev_cap->tx_buff_size)); } +static void cdnsp_set_event_deq(struct cdnsp_device *pdev) +{ + dma_addr_t deq; + u64 temp; + + deq = cdnsp_trb_virt_to_dma(pdev->event_ring->deq_seg, + pdev->event_ring->dequeue); + + /* Update controller event ring dequeue pointer */ + temp = cdnsp_read_64(&pdev->ir_set->erst_dequeue); + temp &= ERST_PTR_MASK; + + /* + * Don't clear the EHB bit (which is RW1C) because + * there might be more events to service. + */ + temp &= ~ERST_EHB; + + cdnsp_write_64(((u64)deq & (u64)~ERST_PTR_MASK) | temp, + &pdev->ir_set->erst_dequeue); +} + +static void cdnsp_add_interrupter(struct cdnsp_device *pdev) +{ + u64 erst_base; + u32 erst_size; + + /* Set ERST count with the number of entries in the segment table. */ + erst_size = readl(&pdev->ir_set->erst_size); + erst_size &= ERST_SIZE_MASK; + erst_size |= ERST_NUM_SEGS; + writel(erst_size, &pdev->ir_set->erst_size); + + /* Set the segment table base address. */ + erst_base = cdnsp_read_64(&pdev->ir_set->erst_base); + erst_base &= ERST_PTR_MASK; + erst_base |= (pdev->erst.erst_dma_addr & (u64)~ERST_PTR_MASK); + cdnsp_write_64(erst_base, &pdev->ir_set->erst_base); + + /* Set the event ring dequeue address. */ + cdnsp_set_event_deq(pdev); +} + +/* Set up basic CDNSP registers */ +static void cdnsp_init(struct cdnsp_device *pdev) +{ + unsigned int val; + u64 val_64; + + val = readl(&pdev->op_regs->config_reg); + val |= ((val & ~MAX_DEVS) | CDNSP_DEV_MAX_SLOTS) | CONFIG_U3E; + writel(val, &pdev->op_regs->config_reg); + + /* Initialize the Command ring */ + cdnsp_ring_init(pdev, pdev->cmd_ring); + + /* Set the address in the Command Ring Control register */ + val_64 = cdnsp_read_64(&pdev->op_regs->cmd_ring); + val_64 = (val_64 & (u64)CMD_RING_RSVD_BITS) | + (pdev->cmd_ring->first_seg->dma & (u64)~CMD_RING_RSVD_BITS) | + pdev->cmd_ring->cycle_state; + cdnsp_write_64(val_64, &pdev->op_regs->cmd_ring); + + /* Set Device Context Base Address Array pointer */ + cdnsp_write_64(pdev->dcbaa->dma, &pdev->op_regs->dcbaa_ptr); + + /* Set Doorbell array pointer */ + val = readl(&pdev->cap_regs->db_off); + val &= DBOFF_MASK; + pdev->dba = (void __iomem *)pdev->cap_regs + val; + + /* Initialize the Primary interrupter */ + cdnsp_ring_init(pdev, pdev->event_ring); + cdnsp_add_interrupter(pdev); +} + static int cdnsp_gen_setup(struct cdnsp_device *pdev) { int ret; @@ -1902,6 +1977,8 @@ static int cdnsp_gen_setup(struct cdnsp_device *pdev) if (ret) return ret; + cdnsp_init(pdev); + /* * Software workaround for U1: after transition * to U1 the controller starts gating clock, and in some cases, @@ -2031,9 +2108,6 @@ static int cdnsp_gadget_suspend(struct cdns *cdns, bool do_wakeup) struct cdnsp_device *pdev = cdns->gadget_dev; unsigned long flags; - if (pdev->link_state == XDEV_U3) - return 0; - spin_lock_irqsave(&pdev->lock, flags); cdnsp_disconnect_gadget(pdev); cdnsp_stop(pdev); @@ -2047,12 +2121,38 @@ static int cdnsp_gadget_resume(struct cdns *cdns, bool lost_power) struct cdnsp_device *pdev = cdns->gadget_dev; enum usb_device_speed max_speed; unsigned long flags; + bool context_lost; + u32 val; int ret; if (!pdev->gadget_driver) return 0; spin_lock_irqsave(&pdev->lock, flags); + val = readl(&pdev->port3x_regs->mode_2); + context_lost = !!(val & CFG_3XPORT_U1_PIPE_CLK_GATE_EN) || lost_power; + + if (context_lost) { + cdnsp_halt(pdev); + cdnsp_set_apb_timeout_value(pdev); + + /* Reset the internal controller memory state and registers. */ + ret = cdnsp_reset(pdev); + if (ret) + goto unlock; + + val = readl(&pdev->port3x_regs->mode_2); + val &= ~CFG_3XPORT_U1_PIPE_CLK_GATE_EN; + writel(val, &pdev->port3x_regs->mode_2); + + cdnsp_clear_cmd_ring(pdev); + + memset(pdev->event_ring->first_seg->trbs, 0, + sizeof(union cdnsp_trb) * (TRBS_PER_SEGMENT)); + + cdnsp_init(pdev); + } + max_speed = pdev->gadget_driver->max_speed; /* Limit speed if necessary. */ @@ -2060,9 +2160,10 @@ static int cdnsp_gadget_resume(struct cdns *cdns, bool lost_power) ret = cdnsp_run(pdev, max_speed); - if (pdev->link_state == XDEV_U3) + if (!context_lost && pdev->link_state == XDEV_U3) __cdnsp_gadget_wakeup(pdev); +unlock: spin_unlock_irqrestore(&pdev->lock, flags); return ret; diff --git a/drivers/usb/cdns3/cdnsp-gadget.h b/drivers/usb/cdns3/cdnsp-gadget.h index c44bca348a41..c3ae5040f9cc 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.h +++ b/drivers/usb/cdns3/cdnsp-gadget.h @@ -1510,6 +1510,7 @@ int cdnsp_endpoint_init(struct cdnsp_device *pdev, int cdnsp_ring_expansion(struct cdnsp_device *pdev, struct cdnsp_ring *ring, unsigned int num_trbs, gfp_t flags); +void cdnsp_ring_init(struct cdnsp_device *pdev, struct cdnsp_ring *ring); struct cdnsp_ring *cdnsp_dma_to_transfer_ring(struct cdnsp_ep *ep, u64 address); int cdnsp_alloc_stream_info(struct cdnsp_device *pdev, struct cdnsp_ep *pep, diff --git a/drivers/usb/cdns3/cdnsp-mem.c b/drivers/usb/cdns3/cdnsp-mem.c index 83f3384b735d..419309c8439e 100644 --- a/drivers/usb/cdns3/cdnsp-mem.c +++ b/drivers/usb/cdns3/cdnsp-mem.c @@ -394,13 +394,6 @@ static struct cdnsp_ring *cdnsp_ring_alloc(struct cdnsp_device *pdev, if (ret) goto fail; - /* Only event ring does not use link TRB. */ - if (type != TYPE_EVENT) - ring->last_seg->trbs[TRBS_PER_SEGMENT - 1].link.control |= - cpu_to_le32(LINK_TOGGLE); - - cdnsp_initialize_ring_info(ring); - trace_cdnsp_ring_alloc(ring); return ring; fail: kfree(ring); @@ -603,6 +596,7 @@ int cdnsp_alloc_stream_info(struct cdnsp_device *pdev, if (!cur_ring) goto cleanup_rings; + cdnsp_ring_init(pdev, cur_ring); cur_ring->stream_id = cur_stream; cur_ring->trb_address_map = &stream_info->trb_address_map; @@ -698,6 +692,8 @@ static int cdnsp_alloc_priv_device(struct cdnsp_device *pdev) if (!pdev->eps[0].ring) goto fail; + cdnsp_ring_init(pdev, pdev->eps[0].ring); + /* Point to output device context in dcbaa. */ pdev->dcbaa->dev_context_ptrs[1] = cpu_to_le64(pdev->out_ctx.dma); pdev->cmd.in_ctx = &pdev->in_ctx; @@ -991,6 +987,8 @@ int cdnsp_endpoint_init(struct cdnsp_device *pdev, if (!pep->ring) return -ENOMEM; + cdnsp_ring_init(pdev, pep->ring); + pep->skip = false; /* Fill the endpoint context */ @@ -1096,28 +1094,6 @@ void cdnsp_mem_cleanup(struct cdnsp_device *pdev) pdev->active_port = NULL; } -static void cdnsp_set_event_deq(struct cdnsp_device *pdev) -{ - dma_addr_t deq; - u64 temp; - - deq = cdnsp_trb_virt_to_dma(pdev->event_ring->deq_seg, - pdev->event_ring->dequeue); - - /* Update controller event ring dequeue pointer */ - temp = cdnsp_read_64(&pdev->ir_set->erst_dequeue); - temp &= ERST_PTR_MASK; - - /* - * Don't clear the EHB bit (which is RW1C) because - * there might be more events to service. - */ - temp &= ~ERST_EHB; - - cdnsp_write_64(((u64)deq & (u64)~ERST_PTR_MASK) | temp, - &pdev->ir_set->erst_dequeue); -} - static void cdnsp_add_in_port(struct cdnsp_device *pdev, struct cdnsp_port *port, __le32 __iomem *addr) @@ -1226,6 +1202,36 @@ static int cdnsp_setup_port_arrays(struct cdnsp_device *pdev) return 0; } +static void cdnsp_initialize_ring_segments(struct cdnsp_device *pdev, struct cdnsp_ring *ring) +{ + struct cdnsp_segment *seg; + + /* Only event ring does not use link TRB. */ + if (ring->type == TYPE_EVENT) + return; + + seg = ring->first_seg; + + while (seg) { + struct cdnsp_segment *next = seg->next; + + cdnsp_link_segments(pdev, seg, next, ring->type); + if (next == ring->first_seg) + break; + + seg = next; + } + + ring->last_seg->trbs[TRBS_PER_SEGMENT - 1].link.control |= cpu_to_le32(LINK_TOGGLE); +} + +void cdnsp_ring_init(struct cdnsp_device *pdev, struct cdnsp_ring *ring) +{ + cdnsp_initialize_ring_segments(pdev, ring); + cdnsp_initialize_ring_info(ring); + trace_cdnsp_ring_alloc(ring); +} + /* * Initialize memory for CDNSP (one-time init). * @@ -1237,10 +1243,8 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) { struct device *dev = pdev->dev; int ret = -ENOMEM; - unsigned int val; dma_addr_t dma; u32 page_size; - u64 val_64; /* * Use 4K pages, since that's common and the minimum the @@ -1248,10 +1252,6 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) */ page_size = 1 << 12; - val = readl(&pdev->op_regs->config_reg); - val |= ((val & ~MAX_DEVS) | CDNSP_DEV_MAX_SLOTS) | CONFIG_U3E; - writel(val, &pdev->op_regs->config_reg); - /* * Doorbell array must be physically contiguous * and 64-byte (cache line) aligned. @@ -1263,8 +1263,6 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) pdev->dcbaa->dma = dma; - cdnsp_write_64(dma, &pdev->op_regs->dcbaa_ptr); - /* * Initialize the ring segment pool. The ring must be a contiguous * structure comprised of TRBs. The TRBs must be 16 byte aligned, @@ -1290,17 +1288,6 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) if (!pdev->cmd_ring) goto destroy_device_pool; - /* Set the address in the Command Ring Control register */ - val_64 = cdnsp_read_64(&pdev->op_regs->cmd_ring); - val_64 = (val_64 & (u64)CMD_RING_RSVD_BITS) | - (pdev->cmd_ring->first_seg->dma & (u64)~CMD_RING_RSVD_BITS) | - pdev->cmd_ring->cycle_state; - cdnsp_write_64(val_64, &pdev->op_regs->cmd_ring); - - val = readl(&pdev->cap_regs->db_off); - val &= DBOFF_MASK; - pdev->dba = (void __iomem *)pdev->cap_regs + val; - /* Set ir_set to interrupt register set 0 */ pdev->ir_set = &pdev->run_regs->ir_set[0]; @@ -1317,21 +1304,6 @@ int cdnsp_mem_init(struct cdnsp_device *pdev) if (ret) goto free_event_ring; - /* Set ERST count with the number of entries in the segment table. */ - val = readl(&pdev->ir_set->erst_size); - val &= ERST_SIZE_MASK; - val |= ERST_NUM_SEGS; - writel(val, &pdev->ir_set->erst_size); - - /* Set the segment table base address. */ - val_64 = cdnsp_read_64(&pdev->ir_set->erst_base); - val_64 &= ERST_PTR_MASK; - val_64 |= (pdev->erst.erst_dma_addr & (u64)~ERST_PTR_MASK); - cdnsp_write_64(val_64, &pdev->ir_set->erst_base); - - /* Set the event ring dequeue address. */ - cdnsp_set_event_deq(pdev); - ret = cdnsp_setup_port_arrays(pdev); if (ret) goto free_erst; From 6e2b571b0a54755b06e092501913e1dfefe75d6c Mon Sep 17 00:00:00 2001 From: Kanishka De Silva Date: Sun, 30 Aug 2026 12:31:33 +0530 Subject: [PATCH 0462/2366] ublk: clear VM_MAYWRITE on read-only ublk char device mmap ublk_ch_mmap() rejects mmap requests with VM_WRITE set, but never clears VM_MAYWRITE on the resulting read-only mapping. This allows a userspace daemon to mmap the per-queue command buffer PROT_READ, then upgrade it to PROT_WRITE via mprotect(), since VM_MAYWRITE was never cleared. The command buffer holds struct ublksrv_io_desc entries that are kernel-written ABI; a writable mapping lets an unprivileged daemon process corrupt fields such as addr, op_flags, nr_sectors, and start_sector. Same bug class as the drm/panthor and drm/vc4 VM_MAYWRITE fixes, and the 2026-08-13 ptp/vmclock fix (a5edadbae57e). Verified via mprotect() PoC: before the fix, a PROT_READ mapping can be upgraded to PROT_READ|PROT_WRITE and a write into the command buffer corrupts io_desc fields (confirmed under KASAN). After the fix, mprotect() returns -EACCES. Fixes: 3fee8d7599e1 ("ublk_drv: add io_uring based userspace block driver") Cc: stable@vger.kernel.org Signed-off-by: Kanishka De Silva Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260830070133.559-1-kpskanna1915@gmail.com Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 6c5bec7da97c..e5ba07d8d281 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -2653,6 +2653,12 @@ static int ublk_ch_mmap(struct file *filp, struct vm_area_struct *vma) if (vma->vm_flags & VM_WRITE) return -EPERM; + /* + * The per-queue command buffer is kernel-written ABI; prevent + * the daemon from upgrading to writable via mprotect(). + */ + vm_flags_clear(vma, VM_MAYWRITE); + end = UBLKSRV_CMD_BUF_OFFSET + ub->dev_info.nr_hw_queues * max_sz; if (phys_off < UBLKSRV_CMD_BUF_OFFSET || phys_off >= end) return -EINVAL; From 4ffee1aebb0c0ffcda9faffd17834ea9b00d42cc Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 27 Jul 2026 21:34:14 +0900 Subject: [PATCH 0463/2366] usb: storage: realtek_cr: fix use-after-free on disconnect realtek_cr_destructor() calls timer_delete() before the chip containing the timer is freed. The timer callback may still be running and can rearm itself, resulting in a use-after-free. Use timer_shutdown_sync() to wait for the callback and prevent further rearming. Do this unconditionally because ss_en may be changed after the timer is armed. Move timer_setup() into init_realtek_cr() so the timer is initialized before any failure path can invoke the destructor. Found by static analysis. Fixes: e931830bb877 ("Realtek cr: Add autosuspend function.") Cc: stable Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260727123414.44700-1-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/realtek_cr.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/usb/storage/realtek_cr.c b/drivers/usb/storage/realtek_cr.c index af038b897c6b..c4b28744693b 100644 --- a/drivers/usb/storage/realtek_cr.c +++ b/drivers/usb/storage/realtek_cr.c @@ -916,7 +916,6 @@ static int realtek_cr_autosuspend_setup(struct us_data *us) us->proto_handler = rts51x_invoke_transport; chip->timer_expires = 0; - timer_setup(&chip->rts51x_suspend_timer, rts51x_suspend_timer_fn, 0); fw5895_init(us); /* enable autosuspend function of the usb device */ @@ -934,10 +933,7 @@ static void realtek_cr_destructor(void *extra) return; #ifdef CONFIG_REALTEK_AUTOPM - if (ss_en) { - timer_delete(&chip->rts51x_suspend_timer); - chip->timer_expires = 0; - } + timer_shutdown_sync(&chip->rts51x_suspend_timer); #endif kfree(chip->status); } @@ -982,6 +978,9 @@ static int init_realtek_cr(struct us_data *us) us->extra = chip; us->extra_destructor = realtek_cr_destructor; +#ifdef CONFIG_REALTEK_AUTOPM + timer_setup(&chip->rts51x_suspend_timer, rts51x_suspend_timer_fn, 0); +#endif us->max_lun = chip->max_lun = rts51x_get_max_lun(us); chip->us = us; From 9f6f095beec82a80daa666a3b2186a5b95841e9a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 17 Aug 2026 18:11:30 +0200 Subject: [PATCH 0464/2366] usb: f_mass_storage: Bump local buffer size in fsg_common_create_luns() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GCC (Debian 14.2.0-19) is not happy about the buffer size: drivers/usb/gadget/function/f_mass_storage.c:2970:48: error: ‘%d’ directive output may be truncated writing between 1 and 9 bytes into a region of size 5 [-Werror=format-truncation=] Bump the size to get it enough for all possible values. Note, although cfg->nluns is limited to FSG_MAX_LUNS (16), the compiler doesn't realize this and complains about the buffer size. Also note, the existing comment is wrong as size 8 for the whole buffer doesn't cover 100 mil numbers, hence drop it altogether. Fixes: b27c08c953e9 ("usb: gadget: f_mass_storage: create lun creation helpers for use in fsg_common_init") Cc: stable Acked-by: Alan Stern Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260817161239.1448582-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_mass_storage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index a50743caf083..1a0fbc808ee2 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -2960,7 +2960,7 @@ EXPORT_SYMBOL_GPL(fsg_common_create_lun); int fsg_common_create_luns(struct fsg_common *common, struct fsg_config *cfg) { - char buf[8]; /* enough for 100000000 different numbers, decimal */ + char buf[14]; int i, rc; fsg_common_remove_luns(common); From 2c0f5ca48674a5b5f9fa4a9c3325aa48053af0bc Mon Sep 17 00:00:00 2001 From: Jeffin Philip Date: Tue, 18 Aug 2026 09:29:04 +0530 Subject: [PATCH 0465/2366] usb: gadget: f_mass_storage: fix null pointer dereference in fsg_common_set_num_buffers() Previously fsg_num_buffers_validate() was removed as it was not necessary due to Kconfig setting the limits for n from 2 to 256 with default as 2. However, setting the page content in such a way that kstrtou8() reflects n value as either 0 or 1 bypasses these restrictions leading to a null pointer dereference if n is 0. Fix this by adding a check for n < 2 and returning -EINVAL if n is either 0 or 1 consistent with Kconfig logic. Reported-by: syzbot+791be35f1fbcc85d06d7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=791be35f1fbcc85d06d7 Fixes: fe5a6c48fd95 ("usb: gadget: storage: get rid of fsg_num_buffers_validate()") Cc: stable Signed-off-by: Jeffin Philip Acked-by: Alan Stern Link: https://patch.msgid.link/20260818035904.10324-1-jeffinphilip14@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_mass_storage.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index 1a0fbc808ee2..fc4818fb2a8b 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -2747,6 +2747,9 @@ int fsg_common_set_num_buffers(struct fsg_common *common, unsigned int n) struct fsg_buffhd *bh, *buffhds; int i; + if (n < 2) + return -EINVAL; + buffhds = kzalloc_objs(*buffhds, n); if (!buffhds) return -ENOMEM; From 7b0df6efd143f8085bdb68778a013a46f1349913 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 19 Aug 2026 16:14:48 +0000 Subject: [PATCH 0466/2366] usb: typec: qcom-pmic: cancel reset_work on stop pdphy_stop() disables IRQs but leaves reset_work pending. If the IRQ handler schedules it just before disable_irq(), the work runs after remove() frees the struct via devm. Call cancel_work_sync() after disabling IRQs to close the window. This issue was found by an in-house static analysis tool. Fixes: a4422ff22142 ("usb: typec: qcom: Add Qualcomm PMIC Type-C driver") Cc: stable Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Reviewed-by: Konrad Dybcio Reviewed-by: Bryan O'Donoghue Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260819161448.76597-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c index e6b28648f440..926fa017ebaf 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c @@ -543,6 +543,8 @@ static void qcom_pmic_typec_pdphy_stop(struct pmic_typec *tcpm) for (i = 0; i < pmic_typec_pdphy->nr_irqs; i++) disable_irq(pmic_typec_pdphy->irq_data[i].irq); + cancel_work_sync(&pmic_typec_pdphy->reset_work); + qcom_pmic_typec_pdphy_reset_on(pmic_typec_pdphy); regulator_disable(pmic_typec_pdphy->vdd_pdphy); From 6e74ac5c596fd246e37eadfc354567179ccbe9aa Mon Sep 17 00:00:00 2001 From: Jeffin Philip Date: Sun, 16 Aug 2026 11:47:12 +0530 Subject: [PATCH 0467/2366] usb: gadget: fix null pointer dereference in usb_put_function_instance() usb_put_function_instance() attempts to dereference fd inside fi struct to get mod in uvc_alloc_inst() error path. However, fd is not allocated until later in try_get_usb_function_instance() after allocating fi in uvc_alloc_inst() and thus guranteed to be null in error path. Fix this by adding a null check for fi->fd that returns if fd is null. Reported-by: syzbot+fd6ef980cf1c722be639@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=fd6ef980cf1c722be639 Fixes: 0062f6e56f70 ("usb: gadget: add a forward pointer from usb_function to its "instance"") Cc: stable Signed-off-by: Jeffin Philip Link: https://patch.msgid.link/20260816061712.15547-1-jeffinphilip14@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/functions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/functions.c b/drivers/usb/gadget/functions.c index 203361a64212..70e31c40e267 100644 --- a/drivers/usb/gadget/functions.c +++ b/drivers/usb/gadget/functions.c @@ -70,7 +70,7 @@ void usb_put_function_instance(struct usb_function_instance *fi) { struct module *mod; - if (!fi) + if (!fi || !fi->fd) return; mod = fi->fd->mod; From 263f7d61a4201cde16849b2d016251806e7418be Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 20 Aug 2026 13:53:06 +0000 Subject: [PATCH 0468/2366] usb: typec: qcom-pmic-typec: disable cc_debounce_dwork on stop cc_debounce_dwork is queued from the set_cc() and start_toggling() callbacks, which run from TCPM's kthread worker. port_stop() returns before tcpm_unregister_port() destroys that worker. Flushing the worker during unregister may therefore run a callback which queues the delayed work after port_stop() has returned. The delayed work can then run after devres has freed pmic_typec_port. Use disable_delayed_work_sync() in port_stop() to cancel a pending instance and prevent the TCPM callbacks from queueing another one. This issue was found by an in-house static analysis tool. Fixes: a4422ff22142 ("usb: typec: qcom: Add Qualcomm PMIC Type-C driver") Cc: stable # v6.10+ Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260820135307.153773-2-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c index bf985efe1cd6..d43799f43184 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c @@ -693,6 +693,8 @@ static void qcom_pmic_typec_port_stop(struct pmic_typec *tcpm) for (i = 0; i < pmic_typec_port->nr_irqs; i++) disable_irq(pmic_typec_port->irq_data[i].irq); + + disable_delayed_work_sync(&pmic_typec_port->cc_debounce_dwork); } int qcom_pmic_typec_port_probe(struct platform_device *pdev, From c9273c83885835dbd1e8835d5665dfb8503d65e0 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 20 Aug 2026 13:53:07 +0000 Subject: [PATCH 0469/2366] usb: typec: qcom-pmic-typec: drain cc_debounce_dwork if port_start() fails cc_debounce_dwork can be queued before port_start() fails: tcpm_register_port() runs first, and its state machine may invoke set_cc() or start_toggling() from the TCPM worker. The error path then calls tcpm_unregister_port(), whose worker flush may queue the delayed work before devres frees pmic_typec_port. Disable and drain the delayed work directly at port_start()'s error exit. Do not use port_stop() for this path: its IRQs use IRQF_NO_AUTOEN and are enabled only after a successful port_start(). This issue was found by an in-house static analysis tool. Fixes: a4422ff22142 ("usb: typec: qcom: Add Qualcomm PMIC Type-C driver") Cc: stable # v6.10+ Suggested-by: Bryan O'Donoghue Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260820135307.153773-3-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c index d43799f43184..d3523435f3e0 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c @@ -683,6 +683,9 @@ static int qcom_pmic_typec_port_start(struct pmic_typec *tcpm, enable_irq(pmic_typec_port->irq_data[i].irq); done: + if (ret) + disable_delayed_work_sync(&pmic_typec_port->cc_debounce_dwork); + return ret; } From bb3a94a6828336dcc2dd891e51ebd92dcdd0b576 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 20 Aug 2026 17:23:29 +0200 Subject: [PATCH 0470/2366] drivers: base: test: DRIVER_PE_KUNIT_TEST should not select OF Enabling a (modular) test should not silently enable additional kernel functionality, as that may increase the attack vector for a product. Fix this by skipping the new test when OF support is disabled instead of selecting OF support. Note that when OF support is disabled, the compiler optimizes away the then unused reference to of_fwnode_ops in of_node_init(), so linking succeeds. Fixes: 0e6f8ccd4618afdb ("device property: add test cases for fwnode_for_each_child_node()") Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/8dfb4afaf70b59cd33af9296464395470405187e.1787239268.git.geert@linux-m68k.org Signed-off-by: Danilo Krummrich --- drivers/base/test/Kconfig | 1 - drivers/base/test/property-entry-test.c | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/base/test/Kconfig b/drivers/base/test/Kconfig index 542ce07530a1..1ecf0791241a 100644 --- a/drivers/base/test/Kconfig +++ b/drivers/base/test/Kconfig @@ -17,7 +17,6 @@ config DM_KUNIT_TEST config DRIVER_PE_KUNIT_TEST tristate "KUnit Tests for property entry API" if !KUNIT_ALL_TESTS depends on KUNIT - select OF default KUNIT_ALL_TESTS config DRIVER_SWNODE_KUNIT_TEST diff --git a/drivers/base/test/property-entry-test.c b/drivers/base/test/property-entry-test.c index 855e73b9b21f..89cdfc2f8498 100644 --- a/drivers/base/test/property-entry-test.c +++ b/drivers/base/test/property-entry-test.c @@ -523,6 +523,9 @@ static void pe_test_child_iteration(struct kunit *test) struct fwnode_handle *child; int error, i, num; + if (!IS_ENABLED(CONFIG_OF)) + kunit_skip(test, "requires CONFIG_OF"); + static const struct software_node node = { .name = "sw" }; static const struct software_node node1 = { .name = "sw-1", .parent = &node}; static const struct software_node node2 = { .name = "sw-2", .parent = &node}; From 6d94c47a2e3a38170a0a141547e4c52fbe232cc3 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Fri, 21 Aug 2026 10:52:15 +0200 Subject: [PATCH 0471/2366] pmdomain: airoha: fix unselectable AIROHA_CPU_PM_DOMAIN kconfig The AIROHA_CPU_PM_DOMAIN config was wrongly guarded under the Mediatek PM Domains menu and was unselectable. Move it outside the menu so it's now visible and correctly selectable by default on Airoha SoC. Cc: stable@vger.kernel.org Fixes: 82e703dd438b ("pmdomain: airoha: Add Airoha CPU PM Domain support") Signed-off-by: Christian Marangi Reviewed-by: Abel Vesa Signed-off-by: Ulf Hansson --- drivers/pmdomain/mediatek/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/pmdomain/mediatek/Kconfig b/drivers/pmdomain/mediatek/Kconfig index 8923e6516441..a2eb704a773c 100644 --- a/drivers/pmdomain/mediatek/Kconfig +++ b/drivers/pmdomain/mediatek/Kconfig @@ -43,9 +43,12 @@ config MTK_MFG_PM_DOMAIN This driver is required for the Mali GPU to work at all on MT8196 and MT6991. +endmenu + config AIROHA_CPU_PM_DOMAIN tristate "Airoha CPU power domain" default ARCH_AIROHA + depends on ARCH_AIROHA || COMPILE_TEST depends on HAVE_ARM_SMCCC depends on PM select PM_GENERIC_DOMAINS @@ -54,5 +57,3 @@ config AIROHA_CPU_PM_DOMAIN CPU frequency and power is controlled by ATF with SMC command to set performance states. - -endmenu From 4956993bb3befdf791d71a4952d8d13bcfd44c7b Mon Sep 17 00:00:00 2001 From: Wei Jie Law <98lawweijie@gmail.com> Date: Tue, 25 Aug 2026 18:31:17 +0800 Subject: [PATCH 0472/2366] HID: rmi: fix OOB access with undersized RMI reports The hid-rmi driver sizes its writeReport/readReport buffer purely from the report descriptor supplied by the device, with no minimum bound: data->input_report_size = hid_report_len(input_report); data->output_report_size = hid_report_len(output_report); alloc_size = data->output_report_size + data->input_report_size; data->writeReport = devm_kzalloc(&hdev->dev, alloc_size, GFP_KERNEL); data->readReport = data->writeReport + data->output_report_size; but then reads and writes fixed offsets into it. A device declaring a 1-byte output and a 1-byte input report makes hid_report_len() return 2 for each, so alloc_size is 4, while rmi_set_page() -- reached unconditionally at probe time through rmi_input_configured() -- stores writeReport[4] and rmi_hid_read_block() stores writeReport[0..5]. Since readReport lives at writeReport + output_report_size, those stores also corrupt the window the next reply is parsed out of. The read path is worse: the copy length comes from readReport[1], which the device fills in and can be up to 255, and the copy starts at &readReport[2] with no regard for input_report_size, so it runs past the end of the allocation into adjacent slab objects. This does not even need a lying device -- rmi_f01_probe() issues a fixed 21-byte register read, so any device declaring an input report smaller than 23 bytes reads out of bounds even when it answers truthfully. Those bytes become the register values the RMI core acts on: rmi_f01_probe() prints them to the kernel log as the product id and exports them through the mode 0444 sysfs attribute of the same name, and rmi_driver_set_irq_bits() sends them back to the device as the interrupt mask, so an undersized report descriptor leaks heap contents both to unprivileged userspace and to the device itself. The write path has no bound either: rmi_hid_write_block() copies an unbounded len to &writeReport[4], and the largest caller a device can drive at probe time is rmi_driver_set_irq_bits(), whose length is derived from the interrupt source counts the device declares in its Page Description Table. Finally, the read loop cannot terminate on a zero-length reply: such a reply copies nothing and advances neither bytes_read nor bytes_needed, and because a reply did arrive the one second wait_event_timeout() does not fire either, so a device answering 0 forever keeps the loop running inside the probe worker with page_mutex held. khungtaskd does not notice, because every reply wakes the task. Reject reports too small for what the driver builds -- 6 output bytes for the write reports and 3 input bytes for the read handshake -- at probe time, clamp the write and the read copy to the report sizes the device declared, and treat a zero-length reply as an error. A device refused this way is started as an ordinary HID device, like one that does not carry the RMI report ids at all. RMI_DEVICE must not be left set in device_flags on that path, because rmi_input_configured() would then run the RMI setup and reach rmi_set_page(), which writes the writeReport buffer the refusal just skipped allocating. The bit can arrive set: rmi_probe() copies id->driver_data into device_flags before the report checks, and a bind through the new_id sysfs attribute can supply driver_data with RMI_DEVICE (BIT(0)) set. Strip the bit where driver_data is copied, so RMI_DEVICE keeps meaning exactly "this probe validated the reports"; the three jumps to start that predate this patch are covered as well. The error path also clears RMI_READ_DATA_PENDING on its way out, because that flag is what the wait at the top of the loop tests: leaving it set would make every later wait_event_timeout() return immediately on the stale reply and kill the read path for the rest of the device's life. Clamping does not regress working hardware: the read loop already handles a reply carrying fewer bytes than requested, and a write longer than the output report was overrunning the buffer already. Verified on v6.12.69 and on v6.12.105 built with CONFIG_KASAN=y and booted kasan_multi_shot, whose hid-rmi.c is identical to mainline here. An emulated RMI4 device driven over /dev/uhid, and the same device again over dummy_hcd plus raw-gadget, give identical results: BUG: KASAN: slab-out-of-bounds in rmi_hid_read_block+0x409/0x750 [hid_rmi] Read of size 21 at addr ffff88800bf33bba by task kworker/0:3/285 __asan_memcpy+0x23/0x60 rmi_hid_read_block+0x409/0x750 [hid_rmi] rmi_f01_probe+0x5dd/0x1dc0 [rmi_core] BUG: KASAN: slab-out-of-bounds in rmi_hid_write_block+0x1a9/0x350 [hid_rmi] Write of size 35 at addr ffff88810a2b24ac by task kworker/1:10/666 __asan_memcpy+0x3c/0x60 rmi_hid_write_block+0x1a9/0x350 [hid_rmi] rmi_driver_set_irq_bits+0x1f6/0x4d0 [rmi_core] rmi_driver_probe+0x636/0xbf0 [rmi_core] rmi_input_configured+0x184/0x2e0 [hid_rmi] rmi_probe+0x952/0xcf0 [hid_rmi] and, for the zero-length reply, a probe worker left in D state in rmi_hid_read_block() after 225 replies at 200 ms intervals. After this change the undersized descriptor is refused at probe with "rmi reports too small (out=2 in=2)", the oversized read and write are both rejected, the zero-length reply fails the read with -EIO while later reads on the same device keep working, and a device declaring reports large enough for a 21-byte register read still probes normally and reports its real product id. A device bound through new_id with RMI_DEVICE in its driver_data no longer reaches rmi_set_page() with an unallocated writeReport either. Link: https://lore.kernel.org/linux-input/20260822121007.153988-1-98lawweijie@gmail.com/ Link: https://lore.kernel.org/linux-input/00a489f38b240624dcb5a4bae36a53fcba9cfb47.1787549195.git.98lawweijie@gmail.com/ Link: https://lore.kernel.org/linux-input/20260824122708.76168-1-98lawweijie@gmail.com/ Link: https://lore.kernel.org/linux-input/20260825060954.104890-1-98lawweijie@gmail.com/ Fixes: 9fb6bf02e3ad ("HID: rmi: introduce RMI driver for Synaptics touchpads") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Assisted-by: GLM:glm-5.3 Signed-off-by: Wei Jie Law <98lawweijie@gmail.com> Signed-off-by: Jiri Kosina --- drivers/hid/hid-rmi.c | 46 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c index 2bd781f1e0f5..ecc19387f6b0 100644 --- a/drivers/hid/hid-rmi.c +++ b/drivers/hid/hid-rmi.c @@ -235,7 +235,23 @@ static int rmi_hid_read_block(struct rmi_transport_dev *xport, u16 addr, break; } - read_input_count = data->readReport[1]; + read_input_count = min_t(int, data->readReport[1], + data->input_report_size - 2); + if (!read_input_count) { + /* + * A zero length reply advances neither + * bytes_read nor bytes_needed, and because a + * reply did arrive the wait above does not + * time out either, so a device answering 0 + * forever would spin here indefinitely with + * page_mutex held. + */ + hid_warn(hdev, "%s: zero-length read reply\n", + __func__); + clear_bit(RMI_READ_DATA_PENDING, &data->flags); + ret = -EIO; + break; + } memcpy(buf + bytes_read, &data->readReport[2], min(read_input_count, bytes_needed)); @@ -271,6 +287,11 @@ static int rmi_hid_write_block(struct rmi_transport_dev *xport, u16 addr, goto exit; } + if (len + 4 > data->output_report_size) { + ret = -EINVAL; + goto exit; + } + data->writeReport[0] = RMI_WRITE_REPORT_ID; data->writeReport[1] = len; data->writeReport[2] = addr & 0xFF; @@ -666,8 +687,16 @@ static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } - if (id->driver_data) - data->device_flags = id->driver_data; + /* + * RMI_DEVICE can only mean "this probe validated the RMI reports and + * allocated writeReport": every bail-out to start below skips that + * allocation, and device_flags left carrying RMI_DEVICE from + * driver_data would send rmi_input_configured() into rmi_set_page() + * with writeReport still NULL. A bind through the new_id sysfs + * attribute can supply driver_data with the bit set, so do not let + * driver_data grant it. + */ + data->device_flags = id->driver_data & ~RMI_DEVICE; /* * Check for the RMI specific report ids. If they are misisng @@ -696,6 +725,17 @@ static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id) data->output_report_size = hid_report_len(output_report); + /* + * The write reports built by this driver occupy 6 bytes and the read + * handshake looks at the first 3 bytes of an input report, so refuse + * to drive a device whose reports cannot hold them. + */ + if (data->output_report_size < 6 || data->input_report_size < 3) { + hid_err(hdev, "rmi reports too small (out=%u in=%u)\n", + data->output_report_size, data->input_report_size); + goto start; + } + data->device_flags |= RMI_DEVICE; alloc_size = data->output_report_size + data->input_report_size; From 7b15d6cf25e6c2aea77129179b75c191b20d79a9 Mon Sep 17 00:00:00 2001 From: Hengyu Liang Date: Sat, 22 Aug 2026 01:17:05 -0400 Subject: [PATCH 0473/2366] kernfs: preserve security xattrs without allocating iattrs Commit d5e81a5650b5 ("kernfs: avoid iattr allocation in listxattr") made kernfs_iop_listxattr() return an empty list when the kernfs node has no allocated kernfs_iattrs. However, this also skips security xattr names provided by simple_xattr_list(). As of now, applications can retrieve the SELinux label of a sysfs file with getxattr(), but cannot do it through listxattr(). A similar issue happened before in commit b09e0fa4b4ea ("tmpfs: implement generic xattr support"). It was fixed by commit 8b0ba61df5a1c ("fs/xattr.c: fix simple_xattr_list to always include security.* xattrs"). Perhaps this recent commit needs a fix as well. The issue can be reproduced with a simple python program: python3 - <<'PY' import os path = "/sys/kernel/warn_count" print("getxattr:", os.getxattr(path, "security.selinux")) print("listxattr:", os.listxattr(path)) PY Before commit d5e81a5650b5 ("kernfs: avoid iattr allocation in listxattr"), the result is: getxattr: b'system_u:object_r:sysfs_t:s0\x00' listxattr: ['security.selinux'] After that commit, the result is: getxattr: b'system_u:object_r:sysfs_t:s0\x00' listxattr: [] This patch will keep listxattr() consistent with getxattr() when security xattrs are available. Fixes: d5e81a5650b5 ("kernfs: avoid iattr allocation in listxattr") Signed-off-by: Hengyu Liang Acked-by: Tejun Heo Link: https://patch.msgid.link/20260822051705.1761850-1-hengyul@cs.unc.edu Signed-off-by: Danilo Krummrich --- fs/kernfs/inode.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c index 237dcdd73fc2..abb286bc3474 100644 --- a/fs/kernfs/inode.c +++ b/fs/kernfs/inode.c @@ -142,10 +142,8 @@ ssize_t kernfs_iop_listxattr(struct dentry *dentry, char *buf, size_t size) struct kernfs_iattrs *attrs; attrs = kernfs_iattrs_noalloc(kn); - if (!attrs) - return 0; - return simple_xattr_list(d_inode(dentry), &attrs->xattrs, buf, size); + return simple_xattr_list(d_inode(dentry), attrs ? &attrs->xattrs : NULL, buf, size); } static inline void set_default_inode_attr(struct inode *inode, umode_t mode) From 1d80a4792f1de236c157bcee2e5400fad4c66c65 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Tue, 1 Sep 2026 13:26:44 +0100 Subject: [PATCH 0474/2366] ASoC: cs35l56: Fix probe deadlock waiting for SoundWire enumeration On SoundWire, don't call snd_soc_register_component() from driver probe(). Instead, queue a work item after first SoundWire attach to call snd_soc_register_component(). This prevents a deadlock if snd_soc_register_component() directly calls cs35l56_component_probe(). On SoundWire, the registers are not accessible during driver probe(). Drivers must return from their probe() and wait for the SoundWire core to call their update_status() callback to report an ATTACHED status. The cs35l56 driver handled this by calling snd_soc_register_component() from driver probe() as usual, and cs35l56_component_probe() waited for init_completion to be signalled. A SoundWire attach calls cs35l56_init() which then signals init_completion. This created a deadlock if this was the last component needed to complete a card. In that case, snd_soc_register_component() directly called cs35l56_component_probe() which led to this: driver probe() calls snd_soc_register_component() calls cs35l56_component_probe() waits for init_completion In this case the driver probe() has not returned, so the SoundWire core would not call update_status() and init_completion would not be signalled. Fortunately, snd_soc_register_component() never returns -EPROBE_DEFER, so it doesn't need to be called from a driver probe(). It can be deferred to a work item. This work is queued after the first completed pass through cs35l56_init(), so there is no need for it to wait for init_completion. snd_soc_register_component() isn't called directly from cs35l56_init() because cs35l56_init() runs in the SoundWire bus driver thread, and there would be a risk of nested locking or lock inversion. The work item is queued on a freezable workqueue to prevent a race between the work item and system_suspend of another instance. If the workqueue were not frozen it would be possible for the work item of one driver instance to call snd_soc_register_component() which then calls cs35l56_component_probe() of another instance while that instance is already executing its system suspend functions. The non-SoundWire case still calls snd_soc_register_component() from cs35l56_common_probe() so that it is the last initialization action. There's no need defer the call for I2S/SPI buses so we can also leave it able to return errors during probe. Fixes: 440c2d38950f7 ("ASoC: cs35l56: Wait for init_complete in cs35l56_component_probe()") Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260901122644.634494-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56.c | 71 +++++++++++++++++++++++++++++++------- sound/soc/codecs/cs35l56.h | 2 ++ 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index b9118ad8fab5..890429ab0dfb 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -1365,12 +1365,6 @@ static int _cs35l56_component_probe(struct snd_soc_component *component) BUILD_BUG_ON(ARRAY_SIZE(cs35l56_tx_input_texts) != ARRAY_SIZE(cs35l56_tx_input_values)); - if (!wait_for_completion_timeout(&cs35l56->init_completion, - msecs_to_jiffies(5000))) { - dev_err(cs35l56->base.dev, "%s: init_completion timed out\n", __func__); - return -ENODEV; - } - cs35l56->dsp.part = kasprintf(GFP_KERNEL, "cs35l%02x", cs35l56->base.type); if (!cs35l56->dsp.part) return -ENOMEM; @@ -1939,6 +1933,40 @@ static int cs35l56_try_get_broken_sdca_spkid_gpio(struct cs35l56_private *cs35l5 return ret; } +static int cs35l56_component_register(struct cs35l56_private *cs35l56) +{ + int ret; + + ret = snd_soc_register_component(cs35l56->base.dev, + &soc_component_dev_cs35l56, + cs35l56_dai, ARRAY_SIZE(cs35l56_dai)); + if (ret < 0) { + dev_err(cs35l56->base.dev, "Register codec failed: %d\n", ret); + return ret; + } + + cs35l56->component_registered = true; + + return 0; +} + +static void cs35l56_component_register_work(struct work_struct *work) +{ + struct cs35l56_private *cs35l56 = container_of(work, + struct cs35l56_private, + component_register_work); + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(cs35l56->base.dev, pm_err); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm_err); + if (ret) { + dev_err(cs35l56->base.dev, "register_work failed to get pm_runtime: %d\n", ret); + return; + } + + cs35l56_component_register(cs35l56); +} + int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq) { int ret; @@ -1947,6 +1975,7 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq) mutex_init(&cs35l56->base.irq_lock); cs35l56->base.cal_index = -1; cs35l56->speaker_id = -ENOENT; + INIT_WORK(&cs35l56->component_register_work, cs35l56_component_register_work); dev_set_drvdata(cs35l56->base.dev, cs35l56); @@ -2020,12 +2049,17 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq) if (ret) goto err_remove_wm_adsp; - ret = snd_soc_register_component(cs35l56->base.dev, - &soc_component_dev_cs35l56, - cs35l56_dai, ARRAY_SIZE(cs35l56_dai)); - if (ret < 0) { - dev_err_probe(cs35l56->base.dev, ret, "Register codec failed\n"); - goto err_free_irq; + /* + * Defer calling snd_soc_register_component() on SoundWire to prevent + * a deadlock where it calls our component_probe(), which requires the + * SoundWire enumeration to complete, but because we are still in probe() + * the SoundWire core will not call the update_status() callback. At time + * of writing snd_soc_register_component() never returns EPROBE_DEFER. + */ + if (!cs35l56->sdw_peripheral) { + ret = cs35l56_component_register(cs35l56); + if (ret < 0) + goto err_free_irq; } return 0; @@ -2055,6 +2089,7 @@ EXPORT_SYMBOL_NS_GPL(cs35l56_common_probe, "SND_SOC_CS35L56_CORE"); int cs35l56_init(struct cs35l56_private *cs35l56) { + bool first_time_init = !cs35l56->base.init_done; int ret; /* @@ -2131,13 +2166,23 @@ int cs35l56_init(struct cs35l56_private *cs35l56) cs35l56->base.init_done = true; complete_all(&cs35l56->init_completion); + if (cs35l56->sdw_peripheral && first_time_init) { + /* + * Hardware now accessible, queue work to call + * snd_soc_register_component(). + */ + queue_work(system_freezable_wq, &cs35l56->component_register_work); + } + return 0; } EXPORT_SYMBOL_NS_GPL(cs35l56_init, "SND_SOC_CS35L56_CORE"); void cs35l56_remove(struct cs35l56_private *cs35l56) { - snd_soc_unregister_component(cs35l56->base.dev); + cancel_work_sync(&cs35l56->component_register_work); + if (cs35l56->component_registered) + snd_soc_unregister_component(cs35l56->base.dev); cs35l56->base.init_done = false; diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h index 35c02ae17de3..f7cf8aa653e2 100644 --- a/sound/soc/codecs/cs35l56.h +++ b/sound/soc/codecs/cs35l56.h @@ -32,6 +32,7 @@ struct sdw_slave; struct cs35l56_private { struct wm_adsp dsp; /* must be first member */ struct cs35l56_base base; + struct work_struct component_register_work; struct work_struct dsp_work; struct workqueue_struct *dsp_wq; struct snd_soc_component *component; @@ -41,6 +42,7 @@ struct cs35l56_private { const char *fallback_fw_suffix; bool soft_resetting; bool sdw_attached; + bool component_registered; struct completion init_completion; int speaker_id; From 2b0ac85512b7f67479127b2713254490662eb13d Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Fri, 21 Aug 2026 15:57:28 +0800 Subject: [PATCH 0475/2366] cpuidle: dt_idle_genpd: kfree() the original name allocation dt_idle_pd_alloc() kasprintf()s the full node path, then points pd->name at kbasename() of that string. dt_idle_pd_free() kfree()s pd->name, which is no longer the start of the allocation. Copy the basename instead. Fixes: 9d976d6721df ("cpuidle: Factor-out power domain related code from PSCI domain driver") Signed-off-by: Linkai Gong Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/cpuidle/dt_idle_genpd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/cpuidle/dt_idle_genpd.c b/drivers/cpuidle/dt_idle_genpd.c index d292975cc468..ed41a90eeeb7 100644 --- a/drivers/cpuidle/dt_idle_genpd.c +++ b/drivers/cpuidle/dt_idle_genpd.c @@ -99,7 +99,7 @@ struct generic_pm_domain *dt_idle_pd_alloc(struct device_node *np, if (!pd) goto out; - pd->name = kasprintf(GFP_KERNEL, "%pOF", np); + pd->name = kstrdup(kbasename(of_node_full_name(np)), GFP_KERNEL); if (!pd->name) goto free_pd; @@ -112,7 +112,6 @@ struct generic_pm_domain *dt_idle_pd_alloc(struct device_node *np, goto free_name; pd->free_states = pd_free_states; - pd->name = kbasename(pd->name); pd->states = states; pd->state_count = state_count; From 3663c8d1f31e65771bd73ee3259f35fd397f9933 Mon Sep 17 00:00:00 2001 From: Lu Yao Date: Mon, 31 Aug 2026 09:42:18 +0800 Subject: [PATCH 0476/2366] drm/xe/oa: Remove sysfs entry on idr_alloc failure in xe_oa_add_config_ioctl() If idr_alloc() fails after create_dynamic_oa_sysfs_entry() has succeeded, the error path frees the OA config without removing the metrics sysfs group. Remove the sysfs group before releasing the config, and fix up the misleading error message copied from the sysfs creation failure path. Fixes: cdf02fe1a94a ("drm/xe/oa/uapi: Add/remove OA config perf ops") Signed-off-by: Lu Yao Link: https://patch.msgid.link/20260831014218.28515-1-yaolu@kylinos.cn Reviewed-by: Rodrigo Vivi Signed-off-by: Rodrigo Vivi (cherry picked from commit 2c6fbda5fdde461d6dedb82a59285182720b8fef) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_oa.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_oa.c b/drivers/gpu/drm/xe/xe_oa.c index 9c5384b95c63..ab09dcff5860 100644 --- a/drivers/gpu/drm/xe/xe_oa.c +++ b/drivers/gpu/drm/xe/xe_oa.c @@ -2435,9 +2435,9 @@ int xe_oa_add_config_ioctl(struct drm_device *dev, u64 data, struct drm_file *fi oa_config->id = idr_alloc(&oa->metrics_idr, oa_config, 1, 0, GFP_KERNEL); if (oa_config->id < 0) { - drm_dbg(&oa->xe->drm, "Failed to create sysfs entry for OA config\n"); + drm_dbg(&oa->xe->drm, "Failed to allocate id for OA config\n"); err = oa_config->id; - goto sysfs_err; + goto id_alloc_err; } id = oa_config->id; @@ -2448,6 +2448,8 @@ int xe_oa_add_config_ioctl(struct drm_device *dev, u64 data, struct drm_file *fi return id; +id_alloc_err: + sysfs_remove_group(oa->metrics_kobj, &oa_config->sysfs_metric); sysfs_err: mutex_unlock(&oa->metrics_lock); reg_err: From 1f1d0812f6a8ab8e6f709c599f137c99646512cc Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Sat, 15 Aug 2026 13:44:19 -0700 Subject: [PATCH 0477/2366] gpiolib: of: don't mark hog nodes OF_POPULATED before a chip is found When a gpio-hog node is attached by a device-tree overlay before its parent GPIO chip has been registered, of_gpio_notify() sets OF_POPULATED on the node via of_node_test_and_set_flag() and only then discovers that there is no gpio_device for the parent, returning NOTIFY_DONE without clearing the flag. Since gpiochip_hog_lines() skips any hog child whose of_node carries OF_POPULATED, the leaked flag makes the hog silently ignored when the chip is registered later. Applying an overlay containing both a GPIO controller node and its hog children - and populating devices only after the overlay apply completes - hits this on every boot; the hog is only applied if the chip driver is unbound (which clears the flag in the remove path) and rebound. Look up the parent gpio_device before claiming the node so that a hog attached ahead of its chip stays unclaimed and is picked up normally by gpiochip_hog_lines() at registration time. Signed-off-by: Abdurrahman Hussain Fixes: a23226b7c1f6 ("gpiolib: handle gpio-hogs only once") Cc: stable@vger.kernel.org Reviewed-by: Daniel Drake Link: https://patch.msgid.link/20260815-gpiolib-of-hog-flag-leak-v1-1-6126aac5f6f3@nexthop.ai Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-of.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index 940b566946ce..f36e4b171fa7 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -788,13 +788,13 @@ static int of_gpio_notify(struct notifier_block *nb, unsigned long action, if (!of_property_read_bool(rd->dn, "gpio-hog")) return NOTIFY_DONE; /* not for us */ - if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) - return NOTIFY_DONE; - gdev = of_find_gpio_device_by_node(rd->dn->parent); if (!gdev) return NOTIFY_DONE; /* not for us */ + if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) + return NOTIFY_DONE; + ret = gpiochip_add_hog(gpio_device_get_chip(gdev), of_fwnode_handle(rd->dn)); if (ret < 0) { pr_err("%s: failed to add hogs for %pOF\n", __func__, From 8d7b3e41ffecc69388a566ecc52093d292074c2a Mon Sep 17 00:00:00 2001 From: Sophon Zhang Date: Tue, 1 Sep 2026 01:09:53 +0800 Subject: [PATCH 0478/2366] rust: pci: reject IRQ vector indices that do not fit in u32 IrqVectorRegistration::index() accepts a usize, but pci_irq_vector() takes an unsigned int. On 64-bit architectures, casting an index larger than u32::MAX wraps it before the PCI core can validate it. In particular, u32::MAX + 1 becomes zero and can resolve to the first allocated vector. Use a checked conversion and return EINVAL when the index cannot be represented by the C API. Fixes: 2fb7755b0a7e ("rust: pci: resolve IRQ in index() and embed IrqRequest in IrqVector") Signed-off-by: Sophon Zhang Reviewed-by: Gary Guo Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260901-fix-pci-irq-vector-index-truncation-v4-1-f94aa6932fd9@hotmail.com Signed-off-by: Danilo Krummrich --- rust/kernel/pci/irq.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/kernel/pci/irq.rs b/rust/kernel/pci/irq.rs index 6741046ec1c0..22e2cdf82a21 100644 --- a/rust/kernel/pci/irq.rs +++ b/rust/kernel/pci/irq.rs @@ -151,8 +151,10 @@ pub fn irq_type(&self) -> IrqType { /// [`Self::len()`]. #[inline] pub fn index(&self, index: usize) -> Result> { + let index = u32::try_from(index)?; + // SAFETY: `self.dev.as_raw()` is a valid pointer to a `struct pci_dev`. - let irq = unsafe { bindings::pci_irq_vector(self.dev.as_raw(), index as u32) }; + let irq = unsafe { bindings::pci_irq_vector(self.dev.as_raw(), index) }; if irq < 0 { return Err(Error::from_errno(irq)); } From 83162eeaf78c71ff6f6fa31dc95e3b6e90ee593f Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 2 Sep 2026 00:39:21 +0900 Subject: [PATCH 0479/2366] ALSA: dummy: Report a change when one capture switch channel moves The snd_dummy_capsrc_put() builds its change flag with &&, so it reports a change only when both channels move at once. Writing a single channel stores the new value and returns 0, the control core then sends no SNDRV_CTL_EVENT_MASK_VALUE, and a second reader keeps showing the old setting until it polls again. The volume put a few lines above compares the same pair of channels with ||. The mixer selftest already reports this. With snd-dummy loaded it fails event_missing on all five capture switches: # CD Capture Switch.1 orig 0 read 1, is_volatile 0 not ok 13 event_missing.Dummy.9 Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Assisted-by: Claude:claude-opus-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260901153921.3971-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- sound/drivers/dummy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index b908d2564aee..3f6bfee29986 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -808,7 +808,7 @@ static int snd_dummy_capsrc_put(struct snd_kcontrol *kcontrol, struct snd_ctl_el left = ucontrol->value.integer.value[0] & 1; right = ucontrol->value.integer.value[1] & 1; guard(spinlock_irq)(&dummy->mixer_lock); - change = dummy->capture_source[addr][0] != left && + change = dummy->capture_source[addr][0] != left || dummy->capture_source[addr][1] != right; dummy->capture_source[addr][0] = left; dummy->capture_source[addr][1] = right; From a20afec40ea1012659861e58374c1bcde2e18a43 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Tue, 1 Sep 2026 17:37:51 +0800 Subject: [PATCH 0480/2366] ASoC: rt721-sdca: Adjust latency control to fix no-sound issue Adjust latency control in speaker preset to fix no-sound issue. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20260901093751.2962786-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index 159c35d19dba..b1c3dc060c24 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -206,6 +206,7 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) regmap_write(rt721->regmap, SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); + regmap_write(rt721->regmap, 0x2f5d, 0x1); } static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) From 6db237eb516774a76b0d6ab8b4090185f6f2f956 Mon Sep 17 00:00:00 2001 From: Russ Weight Date: Tue, 18 Aug 2026 13:46:46 -0600 Subject: [PATCH 0481/2366] firmware_loader: Change contact for sysfs nodes Change the contact name for the firmware_loader sysfs nodes to driver-core@lists.linux.dev. Signed-off-by: Russ Weight Link: https://patch.msgid.link/20260818194648.1014604-2-russ.weight@linux.dev [ Since we have a driver-core mailing list, use it as contact information instead of myself. - Danilo ] Signed-off-by: Danilo Krummrich --- Documentation/ABI/testing/sysfs-class-firmware | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-firmware b/Documentation/ABI/testing/sysfs-class-firmware index fba87a55f3ca..44ca1b78a0e1 100644 --- a/Documentation/ABI/testing/sysfs-class-firmware +++ b/Documentation/ABI/testing/sysfs-class-firmware @@ -1,7 +1,7 @@ What: /sys/class/firmware/.../data Date: July 2022 KernelVersion: 5.19 -Contact: Russ Weight +Contact: driver-core@lists.linux.dev Description: The data sysfs file is used for firmware-fallback and for firmware uploads. Cat a firmware image to this sysfs file after you echo 1 to the loading sysfs file. When the firmware @@ -13,7 +13,7 @@ Description: The data sysfs file is used for firmware-fallback and for What: /sys/class/firmware/.../cancel Date: July 2022 KernelVersion: 5.19 -Contact: Russ Weight +Contact: driver-core@lists.linux.dev Description: Write-only. For firmware uploads, write a "1" to this file to request that the transfer of firmware data to the lower-level device be canceled. This request will be rejected (EBUSY) if @@ -23,7 +23,7 @@ Description: Write-only. For firmware uploads, write a "1" to this file to What: /sys/class/firmware/.../error Date: July 2022 KernelVersion: 5.19 -Contact: Russ Weight +Contact: driver-core@lists.linux.dev Description: Read-only. Returns a string describing a failed firmware upload. This string will be in the form of :, where will be one of the status strings described @@ -37,7 +37,7 @@ Description: Read-only. Returns a string describing a failed firmware What: /sys/class/firmware/.../loading Date: July 2022 KernelVersion: 5.19 -Contact: Russ Weight +Contact: driver-core@lists.linux.dev Description: The loading sysfs file is used for both firmware-fallback and for firmware uploads. Echo 1 onto the loading file to indicate you are writing a firmware file to the data sysfs node. Echo @@ -49,7 +49,7 @@ Description: The loading sysfs file is used for both firmware-fallback and What: /sys/class/firmware/.../remaining_size Date: July 2022 KernelVersion: 5.19 -Contact: Russ Weight +Contact: driver-core@lists.linux.dev Description: Read-only. For firmware upload, this file contains the size of the firmware data that remains to be transferred to the lower-level device driver. The size value is initialized to @@ -62,7 +62,7 @@ Description: Read-only. For firmware upload, this file contains the size What: /sys/class/firmware/.../status Date: July 2022 KernelVersion: 5.19 -Contact: Russ Weight +Contact: driver-core@lists.linux.dev Description: Read-only. Returns a string describing the current status of a firmware upload. The string will be one of the following: idle, "receiving", "preparing", "transferring", "programming". @@ -70,7 +70,7 @@ Description: Read-only. Returns a string describing the current status of What: /sys/class/firmware/.../timeout Date: July 2022 KernelVersion: 5.19 -Contact: Russ Weight +Contact: driver-core@lists.linux.dev Description: This file supports the timeout mechanism for firmware fallback. This file has no affect on firmware uploads. For more information on timeouts please see the documentation From 0d3e690c112939d869931a5c8c0bb23718d58370 Mon Sep 17 00:00:00 2001 From: Russ Weight Date: Tue, 18 Aug 2026 13:46:47 -0600 Subject: [PATCH 0482/2366] CREDITS: Add CREDITS entry for Firmware Upload Add an entry to the CREDITS file for the Firmware Upload functionality of the Firmware Loader. Signed-off-by: Russ Weight Link: https://patch.msgid.link/20260818194648.1014604-3-russ.weight@linux.dev Signed-off-by: Danilo Krummrich --- CREDITS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CREDITS b/CREDITS index a1455b471051..8d52aa65a858 100644 --- a/CREDITS +++ b/CREDITS @@ -4305,6 +4305,10 @@ N: Juergen Weigert E: jnweiger@immd4.informatik.uni-erlangen.de D: The Linux Support Team Erlangen +N: Russ Weight +E: russ.weight@gmail.com +D: Added support for Firmware Upload to the Firmware Loader + N: David Weinehall E: tao@acc.umu.se P: 1024D/DC47CA16 7ACE 0FB0 7A74 F994 9B36 E1D1 D14E 8526 DC47 CA16 From f6d752278c13839888425294c110174fb6c87e3d Mon Sep 17 00:00:00 2001 From: Russ Weight Date: Tue, 18 Aug 2026 13:46:48 -0600 Subject: [PATCH 0483/2366] MAINTAINERS: Remove Russ Weight from Firmware Loader Remove Russ Weight from the MAINTAINERS for FIRMWARE LOADER. Signed-off-by: Russ Weight Link: https://patch.msgid.link/20260818194648.1014604-4-russ.weight@linux.dev Signed-off-by: Danilo Krummrich --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..36159e6437d5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10193,7 +10193,6 @@ F: include/linux/arm_ffa.h FIRMWARE LOADER (request_firmware) M: Luis Chamberlain -M: Russ Weight M: Danilo Krummrich L: driver-core@lists.linux.dev S: Maintained From 3bb3e80faf21e432f6e6d89c55fb587c323b8813 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 1 Sep 2026 14:11:12 +0100 Subject: [PATCH 0484/2366] firmware: arm_ffa: Tear down driver during shutdown The platform core invokes a driver's shutdown callback, rather than its remove callback, while preparing devices for a normal kexec. Without a shutdown callback, the FF-A driver leaves notifications, partition devices, and the RX/TX mapping active before the replacement kernel is booted. Use ffa_remove() for shutdown so the existing cleanup runs before a normal kexec and other orderly system shutdowns. Reported-by: Nat Gurumoorthy Closes: https://lore.kernel.org/all/20260729162731.1383875-1-natg@google.com/ Reported-by: Carol L Soto Closes: https://lore.kernel.org/all/20260818224404.3694580-1-csoto@nvidia.com Reported-by: Maxi Saparov Closes: https://lore.kernel.org/all/20260826222337.73480-1-maxi.saparov@gmail.com Link: https://patch.msgid.link/20260901131112.3437516-1-sudeep.holla@kernel.org Tested-by: Carol L Soto Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 8654b3365c9b..28abc808fd32 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -2225,6 +2225,7 @@ static void ffa_remove(struct platform_device *pdev) static struct platform_driver ffa_driver = { .probe = ffa_probe, .remove = ffa_remove, + .shutdown = ffa_remove, .driver = { .name = FFA_PLATFORM_NAME, }, From 9cdc7e6dc7a99ad7311ad5e7c145f2b9ce4e24b0 Mon Sep 17 00:00:00 2001 From: Shen Yongchao Date: Mon, 3 Aug 2026 22:31:57 +0800 Subject: [PATCH 0485/2366] HID: bpf: serialize device reference release in struct_ops destroy path __hid_bpf_ops_destroy_device() and hid_bpf_unreg() can race on the same registration reference, double-putting struct hid_device and freeing it while hid_destroy_device() still uses it. Serialize the remove/NULL decision under hdev->bpf.prog_list_lock so exactly one path releases each registration reference: unreg re-checks ops->hdev under the lock and returns without putting when the destroy path already cleared it; all put_device() calls happen after the lock is dropped, which is safe because a concurrent unreg then observes ops->hdev == NULL under the lock. Background: each successful attach (hid_bpf_ops_reg) acquires one device reference (hid_get_device()). Two paths can release it: - device destruction: hid_destroy_device() -> hid_bpf_destroy_device() -> __hid_bpf_ops_destroy_device(), which walks hdev->bpf.prog_list under rcu_read_lock() and drops one reference per attached program; - BPF link release: bpf map delete (no BPF_F_LINK) synchronously calls st_ops->unreg() -> hid_bpf_unreg(), which drops the reference for its own registration. The coordination handshake (e->hdev = NULL on the destroy side vs "if (!hdev) return" on the unreg side) is a TOCTOU check: the two paths run under different lock domains (rcu_read_lock vs prog_list_lock), so a concurrent unreg can read ops->hdev as non-NULL, block on prog_list_lock, and then proceed while the destroy traversal executes - both paths then drop the same reference. The refcount reaches zero legitimately (each decrement is individually valid), so no refcount_t saturation fires: the device is simply freed while the transport is still inside hid_destroy_device(), and subsequent teardown touches freed memory. The fix serializes the remove/NULL decision under prog_list_lock on both sides and moves the destroy-side puts outside the lock. With the lock held, plain reads/writes of ops->hdev are sufficient; no READ_ONCE/WRITE_ONCE are added, keeping the patch minimal. Unlocked-read safety: the unlocked read of ops->hdev at the top of hid_bpf_unreg() cannot touch a freed device, because the unreg path itself still holds this registration's reference (released only by its own hid_put_device() after the lock is dropped), and a destroy traversal that already cleared ops->hdev makes the lock-internal re-check return early without any put. At most one of the two paths releases each registration reference. Fixes: ebc0d8093e8c ("HID: bpf: implement HID-BPF through bpf_struct_ops") Cc: stable@vger.kernel.org Signed-off-by: Shen Yongchao Assisted-by: Hermes:kimi-k3 Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/hid_bpf_struct_ops.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/hid/bpf/hid_bpf_struct_ops.c b/drivers/hid/bpf/hid_bpf_struct_ops.c index 56c53aca4511..c90b68956cb3 100644 --- a/drivers/hid/bpf/hid_bpf_struct_ops.c +++ b/drivers/hid/bpf/hid_bpf_struct_ops.c @@ -256,6 +256,11 @@ static void hid_bpf_unreg(void *kdata, struct bpf_link *link) mutex_lock(&hdev->bpf.prog_list_lock); + if (!ops->hdev) { + mutex_unlock(&hdev->bpf.prog_list_lock); + return; + } + list_del_rcu(&ops->list); synchronize_srcu(&hdev->bpf.srcu); ops->hdev = NULL; @@ -316,13 +321,17 @@ static struct bpf_struct_ops bpf_hid_bpf_ops = { void __hid_bpf_ops_destroy_device(struct hid_device *hdev) { struct hid_bpf_ops *e; + int count = 0; - rcu_read_lock(); - list_for_each_entry_rcu(e, &hdev->bpf.prog_list, list) { - hid_put_device(hdev); + mutex_lock(&hdev->bpf.prog_list_lock); + list_for_each_entry(e, &hdev->bpf.prog_list, list) { e->hdev = NULL; + count++; } - rcu_read_unlock(); + mutex_unlock(&hdev->bpf.prog_list_lock); + + while (count--) + hid_put_device(hdev); } static int __init hid_bpf_struct_ops_init(void) From 00aef6b609bf231b4213704b602250ca53f95897 Mon Sep 17 00:00:00 2001 From: Syed Saba Kareem Date: Tue, 1 Sep 2026 15:53:26 +0530 Subject: [PATCH 0486/2366] ASoC: amd: yc: Add DMI entry for HP 255R G10 laptop The HP 255R G10 laptop has an internal DMIC connected to the AMD ACP6x audio coprocessor. Add a DMI quirk entry so the internal microphone is properly detected on this model. Reported-by: eraleexxx@gmail.com Signed-off-by: Syed Saba Kareem Link: https://patch.msgid.link/20260901102338.26403-1-syed.sabakareem@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 21d7ec0e7dbf..c64c727e1034 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -892,6 +892,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_BOARD_NAME, "TM2423"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP 255R 15.6 inch G10 Notebook PC"), + } + }, {} }; From 6d49beec658f61801e79019fd73691b17252dee3 Mon Sep 17 00:00:00 2001 From: Ian Luites Date: Mon, 31 Aug 2026 08:25:47 +0000 Subject: [PATCH 0487/2366] soundwire: dmi-quirks: Disable ghost Realtek on Asus GX651AX The Asus ROG Zephyrus Duo GX651AX exposes a Realtek RT722 device in ACPI which does not exist in the physical hardware. The device remains unattached while the CS42L43 and both CS35L56 devices attach successfully. This confuses the function topology machine driver into creating duplicate DAI links named SDW3-Playback-SimpleJack, and the sof_sdw probe fails with error -12. Add a model-specific quirk to remove the ghost RT722 device. Fixes: 45cf24da0a10 ("ASoC: Intel: soc-acpi-intel-ptl-match: Remove unnecessary cs42l43 match") Cc: stable@vger.kernel.org # 7.2.x Assisted-by: LLM Signed-off-by: Ian Luites Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260831082534.224716-1-ian@luites.com Signed-off-by: Vinod Koul --- drivers/soundwire/dmi-quirks.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/soundwire/dmi-quirks.c b/drivers/soundwire/dmi-quirks.c index 768255dd12db..62fa64b22412 100644 --- a/drivers/soundwire/dmi-quirks.c +++ b/drivers/soundwire/dmi-quirks.c @@ -200,6 +200,13 @@ static const struct dmi_system_id adr_remap_quirk_table[] = { }, .driver_data = (void *)ghost_realtek, }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUS"), + DMI_MATCH(DMI_BOARD_NAME, "GX651AX"), + }, + .driver_data = (void *)ghost_realtek, + }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ASUS"), From 4cb4aead98380fc0b1b0500a1c49d35724854067 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Tue, 1 Sep 2026 18:49:46 +0800 Subject: [PATCH 0488/2366] ASoC: codecs: ES8326: issue about capture pop This RFC asks how to resolve the 'capture pop noise' issue. The ES8326's capture pop noise occurs when LRCK is active. While the pop noise can be effectively reduced through codec configuration, it cannot be completely eliminated. So I decided to create a workqueue. unmute the ADC once the LRCK becomes active. Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260901104946.20114-1-zhangyi@everest-semi.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8326.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c index c5460589a88b..b6eadc2e9659 100644 --- a/sound/soc/codecs/es8326.c +++ b/sound/soc/codecs/es8326.c @@ -26,6 +26,7 @@ struct es8326_priv { struct snd_soc_component *component; struct delayed_work jack_detect_work; struct delayed_work button_press_work; + struct delayed_work capture_pop_work; struct snd_soc_jack *jack; int irq; /* The lock protects the situation that an irq is generated @@ -628,6 +629,7 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) regmap_update_bits(es8326->regmap, ES8326_HP_DRIVER_REF, 0x30, 0x00); } else { + cancel_delayed_work_sync(&es8326->capture_pop_work); regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, 0x0F, 0x0F); if (es8326->version > ES8326_VERSION_B) { @@ -666,8 +668,9 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x70); regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x00); } - regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, - 0x0F, 0x00); + + queue_delayed_work(system_dfl_wq, &es8326->capture_pop_work, + msecs_to_jiffies(40)); } } return 0; @@ -773,6 +776,15 @@ static void es8326_disable_micbias(struct snd_soc_component *component) snd_soc_dapm_mutex_unlock(dapm); } +static void es8326_capture_pop_handler(struct work_struct *work) +{ + struct es8326_priv *es8326 = + container_of(work, struct es8326_priv, capture_pop_work.work); + + regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, + 0x0F, 0x00); +} + /* * For button detection, set the following in soundcard * snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); @@ -1140,6 +1152,7 @@ static int es8326_suspend(struct snd_soc_component *component) struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); cancel_delayed_work_sync(&es8326->jack_detect_work); + cancel_delayed_work_sync(&es8326->capture_pop_work); es8326_disable_micbias(component); es8326->calibrated = false; regmap_write(es8326->regmap, ES8326_CLK_MUX, 0x2d); @@ -1291,6 +1304,8 @@ static int es8326_i2c_probe(struct i2c_client *i2c) es8326_jack_detect_handler); INIT_DELAYED_WORK(&es8326->button_press_work, es8326_jack_button_handler); + INIT_DELAYED_WORK(&es8326->capture_pop_work, + es8326_capture_pop_handler); /* ES8316 is level-based while ES8326 is edge-based */ ret = devm_request_threaded_irq(&i2c->dev, es8326->irq, NULL, es8326_irq, IRQF_TRIGGER_RISING | IRQF_ONESHOT, From 0ba8e0f90039da68342febf613019f4a68d86620 Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Thu, 20 Aug 2026 01:44:57 +0300 Subject: [PATCH 0489/2366] accel/amdxdna: refuse to flush an imported BO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SYNC_BO clflushes an imported BO's scatterlist. An importer may not do that: the memory belongs to the exporter, and dma-buf gives the importer no interface to ask for maintenance on it. Refuse the request instead. is_import_bo() is (obj)->attach, which covers more than foreign buffers. A userptr BO arrives through a ubuf, and on a carveout device every share BO and the device heap arrive through a cbuf, so SYNC_BO answers -EOPNOTSUPP for those too, including the AMDXDNA_BO_DEV path that flushes through its heap. Only the ubuf case gives up maintenance it was getting: on a 64 MiB userptr BO a 4 KiB sync and a full sync both cost 659 us, this arm having ignored the range. amdxdna_cbuf_map() fills in only the DMA address and length, so drm_clflush_sg() already walks zero pages on carveout memory. Userspace maintains these through the mapping it already holds, as XRT's buffer::sync() does unless it is told to sync through the driver. Fixes: dbc8fd7a03cb ("accel/amdxdna: Add expandable device heap support") Reported-by: Christian König Link: https://lore.kernel.org/dri-devel/a505f9e5-b416-43e9-934d-c5c29b8a70e9@amd.com/ Suggested-by: Lizhi Hou Signed-off-by: Taimuraz Kaitmazov Reviewed-by: Lizhi Hou Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260819224458.257346-5-taimuraz@kaitmazov.com --- drivers/accel/amdxdna/amdxdna_gem.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index d18de7eb7af4..4b0d58d0329b 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -1246,6 +1246,9 @@ static int amdxdna_flush_bo(struct amdxdna_gem_obj *abo, u64 offset, u64 size) { u64 end; + if (is_import_bo(abo)) + return -EOPNOTSUPP; + if (offset >= abo->mem.size) return -EINVAL; @@ -1256,9 +1259,7 @@ static int amdxdna_flush_bo(struct amdxdna_gem_obj *abo, u64 offset, u64 size) if (!size) return 0; - if (is_import_bo(abo)) - drm_clflush_sg(abo->base.sgt); - else if (amdxdna_gem_vmap(abo)) + if (amdxdna_gem_vmap(abo)) drm_clflush_virt_range(amdxdna_gem_vmap(abo) + offset, size); else if (abo->base.pages) drm_clflush_pages(abo->base.pages, abo->mem.size >> PAGE_SHIFT); From 4a819ee5f2834330656d6ac168c4c8cf27fdeec2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 31 Aug 2026 22:07:10 +0200 Subject: [PATCH 0490/2366] ACPI: bus: Drop two fields from struct acpi_device_pnp There are two fields in struct acpi_device_pnp, device_name and device_class, that were supposed to be populated and used by device drivers, but they have never been used consistently and now they are only set for the bus object in acpi_set_pnp_ids() (and never read afterward). Drop them along with all of the associated symbols except for MAX_ACPI_CLASS_NAME_LEN and the acpi_device_class typedef that are used by the ACPI netlink messaging code. Move those two definitions closer to the struct acpi_bus_event that refers to the acpi_device_class type. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/6314925.lOV4Wx5bFT@rafael.j.wysocki --- drivers/acpi/scan.c | 4 ---- include/acpi/acpi_bus.h | 11 +++-------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index f48715ed827c..163a3cccf197 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -28,9 +28,7 @@ #include "internal.h" #include "sleep.h" -#define ACPI_BUS_CLASS "system_bus" #define ACPI_BUS_HID "LNXSYBUS" -#define ACPI_BUS_DEVICE_NAME "System Bus" #define INVALID_ACPI_HANDLE ((acpi_handle)ZERO_PAGE(0)) @@ -1450,8 +1448,6 @@ static void acpi_set_pnp_ids(acpi_handle handle, struct acpi_device_pnp *pnp, acpi_object_is_system_bus(handle)) { /* \_SB, \_TZ, LNXSYBUS */ acpi_add_id(pnp, ACPI_BUS_HID); - strscpy(pnp->device_name, ACPI_BUS_DEVICE_NAME); - strscpy(pnp->device_class, ACPI_BUS_CLASS); } break; diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 1a45e0d521d8..a10a591c18b2 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -202,12 +202,8 @@ struct acpi_device_dir { /* Plug and Play */ -#define MAX_ACPI_DEVICE_NAME_LEN 40 -#define MAX_ACPI_CLASS_NAME_LEN 20 typedef char acpi_bus_id[8]; typedef u64 acpi_bus_address; -typedef char acpi_device_name[MAX_ACPI_DEVICE_NAME_LEN]; -typedef char acpi_device_class[MAX_ACPI_CLASS_NAME_LEN]; struct acpi_hardware_id { struct list_head list; @@ -229,16 +225,12 @@ struct acpi_device_pnp { acpi_bus_address bus_address; /* _ADR */ char *unique_id; /* _UID */ struct list_head ids; /* _HID and _CIDs */ - acpi_device_name device_name; /* Driver-determined */ - acpi_device_class device_class; /* " */ }; #define acpi_device_bid(d) ((d)->pnp.bus_id) #define acpi_device_adr(d) ((d)->pnp.bus_address) const char *acpi_device_hid(struct acpi_device *device); #define acpi_device_uid(d) ((d)->pnp.unique_id) -#define acpi_device_name(d) ((d)->pnp.device_name) -#define acpi_device_class(d) ((d)->pnp.device_class) /* Power Management */ @@ -578,6 +570,9 @@ int acpi_dev_for_each_child_reverse(struct acpi_device *adev, * ------ */ +#define MAX_ACPI_CLASS_NAME_LEN 20 +typedef char acpi_device_class[MAX_ACPI_CLASS_NAME_LEN]; + struct acpi_bus_event { struct list_head node; acpi_device_class device_class; From 774b73428e6eabb4f0382aeeb76e569c7b106a29 Mon Sep 17 00:00:00 2001 From: Faith Ekstrand Date: Fri, 21 Aug 2026 23:42:59 -0500 Subject: [PATCH 0491/2366] drm/nouveau: Use write-combined maps for coherent On Tegra devices, uncached maps translate to device memory, causing unaligned accesses by userspace resulting in a SIGBUS. Instead, use write-combined maps to ensure proper access. This would also affect discrete cards on any Arm device. It was determined that discrete cards regardless of cpu arch should use write-combined maps for coherent anyways. Thus this change is made for all gpu types. Cc: stable@vger.kernel.org Signed-off-by: Faith Ekstrand Co-developed-by: Aaron Kling Signed-off-by: Aaron Kling Fixes: 1b4ea4c5980f ("drm/ttm: set the tt caching state at creation time") Link: https://patch.msgid.link/20260821-tegra-coherent-wc-v2-1-2b1ddb67bf18@gmail.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/nouveau/nouveau_sgdma.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_sgdma.c b/drivers/gpu/drm/nouveau/nouveau_sgdma.c index fa3b4ebf38a8..2bd0376193ae 100644 --- a/drivers/gpu/drm/nouveau/nouveau_sgdma.c +++ b/drivers/gpu/drm/nouveau/nouveau_sgdma.c @@ -72,9 +72,7 @@ nouveau_sgdma_create_ttm(struct ttm_buffer_object *bo, uint32_t page_flags) struct nouveau_sgdma_be *nvbe; enum ttm_caching caching; - if (nvbo->force_coherent) - caching = ttm_uncached; - else if (drm->agp.bridge) + if (nvbo->force_coherent || drm->agp.bridge) caching = ttm_write_combined; else caching = ttm_cached; From f4a771cc684c7354b6200147f7252c58d17408ff Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Fri, 28 Aug 2026 09:41:53 -0400 Subject: [PATCH 0492/2366] tracing: Have show_event_filters/triggers files take trace array ref The newly added files show_event_filters and show_event_triggers that show all filters or triggers that are set within the trace array do not take a reference for the trace array it is showing. Without taking a reference, the trace_array may be freed via "rmdir" while a task is reading one of theses files. Those files iterate all the events within an instance (trace_array) and nothing prevents that instance from being freed while its data is being read. This causes a use-after-free crash. Have the open of both those files take the trace_array reference via the trace_array_get() that prevents the trace_array from being freed while the files are opened. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260828094153.17b95037@gandalf.local.home Fixes: 729757b96a662 ("tracing: Add show_event_filters to expose active event filters") Fixes: 6a80838814eea ("tracing: Add show_event_triggers to expose active event triggers") Reported-by: Farhad Alemi Closes: https://lore.kernel.org/all/CA+0ovCjerKZJLwXScM9bF2ga2rLi4_XOpUfK41NDbENpeu98jA@mail.gmail.com/ Reviewed-by: Aaron Tomlin Signed-off-by: Steven Rostedt --- kernel/trace/trace_events.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 1d39eaf6a0f7..9dbc2441763b 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -2736,14 +2736,14 @@ static const struct file_operations ftrace_show_event_filters_fops = { .open = ftrace_event_show_filters_open, .read = seq_read, .llseek = seq_lseek, - .release = seq_release, + .release = ftrace_event_release, }; static const struct file_operations ftrace_show_event_triggers_fops = { .open = ftrace_event_show_triggers_open, .read = seq_read, .llseek = seq_lseek, - .release = seq_release, + .release = ftrace_event_release, }; static const struct file_operations ftrace_set_event_pid_fops = { @@ -2908,7 +2908,17 @@ ftrace_event_set_open(struct inode *inode, struct file *file) static int ftrace_event_show_filters_open(struct inode *inode, struct file *file) { - return ftrace_event_open(inode, file, &show_show_event_filters_seq_ops); + struct trace_array *tr = inode->i_private; + int ret; + + ret = tracing_check_open_get_tr(tr); + if (ret) + return ret; + + ret = ftrace_event_open(inode, file, &show_show_event_filters_seq_ops); + if (ret < 0) + trace_array_put(tr); + return ret; } /** @@ -2922,7 +2932,17 @@ ftrace_event_show_filters_open(struct inode *inode, struct file *file) static int ftrace_event_show_triggers_open(struct inode *inode, struct file *file) { - return ftrace_event_open(inode, file, &show_show_event_triggers_seq_ops); + struct trace_array *tr = inode->i_private; + int ret; + + ret = tracing_check_open_get_tr(tr); + if (ret) + return ret; + + ret = ftrace_event_open(inode, file, &show_show_event_triggers_seq_ops); + if (ret < 0) + trace_array_put(tr); + return ret; } static int From 9100191e5acb2e5ea2313f436667bb5fce129f47 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Fri, 28 Aug 2026 22:39:01 -0400 Subject: [PATCH 0493/2366] ftrace: Take trace_array reference before accessing its ftrace_ops The trace instance files set_ftrace_filter and set_ftrace_notrace was updated to work with specific trace instances (trace_arrays). The issue is that when these files are opened, there is a small race window where it will use the ftrace_ops from the inode->private pointer to get a reference to the trace_array and then take its reference. The problem is that the ftrace_ops itself could be freed. If the rmdir on the instance happens at the same time the set_ftrace_filter file is opened, the rmdir could have also freed the ftrace_ops and referencing it will cause a use-after-free bug and crash the kernel. Instead, pass in the trace_array as the file private data (NULL for the top level instance), and then pass both the trace_array and the ftrace_ops to the ftrace_regex_open() function. If the trace_array is NULL, then it just uses the ftrace_ops without the need to take its reference (like normal). If the ftrace_ops is NULL, that is only the case for the top level instance and the global_ops can be used. This allows the trace_array to have its reference incremented before touching the ftrace_ops that could also be freed when the instance is. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260828223901.29e26edb@robin Fixes: 591dffdade9f0 ("ftrace: Allow for function tracing instance to filter functions") Reported-by: Breno Leitao Tested-by: Breno Leitao Closes: https://lore.kernel.org/all/apGORjltZgAiAYHT@gmail.com/ Signed-off-by: Steven Rostedt --- include/linux/ftrace.h | 5 +-- kernel/trace/ftrace.c | 57 ++++++++++++++++++++++------------ kernel/trace/trace.h | 5 +-- kernel/trace/trace_functions.c | 2 +- kernel/trace/trace_stack.c | 2 +- 5 files changed, 45 insertions(+), 26 deletions(-) diff --git a/include/linux/ftrace.h b/include/linux/ftrace.h index 02bc5027523a..bd76a16a63af 100644 --- a/include/linux/ftrace.h +++ b/include/linux/ftrace.h @@ -866,8 +866,9 @@ unsigned long ftrace_get_addr_new(struct dyn_ftrace *rec); unsigned long ftrace_get_addr_curr(struct dyn_ftrace *rec); extern ftrace_func_t ftrace_trace_function; +struct trace_array; -int ftrace_regex_open(struct ftrace_ops *ops, int flag, +int ftrace_regex_open(struct trace_array *tr, struct ftrace_ops *ops, int flag, struct inode *inode, struct file *file); ssize_t ftrace_filter_write(struct file *file, const char __user *ubuf, size_t cnt, loff_t *ppos); @@ -1077,7 +1078,7 @@ static inline unsigned long ftrace_location(unsigned long ip) * have them defined when ftrace is not enabled, but these * functions may still be called. Use a macro instead of inline. */ -#define ftrace_regex_open(ops, flag, inod, file) ({ -ENODEV; }) +#define ftrace_regex_open(tr, ops, flag, inode, file) ({ -ENODEV; }) #define ftrace_set_early_filter(ops, buf, enable) do { } while (0) #define ftrace_set_filter_ip(ops, ip, remove, reset) ({ -ENODEV; }) #define ftrace_set_filter_ips(ops, ips, cnt, remove, reset) ({ -ENODEV; }) diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index f9d80c7bd9f1..c7cf36f2dd7b 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -4677,7 +4677,8 @@ ftrace_avail_addrs_open(struct inode *inode, struct file *file) /** * ftrace_regex_open - initialize function tracer filter files - * @ops: The ftrace_ops that hold the hash filters + * @tr: The trace_array that holds the ftrace_ops [optional] + * @ops: The ftrace_ops that hold the hash filters [optional] * @flag: The type of filter to process * @inode: The inode, usually passed in to your open routine * @file: The file, usually passed in to your open routine @@ -4691,26 +4692,45 @@ ftrace_avail_addrs_open(struct inode *inode, struct file *file) * tracing_lseek() should be used as the lseek routine, and * release must call ftrace_regex_release(). * + * Note, If @tr is not NULL, its reference has to be taken before + * @ops may be referenced. + * If @ops is NULL and @tr is not, then @tr->ops is used. + * If @tr is NULL and @ops is not then @ops->private is uesd for @tr. + * If both @tr and @ops are NULL, then the &global_ops is + * to be used, and @tr will be the global_ops.private pointer. + * * Returns: 0 on success or a negative errno value on failure */ int -ftrace_regex_open(struct ftrace_ops *ops, int flag, +ftrace_regex_open(struct trace_array *tr, struct ftrace_ops *ops, int flag, struct inode *inode, struct file *file) { - struct ftrace_iterator *iter; + struct ftrace_iterator *iter = NULL; struct ftrace_hash *hash; struct list_head *mod_head; - struct trace_array *tr = ops->private; - int ret = -ENOMEM; - - ftrace_ops_init(ops); + int ret = -ENODEV; if (unlikely(ftrace_disabled)) return -ENODEV; + if (!tr) { + if (!ops) + ops = &global_ops; + tr = ops->private; + } + if (tracing_check_open_get_tr(tr)) return -ENODEV; + if (!ops) + ops = tr->ops; + + if (WARN_ON_ONCE(!ops)) + goto out; + + ftrace_ops_init(ops); + + ret = -ENOMEM; iter = kzalloc_obj(*iter); if (!iter) goto out; @@ -4788,21 +4808,19 @@ ftrace_regex_open(struct ftrace_ops *ops, int flag, static int ftrace_filter_open(struct inode *inode, struct file *file) { - struct ftrace_ops *ops = inode->i_private; + struct trace_array *tr = inode->i_private; - /* Checks for tracefs lockdown */ - return ftrace_regex_open(ops, - FTRACE_ITER_FILTER | FTRACE_ITER_DO_PROBES, - inode, file); + return ftrace_regex_open(tr, NULL, + FTRACE_ITER_FILTER | FTRACE_ITER_DO_PROBES, + inode, file); } static int ftrace_notrace_open(struct inode *inode, struct file *file) { - struct ftrace_ops *ops = inode->i_private; + struct trace_array *tr = inode->i_private; - /* Checks for tracefs lockdown */ - return ftrace_regex_open(ops, FTRACE_ITER_NOTRACE, + return ftrace_regex_open(tr, NULL, FTRACE_ITER_NOTRACE, inode, file); } @@ -7492,15 +7510,15 @@ static const struct file_operations ftrace_graph_notrace_fops = { }; #endif /* CONFIG_FUNCTION_GRAPH_TRACER */ -void ftrace_create_filter_files(struct ftrace_ops *ops, +void ftrace_create_filter_files(struct trace_array *tr, struct dentry *parent) { trace_create_file("set_ftrace_filter", TRACE_MODE_WRITE, parent, - ops, &ftrace_filter_fops); + tr, &ftrace_filter_fops); trace_create_file("set_ftrace_notrace", TRACE_MODE_WRITE, parent, - ops, &ftrace_notrace_fops); + tr, &ftrace_notrace_fops); } /* @@ -7525,7 +7543,6 @@ void ftrace_destroy_filter_files(struct ftrace_ops *ops) static __init int ftrace_init_dyn_tracefs(struct dentry *d_tracer) { - trace_create_file("available_filter_functions", TRACE_MODE_READ, d_tracer, NULL, &ftrace_avail_fops); @@ -7538,7 +7555,7 @@ static __init int ftrace_init_dyn_tracefs(struct dentry *d_tracer) trace_create_file("touched_functions", TRACE_MODE_READ, d_tracer, NULL, &ftrace_touched_fops); - ftrace_create_filter_files(&global_ops, d_tracer); + ftrace_create_filter_files(NULL, d_tracer); #ifdef CONFIG_FUNCTION_GRAPH_TRACER trace_create_file("set_graph_function", TRACE_MODE_WRITE, d_tracer, diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 74a7a50d1e78..3c111ca88e32 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -1340,7 +1340,7 @@ extern void clear_ftrace_function_probes(struct trace_array *tr); int register_ftrace_command(struct ftrace_func_command *cmd); int unregister_ftrace_command(struct ftrace_func_command *cmd); -void ftrace_create_filter_files(struct ftrace_ops *ops, +void ftrace_create_filter_files(struct trace_array *tr, struct dentry *parent); void ftrace_destroy_filter_files(struct ftrace_ops *ops); @@ -1363,11 +1363,12 @@ static inline void clear_ftrace_function_probes(struct trace_array *tr) { } +static inline void ftrace_create_filter_files(struct trace_array *tr, + struct dentry *parent) { } /* * The ops parameter passed in is usually undefined. * This must be a macro. */ -#define ftrace_create_filter_files(ops, parent) do { } while (0) #define ftrace_destroy_filter_files(ops) do { } while (0) #endif /* CONFIG_FUNCTION_TRACER && CONFIG_DYNAMIC_FTRACE */ diff --git a/kernel/trace/trace_functions.c b/kernel/trace/trace_functions.c index cd37f2013758..c879d43a5fbb 100644 --- a/kernel/trace/trace_functions.c +++ b/kernel/trace/trace_functions.c @@ -101,7 +101,7 @@ int ftrace_create_function_files(struct trace_array *tr, return ret; } - ftrace_create_filter_files(tr->ops, parent); + ftrace_create_filter_files(tr, parent); return 0; } diff --git a/kernel/trace/trace_stack.c b/kernel/trace/trace_stack.c index 0aa2514a6593..e7f4e523587d 100644 --- a/kernel/trace/trace_stack.c +++ b/kernel/trace/trace_stack.c @@ -499,7 +499,7 @@ stack_trace_filter_open(struct inode *inode, struct file *file) struct ftrace_ops *ops = inode->i_private; /* Checks for tracefs lockdown */ - return ftrace_regex_open(ops, FTRACE_ITER_FILTER, + return ftrace_regex_open(NULL, ops, FTRACE_ITER_FILTER, inode, file); } From 40fe154ba049a33f063c0058cd185d7f682088f3 Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Sat, 8 Aug 2026 14:38:32 +0800 Subject: [PATCH 0494/2366] btrfs: clean up target device if block group marking fails btrfs_dev_replace_start() adds the replacement target to the device list before marking block groups to copy. If marking fails, returning directly leaves the target linked and keeps the device accounting incremented. Jump to the existing cleanup path so the target device is removed and released on failure. The issue was found by a failure-path metadata residual analyzer and verified with targeted failure injection on v6.14. Assisted-by: Codex:gpt-5 Reviewed-by: Qu Wenruo Signed-off-by: Guanghui Yang <3497809730@qq.com> Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/dev-replace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c index 318ddb790429..bf0b78790171 100644 --- a/fs/btrfs/dev-replace.c +++ b/fs/btrfs/dev-replace.c @@ -626,7 +626,7 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info, ret = mark_block_group_to_copy(fs_info, src_device); if (ret) - return ret; + goto leave; down_write(&dev_replace->rwsem); dev_replace->replace_task = current; From c93b3c43df561cd9f592cee20ae058b563f9e5b6 Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Mon, 10 Aug 2026 20:16:04 +0800 Subject: [PATCH 0495/2366] btrfs: detach failed sprout device from transaction update list When creating the first metadata chunk for a sprout filesystem, create_chunk() adds the new device to the transaction dev_update_list through device->post_commit_list. If the subsequent system chunk creation fails, btrfs_init_new_device() aborts the transaction and releases the device while post_commit_list is still linked. This triggers a warning in btrfs_free_device() and leaves the transaction list referencing freed memory. Detach the device while holding chunk_mutex before releasing it. Fixes: bbbf7243d62d ("btrfs: combine device update operations during transaction commit") Assisted-by: Codex:gpt-5 Reviewed-by: Qu Wenruo Signed-off-by: Guanghui Yang <3497809730@qq.com> Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/volumes.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index a8e27db8e4bc..cbb491c6d4be 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -3036,6 +3036,8 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path btrfs_sysfs_remove_device(device); mutex_lock(&fs_info->fs_devices->device_list_mutex); mutex_lock(&fs_info->chunk_mutex); + if (!list_empty(&device->post_commit_list)) + list_del_init(&device->post_commit_list); list_del_rcu(&device->dev_list); list_del(&device->dev_alloc_list); fs_info->fs_devices->num_devices--; From e0b54613aabeb8e9da597f23b90c6a03d0981986 Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Mon, 10 Aug 2026 20:16:05 +0800 Subject: [PATCH 0496/2366] btrfs: restore active device pointers after failed sprout btrfs_init_new_device() switches latest_dev and possibly s_bdev from the seed device to the new sprout device before creating the first writable chunks. If chunk creation or the subsequent sprout setup fails, the error path releases the new device without switching those pointers back. btrfs_show_devname() can then dereference the freed latest_dev and crash. Restore the active device pointers to the latest seed device before removing and releasing the failed sprout device. Fixes: b7cb29e666fe ("btrfs: update latest_dev when we create a sprout device") Assisted-by: Codex:gpt-5 Reviewed-by: Qu Wenruo Signed-off-by: Guanghui Yang <3497809730@qq.com> Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/volumes.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index cbb491c6d4be..427aa8e24fc3 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -3035,6 +3035,8 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path error_sysfs: btrfs_sysfs_remove_device(device); mutex_lock(&fs_info->fs_devices->device_list_mutex); + if (seeding_dev) + btrfs_assign_next_active_device(device, seed_devices->latest_dev); mutex_lock(&fs_info->chunk_mutex); if (!list_empty(&device->post_commit_list)) list_del_init(&device->post_commit_list); From e8a0095c7df170945b4740eda4e2738a50f97fc6 Mon Sep 17 00:00:00 2001 From: Sam Ho Date: Fri, 14 Aug 2026 13:01:11 +0000 Subject: [PATCH 0497/2366] btrfs: preserve the compression property when other inode flags change Setting the compression property on an inode also sets BTRFS_INODE_COMPRESS on it, and btrfs_inode_flags_to_fsflags() reports that back as FS_COMPR_FL to FS_IOC_GETFLAGS. chattr(1), like any other FS_IOC_SETFLAGS caller, reads the current flags, flips only the bit the user asked for and writes the whole set back, so a request as unrelated as "chattr +i" reaches btrfs_fileattr_set() with FS_COMPR_FL set. btrfs_fileattr_set() takes that as a request to enable compression and overwrites the compression property with the algorithm from the mount options, falling back to zlib when the filesystem was not mounted with -o compress. The algorithm the user selected is silently replaced: # btrfs property set /mnt/foo compression zstd # btrfs property get /mnt/foo compression compression=zstd # chattr +i /mnt/foo # btrfs property get /mnt/foo compression compression=zlib Every chattr operation triggers this, not just +i, and directories are affected as well, so files created afterwards inherit the wrong algorithm too. On a filesystem mounted with -o compress=lzo the property is replaced with lzo instead. Recovering needs a chattr -i first, because the immutable flag rejects the setxattr that "btrfs property set" issues. Prefer the algorithm recorded in the compression property and only fall back to the mount default when there is no property, so that unrelated flag changes no longer overwrite the user's choice. Inodes that have the compress flag set but no property still get the default, so they behave as before. Reviewed-by: Qu Wenruo Signed-off-by: Sam Ho Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/ioctl.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index ebfb258161c8..21c4e755f9c5 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -384,6 +384,7 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, inode_flags &= ~BTRFS_INODE_COMPRESS; inode_flags |= BTRFS_INODE_NOCOMPRESS; } else if (fsflags & FS_COMPR_FL) { + enum btrfs_compression_type comp_type; if (IS_SWAPFILE(&inode->vfs_inode)) return -ETXTBSY; @@ -391,9 +392,23 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, inode_flags |= BTRFS_INODE_COMPRESS; inode_flags &= ~BTRFS_INODE_NOCOMPRESS; - comp = btrfs_compress_type2str(fs_info->compress_type); - if (!comp || comp[0] == 0) - comp = btrfs_compress_type2str(BTRFS_COMPRESS_ZLIB); + /* + * Keep the algorithm recorded in the compression property, + * otherwise changing an unrelated attribute would reset it to + * the mount default, since FS_IOC_SETFLAGS callers write back + * the whole flag set they got from FS_IOC_GETFLAGS and that + * includes FS_COMPR_FL for any inode carrying the property. + * + * Inodes with the compress flag set but no property keep using + * the mount default, so they behave as before. + */ + if (inode->prop_compress) + comp_type = inode->prop_compress; + else if (fs_info->compress_type) + comp_type = fs_info->compress_type; + else + comp_type = BTRFS_COMPRESS_ZLIB; + comp = btrfs_compress_type2str(comp_type); } else { inode_flags &= ~(BTRFS_INODE_COMPRESS | BTRFS_INODE_NOCOMPRESS); } From 2625480a1bf79c62ffb09aafdf61778e682da492 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 1 Sep 2026 23:50:02 +0100 Subject: [PATCH 0498/2366] hardening: Default randstruct off with rust for better allmodconfig support Currently randstruct does not support rust so we have Kconfig dependencies which prevent rust being enabled when randstruct is. Unfortunately this prevents rust being enabled in allmodconfig, our standard coverage build. randstruct gets turned on by default, then the dependency on !RANDSTRUCT causes rust to get disabled. Work around this by disabling randstruct by default if we have a usable rust toolchain and rust support for the architecture, circular dependencies prevent us directly depending on !RUST. This means we might end up with a configuration that disables both rust and randstruct but hopefully it's more likely go give the expected result. Signed-off-by: Mark Brown Acked-by: Miguel Ojeda Link: https://patch.msgid.link/20260901-rust-reverse-randstruct-dep-v4-1-3bfa19efe1fa@kernel.org Signed-off-by: Kees Cook --- security/Kconfig.hardening | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening index 6923036e1a2f..81c81ad983ad 100644 --- a/security/Kconfig.hardening +++ b/security/Kconfig.hardening @@ -278,7 +278,7 @@ config CC_HAS_RANDSTRUCT choice prompt "Randomize layout of sensitive kernel structures" - default RANDSTRUCT_FULL if COMPILE_TEST && (GCC_PLUGINS || CC_HAS_RANDSTRUCT) + default RANDSTRUCT_FULL if !(RUST_IS_AVAILABLE && HAVE_RUST) && COMPILE_TEST && (GCC_PLUGINS || CC_HAS_RANDSTRUCT) default RANDSTRUCT_NONE help If you enable this, the layouts of structures that are entirely From b264d8422779d69febce914efc47a92a85cc382c Mon Sep 17 00:00:00 2001 From: Aswin Karuvally Date: Thu, 27 Aug 2026 08:34:08 +0200 Subject: [PATCH 0499/2366] s390/ctcm: Prevent XID null dereference The mpc_validate_xid() function sets grp->saved_xid2->xid2_flag2 to 0x40 to signal XID validation error. If peer XID is NULL or r/w channel pairing mismatch happens, grp->saved_xid2 is never initialized. An attempt to set the flag in such case leads to NULL dereference. Fix this by using the always available priv->xid->xid2_flag2 instead of grp->saved_xid2->xid2_flag2 for validation errors. Fixes: 293d984f0e36 ("ctcm: infrastructure for replaced ctc driver") Cc: stable@vger.kernel.org Signed-off-by: Aswin Karuvally Link: https://patch.msgid.link/20260827063408.2168914-1-aswin@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/s390/net/ctcm_mpc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/s390/net/ctcm_mpc.c b/drivers/s390/net/ctcm_mpc.c index 08e36685e578..61c88fe853c5 100644 --- a/drivers/s390/net/ctcm_mpc.c +++ b/drivers/s390/net/ctcm_mpc.c @@ -826,7 +826,7 @@ static void mpc_action_go_ready(fsm_instance *fsm, int event, void *arg) fsm_deltimer(&grp->timer); - if (grp->saved_xid2->xid2_flag2 == 0x40) { + if (priv->xid->xid2_flag2 == 0x40) { priv->xid->xid2_flag2 = 0x00; if (grp->estconnfunc) { grp->estconnfunc(grp->port_num, 1, @@ -1636,7 +1636,6 @@ static int mpc_validate_xid(struct mpcg_info *mpcginfo) "The XID used in the MPC protocol is not valid, " "rc = %d\n", rc); priv->xid->xid2_flag2 = 0x40; - grp->saved_xid2->xid2_flag2 = 0x40; } return rc; From 70f3995830d3f1e79faa14eb0605914f778feca9 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 31 Aug 2026 19:46:26 +0000 Subject: [PATCH 0500/2366] bonding: alb: fix uninitialized transport header access in alb_determine_nd() alb_determine_nd() uses icmp6_hdr(skb) to inspect ICMPv6 headers. However, in xmit paths (e.g. packets sent via AF_PACKET / raw sockets or forwarded packets), skb->transport_header is not guaranteed to be initialized. While pskb_network_may_pull() ensures the packet data is linear starting from the network header, it does not set or adjust the transport header offset. Dereferencing icmp6_hdr(skb) can therefore access out-of-bounds memory. Fetch the icmp6hdr directly after ipv6hdr following pskb_network_may_pull(), and reload ipv6hdr in case pskb_may_pull() reallocated skb->head. Also remove the unused bond argument from alb_determine_nd(). Fixes: 0da8aa00bfcf ("net: bonding: Add support for IPV6 ns/na to balance-alb/balance-tlb mode") Signed-off-by: Eric Dumazet Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260831194626.119371-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_alb.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c index d2fb67a47cf9..654f051d0023 100644 --- a/drivers/net/bonding/bond_alb.c +++ b/drivers/net/bonding/bond_alb.c @@ -1281,10 +1281,10 @@ static int alb_set_mac_address(struct bonding *bond, void *addr) } /* determine if the packet is NA or NS */ -static bool alb_determine_nd(struct sk_buff *skb, struct bonding *bond) +static bool alb_determine_nd(struct sk_buff *skb) { - struct ipv6hdr *ip6hdr; - struct icmp6hdr *hdr; + const struct ipv6hdr *ip6hdr; + const struct icmp6hdr *hdr; if (!pskb_network_may_pull(skb, sizeof(*ip6hdr))) return true; @@ -1296,7 +1296,8 @@ static bool alb_determine_nd(struct sk_buff *skb, struct bonding *bond) if (!pskb_network_may_pull(skb, sizeof(*ip6hdr) + sizeof(*hdr))) return true; - hdr = icmp6_hdr(skb); + ip6hdr = ipv6_hdr(skb); + hdr = (const struct icmp6hdr *)(ip6hdr + 1); return hdr->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT || hdr->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION; } @@ -1381,7 +1382,7 @@ struct slave *bond_xmit_tlb_slave_get(struct bonding *bond, if (!is_multicast_ether_addr(eth_data->h_dest)) { switch (skb->protocol) { case htons(ETH_P_IPV6): - if (alb_determine_nd(skb, bond)) + if (alb_determine_nd(skb)) break; fallthrough; case htons(ETH_P_IP): @@ -1467,7 +1468,7 @@ struct slave *bond_xmit_alb_slave_get(struct bonding *bond, break; } - if (alb_determine_nd(skb, bond)) { + if (alb_determine_nd(skb)) { do_tx_balance = false; break; } From 6809da6e9c08ccc9a09cb0a48c61471679274aaa Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 18 Aug 2026 17:10:00 +0800 Subject: [PATCH 0501/2366] perf: RISC-V: store available counter mask as bitmap The available-counter mask was a single unsigned long, but iteration uses RISCV_MAX_COUNTERS, which is 64. On RV32 that reads past the object. Filling with an unsigned-long bit at index 32 and above is also wrong. Use DECLARE_BITMAP and set_bit/bitmap helpers. Walk each bitmap word into CFG_MATCH when checking events, when allocating an index, and when stopping all counters. Set the counter base to i times BITS_PER_LONG. Share the CFG_MATCH ecall through a small helper so the 32-bit argument split is not duplicated. On qemu-system-riscv32 the probe bitmap has bits above XLEN set, so the first word alone is not enough. Fixes: e9991434596f ("RISC-V: Add perf platform driver based on SBI PMU extension") Assisted-by: DeepSeek:deepseek-v3 Signed-off-by: Xixin Liu Link: https://patch.msgid.link/prpmask02cmap.v2.1786434000.git.liuxixin@kylinos.cn Cc: stable@kernel.org [pjw@kernel.org: updated to apply; fixed checkpatch.pl issues] Signed-off-by: Paul Walmsley --- drivers/perf/riscv_pmu_legacy.c | 5 +- drivers/perf/riscv_pmu_sbi.c | 88 +++++++++++++++++++++++---------- include/linux/perf/riscv_pmu.h | 2 +- 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/drivers/perf/riscv_pmu_legacy.c b/drivers/perf/riscv_pmu_legacy.c index 4d6461d6a74f..1b8e4789cb3c 100644 --- a/drivers/perf/riscv_pmu_legacy.c +++ b/drivers/perf/riscv_pmu_legacy.c @@ -110,8 +110,9 @@ static void pmu_legacy_init(struct riscv_pmu *pmu) { pr_info("Legacy PMU implementation is available\n"); - pmu->cmask = BIT(RISCV_PMU_LEGACY_CYCLE) | - BIT(RISCV_PMU_LEGACY_INSTRET); + bitmap_zero(pmu->cmask, RISCV_MAX_COUNTERS); + set_bit(RISCV_PMU_LEGACY_CYCLE, pmu->cmask); + set_bit(RISCV_PMU_LEGACY_INSTRET, pmu->cmask); pmu->ctr_start = pmu_legacy_ctr_start; pmu->ctr_stop = NULL; pmu->event_map = pmu_legacy_event_map; diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c index 8ea5ae617347..2c50a6ba2a80 100644 --- a/drivers/perf/riscv_pmu_sbi.c +++ b/drivers/perf/riscv_pmu_sbi.c @@ -97,7 +97,7 @@ static unsigned int riscv_pmu_irq_mask; static unsigned int riscv_pmu_irq; /* Cache the available counters in a bitmask */ -static unsigned long cmask; +static DECLARE_BITMAP(cmask, RISCV_MAX_COUNTERS); static int pmu_event_find_cache(u64 config); struct sbi_pmu_event_data { @@ -359,16 +359,38 @@ static int pmu_sbi_check_event_info(void) return result; } +static struct sbiret pmu_sbi_ctr_cfg_match(unsigned long cbase, + unsigned long ctr_mask, + unsigned long cflags, + unsigned long event_idx, + u64 config) +{ +#if defined(CONFIG_32BIT) + return sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_CFG_MATCH, cbase, + ctr_mask, cflags, event_idx, config, config >> 32); +#else + return sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_CFG_MATCH, cbase, + ctr_mask, cflags, event_idx, config, 0); +#endif +} + static void pmu_sbi_check_event(struct sbi_pmu_event_data *edata) { - struct sbiret ret; + struct sbiret ret = { .error = SBI_ERR_NOT_SUPPORTED }; + int i; - ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_CFG_MATCH, - 0, cmask, 0, edata->event_idx, 0, 0); - if (!ret.error) { - sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, - ret.value, 0x1, SBI_PMU_STOP_FLAG_RESET, 0, 0, 0); - } else if (ret.error == SBI_ERR_NOT_SUPPORTED) { + for (i = 0; i < BITS_TO_LONGS(RISCV_MAX_COUNTERS); i++) { + if (!cmask[i]) + continue; + ret = pmu_sbi_ctr_cfg_match(i * BITS_PER_LONG, cmask[i], 0, + edata->event_idx, 0); + if (!ret.error) { + sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, + ret.value, 0x1, SBI_PMU_STOP_FLAG_RESET, 0, 0, 0); + return; + } + } + if (ret.error == SBI_ERR_NOT_SUPPORTED) { /* This event cannot be monitored by any counter */ edata->event_idx = -ENOENT; } @@ -488,10 +510,10 @@ int riscv_pmu_get_hpm_info(u32 *hw_ctr_width, u32 *num_hw_ctr) union sbi_pmu_ctr_info *info; u32 hpm_width = 0, hpm_count = 0; - if (!cmask) + if (bitmap_empty(cmask, RISCV_MAX_COUNTERS)) return -EINVAL; - for_each_set_bit(i, &cmask, RISCV_MAX_COUNTERS) { + for_each_set_bit(i, cmask, RISCV_MAX_COUNTERS) { info = &pmu_ctr_list[i]; if (!info) continue; @@ -540,8 +562,8 @@ static int pmu_sbi_ctr_get_idx(struct perf_event *event) struct riscv_pmu *rvpmu = to_riscv_pmu(event->pmu); struct cpu_hw_events *cpuc = this_cpu_ptr(rvpmu->hw_events); struct sbiret ret; - int idx; - uint64_t cbase = 0, cmask = rvpmu->cmask; + int idx, i; + u64 cbase = 0, cmask = 0; unsigned long cflags = 0; cflags = pmu_sbi_get_filter_flags(event); @@ -562,14 +584,21 @@ static int pmu_sbi_ctr_get_idx(struct perf_event *event) } /* retrieve the available counter index */ -#if defined(CONFIG_32BIT) - ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_CFG_MATCH, cbase, - cmask, cflags, hwc->event_base, hwc->config, - hwc->config >> 32); -#else - ret = sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_CFG_MATCH, cbase, - cmask, cflags, hwc->event_base, hwc->config, 0); -#endif + if (cmask) { + ret = pmu_sbi_ctr_cfg_match(cbase, cmask, cflags, hwc->event_base, + hwc->config); + } else { + ret.error = SBI_ERR_NOT_SUPPORTED; + for (i = 0; i < BITS_TO_LONGS(RISCV_MAX_COUNTERS); i++) { + if (!rvpmu->cmask[i]) + continue; + cbase = i * BITS_PER_LONG; + ret = pmu_sbi_ctr_cfg_match(cbase, rvpmu->cmask[i], cflags, + hwc->event_base, hwc->config); + if (!ret.error) + break; + } + } if (ret.error) { pr_debug("Not able to find a counter for event %lx config %llx\n", hwc->event_base, hwc->config); @@ -577,7 +606,7 @@ static int pmu_sbi_ctr_get_idx(struct perf_event *event) } idx = ret.value; - if (!test_bit(idx, &rvpmu->cmask) || !pmu_ctr_list[idx].value) + if (!test_bit(idx, rvpmu->cmask) || !pmu_ctr_list[idx].value) return -ENOENT; /* Additional sanity check for the counter id */ @@ -881,7 +910,7 @@ static int pmu_sbi_get_ctrinfo(int nctr, unsigned long *mask) /* The logical counter ids are not expected to be contiguous */ continue; - *mask |= BIT(i); + set_bit(i, mask); cinfo.value = ret.value; if (cinfo.type == SBI_PMU_CTR_TYPE_FW) @@ -898,12 +927,19 @@ static int pmu_sbi_get_ctrinfo(int nctr, unsigned long *mask) static inline void pmu_sbi_stop_all(struct riscv_pmu *pmu) { + int i; + /* * No need to check the error because we are disabling all the counters * which may include counters that are not enabled yet. */ - sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, - 0, pmu->cmask, SBI_PMU_STOP_FLAG_RESET, 0, 0, 0); + for (i = 0; i < BITS_TO_LONGS(RISCV_MAX_COUNTERS); i++) { + if (!pmu->cmask[i]) + continue; + sbi_ecall(SBI_EXT_PMU, SBI_EXT_PMU_COUNTER_STOP, + i * BITS_PER_LONG, pmu->cmask[i], + SBI_PMU_STOP_FLAG_RESET, 0, 0, 0); + } } static inline void pmu_sbi_stop_hw_ctrs(struct riscv_pmu *pmu) @@ -1451,7 +1487,7 @@ static int pmu_sbi_device_probe(struct platform_device *pdev) } /* cache all the information about counters now */ - if (pmu_sbi_get_ctrinfo(num_counters, &cmask)) + if (pmu_sbi_get_ctrinfo(num_counters, cmask)) goto out_free; ret = pmu_sbi_setup_irqs(pmu, pdev); @@ -1464,7 +1500,7 @@ static int pmu_sbi_device_probe(struct platform_device *pdev) pmu->pmu.attr_groups = riscv_pmu_attr_groups; pmu->pmu.parent = &pdev->dev; - pmu->cmask = cmask; + bitmap_copy(pmu->cmask, cmask, RISCV_MAX_COUNTERS); pmu->ctr_start = pmu_sbi_ctr_start; pmu->ctr_stop = pmu_sbi_ctr_stop; pmu->event_map = pmu_sbi_event_map; diff --git a/include/linux/perf/riscv_pmu.h b/include/linux/perf/riscv_pmu.h index f82a28040594..ecaa40370830 100644 --- a/include/linux/perf/riscv_pmu.h +++ b/include/linux/perf/riscv_pmu.h @@ -55,7 +55,7 @@ struct riscv_pmu { irqreturn_t (*handle_irq)(int irq_num, void *dev); - unsigned long cmask; + DECLARE_BITMAP(cmask, RISCV_MAX_COUNTERS); u64 (*ctr_read)(struct perf_event *event); int (*ctr_get_idx)(struct perf_event *event); int (*ctr_get_width)(int idx); From 248dbaf7770c0843702355a9fb724a882c669062 Mon Sep 17 00:00:00 2001 From: JinRui Date: Tue, 11 Aug 2026 08:15:13 +0000 Subject: [PATCH 0502/2366] riscv: report Zfhmin/Zvfhmin when Zfh/Zvfh are present The RISC-V ISA manual specifies that Zfh implies Zfhmin, a normative rule clarified in https://github.com/riscv/riscv-isa-manual/pull/3070. Zvfh likewise implies Zvfhmin, as stated by the vector extension specification. The kernel currently reports ZFH and ZFHMIN (and ZVFH and ZVFHMIN) as independent hwprobe bits derived only from what the device tree declares. Platforms that declare just "zfh" (Zfh being a superset that already contains all Zfhmin instructions) therefore report RISCV_HWPROBE_EXT_ZFHMIN=0, which breaks userspace RVA23 conformance checks (e.g. snapd installing core26 on riscv64). Use the existing superset mechanism to set the implied subset bits: - zfh implies zfhmin - zvfh implies zvfhmin Add a hwprobe selftest asserting the implication holds and update the hwprobe documentation accordingly. This is complementary to the rva23u64 base behavior discussion: the RVA23 conformance query proposed there is derived from the per-extension bits fixed here, so correct EXT_0 reporting is a prerequisite for it to work on harts whose device tree declares only "zfh". Tested on a RISC-V QEMU VM whose device tree only declares "zfh" and "zvfh": with this change both /proc/cpuinfo and the hwprobe RISCV_HWPROBE_KEY_IMA_EXT_0 bitmap report ZFHMIN and ZVFHMIN, and the hwprobe selftest (including the new implication check) passes. Link: https://lore.kernel.org/kvm-riscv/20260206002349.96740-1-andrew.jones@oss.qualcomm.com/ Signed-off-by: JinRui Link: https://patch.msgid.link/7190E4DB338251C3+20260811081513.2849980-1-jinrui@haiwei.tech [pjw@kernel.org: trimmed superfluous blank line in tags] Signed-off-by: Paul Walmsley --- Documentation/arch/riscv/hwprobe.rst | 8 +++++--- arch/riscv/kernel/cpufeature.c | 20 +++++++++++++++++-- .../testing/selftests/riscv/hwprobe/hwprobe.c | 20 ++++++++++++++++++- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Documentation/arch/riscv/hwprobe.rst b/Documentation/arch/riscv/hwprobe.rst index 893e1a1215d2..bb1e0cbab36f 100644 --- a/Documentation/arch/riscv/hwprobe.rst +++ b/Documentation/arch/riscv/hwprobe.rst @@ -155,7 +155,8 @@ The following keys are defined: defined in version 1.0 of the RISC-V Cryptography Extensions Volume II. * :c:macro:`RISCV_HWPROBE_EXT_ZFH`: The Zfh extension version 1.0 is supported - as defined in the RISC-V ISA manual. + as defined in the RISC-V ISA manual. Zfh is a superset of Zfhmin, so + RISCV_HWPROBE_EXT_ZFHMIN is reported whenever RISCV_HWPROBE_EXT_ZFH is. * :c:macro:`RISCV_HWPROBE_EXT_ZFHMIN`: The Zfhmin extension version 1.0 is supported as defined in the RISC-V ISA manual. @@ -164,8 +165,9 @@ The following keys are defined: is supported as defined in the RISC-V ISA manual. * :c:macro:`RISCV_HWPROBE_EXT_ZVFH`: The Zvfh extension is supported as - defined in the RISC-V Vector manual starting from commit e2ccd0548d6c - ("Remove draft warnings from Zvfh[min]"). + defined in the RISC-V Vector manual starting from commit e2ccd0548d6c + ("Remove draft warnings from Zvfh[min]"). Zvfh is a superset of Zvfhmin, + so RISCV_HWPROBE_EXT_ZVFHMIN is reported whenever RISCV_HWPROBE_EXT_ZVFH is. * :c:macro:`RISCV_HWPROBE_EXT_ZVFHMIN`: The Zvfhmin extension is supported as defined in the RISC-V Vector manual starting from commit e2ccd0548d6c diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c index d2ec96843456..61d21f714830 100644 --- a/arch/riscv/kernel/cpufeature.c +++ b/arch/riscv/kernel/cpufeature.c @@ -412,6 +412,19 @@ static const unsigned int riscv_zvbb_exts[] = { RISCV_ISA_EXT_ZVKB }; +/* + * The RISC-V ISA manual specifies that Zfh implies Zfhmin and Zvfh implies + * Zvfhmin. Report the implied subset extensions whenever the supersets are + * detected (see https://github.com/riscv/riscv-isa-manual/pull/3070). + */ +static const unsigned int riscv_zfh_exts[] = { + RISCV_ISA_EXT_ZFHMIN +}; + +static const unsigned int riscv_zvfh_exts[] = { + RISCV_ISA_EXT_ZVFHMIN +}; + #define RISCV_ISA_EXT_ZVE64F_IMPLY_LIST \ RISCV_ISA_EXT_ZVE64X, \ RISCV_ISA_EXT_ZVE32F, \ @@ -550,7 +563,8 @@ const struct riscv_isa_ext_data riscv_isa_ext[] = { __RISCV_ISA_EXT_DATA(zawrs, RISCV_ISA_EXT_ZAWRS), __RISCV_ISA_EXT_DATA_VALIDATE(zfa, RISCV_ISA_EXT_ZFA, riscv_ext_f_depends), __RISCV_ISA_EXT_DATA_VALIDATE(zfbfmin, RISCV_ISA_EXT_ZFBFMIN, riscv_ext_f_depends), - __RISCV_ISA_EXT_DATA_VALIDATE(zfh, RISCV_ISA_EXT_ZFH, riscv_ext_f_depends), + __RISCV_ISA_EXT_SUPERSET_VALIDATE(zfh, RISCV_ISA_EXT_ZFH, + riscv_zfh_exts, riscv_ext_f_depends), __RISCV_ISA_EXT_DATA_VALIDATE(zfhmin, RISCV_ISA_EXT_ZFHMIN, riscv_ext_f_depends), __RISCV_ISA_EXT_DATA(zca, RISCV_ISA_EXT_ZCA), __RISCV_ISA_EXT_DATA_VALIDATE(zcb, RISCV_ISA_EXT_ZCB, riscv_ext_zca_depends), @@ -586,7 +600,9 @@ const struct riscv_isa_ext_data riscv_isa_ext[] = { __RISCV_ISA_EXT_SUPERSET_VALIDATE(zve64x, RISCV_ISA_EXT_ZVE64X, riscv_zve64x_exts, riscv_ext_vector_x_validate), __RISCV_ISA_EXT_DATA_VALIDATE(zvfbfmin, RISCV_ISA_EXT_ZVFBFMIN, riscv_vector_f_validate), __RISCV_ISA_EXT_DATA_VALIDATE(zvfbfwma, RISCV_ISA_EXT_ZVFBFWMA, riscv_ext_zvfbfwma_validate), - __RISCV_ISA_EXT_DATA(zvfh, RISCV_ISA_EXT_ZVFH), + __RISCV_ISA_EXT_SUPERSET_VALIDATE(zvfh, RISCV_ISA_EXT_ZVFH, + riscv_zvfh_exts, + riscv_ext_vector_float_validate), __RISCV_ISA_EXT_DATA(zvfhmin, RISCV_ISA_EXT_ZVFHMIN), __RISCV_ISA_EXT_DATA_VALIDATE(zvkb, RISCV_ISA_EXT_ZVKB, riscv_ext_vector_crypto_validate), __RISCV_ISA_EXT_DATA_VALIDATE(zvkg, RISCV_ISA_EXT_ZVKG, riscv_ext_vector_crypto_validate), diff --git a/tools/testing/selftests/riscv/hwprobe/hwprobe.c b/tools/testing/selftests/riscv/hwprobe/hwprobe.c index 54c435af9923..eca4441ee77f 100644 --- a/tools/testing/selftests/riscv/hwprobe/hwprobe.c +++ b/tools/testing/selftests/riscv/hwprobe/hwprobe.c @@ -9,7 +9,7 @@ int main(int argc, char **argv) long out; ksft_print_header(); - ksft_set_plan(5); + ksft_set_plan(6); /* Fake the CPU_SET ops. */ cpus = -1; @@ -62,5 +62,23 @@ int main(int argc, char **argv) pairs[1].key == 1 && pairs[1].value != 0xAAAA, "Unknown key overwritten with -1 and doesn't block other elements\n"); + pairs[0].key = RISCV_HWPROBE_KEY_IMA_EXT_0; + out = riscv_hwprobe(pairs, 1, 0, 0, 0); + if (out != 0) + ksft_exit_fail_msg("hwprobe(IMA_EXT_0) failed with %ld\n", out); + + /* + * The RISC-V ISA manual specifies that Zfh implies Zfhmin and Zvfh + * implies Zvfhmin, so hwprobe must report the implied subset + * extensions whenever the supersets are present. + */ + if ((pairs[0].value & RISCV_HWPROBE_EXT_ZFH) && + !(pairs[0].value & RISCV_HWPROBE_EXT_ZFHMIN)) + ksft_exit_fail_msg("Zfh reported without implied Zfhmin\n"); + if ((pairs[0].value & RISCV_HWPROBE_EXT_ZVFH) && + !(pairs[0].value & RISCV_HWPROBE_EXT_ZVFHMIN)) + ksft_exit_fail_msg("Zvfh reported without implied Zvfhmin\n"); + ksft_test_result_pass("Zfh/Zvfh imply Zfhmin/Zvfhmin\n"); + ksft_finished(); } From f643f520c4c6998fa27dce90dd3b3ff6414e0bae Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Tue, 11 Aug 2026 11:51:00 +0800 Subject: [PATCH 0503/2366] perf: RISC-V: check cpu_hw_evt before dereference in overflow IRQ The overflow IRQ handler dereferences cpu_hw_evt before the null check. Move the check first. Defensive only; the cookie is valid on the normal path today. Fixes: a8625217a054 ("drivers/perf: riscv: Implement SBI PMU snapshot function") Assisted-by: DeepSeek:deepseek-v3 Signed-off-by: Xixin Liu Link: https://patch.msgid.link/fdd42c791752.v2.1786420235.git.liuxixin@kylinos.cn Signed-off-by: Paul Walmsley --- drivers/perf/riscv_pmu_sbi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/perf/riscv_pmu_sbi.c b/drivers/perf/riscv_pmu_sbi.c index 2c50a6ba2a80..2991dd92def2 100644 --- a/drivers/perf/riscv_pmu_sbi.c +++ b/drivers/perf/riscv_pmu_sbi.c @@ -1086,11 +1086,13 @@ static irqreturn_t pmu_sbi_ovf_handler(int irq, void *dev) u64 overflowed_ctrs = 0; struct cpu_hw_events *cpu_hw_evt = dev; u64 start_clock = sched_clock(); - struct riscv_pmu_snapshot_data *sdata = cpu_hw_evt->snapshot_addr; + struct riscv_pmu_snapshot_data *sdata; if (WARN_ON_ONCE(!cpu_hw_evt)) return IRQ_NONE; + sdata = cpu_hw_evt->snapshot_addr; + /* Firmware counter don't support overflow yet */ fidx = find_first_bit(cpu_hw_evt->used_hw_ctrs, RISCV_MAX_COUNTERS); if (fidx == RISCV_MAX_COUNTERS) { From 93a27367bacdc32e2cdb478597102175db3fb80c Mon Sep 17 00:00:00 2001 From: Ivy Lopez Date: Mon, 31 Aug 2026 19:37:46 -0600 Subject: [PATCH 0504/2366] riscv: hwprobe: simplify has_fpu() to check D extension only The kernel never supports D without F, since D depends on F. The D-extension flag is cleared during devicetree/ACPI parsing whenever F is not present, so has_fpu() checking either extension with '||' never actually produces a different result than checking D alone - F without D cannot occur in practice, and there is no observable impact on RISCV_HWPROBE_IMA_FD or userspace. Simplify has_fpu() to check D only, matching the expectations set elsewhere in the kernel for this dependency, rather than relying on a redundant OR condition. sys_hwprobe.c already calls has_fpu() and needs no changes. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221874 Suggested-by: Conor Dooley Suggested-by: Andreas Schwab Signed-off-by: Ivy Lopez Reviewed-by: Conor Dooley Link: https://patch.msgid.link/20260901013746.19386-1-skunkolee@gmail.com [pjw@kernel.org: updated to apply] Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/switch_to.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/riscv/include/asm/switch_to.h b/arch/riscv/include/asm/switch_to.h index 04f10a949066..123c89b694e8 100644 --- a/arch/riscv/include/asm/switch_to.h +++ b/arch/riscv/include/asm/switch_to.h @@ -61,8 +61,8 @@ static inline void __switch_to_fpu(struct task_struct *prev, static __always_inline bool has_fpu(void) { - return riscv_has_extension_likely(RISCV_ISA_EXT_F) || - riscv_has_extension_likely(RISCV_ISA_EXT_D); + /* D extension depends on F, so checking D alone is sufficient. */ + return riscv_has_extension_likely(RISCV_ISA_EXT_D); } #else static __always_inline bool has_fpu(void) { return false; } From 817ecd588fa5820527ee3affa43b5116276d2520 Mon Sep 17 00:00:00 2001 From: Jia Wang Date: Mon, 24 Aug 2026 13:54:14 +0800 Subject: [PATCH 0505/2366] dt-bindings: riscv: cpus: Fix yamllint style issues Wrap an overlong description line and normalize the inline dependency lists to satisfy yamllint. Signed-off-by: Jia Wang Acked-by: Conor Dooley Link: https://patch.msgid.link/20260824-ultrarisc-dts-v1-1-61ab7aebe9e5@ultrarisc.com Signed-off-by: Paul Walmsley --- Documentation/devicetree/bindings/riscv/cpus.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/riscv/cpus.yaml b/Documentation/devicetree/bindings/riscv/cpus.yaml index 5feeb2203050..0da219ae6769 100644 --- a/Documentation/devicetree/bindings/riscv/cpus.yaml +++ b/Documentation/devicetree/bindings/riscv/cpus.yaml @@ -117,8 +117,8 @@ properties: $ref: /schemas/types.yaml#/definitions/uint32 description: VLEN/8, the vector register length in bytes. This property is required on - thead systems where the vector register length is not identical on all harts, or - the vlenb CSR is not available. + thead systems where the vector register length is not identical on all + harts, or the vlenb CSR is not available. # RISC-V has multiple properties for cache op block sizes as the sizes # differ between individual CBO extensions @@ -151,8 +151,8 @@ anyOf: - riscv,isa-base dependencies: - riscv,isa-base: [ "riscv,isa-extensions" ] - riscv,isa-extensions: [ "riscv,isa-base" ] + riscv,isa-base: ["riscv,isa-extensions"] + riscv,isa-extensions: ["riscv,isa-base"] required: - interrupt-controller From 74e26c692c40565448b8a1d1398c69e221a299cf Mon Sep 17 00:00:00 2001 From: Yukai Wu Date: Sat, 29 Aug 2026 10:23:27 +0800 Subject: [PATCH 0506/2366] docs/zh_CN: Update arch/riscv/patch-acceptance.rst translation Update Documentation/arch/riscv/patch-acceptance.rst translation. Update the translation through commit ed843ae947f8 ("docs: move riscv under arch") Signed-off-by: Yukai Wu Reviewed-by: Dongliang Mu Acked-by: Weijie Yuan Link: https://patch.msgid.link/20260829022344.192491-1-xiaoyewuz.Ruster@gmail.com Signed-off-by: Paul Walmsley --- .../zh_CN/arch/riscv/patch-acceptance.rst | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/Documentation/translations/zh_CN/arch/riscv/patch-acceptance.rst b/Documentation/translations/zh_CN/arch/riscv/patch-acceptance.rst index c8eb230ca8ee..20b91d0433a9 100644 --- a/Documentation/translations/zh_CN/arch/riscv/patch-acceptance.rst +++ b/Documentation/translations/zh_CN/arch/riscv/patch-acceptance.rst @@ -15,19 +15,41 @@ arch/riscv 开发者维护指南 概述 ---- -RISC-V指令集体系结构是公开开发的: +RISC-V 指令集体系结构是公开开发的: 正在进行的草案可供所有人查看和测试实现。新模块或者扩展草案可能会在开发过程中发 -生更改---有时以不兼容的方式对以前的草案进行更改。这种灵活性可能会给RISC-V Linux -维护者带来挑战。Linux开发过程更喜欢经过良好检查和测试的代码,而不是试验代码。我 -们希望推广同样的规则到即将被内核合并的RISC-V相关代码。 +生更改 --- 有时以不兼容的方式对以前的草案进行更改。这种灵活性可能会给 RISC-V +Linux 维护者带来挑战。Linux 维护者不赞成频繁的变更,且 Linux 开发过程更喜欢经过 +良好检查和测试的代码,而不是试验代码。我们希望推广同样的规则到即将被内核合并的 +RISC-V 相关代码。 + +Patchwork +--------- + +RISC-V 有一个 patchwork 实例,可以在那里查看补丁的状态: + + https://patchwork.kernel.org/project/linux-riscv/list/ + +如果你的补丁不在默认视图中出现,那么 RISC-V 维护者很有可能已要求修改,或者希望 +将其应用到另一个代码树上。 + +自动化流程会在该 patchwork 实例上运行,在每个补丁到达时立刻对其进行构建/测试。 +自动化流程会根据补丁是否被识别为修复,选用 RISC-V `for-next` 或 `fixes` 分支 +当前的 HEAD;若上述均应用失败,则使用 RISC-V `master` 分支。补丁系列被应用到的具 +体提交将标注在 patchwork 上。任何检查未通过的补丁通常不会被应用,并且在大多数情 +况下将需要重新提交。 附加的提交检查单 ---------------- -我们仅接受相关标准已经被RISC-V基金会标准为“已批准”或“已冻结”的扩展或模块的补丁。 -(开发者当然可以维护自己的Linux内核树,其中包含所需代码扩展草案的代码。) +我们仅接受针对新模块或扩展的补丁,前提是这些模块或扩展的规范被列为未来不太可能发 +生不兼容的变更。对于来自 RISC-V 基金会的规范,这意味着“已冻结”或“已批准”,对于 +UEFI 论坛的规范,这意味着已发布的 ECR。(开发者当然可以维护自己的 Linux 内核树, +其中包含他们所需的任何扩展草案的代码。) -此外,RISC-V规范允许爱好者创建自己的自定义扩展。这些自定义拓展不需要通过RISC-V -基金会的任何审核或批准。为了避免将爱好者一些特别的RISC-V拓展添加进内核代码带来 -的维护复杂性和对性能的潜在影响,我们将只接受RISC-V基金会正式冻结或批准的的扩展 -补丁。(开发者当然可以维护自己的Linux内核树,其中包含他们想要的任何自定义扩展 -的代码。) +此外,RISC-V 规范允许实现者创建自己的自定义扩展。这些自定义扩展不需要通过 RISC-V +基金会的任何审核或批准流程。为了避免因添加实现者特定的 RISC-V 扩展带来的维护复杂 +性和对性能的潜在影响,我们将只考虑符合以下任一条件的扩展补丁: + +- 已由 RISC-V 基金会正式冻结或批准 +- 已按照标准 Linux 惯例,在广泛可用的硬件中实现 + +(实现者当然可以维护自己的 Linux 内核树,其中包含他们所需的任何自定义扩展的代码。) From 2464ac8a6ff8d8a18b50088eb1d1d638f9cee2f1 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Thu, 13 Aug 2026 09:53:04 +0200 Subject: [PATCH 0507/2366] kselftest/riscv: Replace __ASSEMBLY__ with __ASSEMBLER__ While the GCC and Clang compilers already define __ASSEMBLER__ automatically when compiling assembly code, __ASSEMBLY__ is a macro that only gets defined by the Makefiles in the kernel. This can be very confusing when switching between userspace and kernelspace coding, or when dealing with uapi headers that rather should use __ASSEMBLER__ instead. So let's standardize now on the __ASSEMBLER__ macro that is provided by the compilers. Signed-off-by: Thomas Huth Reviewed-by: Nick Desaulniers Link: https://patch.msgid.link/20260813075304.75988-1-thuth@redhat.com Signed-off-by: Paul Walmsley --- tools/testing/selftests/riscv/cfi/cfi_rv_test.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/riscv/cfi/cfi_rv_test.h b/tools/testing/selftests/riscv/cfi/cfi_rv_test.h index 1c8043f2b778..184df6903d01 100644 --- a/tools/testing/selftests/riscv/cfi/cfi_rv_test.h +++ b/tools/testing/selftests/riscv/cfi/cfi_rv_test.h @@ -56,7 +56,7 @@ #define CSR_SSP 0x011 -#ifdef __ASSEMBLY__ +#ifdef __ASSEMBLER__ #define __ASM_STR(x) x #else #define __ASM_STR(x) #x From 839f075aabdf5c21048f9801b87c0b841dd3d064 Mon Sep 17 00:00:00 2001 From: Jingbo Xu Date: Wed, 2 Sep 2026 09:50:39 +0800 Subject: [PATCH 0508/2366] erofs: add sysfs feature entry for xattr prefixes Let /sys/fs/erofs/features/xattr_prefixes advertise that this kernel supports the EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES on-disk format. Fixes: 6a318ccd7e08 ("erofs: enable long extended attribute name prefixes") Cc: stable@vger.kernel.org # 6.4+ Reviewed-by: Gao Xiang Signed-off-by: Jingbo Xu Signed-off-by: Gao Xiang --- Documentation/ABI/testing/sysfs-fs-erofs | 2 +- fs/erofs/sysfs.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-fs-erofs b/Documentation/ABI/testing/sysfs-fs-erofs index e4cf6fc6a106..0b8b4354e40b 100644 --- a/Documentation/ABI/testing/sysfs-fs-erofs +++ b/Documentation/ABI/testing/sysfs-fs-erofs @@ -5,7 +5,7 @@ Description: Shows all enabled kernel features. Supported features: compr_cfgs, big_pcluster, chunked_file, device_table, compr_head2, sb_chksum, ztailpacking, dedupe, fragments, - 48bit, metabox. + xattr_prefixes, 48bit, metabox. What: /sys/fs/erofs//sync_decompress Date: November 2021 diff --git a/fs/erofs/sysfs.c b/fs/erofs/sysfs.c index 6734483a440f..dfcec9376cd5 100644 --- a/fs/erofs/sysfs.c +++ b/fs/erofs/sysfs.c @@ -95,6 +95,7 @@ EROFS_ATTR_FEATURE(sb_chksum); EROFS_ATTR_FEATURE(ztailpacking); EROFS_ATTR_FEATURE(fragments); EROFS_ATTR_FEATURE(dedupe); +EROFS_ATTR_FEATURE(xattr_prefixes); EROFS_ATTR_FEATURE(48bit); EROFS_ATTR_FEATURE(metabox); @@ -108,6 +109,7 @@ static struct attribute *erofs_feat_attrs[] = { ATTR_LIST(ztailpacking), ATTR_LIST(fragments), ATTR_LIST(dedupe), + ATTR_LIST(xattr_prefixes), ATTR_LIST(48bit), ATTR_LIST(metabox), NULL, From f4825922d2fb371e2b969697d792077f1b62b62c Mon Sep 17 00:00:00 2001 From: Sujal Tuladhar Date: Sat, 1 Aug 2026 21:30:00 +0545 Subject: [PATCH 0509/2366] scsi: target: iscsi: Reserve a terminator byte for the login payload iscsi_target_check_login_request() rejects a login PDU whose DataSegmentLength exceeds MAX_KEY_VALUE_PAIRS, but the test is '>' and login->req_buf is allocated with exactly MAX_KEY_VALUE_PAIRS bytes. Since iscsit_get_login_rx() receives payload_length + padding bytes, where padding = ((-payload_length) & 3); any payload_length from 8189 to 8192 fills the whole 8192 byte buffer. The write stays in bounds, but no byte is left for a NUL terminator. The buffer is subsequently consumed as a C string. In the CHAP path chap_check_algorithm() calls kstrdup(a_str), and extract_param() calls strstr(in_buf, pattern) followed by strlen_semi(), none of which take a length. convert_null_to_semi() additionally rewrites every embedded NUL to ';', so even a payload made of well formed NUL separated key=value records is left without a terminator. These walk past the end of the object into adjacent slab memory. It is reachable by an unauthenticated initiator against a portal configured for CHAP; when authentication is not required iscsi_login_zero_tsih_s2() rewrites AuthMethod to None and the CHAP path is never entered. Allocate one extra byte. kzalloc() zeroes it and nothing ever writes to it, as every writer copies to offset 0 for at most MAX_KEY_VALUE_PAIRS bytes, so the buffer is always terminated. Fixes: e48354ce078c ("iscsi-target: Add iSCSI fabric support for target v4.1") Assisted-by: Claude Opus5 (custom harness) Cc: stable@vger.kernel.org Signed-off-by: Sujal Tuladhar Signed-off-by: Martin K. Petersen (Oracle) --- drivers/target/iscsi/iscsi_target_login.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/target/iscsi/iscsi_target_login.c b/drivers/target/iscsi/iscsi_target_login.c index aafc94bcb635..c282b6a70296 100644 --- a/drivers/target/iscsi/iscsi_target_login.c +++ b/drivers/target/iscsi/iscsi_target_login.c @@ -47,7 +47,7 @@ static struct iscsi_login *iscsi_login_init_conn(struct iscsit_conn *conn) login->conn = conn; login->first_request = 1; - login->req_buf = kzalloc(MAX_KEY_VALUE_PAIRS, GFP_KERNEL); + login->req_buf = kzalloc(MAX_KEY_VALUE_PAIRS + 1, GFP_KERNEL); if (!login->req_buf) { pr_err("Unable to allocate memory for response buffer.\n"); goto out_login; From 139f57343b3d6b26d9f01580123b2ba2d2150337 Mon Sep 17 00:00:00 2001 From: Laurence Oberman Date: Mon, 31 Aug 2026 07:59:17 -0400 Subject: [PATCH 0510/2366] scsi: mpi3mr: Fix use-after-free on tgt_dev->starget during target device refresh/update mpi3mr_refresh_tgtdevs() and mpi3mr_devinfochg_evt_bh() read tgt_dev->starget and immediately pass it to starget_for_each_device() without holding mrioc->tgtdev_lock. Every writer of this field -- mpi3mr_target_alloc(), mpi3mr_target_destroy(), mpi3mr_slave_destroy() and mpi3mr_sdev_init() -- correctly serializes access under tgtdev_lock, but these two read sites do not, which leaves a check-then-use window against the SCSI core's target teardown path (scsi_remove_target(), invoked e.g. via a concurrent host reset, sysfs "delete", or SCSI EH device offlining running independently of the fwevt workqueue). Sequence observed on production hardware, triggered on the mpi3mr0_fwevt_wrkr workqueue during a SAS topology change shortly after a controller reset: BUG: kernel NULL pointer dereference, address: 0000000000000058 RIP: scsi_is_host_device+0x7/0x20 Call Trace: starget_for_each_device+0x34/0x100 mpi3mr_refresh_tgtdevs+0x152/0x1d0 [mpi3mr] mpi3mr_fwevt_bh+0x514/0x6c0 [mpi3mr] mpi3mr_fwevt_worker+0x1a/0x50 [mpi3mr] process_one_work+0x194/0x380 worker_thread+0x2fe/0x410 mpi3mr_refresh_tgtdevs() reads tgt_dev->starget as non-NULL, but by the time starget_for_each_device() dereferences it, a concurrent mpi3mr_target_destroy() has already cleared tgt_dev->starget under tgtdev_lock and the SCSI/device core has freed the underlying scsi_target (and its embedded struct device). The stale pointer is then walked by dev_to_shost() -> scsi_is_host_device(), producing the NULL/garbage dereference above. Fix this by taking mrioc->tgtdev_lock around every read of tgt_dev->starget, matching the existing writer-side discipline. Since starget_for_each_device() and mpi3mr_update_sdev() can end up doing non-atomic work (e.g. queue_limits_commit_update()), the lock cannot be held across the whole call, so instead pin the target's device with get_device() while holding the lock, drop the lock, then run starget_for_each_device() against the pinned reference and put_device() afterwards. This closes the TOCTOU window instead of merely narrowing it. The same unlocked read-and-dereference pattern also exists earlier in mpi3mr_refresh_tgtdevs()'s first removal-scan loop (tgt_dev->starget->hostdata); fix it the same way by holding tgtdev_lock across that check, which is cheap since it only touches plain struct fields. Assisted-by: Claude:Sonnet5 [Claude Code] Signed-off-by: Laurence Oberman Acked-by: Chandrakanth Patil Link: https://patch.msgid.link/20260831120047.14690-1-loberman@redhat.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/mpi3mr/mpi3mr_os.c | 45 +++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/drivers/scsi/mpi3mr/mpi3mr_os.c b/drivers/scsi/mpi3mr/mpi3mr_os.c index f80a21ec161b..0f7380448718 100644 --- a/drivers/scsi/mpi3mr/mpi3mr_os.c +++ b/drivers/scsi/mpi3mr/mpi3mr_os.c @@ -1094,10 +1094,13 @@ static void mpi3mr_refresh_tgtdevs(struct mpi3mr_ioc *mrioc) { struct mpi3mr_tgt_dev *tgtdev, *tgtdev_next; struct mpi3mr_stgt_priv_data *tgt_priv; + struct scsi_target *starget; + unsigned long flags; dprint_reset(mrioc, "refresh target devices: check for removals\n"); list_for_each_entry_safe(tgtdev, tgtdev_next, &mrioc->tgtdev_list, list) { + spin_lock_irqsave(&mrioc->tgtdev_lock, flags); if (((tgtdev->dev_handle == MPI3MR_INVALID_DEV_HANDLE) || tgtdev->is_hidden) && tgtdev->host_exposed && tgtdev->starget && @@ -1106,6 +1109,7 @@ static void mpi3mr_refresh_tgtdevs(struct mpi3mr_ioc *mrioc) tgt_priv->dev_removed = 1; atomic_set(&tgt_priv->block_io, 0); } + spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags); } list_for_each_entry_safe(tgtdev, tgtdev_next, &mrioc->tgtdev_list, @@ -1127,15 +1131,25 @@ static void mpi3mr_refresh_tgtdevs(struct mpi3mr_ioc *mrioc) tgtdev = NULL; list_for_each_entry(tgtdev, &mrioc->tgtdev_list, list) { if ((tgtdev->dev_handle != MPI3MR_INVALID_DEV_HANDLE) && - !tgtdev->is_hidden) { - if (!tgtdev->host_exposed) + !tgtdev->is_hidden) { + if (!tgtdev->host_exposed) { mpi3mr_report_tgtdev_to_host(mrioc, - tgtdev->perst_id); - else if (tgtdev->starget) - starget_for_each_device(tgtdev->starget, - (void *)tgtdev, mpi3mr_update_sdev); - } + tgtdev->perst_id); + continue; + } + spin_lock_irqsave(&mrioc->tgtdev_lock, flags); + starget = tgtdev->starget; + if (starget) + get_device(&starget->dev); + spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags); + if (starget) { + starget_for_each_device(starget, (void *)tgtdev, + mpi3mr_update_sdev); + put_device(&starget->dev); + } + } } + dprint_reset(mrioc, "refresh target devices: done\n"); } /** @@ -1515,6 +1529,8 @@ static void mpi3mr_devinfochg_evt_bh(struct mpi3mr_ioc *mrioc, struct mpi3_device_page0 *dev_pg0) { struct mpi3mr_tgt_dev *tgtdev = NULL; + struct scsi_target *starget; + unsigned long flags; u16 dev_handle = 0, perst_id = 0; perst_id = le16_to_cpu(dev_pg0->persistent_id); @@ -1535,9 +1551,18 @@ static void mpi3mr_devinfochg_evt_bh(struct mpi3mr_ioc *mrioc, mpi3mr_report_tgtdev_to_host(mrioc, perst_id); if (tgtdev->is_hidden && tgtdev->host_exposed) mpi3mr_remove_tgtdev_from_host(mrioc, tgtdev); - if (!tgtdev->is_hidden && tgtdev->host_exposed && tgtdev->starget) - starget_for_each_device(tgtdev->starget, (void *)tgtdev, - mpi3mr_update_sdev); + if (!tgtdev->is_hidden && tgtdev->host_exposed) { + spin_lock_irqsave(&mrioc->tgtdev_lock, flags); + starget = tgtdev->starget; + if (starget) + get_device(&starget->dev); + spin_unlock_irqrestore(&mrioc->tgtdev_lock, flags); + if (starget) { + starget_for_each_device(starget, (void *)tgtdev, + mpi3mr_update_sdev); + put_device(&starget->dev); + } + } out: if (tgtdev) mpi3mr_tgtdev_put(tgtdev); From e0d26fe176a8db6ccad4ab38c5bab29391c1946b Mon Sep 17 00:00:00 2001 From: Ivy Lopez Date: Tue, 25 Aug 2026 13:03:13 -0600 Subject: [PATCH 0511/2366] scsi: mpt3sas: Avoid out-of-bounds cpumask_of_node() call in _base_assign_reply_queues() dev_to_node() can return NUMA_NO_NODE (-1) on systems without NUMA topology information for the PCI device, such as single-socket boards that don't expose device-to-node affinity. Passing -1 directly into cpumask_of_node() indexes node_to_cpumask_map[-1], an out-of-bounds array read caught by UBSAN: UBSAN: array-index-out-of-bounds in arch/x86/include/asm/topology.h:72:28 index -1 is out of range for type 'cpumask *[1024]' Fall back to cpu_online_mask when no NUMA node is available, rather than assuming dev_to_node() always returns a valid node index. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221294 Suggested-by: Johannes Thumshirn Fixes: 728bbc6cbff7 ("scsi: mpt3sas: Affinity high iops queues IRQs to local node") Signed-off-by: Ivy Lopez Reviewed-by: John Garry Link: https://patch.msgid.link/20260825190313.24013-1-skunkolee@gmail.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/mpt3sas/mpt3sas_base.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/mpt3sas/mpt3sas_base.c b/drivers/scsi/mpt3sas/mpt3sas_base.c index fed7aeffec58..1af25a22611a 100644 --- a/drivers/scsi/mpt3sas/mpt3sas_base.c +++ b/drivers/scsi/mpt3sas/mpt3sas_base.c @@ -3238,7 +3238,10 @@ _base_assign_reply_queues(struct MPT3SAS_ADAPTER *ioc) * corresponding to high iops queues. */ if (ioc->high_iops_queues) { - mask = cpumask_of_node(dev_to_node(&ioc->pdev->dev)); + int node = dev_to_node(&ioc->pdev->dev); + + mask = (node == NUMA_NO_NODE) ? + cpu_online_mask : cpumask_of_node(node); for (index = 0; index < ioc->high_iops_queues; index++) { irq = pci_irq_vector(ioc->pdev, index); From ab84c314417e4743f72f4d3d5e58cf96e07213cc Mon Sep 17 00:00:00 2001 From: Frederick Lawler Date: Wed, 19 Aug 2026 18:45:22 -0500 Subject: [PATCH 0512/2366] configfs: move CONFIGFS_MAGIC definition to magic.h IMA shouldn't measure or appraise configfs, but currently does because it's missing from the default exclusion policies. Move CONFIGFS_MAGIC to magic.h to expose the file system's magic to IMA, as well as other userland applications. Suggested-by: Mimi Zohar Signed-off-by: Frederick Lawler Acked-by: Breno Leitao Signed-off-by: Mimi Zohar --- fs/configfs/mount.c | 4 +--- include/uapi/linux/magic.h | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/configfs/mount.c b/fs/configfs/mount.c index 4929f3431189..d8cac1cbf3bd 100644 --- a/fs/configfs/mount.c +++ b/fs/configfs/mount.c @@ -9,6 +9,7 @@ */ #include +#include #include #include #include @@ -19,9 +20,6 @@ #include #include "configfs_internal.h" -/* Random magic number */ -#define CONFIGFS_MAGIC 0x62656570 - static struct vfsmount *configfs_mount = NULL; struct kmem_cache *configfs_dir_cachep; static int configfs_mnt_count = 0; diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h index fd5f0e95648e..66a91c8b1cb9 100644 --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -8,6 +8,7 @@ #define AUTOFS_SUPER_MAGIC 0x0187 #define CEPH_SUPER_MAGIC 0x00c36400 #define CODA_SUPER_MAGIC 0x73757245 +#define CONFIGFS_MAGIC 0x62656570 /* some random number */ #define CRAMFS_MAGIC 0x28cd3d45 /* some random number */ #define CRAMFS_MAGIC_WEND 0x453dcd28 /* magic number with the wrong endianess */ #define DEBUGFS_MAGIC 0x64626720 From 8e22ce504f8a332c57ca9676ab804da5ac4a1325 Mon Sep 17 00:00:00 2001 From: Frederick Lawler Date: Wed, 19 Aug 2026 18:45:23 -0500 Subject: [PATCH 0513/2366] ima: don't measure/appraise files on configfs IMA measurement of a configfs file causes process_measurement() to hold iint->mutex while performing a kernel_read() to hash it, which re-enters configfs's own file locking (buffer->mutex, frag_sem). Separately, opening any file with O_TRUNC now causes ima_file_truncate() to take iint->mutex to reset the cached action flags, while sb_writers is already held for that mount. When a configfs-backed nvmet namespace is involved, these two independent lock chains combine into a cycle: iint->mutex -> configfs locks -> subsys->lock -> sb_writers -> iint->mutex Add configfs to the builtin don't measure/appraise rules, similarly to other pseudo file systems, so IMA never takes iint->mutex for configfs file in the first place. Reported-by: syzbot+448c2e24b1ceff13ed2a@syzkaller.appspotmail.com Link: https://lore.kernel.org/all/6a77c7cd.b50370da.49fe0.0031.GAE@google.com/ Suggested-by: Mimi Zohar Signed-off-by: Frederick Lawler Signed-off-by: Mimi Zohar --- Documentation/ABI/testing/ima_policy | 3 +++ security/integrity/ima/ima_policy.c | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy index 19258471b7b2..b8a763e4c9fb 100644 --- a/Documentation/ABI/testing/ima_policy +++ b/Documentation/ABI/testing/ima_policy @@ -108,6 +108,9 @@ Description: # NSFS_MAGIC dont_measure fsmagic=0x6e736673 dont_appraise fsmagic=0x6e736673 + # CONFIGFS_MAGIC + dont_measure fsmagic=0x62656570 + dont_appraise fsmagic=0x62656570 measure func=BPRM_CHECK measure func=FILE_MMAP mask=MAY_EXEC diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index f79d07bb63c6..68d9a5e6c232 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -165,7 +165,10 @@ static struct ima_rule_entry dont_measure_rules[] __ro_after_init = { {.action = DONT_MEASURE, .fsmagic = CGROUP2_SUPER_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_MEASURE, .fsmagic = NSFS_MAGIC, .flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE, .fsmagic = EFIVARFS_MAGIC, .flags = IMA_FSMAGIC} + {.action = DONT_MEASURE, .fsmagic = EFIVARFS_MAGIC, + .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = CONFIGFS_MAGIC, + .flags = IMA_FSMAGIC} }; static struct ima_rule_entry original_measurement_rules[] __ro_after_init = { @@ -211,6 +214,8 @@ static struct ima_rule_entry default_appraise_rules[] __ro_after_init = { {.action = DONT_APPRAISE, .fsmagic = EFIVARFS_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = CGROUP_SUPER_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = CGROUP2_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = CONFIGFS_MAGIC, + .flags = IMA_FSMAGIC}, #ifdef CONFIG_IMA_WRITE_POLICY {.action = APPRAISE, .func = POLICY_CHECK, .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, From 8861f6d5c0678a7c5089c7b272509fc5931b8437 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Thu, 27 Aug 2026 17:43:38 +0000 Subject: [PATCH 0514/2366] ima: Check for ERR_PTR from dentry_path() in validate_hash_algo() dentry_path() returns ERR_PTR(-ENAMETOOLONG) when the path exceeds the buffer. validate_hash_algo() passes the result straight to integrity_audit_msg() without checking. ERR_PTR is not NULL, so integrity_audit_message() sees a valid pointer and calls strlen() on it, which faults: BUG: unable to handle page fault for address: ffffffffffffffdc RIP: 0010:strlen+0x30/0xa0 Call Trace: audit_log_untrustedstring+0x19/0x30 integrity_audit_message+0x366/0x4f0 ima_inode_setxattr+0x512/0x5f0 Check for IS_ERR() and use NULL instead, which makes the audit message skip the name= field instead of crashing. Fixes: 4f2946aa0c45 ("IMA: introduce a new policy option func=SETXATTR_CHECK") Cc: stable@vger.kernel.org Reported-by: syzbot+5ebeb3089ea6439c37be@syzkaller.appspotmail.com Link: https://lore.kernel.org/all/6a8f89e5.1d9ded08.62e62.00bf.GAE@google.com/ Signed-off-by: Bradley Morgan Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_appraise.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index ced2e131b061..b280488e15fc 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -748,6 +748,8 @@ static int validate_hash_algo(struct dentry *dentry, return -EACCES; path = dentry_path(dentry, pathbuf, PATH_MAX); + if (IS_ERR(path)) + path = NULL; integrity_audit_msg(AUDIT_INTEGRITY_DATA, d_inode(dentry), path, "set_data", errmsg, -EACCES, 0); From 617d0d8d199ba1790c94310fd75a22d01c97a8d6 Mon Sep 17 00:00:00 2001 From: Nikhil Gurudasani Date: Sun, 30 Aug 2026 16:11:09 +0530 Subject: [PATCH 0515/2366] erofs: preserve LZMA decoders on resize failure The pool-resize path frees each stream's old decoder before allocating its replacement. If an allocation fails after some streams have already been replaced, the failed stream is put back on the list with state == NULL. z_erofs_lzma_max_dictsize is still advanced as if the whole pool had been resized. An existing LZMA mount can select the broken stream and pass NULL to xz_dec_microlzma_reset(). A retry at the same size also skip another resize attempt. Since the global maximum was advanced, thus, the invalid state is left unrepaired. Allocate each replacement before freeing the old decoder, temporarily retaining one old decoder during allocation. Stop at the first failure and advance z_erofs_lzma_max_dictsize only after all streams satisfy the request. Record each stream's dictionary capacity so retries can skip streams already enlarged before a partial failure. Fixes: 622ceaddb764 ("erofs: lzma compression support") Cc: stable@vger.kernel.org Signed-off-by: Nikhil Gurudasani Reviewed-by: Gao Xiang Signed-off-by: Gao Xiang --- fs/erofs/decompressor_lzma.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/fs/erofs/decompressor_lzma.c b/fs/erofs/decompressor_lzma.c index 6b0cdb446c6a..9d15f94cbee1 100644 --- a/fs/erofs/decompressor_lzma.c +++ b/fs/erofs/decompressor_lzma.c @@ -5,6 +5,7 @@ struct z_erofs_lzma { struct z_erofs_lzma *next; struct xz_dec_microlzma *state; + unsigned int dict_size; u8 bounce[PAGE_SIZE]; }; @@ -128,11 +129,19 @@ static int z_erofs_load_lzma_config(struct super_block *sb, err = 0; /* 2. walk each isolated stream and grow max dict_size if needed */ for (strm = head; strm; strm = strm->next) { + struct xz_dec_microlzma *state; + + if (strm->dict_size >= dict_size) + continue; + state = xz_dec_microlzma_alloc(XZ_PREALLOC, dict_size); + if (!state) { + err = -ENOMEM; + break; + } if (strm->state) xz_dec_microlzma_end(strm->state); - strm->state = xz_dec_microlzma_alloc(XZ_PREALLOC, dict_size); - if (!strm->state) - err = -ENOMEM; + strm->state = state; + strm->dict_size = dict_size; } /* 3. push back all to the global list and update max dict_size */ @@ -142,7 +151,8 @@ static int z_erofs_load_lzma_config(struct super_block *sb, spin_unlock(&z_erofs_lzma_lock); wake_up_all(&z_erofs_lzma_wq); - z_erofs_lzma_max_dictsize = dict_size; + if (!err) + z_erofs_lzma_max_dictsize = dict_size; mutex_unlock(&lzma_resize_mutex); return err; } From 7b8a8ae4dd176a232e973017d2aa3c536a7275e2 Mon Sep 17 00:00:00 2001 From: Sourav Panda Date: Tue, 11 Aug 2026 05:29:09 +0000 Subject: [PATCH 0516/2366] mm/hugetlb_cma: fix null nodemask dereference in hugetlb_cma_alloc_frozen_folio alloc_buddy_hugetlb_folio_with_mpol() can pass a NULL nodemask to alloc_fresh_hugetlb_folio() as a fallback to allocate from all nodes. If order is gigantic, alloc_fresh_hugetlb_folio() propagates the NULL nodemask down to hugetlb_cma_alloc_frozen_folio() via alloc_gigantic_frozen_folio(). Additionally, hugetlb_cma_alloc_frozen_folio() previously attempted allocation on hugetlb_cma[nid] without verifying if nid is included in the caller's nodemask. Adding a node_isset(nid, *nodemask) check ensures the initial preferred node allocation honors the memory policy / nodemask. However, hugetlb_cma_alloc_frozen_folio() dereferences the nodemask in node_isset(nid, *nodemask) and for_each_node_mask(node, *nodemask), leading to a null pointer dereference kernel panic when nodemask is NULL. Fix this by checking if nodemask is NULL in hugetlb_cma_alloc_frozen_folio() and defaulting it to cpuset_current_mems_allowed. Enclose the allocation attempts within the cpuset seqcount retry loop so that if the cpuset changes concurrently during allocation, the attempts are retried using the updated nodemask. This ensures that the initial node check and fallback loop safely honor the task's cpuset without violating cpuset constraints or causing NULL pointer dereferences or unexpected allocation failures. From a userspace perspective, this bug allows an unprivileged user to crash the kernel (trigger a panic) by requesting a gigantic hugepage allocation with MPOL_PREFERRED_MANY on a system where CMA is only configured on a subset of NUMA nodes. This can be reproduced by booting a VM with two NUMA nodes, restricting CMA to Node 1 (e.g., hugetlb_cma=1:1G default_hugepagesz=1G hugepagesz=1G hugepages=0), and running a program that allocates a 1GB hugepage area without reserving, restricts allocation to Node 0 using mbind() with MPOL_PREFERRED_MANY, and triggers a page fault: void *ptr = mmap(NULL, 1UL << 30, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_HUGE_1GB | MAP_NORESERVE, -1, 0); unsigned long nodemask = 1; /* Node 0 */ mbind(ptr, 1UL << 30, MPOL_PREFERRED_MANY, &nodemask, sizeof(nodemask) * 8, 0); memset(ptr, 0, 1UL << 30); /* Trigger fault */ This results in a NULL pointer dereference: BUG: kernel NULL pointer dereference, address: 0000000000000000 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page Oops: Oops: 0000 [#1] SMP NOPTI RIP: 0010:hugetlb_cma_alloc_frozen_folio+0x75/0x120 Call Trace: only_alloc_fresh_hugetlb_folio.isra.0+0x2c/0x160 alloc_surplus_hugetlb_folio+0x6d/0x100 alloc_hugetlb_folio+0x3c5/0x660 hugetlb_no_page+0x3d9/0x650 Link: https://lore.kernel.org/20260811052909.475635-1-souravpanda@google.com Fixes: eb02f14c4a2b ("mm/hugetlb: allow overcommitting gigantic hugepages") Signed-off-by: Sourav Panda Reviewed-by: Muchun Song Reviewed-by: Anshuman Khandual Cc: David Hildenbrand Cc: Frank van der Linden Cc: Greg Thelen Cc: Johannes Weiner Cc: Kefeng Wang Cc: Michal Hocko Cc: Oscar Salvador Cc: Rik van Riel Cc: SeongJae Park Cc: Shakeel Butt Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- mm/hugetlb_cma.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/mm/hugetlb_cma.c b/mm/hugetlb_cma.c index db0680e82847..95fd2d190f0d 100644 --- a/mm/hugetlb_cma.c +++ b/mm/hugetlb_cma.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -55,15 +56,25 @@ struct folio *hugetlb_cma_alloc_frozen_folio(int order, gfp_t gfp_mask, int node; struct folio *folio; struct page *page = NULL; + const nodemask_t *nmask; + unsigned int cpuset_mems_cookie; if (!hugetlb_cma_size) return NULL; - if (hugetlb_cma[nid]) +retry_cpuset: + if (!nodemask) { + cpuset_mems_cookie = read_mems_allowed_begin(); + nmask = &cpuset_current_mems_allowed; + } else { + nmask = nodemask; + } + + if (hugetlb_cma[nid] && node_isset(nid, *nmask)) page = cma_alloc_frozen_compound(hugetlb_cma[nid], order); if (!page && !(gfp_mask & __GFP_THISNODE)) { - for_each_node_mask(node, *nodemask) { + for_each_node_mask(node, *nmask) { if (node == nid || !hugetlb_cma[node]) continue; @@ -73,8 +84,12 @@ struct folio *hugetlb_cma_alloc_frozen_folio(int order, gfp_t gfp_mask, } } - if (!page) + if (!page) { + if (!nodemask && + unlikely(read_mems_allowed_retry(cpuset_mems_cookie))) + goto retry_cpuset; return NULL; + } folio = page_folio(page); folio_set_hugetlb_cma(folio); From a3417097fb107cea3358b19bcbb4eb655fd67f8c Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Tue, 11 Aug 2026 13:31:55 -0700 Subject: [PATCH 0517/2366] memcg: make the v1 soft limit knob inert The v1 soft limit has been deprecated since v6.12 and nobody has reported depending on it. Start the removal by decoupling the interface from the implementation: keep memory.soft_limit_in_bytes, but ignore writes to it and always report the maximum value on read similar to what memory.kmem.limit_in_bytes already does. Writes are still parsed, so malformed input keeps returning -EINVAL. The knob now also behaves the same everywhere: it used to return -EOPNOTSUPP on PREEMPT_RT, where soft limit reclaim has always been disabled. This also fixes the syzbot report linked below. Soft limit reclaim is the only caller that runs shrink_lruvec() from kswapd against a specific memcg, so it is the only way to reach lru_gen_shrink_lruvec() and in turn set_mm_walk(), which warns when called from kswapd. Link: https://lore.kernel.org/20260811203203.3456029-2-shakeel.butt@linux.dev Signed-off-by: Shakeel Butt Reported-by: syzbot+12ee2725d5fde63a9c96@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a7a6929.b50370da.49fe0.005e.GAE@google.com/ Acked-by: Michal Hocko Cc: Axel Rasmussen Cc: Barry Song Cc: David Hildenbrand Cc: Johannes Weiner Cc: Kairui Song Cc: Lorenzo Stoakes Cc: Muchun Song Cc: Roman Gushchin Cc: Signed-off-by: Andrew Morton --- .../admin-guide/cgroup-v1/memory.rst | 49 +++---------------- mm/memcontrol-v1.c | 43 +++++++++------- 2 files changed, 32 insertions(+), 60 deletions(-) diff --git a/Documentation/admin-guide/cgroup-v1/memory.rst b/Documentation/admin-guide/cgroup-v1/memory.rst index 7db63c002922..7d2a44af52c9 100644 --- a/Documentation/admin-guide/cgroup-v1/memory.rst +++ b/Documentation/admin-guide/cgroup-v1/memory.rst @@ -47,7 +47,6 @@ Features: - pages are linked to per-memcg LRU exclusively, and there is no global LRU. - optionally, memory+swap usage can be accounted and limited. - hierarchical accounting - - soft limit - moving (recharging) account at moving a task is selectable. - usage threshold notifier - memory pressure notifier @@ -76,10 +75,9 @@ Brief summary of control files. memory.memsw.failcnt show the number of memory+Swap hits limits memory.max_usage_in_bytes show max memory usage recorded memory.memsw.max_usage_in_bytes show max memory+Swap usage recorded - memory.soft_limit_in_bytes set/show soft limit of memory usage - This knob is not available on CONFIG_PREEMPT_RT systems. - This knob is deprecated and shouldn't be - used. + memory.soft_limit_in_bytes This knob is deprecated and has no effect. + Writes are ignored and reads always + return the maximum value. memory.stat show various statistics memory.use_hierarchy set/show hierarchical account enabled This knob is deprecated and shouldn't be @@ -340,9 +338,6 @@ memory.kmem.usage_in_bytes, or in a separate counter when it makes sense. The main "kmem" counter is fed into the main counter, so kmem charges will also be visible from the user counter. -Currently no soft limit is implemented for kernel memory. It is future work -to trigger slab reclaim when those limits are reached. - 2.7.1 Current Kernel Memory resources accounted ----------------------------------------------- @@ -710,42 +705,10 @@ For compatibility reasons writing 1 to memory.use_hierarchy will always pass:: THIS IS DEPRECATED! -Soft limits allow for greater sharing of memory. The idea behind soft limits -is to allow control groups to use as much of the memory as needed, provided +Writing to memory.soft_limit_in_bytes has no effect and reading it will +always return the maximum value. -a. There is no memory contention -b. They do not exceed their hard limit - -When the system detects memory contention or low memory, control groups -are pushed back to their soft limits. If the soft limit of each control -group is very high, they are pushed back as much as possible to make -sure that one control group does not starve the others of memory. - -Please note that soft limits is a best-effort feature; it comes with -no guarantees, but it does its best to make sure that when memory is -heavily contended for, memory is allocated based on the soft limit -hints/setup. Currently soft limit based reclaim is set up such that -it gets invoked from balance_pgdat (kswapd). - -7.1 Interface -------------- - -Soft limits can be setup by using the following commands (in this example we -assume a soft limit of 256 MiB):: - - # echo 256M > memory.soft_limit_in_bytes - -If we want to change this to 1G, we can at any time use:: - - # echo 1G > memory.soft_limit_in_bytes - -.. note:: - Soft limits take effect over a long period of time, since they involve - reclaiming memory for balancing between memory cgroups - -.. note:: - It is recommended to set the soft limit always below the hard limit, - otherwise the hard limit will take precedence. +Use memory.low and memory.min in cgroup v2 instead. .. _cgroup-v1-memory-move-charges: diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c index 835fc8e51184..05ef55cae4dc 100644 --- a/mm/memcontrol-v1.c +++ b/mm/memcontrol-v1.c @@ -96,7 +96,6 @@ enum { RES_LIMIT, RES_MAX_USAGE, RES_FAILCNT, - RES_SOFT_LIMIT, }; #ifdef CONFIG_LOCKDEP @@ -1888,6 +1887,30 @@ static int mem_cgroup_hierarchy_write(struct cgroup_subsys_state *css, return -EINVAL; } +static u64 mem_cgroup_soft_limit_read(struct cgroup_subsys_state *css, + struct cftype *cft) +{ + return (u64)PAGE_COUNTER_MAX * PAGE_SIZE; +} + +static ssize_t mem_cgroup_soft_limit_write(struct kernfs_open_file *of, + char *buf, size_t nbytes, loff_t off) +{ + unsigned long nr_pages; + int ret; + + ret = page_counter_memparse(strstrip(buf), "-1", &nr_pages); + if (ret) + return ret; + + pr_warn_once("soft_limit_in_bytes is deprecated and will be removed. " + "Writing any value to this file has no effect. " + "Please report your usecase to linux-mm@kvack.org if you " + "depend on this functionality.\n"); + + return nbytes; +} + static u64 mem_cgroup_read_u64(struct cgroup_subsys_state *css, struct cftype *cft) { @@ -1924,8 +1947,6 @@ static u64 mem_cgroup_read_u64(struct cgroup_subsys_state *css, return (u64)counter->watermark * PAGE_SIZE; case RES_FAILCNT: return counter->failcnt; - case RES_SOFT_LIMIT: - return (u64)READ_ONCE(memcg->soft_limit) * PAGE_SIZE; default: BUG(); } @@ -2020,17 +2041,6 @@ static ssize_t mem_cgroup_write(struct kernfs_open_file *of, break; } break; - case RES_SOFT_LIMIT: - if (IS_ENABLED(CONFIG_PREEMPT_RT)) { - ret = -EOPNOTSUPP; - } else { - pr_warn_once("soft_limit_in_bytes is deprecated and will be removed. " - "Please report your usecase to linux-mm@kvack.org if you " - "depend on this functionality.\n"); - WRITE_ONCE(memcg->soft_limit, nr_pages); - ret = 0; - } - break; } return ret ?: nbytes; } @@ -2384,9 +2394,8 @@ struct cftype mem_cgroup_legacy_files[] = { }, { .name = "soft_limit_in_bytes", - .private = MEMFILE_PRIVATE(_MEM, RES_SOFT_LIMIT), - .write = mem_cgroup_write, - .read_u64 = mem_cgroup_read_u64, + .write = mem_cgroup_soft_limit_write, + .read_u64 = mem_cgroup_soft_limit_read, }, { .name = "failcnt", From eedc8474d469a2e88f4dc61f8cfe05c147478b43 Mon Sep 17 00:00:00 2001 From: Narek Jilavyan Date: Mon, 17 Aug 2026 10:34:33 +0000 Subject: [PATCH 0518/2366] mm/hugetlb_cgroup: call page_counter_set_max() outside VM_BUG_ON() hugetlb_cgroup_css_alloc() rounds the counter limit down to a multiple of the huge page size and then applies it inside an assertion: VM_BUG_ON(page_counter_set_max(fault, limit)); VM_BUG_ON(page_counter_set_max(rsvd, limit)); With CONFIG_DEBUG_VM=n, VM_BUG_ON(cond) is BUILD_BUG_ON_INVALID(cond), i.e. ((void)(sizeof((__force long)(cond)))), whose operand is never evaluated. page_counter_set_max() is not a predicate - it performs xchg(&counter->max, nr_pages) - so on every non-debug kernel the limit is never applied and the counters keep page_counter_init()'s PAGE_COUNTER_MAX. That is user-visible, because hugetlb_cgroup_read_u64_max() recomputes the same rounded value and uses equality as its "unlimited" sentinel. PAGE_COUNTER_MAX is LONG_MAX / PAGE_SIZE = 2251799813685247, which is odd, so round_down() really does change it and the two sides disagree. With CONFIG_DEBUG_VM=n: $ cat /sys/fs/cgroup/t/hugetlb.2MB.max 9223372036854771712 and with this patch: $ cat /sys/fs/cgroup/t/hugetlb.2MB.max max A debug option should not change cgroup output. Call the function, then assert the result, as v6.12 did. Use VM_WARN_ON_ONCE() rather than restoring VM_BUG_ON(): the two are identical under CONFIG_DEBUG_VM=n, and checkpatch asks that new code not use BUG() variants. Link: https://lore.kernel.org/20260817103433.191266-1-njilav@gmail.com Fixes: 0e2759afcaf9 ("page_counter: track failcnt only for legacy cgroups") Signed-off-by: Narek Jilavyan Reviewed-by: Muchun Song Cc: David Hildenbrand Cc: Oscar Salvador Cc: Shakeel Butt Cc: Signed-off-by: Andrew Morton --- mm/hugetlb_cgroup.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mm/hugetlb_cgroup.c b/mm/hugetlb_cgroup.c index e0083de1ca82..ecb6e0b7819a 100644 --- a/mm/hugetlb_cgroup.c +++ b/mm/hugetlb_cgroup.c @@ -97,6 +97,7 @@ static void hugetlb_cgroup_init(struct hugetlb_cgroup *h_cgroup, struct page_counter *fault, *fault_parent = NULL; struct page_counter *rsvd, *rsvd_parent = NULL; unsigned long limit; + int ret; if (parent_h_cgroup) { fault_parent = hugetlb_cgroup_counter_from_cgroup( @@ -118,8 +119,10 @@ static void hugetlb_cgroup_init(struct hugetlb_cgroup *h_cgroup, limit = round_down(PAGE_COUNTER_MAX, pages_per_huge_page(&hstates[idx])); - VM_BUG_ON(page_counter_set_max(fault, limit)); - VM_BUG_ON(page_counter_set_max(rsvd, limit)); + ret = page_counter_set_max(fault, limit); + VM_WARN_ON_ONCE(ret); + ret = page_counter_set_max(rsvd, limit); + VM_WARN_ON_ONCE(ret); } } From dc41e961a269f2ca4196e669d6d8e05480899cd4 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Mon, 17 Aug 2026 20:08:00 +0800 Subject: [PATCH 0519/2366] mm/migrate_device: avoid out-of-bounds writes for compound folios migrate_device_range() and migrate_device_pfns() clear the entries following a compound folio so that the PFN arrays retain their page-granular representation. If a compound folio extends beyond the end of the caller-provided range, the loops clear all following folio entries without limiting them to the number of slots remaining in the npages-sized array, causing an out-of-bounds write. Do not proceed with a compound folio if its page-granular representation does not fit entirely in the remaining PFN array. If this happens, drop any reference and lock acquired for the folio, clear the remaining entries, and stop collecting. Observed with a KASAN x86 QEMU kernel using the HMM migrate_anon_huge_zero selftest. Closing /dev/hmm_dmirror0 after migrating an anonymous huge page to device memory exercises: dmirror_fops_release() -> dmirror_device_evict_chunk() -> migrate_device_range() Link: https://lore.kernel.org/20260817120758.669807-3-sh_def@163.com Fixes: a30b48bf1b24 ("mm/migrate_device: implement THP migration of zone device pages") Signed-off-by: Hui Su Cc: Alistair Popple Cc: Balbir Singh Cc: Byungchul Park Cc: David Hildenbrand Cc: Gregory Price Cc: "Huang, Ying" Cc: Joshua Hahn Cc: Matthew Brost Cc: Rakie Kim Cc: Zi Yan Cc: Signed-off-by: Andrew Morton --- mm/migrate_device.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mm/migrate_device.c b/mm/migrate_device.c index 762c5cee8fec..009bfa8b212d 100644 --- a/mm/migrate_device.c +++ b/mm/migrate_device.c @@ -1423,6 +1423,15 @@ int migrate_device_range(unsigned long *src_pfns, unsigned long start, src_pfns[i] = migrate_device_pfn_lock(pfn); nr = folio_nr_pages(folio); + if (nr > npages - i) { + if (src_pfns[i] & MIGRATE_PFN_MIGRATE) { + folio_unlock(folio); + folio_put(folio); + } + memset(&src_pfns[i], 0, + (npages - i) * sizeof(*src_pfns)); + break; + } if (nr > 1) { src_pfns[i] |= MIGRATE_PFN_COMPOUND; for (j = 1; j < nr; j++) @@ -1457,6 +1466,15 @@ int migrate_device_pfns(unsigned long *src_pfns, unsigned long npages) src_pfns[i] = migrate_device_pfn_lock(src_pfns[i]); nr = folio_nr_pages(folio); + if (nr > npages - i) { + if (src_pfns[i] & MIGRATE_PFN_MIGRATE) { + folio_unlock(folio); + folio_put(folio); + } + memset(&src_pfns[i], 0, + (npages - i) * sizeof(*src_pfns)); + break; + } if (nr > 1) { src_pfns[i] |= MIGRATE_PFN_COMPOUND; for (j = 1; j < nr; j++) From 267bede12d3b108ca29997ce280e927a570ec97f Mon Sep 17 00:00:00 2001 From: Longlong Xia Date: Fri, 14 Aug 2026 16:30:27 +0800 Subject: [PATCH 0520/2366] mm/hugetlb: keep max_huge_pages when dissolving surplus folios dissolve_free_hugetlb_folio() can remove a free folio as surplus when its node has surplus pages. In that case remove_hugetlb_folio() decrements both nr_huge_pages and surplus_huge_pages, leaving the persistent pool size unchanged. Updating max_huge_pages as if a persistent folio had been removed can therefore corrupt the persistent pool target and underflow it when max_huge_pages is zero. Keep max_huge_pages unchanged for surplus folios, including the vmemmap restoration rollback path. Link: https://lore.kernel.org/20260814083027.1419487-1-xialonglong2025@163.com Fixes: cb402bbdabca ("mm/hugetlb: fix surplus pages in dissolve_free_huge_page()") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Longlong Xia Reviewed-by: Muchun Song Cc: David Hildenbrand Cc: Jinjiang Tu Cc: Longlong Xia Cc: Oscar Salvador Cc: Signed-off-by: Andrew Morton --- mm/hugetlb.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 785772845795..885017e26fd4 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -1992,7 +1992,8 @@ int dissolve_free_hugetlb_folio(struct folio *folio) if (h->surplus_huge_pages_node[folio_nid(folio)]) adjust_surplus = true; remove_hugetlb_folio(h, folio, adjust_surplus); - h->max_huge_pages--; + if (!adjust_surplus) + h->max_huge_pages--; spin_unlock_irq(&hugetlb_lock); /* @@ -2012,7 +2013,8 @@ int dissolve_free_hugetlb_folio(struct folio *folio) if (rc) { spin_lock_irq(&hugetlb_lock); add_hugetlb_folio(h, folio, adjust_surplus); - h->max_huge_pages++; + if (!adjust_surplus) + h->max_huge_pages++; goto out; } } else { From 2fd4e7693674b17807a6d082feb01a3fbf86f5f8 Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Tue, 18 Aug 2026 10:47:26 +0800 Subject: [PATCH 0521/2366] mm: fix incorrect vm_flags usage when checking allowable orders for tmpfs Lance reported that when nothing else causes the mm to be considered for khugepaged collapse, an MADV_HUGEPAGE-advised tmpfs VMA alone does not trigger scanning. After commit 6beeab870e70 ("mm: shmem: move shmem_huge_global_enabled() into shmem_allowable_huge_orders()"), the shmem/tmpfs allowable order check reads vma->flags directly. However, when MADV_HUGEPAGE is handled, khugepaged_enter_vma() is called before the VMA's flags have been updated, so the check uses stale flags and incorrectly rejects the VMA for collapse. As a result, khugepaged does not collapse the tmpfs file into PMD order in time. Fix this by calling khugepaged_enter_vma() with the new VMA flags in madvise_update_vma(). Meanwhile we can remove the khugepaged_enter_vma() in hugepage_madvise(). Link: https://lore.kernel.org/7d5b5eb27be798f89d563b06254c947ff53db0b2.1787020910.git.baolin.wang@linux.alibaba.com Fixes: 6beeab870e70 ("mm: shmem: move shmem_huge_global_enabled() into shmem_allowable_huge_orders()") Signed-off-by: Baolin Wang Reported-by: Lance Yang Closes: https://lore.kernel.org/all/20260815181632.21453-1-lance.yang@linux.dev/ Suggested-by: Lorenzo Stoakes (ARM) Reviewed-by: Zi Yan Reviewed-by: Lorenzo Stoakes (ARM) Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Hugh Dickins Cc: Lance Yang Cc: Liam R. Howlett Cc: Ryan Roberts Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- mm/khugepaged.c | 6 ------ mm/madvise.c | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/mm/khugepaged.c b/mm/khugepaged.c index 11ff98d55c76..75639298efc2 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -454,12 +454,6 @@ int hugepage_madvise(struct vm_area_struct *vma, case MADV_HUGEPAGE: *vm_flags &= ~VM_NOHUGEPAGE; *vm_flags |= VM_HUGEPAGE; - /* - * If the vma become good for khugepaged to scan, - * register it here without waiting a page fault that - * may not happen any time soon. - */ - khugepaged_enter_vma(vma, *vm_flags); break; case MADV_NOHUGEPAGE: *vm_flags &= ~VM_HUGEPAGE; diff --git a/mm/madvise.c b/mm/madvise.c index 96f2387b2f46..eeee82cf2b3f 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -178,6 +178,14 @@ static int madvise_update_vma(vm_flags_t new_flags, /* vm_flags is protected by the mmap_lock held in write mode. */ vma_start_write(vma); vma->flags = new_vma_flags; + /* + * If the vma become good for khugepaged to scan, + * register it here without waiting a page fault that + * may not happen any time soon. + */ + if (vma_flags_test(&new_vma_flags, VMA_HUGEPAGE_BIT)) + khugepaged_enter_vma(vma, vma_flags_to_legacy(new_vma_flags)); + if (set_new_anon_name) return replace_anon_vma_name(vma, anon_name); From f025ca73decda1f895a4b80b961d3bc88825298a Mon Sep 17 00:00:00 2001 From: Bryan Lim Date: Wed, 19 Aug 2026 10:08:24 +0700 Subject: [PATCH 0522/2366] userfaultfd: reset err to be 0 when move_pages_ptes succeeded During move_pages() operation, when move_pages_ptes() returns EAGAIN, the error code is not cleared even after we processed it. This leads to a successful retry but then the same pages are retried again due to the stale error code. This time move fails because pages are already moved, loop is terminated and move_pages() reports a failure. Clear the error code once we processes EAGAIN. Link: https://lore.kernel.org/e1e0b5f8-c3c6-0537-670b-4397f822f980@gmail.com Fixes: 50944692052b ("userfaultfd: opportunistic TLB-flush batching for present pages in MOVE") Assisted-by: ChatGPT:GPT-5.6-Luna Signed-off-by: Bryan Lim Reviewed-by: Suren Baghdasaryan Acked-by: Mike Rapoport (Microsoft) Cc: Peter Xu Cc: Signed-off-by: Andrew Morton --- mm/userfaultfd.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c index 23fb68fce000..74f04c323c50 100644 --- a/mm/userfaultfd.c +++ b/mm/userfaultfd.c @@ -2171,8 +2171,10 @@ static ssize_t move_pages(struct userfaultfd_ctx *ctx, unsigned long dst_start, } if (err) { - if (err == -EAGAIN) + if (err == -EAGAIN) { + err = 0; continue; + } break; } From 6e0803a170552a6ab48538721df6467582fc940c Mon Sep 17 00:00:00 2001 From: Lance Yang Date: Thu, 20 Aug 2026 09:45:35 +0800 Subject: [PATCH 0523/2366] MAINTAINERS: add Lance Yang as a hung task detector co-maintainer I've been a hung_task reviewer for over a year now and plan to stay involved. Take on more responsibility for hung_task as a co-maintainer. Link: https://lore.kernel.org/20260820014535.79105-1-lance.yang@linux.dev Signed-off-by: Lance Yang Acked-by: Petr Mladek Cc: "Masami Hiramatsu (Google)" Signed-off-by: Andrew Morton --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 4dfc1fb14ef7..00843d667a4f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12221,7 +12221,7 @@ F: drivers/tty/hvc/ HUNG TASK DETECTOR M: Andrew Morton -R: Lance Yang +M: Lance Yang R: Masami Hiramatsu R: Petr Mladek L: linux-kernel@vger.kernel.org From fe6cf984939d8e12cb33a99673c8d026c5135e68 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Wed, 19 Aug 2026 03:12:22 -0700 Subject: [PATCH 0524/2366] mm/huge_memory: transfer the pmd dirty bit to the folio on zap zap_huge_pmd_folio() propagates the pmd young bit to the folio for the file case, but not the dirty bit. The pte path does propagate it, in zap_present_folio_ptes() and so does the pmd split path, in __split_huge_pmd_locked(). For most file mappings the omission is harmless, because writing to a shared file mapping goes through page_mkwrite(), which dirties the folio. tmpfs is different: it has no page_mkwrite(), and vma_wants_writenotify() is false for it, so a *read* fault on a MAP_SHARED tmpfs mapping installs a writable pmd via do_read_fault(). do_read_fault() does not call fault_dirty_shared_page(), so subsequent stores through that mapping set only the hardware dirty bit in the pmd and never call folio_mark_dirty(). A shmem folio allocated by a fault is marked uptodate but not dirty (see the clear: block in shmem_get_folio_gfp()), so PG_dirty is never set at all. Unmapping such a folio - munmap(), or exit_mmap() when the process dies - then loses the only record that it was written, because zap_huge_pmd() drops the pmd without transferring the dirty bit. Reclaim afterwards sees a clean shmem folio: the whole swap-out block in shrink_folio_list() is inside "if (folio_test_dirty(folio))", so pageout() is skipped and the folio falls into __remove_mapping(). There, folio_is_file_lru() is false for a swapbacked folio, so no shadow entry is created and __filemap_remove_folio(folio, NULL) simply empties the i_pages slot. The data is freed without ever being written to swap, and the next fault on that index returns a freshly zeroed folio. This is silent data loss for any process that keeps state in a MAP_SHARED tmpfs segment across an unmap - for example a cache handed from one process generation to the next through /dev/shm. It requires the folio to be PMD-mapped, so it only shows up once shmem THP is enabled (which is what we did in Meta fleet and started noticing crashes); with THP off the pte path transfers the dirty bit correctly. It also only becomes visible when swap is enabled, because with no swap device shmem folios (which are on the anon LRU) are not scanned by reclaim at all, so the clean folio is never dropped. Reproduced on x86_64 with a tmpfs mounted huge=within_size: read-fault a 2MB-backed region, write a known pattern through the resulting mapping, munmap, force reclaim of the cgroup, then re-map and read back. Without this patch the region reads back as zeros and vmstat shows zswpout 0 - the data was discarded rather than swapped. With this patch the region reads back correctly and the pages are swapped out as expected. With huge=never, or when the first touch is a write, the test passes either way. Link: https://lore.kernel.org/20260819101222.3732660-1-usama.arif@linux.dev Fixes: b5072380eb61 ("thp: support file pages in zap_huge_pmd()") Signed-off-by: Usama Arif Acked-by: David Hildenbrand (Arm) Reviewed-by: Kiryl Shutsemau Acked-by: Hugh Dickins Tested-by: Lance Yang Reviewed-by: Zi Yan Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Baolin Wang Cc: Barry Song Cc: Dev Jain Cc: Johannes Weiner Cc: Liam R. Howlett Cc: Nhat Pham Cc: Rik van Riel Cc: Ryan Roberts Cc: Shakeel Butt Cc: Signed-off-by: Andrew Morton --- mm/huge_memory.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index ced400f72d43..afbb5974bd22 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -2449,6 +2449,8 @@ static void zap_huge_pmd_folio(struct mm_struct *mm, struct vm_area_struct *vma, add_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR); + if (is_present && pmd_dirty(pmdval)) + folio_mark_dirty(folio); if (is_present && pmd_young(pmdval) && likely(vma_has_recency(vma))) folio_mark_accessed(folio); From 540e583b66d6402bf556fde5e53c817a54c1afe5 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 21 Aug 2026 17:04:07 +0000 Subject: [PATCH 0525/2366] mm/mempolicy: fix sleeping allocation in alloc_pages_bulk_weighted_interleave() syzbot reported a sleeping function called from invalid context splat in bucket_table_alloc(). When rhashtable_insert_slow() rehashes the table under rcu_read_lock(), it calls bucket_table_alloc(..., GFP_ATOMIC | __GFP_NOWARN). If the bucket table allocation uses vmalloc, __vmalloc_node_range_noprof() invokes vm_area_alloc_pages() -> alloc_pages_bulk_mempolicy_noprof() with the passed GFP_ATOMIC flags. If the current task has an MPOL_WEIGHTED_INTERLEAVE mempolicy, alloc_pages_bulk_weighted_interleave() is called and currently hardcodes GFP_KERNEL when allocating the temporary weights array, triggering a might_alloc() splat in atomic/RCU contexts. Pass the gfp flags (masked with GFP_RECLAIM_MASK to strip page-allocator zone modifiers like __GFP_HIGHMEM) received by alloc_pages_bulk_weighted_interleave() to kmalloc() instead of hardcoding GFP_KERNEL. Since the weights buffer is immediately initialized in full, kmalloc() is sufficient. Link: https://lore.kernel.org/20260821170407.3721004-1-edumazet@google.com Fixes: fa3bea4e1f82 ("mm/mempolicy: introduce MPOL_WEIGHTED_INTERLEAVE for weighted interleaving") Signed-off-by: Eric Dumazet Reported-by: syzbot+0dbf6d295b3350944f0b@syzkaller.appspotmail.com Closes: https://lore.kernel.org/lkml/6a88837e.ae6ddae5.3da009.0040.GAE@google.com/T/#u Reviewed-by: Andrew Morton Reviewed-by: Gregory Price (Meta) Acked-by: David Hildenbrand (Arm) Cc: Alistair Popple Cc: Byungchul Park Cc: "Huang, Ying" Cc: Joshua Hahn Cc: Matthew Brost Cc: Rakie Kim Cc: Zi Yan Cc: Signed-off-by: Andrew Morton --- mm/mempolicy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/mempolicy.c b/mm/mempolicy.c index 3498a5651d50..79053ece02cd 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -2679,7 +2679,7 @@ static unsigned long alloc_pages_bulk_weighted_interleave(gfp_t gfp, prev_node = node; /* create a local copy of node weights to operate on outside rcu */ - weights = kzalloc(nr_node_ids, GFP_KERNEL); + weights = kmalloc(nr_node_ids, gfp & GFP_RECLAIM_MASK); if (!weights) return total_allocated; From 8ee1ef0f2f8ce29338f4ab00a3d344c010208058 Mon Sep 17 00:00:00 2001 From: Wupeng Ma Date: Tue, 7 Jul 2026 19:02:54 +0800 Subject: [PATCH 0526/2366] mm/hugetlb: fix missing migratable flag on same-node hugetlb migration Commit ba23f58de896 ("mm/migrate: don't call folio_putback_active_hugetlb() on dst hugetlb folio") moved setting of the migratable flag and active-list placement from folio_putback_active_hugetlb(dst) into move_hugetlb_state(), so that the freshly allocated destination folio is handled where allocation is known to have succeeded. Unfortunately, the new code was appended after the existing temporary-folio block in move_hugetlb_state(), which contains an early return added earlier by commit 5af1ab1d24e08 ("mm/hugetlb: optimize the surplus state transfer code in move_hugetlb_state()"): if (folio_test_hugetlb_temporary(new_folio)) { ... if (new_nid == old_nid) return; <-- skips the new code ... } /* added by ba23f58 */ folio_set_hugetlb_migratable(new_folio); list_move_tail(&new_folio->lru, ...&h->hugepage_activelist); When the destination folio is temporary (i.e. the hugetlb pool was exhausted and the migration callback fell back to alloc_migrate_hugetlb_folio()) and the migration does not cross a node -- the common case, and always true on a single-NUMA system -- move_hugetlb_state() returns before setting the migratable flag or adding the new folio to the active list. The destination folio is then installed in the page table but cannot be isolated afterwards, since folio_isolate_hugetlb() rejects folios without the migratable flag; a subsequent soft-offline, hard-offline or memory-hotplug offline of that folio fails with -EBUSY. This was reproduced on a single-NUMA arm64 VM: a second MADV_SOFT_OFFLINE on an already-migrated hugetlb page returned EBUSY and logged "hugepage isolation failed". Keep the surplus adjustment, which is the only part that depends on the node crossing, guarded by `if (new_nid != old_nid)', while making the migratable flag and active-list placement unconditional. This preserves the cleanup intent of ba23f58 and closes the early-return hole. Link: https://lore.kernel.org/20260707110254.3147686-1-mawupeng1@huawei.com Fixes: ba23f58de896 ("mm/migrate: don't call folio_putback_active_hugetlb() on dst hugetlb folio") Signed-off-by: Wupeng Ma Acked-by: David Hildenbrand (Arm) Cc: Baolin Wang Cc: Muchun Song Cc: Oscar Salvador Cc: Signed-off-by: Andrew Morton --- mm/hugetlb.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 885017e26fd4..4f6f58bf3db6 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -7332,14 +7332,14 @@ void move_hugetlb_state(struct folio *old_folio, struct folio *new_folio, * There is no need to transfer the per-node surplus state * when we do not cross the node. */ - if (new_nid == old_nid) - return; - spin_lock_irq(&hugetlb_lock); - if (h->surplus_huge_pages_node[old_nid]) { - h->surplus_huge_pages_node[old_nid]--; - h->surplus_huge_pages_node[new_nid]++; + if (new_nid != old_nid) { + spin_lock_irq(&hugetlb_lock); + if (h->surplus_huge_pages_node[old_nid]) { + h->surplus_huge_pages_node[old_nid]--; + h->surplus_huge_pages_node[new_nid]++; + } + spin_unlock_irq(&hugetlb_lock); } - spin_unlock_irq(&hugetlb_lock); } /* From 0ba6912f7e974045dcdd170f022cba19247e00bc Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 25 Aug 2026 14:25:15 +0000 Subject: [PATCH 0527/2366] Revert "once: don't use a work queue to reset sleepable static key" This reverts commit e8eef69a99f185e75909adb24ab93d706e07bf27. While DO_ONCE_SLEEPABLE() is used from sleepable/process context, callers may still be holding arbitrary subsystem locks. For instance, __inet_hash_connect() uses get_random_sleepable_once() which invokes DO_ONCE_SLEEPABLE() while holding the socket lock (sk_lock): lock_sock(sk) __inet_hash_connect() get_random_sleepable_once() DO_ONCE_SLEEPABLE() __do_once_sleepable_done() static_branch_disable() static_key_disable() cpus_read_lock() Calling static_branch_disable() directly from __do_once_sleepable_done() causes static_key_disable() to synchronously acquire cpus_read_lock() (cpu_hotplug_lock) and jump_label_mutex inside the caller's lock context. This introduces an unwanted lockdep dependency: sk_lock -> cpu_hotplug_lock Because cpu_hotplug_lock depends on fs_reclaim (via workqueue CPU bringup allocating memory with GFP_KERNEL), and storage/block layers (such as NVMe-TCP) acquire sk_lock during I/O dispatch, lockdep reports circular locking dependencies: set->srcu -> sk_lock -> cpu_hotplug_lock -> fs_reclaim -> q_usage_counter -> elevator_lock -> set->srcu This false positive previously prompted commit 19bdb70c77d3 ("nvme-tcp: lockdep: use dynamic lockdep keys per socket instance") to work around the warning using per-socket dynamic keys in NVMe-TCP. That in turn broke asynchronous socket teardown and caused syzbot warnings in tcp_tsq_handler(). Restoring once_disable_jump() in __do_once_sleepable_done() ensures that static_branch_disable() is executed asynchronously from a system workqueue without holding the caller's locks. Link: https://lore.kernel.org/20260825142515.1965654-1-edumazet@google.com Fixes: e8eef69a99f1 ("once: don't use a work queue to reset sleepable static key") Signed-off-by: Eric Dumazet Closes: https://lore.kernel.org/lkml/ao0mwtt8ePAINFni@shinhome/ Reported-by: Shin'ichiro Kawasaki Cc: Tony Luck Cc: Reinette Chatre Cc: Keith Busch Cc: Nilay Shroff Cc: Signed-off-by: Andrew Morton --- lib/once.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/once.c b/lib/once.c index d801bfa945e6..0a0a919156e0 100644 --- a/lib/once.c +++ b/lib/once.c @@ -93,6 +93,6 @@ void __do_once_sleepable_done(bool *done, struct static_key_true *once_key, { *done = true; mutex_unlock(&once_mutex); - static_branch_disable(once_key); + once_disable_jump(once_key, mod); } EXPORT_SYMBOL(__do_once_sleepable_done); From 627824f20f237902e696efe5b563c18422443370 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 26 Aug 2026 10:56:39 +0100 Subject: [PATCH 0528/2366] MAINTAINERS: remove Lorenzo as THP co-maintainer Unfortunately my workload is such that I simply no longer have the time to give THP the focus that it deserves. So, at least temporarily, step down from the role. Link: https://lore.kernel.org/20260826-drop-thp-maintainership-v1-1-3d102748fa17@kernel.org Signed-off-by: Lorenzo Stoakes (ARM) Cc: Baolin Wang Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Lance Yang Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Ryan Roberts Cc: Zi Yan Cc: Kiryl Shutsemau Signed-off-by: Andrew Morton --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 00843d667a4f..85a2983e430a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17412,7 +17412,6 @@ F: mm/swapfile.c MEMORY MANAGEMENT - THP (TRANSPARENT HUGE PAGE) M: Andrew Morton M: David Hildenbrand -M: Lorenzo Stoakes R: Zi Yan R: Baolin Wang R: Liam R. Howlett From 341b9b4f8f540fc03e928e67351e4be9461bc56d Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 25 Aug 2026 18:49:27 +0200 Subject: [PATCH 0529/2366] MAINTAINERS: mailmap: update entries for Thorsten Blum Map my previously used email addresses to blum@kernel.org. Link: https://lore.kernel.org/20260825164933.105605-2-blum@kernel.org Signed-off-by: Thorsten Blum Cc: Jakub Kacinski Cc: Martin Kepplinger Signed-off-by: Andrew Morton --- .mailmap | 3 ++- MAINTAINERS | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.mailmap b/.mailmap index 6803f3bd2865..9dc7096b79f7 100644 --- a/.mailmap +++ b/.mailmap @@ -898,7 +898,8 @@ Thomas Graf Thomas Gleixner Thomas Körper Thomas Pedersen -Thorsten Blum +Thorsten Blum +Thorsten Blum Tiezhu Yang Tingwei Zhang Tirupathi Reddy diff --git a/MAINTAINERS b/MAINTAINERS index 85a2983e430a..85cc77fe75b7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17693,7 +17693,7 @@ F: Documentation/devicetree/bindings/serial/atmel,at91-usart.yaml F: drivers/spi/spi-at91-usart.c MICROCHIP ATSHA204A DRIVER -M: Thorsten Blum +M: Thorsten Blum L: linux-crypto@vger.kernel.org S: Maintained F: drivers/crypto/atmel-sha204a.c @@ -17717,7 +17717,7 @@ F: Documentation/devicetree/bindings/media/microchip,csi2dc.yaml F: drivers/media/platform/microchip/microchip-csi2dc.c MICROCHIP ECC DRIVER -M: Thorsten Blum +M: Thorsten Blum L: linux-crypto@vger.kernel.org S: Maintained F: drivers/crypto/atmel-ecc.c From dc3565a4ae538e584e5e63b3b3cd1eaf502593c1 Mon Sep 17 00:00:00 2001 From: Hajo Noerenberg Date: Mon, 31 Aug 2026 14:43:03 +0200 Subject: [PATCH 0530/2366] ata: ahci: work around lost interrupts on Marvell 88SE61xx ahci_single_level_irq_intr() services the ports first and clears the global HOST_IRQ_STAT afterwards, as recommended by AHCI 1.1 section 10.6.2. The Marvell 88SE6111/6121/6145 family stops reporting interrupts for a port when HOST_IRQ_STAT is cleared while PxIS still holds bits: PxIS keeps its content, HOST_IRQ_STAT reads back as 0, the port is never looked at again, and the command in flight only ends in a timeout. Measured on a Seagate Blackarmor NAS440 (Marvell 88F6281 Kirkwood, 88SE6121 rev B2 behind PCIe) by polling the AHCI registers from userspace while an IDENTIFY was outstanding: t=303.046 irqs 127 PxIS 0x00000000 PxCI 0x00000001 IDENTIFY issued t=303.057 irqs 128 PxIS 0x00000020 PxCI 0x00000000 CI cleared, DPS set, one interrupt taken ... PxIS stays 0x00000020, HOST_IRQ_STAT stays 0 ... t~308.05 qc timeout after 5000 msecs The command had completed - PxCI was clear and PxIS had DPS set - so ahci_qc_complete() would have completed it. It never got the chance because the handler read HOST_IRQ_STAT as 0 and returned IRQ_NONE. Marvell's own driver for these chips clears the two registers in the opposite order and says so ("clear global before channel"), and ahci_xgene handles its broken edge latch the same way. Since the reordering costs at most one spurious interrupt per valid one on conforming controllers, do it in a private interrupt handler selected for board_ahci_mv instead of changing libahci for everyone. With this applied, SATA-2 and SATA-3 disks work at 3.0 Gbps on the 88SE6121 without the drive-side 1.5 Gbps jumper that was needed before. Time from link up to a successful IDENTIFY: WDC WD5000AADS-00S9B0 port 0 7 ms (never identified before) WDC WD3202ABYS-01B7A0 port 1 28 ms WDC WD30EFRX-68EUZN0 port 1 200 ms (3 TB, HPA detection ok) Only the 88SE6121 was tested; board_ahci_mv also covers the 88SE6145, which Marvell's driver treats identically. Fixes: cd70c26617f4 ("[libata] AHCI: Add support for Marvell AHCI-like chips (initially 6145)") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/linux-ide/db6b48b7-d69a-564b-24f0-75fbd6a9e543@noerenberg.de/ Link: https://bugzilla.kernel.org/show_bug.cgi?id=216094 Signed-off-by: Hajo Noerenberg Reviewed-by: Damien Le Moal Acked-by: Pali Rohar Link: https://lore.kernel.org/r/20260831124303.920391-1-hajo-linux-ide@noerenberg.de Signed-off-by: Niklas Cassel --- drivers/ata/ahci.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c index 58f512f8952a..9b8c0935001c 100644 --- a/drivers/ata/ahci.c +++ b/drivers/ata/ahci.c @@ -2614,6 +2614,51 @@ static irqreturn_t ahci_thunderx_irq_handler(int irq, void *dev_instance) } #endif +/* + * The Marvell 88SE6111/6121/6145 ("Thor") family stops reporting interrupts + * for a port when HOST_IRQ_STAT is cleared while PxIS still holds bits: PxIS + * keeps its content, HOST_IRQ_STAT reads back as 0, the port is never looked + * at again and the command in flight only ends in a timeout. On a 88SE6121 + * this makes every SATA-2 or SATA-3 disk fail to IDENTIFY, while SATA-1 disks + * happen to win the race often enough to work. + * + * Clearing the host status before servicing the ports avoids it. Marvell's + * own driver for these chips does the same and says so ("clear global before + * channel"), and ahci_xgene handles its broken edge latch the same way. The + * price is at most one spurious interrupt per valid one, which is why this is + * not the generic behaviour - see AHCI 1.1 section 10.6.2. + * + * Link: https://bugzilla.kernel.org/show_bug.cgi?id=216094 + */ +static irqreturn_t ahci_mv_irq_handler(int irq, void *dev_instance) +{ + struct ata_host *host = dev_instance; + struct ahci_host_priv *hpriv = host->private_data; + void __iomem *mmio = hpriv->mmio; + unsigned int rc; + u32 irq_stat, irq_masked; + + irq_stat = readl(mmio + HOST_IRQ_STAT); + if (!irq_stat) + return IRQ_NONE; + + irq_masked = irq_stat & hpriv->port_map; + + spin_lock(&host->lock); + + /* + * Use the unmasked value to clear the interrupt, as a spurious pending + * event on a dummy port might cause a screaming IRQ. + */ + writel(irq_stat, mmio + HOST_IRQ_STAT); + + rc = ahci_handle_port_intr(host, irq_masked); + + spin_unlock(&host->lock); + + return IRQ_RETVAL(rc); +} + static void ahci_remap_check(struct pci_dev *pdev, int bar, struct ahci_host_priv *hpriv) { @@ -2917,6 +2962,10 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) return -ENOMEM; hpriv->flags |= (unsigned long)pi.private_data; + /* the Marvell "Thor" family needs HOST_IRQ_STAT cleared first */ + if (board_id == board_ahci_mv) + hpriv->irq_handler = ahci_mv_irq_handler; + /* MCP65 revision A1 and A2 can't do MSI */ if (board_id == board_ahci_mcp65 && (pdev->revision == 0xa1 || pdev->revision == 0xa2)) From 82cfab6a52621febf36752bd5ed5184f82c96e07 Mon Sep 17 00:00:00 2001 From: Jonne Vuorela Date: Tue, 1 Sep 2026 18:28:27 +0000 Subject: [PATCH 0531/2366] ALSA: hda/cs420x: Add CS4208 fixup for MacBookAir 7,2 The MacBookAir 7,2 HDA controller has PCI subsystem 8086:7270 rather than Apple 0x106b, so no fixup is selected and all pins default to 0x400000f0. The pin wiring matches MBA6, so add the subsystem ID to both lookup tables pointing to CS4208_MBA6. Tested on MacBookAir 7,2. [ sorted table entries in SSID order -- tiwai ] Signed-off-by: Jonne Vuorela Link: https://patch.msgid.link/Qcui5livawCaJbO1jfx_jebS64rR0f9KATkqn8May0pPyLw8U5DqPQBigmSzRxJp-GeMPqs-jAK6PCnagk3bspzmH__YPte-eyvPtFtuolo=@proton.me Signed-off-by: Takashi Iwai --- sound/hda/codecs/cirrus/cs420x.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/codecs/cirrus/cs420x.c b/sound/hda/codecs/cirrus/cs420x.c index 85c2ecf46d38..6cba01228c27 100644 --- a/sound/hda/codecs/cirrus/cs420x.c +++ b/sound/hda/codecs/cirrus/cs420x.c @@ -571,6 +571,7 @@ static const struct hda_model_fixup cs4208_models[] = { static const struct hda_quirk cs4208_fixup_tbl[] = { SND_PCI_QUIRK_VENDOR(0x106b, "Apple", CS4208_MAC_AUTO), + SND_PCI_QUIRK(0x8086, 0x7270, "MacBookAir 7,2", CS4208_MAC_AUTO), {} /* terminator */ }; @@ -583,6 +584,7 @@ static const struct hda_quirk cs4208_mac_fixup_tbl[] = { SND_PCI_QUIRK(0x106b, 0x7800, "MacPro 6,1", CS4208_MACMINI), SND_PCI_QUIRK(0x106b, 0x7b00, "MacBookPro 12,1", CS4208_MBP11), SND_PCI_QUIRK(0x106b, 0x7f00, "iMac 16,1", CS4208_MBP11), + SND_PCI_QUIRK(0x8086, 0x7270, "MacBookAir 7,2", CS4208_MBA6), {} /* terminator */ }; From 9012da455ab9a05d8205b90d0ad7c8b526f89062 Mon Sep 17 00:00:00 2001 From: Chenguang Zhao Date: Thu, 6 Aug 2026 17:10:45 +0800 Subject: [PATCH 0532/2366] net: 6lowpan: fix mismatched comments Rename @_nexthdrlen to @_hdrlen and drop stale @nhc from lowpan_nhc_do_uncompression docs Signed-off-by: Chenguang Zhao Reviewed-by: Simon Horman Link: https://lore.kernel.org/20260806091045.1701326-1-chenguang.zhao@linux.dev Signed-off-by: Stefan Schmidt --- net/6lowpan/nhc.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/6lowpan/nhc.h b/net/6lowpan/nhc.h index ab7b4977c32b..c995029696d0 100644 --- a/net/6lowpan/nhc.h +++ b/net/6lowpan/nhc.h @@ -15,7 +15,7 @@ * @__nhc: variable name of the lowpan_nhc struct. * @_name: const char * of common header compression name. * @_nexthdr: ipv6 nexthdr field for the header compression. - * @_nexthdrlen: ipv6 nexthdr len for the reserved space. + * @_hdrlen: ipv6 nexthdr len for the reserved space. * @_id: one byte nhc id value. * @_idmask: one byte nhc id mask value. * @_uncompress: callback for uncompression call. @@ -102,7 +102,6 @@ int lowpan_nhc_do_compression(struct sk_buff *skb, const struct ipv6hdr *hdr, /** * lowpan_nhc_do_uncompression - calling uncompress callback for nhc * - * @nhc: 6LoWPAN nhc context, get by lowpan_nhc_by_ functions. * @skb: skb of 6LoWPAN header, skb->data should be pointed to nhc id value. * @dev: netdevice for print logging information. * @hdr: ipv6hdr for setting nexthdr value. From 29871903f3a38e75e896f9a644713859490cb85f Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Tue, 1 Sep 2026 20:43:47 +0800 Subject: [PATCH 0533/2366] sched_ext: Don't deliver duplicate ops.cgroup_set_idle() for same value ops.cgroup_set_idle() is documented to be invoked when a cgroup transitions between idle and non-idle states, and scx_group_set_weight() already skips value-preserving writes. scx_group_set_idle() delivers every write unconditionally, so rewriting an already-correct cpu.idle value feeds the BPF scheduler a transition callback each time, which toggle- or accounting-based schedulers miscount. Mirror the weight guard and only deliver on an actual change. Verified with a probe scheduler printing each callback: rewriting cpu.idle=1 twice on an already-idle cgroup delivered two callbacks before and none after. Fixes: 347ed2d566da ("sched/ext: Implement cgroup_set_idle() callback") Link: https://lore.kernel.org/r/b53c61a1-4d7d-4232-941f-d48b0563d4ed@linux.dev Signed-off-by: Tao Cui Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 901fb0f8b976..5f242ab69cf4 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -4933,7 +4933,8 @@ void scx_group_set_idle(struct task_group *tg, bool idle) percpu_down_read(&scx_cgroup_ops_rwsem); sch = scx_tg_knob_sched(tg); - if (scx_cgroup_enabled && sch && SCX_HAS_OP(sch, cgroup_set_idle)) + if (scx_cgroup_enabled && sch && SCX_HAS_OP(sch, cgroup_set_idle) && + tg->scx.idle != idle) SCX_CALL_OP(sch, cgroup_set_idle, NULL, tg_cgrp(tg), idle); /* Update the task group's idle state */ From 1719d035a6fa90b7467b6daf45a573f5180013b2 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Sat, 22 Aug 2026 18:59:30 +0800 Subject: [PATCH 0534/2366] sched/fair: Use update_curr_eevdf() for the remaining root cfs_rq callers pick_task_fair() and yield_task_fair() call update_curr(&rq->cfs) to bring curr up to date before they look at the eevdf state. With cgroups that does not happen: update_curr() reads ->h_curr, which on the root cfs_rq is the top level group entity, and returns at the !entity_is_task() check before touching vruntime. Both then read ->curr, so the guard and the update disagree about which entity they mean. Counting how often ->h_curr and ->curr differ at pick_task_fair(), on one CPU for 10s with three busy tasks and one 200us-periodic task: all tasks in the root cgroup 43321 calls, 0 no-ops busy tasks in G0, periodic in G1 45211 calls, 45193 no-ops Whether that matters depends on what precedes the pick. Since commit 68e37487810a ("sched/fair: Fix flat hierarchy") the tick and enqueue/dequeue all update curr correctly, so on the normal reschedule path only the microseconds between those and the pick are missing, and I could not measure a latency difference there. Three paths have nothing before them on that rq though: - pick_task() on the sibling rqs of a core under core scheduling (kernel/sched/core.c), which updates that rq's clock first for exactly this reason - fair_server_pick_task() - yield_task_fair(), where the stale value feeds the entity_eligible() test that guards forfeiting the remaining vruntime There curr can be a full tick behind, as it was before that commit. No new behaviour for the entity being updated: without cgroups ->h_curr is already the task, so these two call sites already run the full update_curr() including update_deadline(), dl_server_update() and the resched_curr_lazy() at the end. This makes the cgroup case do the same. Fixes: 85570f10a4c6 ("sched/eevdf: Move to a single runqueue") Signed-off-by: Zhan Xusheng Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Vincent Guittot Link: https://patch.msgid.link/20260822105930.2352761-1-zhanxusheng1024@gmail.com --- kernel/sched/fair.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 8dff37059faf..5d47de512f32 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -10057,7 +10057,7 @@ struct task_struct *pick_task_fair(struct rq *rq, struct rq_flags *rf) /* Might not have done put_prev_entity() */ if (cfs_rq->curr && cfs_rq->curr->on_rq) - update_curr(cfs_rq); + update_curr_eevdf(cfs_rq); se = pick_next_entity(rq, true); if (!se) @@ -10160,7 +10160,7 @@ static void yield_task_fair(struct rq *rq) /* * Update run-time statistics of the 'current'. */ - update_curr(cfs_rq); + update_curr_eevdf(cfs_rq); /* * Tell update_rq_clock() that we've just updated, * so we don't do microscopic update in schedule() From dae5c0292080dd7b9c7d784268dcf443f1f3d15e Mon Sep 17 00:00:00 2001 From: Seiji Nishikawa Date: Sun, 30 Aug 2026 16:37:46 +0900 Subject: [PATCH 0535/2366] sched/rt,dl: Skip migrate-disabled tasks when picking a push candidate A migrate_disable()'d RT task cannot be moved to another CPU, but the scheduler still keeps such a task on that CPU's pushable list (rq->rt.pushable_tasks) and still marks the runqueue RT-overloaded (rq->rt.overloaded = 1). So the RT balancer keeps treating this CPU as having a task to move away, and keeps trying to move the task, but the push can never succeed. When the head is pinned, push_rt_task() does not give up either. It falls back to pushing rq->curr instead, using the per-CPU stopper, as added by commit a7c81556ec4d ("sched: Fix migrate_disable() vs rt/dl balancing"). The CPU spends tens of milliseconds in this retry loop. The core is isolated for real-time work, but during the loop nearly half of its time is consumed by pushes that cannot succeed. An ftrace capture of the affected CPU, with sched_switch enabled and commit 94894c9c477e ("sched/rt: Skip currently executing CPU in rto_next_cpu()") applied, shows where the CPU time went. Two SCHED_FIFO tasks at equal priority shared the CPU, taskA migrate_disable()'d and queued, taskB as rq->curr. In one 89 ms window, taskB got only 52 ms of CPU. The other 37 ms went to the stopper thread. The scheduler kept trying to push taskA, the pinned head of the pushable list, fell back to pushing taskB instead, and woke the stopper 5204 times. Every one of those pushes failed and no task was moved. taskA stayed runnable and queued the whole time, and never ran. Pushing taskB fails on a re-check. find_lock_lowest_rq() drops the rq lock to take the target rq lock, then checks again with "task != pick_next_pushable_task(rq)". The task being pushed is taskB, but the pick returns taskA, the head of the pushable list. taskB is rq->curr, and set_next_task_rt() removes the running task from that list, so taskB can never be the head. The check expects a candidate taken from the pushable list, but the fallback pushes rq->curr, which is never on that list. So the check fails every time. .--> push-IPI arrives | | | v | pushable head = taskA -> pinned, cannot be pushed | | | v | so push taskB instead -> wake migration/N, a stop-class | | thread, so it preempts taskB | v | re-check compares taskB against the pushable head, | which is still taskA -> give up | | | v | nothing moved, taskA still queued, rq still overloaded | | '----------' repeats every ~17 us, 5204 times, for 89 ms The loop cannot stop itself. Every round leaves the runqueue exactly as it was, so the next push-IPI does the same thing. In the capture it ended only when taskB went to sleep on its own. taskA was then picked locally and left the pushable list. CPU time per task in the window, from sched_switch: taskB 51.95 ms real work migration/N 37.18 ms nothing moved taskA 0.00 ms queued the whole time, never picked idle 0.01 ms Counts over the same window: 7667 push-IPIs handled on this CPU 17481 pick_next_pushable_task() returned taskA, still pinned 5204 find_lock_lowest_rq() gave up on the re-check 1 push that actually completed 0 migrations of taskA The CPU times and the window length come from the standard sched_switch tracepoint. The counts needed tracepoints added inside the RT balancer for this investigation. The self-IPI path is closed by the rto_next_cpu() fix above, and that part works. But the runqueue is still marked overloaded, because the pinned task is still advertised as pushable. Other CPUs now send the push-IPIs during their own RT balancing, and the same loop runs again. Closing the self-IPI path did not stop a pinned task from triggering push balancing. A pinned task should never have been returned as a push candidate in the first place. A migrate_disable()'d task cannot be migrated, so it belongs in the same skip that was added for on_cpu tasks by commit e0ca8991b2de ("sched: Make class_schedulers avoid pushing current, and get rid of proxy_tag_curr()"). Add is_migration_disabled() to the skip condition in pick_next_pushable_task() and pick_next_pushable_dl_task(). With the skip in place, if the pinned task is the only extra runnable task the helpers return NULL, push_rt_task() and push_dl_task() give up early, and no stopper is woken. The pinned task then runs locally once curr yields. If a task that really can be migrated is queued behind the pinned head, it is now picked and pushed for real. This makes the fallback that pushes rq->curr unreachable when the pushable head is migrate-disabled. Nothing is lost, because that path was always stopped by the re-check described above. In the capture it ran 5204 times and moved nothing. Fixes: a7c81556ec4d ("sched: Fix migrate_disable() vs rt/dl balancing") Signed-off-by: Seiji Nishikawa Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260830073746.2189355-1-snishika@redhat.com --- kernel/sched/deadline.c | 4 ++-- kernel/sched/rt.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c index 857dbe3519a8..0663c00c41c0 100644 --- a/kernel/sched/deadline.c +++ b/kernel/sched/deadline.c @@ -3028,8 +3028,8 @@ static struct task_struct *pick_next_pushable_dl_task(struct rq *rq) next_node = rb_first_cached(&rq->dl.pushable_dl_tasks_root); while (next_node) { i = __node_2_pdl(next_node); - /* make sure task isn't on_cpu (possible with proxy-exec) */ - if (!task_on_cpu(rq, i)) { + /* skip tasks that cannot be migrated */ + if (!task_on_cpu(rq, i) && !is_migration_disabled(i)) { p = i; break; } diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index e6e5f8a2caaf..85303add726d 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -1872,8 +1872,8 @@ static struct task_struct *pick_next_pushable_task(struct rq *rq) return NULL; plist_for_each_entry(i, head, pushable_tasks) { - /* make sure task isn't on_cpu (possible with proxy-exec) */ - if (!task_on_cpu(rq, i)) { + /* skip tasks that cannot be migrated */ + if (!task_on_cpu(rq, i) && !is_migration_disabled(i)) { p = i; break; } From c6dcd97c8be75f052a1ca52cf79b03e7292962f1 Mon Sep 17 00:00:00 2001 From: "Shubhang Kaushik (Ampere)" Date: Fri, 7 Aug 2026 13:38:52 -0700 Subject: [PATCH 0536/2366] sched/core: Skip rq->avg_idle update without a valid idle_stamp Commit 4b603f1551a73 ("sched: Update rq->avg_idle when a task is moved to an idle CPU") moved rq->avg_idle accounting out of the wakeup path and into put_prev_task_idle(), so that the idle interval is consumed whenever the idle task is switched out. The wakeup-side accounting that it replaced only updated rq->avg_idle when rq->idle_stamp was non-zero. The new helper lost that validity check and unconditionally computes: rq_clock(rq) - rq->idle_stamp If rq->idle_stamp is zero, this uses rq_clock(rq) as the sample. That is not a valid idle duration and can immediately drive rq->avg_idle to its clamp. This can happen when sched_balance_newidle() returns before setting rq->idle_stamp, for example when this_rq->ttwu_pending is set. In that case the rq can switch to the idle task with idle_stamp still zero and leave idle again when the pending wakeup is processed. Other paths can also switch to the idle task without setting rq->idle_stamp via newidle_balance(), for example find_proxy_task() or force-idling. Restore the idle_stamp validity check in update_rq_avg_idle() and skip the rq->avg_idle update when there is no measured idle interval. Fixes: 4b603f1551a73 ("sched: Update rq->avg_idle when a task is moved to an idle CPU") Signed-off-by: Shubhang Kaushik (Ampere) Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: K Prateek Nayak Reviewed-by: Vincent Guittot Acked-by: John Stultz Link: https://patch.msgid.link/20260807-master-v3-1-c328354efed3@gentwo.org --- kernel/sched/core.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index f78275192036..74724501c623 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -3742,11 +3742,17 @@ static inline void ttwu_do_wakeup(struct task_struct *p) void update_rq_avg_idle(struct rq *rq) { - u64 delta = rq_clock(rq) - rq->idle_stamp; - u64 max = 2*rq->max_idle_balance_cost; + u64 idle_stamp = rq->idle_stamp; + u64 delta, max; + + if (!idle_stamp) + return; + + delta = rq_clock(rq) - idle_stamp; update_avg(&rq->avg_idle, delta); + max = 2 * rq->max_idle_balance_cost; if (rq->avg_idle > max) rq->avg_idle = max; rq->idle_stamp = 0; From f8610c57f4078c63d1d4e2f3d7134f3dc1768403 Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Mon, 31 Aug 2026 18:11:40 +0800 Subject: [PATCH 0537/2366] sched/fair: Use cfs_rq->h_curr in throttle_cfs_rq() After commit 85570f10a4c6 ("sched/eevdf: Move to a single runqueue"), cfs_rq->curr is only maintained on the root cfs_rq (set/cleared from set_next_task_fair()/put_prev_task_fair()), while cfs_rq->h_curr is the per-level current entity, set by set_next_entity() at every level of the hierarchy. For an intermediate cfs_rq (a cgroup), cfs_rq->curr is always NULL, but cfs_rq->h_curr is the group entity at that level. throttle_cfs_rq() reads cfs_rq->curr to decide whether there is a running entity at the throttled level, in which case it should request a full sched_cfs_bandwidth_slice() of runtime and arm the deferred throttle task_work via task_throttle_setup_work(). For intermediate cfs_rqs the check is always false, so bandwidth-controlled cgroups always get just 1ns of runtime and never arm the deferred throttle work; the running task then escapes throttling until the next pick arms the work instead, even though there is an on-rq entity at this level. Switch the read to cfs_rq->h_curr so intermediate bandwidth-controlled cgroups behave consistently with the root cfs_rq, matching the existing usage of cfs_rq->h_curr in update_curr() and check_enqueue_throttle(). Fixes: 85570f10a4c6 ("sched/eevdf: Move to a single runqueue") Signed-off-by: Wanwu Li Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Aaron Lu Tested-by: Aaron Lu Link: https://patch.msgid.link/20260831101141.391382-2-liwanwu@kylinos.cn --- kernel/sched/fair.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 5d47de512f32..73797d661486 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -6978,14 +6978,14 @@ static int tg_throttle_down(struct task_group *tg, void *data) static bool throttle_cfs_rq(struct cfs_rq *cfs_rq) { struct cfs_bandwidth *cfs_b = tg_cfs_bandwidth(cfs_rq->tg); - struct sched_entity *curr = cfs_rq->curr; + struct sched_entity *curr = cfs_rq->h_curr; struct rq *rq = rq_of(cfs_rq); scoped_guard(raw_spinlock, &cfs_b->lock) { u64 target_runtime = 1; /* - * If cfs_rq->curr is still runnable, we are here from an + * If cfs_rq->h_curr is still runnable, we are here from an * update_curr(). Request sysctl_sched_cfs_bandwidth_slice * worth of bandwidth to continue running. * From b038383526d8c7883ea0486dd1911102b6dda414 Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Mon, 31 Aug 2026 18:11:41 +0800 Subject: [PATCH 0538/2366] sched/fair: Use cfs_rq->h_curr in distribute_cfs_runtime() distribute_cfs_runtime() refreshes the rq clock and accounts elapsed runtime with update_curr() before redistributing bandwidth, but gates this on cfs_rq->curr. Since commit 85570f10a4c6 ("sched/eevdf: Move to a single runqueue") cfs_rq->curr is only maintained on the root cfs_rq, so for the cgroup cfs_rqs it walks, the check never fires and the refresh is dead code. Use cfs_rq->h_curr, the per-level current entity, restoring the intended behaviour: only refresh when something is actually running at the throttled level, i.e. within the deferred throttle window. Without this, runtime consumed by a still-running task of the throttled hierarchy is not docked before redistribution; unthrottle_cfs_rq() catches up unconditionally since commit 28ad5427682b ("sched/fair: Call update_curr() before unthrottling the hierarchy"), so this is not a correctness hole today, but the refresh the check was written for is gone. Fixes: 85570f10a4c6 ("sched/eevdf: Move to a single runqueue") Signed-off-by: Wanwu Li Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Aaron Lu Tested-by: Aaron Lu Link: https://patch.msgid.link/20260831101141.391382-3-liwanwu@kylinos.cn --- kernel/sched/fair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 73797d661486..97021a5033fb 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -7192,7 +7192,7 @@ static bool distribute_cfs_runtime(struct cfs_bandwidth *cfs_b) if (!list_empty(&cfs_rq->throttled_csd_list)) continue; - if (cfs_rq->curr) { + if (cfs_rq->h_curr) { update_rq_clock(rq); update_curr(cfs_rq); } From eaece4849991d62fcd6f46637c55dcce00e25d70 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 31 Aug 2026 00:38:33 -0500 Subject: [PATCH 0539/2366] x86/itmt: Don't make ITMT enablement depend on debugfs sched_set_itmt_support() treats debugfs file creation failures as fatal. When CONFIG_DEBUG_FS is disabled, debugfs stubs return ERR_PTR(-ENODEV), causing ITMT to be silently disabled. debugfs is a debug-only facility; its return values should be ignored. Drop the fatal error handling and enable ITMT unconditionally. Fixes: d04013a4b21b ("x86/itmt: Move the "sched_itmt_enabled" sysctl to debugfs") Reported-by: Klaus Kusche Signed-off-by: Mario Limonciello Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Tim Chen Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Link: https://patch.msgid.link/20260831053836.1881864-1-mario.limonciello@amd.com --- arch/x86/kernel/itmt.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/arch/x86/kernel/itmt.c b/arch/x86/kernel/itmt.c index 243a769fdd97..85ebde361d6a 100644 --- a/arch/x86/kernel/itmt.c +++ b/arch/x86/kernel/itmt.c @@ -110,18 +110,14 @@ int sched_set_itmt_support(void) arch_debugfs_dir, &sysctl_sched_itmt_enabled, &dfs_sched_itmt_fops); - if (IS_ERR_OR_NULL(dfs_sched_itmt)) { + if (IS_ERR(dfs_sched_itmt)) dfs_sched_itmt = NULL; - return -ENOMEM; - } dfs_sched_core_prio = debugfs_create_file("sched_core_priority", 0644, arch_debugfs_dir, NULL, &sched_core_priority_fops); - if (IS_ERR_OR_NULL(dfs_sched_core_prio)) { + if (IS_ERR(dfs_sched_core_prio)) dfs_sched_core_prio = NULL; - return -ENOMEM; - } sched_itmt_capable = true; From f0d243a96f2684ad771d678767d17972cf840bd7 Mon Sep 17 00:00:00 2001 From: Tim Chen Date: Mon, 31 Aug 2026 10:40:53 -0700 Subject: [PATCH 0540/2366] sched/fair: Avoid creating misfits during cache-aware balancing Cache-aware load balancing biases tasks toward their preferred LLC. On asymmetric CPU capacity systems (e.g. big.LITTLE) the destination LLC may contain CPUs that are too small to run the task. Pulling the task there turns it into a misfit, trading a cache-locality gain for a capacity loss that's more detrimental to performance. Guard both cache-aware migration entry points against this: - can_migrate_llc_task(): forbid the LLC migration when the task fits its source CPU but would not fit the destination CPU. - alb_break_llc(): veto the active balance under the same condition so the runnable task is not pushed onto a CPU that cannot accommodate it. Both checks are gated with checks for hybrid processors, so symmetric systems are unaffected. Tasks that already do not fit their source CPU are left to the existing LLC policy, since the move cannot make their fitness worse (this also preserves misfit up-migration to bigger CPUs). Additionally, if there are misfit tasks found in the load balancing classification phase, prioritize misfit task migrations over LLC load aggregation on asymmetric systems. A better fitting CPU will boost performance more than better cache locality. Reviewed-by: Ricardo Neri Tested-by: Ricardo Neri Reviewed-by: Chen Yu Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/edbb2503d554c63dc9b72e201fb4a17e1cb119e7.camel@linux.intel.com --- kernel/sched/fair.c | 50 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 97021a5033fb..ade1eceb39b8 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -10691,17 +10691,40 @@ static enum llc_mig can_migrate_llc(int src_cpu, int dst_cpu, return mig_llc; } +static inline bool task_misfits_asym_cpu(struct lb_env *env, struct task_struct *p) +{ + /* + * On asymmetric CPU capacity domains, do not let cache-aware + * balancing pull the task onto a destination CPU that cannot + * accommodate it. Doing so would turn the task into a misfit on + * the destination, trading a cache-locality gain for a capacity + * loss. If the task already does not fit its source CPU, the move + * cannot make things worse, so let the LLC preference decide. + */ + if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && p && + !task_fits_cpu(p, env->dst_cpu) && + task_fits_cpu(p, env->src_cpu)) + return true; + + return false; +} + /* * Check if task p can migrate from source LLC to * destination LLC in terms of cache aware load balance. */ -static enum llc_mig can_migrate_llc_task(int src_cpu, int dst_cpu, +static enum llc_mig can_migrate_llc_task(struct lb_env *env, struct task_struct *p) { struct mm_struct *mm; bool to_pref; - int cpu; + int cpu, src_cpu, dst_cpu; + if (task_misfits_asym_cpu(env, p)) + return mig_forbid; + + src_cpu = env->src_cpu; + dst_cpu = env->dst_cpu; mm = p->mm; if (!mm) return mig_unrestricted; @@ -10758,6 +10781,14 @@ alb_break_llc(struct lb_env *env) unsigned long util = 0; struct task_struct *cur; + /* + * Migrating misfit tasks from current CPU + * to CPU with a better fit. + * Prioritize that over LLC preference. + */ + if (env->migration_type == migrate_misfit) + return false; + if (env->src_rq->nr_running <= 1) return true; @@ -10765,7 +10796,8 @@ alb_break_llc(struct lb_env *env) if (cur && cur->sched_class == &fair_sched_class) util = task_util(cur); - if (can_migrate_llc(env->src_cpu, env->dst_cpu, + if (task_misfits_asym_cpu(env, cur) || + can_migrate_llc(env->src_cpu, env->dst_cpu, util, false) == mig_forbid) return true; } @@ -10805,8 +10837,7 @@ static bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu)) return true; - if (can_migrate_llc_task(env->src_cpu, - env->dst_cpu, p) != mig_forbid) + if (can_migrate_llc_task(env, p) != mig_forbid) return false; return true; @@ -11869,6 +11900,15 @@ static inline bool llc_balance(struct lb_env *env, struct sg_lb_stats *sgs, if (env->sd->flags & SD_SHARE_LLC) return false; + /* + * On asymmetric domains, group_misfit_task_load + * should be prioritized to move tasks to CPU that fit them + * over aggregating tasks to their preferred LLC. + */ + if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && + sgs->group_misfit_task_load) + return false; + /* * Skip cache aware tagging if nr_balanced_failed is sufficiently high. * Threshold of cache_nice_tries is set to 1 higher than nr_balance_failed From 8a7f5b5e860b5c113ca99acd5b1e9074f5c5af3c Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 25 Aug 2026 11:37:06 +0100 Subject: [PATCH 0541/2366] perf/core: Skip empty AUX records with only format flags perf_aux_output_end() emits a PERF_RECORD_AUX when the recorded size is nonzero or when any flag other than PERF_AUX_FLAG_OVERWRITE is set. PMU format flags describe how an AUX payload is encoded. TRBE driver sets PERF_AUX_FLAG_CORESIGHT_FORMAT_RAW for raw trace buffers, causing an AUX record to be emitted even when no trace data. This is noticeable when tracing a task with strace. Ptrace stops repeatedly end empty AUX transactions, producing many zero-sized PERF_RECORD_AUX records. For example: perf record -e cs_etm//u -m,128M -- strace ls perf script -D 2>&1 | awk '/PERF_RECORD_AUX offset/ { for (i = 1; i <= NF; i++) if ($i == "size:" && $(i + 1) == "0") count++ } END { print count }' 165 This recording contains 165 zero-sized AUX records which provide no useful information to userspace. Ignore PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK, together with PERF_AUX_FLAG_OVERWRITE, when deciding whether an empty AUX record is useful. Zero-sized records carrying TRUNCATED, PARTIAL or COLLISION are still emitted. Fixes: 547b60988e63 ("perf: aux: Add flags for the buffer format") Reported-by: Tamas Petz Signed-off-by: Leo Yan Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260825-perf_core_fix_zero_aux_records-v1-1-23b95e8d5df3@arm.com --- kernel/events/ring_buffer.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/kernel/events/ring_buffer.c b/kernel/events/ring_buffer.c index 9fe92161715e..1b1ffe0533e5 100644 --- a/kernel/events/ring_buffer.c +++ b/kernel/events/ring_buffer.c @@ -509,7 +509,10 @@ void perf_aux_output_end(struct perf_output_handle *handle, unsigned long size) /* * Only send RECORD_AUX if we have something useful to communicate * - * Note: the OVERWRITE records by themselves are not considered + * PMU_FORMAT bits identify the PMU type rather than an AUX event + * has occurred, so ignore them for zero-sized records. + * + * The OVERWRITE records by themselves are not considered * useful, as they don't communicate any *new* information, * aside from the short-lived offset, that becomes history at * the next event sched-in and therefore isn't useful. @@ -518,7 +521,9 @@ void perf_aux_output_end(struct perf_output_handle *handle, unsigned long size) * offset. So, from now on we don't output AUX records that * have *only* OVERWRITE flag set. */ - if (size || (handle->aux_flags & ~(u64)PERF_AUX_FLAG_OVERWRITE)) + if (size || + (handle->aux_flags & ~(u64)(PERF_AUX_FLAG_PMU_FORMAT_TYPE_MASK | + PERF_AUX_FLAG_OVERWRITE))) perf_event_aux_event(handle->event, aux_head, size, handle->aux_flags); From 58a8108bc73de0740d5b88150465d6690ea5f85f Mon Sep 17 00:00:00 2001 From: Yilin Zhang Date: Tue, 1 Sep 2026 00:21:55 +0800 Subject: [PATCH 0542/2366] perf: Fix use-after-free when perf mmap() revival races with the last munmap() perf_mmap_close() drops rb->mmap_count *without* holding event->mmap_mutex (the refcount_dec_and_test() right before the refcount_dec_and_mutex_lock() of event->mmap_count). A concurrent perf_mmap_rb() can slot its entire "revival" path into that window (perf_mmap holds event->mmap_mutex for its whole duration, including rb_alloc): munmap side (perf_mmap_close) mmap side (perf_mmap_rb) ----------------------------------- -------------------------------- rb->mmap_count 1 -> 0 (no lock) (holds event->mmap_mutex) inc_not_zero(rb->mmap_count) fails ring_buffer_attach(event, NULL) rb_alloc() + attach new rb refcount_set(&event->mmap_count, 1) lock; event->mmap_count 1 -> 0 ring_buffer_attach(event, NULL) ring_buffer_put() -> frees the *new* rb The revival's refcount_set(&event->mmap_count, 1) is an invisible 1 -> 1 write: the close frees the just-revived buffer although the other process still has it mapped -- a page-level use-after-free allowing local privilege escalation to root by any unprivileged user (default kernel.perf_event_paranoid=2). Swap the order of the two counter updates: event->mmap_count is dropped first via refcount_dec_and_mutex_lock(), so its 1 -> 0 transition and the ring_buffer_attach() stay serialized with perf_mmap(). rb->mmap_count == 0 then implies every event using the buffer is detached already, so the result of the rb->mmap_count drop can gate the remaining teardown directly and detach_rest is no longer needed. An earlier fix for this race from Kyle Zeng and David Lee takes event->mmap_mutex around both counter updates [0]; here the not-last close stays lockless. Fixes: 59741451b49c ("perf: Identify the 0->1 transition for event::mmap_count") Reported-by: Kimi Security Team Suggested-by: Peter Zijlstra Co-developed-by: Weiming Shi Signed-off-by: Weiming Shi Signed-off-by: Yilin Zhang Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/linux-perf-users/20260804060931.711308-1-david.lee@trailofbits.com/ [0] Cc: Cc: stable@vger.kernel.org # 6.18+ Link: https://patch.msgid.link/20260831162155.1437652-1-yilinzhang@moonshot.ai --- kernel/events/core.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index a6c8e38a3110..f02780529b43 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7029,7 +7029,6 @@ static void perf_mmap_close(struct vm_area_struct *vma) mapped_f unmapped = get_mapped(event, event_unmapped); struct perf_buffer *rb = ring_buffer_get(event); struct user_struct *mmap_user = rb->mmap_user; - bool detach_rest = false; /* FIXIES vs perf_pmu_unregister() */ if (unmapped) @@ -7060,17 +7059,18 @@ static void perf_mmap_close(struct vm_area_struct *vma) mutex_unlock(&rb->aux_mutex); } - if (refcount_dec_and_test(&rb->mmap_count)) - detach_rest = true; - - if (!refcount_dec_and_mutex_lock(&event->mmap_count, &event->mmap_mutex)) - goto out_put; - - ring_buffer_attach(event, NULL); - mutex_unlock(&event->mmap_mutex); + /* + * Drop references in reverse order of perf_mmap() to prevent + * rb revival after rb->mmap_count reaches zero. + */ + if (refcount_dec_and_mutex_lock(&event->mmap_count, + &event->mmap_mutex)) { + ring_buffer_attach(event, NULL); + mutex_unlock(&event->mmap_mutex); + } /* If there's still other mmap()s of this buffer, we're done. */ - if (!detach_rest) + if (!refcount_dec_and_test(&rb->mmap_count)) goto out_put; /* From 02c6be7d675b21d81f0ba3a524346850a8c0e3bf Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 24 Aug 2026 15:51:29 +0000 Subject: [PATCH 0543/2366] locking/lockdep: Invalidate stale class_cache entries for zapped classes syzbot reported a lockdep splat hitting DEBUG_LOCKS_WARN_ON(1) in hlock_class() due to an invalid class_idx: WARNING: kernel/locking/lockdep.c:238 at __lock_acquire+0x382/0x2cf0 kernel/locking/lockdep.c:5203 Workqueue: wg-crypt-wg0 wg_packet_tx_worker RIP: 0010:hlock_class kernel/locking/lockdep.c:238 [inline] RIP: 0010:check_wait_context kernel/locking/lockdep.c:4870 [inline] RIP: 0010:__lock_acquire+0x389/0x2cf0 kernel/locking/lockdep.c:5203 Call Trace: lock_acquire+0x106/0x350 kernel/locking/lockdep.c:5886 _raw_spin_lock+0x2e/0x40 kernel/locking/spinlock.c:173 tcp_tsq_handler+0x29/0x200 net/ipv4/tcp_output.c:1291 tcp_tsq_workfn+0x384/0x410 net/ipv4/tcp_output.c:1325 ... When a lock class is zapped (e.g. during module unload or key unregistration), zap_class() clears the class's bit in lock_classes_in_use and removes it from the class hash table. However, existing lockdep_map instances embedded in data structures may still retain a pointer to the zapped class in their class_cache[] array. When __lock_acquire() subsequently runs on such a lock, it finds lock->class_cache[subclass] != NULL, skipping register_lock_class() and assigning hlock->class_idx to the index of the zapped class. When check_wait_context() or hlock_class() inspects the held_lock, it finds !test_bit(class_idx, lock_classes_in_use) and warns. Furthermore, if the zapped slot is subsequently re-allocated to an unrelated lock key, the stale class_cache entry would erroneously match the unrelated class (ABA issue). Add lock_class_cache_is_valid() to validate that the cached class is within lock_classes bounds, still allocated in lock_classes_in_use (using uninstrumented arch_test_bit() in __always_inline context so it is safe in noinstr contexts like match_held_lock()), and that class->key matches the expected subkey (taking lockdep_set_subclass() overrides into account). Also use READ_ONCE()/WRITE_ONCE() when accessing class_cache[]. If the entry is invalid or stale, fall back to register_lock_class() / look_up_lock_class(). Fixes: a0b0fd53e1e6 ("locking/lockdep: Free lock classes that are no longer in use") Closes: https://lore.kernel.org/netdev/6a8c66dc.4d75e56a.c9a88.0050.GAE@google.com/T/#u Reported-by: syzbot+2d770620059281e225a4@syzkaller.appspotmail.com Assisted-by: Gemini:gemini-3.1-pro Signed-off-by: Eric Dumazet Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824155129.676096-1-edumazet@google.com --- kernel/locking/lockdep.c | 50 +++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c index 25d77d4a1061..763f79806bd8 100644 --- a/kernel/locking/lockdep.c +++ b/kernel/locking/lockdep.c @@ -963,6 +963,34 @@ look_up_lock_class(const struct lockdep_map *lock, unsigned int subclass) return NULL; } +static __always_inline bool lock_class_cache_is_valid(const struct lockdep_map *lock, + const struct lock_class *class, + unsigned int subclass) +{ + unsigned int class_subclass; + + if (!class) + return false; + + if (unlikely(class < lock_classes || class >= lock_classes + MAX_LOCKDEP_KEYS)) + return false; + + if (unlikely(!arch_test_bit(class - lock_classes, lock_classes_in_use))) + return false; + + if (unlikely(!lock->key)) + return false; + + class_subclass = subclass ? subclass : class->subclass; + if (unlikely(class_subclass >= MAX_LOCKDEP_SUBCLASSES)) + return false; + + if (unlikely(READ_ONCE(class->key) != lock->key->subkeys + class_subclass)) + return false; + + return true; +} + /* * Static locks do not have their class-keys yet - for them the key is * the lock object itself. If the lock is in the per cpu area, the @@ -1395,9 +1423,9 @@ register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force) out_set_class_cache: if (!subclass || force) - lock->class_cache[0] = class; + WRITE_ONCE(lock->class_cache[0], class); else if (subclass < NR_LOCKDEP_CACHING_CLASSES) - lock->class_cache[subclass] = class; + WRITE_ONCE(lock->class_cache[subclass], class); /* * Hash collision, did we smoke some? We found a class with a matching @@ -4957,7 +4985,7 @@ void lockdep_init_map_type(struct lockdep_map *lock, const char *name, int i; for (i = 0; i < NR_LOCKDEP_CACHING_CLASSES; i++) - lock->class_cache[i] = NULL; + WRITE_ONCE(lock->class_cache[i], NULL); #ifdef CONFIG_LOCK_STAT lock->cpu = raw_smp_processor_id(); @@ -5022,12 +5050,15 @@ EXPORT_SYMBOL_GPL(__lockdep_no_track__); void lockdep_set_lock_cmp_fn(struct lockdep_map *lock, lock_cmp_fn cmp_fn, lock_print_fn print_fn) { - struct lock_class *class = lock->class_cache[0]; + struct lock_class *class = READ_ONCE(lock->class_cache[0]); unsigned long flags; raw_local_irq_save(flags); lockdep_recursion_inc(); + if (!lock_class_cache_is_valid(lock, class, 0)) + class = NULL; + if (!class) class = register_lock_class(lock, 0, 0); @@ -5119,8 +5150,11 @@ static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass, if (DEBUG_LOCKS_WARN_ON(subclass >= MAX_LOCKDEP_SUBCLASSES)) return 0; - if (subclass < NR_LOCKDEP_CACHING_CLASSES) - class = lock->class_cache[subclass]; + if (subclass < NR_LOCKDEP_CACHING_CLASSES) { + class = READ_ONCE(lock->class_cache[subclass]); + if (!lock_class_cache_is_valid(lock, class, subclass)) + class = NULL; + } /* * Not cached? */ @@ -5323,9 +5357,9 @@ static noinstr int match_held_lock(const struct held_lock *hlock, return 1; if (hlock->references) { - const struct lock_class *class = lock->class_cache[0]; + const struct lock_class *class = READ_ONCE(lock->class_cache[0]); - if (!class) + if (!lock_class_cache_is_valid(lock, class, 0)) class = look_up_lock_class(lock, 0); /* From ff5891b266a7fc6a062710836be84f1cc19338b5 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 12 Aug 2026 06:17:14 +0000 Subject: [PATCH 0544/2366] ieee802154: cc2520: fix FIFOP work use-after-free The FIFOP interrupt handler queues cc2520_fifop_irqwork. On removal, cc2520_remove() only flushes the work. The devm-managed FIFOP IRQ remains active until after ->remove() returns and can queue the work again after that flush, allowing it to run after the private data is released. Disable the work with disable_work_sync() instead of flushing it, so the handler can no longer queue it once removal begins. Destroy the buffer mutex last, since the worker and the stop callback invoked through ieee802154_unregister_hw() both take it. Found by an in-house static analysis tool. Fixes: 0da6bc8cc341 ("ieee802154: cc2520: adds driver for TI CC2520 radio") Cc: stable@vger.kernel.org # v6.10+ Suggested-by: Miquel Raynal Reviewed-by: Miquel Raynal Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://lore.kernel.org/20260812061714.175966-1-fanwu01@zju.edu.cn Signed-off-by: Stefan Schmidt --- drivers/net/ieee802154/cc2520.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ieee802154/cc2520.c b/drivers/net/ieee802154/cc2520.c index 2b7034193a00..abfcfe07246a 100644 --- a/drivers/net/ieee802154/cc2520.c +++ b/drivers/net/ieee802154/cc2520.c @@ -1156,11 +1156,10 @@ static void cc2520_remove(struct spi_device *spi) { struct cc2520_private *priv = spi_get_drvdata(spi); - mutex_destroy(&priv->buffer_mutex); - flush_work(&priv->fifop_irqwork); - + disable_work_sync(&priv->fifop_irqwork); ieee802154_unregister_hw(priv->hw); ieee802154_free_hw(priv->hw); + mutex_destroy(&priv->buffer_mutex); } static const struct spi_device_id cc2520_ids[] = { From bf79662bc85e820ac3b846e2f347da29fbf6ac95 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 29 Aug 2026 18:07:23 +0800 Subject: [PATCH 0545/2366] ieee802154: 6lowpan: fix NULL dereference in lowpan_newlink TUNSETLINK allows a TUN device to change its link-layer type to ARPHRD_IEEE802154 without initializing ieee802154_ptr. lowpan_newlink() checks only the device type before dereferencing the pointer, so an RTM_NEWLINK request can trigger a NULL pointer dereference. Reject devices without ieee802154_ptr along with devices of the wrong type. Fixes: 51e0e5d8124e ("ieee802154: 6lowpan: remove multiple lowpan per wpan support") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Link: https://lore.kernel.org/0b715da69bd15a86ddc47dad5cf12da648211050.1787997209.git.zhilinz@nebusec.ai Signed-off-by: Stefan Schmidt --- net/ieee802154/6lowpan/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ieee802154/6lowpan/core.c b/net/ieee802154/6lowpan/core.c index 018929563c6b..6a8d6852cb93 100644 --- a/net/ieee802154/6lowpan/core.c +++ b/net/ieee802154/6lowpan/core.c @@ -150,7 +150,7 @@ static int lowpan_newlink(struct net_device *ldev, wdev = dev_get_by_index(dev_net(ldev), nla_get_u32(tb[IFLA_LINK])); if (!wdev) return -ENODEV; - if (wdev->type != ARPHRD_IEEE802154) { + if (wdev->type != ARPHRD_IEEE802154 || !wdev->ieee802154_ptr) { dev_put(wdev); return -EINVAL; } From 979d5b8de8ed4e1f997aef12da5694b99be7b871 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 9 Jul 2026 23:18:58 +0100 Subject: [PATCH 0546/2366] ieee802154: hwsim: serialize pib updates to fix double-free hwsim_update_pib() does an unserialized read-swap-free of phy->pib: pib_old = rtnl_dereference(phy->pib); ... rcu_assign_pointer(phy->pib, pib); kfree_rcu(pib_old, rcu); It assumes the RTNL is held, but ->set_channel is not always called under it: the mac802154 scan worker changes channels via drv_set_channel() without the RTNL. Such an update can race an RTNL-held one on the same phy; both read the same pib_old and both kfree_rcu() it, double-freeing the object. With SLUB percpu sheaves batching kfree_rcu(), this surfaces as a KASAN invalid-free in rcu_free_sheaf(). struct hwsim_phy has no lock for pib. Add one and make the swap atomic with rcu_replace_pointer() under it, dropping the misleading rtnl_dereference(). Reported-by: syzbot+60332fd095f8bb2946ad@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=60332fd095f8bb2946ad Fixes: f25da51fdc38 ("ieee802154: hwsim: add replacement for fakelb") Signed-off-by: David Carlier Cc: stable@vger.kernel.org Link: https://lore.kernel.org/20260709221858.158063-1-devnexen@gmail.com Signed-off-by: Stefan Schmidt --- drivers/net/ieee802154/mac802154_hwsim.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/ieee802154/mac802154_hwsim.c b/drivers/net/ieee802154/mac802154_hwsim.c index 6daa0f198b9f..a9bd1555d2dc 100644 --- a/drivers/net/ieee802154/mac802154_hwsim.c +++ b/drivers/net/ieee802154/mac802154_hwsim.c @@ -72,6 +72,8 @@ struct hwsim_phy { struct ieee802154_hw *hw; u32 idx; + /* Serializes phy->pib_updates. */ + spinlock_t pib_lock; struct hwsim_pib __rcu *pib; bool suspended; @@ -102,8 +104,6 @@ static int hwsim_update_pib(struct ieee802154_hw *hw, u8 page, u8 channel, if (!pib) return -ENOMEM; - pib_old = rtnl_dereference(phy->pib); - pib->page = page; pib->channel = channel; pib->filt.short_addr = filt->short_addr; @@ -112,7 +112,10 @@ static int hwsim_update_pib(struct ieee802154_hw *hw, u8 page, u8 channel, pib->filt.pan_coord = filt->pan_coord; pib->filt_level = filt_level; - rcu_assign_pointer(phy->pib, pib); + spin_lock_bh(&phy->pib_lock); + pib_old = rcu_replace_pointer(phy->pib, pib, + lockdep_is_held(&phy->pib_lock)); + spin_unlock_bh(&phy->pib_lock); kfree_rcu(pib_old, rcu); return 0; } @@ -952,6 +955,7 @@ static int hwsim_add_one(struct genl_info *info, struct device *dev, goto err_pib; } + spin_lock_init(&phy->pib_lock); pib->channel = 13; pib->filt.short_addr = cpu_to_le16(IEEE802154_ADDR_BROADCAST); pib->filt.pan_id = cpu_to_le16(IEEE802154_PANID_BROADCAST); From f695390ea63941a9e412bf1f3afe65ab245fc681 Mon Sep 17 00:00:00 2001 From: Sunil Goutham Date: Fri, 28 Aug 2026 14:49:45 +0530 Subject: [PATCH 0547/2366] octeontx2-af: Fix limiting SRIOV VF count logic When RVU PF0/AF's VFs are SDP instead of LBK, limiting the VF count based on the LBK channel count is incorrect. Apply LBK channel-based VF limits only when the VF device ID matches the LBK RVU AFVF device. Fixes: 9bd6caf33567 ("octeontx2-af: Enable sriov on AF to create VFs") Signed-off-by: Sunil Goutham Signed-off-by: Nitin Shetty J Signed-off-by: David S. Miller --- .../net/ethernet/marvell/octeontx2/af/rvu.c | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c index 74c041ab5280..937b085582b5 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c @@ -3468,6 +3468,8 @@ int rvu_get_num_lbk_chans(void) return ret; } +#define PCI_DEVID_OCTEONTX2_RVU_AFVF 0xA0F8 + static int rvu_enable_sriov(struct rvu *rvu) { struct pci_dev *pdev = rvu->pdev; @@ -3486,24 +3488,27 @@ static int rvu_enable_sriov(struct rvu *rvu) return 0; pci_read_config_word(pdev, pos + PCI_SRIOV_VF_DID, &rvu->vf_devid); - chans = rvu_get_num_lbk_chans(); - if (chans < 0) - return chans; - vfs = pci_sriov_get_totalvfs(pdev); - - /* Limit VFs in case we have more VFs than LBK channels available. */ - if (vfs > chans) - vfs = chans; - if (!vfs) return 0; - /* LBK channel number 63 is used for switching packets between - * CGX mapped VFs. Hence limit LBK pairs till 62 only. - */ - if (vfs > 62) - vfs = 62; + if (rvu->vf_devid == PCI_DEVID_OCTEONTX2_RVU_AFVF) { + chans = rvu_get_num_lbk_chans(); + if (chans < 0) + return chans; + + /* The last LBK channel is reserved for switching packets between + * CGX mapped VFs. Also, since LBK VFs work in pairs, limit VF + * count to available LBK channels minus 2. + */ + vfs = min(vfs, chans - 2); + + if (vfs <= 0) { + dev_warn(&pdev->dev, + "Skipping SRIOV enablement, not enough LBK channels available\n"); + return 0; + } + } /* Save VFs number for reference in VF interrupts handlers. * Since interrupts might start arriving during SRIOV enablement From 636abbe7a66d80e179011a31754d55001cd44f63 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 2 Sep 2026 13:23:52 +0900 Subject: [PATCH 0548/2366] ksmbd: fix sparc build with atomic work state Use an unsigned int for the work state so xchg() uses a supported 4-byte operation on sparc. Fixes: d12168084c8c ("ksmbd: safely drain sessions during logoff") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609021157.8f7Wx34I-lkp@intel.com/ Signed-off-by: Namjae Jeon --- fs/smb/server/ksmbd_work.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h index 5f1d3ebab4fb..0844aa929f55 100644 --- a/fs/smb/server/ksmbd_work.h +++ b/fs/smb/server/ksmbd_work.h @@ -82,7 +82,7 @@ struct ksmbd_work { /* Contiguous SMB2 compression transform owned by this work item. */ void *compress_buf; - unsigned char state; + unsigned int state; /* No response for cancelled request */ bool send_no_response:1; /* Request is encrypted */ From 0e753899627b5e28a9fea8bca98262a6f65a2452 Mon Sep 17 00:00:00 2001 From: Abdifatah Suruur Date: Sat, 29 Aug 2026 18:40:22 +0300 Subject: [PATCH 0549/2366] ksmbd: fix use-after-free in oplock break notification smb2_oplock_break_noti() reads opinfo->conn without any lock and dereferences it after two allocations which may sleep. When the durable handle owning the oplock is disconnected, session_fd_check() clears opinfo->conn and drops its conn reference under ci->m_lock, and the last ksmbd_conn_put() frees the connection. A break triggered by another connection that races with the teardown can then resurrect the freed connection: ksmbd_conn_get() is a plain atomic_inc, and the queued break work later dereferences the stale conn via ksmbd_conn_write(), a use-after-free reachable by any authenticated client holding a durable batch oplock. Thread the caller's inode into the notification path instead of taking a new reference on it. Every caller of oplock_break() already holds a live ksmbd_file (or an explicit ksmbd_inode_lookup_lock() reference, in the parent lease break paths) on the inode that owns the break target's oplock list, so ci cannot be freed during the call, and its lock can be taken without dereferencing opinfo->o_fp, which a concurrent close may free. Select and pin the connection under ci->m_lock, the same lock session_fd_check() and ksmbd_reopen_durable_fd() use to update opinfo->conn, so a concurrent detach either loses the race to the clear or keeps the connection alive until the notification work releases it. Transfer the reference to the work item and release it on allocation failures. Fixes: b003086d7696 ("ksmbd: fix NULL-deref of opinfo->conn in oplock/lease break notifiers") Cc: stable@vger.kernel.org Signed-off-by: Abdifatah Suruur Signed-off-by: Namjae Jeon --- fs/smb/server/oplock.c | 73 +++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c index 58af0fddf39f..1b8c3482d1e4 100644 --- a/fs/smb/server/oplock.c +++ b/fs/smb/server/oplock.c @@ -924,31 +924,69 @@ static void __smb2_oplock_break_noti(struct work_struct *wk) ksmbd_conn_put(conn); } +/* + * Select and pin the connection used for an oplock break before doing any + * allocations which may sleep. The caller of oplock_break() holds a live + * reference on ci (a file being opened, a file being operated on, or an + * explicit ksmbd_inode_lookup_lock() reference in the parent lease break + * paths), so the inode cannot be freed during the call and its lock is + * reachable without dereferencing opinfo->o_fp, which is not pinned by + * the oplock reference and may be freed by a concurrent close. + * + * opinfo->conn is cleared under ci->m_lock by session_fd_check() when the + * durable handle owning the oplock is disconnected, reassigned by + * ksmbd_reopen_durable_fd() under the same lock, and the last + * ksmbd_conn_put() of the old connection frees it. Holding the read lock + * excludes both writers, so the connection cannot be freed while it is + * selected. + */ +static struct ksmbd_conn *smb2_oplock_break_conn_get(struct oplock_info *opinfo, + struct ksmbd_inode *ci) +{ + struct ksmbd_conn *conn; + + down_read(&ci->m_lock); + conn = READ_ONCE(opinfo->conn); + if (conn && !ksmbd_conn_releasing(conn)) + conn = ksmbd_conn_get(conn); + else + conn = NULL; + up_read(&ci->m_lock); + + return conn; +} + /** * smb2_oplock_break_noti() - send smb2 exclusive/batch to level2 oplock * break command from server to client * @opinfo: oplock info object + * @ci: inode owning the break target's oplock list, pinned by + * the caller * * Return: 0 on success, otherwise error */ -static int smb2_oplock_break_noti(struct oplock_info *opinfo) +static int smb2_oplock_break_noti(struct oplock_info *opinfo, + struct ksmbd_inode *ci) { struct ksmbd_conn *conn; struct oplock_break_info *br_info; int ret = 0; struct ksmbd_work *work; - conn = READ_ONCE(opinfo->conn); + conn = smb2_oplock_break_conn_get(opinfo, ci); if (!conn) return ksmbd_invalidate_durable_fd(opinfo->fid); work = ksmbd_alloc_work_struct(); - if (!work) + if (!work) { + ksmbd_conn_put(conn); return -ENOMEM; + } br_info = kmalloc_obj(struct oplock_break_info, KSMBD_DEFAULT_GFP); if (!br_info) { ksmbd_free_work_struct(work); + ksmbd_conn_put(conn); return -ENOMEM; } @@ -957,7 +995,8 @@ static int smb2_oplock_break_noti(struct oplock_info *opinfo) br_info->open_trunc = opinfo->open_trunc; work->request_buf = (char *)br_info; - work->conn = ksmbd_conn_get(conn); + /* Transfer the reference acquired by smb2_oplock_break_conn_get(). */ + work->conn = conn; work->sess = opinfo->sess; ksmbd_conn_r_count_inc(conn); @@ -1154,9 +1193,9 @@ static void wait_lease_breaking(struct oplock_info *opinfo) } } -static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level, - struct ksmbd_work *in_work, bool share_break, - bool sync_lease_break) +static int oplock_break(struct oplock_info *brk_opinfo, struct ksmbd_inode *ci, + int req_op_level, struct ksmbd_work *in_work, + bool share_break, bool sync_lease_break) { int err = 0; bool sent_interim = false; @@ -1298,7 +1337,7 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level, } } - err = smb2_oplock_break_noti(brk_opinfo); + err = smb2_oplock_break_noti(brk_opinfo, ci); ksmbd_debug(OPLOCK, "oplock granted = %d\n", brk_opinfo->level); if (brk_opinfo->op_state == OPLOCK_CLOSING) @@ -1326,13 +1365,14 @@ static int oplock_break_add(struct list_head *head, struct oplock_info *opinfo) return 0; } -static void oplock_break_drain_none(struct list_head *head) +static void oplock_break_drain_none(struct list_head *head, + struct ksmbd_inode *ci) { struct oplock_break_entry *ent, *tmp; list_for_each_entry_safe(ent, tmp, head, list) { - oplock_break(ent->opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false, - false); + oplock_break(ent->opinfo, ci, SMB2_OPLOCK_LEVEL_NONE, NULL, + false, false); list_del(&ent->list); opinfo_put(ent->opinfo); kfree(ent); @@ -1481,7 +1521,7 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp, } up_read(&p_ci->m_lock); - oplock_break_drain_none(&brk_list); + oplock_break_drain_none(&brk_list, p_ci); ksmbd_inode_put(p_ci); } @@ -1525,7 +1565,7 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp) } up_read(&p_ci->m_lock); - oplock_break_drain_none(&brk_list); + oplock_break_drain_none(&brk_list, p_ci); ksmbd_inode_put(p_ci); } @@ -1665,7 +1705,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid, prev_durable_detached = prev_op_snapshot.durable_detached; prev_fid = prev_op_snapshot.fid; - err = oplock_break(prev_opinfo, break_level, work, + err = oplock_break(prev_opinfo, ci, break_level, work, share_ret < 0 && prev_opinfo->is_lease, false); if (prev_durable_detached || (prev_durable_open && err == -ENOENT)) ksmbd_invalidate_durable_fd(prev_fid); @@ -1771,7 +1811,8 @@ static bool smb_break_all_write_oplock(struct ksmbd_work *work, } brk_opinfo->open_trunc = is_trunc; - oplock_break(brk_opinfo, SMB2_OPLOCK_LEVEL_II, work, false, false); + oplock_break(brk_opinfo, fp->f_ci, SMB2_OPLOCK_LEVEL_II, work, false, + false); sent_break = true; opinfo_put(brk_opinfo); @@ -1863,7 +1904,7 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work, brk_op->op_state = OPLOCK_STATE_NONE; spin_unlock(&brk_op->state_lock); } else { - oplock_break(brk_op, + oplock_break(brk_op, ci, brk_op->is_lease && !is_trunc ? SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE, send_interim && !sent_interim ? work : NULL, From 0480cee8cc3cc906124d398a9779eda144de6b41 Mon Sep 17 00:00:00 2001 From: Alon Shakevsky Date: Tue, 1 Sep 2026 00:05:31 +0000 Subject: [PATCH 0550/2366] ksmbd: validate COPYCHUNK source and target ranges ksmbd_vfs_copy_file_ranges() rejects negative source offsets in the copy loop, but it does not validate target offsets. It also calculates lock and overlap endpoints before ensuring that either range fits within MAX_LFS_FILESIZE. When the target is an alternate data stream, the buffered path passes a negative target offset to ksmbd_vfs_stream_write(). Let n be Length and let -d be TargetOffset, where 0 < d < n <= XATTR_SIZE_MAX. For an empty stream, the writer allocates n - d bytes, then copies n bytes starting d bytes before the allocation. An authenticated SMB client can control d and the source data, overwrite kernel heap memory, and crash the host. Validate both ranges before lock, overlap, or I/O calculations. Fixes: 8482150a0743 ("ksmbd: support copychunk for alternate data streams") Assisted-by: Antiproof:GPT-5.6-Sol Signed-off-by: Alon Shakevsky Signed-off-by: Namjae Jeon --- fs/smb/server/vfs.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index d2b524f79cbe..c2c9aaa5de1b 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -2007,6 +2007,11 @@ static ssize_t ksmbd_vfs_copy_file_range_buffered(struct ksmbd_work *work, return ret; } +static bool ksmbd_vfs_copy_range_valid(loff_t offset, size_t len) +{ + return offset >= 0 && (loff_t)len <= MAX_LFS_FILESIZE - offset; +} + int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work, struct ksmbd_file *src_fp, struct ksmbd_file *dst_fp, @@ -2042,6 +2047,10 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work, dst_off = le64_to_cpu(chunks[i].TargetOffset); len = le32_to_cpu(chunks[i].Length); + if (!ksmbd_vfs_copy_range_valid(src_off, len) || + !ksmbd_vfs_copy_range_valid(dst_off, len)) + return -E2BIG; + if (check_lock_range(src_fp->filp, src_off, src_off + len - 1, READ)) return -EAGAIN; @@ -2134,7 +2143,8 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work, len = le32_to_cpu(chunks[i].Length); copy_len = len; - if (src_off < 0) + if (!ksmbd_vfs_copy_range_valid(src_off, len) || + !ksmbd_vfs_copy_range_valid(dst_off, len)) return -E2BIG; if (src_off > src_file_size || len > src_file_size - src_off) { From b5ec6c462aab1062cf5d1e667ba7c6442f737055 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft Security FORGE Labs)" Date: Tue, 1 Sep 2026 14:21:36 -0400 Subject: [PATCH 0551/2366] ksmbd: fix tree connection use-after-free in smb2_tree_connect() ksmbd_tree_conn_connect() publishes a new tree connection in sess->tree_conns with a single reference and returns its pointer to smb2_tree_connect(). The handler continues to initialize the object and build the response after publication. A concurrent session logoff can erase the connection and drop that reference, freeing the object while the handler still uses it. BUG: KASAN: slab-use-after-free in smb2_tree_connect+0xe3d/0xf90 smb2_tree_connect (fs/smb/server/smb2pdu.c:2872) handle_ksmbd_work process_one_work worker_thread kthread After xa_store() succeeds, take a second reference before releasing tree_conns_lock. The original reference belongs to the xarray entry and the second belongs to the creating smb2_tree_connect() handler. Keep the references balanced in every path: - On normal exit or an error after publication, smb2_tree_connect() drops its creator reference. Error cleanup also calls ksmbd_tree_conn_disconnect(), which drops the xarray reference only if it removes the exact entry. - SMB2 TREE_DISCONNECT uses the same helper to remove the entry and drop its xarray reference. The request's existing lookup reference remains owned by the request and is released by the existing cleanup. - Session LOGOFF removes each entry and drops its xarray reference. If it wins the race, later cleanup sees that the entry is gone and does not drop that reference again. To enforce this ownership, claim the disconnected state and erase the exact entry atomically under tree_conns_lock. This guarantees one drop for the xarray reference and one drop by each in-flight user, regardless of which teardown path wins. If logoff removes the entry before initialization completes, fail the connect instead of marking the detached object TREE_CONNECTED. Fixes: 33b235a6e6eb ("ksmbd: fix race condition between tree conn lookup and disconnect") Reported-by: Xiang Mei (Microsoft) Cc: AutonomousCodeSecurity@microsoft.com Cc: stable@vger.kernel.org Signed-off-by: Cen Zhang (Microsoft Security FORGE Labs) Signed-off-by: Namjae Jeon --- fs/smb/server/mgmt/tree_connect.c | 8 ++++++++ fs/smb/server/smb2pdu.c | 28 +++++++++++++--------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/fs/smb/server/mgmt/tree_connect.c b/fs/smb/server/mgmt/tree_connect.c index 5f63e236267a..dd1db3554cae 100644 --- a/fs/smb/server/mgmt/tree_connect.c +++ b/fs/smb/server/mgmt/tree_connect.c @@ -82,6 +82,8 @@ ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name) down_write(&sess->tree_conns_lock); ret = xa_err(xa_store(&sess->tree_conns, tree_conn->id, tree_conn, KSMBD_DEFAULT_GFP)); + if (!ret) + atomic_inc(&tree_conn->refcount); up_write(&sess->tree_conns_lock); if (ret) { status.ret = -ENOMEM; @@ -129,6 +131,12 @@ int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess, struct ksmbd_tree_connect *tree_conn) { down_write(&sess->tree_conns_lock); + if (tree_conn->t_state == TREE_DISCONNECTED || + xa_load(&sess->tree_conns, tree_conn->id) != tree_conn) { + up_write(&sess->tree_conns_lock); + return -ENOENT; + } + tree_conn->t_state = TREE_DISCONNECTED; xa_erase(&sess->tree_conns, tree_conn->id); up_write(&sess->tree_conns_lock); diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index d656832d82ef..0ecc52fde69c 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -2790,6 +2790,7 @@ int smb2_tree_connect(struct ksmbd_work *work) struct ksmbd_session *sess = work->sess; char *treename = NULL, *name = NULL; struct ksmbd_tree_conn_status status; + struct ksmbd_tree_connect *tree_conn = NULL; struct ksmbd_share_config *share = NULL; int rc = -EINVAL; @@ -2817,6 +2818,7 @@ int smb2_tree_connect(struct ksmbd_work *work) status = ksmbd_tree_conn_connect(work, name); if (status.ret == KSMBD_TREE_CONN_STATUS_OK) { + tree_conn = status.tree_conn; rsp->hdr.Id.SyncId.TreeId = cpu_to_le32(status.tree_conn->id); share = status.tree_conn->share_conf; @@ -2860,8 +2862,15 @@ int smb2_tree_connect(struct ksmbd_work *work) status.tree_conn->posix_extensions = true; down_write(&sess->tree_conns_lock); - status.tree_conn->t_state = TREE_CONNECTED; + if (status.tree_conn->t_state == TREE_DISCONNECTED) { + status.ret = KSMBD_TREE_CONN_STATUS_ERROR; + share = NULL; + } else { + status.tree_conn->t_state = TREE_CONNECTED; + } up_write(&sess->tree_conns_lock); + if (status.ret != KSMBD_TREE_CONN_STATUS_OK) + goto out_err1; rsp->StructureSize = cpu_to_le16(16); out_err1: /* @@ -2888,9 +2897,6 @@ int smb2_tree_connect(struct ksmbd_work *work) rc = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_tree_connect_rsp)); if (rc) { if (status.ret == KSMBD_TREE_CONN_STATUS_OK) { - down_write(&sess->tree_conns_lock); - status.tree_conn->t_state = TREE_DISCONNECTED; - up_write(&sess->tree_conns_lock); ksmbd_tree_conn_disconnect(sess, status.tree_conn); status.tree_conn = NULL; } @@ -2931,6 +2937,9 @@ int smb2_tree_connect(struct ksmbd_work *work) if (status.ret != KSMBD_TREE_CONN_STATUS_OK) smb2_set_err_rsp(work); + if (tree_conn) + ksmbd_tree_connect_put(tree_conn); + return rc; } @@ -3034,17 +3043,6 @@ int smb2_tree_disconnect(struct ksmbd_work *work) ksmbd_close_tree_conn_fds(work); - down_write(&sess->tree_conns_lock); - if (tcon->t_state == TREE_DISCONNECTED) { - up_write(&sess->tree_conns_lock); - rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED; - err = -ENOENT; - goto err_out; - } - - tcon->t_state = TREE_DISCONNECTED; - up_write(&sess->tree_conns_lock); - err = ksmbd_tree_conn_disconnect(sess, tcon); if (err) { rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED; From adeee7187694719890aaffdc14b7e89cfd736f1d Mon Sep 17 00:00:00 2001 From: Qingyu Zhang Date: Wed, 2 Sep 2026 15:39:18 +0800 Subject: [PATCH 0552/2366] ALSA: ump: do not touch legacy_rmidi before it exists snd_ump_parse_endpoint() sets ump->parsed on every exit, including error, before the caller attaches the legacy rawmidi device. ump_handle_ep_name_msg() then treats parsed as "legacy_rmidi is live" and calls ump_legacy_set_rawmidi_name(), which snprintf()s into ump->legacy_rmidi->name. If a UMP packet arrives in that window (IRQ path from snd_ump_receive), legacy_rmidi is still NULL (KASAN null-ptr-deref in snprintf). Guard the legacy helpers. parsed only means endpoint info was parsed, not that legacy_rmidi exists. Fixes: 37e0e14128e0 ("ALSA: ump: Support UMP Endpoint and Function Block parsing") Signed-off-by: Qingyu Zhang Link: https://patch.msgid.link/20260902073918.880245-1-usupergate@gmail.com Signed-off-by: Takashi Iwai --- sound/core/ump.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/core/ump.c b/sound/core/ump.c index d183c8a000bd..3d1a2ed3b476 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -1335,6 +1335,8 @@ static void update_legacy_names(struct snd_ump_endpoint *ump) { struct snd_rawmidi *rmidi = ump->legacy_rmidi; + if (!rmidi) + return; update_legacy_substreams(ump, rmidi, SNDRV_RAWMIDI_STREAM_INPUT); update_legacy_substreams(ump, rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT); } @@ -1343,6 +1345,8 @@ static void ump_legacy_set_rawmidi_name(struct snd_ump_endpoint *ump) { struct snd_rawmidi *rmidi = ump->legacy_rmidi; + if (!rmidi) + return; snprintf(rmidi->name, sizeof(rmidi->name), "%.68s (MIDI 1.0)", ump->core.name); } From 0433632bbe8d279e978f3f85212f36281a89946c Mon Sep 17 00:00:00 2001 From: John Ogness Date: Tue, 1 Sep 2026 11:37:42 +0206 Subject: [PATCH 0553/2366] printk/nbcon: Flush nbcon_irq_work in nbcon_free() Ensure any pending nbcon_irq_work is flushed before allowing the console to be recycled. Signed-off-by: John Ogness Reviewed-by: Petr Mladek Link: https://patch.msgid.link/20260901093245.344455-2-john.ogness@linutronix.de Signed-off-by: Petr Mladek --- kernel/printk/nbcon.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index 4b03b019cd5e..354f274d8a42 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -1837,6 +1837,8 @@ void nbcon_free(struct console *con) /* Synchronize the kthread stop. */ lockdep_assert_console_list_lock_held(); + irq_work_sync(&con->irq_work); + if (printk_kthreads_running) { nbcon_kthread_stop(con); From 560f4deda32785e260056200f8bb911c475c5b88 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Tue, 1 Sep 2026 11:37:43 +0206 Subject: [PATCH 0554/2366] printk/nbcon: Change nbcon_irq_work to IRQ_WORK_LAZY Change the nbcon_irq_work to be IRQ_WORK_LAZY, thus not raising an IRQ upon irq_work queuing. The irq_work is then handled on the next kernel tick. This additional delay is acceptable because nbcon_irq_work is only responsible for non-emergency deferred printing, which is delayed anyway. This has the benefit of not needing to raise an IRQ for each printk() call. On a side note, the Tegra20 and Tegra30 platforms can hang if an irq_work IRQ is raised while entering cpuidle states. This problem was reproducible by calling printk() while entering cpuidle. So this change also provides a workaround for these platforms (as long as they are not running tickless). Link: https://lore.kernel.org/lkml/f3757a75-0ba1-4558-bf57-f19ab7e59a4c@nvidia.com Fixes: 76f258bf3f2a ("printk: nbcon: Introduce printer kthreads") Signed-off-by: John Ogness Reviewed-by: Sebastian Andrzej Siewior Reviewed-by: Petr Mladek Tested-by: Jon Hunter Link: https://patch.msgid.link/20260901093245.344455-3-john.ogness@linutronix.de Signed-off-by: Petr Mladek --- kernel/printk/nbcon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index 354f274d8a42..c8502fc4f4e5 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -1782,7 +1782,7 @@ bool nbcon_alloc(struct console *con) } rcuwait_init(&con->rcuwait); - init_irq_work(&con->irq_work, nbcon_irq_work); + con->irq_work = IRQ_WORK_INIT_LAZY(nbcon_irq_work); atomic_long_set(&ACCESS_PRIVATE(con, nbcon_prev_seq), -1UL); nbcon_state_set(con, &state); From a61c6ae1dae2611082b831b4aaa780878099c012 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 24 Aug 2026 18:47:07 +0200 Subject: [PATCH 0555/2366] ceph: lock mutex in ceph_mds_check_access() MDS session OPEN handling replaces mdsc->s_cap_auths under mdsc->mutex, freeing the previous array and its strings. ceph_mds_check_access() traverses this array without holding the mutex. A concurrent session reopen can therefore free the array while it is being inspected, resulting in a use-after-free like this: Unable to handle kernel paging request at virtual address 003aaad64b2c8bb9 [...] Internal error: Oops: 0000000096000004 [#1] SMP Modules linked in: CPU: 56 UID: 2953037534 PID: 1253231 Comm: php-cgi8.4 Not tainted 6.18.45-i2-ampere #1146 NONE [..] pc : ceph_mds_check_access+0xd4/0x550 lr : ceph_mds_check_access+0xc8/0x550 [...] Call trace: ceph_mds_check_access+0xd4/0x550 (P) ceph_atomic_open+0x138/0xbe8 path_openat+0xa24/0xfa8 do_filp_open+0x94/0x158 do_sys_openat2+0x88/0xf8 Cc: stable@vger.kernel.org Fixes: 596afb0b8933 ("ceph: add ceph_mds_check_access() helper") Signed-off-by: Max Kellermann Reviewed-by: Alex Markuze Signed-off-by: Ilya Dryomov --- fs/ceph/mds_client.c | 4 ++++ fs/ceph/mds_client.h | 1 + 2 files changed, 5 insertions(+) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index a091f77cedaf..c4a35547dcc6 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -6600,11 +6600,13 @@ int ceph_mds_check_access(struct ceph_mds_client *mdsc, char *tpath, int mask) doutc(cl, "tpath '%s', mask %d, caller_uid %d, caller_gid %d\n", tpath, mask, caller_uid, caller_gid); + mutex_lock(&mdsc->mutex); for (i = 0; i < mdsc->s_cap_auths_num; i++) { struct ceph_mds_cap_auth *s = &mdsc->s_cap_auths[i]; err = ceph_mds_auth_match(mdsc, s, cred, tpath); if (err < 0) { + mutex_unlock(&mdsc->mutex); put_cred(cred); return err; } else if (err > 0) { @@ -6626,6 +6628,7 @@ int ceph_mds_check_access(struct ceph_mds_client *mdsc, char *tpath, int mask) doutc(cl, "root_squash_perms %d, rw_perms_s %p\n", root_squash_perms, rw_perms_s); if (root_squash_perms && rw_perms_s == NULL) { + mutex_unlock(&mdsc->mutex); doutc(cl, "access allowed\n"); return 0; } @@ -6640,6 +6643,7 @@ int ceph_mds_check_access(struct ceph_mds_client *mdsc, char *tpath, int mask) !!(mask & MAY_READ), !!(mask & MAY_WRITE)); } doutc(cl, "access denied\n"); + mutex_unlock(&mdsc->mutex); return -EACCES; } diff --git a/fs/ceph/mds_client.h b/fs/ceph/mds_client.h index 3c62e3c3530b..e7a262c9c2ab 100644 --- a/fs/ceph/mds_client.h +++ b/fs/ceph/mds_client.h @@ -604,6 +604,7 @@ struct ceph_mds_client { struct rw_semaphore pool_perm_rwsem; struct rb_root pool_perm_tree; + /* protected by mutex */ u32 s_cap_auths_num; struct ceph_mds_cap_auth *s_cap_auths; From f75987e543c243e19f49fe32ce870b2e24ab8c23 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Tue, 25 Aug 2026 11:41:38 -0400 Subject: [PATCH 0556/2366] libceph: remove pinning assertion in ceph_msg_data_iter_next() ceph_msg_data_iter_next() gets a page reference from iov_iter_get_pages2() only to immediately drop it, asserting that the page is pinned some other way. The assertion is the last caller of PageWriteback() in the tree, blocking removal of the PG_writeback page flag accessors. Remove the assertion, as it is a CONFIG_DEBUG_VM-only check of an assumption the FIXME comment already documents. Converting to iov_iter_extract_pages() instead was considered, but the messenger never releases what it extracts, so it would still rely entirely on the caller holding the pages. That would be just as much of an abuse of the API, so leave it as-is for now. Signed-off-by: Tal Zussman Reviewed-by: Christoph Hellwig Reviewed-by: Ilya Dryomov Signed-off-by: Ilya Dryomov --- net/ceph/messenger.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/ceph/messenger.c b/net/ceph/messenger.c index 9c1b6cf8c36f..212e7797f9e4 100644 --- a/net/ceph/messenger.c +++ b/net/ceph/messenger.c @@ -1003,7 +1003,6 @@ static struct page *ceph_msg_data_iter_next(struct ceph_msg_data_cursor *cursor, * we'll get an iov_iter_get_pages2 variant that doesn't take * page refs. Until then, just put the page ref. */ - VM_BUG_ON_PAGE(!PageWriteback(page) && page_count(page) < 2, page); put_page(page); *length = min_t(size_t, len, cursor->resid); From dc173b37415e8f738fc4de477490056b479ddc9f Mon Sep 17 00:00:00 2001 From: Alex Markuze Date: Thu, 27 Aug 2026 15:16:21 +0000 Subject: [PATCH 0557/2366] ceph: apply nearfull_sync option on remount ceph_parse_mount_param() stores nearfull_sync / nonearfull_sync on the temporary fs_context options, but ceph_reconfigure_fc() never copied CEPH_MOUNT_OPT_NEARFULL_SYNC onto the live mount. Remount therefore succeeded while writes and /proc/mounts kept the original-mount flag. Apply the flag the same way as ASYNC_DIROPS and SPARSEREAD so remount can enable or disable NEARFULL IOCB_DSYNC promotion. Fixes: c7a12c20bfba ("ceph: make nearfull sync writes opt-in") Signed-off-by: Alex Markuze Reviewed-by: Xiubo Li Signed-off-by: Ilya Dryomov --- fs/ceph/super.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/ceph/super.c b/fs/ceph/super.c index 15edea30dc8b..72935f665f11 100644 --- a/fs/ceph/super.c +++ b/fs/ceph/super.c @@ -1420,6 +1420,11 @@ static int ceph_reconfigure_fc(struct fs_context *fc) else ceph_clear_mount_opt(fsc, SPARSEREAD); + if (fsopt->flags & CEPH_MOUNT_OPT_NEARFULL_SYNC) + ceph_set_mount_opt(fsc, NEARFULL_SYNC); + else + ceph_clear_mount_opt(fsc, NEARFULL_SYNC); + if (strcmp_null(fsc->mount_options->mon_addr, fsopt->mon_addr)) { kfree(fsc->mount_options->mon_addr); fsc->mount_options->mon_addr = fsopt->mon_addr; From 3b54dbd119805361695cb50ca6a875f4c7518b74 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Wed, 26 Aug 2026 13:27:24 +0100 Subject: [PATCH 0558/2366] power: sequencing: Fix build issue with COMPILE_TEST The POWER_SEQUENCING_TH1520_GPU driver depends on (ARCH_THEAD && AUXILIARY_BUS) || COMPILE_TEST. This means when COMPILE_TEST=y and ARCH_THEAD is not set, the driver can still be built even though it requires AUXILIARY_BUS, which may not be selected in that configuration, leading to a build failure. Fix this by dropping AUXILIARY_BUS from the dependency and instead selecting it directly, so the dependency is satisfied regardless of whether COMPILE_TEST or ARCH_THEAD is enabled. Fixes: 1a7312b93ab0 ("power: sequencing: extend build coverage with COMPILE_TEST=y") Signed-off-by: Biju Das Link: https://patch.msgid.link/20260826122742.153643-3-biju.das.jz@bp.renesas.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig index 1c5f5820f5b7..226c62704d9b 100644 --- a/drivers/power/sequencing/Kconfig +++ b/drivers/power/sequencing/Kconfig @@ -29,7 +29,8 @@ config POWER_SEQUENCING_QCOM_WCN config POWER_SEQUENCING_TH1520_GPU tristate "T-HEAD TH1520 GPU power sequencing driver" - depends on (ARCH_THEAD && AUXILIARY_BUS) || COMPILE_TEST + depends on ARCH_THEAD || COMPILE_TEST + select AUXILIARY_BUS help Say Y here to enable the power sequencing driver for the TH1520 SoC GPU. This driver handles the complex clock and reset sequence From 544d85de4dc22c01badfd8cefa59829ce35c4858 Mon Sep 17 00:00:00 2001 From: Chris Lew Date: Thu, 27 Aug 2026 17:48:46 +0530 Subject: [PATCH 0559/2366] net: qrtr: Send HELLO message on endpoint register HELLO is currently handled entirely by the name server (NS): it is sent once as a broadcast when the NS initializes, and again as a reply whenever the NS receives an inbound HELLO from a remote. Some remote QRTR endpoints (e.g. an external WLAN chipset attached over MHI) operate in a slave role: they only ever send a HELLO in response to one they receive, and never initiate. Since the host cannot tell in advance which remotes behave this way, if the host also only replies, both sides wait on the other to speak first and no HELLO is ever exchanged, stalling further communication. To fix this: - Transfer HELLO handshake ownership to the core layer. A HELLO is now sent once, per endpoint, at registration time. - Schedule a delayed work item on endpoint registration to send a HELLO once the name server is bound. The work reschedules itself with a 100ms backoff if the name server socket is not yet bound or if allocating the control packet fails, so a transient startup condition does not abandon the handshake permanently. - Enforce HELLO-first ordering by dropping non-HELLO packets and returning -EAGAIN until the HELLO is confirmed sent, using bool hello_sent guarded by ep_lock to make the gate check atomic with xmit(). - Skip nodes with nid == QRTR_EP_NID_AUTO in bcast_enqueue(), to avoid broadcasting control packets with QRTR_EP_NID_AUTO as the destination node ID. - Remove say_hello() from the name server's ctrl_cmd_hello() handler and from qrtr_ns_init(); the core layer is now the sole sender of the outbound HELLO. This removes the NS's reply-on-receive behaviour without a replacement. Signed-off-by: Chris Lew Co-developed-by: Deepak Kumar Singh Signed-off-by: Deepak Kumar Singh Co-developed-by: Pranav Mahesh Phansalkar Signed-off-by: Pranav Mahesh Phansalkar Signed-off-by: David S. Miller --- net/qrtr/af_qrtr.c | 66 ++++++++++++++++++++++++++++++++++++++++++++-- net/qrtr/ns.c | 35 +----------------------- 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/net/qrtr/af_qrtr.c b/net/qrtr/af_qrtr.c index a30fa56e6aa3..78347c937af7 100644 --- a/net/qrtr/af_qrtr.c +++ b/net/qrtr/af_qrtr.c @@ -9,6 +9,7 @@ #include /* For TIOCINQ/OUTQ */ #include #include +#include #include @@ -120,8 +121,10 @@ static DEFINE_XARRAY_ALLOC(qrtr_ports); * @nid: node id * @qrtr_tx_flow: xarray of qrtr_tx_flow, keyed by node << 32 | port * @qrtr_tx_lock: lock for qrtr_tx_flow inserts + * @hello_sent: hello packet send successful * @rx_queue: receive queue * @item: list item for broadcast list + * @say_hello: delayed work for sending hello packet */ struct qrtr_node { struct mutex ep_lock; @@ -132,8 +135,11 @@ struct qrtr_node { struct xarray qrtr_tx_flow; struct mutex qrtr_tx_lock; /* for qrtr_tx_flow */ + bool hello_sent; + struct sk_buff_head rx_queue; struct list_head item; + struct delayed_work say_hello; }; /** @@ -187,6 +193,8 @@ static void __qrtr_node_release(struct kref *kref) list_del(&node->item); mutex_unlock(&qrtr_node_lock); + cancel_delayed_work_sync(&node->say_hello); + skb_queue_purge(&node->rx_queue); /* Free tx flow counters */ @@ -341,6 +349,14 @@ static int qrtr_node_enqueue(struct qrtr_node *node, struct sk_buff *skb, size_t len = skb->len; int rc, confirm_rx; + mutex_lock(&node->ep_lock); + if (!node->hello_sent && type != QRTR_TYPE_HELLO) { + mutex_unlock(&node->ep_lock); + kfree_skb(skb); + return -EAGAIN; + } + mutex_unlock(&node->ep_lock); + confirm_rx = qrtr_tx_wait(node, to->sq_node, to->sq_port, type); if (confirm_rx < 0) { kfree_skb(skb); @@ -353,7 +369,7 @@ static int qrtr_node_enqueue(struct qrtr_node *node, struct sk_buff *skb, hdr->src_node_id = cpu_to_le32(from->sq_node); hdr->src_port_id = cpu_to_le32(from->sq_port); if (to->sq_port == QRTR_PORT_CTRL) { - hdr->dst_node_id = cpu_to_le32(node->nid); + hdr->dst_node_id = cpu_to_le32(READ_ONCE(node->nid)); hdr->dst_port_id = cpu_to_le32(QRTR_PORT_CTRL); } else { hdr->dst_node_id = cpu_to_le32(to->sq_node); @@ -372,6 +388,8 @@ static int qrtr_node_enqueue(struct qrtr_node *node, struct sk_buff *skb, rc = node->ep->xmit(node->ep, skb); else kfree_skb(skb); + if (!rc && type == QRTR_TYPE_HELLO) + node->hello_sent = true; mutex_unlock(&node->ep_lock); } /* Need to ensure that a subsequent message carries the otherwise lost @@ -379,6 +397,9 @@ static int qrtr_node_enqueue(struct qrtr_node *node, struct sk_buff *skb, if (rc && confirm_rx) qrtr_tx_flow_failed(node, to->sq_node, to->sq_port); + if (rc == -EAGAIN && type == QRTR_TYPE_HELLO) + schedule_delayed_work(&node->say_hello, msecs_to_jiffies(100)); + return rc; } @@ -416,7 +437,7 @@ static void qrtr_node_assign(struct qrtr_node *node, unsigned int nid) spin_lock_irqsave(&qrtr_nodes_lock, flags); radix_tree_insert(&qrtr_nodes, nid, node); if (node->nid == QRTR_EP_NID_AUTO) - node->nid = nid; + WRITE_ONCE(node->nid, nid); spin_unlock_irqrestore(&qrtr_nodes_lock, flags); } @@ -570,6 +591,38 @@ static struct sk_buff *qrtr_alloc_ctrl_packet(struct qrtr_ctrl_pkt **pkt, return skb; } +static void qrtr_hello_work(struct work_struct *work) +{ + struct sockaddr_qrtr from = {AF_QIPCRTR, 0, QRTR_PORT_CTRL}; + struct sockaddr_qrtr to = {AF_QIPCRTR, 0, QRTR_PORT_CTRL}; + struct qrtr_ctrl_pkt *pkt; + struct qrtr_node *node; + struct qrtr_sock *ctrl; + struct sk_buff *skb; + + node = container_of(to_delayed_work(work), struct qrtr_node, say_hello); + + /* NS must be bound before we can send; retry with backoff if not ready */ + ctrl = qrtr_port_lookup(QRTR_PORT_CTRL); + if (!ctrl) { + schedule_delayed_work(&node->say_hello, msecs_to_jiffies(100)); + return; + } + + skb = qrtr_alloc_ctrl_packet(&pkt, GFP_KERNEL); + if (!skb) { + qrtr_port_put(ctrl); + schedule_delayed_work(&node->say_hello, msecs_to_jiffies(100)); + return; + } + + pkt->cmd = cpu_to_le32(QRTR_TYPE_HELLO); + from.sq_node = qrtr_local_nid; + to.sq_node = node->nid; + qrtr_node_enqueue(node, skb, QRTR_TYPE_HELLO, &from, &to); + qrtr_port_put(ctrl); +} + /** * qrtr_endpoint_register() - register a new endpoint * @ep: endpoint to register @@ -595,6 +648,9 @@ int qrtr_endpoint_register(struct qrtr_endpoint *ep, unsigned int nid) node->nid = QRTR_EP_NID_AUTO; node->ep = ep; + node->hello_sent = false; + INIT_DELAYED_WORK(&node->say_hello, qrtr_hello_work); + xa_init(&node->qrtr_tx_flow); mutex_init(&node->qrtr_tx_lock); @@ -605,6 +661,9 @@ int qrtr_endpoint_register(struct qrtr_endpoint *ep, unsigned int nid) mutex_unlock(&qrtr_node_lock); ep->node = node; + /* Initiate HELLO handshake from the core layer */ + schedule_delayed_work(&node->say_hello, 0); + return 0; } EXPORT_SYMBOL_GPL(qrtr_endpoint_register); @@ -879,6 +938,9 @@ static int qrtr_bcast_enqueue(struct qrtr_node *node, struct sk_buff *skb, mutex_lock(&qrtr_node_lock); list_for_each_entry(node, &qrtr_all_nodes, item) { + /* Skip nodes with no assigned node ID yet. */ + if (READ_ONCE(node->nid) == QRTR_EP_NID_AUTO) + continue; skbn = pskb_copy(skb, GFP_KERNEL); if (!skbn) break; diff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index c5e7e01db249..bcb090ee79d4 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -212,6 +212,7 @@ static void lookup_notify(struct sockaddr_qrtr *to, struct qrtr_server *srv, pr_err("failed to send lookup notification\n"); } +/* Announce the list of servers registered on the local node */ static int announce_servers(struct sockaddr_qrtr *sq) { struct qrtr_server *srv; @@ -326,38 +327,8 @@ static int server_del(struct qrtr_node *node, unsigned int port, bool bcast) return 0; } -static int say_hello(struct sockaddr_qrtr *dest) -{ - struct qrtr_ctrl_pkt pkt; - struct msghdr msg = { }; - struct kvec iv; - int ret; - - iv.iov_base = &pkt; - iv.iov_len = sizeof(pkt); - - memset(&pkt, 0, sizeof(pkt)); - pkt.cmd = cpu_to_le32(QRTR_TYPE_HELLO); - - msg.msg_name = (struct sockaddr *)dest; - msg.msg_namelen = sizeof(*dest); - - ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); - if (ret < 0) - pr_err("failed to send hello msg\n"); - - return ret; -} - -/* Announce the list of servers registered on the local node */ static int ctrl_cmd_hello(struct sockaddr_qrtr *sq) { - int ret; - - ret = say_hello(sq); - if (ret < 0) - return ret; - return announce_servers(sq); } @@ -774,10 +745,6 @@ int qrtr_ns_init(void) qrtr_ns.bcast_sq.sq_node = QRTR_NODE_BCAST; qrtr_ns.bcast_sq.sq_port = QRTR_PORT_CTRL; - ret = say_hello(&qrtr_ns.bcast_sq); - if (ret < 0) - goto err_wq; - /* As the qrtr ns socket owner and creator is the same module, we have * to decrease the qrtr module reference count to guarantee that it * remains zero after the ns socket is created, otherwise, executing From 355b6558dd7be049aff4f0d438b0128f91a982eb Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 30 Aug 2026 04:15:05 -0700 Subject: [PATCH 0560/2366] platform/x86: x86-android-tablets: fix Arizona GPIO swnode references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The standalone arizona_gpiochip_node was created when gpiolib supported matching a software node name against the GPIO chip label ("arizona"). Later, gpiolib replaced name matching with firmware node identity mapping (and eventually dropped the fallback mechanism), causing GPIO lookups on unattached software nodes to fail. In gpio-arizona, the GPIO chip inherits the firmware node of the parent codec device. Fix the lookups by pointing the GPIO property entries directly to the codec device software node (which is attached to the parent device) and dropping the obsolete arizona_gpiochip_node. Fixes: 611fd6cfe139 ("gpio: swnode: remove deprecated lookup mechanism") Assisted-by: LLM Signed-off-by: Dmitry Torokhov Tested-by: Hans de Goede # Yoga tab 2 1380, yt3 Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260830-x86-android-lenovo-swnode-v1-1-066a91acb4ba@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- .../platform/x86/x86-android-tablets/lenovo.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index 8d825e0b4661..c34f62bdf8f9 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -61,10 +61,6 @@ static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = { .initial_brightness = 128, }; -static const struct software_node arizona_gpiochip_node = { - .name = "arizona", -}; - static const struct software_node crystalcove_gpiochip_node = { .name = "gpio_crystalcove", }; @@ -416,15 +412,17 @@ static const struct platform_device_info lenovo_yoga_tab2_830_1050_pdevs[] __ini #define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00" +static const struct software_node lenovo_yoga_tab2_830_1050_wm5102; + static const struct property_entry lenovo_yoga_tab2_830_1050_wm1502_props[] = { PROPERTY_ENTRY_GPIO("reset-gpios", &crystalcove_gpiochip_node, 3, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", &baytrail_gpiochip_nodes[1], 23, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", - &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), + &lenovo_yoga_tab2_830_1050_wm5102, 2, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", - &arizona_gpiochip_node, 4, GPIO_ACTIVE_LOW), + &lenovo_yoga_tab2_830_1050_wm5102, 4, GPIO_ACTIVE_LOW), { } }; @@ -434,7 +432,6 @@ static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = { static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = { &crystalcove_gpiochip_node, - &arizona_gpiochip_node, &lenovo_yoga_tab2_830_1050_wm5102, &generic_lipo_hv_4v35_battery_node, NULL @@ -985,13 +982,15 @@ static struct arizona_pdata lenovo_yt3_wm5102_pdata = { }, }; +static const struct software_node lenovo_yt3_wm5102; + static const struct property_entry lenovo_yt3_wm1502_props[] = { PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", &cherryview_gpiochip_nodes[0], 75, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", &cherryview_gpiochip_nodes[0], 81, GPIO_ACTIVE_HIGH), PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_HIGH), - PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &lenovo_yt3_wm5102, 2, GPIO_ACTIVE_HIGH), { } }; @@ -1001,7 +1000,6 @@ static const struct software_node lenovo_yt3_wm5102 = { }; static const struct software_node *lenovo_yt3_swnodes[] = { - &arizona_gpiochip_node, &lenovo_yt3_wm5102, NULL }; From 144113b0a70fa18033a747ee5db6803308f7688c Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 30 Aug 2026 04:15:06 -0700 Subject: [PATCH 0561/2366] platform/x86: x86-android-tablets: hold device reference for secondary fwnode teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In gpio_secondary_fwnode_init(), acpi_bus_find_device_by_name() returns a device reference, but the local dev variable is declared with __free(put_device), dropping the reference at the end of each iteration. Meanwhile, devm_add_action_or_reset() saves the dev pointer for gpio_secondary_unset() without incrementing its reference count, which could lead to a use-after-free during driver teardown if the device is released in the interim. Acquire an explicit device reference with get_device() when registering the devres action, and drop it with put_device() inside gpio_secondary_unset(). Fixes: 1448c2d2ca5c ("platform/x86: x86-android-tablets: enable fwnode matching of GPIO chips") Assisted-by: LLM Signed-off-by: Dmitry Torokhov Tested-by: Hans de Goede # Yoga tab 2 1380, yt3 Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260830-x86-android-lenovo-swnode-v1-2-066a91acb4ba@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/x86-android-tablets/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index 5db794d65eb5..722c0ae4ecd1 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -367,6 +367,7 @@ static void gpio_secondary_unset(void *data) struct device *dev = data; set_secondary_fwnode(dev, NULL); + put_device(dev); } static void gpio_secondary_unregister_node_group(void *data) @@ -409,7 +410,7 @@ static int gpio_secondary_fwnode_init(struct device *parent) set_secondary_fwnode(dev, fwnode); - ret = devm_add_action_or_reset(parent, gpio_secondary_unset, dev); + ret = devm_add_action_or_reset(parent, gpio_secondary_unset, get_device(dev)); if (ret) return ret; } From aab060ec969c3859b81f80f3444fd5a2edfc3cf5 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 30 Aug 2026 04:15:07 -0700 Subject: [PATCH 0562/2366] platform/x86: x86-android-tablets: pass node group to gpio_secondary_fwnode_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently gpio_secondary_fwnode_init() uses a file-scope static gpiochip_node_group variable initialized in probe. Pass the node group directly to gpio_secondary_fwnode_init() as an argument instead of using a global variable. This allows reusing the helper for additional GPIO controllers in subsequent patches. Assisted-by: LLM Signed-off-by: Dmitry Torokhov Tested-by: Hans de Goede # Yoga tab 2 1380, yt3 Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260830-x86-android-lenovo-swnode-v1-3-066a91acb4ba@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/x86-android-tablets/core.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index 722c0ae4ecd1..45673bfc0b7b 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -156,7 +156,6 @@ static struct platform_device **pdevs; static struct serdev_device **serdevs; static const struct software_node **gpio_button_swnodes; static const struct software_node **swnode_group; -static const struct software_node **gpiochip_node_group; static void (*exit_handler)(void); static __init struct i2c_adapter * @@ -377,26 +376,27 @@ static void gpio_secondary_unregister_node_group(void *data) software_node_unregister_node_group(nodes); } -static int gpio_secondary_fwnode_init(struct device *parent) +static int gpio_secondary_fwnode_init(struct device *parent, + const struct software_node * const *node_group) { const struct software_node *const *swnode; struct fwnode_handle *fwnode; int ret; - if (!gpiochip_node_group) + if (!node_group) return 0; - ret = software_node_register_node_group(gpiochip_node_group); + ret = software_node_register_node_group(node_group); if (ret) return ret; ret = devm_add_action_or_reset(parent, gpio_secondary_unregister_node_group, - gpiochip_node_group); + (void *)node_group); if (ret) return ret; - for (swnode = gpiochip_node_group; *swnode; swnode++) { + for (swnode = node_group; *swnode; swnode++) { struct device *dev __free(put_device) = acpi_bus_find_device_by_name((*swnode)->name); if (!dev) @@ -453,6 +453,7 @@ static void x86_android_tablet_remove(struct platform_device *pdev) static __init int x86_android_tablet_probe(struct platform_device *pdev) { + const struct software_node * const *gpiochip_node_group; const struct x86_dev_info *dev_info; const struct dmi_system_id *id; int i, ret = 0; @@ -484,7 +485,7 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev) break; } - ret = gpio_secondary_fwnode_init(&pdev->dev); + ret = gpio_secondary_fwnode_init(&pdev->dev, gpiochip_node_group); if (ret) { x86_android_tablet_remove(pdev); return ret; From 7872c625cd83a0247821cedd6c6f63938d4bddbc Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 30 Aug 2026 04:15:08 -0700 Subject: [PATCH 0563/2366] platform/x86: x86-android-tablets: add Crystal Cove GPIO swnode support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The crystalcove_gpiochip_node was created when gpiolib supported matching a software node name against the GPIO chip label. Later, gpiolib replaced name matching with firmware node identity mapping, and support for dynamically attaching software nodes to ACPI GPIO chips as secondary firmware nodes was added for Baytrail and Cherryview, but Crystal Cove ("INT33FD:00") was omitted. Consequently, lookups on the unattached Crystal Cove software node fail. Add support for attaching crystalcove_gpiochip_node to the INT33FD:00 ACPI device as a secondary firmware node, and enable it on Lenovo Yoga Tab 2 models. Fixes: 611fd6cfe139 ("gpio: swnode: remove deprecated lookup mechanism") Assisted-by: LLM Signed-off-by: Dmitry Torokhov Tested-by: Hans de Goede # Yoga tab 2 1380, yt3 Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260830-x86-android-lenovo-swnode-v1-4-066a91acb4ba@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/x86-android-tablets/core.c | 17 +++++++++++++++++ .../platform/x86/x86-android-tablets/lenovo.c | 6 ++---- .../x86-android-tablets/x86-android-tablets.h | 2 ++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index 45673bfc0b7b..b028af1c9942 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -361,6 +361,15 @@ static const struct software_node *cherryview_gpiochip_node_group[] = { NULL }; +const struct software_node crystalcove_gpiochip_node = { + .name = "INT33FD:00", +}; + +static const struct software_node *crystalcove_gpiochip_node_group[] = { + &crystalcove_gpiochip_node, + NULL +}; + static void gpio_secondary_unset(void *data) { struct device *dev = data; @@ -491,6 +500,14 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev) return ret; } + if (dev_info->has_crystalcove) { + ret = gpio_secondary_fwnode_init(&pdev->dev, crystalcove_gpiochip_node_group); + if (ret) { + x86_android_tablet_remove(pdev); + return ret; + } + } + ret = software_node_register_node_group(dev_info->swnode_group); if (ret) { x86_android_tablet_remove(pdev); diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index c34f62bdf8f9..54068a0f4633 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -61,9 +61,6 @@ static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = { .initial_brightness = 128, }; -static const struct software_node crystalcove_gpiochip_node = { - .name = "gpio_crystalcove", -}; /* Lenovo Yoga Book X90F / X90L's Android factory image has everything hardcoded */ @@ -431,7 +428,6 @@ static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = { }; static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = { - &crystalcove_gpiochip_node, &lenovo_yoga_tab2_830_1050_wm5102, &generic_lipo_hv_4v35_battery_node, NULL @@ -454,6 +450,7 @@ const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = { .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, .modules = lenovo_yoga_tab2_modules, + .has_crystalcove = true, .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, .init = lenovo_yoga_tab2_830_1050_init, .exit = lenovo_yoga_tab2_830_1050_exit, @@ -799,6 +796,7 @@ const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = { .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, .modules = lenovo_yoga_tab2_modules, + .has_crystalcove = true, .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, .init = lenovo_yoga_tab2_1380_init, .exit = lenovo_yoga_tab2_830_1050_exit, diff --git a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h index c756961ae5fd..6e6534f8fa6c 100644 --- a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +++ b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h @@ -96,6 +96,7 @@ struct x86_dev_info { int (*init)(struct device *dev); void (*exit)(void); bool use_pci; + bool has_crystalcove; enum x86_gpiochip_type gpiochip_type; }; @@ -107,6 +108,7 @@ int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data); /* Software nodes representing GPIO chips used by various tablets */ extern const struct software_node baytrail_gpiochip_nodes[]; extern const struct software_node cherryview_gpiochip_nodes[]; +extern const struct software_node crystalcove_gpiochip_node; /* * Extern declarations of x86_dev_info structs so there can be a single From 74884436a53df0bbaf6d92d2922f643a4581b247 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 30 Aug 2026 04:15:09 -0700 Subject: [PATCH 0564/2366] platform/x86: x86-android-tablets: drop redundant swnode group on YT3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WM5102 codec software node lenovo_yt3_wm5102 is assigned to the board info swnode pointer in lenovo_yt3_spi_devs. When spi_new_device() instantiates the SPI device, device_add_software_node() automatically registers the software node. Therefore, explicitly registering lenovo_yt3_swnodes via software_node_register_node_group() and listing it in .swnode_group is redundant. Drop the unused node group and registration. Assisted-by: LLM Signed-off-by: Dmitry Torokhov Tested-by: Hans de Goede # Yoga tab 2 1380, yt3 Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260830-x86-android-lenovo-swnode-v1-5-066a91acb4ba@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/x86-android-tablets/lenovo.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index 54068a0f4633..cd8cee1f4aed 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -997,10 +997,6 @@ static const struct software_node lenovo_yt3_wm5102 = { .name = "wm5102", }; -static const struct software_node *lenovo_yt3_swnodes[] = { - &lenovo_yt3_wm5102, - NULL -}; static const struct x86_spi_dev_info lenovo_yt3_spi_devs[] __initconst = { { @@ -1068,7 +1064,6 @@ const struct x86_dev_info lenovo_yt3_info __initconst = { .i2c_client_count = ARRAY_SIZE(lenovo_yt3_i2c_clients), .spi_dev_info = lenovo_yt3_spi_devs, .spi_dev_count = ARRAY_SIZE(lenovo_yt3_spi_devs), - .swnode_group = lenovo_yt3_swnodes, .modules = lenovo_yt3_modules, .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW, .init = lenovo_yt3_init, From 312fd3f3a85b89aa0d4fb5417043d640daa3732c Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 30 Aug 2026 04:15:10 -0700 Subject: [PATCH 0565/2366] platform/x86: x86-android-tablets: use shared battery swnode group on Yoga Tab 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WM5102 codec software node lenovo_yoga_tab2_830_1050_wm5102 is registered automatically when attached to the codec device via device_add_software_node() in lenovo_yoga_tab2_830_1050_init_codec(). Including it in lenovo_yoga_tab2_830_1050_swnodes is therefore redundant, leaving generic_lipo_hv_4v35_battery_node as the only node needing registration. Switch lenovo_yoga_tab2_830_1050_info and lenovo_yoga_tab2_1380_info to use the shared generic_lipo_hv_4v35_battery_swnodes group directly and drop the custom lenovo_yoga_tab2_830_1050_swnodes array. Assisted-by: LLM Signed-off-by: Dmitry Torokhov Tested-by: Hans de Goede # Yoga tab 2 1380, yt3 Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260830-x86-android-lenovo-swnode-v1-6-066a91acb4ba@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/x86-android-tablets/lenovo.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c index cd8cee1f4aed..52d96ae89078 100644 --- a/drivers/platform/x86/x86-android-tablets/lenovo.c +++ b/drivers/platform/x86/x86-android-tablets/lenovo.c @@ -427,12 +427,6 @@ static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = { .properties = lenovo_yoga_tab2_830_1050_wm1502_props, }; -static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = { - &lenovo_yoga_tab2_830_1050_wm5102, - &generic_lipo_hv_4v35_battery_node, - NULL -}; - static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev); static void lenovo_yoga_tab2_830_1050_exit(void); @@ -448,7 +442,7 @@ const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = { .pdev_info = lenovo_yoga_tab2_830_1050_pdevs, .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_pdevs), .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, - .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, + .swnode_group = generic_lipo_hv_4v35_battery_swnodes, .modules = lenovo_yoga_tab2_modules, .has_crystalcove = true, .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, @@ -794,7 +788,7 @@ const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = { .pdev_info = lenovo_yoga_tab2_1380_pdevs, .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_pdevs), .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, - .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, + .swnode_group = generic_lipo_hv_4v35_battery_swnodes, .modules = lenovo_yoga_tab2_modules, .has_crystalcove = true, .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, From c37ba8fe00f264eee2fd18b0bff7c5f188136c51 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:51 +0200 Subject: [PATCH 0566/2366] ASoC: ux500: Fix MSP stream lifecycle handling The trigger stop path drops the direction busy flag even though ALSA still owns the stream until shutdown. A later trigger cannot reliably restart it, shutdown may leave the block configured, and a second stream may overwrite shared duplex configuration. Keep configured and running directions as separate state. Program shared settings only for the first direction, require a compatible configuration for the other half of a duplex stream, and enable the frame generator only while a provider stream is running. Also fix the RX-disable direction test and preserve the other direction multichannel setup. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-1-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 8 +- sound/soc/ux500/ux500_msp_i2s.c | 179 ++++++++++++++++++++++++-------- sound/soc/ux500/ux500_msp_i2s.h | 4 + 3 files changed, 147 insertions(+), 44 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 499e826d7120..994422e72512 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -34,8 +34,10 @@ static int setup_pcm_multichan(struct snd_soc_dai *dai, if (drvdata->slots > 1) { msp_config->multichannel_configured = 1; - multi->tx_multichannel_enable = true; - multi->rx_multichannel_enable = true; + multi->tx_multichannel_enable = + msp_config->direction & MSP_DIR_TX; + multi->rx_multichannel_enable = + msp_config->direction & MSP_DIR_RX; multi->rx_comparison_enable_mode = MSP_COMPARISON_DISABLED; multi->tx_channel_0_enable = drvdata->tx_mask; @@ -192,6 +194,7 @@ static int setup_clocking(struct snd_soc_dai *dai, case SND_SOC_DAIFMT_BC_FC: dev_dbg(dai->dev, "%s: Codec is master.\n", __func__); + msp_config->clock_provider = false; msp_config->iodelay = 0x20; msp_config->rx_fsync_sel = 0; msp_config->tx_fsync_sel = 1 << TFSSEL_SHIFT; @@ -204,6 +207,7 @@ static int setup_clocking(struct snd_soc_dai *dai, case SND_SOC_DAIFMT_BP_FP: dev_dbg(dai->dev, "%s: Codec is slave.\n", __func__); + msp_config->clock_provider = true; msp_config->tx_clk_sel = TX_CLK_SEL_SRG; msp_config->tx_fsync_sel = TX_SYNC_SRG_PROG; msp_config->rx_clk_sel = RX_CLK_SEL_SRG; diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index fbfeefa418ca..ec6f0874294a 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -344,20 +344,27 @@ static int configure_multichannel(struct ux500_msp *msp, return 0; } -static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config) +static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config, + bool first) { - int status = 0; - u32 reg_val_DMACR, reg_val_GCR; + int status; + u32 reg_val_DMACR; /* Configure msp with protocol dependent settings */ - configure_protocol(msp, config); - setup_bitclk(msp, config); + status = configure_protocol(msp, config); + if (status) + return status; + + if (first && config->clock_provider) { + status = setup_bitclk(msp, config); + if (status) + return status; + } + if (config->multichannel_configured == 1) { status = configure_multichannel(msp, config); if (status) - dev_warn(msp->dev, - "%s: WARN: configure_multichannel failed (%d)!\n", - __func__, status); + return status; } reg_val_DMACR = readl(msp->registers + MSP_DMACR); @@ -369,11 +376,7 @@ static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config) writel(config->iodelay, msp->registers + MSP_IODLY); - /* Enable frame generation logic */ - reg_val_GCR = readl(msp->registers + MSP_GCR); - writel(reg_val_GCR | FRAME_GEN_ENABLE, msp->registers + MSP_GCR); - - return status; + return 0; } static void flush_fifo_rx(struct ux500_msp *msp) @@ -411,12 +414,36 @@ static void flush_fifo_tx(struct ux500_msp *msp) writel(reg_val_GCR, msp->registers + MSP_GCR); } +static bool ux500_msp_config_compatible(struct ux500_msp *msp, + struct ux500_msp_config *config) +{ + struct ux500_msp_config *active = &msp->config; + + return active->f_inputclk == config->f_inputclk && + active->tx_clk_sel == config->tx_clk_sel && + active->rx_clk_sel == config->rx_clk_sel && + active->srg_clk_sel == config->srg_clk_sel && + active->rx_fsync_pol == config->rx_fsync_pol && + active->tx_fsync_pol == config->tx_fsync_pol && + active->rx_fsync_sel == config->rx_fsync_sel && + active->tx_fsync_sel == config->tx_fsync_sel && + active->default_protdesc == config->default_protdesc && + active->protocol == config->protocol && + active->frame_freq == config->frame_freq && + active->data_size == config->data_size && + active->def_elem_len == config->def_elem_len && + active->clock_provider == config->clock_provider && + !memcmp(&active->protdesc, &config->protdesc, + sizeof(active->protdesc)); +} + int ux500_msp_i2s_open(struct ux500_msp *msp, struct ux500_msp_config *config) { u32 old_reg, new_reg, mask; int res; unsigned int tx_sel, rx_sel, tx_busy, rx_busy; + bool first; if (in_interrupt()) { dev_err(msp->dev, @@ -444,40 +471,66 @@ int ux500_msp_i2s_open(struct ux500_msp *msp, return -EBUSY; } - msp->dir_busy |= (tx_sel ? MSP_DIR_TX : 0) | (rx_sel ? MSP_DIR_RX : 0); + first = !msp->dir_busy; + if (!first && !ux500_msp_config_compatible(msp, config)) { + dev_err(msp->dev, "%s: Incompatible duplex configuration\n", + __func__); + return -EBUSY; + } - /* First do the global config register */ - mask = RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FSYNC_MASK | - TX_FSYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK | - RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | SRG_CLK_SEL_MASK | - LOOPBACK_MASK | TX_EXTRA_DELAY_MASK; + if (first) { + /* First do the global config register */ + mask = RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FSYNC_MASK | + TX_FSYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK | + RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | + SRG_CLK_SEL_MASK | LOOPBACK_MASK | TX_EXTRA_DELAY_MASK; - new_reg = (config->tx_clk_sel | config->rx_clk_sel | - config->rx_fsync_pol | config->tx_fsync_pol | - config->rx_fsync_sel | config->tx_fsync_sel | - config->rx_fifo_config | config->tx_fifo_config | - config->srg_clk_sel | config->loopback_enable | - config->tx_data_enable); + new_reg = config->tx_clk_sel | config->rx_clk_sel | + config->rx_fsync_pol | config->tx_fsync_pol | + config->rx_fsync_sel | config->tx_fsync_sel | + config->rx_fifo_config | config->tx_fifo_config | + config->srg_clk_sel | config->loopback_enable | + config->tx_data_enable; - old_reg = readl(msp->registers + MSP_GCR); - old_reg &= ~mask; - new_reg |= old_reg; - writel(new_reg, msp->registers + MSP_GCR); + old_reg = readl(msp->registers + MSP_GCR); + old_reg &= ~mask; + new_reg |= old_reg; + writel(new_reg, msp->registers + MSP_GCR); + } - res = enable_msp(msp, config); + res = enable_msp(msp, config, first); if (res < 0) { dev_err(msp->dev, "%s: ERROR: enable_msp failed (%d)!\n", __func__, res); - return -EBUSY; + if (tx_sel) + writel(0, msp->registers + MSP_TCF); + if (rx_sel) + writel(0, msp->registers + MSP_RCF); + if (first) { + writel(0, msp->registers + MSP_GCR); + writel(0, msp->registers + MSP_DMACR); + writel(0, msp->registers + MSP_SRG); + writel(0, msp->registers + MSP_MCR); + } + return res; + } + + msp->dir_busy |= config->direction; + if (first) { + msp->config = *config; + msp->clock_provider = config->clock_provider; } if (config->loopback_enable & 0x80) msp->loopback_enable = 1; /* Flush FIFOs */ - flush_fifo_tx(msp); - flush_fifo_rx(msp); + if (tx_sel) + flush_fifo_tx(msp); + if (rx_sel) + flush_fifo_rx(msp); - msp->msp_state = MSP_STATE_CONFIGURED; + if (!msp->dir_running) + msp->msp_state = MSP_STATE_CONFIGURED; return 0; } @@ -494,7 +547,6 @@ static void disable_msp_rx(struct ux500_msp *msp) ~(RX_SERVICE_INT | RX_OVERRUN_ERROR_INT), msp->registers + MSP_IMSC); - msp->dir_busy &= ~MSP_DIR_RX; } static void disable_msp_tx(struct ux500_msp *msp) @@ -510,7 +562,6 @@ static void disable_msp_tx(struct ux500_msp *msp) ~(TX_SERVICE_INT | TX_UNDERRUN_ERR_INT), msp->registers + MSP_IMSC); - msp->dir_busy &= ~MSP_DIR_TX; } static int disable_msp(struct ux500_msp *msp, unsigned int dir) @@ -520,7 +571,7 @@ static int disable_msp(struct ux500_msp *msp, unsigned int dir) reg_val_GCR = readl(msp->registers + MSP_GCR); disable_tx = dir & MSP_DIR_TX; - disable_rx = dir & MSP_DIR_TX; + disable_rx = dir & MSP_DIR_RX; if (disable_tx && disable_rx) { reg_val_GCR = readl(msp->registers + MSP_GCR); writel(reg_val_GCR | LOOPBACK_MASK, @@ -553,7 +604,15 @@ static int disable_msp(struct ux500_msp *msp, unsigned int dir) int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, int direction) { - u32 reg_val_GCR, enable_bit; + u32 reg_val_DMACR, reg_val_GCR, dma_enable_bit, enable_bit; + unsigned int dir; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dir = MSP_DIR_TX; + else if (direction == SNDRV_PCM_STREAM_CAPTURE) + dir = MSP_DIR_RX; + else + return -EINVAL; if (msp->msp_state == MSP_STATE_IDLE) { dev_err(msp->dev, "%s: ERROR: MSP is not configured!\n", @@ -565,21 +624,44 @@ int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, int direction) case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if (direction == SNDRV_PCM_STREAM_PLAYBACK) + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { enable_bit = TX_ENABLE; - else + dma_enable_bit = TX_DMA_ENABLE; + } else { enable_bit = RX_ENABLE; + dma_enable_bit = RX_DMA_ENABLE; + } + if (!(msp->dir_busy & dir)) + return -EINVAL; + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + writel(reg_val_DMACR | dma_enable_bit, + msp->registers + MSP_DMACR); reg_val_GCR = readl(msp->registers + MSP_GCR); + if (msp->clock_provider) + enable_bit |= FRAME_GEN_ENABLE; writel(reg_val_GCR | enable_bit, msp->registers + MSP_GCR); + msp->dir_running |= dir; + msp->msp_state = MSP_STATE_RUNNING; break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (direction == SNDRV_PCM_STREAM_PLAYBACK) + if (!(msp->dir_busy & dir)) + return -EINVAL; + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { disable_msp_tx(msp); - else + msp->dir_running &= ~MSP_DIR_TX; + } else { disable_msp_rx(msp); + msp->dir_running &= ~MSP_DIR_RX; + } + if (!msp->dir_running) { + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~FRAME_GEN_ENABLE, + msp->registers + MSP_GCR); + msp->msp_state = MSP_STATE_CONFIGURED; + } break; default: return -EINVAL; @@ -594,7 +676,18 @@ int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir) dev_dbg(msp->dev, "%s: Enter (dir = 0x%01x).\n", __func__, dir); + if (!dir || dir & ~(MSP_DIR_TX | MSP_DIR_RX) || + (msp->dir_busy & dir) != dir) + return -EINVAL; + status = disable_msp(msp, dir); + msp->dir_busy &= ~dir; + msp->dir_running &= ~dir; + if (msp->dir_busy && !msp->dir_running) { + writel(readl(msp->registers + MSP_GCR) & ~FRAME_GEN_ENABLE, + msp->registers + MSP_GCR); + msp->msp_state = MSP_STATE_CONFIGURED; + } if (msp->dir_busy == 0) { /* disable sample rate and frame generators */ msp->msp_state = MSP_STATE_IDLE; @@ -618,6 +711,8 @@ int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir) writel(0, msp->registers + MSP_RCE1); writel(0, msp->registers + MSP_RCE2); writel(0, msp->registers + MSP_RCE3); + memset(&msp->config, 0, sizeof(msp->config)); + msp->clock_provider = false; } return status; diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h index 69d4ebc409fc..d75a0974369a 100644 --- a/sound/soc/ux500/ux500_msp_i2s.h +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -460,6 +460,7 @@ struct ux500_msp_config { enum msp_data_size data_size; unsigned int def_elem_len; unsigned int iodelay; + bool clock_provider; }; struct ux500_msp { @@ -470,8 +471,11 @@ struct ux500_msp { enum msp_state msp_state; int def_elem_len; unsigned int dir_busy; + unsigned int dir_running; int loopback_enable; unsigned int f_bitclk; + bool clock_provider; + struct ux500_msp_config config; }; int ux500_msp_i2s_init_msp(struct platform_device *pdev, From 3415421a2b0bc4e32bb5a9df24ed7863512d47a7 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:52 +0200 Subject: [PATCH 0567/2366] ASoC: ux500: Propagate MSP setup errors The prepare callback continues with a partly initialized configuration when format setup fails. Probe likewise tests the allocated pointer instead of the return value, so an MMIO resource or mapping failure can be ignored after allocation succeeds. Return configuration failures from prepare and test the MSP initialization result directly. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-2-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 994422e72512..29d25a3f6f40 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -468,7 +468,9 @@ static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, dev_dbg(dai->dev, "%s: MSP %d (%s): Enter (rate = %d).\n", __func__, dai->id, snd_pcm_stream_str(substream), runtime->rate); - setup_msp_config(substream, dai, &msp_config); + ret = setup_msp_config(substream, dai, &msp_config); + if (ret) + return ret; ret = ux500_msp_i2s_open(drvdata->msp, &msp_config); if (ret < 0) { @@ -764,7 +766,7 @@ static int ux500_msp_drv_probe(struct platform_device *pdev) } ret = ux500_msp_i2s_init_msp(pdev, &drvdata->msp); - if (!drvdata->msp) { + if (ret) { dev_err(&pdev->dev, "%s: ERROR: Failed to init MSP-struct (%d)!", __func__, ret); From 94c18cea657c48680e4ee20b635b6c01f3eb352e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:53 +0200 Subject: [PATCH 0568/2366] ASoC: ux500: Correct MSP frame and bit clock setup FRPER plus one is the number of bit clocks in a frame. It must follow the configured slot count and width. The legacy rate-dependent constants produce malformed frames; notably, a 16-slot, 16-bit frame is programmed as 278 rather than 256 clocks. Derive the frame period from the TDM geometry and use the real functional clock rate. Validate that the requested bit clock has an exact, representable divider, program SCKDIV as divider minus one, and report the resulting bit clock using that same divisor. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-3-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 75 +++++++-------------------------- sound/soc/ux500/ux500_msp_dai.h | 11 ----- sound/soc/ux500/ux500_msp_i2s.c | 54 ++++++++++++++---------- sound/soc/ux500/ux500_msp_i2s.h | 2 - 4 files changed, 46 insertions(+), 96 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 29d25a3f6f40..56d5591e2269 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -59,72 +59,21 @@ static int setup_pcm_multichan(struct snd_soc_dai *dai, return 0; } -static int setup_frameper(struct snd_soc_dai *dai, unsigned int rate, - struct msp_protdesc *prot_desc) +static void setup_frameper(struct snd_soc_dai *dai, + struct msp_protdesc *prot_desc) { struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); - switch (drvdata->slots) { - case 1: - switch (rate) { - case 8000: - prot_desc->frame_period = - FRAME_PER_SINGLE_SLOT_8_KHZ; - break; - - case 16000: - prot_desc->frame_period = - FRAME_PER_SINGLE_SLOT_16_KHZ; - break; - - case 44100: - prot_desc->frame_period = - FRAME_PER_SINGLE_SLOT_44_1_KHZ; - break; - - case 48000: - prot_desc->frame_period = - FRAME_PER_SINGLE_SLOT_48_KHZ; - break; - - default: - dev_err(dai->dev, - "%s: Error: Unsupported sample-rate (freq = %d)!\n", - __func__, rate); - return -EINVAL; - } - break; - - case 2: - prot_desc->frame_period = FRAME_PER_2_SLOTS; - break; - - case 8: - prot_desc->frame_period = FRAME_PER_8_SLOTS; - break; - - case 16: - prot_desc->frame_period = FRAME_PER_16_SLOTS; - break; - default: - dev_err(dai->dev, - "%s: Error: Unsupported slot-count (slots = %d)!\n", - __func__, drvdata->slots); - return -EINVAL; - } - - prot_desc->clocks_per_frame = - prot_desc->frame_period+1; + prot_desc->clocks_per_frame = drvdata->slots * drvdata->slot_width; + prot_desc->frame_period = prot_desc->clocks_per_frame - 1; dev_dbg(dai->dev, "%s: Clocks per frame: %u\n", __func__, prot_desc->clocks_per_frame); - - return 0; } -static int setup_pcm_framing(struct snd_soc_dai *dai, unsigned int rate, - struct msp_protdesc *prot_desc) +static int setup_pcm_framing(struct snd_soc_dai *dai, + struct msp_protdesc *prot_desc) { struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); @@ -165,7 +114,9 @@ static int setup_pcm_framing(struct snd_soc_dai *dai, unsigned int rate, prot_desc->tx_elem_len_2 = MSP_ELEM_LEN_16; prot_desc->rx_elem_len_2 = MSP_ELEM_LEN_16; - return setup_frameper(dai, rate, prot_desc); + setup_frameper(dai, prot_desc); + + return 0; } static int setup_clocking(struct snd_soc_dai *dai, @@ -366,7 +317,7 @@ static int setup_msp_config(struct snd_pcm_substream *substream, if (ret < 0) return ret; - ret = setup_pcm_framing(dai, runtime->rate, prot_desc); + ret = setup_pcm_framing(dai, prot_desc); if (ret < 0) return ret; @@ -735,7 +686,6 @@ static int ux500_msp_drv_probe(struct platform_device *pdev) drvdata->tx_mask = 0x01; drvdata->rx_mask = 0x01; drvdata->slot_width = 16; - drvdata->master_clk = MSP_INPUT_FREQ_APB; drvdata->reg_vape = devm_regulator_get(&pdev->dev, "v-ape"); if (IS_ERR(drvdata->reg_vape)) { @@ -764,6 +714,11 @@ static int ux500_msp_drv_probe(struct platform_device *pdev) __func__, ret); return ret; } + drvdata->master_clk = clk_get_rate(drvdata->clk); + if (!drvdata->master_clk) { + dev_err(&pdev->dev, "MSP clock has no rate\n"); + return -EINVAL; + } ret = ux500_msp_i2s_init_msp(pdev, &drvdata->msp); if (ret) { diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h index 30bf70838196..19058c238420 100644 --- a/sound/soc/ux500/ux500_msp_dai.h +++ b/sound/soc/ux500/ux500_msp_dai.h @@ -22,17 +22,6 @@ #define UX500_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) -#define FRAME_PER_SINGLE_SLOT_8_KHZ 31 -#define FRAME_PER_SINGLE_SLOT_16_KHZ 124 -#define FRAME_PER_SINGLE_SLOT_44_1_KHZ 63 -#define FRAME_PER_SINGLE_SLOT_48_KHZ 49 -#define FRAME_PER_2_SLOTS 31 -#define FRAME_PER_8_SLOTS 138 -#define FRAME_PER_16_SLOTS 277 - -#define UX500_MSP_INTERNAL_CLOCK_FREQ 40000000 -#define UX500_MSP1_INTERNAL_CLOCK_FREQ UX500_MSP_INTERNAL_CLOCK_FREQ - #define UX500_MSP_MIN_CHANNELS 1 #define UX500_MSP_MAX_CHANNELS 8 diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index ec6f0874294a..ef41de92d8e7 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -212,35 +212,20 @@ static int configure_protocol(struct ux500_msp *msp, static int setup_bitclk(struct ux500_msp *msp, struct ux500_msp_config *config) { + struct msp_protdesc *protdesc; + u64 desired_bitclk; + unsigned int bitclk; u32 reg_val_GCR; - u32 frame_per = 0; - u32 sck_div = 0; - u32 frame_width = 0; - u32 temp_reg = 0; - struct msp_protdesc *protdesc = NULL; + u32 sck_div; + u32 temp_reg; reg_val_GCR = readl(msp->registers + MSP_GCR); writel(reg_val_GCR & ~SRG_ENABLE, msp->registers + MSP_GCR); - if (config->default_protdesc) - protdesc = - (struct msp_protdesc *)&prot_descs[config->protocol]; - else - protdesc = (struct msp_protdesc *)&config->protdesc; - switch (config->protocol) { case MSP_PCM_PROTOCOL: case MSP_PCM_COMPAND_PROTOCOL: - frame_width = protdesc->frame_width; - sck_div = config->f_inputclk / (config->frame_freq * - (protdesc->clocks_per_frame)); - frame_per = protdesc->frame_period; - break; case MSP_I2S_PROTOCOL: - frame_width = protdesc->frame_width; - sck_div = config->f_inputclk / (config->frame_freq * - (protdesc->clocks_per_frame)); - frame_per = protdesc->frame_period; break; default: dev_err(msp->dev, "%s: ERROR: Unknown protocol (%d)!\n", @@ -249,12 +234,35 @@ static int setup_bitclk(struct ux500_msp *msp, struct ux500_msp_config *config) return -EINVAL; } + if (config->default_protdesc) + protdesc = (struct msp_protdesc *)&prot_descs[config->protocol]; + else + protdesc = &config->protdesc; + + if (!config->frame_freq || !protdesc->clocks_per_frame) + return -EINVAL; + + desired_bitclk = (u64)config->frame_freq * protdesc->clocks_per_frame; + if (desired_bitclk > config->f_inputclk) + return -EINVAL; + bitclk = desired_bitclk; + if (config->f_inputclk % bitclk) { + dev_err(msp->dev, + "Input clock %u cannot generate bit clock %u\n", + config->f_inputclk, bitclk); + return -EINVAL; + } + + sck_div = config->f_inputclk / bitclk; + if (!sck_div || sck_div > SCK_DIV_MASK + 1) + return -EINVAL; + temp_reg = (sck_div - 1) & SCK_DIV_MASK; - temp_reg |= FRAME_WIDTH_BITS(frame_width); - temp_reg |= FRAME_PERIOD_BITS(frame_per); + temp_reg |= FRAME_WIDTH_BITS(protdesc->frame_width); + temp_reg |= FRAME_PERIOD_BITS(protdesc->frame_period); writel(temp_reg, msp->registers + MSP_SRG); - msp->f_bitclk = (config->f_inputclk)/(sck_div + 1); + msp->f_bitclk = config->f_inputclk / sck_div; /* Enable bit-clock */ udelay(100); diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h index d75a0974369a..80085dde5079 100644 --- a/sound/soc/ux500/ux500_msp_i2s.h +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -12,8 +12,6 @@ #include -#define MSP_INPUT_FREQ_APB 48000000 - /*** Stereo mode. Used for APB data accesses as 16 bits accesses (mono), * 32 bits accesses (stereo). ***/ From 9ccbacf5a0120964fc1ffacb8151e3347bee9287 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:54 +0200 Subject: [PATCH 0569/2366] ASoC: ux500: Validate MSP DAI configuration Installing channel constraints from hw_params is too late to affect the parameters being committed. The driver consequently accepts channel counts which disagree with the I2S or TDM setup. It also silently truncates out-of-range slot masks and accepts inverted bit clock formats which prepare then rejects. Validate the selected channel count directly, reject invalid masks before changing cached TDM state, and implement all four standard clock and frame inversion combinations. Use the requested format in validation diagnostics. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-4-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 41 ++++++++++++++++++++++----------- sound/soc/ux500/ux500_msp_i2s.c | 7 ++++-- sound/soc/ux500/ux500_msp_i2s.h | 1 + 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 56d5591e2269..78278927cc53 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -130,7 +130,16 @@ static int setup_clocking(struct snd_soc_dai *dai, case SND_SOC_DAIFMT_NB_IF: msp_config->tx_fsync_pol ^= 1 << TFSPOL_SHIFT; msp_config->rx_fsync_pol ^= 1 << RFSPOL_SHIFT; + break; + case SND_SOC_DAIFMT_IB_NF: + msp_config->bclk_inverted = true; + break; + + case SND_SOC_DAIFMT_IB_IF: + msp_config->bclk_inverted = true; + msp_config->tx_fsync_pol ^= 1 << TFSPOL_SHIFT; + msp_config->rx_fsync_pol ^= 1 << RFSPOL_SHIFT; break; default: @@ -453,7 +462,6 @@ static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { unsigned int mask, slots_active; - struct snd_pcm_runtime *runtime = substream->runtime; struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", @@ -461,9 +469,8 @@ static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream, switch (drvdata->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: - snd_pcm_hw_constraint_minmax(runtime, - SNDRV_PCM_HW_PARAM_CHANNELS, - 1, 2); + if (params_channels(params) < 1 || params_channels(params) > 2) + return -EINVAL; break; case SND_SOC_DAIFMT_DSP_B: @@ -475,9 +482,8 @@ static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream, slots_active = hweight32(mask); dev_dbg(dai->dev, "TDM-slots active: %d", slots_active); - snd_pcm_hw_constraint_single(runtime, - SNDRV_PCM_HW_PARAM_CHANNELS, - slots_active); + if (!slots_active || params_channels(params) != slots_active) + return -EINVAL; break; default: @@ -510,20 +516,21 @@ static int ux500_msp_dai_set_dai_fmt(struct snd_soc_dai *dai, default: dev_err(dai->dev, "%s: Error: Unsupported protocol/master (fmt = 0x%x)!\n", - __func__, drvdata->fmt); + __func__, fmt); return -EINVAL; } switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: case SND_SOC_DAIFMT_NB_IF: + case SND_SOC_DAIFMT_IB_NF: case SND_SOC_DAIFMT_IB_IF: break; default: dev_err(dai->dev, "%s: Error: Unsupported inversion (fmt = 0x%x)!\n", - __func__, drvdata->fmt); + __func__, fmt); return -EINVAL; } @@ -557,17 +564,23 @@ static int ux500_msp_dai_set_tdm_slot(struct snd_soc_dai *dai, __func__, slots); return -EINVAL; } - drvdata->slots = slots; - if (!(slot_width == 16)) { + if (slot_width != 16) { dev_err(dai->dev, "%s: Error: Unsupported slot-width (%d)!\n", __func__, slot_width); return -EINVAL; } - drvdata->slot_width = slot_width; - drvdata->tx_mask = tx_mask & cap; - drvdata->rx_mask = rx_mask & cap; + if ((tx_mask | rx_mask) & ~cap) { + dev_err(dai->dev, "%s: Slot mask exceeds %d slots\n", + __func__, slots); + return -EINVAL; + } + + drvdata->slots = slots; + drvdata->slot_width = slot_width; + drvdata->tx_mask = tx_mask; + drvdata->rx_mask = rx_mask; return 0; } diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index ef41de92d8e7..bc77174e0070 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -201,10 +201,12 @@ static int configure_protocol(struct ux500_msp *msp, /* The code below should not be separated. */ temp_reg = readl(msp->registers + MSP_GCR) & ~TX_CLK_POL_RISING; - temp_reg |= MSP_TX_CLKPOL_BIT(~protdesc->tx_clk_pol); + temp_reg |= MSP_TX_CLKPOL_BIT(!protdesc->tx_clk_pol ^ + config->bclk_inverted); writel(temp_reg, msp->registers + MSP_GCR); temp_reg = readl(msp->registers + MSP_GCR) & ~RX_CLK_POL_RISING; - temp_reg |= MSP_RX_CLKPOL_BIT(protdesc->rx_clk_pol); + temp_reg |= MSP_RX_CLKPOL_BIT(protdesc->rx_clk_pol ^ + config->bclk_inverted); writel(temp_reg, msp->registers + MSP_GCR); return 0; @@ -441,6 +443,7 @@ static bool ux500_msp_config_compatible(struct ux500_msp *msp, active->data_size == config->data_size && active->def_elem_len == config->def_elem_len && active->clock_provider == config->clock_provider && + active->bclk_inverted == config->bclk_inverted && !memcmp(&active->protdesc, &config->protdesc, sizeof(active->protdesc)); } diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h index 80085dde5079..17b5c37a7e5d 100644 --- a/sound/soc/ux500/ux500_msp_i2s.h +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -459,6 +459,7 @@ struct ux500_msp_config { unsigned int def_elem_len; unsigned int iodelay; bool clock_provider; + bool bclk_inverted; }; struct ux500_msp { From 66ec63e7a90bedc56aa050fbe437964008c3d584 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:55 +0200 Subject: [PATCH 0570/2366] ASoC: ux500: Deassert the MSP reset during probe The devicetree has described each MSP reset line since the PRCC reset controller was added, but the driver never acquires or deasserts it. The block can consequently remain inaccessible when firmware has left it in reset. Acquire the reset exclusively and keep it deasserted for the lifetime of the bound device. Fixes: 95f04048325c ("ARM: dts: ux500: Add reset lines to IP blocks") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-5-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 78278927cc53..b3de115d829a 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -686,6 +687,7 @@ static const struct snd_soc_component_driver ux500_msp_component = { static int ux500_msp_drv_probe(struct platform_device *pdev) { struct ux500_msp_i2s_drvdata *drvdata; + struct reset_control *reset; int ret = 0; drvdata = devm_kzalloc(&pdev->dev, @@ -733,6 +735,11 @@ static int ux500_msp_drv_probe(struct platform_device *pdev) return -EINVAL; } + reset = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); + if (IS_ERR(reset)) + return dev_err_probe(&pdev->dev, PTR_ERR(reset), + "Failed to deassert MSP reset\n"); + ret = ux500_msp_i2s_init_msp(pdev, &drvdata->msp); if (ret) { dev_err(&pdev->dev, From 4fb67925f33ad789e9e00903a73306ed40f7ae32 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:56 +0200 Subject: [PATCH 0571/2366] ASoC: ux500: Request the MSP MMIO resource A bare devm_ioremap() neither reserves the register range nor preserves the platform resource error. This permits another driver to claim the same range and reports every mapping failure as an allocation failure. Use the managed platform resource helper, retaining the resolved resource only to derive the DMA register address. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-6-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_i2s.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index bc77174e0070..43dc9b3aa4ef 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -733,7 +733,7 @@ int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir) int ux500_msp_i2s_init_msp(struct platform_device *pdev, struct ux500_msp **msp_p) { - struct resource *res = NULL; + struct resource *res; struct ux500_msp *msp; *msp_p = devm_kzalloc(&pdev->dev, sizeof(struct ux500_msp), GFP_KERNEL); @@ -743,20 +743,10 @@ int ux500_msp_i2s_init_msp(struct platform_device *pdev, msp->dev = &pdev->dev; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res == NULL) { - dev_err(&pdev->dev, "%s: ERROR: Unable to get resource!\n", - __func__); - return -ENOMEM; - } - + msp->registers = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(msp->registers)) + return PTR_ERR(msp->registers); msp->tx_rx_addr = res->start + MSP_DR; - msp->registers = devm_ioremap(&pdev->dev, res->start, - resource_size(res)); - if (msp->registers == NULL) { - dev_err(&pdev->dev, "%s: ERROR: ioremap failed!\n", __func__); - return -ENOMEM; - } msp->msp_state = MSP_STATE_IDLE; msp->loopback_enable = 0; From 7b819677b503667422b0b7bdb21853e0066f8606 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:57 +0200 Subject: [PATCH 0572/2366] ASoC: ux500: Remove obsolete PRCMU QoS calls The DB8500 PRCMU QoS interface consists of unconditional inline stubs, so the MSP calls and cached constraint state have no effect. Device power and clocks are already represented by the regulator, power-domain and common-clock frameworks. Remove the dead calls and their private state instead of pretending to change the APE operating point. Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-7-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 26 -------------------------- sound/soc/ux500/ux500_msp_dai.h | 2 -- 2 files changed, 28 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index b3de115d829a..5b4b3126637e 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -393,12 +392,6 @@ static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream, dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id, snd_pcm_stream_str(substream)); - if (drvdata->vape_opp_constraint == 1) { - prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, - "ux500_msp_i2s", 50); - drvdata->vape_opp_constraint = 0; - } - if (ux500_msp_i2s_close(drvdata->msp, is_playback ? MSP_DIR_TX : MSP_DIR_RX)) { dev_err(dai->dev, @@ -440,21 +433,6 @@ static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, return ret; } - /* Set OPP-level */ - if ((drvdata->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) && - (drvdata->msp->f_bitclk > 19200000)) { - /* If the bit-clock is higher than 19.2MHz, Vape should be - * run in 100% OPP. Only when bit-clock is used (MSP master) - */ - prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, - "ux500-msp-i2s", 100); - drvdata->vape_opp_constraint = 1; - } else { - prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, - "ux500-msp-i2s", 50); - drvdata->vape_opp_constraint = 0; - } - return ret; } @@ -710,8 +688,6 @@ static int ux500_msp_drv_probe(struct platform_device *pdev) __func__, ret); return ret; } - prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, (char *)pdev->name, 50); - drvdata->pclk = devm_clk_get(&pdev->dev, "apb_pclk"); if (IS_ERR(drvdata->pclk)) { ret = PTR_ERR(drvdata->pclk); @@ -780,8 +756,6 @@ static void ux500_msp_drv_remove(struct platform_device *pdev) snd_soc_unregister_component(&pdev->dev); - prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s"); - ux500_msp_i2s_cleanup_msp(pdev, drvdata->msp); } diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h index 19058c238420..ad4ce69bfbf5 100644 --- a/sound/soc/ux500/ux500_msp_dai.h +++ b/sound/soc/ux500/ux500_msp_dai.h @@ -46,8 +46,6 @@ struct ux500_msp_i2s_drvdata { struct clk *clk; struct clk *pclk; - /* Regulators */ - int vape_opp_constraint; }; int ux500_msp_dai_set_data_delay(struct snd_soc_dai *dai, int delay); From dc1a1b1e22066f01bb86a9b11ee998d4dc72db66 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:58 +0200 Subject: [PATCH 0573/2366] ASoC: ux500: Allow repeated MSP prepare calls ALSA can call the DAI prepare callback again after an XRUN without first shutting down the stream. The MSP open helper rejects the second call with -EBUSY because the direction remains configured. Track successful playback and capture configurations at the DAI layer. Make repeated prepare calls no-ops and only close directions which were successfully prepared. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-8-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_dai.c | 28 +++++++++++++++++++++------- sound/soc/ux500/ux500_msp_dai.h | 1 + 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 5b4b3126637e..37c48cc70394 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -388,15 +388,21 @@ static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream, int ret; struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + unsigned int configured = is_playback ? PLAYBACK_CONFIGURED : + CAPTURE_CONFIGURED; + unsigned int dir = is_playback ? MSP_DIR_TX : MSP_DIR_RX; dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id, snd_pcm_stream_str(substream)); - if (ux500_msp_i2s_close(drvdata->msp, - is_playback ? MSP_DIR_TX : MSP_DIR_RX)) { - dev_err(dai->dev, - "%s: Error: MSP %d (%s): Unable to close i2s.\n", - __func__, dai->id, snd_pcm_stream_str(substream)); + if (drvdata->configured & configured) { + if (ux500_msp_i2s_close(drvdata->msp, dir)) { + dev_err(dai->dev, + "%s: Error: MSP %d (%s): Unable to close i2s.\n", + __func__, dai->id, + snd_pcm_stream_str(substream)); + } + drvdata->configured &= ~configured; } /* Disable and unprepare clocks */ @@ -414,14 +420,20 @@ static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream, static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - int ret = 0; struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev); struct snd_pcm_runtime *runtime = substream->runtime; struct ux500_msp_config msp_config; + bool is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int configured = is_playback ? PLAYBACK_CONFIGURED : + CAPTURE_CONFIGURED; + int ret; dev_dbg(dai->dev, "%s: MSP %d (%s): Enter (rate = %d).\n", __func__, dai->id, snd_pcm_stream_str(substream), runtime->rate); + if (drvdata->configured & configured) + return 0; + ret = setup_msp_config(substream, dai, &msp_config); if (ret) return ret; @@ -433,7 +445,9 @@ static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream, return ret; } - return ret; + drvdata->configured |= configured; + + return 0; } static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream, diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h index ad4ce69bfbf5..aae582030d95 100644 --- a/sound/soc/ux500/ux500_msp_dai.h +++ b/sound/soc/ux500/ux500_msp_dai.h @@ -36,6 +36,7 @@ struct ux500_msp_i2s_drvdata { struct ux500_msp *msp; struct regulator *reg_vape; unsigned int fmt; + unsigned int configured; unsigned int tx_mask; unsigned int rx_mask; int slots; From 2519439b4b5f6ee95879b1a44fc373127291b1e4 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 2 Sep 2026 09:55:59 +0200 Subject: [PATCH 0574/2366] ASoC: ux500: Program the MSP FIFO watermarks The DMA engine is configured for four-element bursts, but the MSP driver never programs the FIFO watermark register and instead depends on its previous or reset value. The DB8500 DMA request protocol requires the peripheral watermark to match the DMA packet size. Program four-element receive and transmit watermarks when configuring the first direction, before enabling MSP DMA requests. Fixes: 3592b7f69a54 ("ASoC: Ux500: Add MSP I2S-driver") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260902-ux500-msp-fixes-v2-9-4b60b002d55a@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_i2s.c | 2 ++ sound/soc/ux500/ux500_msp_i2s.h | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index 43dc9b3aa4ef..683b485fb570 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -507,6 +507,8 @@ int ux500_msp_i2s_open(struct ux500_msp *msp, old_reg &= ~mask; new_reg |= old_reg; writel(new_reg, msp->registers + MSP_GCR); + writel(MSP_WMRK_TX_4_ELEMENTS | MSP_WMRK_RX_4_ELEMENTS, + msp->registers + MSP_WMRK); } res = enable_msp(msp, config, first); diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h index 17b5c37a7e5d..2bf2699bdc49 100644 --- a/sound/soc/ux500/ux500_msp_i2s.h +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -62,6 +62,7 @@ enum msp_direction { #define MSP_SRG 0x10 #define MSP_FLR 0x14 #define MSP_DMACR 0x18 +#define MSP_WMRK 0x1c #define MSP_IMSC 0x20 #define MSP_RIS 0x24 @@ -228,6 +229,10 @@ enum msp_direction { #define RDMAE_SHIFT 0 #define TDMAE_SHIFT 1 +/* FIFO watermark register */ +#define MSP_WMRK_RX_4_ELEMENTS BIT(0) +#define MSP_WMRK_TX_4_ELEMENTS BIT(3) + /* Interrupt Register */ #define RX_SERVICE_INT BIT(0) #define RX_OVERRUN_ERROR_INT BIT(1) From 950ae84b5cc944fbe27d81806d0b76af765f779c Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 2 Sep 2026 13:10:19 +0100 Subject: [PATCH 0575/2366] afs: Fix missing kunmap in afs_dir_search_bucket() Fix afs_dir_search_bucket() to kunmap the block it's using in the "bad:" path. Fixes: a5b5beebcf96 ("afs: Use the contained hashtable to search a directory") Closes: https://sashiko.dev/#/patchset/20260716103030.3065561-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260902121024.3328255-2-dhowells@redhat.com cc: Marc Dionne cc: linux-afs@lists.infradead.org cc: linux-fsdevel@vger.kernel.org cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/dir_search.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/afs/dir_search.c b/fs/afs/dir_search.c index 104411c0692f..4977ad81fa82 100644 --- a/fs/afs/dir_search.c +++ b/fs/afs/dir_search.c @@ -173,12 +173,11 @@ int afs_dir_search_bucket(struct afs_dir_iter *iter, const struct qstr *name, ret = -ENOENT; found: +bad: if (iter->block) { kunmap_local(iter->block); iter->block = NULL; } - -bad: if (ret == -ESTALE) afs_invalidate_dir(iter->dvnode, afs_dir_invalid_iter_stale); _leave(" = %d", ret); From e3cfd3eb7d5be7787cc69530b423f788f14d084f Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 2 Sep 2026 13:10:20 +0100 Subject: [PATCH 0576/2366] afs: Fix double-unmap of directory block Fix afs_edit_dir_remove() to use a cleanup function to unmap the block pointed to by afs_dir_iter::block if it's left pointing to something rather than manually kunmapping the blocks. Manually kunmapping without clearing iter.blocks can result in a double-kunmap if afs_dir_find_block() is called twice in a row (which would be the case if the block being modified is not first in the hash chain). Fixes: a5b5beebcf96 ("afs: Use the contained hashtable to search a directory") Closes: https://sashiko.dev/#/patchset/20260716103030.3065561-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260902121024.3328255-3-dhowells@redhat.com cc: Marc Dionne cc: linux-afs@lists.infradead.org cc: linux-fsdevel@vger.kernel.org cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/dir_edit.c | 9 ++------- fs/afs/dir_search.c | 10 ++-------- fs/afs/internal.h | 8 ++++++++ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/fs/afs/dir_edit.c b/fs/afs/dir_edit.c index 3ead36a07048..c31303059444 100644 --- a/fs/afs/dir_edit.c +++ b/fs/afs/dir_edit.c @@ -442,7 +442,7 @@ void afs_edit_dir_remove(struct afs_vnode *vnode, /* Check and clear the entry. */ de = &block->dirents[slot]; if (de->u.valid != 1) - goto error_unmap; + goto error; trace_afs_edit_dir(vnode, why, afs_edit_dir_delete, b, slot, ntohl(de->u.vnode), ntohl(de->u.unique), @@ -458,7 +458,6 @@ void afs_edit_dir_remove(struct afs_vnode *vnode, /* Clear the constituent entries. */ next = de->u.hash_next; memset(de, 0, sizeof(*de) * iter.nr_slots); - kunmap_local(block); /* Adjust the hash chain: if iter->prev_entry is 0, the hashtable head * index is previous; otherwise it's slot number of the previous entry. @@ -485,7 +484,6 @@ void afs_edit_dir_remove(struct afs_vnode *vnode, pde = &pblock->dirents[ps]; prev_next = pde->u.hash_next; if (prev_next != htons(entry)) { - kunmap_local(pblock); pr_warn("%llx:%llx:%x: not prev in chain b=%x p=%x,%x e=%x %*s", vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique, iter.bucket, iter.prev_entry, prev_next, entry, @@ -493,7 +491,6 @@ void afs_edit_dir_remove(struct afs_vnode *vnode, goto error; } pde->u.hash_next = next; - kunmap_local(pblock); } netfs_single_mark_inode_dirty(&vnode->netfs.inode); @@ -503,18 +500,16 @@ void afs_edit_dir_remove(struct afs_vnode *vnode, _debug("Remove %s from %u[%u]", name->name, b, slot); out_unmap: + afs_dir_end_iter(&iter); kunmap_local(meta); _leave(""); return; already_invalidated: - kunmap_local(block); trace_afs_edit_dir(vnode, why, afs_edit_dir_delete_inval, 0, 0, 0, 0, name->name); goto out_unmap; -error_unmap: - kunmap_local(block); error: trace_afs_edit_dir(vnode, why, afs_edit_dir_delete_error, 0, 0, 0, 0, name->name); diff --git a/fs/afs/dir_search.c b/fs/afs/dir_search.c index 4977ad81fa82..11ebdfffcb1d 100644 --- a/fs/afs/dir_search.c +++ b/fs/afs/dir_search.c @@ -75,10 +75,7 @@ union afs_xdr_dir_block *afs_dir_find_block(struct afs_dir_iter *iter, size_t bl _enter("%zx,%d", block, slot); - if (iter->block) { - kunmap_local(iter->block); - iter->block = NULL; - } + afs_dir_end_iter(iter); if (dvnode->directory_size < blend) goto fail; @@ -174,10 +171,7 @@ int afs_dir_search_bucket(struct afs_dir_iter *iter, const struct qstr *name, ret = -ENOENT; found: bad: - if (iter->block) { - kunmap_local(iter->block); - iter->block = NULL; - } + afs_dir_end_iter(iter); if (ret == -ESTALE) afs_invalidate_dir(iter->dvnode, afs_dir_invalid_iter_stale); _leave(" = %d", ret); diff --git a/fs/afs/internal.h b/fs/afs/internal.h index 290873bac89b..330654ed16ec 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h @@ -1133,6 +1133,14 @@ int afs_dir_search_bucket(struct afs_dir_iter *iter, const struct qstr *name, int afs_dir_search(struct afs_vnode *dvnode, const struct qstr *name, struct afs_fid *_fid, afs_dataversion_t *_dir_version); +static inline void afs_dir_end_iter(struct afs_dir_iter *iter) +{ + if (iter->block) { + kunmap_local(iter->block); + iter->block = NULL; + } +} + /* * dir_silly.c */ From 044d596094af4b769fb8e1173dff0d08bd68db6c Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 2 Sep 2026 13:10:21 +0100 Subject: [PATCH 0577/2366] afs: Fix incorrect free in candidate cleanup in afs_lookup_server() Fix afs_lookup_server() to not free an existing server's endpoint state when cleaning up a candidate server. The candidate record doesn't have an endpoint state yet at this point, so the free for that can just be removed. Fixes: 4882ba78574e ("afs: Fix afs_server ref accounting") Link: https://sashiko.dev/#/patchset/20260729160108.2031453-1-dhowells%40redhat.com Signed-off-by: David Howells Link: https://patch.msgid.link/20260902121024.3328255-4-dhowells@redhat.com cc: Marc Dionne cc: linux-afs@lists.infradead.org cc: linux-fsdevel@vger.kernel.org cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/server.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/afs/server.c b/fs/afs/server.c index 0fe162ea2a36..189138bd6d71 100644 --- a/fs/afs/server.c +++ b/fs/afs/server.c @@ -242,7 +242,6 @@ struct afs_server *afs_lookup_server(struct afs_cell *cell, struct key *key, out: afs_put_addrlist(alist, afs_alist_trace_put_server_create); if (candidate) { - kfree(rcu_access_pointer(server->endpoint_state)); kfree(candidate); afs_dec_servers_outstanding(cell->net); } From ba0623fc19a424f4745394c499f9f28a8d88d397 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Wed, 2 Sep 2026 13:10:22 +0100 Subject: [PATCH 0578/2366] afs: Clear stale peer app data after address list changes afs_fs_probe_fileserver() fetches the current endpoint state under server->fs_lock, but leaves old_alist as NULL. Consequently, afs_set_peer_appdata() treats every address list replacement as initial setup and only binds the new peers; it never unbinds peers removed from the old list. An address refresh can therefore proceed as follows. CPU 0 replaces server S's list and drops Pold without clearing Pold->app_data. The server destroyer then clears only S's current peers and lets S reach its RCU callback. After the callback frees S, CPU 1 handles a callback through an RxRPC connection that still pins Pold, reads Pold->app_data, and calls afs_use_server() on the freed object. KASAN reported: BUG: KASAN: slab-use-after-free in afs_find_server+0x3c/0xa0 Read of size 4 at addr ffff8881013e1af0 by task krxrpcio/7001/74 Call Trace: afs_find_server+0x3c/0xa0 afs_rx_new_call+0x15c/0x390 rxrpc_new_incoming_call+0x97c/0x1730 rxrpc_input_packet.constprop.0+0xd03/0xec0 rxrpc_io_thread+0x967/0x1640 Allocated by task 93: afs_lookup_server+0x1a7/0x14c0 afs_alloc_server_list+0x43f/0xb60 afs_create_volume+0x923/0x1490 afs_get_tree+0x1c6/0x10a0 Freed by task 0: kfree+0x131/0x3c0 rcu_core+0x50a/0x1850 Last potentially related work creation: __call_rcu_common.constprop.0+0x71/0xa10 afs_put_server+0x213/0x2b0 Preserve old->addresses for the peer app-data update so that removed peers are cleared before the endpoint state is replaced. Also advance both cursors when the old and new lists share a peer; activating the old/new comparison without this would otherwise loop forever on the shared entry. Fixes: 40e8b52fe8c8 ("afs: Use the per-peer app data provided by rxrpc") Signed-off-by: Chengfeng Ye Signed-off-by: Qi Zhang Signed-off-by: David Howells Link: https://patch.msgid.link/20260902121024.3328255-5-dhowells@redhat.com cc: Marc Dionne cc: linux-afs@lists.infradead.org cc: linux-fsdevel@vger.kernel.org cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/addr_list.c | 5 ++++- fs/afs/fs_probe.c | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/afs/addr_list.c b/fs/afs/addr_list.c index 63bf096b721a..73195d76b481 100644 --- a/fs/afs/addr_list.c +++ b/fs/afs/addr_list.c @@ -394,8 +394,11 @@ void afs_set_peer_appdata(struct afs_server *server, struct rxrpc_peer *pn = new_alist->addrs[n].peer; struct rxrpc_peer *po = old_alist->addrs[o].peer; - if (pn == po) + if (pn == po) { + n++; + o++; continue; + } if (pn < po) { rxrpc_kernel_set_peer_data(pn, data); n++; diff --git a/fs/afs/fs_probe.c b/fs/afs/fs_probe.c index a91ad1938d07..8c62334dbfe7 100644 --- a/fs/afs/fs_probe.c +++ b/fs/afs/fs_probe.c @@ -258,6 +258,7 @@ int afs_fs_probe_fileserver(struct afs_net *net, struct afs_server *server, lockdep_is_held(&server->fs_lock)); if (old) { estate->responsive_set = old->responsive_set; + old_alist = old->addresses; if (!new_alist) new_alist = old->addresses; } From 33da68f61d25ef8411489d06514ff627c1f88152 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:49 +0000 Subject: [PATCH 0579/2366] arm64: dts: renesas: r9a09g057: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: 050ee38d0002 ("arm64: dts: renesas: r9a09g057: Add GBETH nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260722085353.136986-2-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g057.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g057.dtsi b/arch/arm64/boot/dts/renesas/r9a09g057.dtsi index 639693d464a7..188ce9f9c7c2 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g057.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g057.dtsi @@ -1715,23 +1715,28 @@ queue3 { mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; @@ -1816,23 +1821,28 @@ queue3 { mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; From 66fcbdbeca0118b8aeac218b33fa18c394513543 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:50 +0000 Subject: [PATCH 0580/2366] arm64: dts: renesas: r9a09g056: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: c8c8a57c5b40 ("arm64: dts: renesas: r9a09g056: Add GBETH nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260722085353.136986-3-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g056.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g056.dtsi b/arch/arm64/boot/dts/renesas/r9a09g056.dtsi index 76fa34ff3d07..77c2221a9e2a 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g056.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g056.dtsi @@ -1585,23 +1585,28 @@ queue3 { mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; @@ -1686,23 +1691,28 @@ queue3 { mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; From 63016c3a91f2c458ca75869c8c782e899591f22d Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:51 +0000 Subject: [PATCH 0581/2366] arm64: dts: renesas: r9a09g047: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: 41ffbb1c42d3 ("arm64: dts: renesas: r9a09g047: Add GBETH nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Tested-by: Tommaso Merciai Link: https://patch.msgid.link/20260722085353.136986-4-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g047.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g047.dtsi b/arch/arm64/boot/dts/renesas/r9a09g047.dtsi index 73757e8e2197..060405d4a2dd 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g047.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g047.dtsi @@ -1913,23 +1913,28 @@ queue3 { mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; @@ -2013,23 +2018,28 @@ queue3 { mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <4>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; snps,priority = <0x1>; }; queue1 { + snps,weight = <0x12>; snps,dcb-algorithm; snps,priority = <0x2>; }; queue2 { + snps,weight = <0x14>; snps,dcb-algorithm; snps,priority = <0x4>; }; queue3 { + snps,weight = <0x18>; snps,dcb-algorithm; snps,priority = <0x8>; }; From f9d9a1913c09366bf7b967d39575e06cb80128dc Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:52 +0000 Subject: [PATCH 0582/2366] arm64: dts: renesas: r9a09g077: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: 394c1e24a4cf ("arm64: dts: renesas: r9a09g077: Add GMAC nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260722085353.136986-5-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g077.dtsi | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g077.dtsi b/arch/arm64/boot/dts/renesas/r9a09g077.dtsi index 40494159831d..bac39390ead7 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g077.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g077.dtsi @@ -642,36 +642,45 @@ queue7 { mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; @@ -788,36 +797,45 @@ queue7 { mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; @@ -934,36 +952,45 @@ queue7 { mtl_tx_setup2: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; From 2ac7bad110be6ebe478d6bd57821f7f5259a1f54 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 22 Jul 2026 08:53:53 +0000 Subject: [PATCH 0583/2366] arm64: dts: renesas: r9a09g087: Switch GBETH TX queue scheduling to WRR The GBETH ethernet nodes don't specify a TX scheduling policy, so stmmac falls back to Strict Priority. In this configuration the queue with the highest priority gets all the traffic, starving the others under load. Under sustained UDP TX load with multiple data streams, this starvation triggers spurious adapter resets due to TX queue timeouts: iperf3 -c -i0 -t60 --bind-dev end0 -u -b0 -P4 end0: NETDEV WATCHDOG: CPU: 1: transmit queue 1 timed out 5228 ms end0: Reset adapter. Investigation shows that only the highest priority queue is advancing while the others stall for more than 5 seconds, causing a netdev watchdog reset. Switch the TX scheduling policy to Weighted-Round-Robin (WRR) so that traffic is processed across all queues, eliminating the stalls. Fixes: c4698a34993b ("arm64: dts: renesas: r9a09g087: Add GMAC nodes") Signed-off-by: Ovidiu Panait Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260722085353.136986-6-ovidiu.panait.rb@renesas.com Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r9a09g087.dtsi | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/arch/arm64/boot/dts/renesas/r9a09g087.dtsi b/arch/arm64/boot/dts/renesas/r9a09g087.dtsi index e8d4f76949cc..03b976d93e10 100644 --- a/arch/arm64/boot/dts/renesas/r9a09g087.dtsi +++ b/arch/arm64/boot/dts/renesas/r9a09g087.dtsi @@ -643,36 +643,45 @@ queue7 { mtl_tx_setup0: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; @@ -790,36 +799,45 @@ queue7 { mtl_tx_setup1: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; @@ -937,36 +955,45 @@ queue7 { mtl_tx_setup2: tx-queues-config { snps,tx-queues-to-use = <8>; + snps,tx-sched-wrr; queue0 { + snps,weight = <0x10>; snps,dcb-algorithm; }; queue1 { + snps,weight = <0x11>; snps,dcb-algorithm; }; queue2 { + snps,weight = <0x12>; snps,dcb-algorithm; }; queue3 { + snps,weight = <0x13>; snps,dcb-algorithm; }; queue4 { + snps,weight = <0x14>; snps,dcb-algorithm; }; queue5 { + snps,weight = <0x15>; snps,dcb-algorithm; }; queue6 { + snps,weight = <0x16>; snps,dcb-algorithm; }; queue7 { + snps,weight = <0x17>; snps,dcb-algorithm; }; }; From 9cdfad5dd5529e1700f51feaed5e8a90044d7e35 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 18 Aug 2026 12:52:29 +0900 Subject: [PATCH 0584/2366] RDMA/srp: Fix srp_remove_target() Remove all logical units before disconnecting the transport because one or more SCSI commands may be submitted while removing logical units. Remove the SCSI host after the transport has been disconnected because the code that disconnects the transport needs resources that are freed by the code that removes the SCSI host (SCSI host tag set). Remove the srp_rport_get() and srp_rport_put() calls because the purpose of these calls was to keep the rport until tl_err_work is cancelled. Reported-by: Yehyeong Lee Closes: https://lore.kernel.org/linux-rdma/20260812190418.200337-1-yhlee@isslab.korea.ac.kr/ Signed-off-by: Bart Van Assche Signed-off-by: Yehyeong Lee Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/srp/ib_srp.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/ulp/srp/ib_srp.c b/drivers/infiniband/ulp/srp/ib_srp.c index 6b429ef63f8f..955f36efeebd 100644 --- a/drivers/infiniband/ulp/srp/ib_srp.c +++ b/drivers/infiniband/ulp/srp/ib_srp.c @@ -1038,15 +1038,20 @@ static void srp_del_scsi_host_attr(struct Scsi_Host *shost) static void srp_remove_target(struct srp_target_port *target) { + struct scsi_device *sdev; struct srp_rdma_ch *ch; int i; WARN_ON_ONCE(target->state != SRP_TARGET_REMOVED); srp_del_scsi_host_attr(target->scsi_host); - srp_rport_get(target->rport); - srp_remove_host(target->scsi_host); - scsi_remove_host(target->scsi_host); + /* + * Remove all logical units. This must happen before the + * srp_disconnect_target() call because scsi_remove_device() may trigger + * submission of SCSI commands. See also sd_shutdown(). + */ + shost_for_each_device(sdev, target->scsi_host) + scsi_remove_device(sdev); srp_stop_rport_timers(target->rport); srp_disconnect_target(target); kobj_ns_drop(KOBJ_NS_TYPE_NET, to_ns_common(target->net)); @@ -1055,7 +1060,8 @@ static void srp_remove_target(struct srp_target_port *target) srp_free_ch_ib(target, ch); } cancel_work_sync(&target->tl_err_work); - srp_rport_put(target->rport); + srp_remove_host(target->scsi_host); + scsi_remove_host(target->scsi_host); kfree(target->ch); target->ch = NULL; From d85f0f0a7c85756fc992c70d869706f19dac9259 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Wed, 19 Aug 2026 10:08:04 +0900 Subject: [PATCH 0585/2366] IB/iser: reject a remote invalidation of an unregistered direction A write command whose data is sent entirely as immediate data is not registered. iser_reg_mem_fastreg() takes the DMA key path and leaves rdma_reg[ISER_DIR_OUT].desc at NULL, while iser_dma_map_task_data() has already set dir[ISER_DIR_OUT]. iser_check_remote_inv() looks at dir[] alone and hands the descriptor to iser_inv_desc(), which reads desc->sig_protected. A target that answers such a command with IB_WR_SEND_WITH_INV faults the initiator. Leaving those commands unregistered is deliberate. The same function already terminates the connection when a target sends a remote invalidation the initiator did not ask for. A target that invalidates a direction that was never registered is in the same class, so give it the same answer. Oops: general protection fault, probably for non-canonical address 0xdffffc0000000004: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000020-0x0000000000000027] CPU: 0 UID: 0 PID: 40 Comm: kworker/u8:2 Not tainted 7.2.0-rc5-ISERHOST-gf5098b6bae76-dirty #3 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: rxe_wq do_work RIP: 0010:iser_task_rsp+0x6d6/0xec0 Code: 48 c1 ea 03 80 3c 02 00 0f 85 ba 06 00 00 48 8b 9b 78 01 00 00 48 b8 00 00 00 00 00 fc ff df 48 8d 7b 20 48 89 fa 48 c1 ea 03 <0f> b6 04 02 84 c0 74 06 0f 8e 76 06 00 00 80 7b 20 00 0f 84 3d 04 RSP: 0018:ffff88811b008db8 EFLAGS: 00010202 RAX: dffffc0000000000 RBX: 0000000000000000 RCX: 0000000000001848 RDX: 0000000000000004 RSI: 1ffff11021587b12 RDI: 0000000000000020 RBP: ffff88810adc1ae4 R08: ffff888109b7f860 R09: ffffffff90a922c0 R10: ffff88810adc1a1c R11: 000000000000003c R12: ffff888109b7f800 R13: ffff88810adc1acc R14: ffff888109b7f820 R15: 0000000000000000 FS: 0000000000000000(0000) GS:ffff88818a676000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00000000005afe2b CR3: 000000010af23005 CR4: 0000000000770ef0 PKRU: 55555554 Call Trace: __ib_process_cq+0xe1/0x390 ib_poll_handler+0x6e/0x200 irq_poll_softirq+0x1df/0x480 ? clockevents_program_event+0x2ba/0x860 ? __pfx_irq_poll_softirq+0x10/0x10 handle_softirqs+0x18e/0x590 ? __pfx_handle_softirqs+0x10/0x10 ? __hrtimer_rearm_deferred+0x156/0x450 do_softirq+0x3b/0x60 __local_bh_enable_ip+0x61/0x70 __alloc_skb+0x732/0x890 ? _raw_spin_lock_irqsave+0x85/0xe0 ? __pfx___alloc_skb+0x10/0x10 ? _raw_read_unlock_irqrestore+0x16/0x50 rxe_init_packet+0x16b/0x4f0 prepare_ack_packet+0xb8/0x830 rxe_receiver+0x499/0x9980 ? __pfx_rxe_receiver+0x10/0x10 ? rxe_completer+0x29e5/0x38c0 ? hrtimer_start_range_ns_common+0x75f/0x1730 ? hrtimer_start_range_ns+0xa6/0x2c0 ? __pfx__raw_spin_lock_irqsave+0x10/0x10 ? __pfx_rxe_receiver+0x10/0x10 do_work+0x144/0x470 process_one_work+0x633/0x1030 ? assign_work+0x11d/0x370 worker_thread+0x45b/0xd10 ? __pfx_worker_thread+0x10/0x10 kthread+0x2c6/0x3b0 ? recalc_sigpending+0x15c/0x1e0 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x36e/0x5a0 ? __pfx_ret_from_fork+0x10/0x10 ? __switch_to+0x572/0xdd0 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Modules linked in: ---[ end trace 0000000000000000 ]--- Fixes: 59caaed7a72a ("IB/iser: Support the remote invalidation exception") Signed-off-by: Yehyeong Lee Link: https://patch.msgid.link/20260819010804.641772-1-yhlee@isslab.korea.ac.kr Reviewed-by: Max Gurtovoy Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/iser/iser_initiator.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/ulp/iser/iser_initiator.c b/drivers/infiniband/ulp/iser/iser_initiator.c index 12a2d12fef07..7ea6888b479c 100644 --- a/drivers/infiniband/ulp/iser/iser_initiator.c +++ b/drivers/infiniband/ulp/iser/iser_initiator.c @@ -598,11 +598,8 @@ static int iser_check_remote_inv(struct iser_conn *iser_conn, struct ib_wc *wc, iser_dbg("conn %p: remote invalidation for rkey %#x\n", iser_conn, rkey); - if (unlikely(!iser_conn->snd_w_inv)) { - iser_err("conn %p: unexpected remote invalidation, terminating connection\n", - iser_conn); - return -EPROTO; - } + if (unlikely(!iser_conn->snd_w_inv)) + goto bad_inv; task = iscsi_itt_to_ctask(iser_conn->iscsi_conn, hdr->itt); if (likely(task)) { @@ -611,12 +608,16 @@ static int iser_check_remote_inv(struct iser_conn *iser_conn, struct ib_wc *wc, if (iser_task->dir[ISER_DIR_IN]) { desc = iser_task->rdma_reg[ISER_DIR_IN].desc; + if (unlikely(!desc)) + goto bad_inv; if (unlikely(iser_inv_desc(desc, rkey))) return -EINVAL; } if (iser_task->dir[ISER_DIR_OUT]) { desc = iser_task->rdma_reg[ISER_DIR_OUT].desc; + if (unlikely(!desc)) + goto bad_inv; if (unlikely(iser_inv_desc(desc, rkey))) return -EINVAL; } @@ -627,6 +628,11 @@ static int iser_check_remote_inv(struct iser_conn *iser_conn, struct ib_wc *wc, } return 0; + +bad_inv: + iser_err("conn %p: unexpected remote invalidation, terminating connection\n", + iser_conn); + return -EPROTO; } From a8fe3dfce8c0d8a76dc3d8486a5bff5feebe156f Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Fri, 21 Aug 2026 17:06:20 +0900 Subject: [PATCH 0586/2366] IB/isert: wait for deferred control PDU completions before releasing the connection isert_send_done() hands ISTATE_SEND_TASKMGTRSP, ISTATE_SEND_REJECT and ISTATE_SEND_TEXTRSP completions off to isert_comp_wq and returns. The work item then runs isert_completion_put() -> isert_put_cmd(), which reads isert_conn->conn and takes conn->cmd_lock. Nothing orders that work item against teardown. isert_wait_conn() queues isert_release_work, which frees isert_conn, and iscsit_close_connection() frees the iscsit_conn right after it returns, so the queued work can run against freed memory. Count the deferred control PDU completions per connection and let isert_wait_conn() wait for them before the release work is queued. ISTATE_SEND_LOGOUTRSP is deliberately not counted: that branch runs iscsit_logout_post_handler(), which ends up waiting for conn->conn_wait_comp, and that completion is only sent by iscsit_close_connection() after it has called iscsit_wait_conn(). Waiting for it here would deadlock. Its wait stays the existing isert_wait4logout(). The splat below is from a kernel with tracing printk()s and an msleep(200) injected into isert_do_control_comp() to widen the window: BUG: KASAN: slab-use-after-free in isert_put_cmd+0x53d/0x620 Read of size 8 at addr ffff8881054f1038 by task kworker/u17:1/182 CPU: 0 UID: 0 PID: 182 Comm: kworker/u17:1 Tainted: G B 7.2.0-rc5-TWIDE-gb8babf08acc7 #1 PREEMPT(lazy) Tainted: [B]=BAD_PAGE Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: isert_comp_wq isert_do_control_comp Call Trace: dump_stack_lvl+0x53/0x70 print_report+0xd0/0x630 ? __pfx__raw_spin_lock_irqsave+0x10/0x10 ? _raw_spin_unlock_irqrestore+0x3e/0x70 ? isert_put_cmd+0x53d/0x620 kasan_report+0xce/0x100 ? isert_put_cmd+0x53d/0x620 isert_put_cmd+0x53d/0x620 ? isert_completion_put+0x305/0x330 ? isert_do_control_comp+0x2ef/0x310 process_one_work+0x633/0x1030 ? assign_work+0x11d/0x370 worker_thread+0x45b/0xd10 ? __pfx_worker_thread+0x10/0x10 ? __pfx_worker_thread+0x10/0x10 kthread+0x2c6/0x3b0 ? recalc_sigpending+0x15c/0x1e0 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x36e/0x5a0 ? __pfx_ret_from_fork+0x10/0x10 ? __switch_to+0x572/0xdd0 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 Allocated by task 48: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0x8f/0xa0 __kmalloc_cache_noprof+0x158/0x370 isert_cma_handler+0x1e3/0x2ae0 cma_cm_event_handler+0x3e/0x240 cma_ib_req_handler+0x17d9/0x4490 cm_process_work+0x41/0x330 cm_work_handler+0x5727/0xc160 process_one_work+0x633/0x1030 worker_thread+0x45b/0xd10 kthread+0x2c6/0x3b0 ret_from_fork+0x36e/0x5a0 ret_from_fork_asm+0x1a/0x30 Freed by task 184: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x43/0x70 kfree+0x121/0x380 iscsit_close_connection+0x7cf/0x1e60 iscsit_take_action_for_connection_exit+0x1b6/0x360 iscsi_target_tx_thread+0x472/0x690 kthread+0x2c6/0x3b0 ret_from_fork+0x36e/0x5a0 ret_from_fork_asm+0x1a/0x30 Fixes: b8d26b3be8b3 ("iser-target: Add iSCSI Extensions for RDMA (iSER) target driver") Signed-off-by: Yehyeong Lee Link: https://patch.msgid.link/20260821080620.1694119-1-yhlee@isslab.korea.ac.kr Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/isert/ib_isert.c | 22 ++++++++++++++++++++++ drivers/infiniband/ulp/isert/ib_isert.h | 2 ++ 2 files changed, 24 insertions(+) diff --git a/drivers/infiniband/ulp/isert/ib_isert.c b/drivers/infiniband/ulp/isert/ib_isert.c index 5087ea983071..e69db43370ff 100644 --- a/drivers/infiniband/ulp/isert/ib_isert.c +++ b/drivers/infiniband/ulp/isert/ib_isert.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "ib_isert.h" @@ -310,6 +311,7 @@ isert_init_conn(struct isert_conn *isert_conn) init_completion(&isert_conn->login_req_comp); init_waitqueue_head(&isert_conn->rem_wait); kref_init(&isert_conn->kref); + atomic_set(&isert_conn->ctrl_comp_cnt, 0); mutex_init(&isert_conn->mutex); INIT_WORK(&isert_conn->release_work, isert_release_work); } @@ -1694,6 +1696,8 @@ isert_do_control_comp(struct work_struct *work) struct isert_conn *isert_conn = isert_cmd->conn; struct ib_device *ib_dev = isert_conn->cm_id->device; struct iscsit_cmd *cmd = isert_cmd->iscsit_cmd; + /* The switch below may free isert_cmd. */ + bool counted = isert_cmd->ctrl_counted; isert_dbg("Cmd %p i_state %d\n", isert_cmd, cmd->i_state); @@ -1715,6 +1719,14 @@ isert_do_control_comp(struct work_struct *work) dump_stack(); break; } + + /* + * The count is what keeps isert_conn alive, so drop it last. The wait + * queue lives in the global hash table, not in isert_conn, so this is + * safe even if the waiter has already freed the connection. + */ + if (counted && atomic_dec_and_test(&isert_conn->ctrl_comp_cnt)) + wake_up_var(&isert_conn->ctrl_comp_cnt); } static void @@ -1758,6 +1770,12 @@ isert_send_done(struct ib_cq *cq, struct ib_wc *wc) case ISTATE_SEND_TEXTRSP: isert_unmap_tx_desc(tx_desc, ib_dev); + /* Paired with the wait in isert_wait_conn(). */ + isert_cmd->ctrl_counted = + isert_cmd->iscsit_cmd->i_state != ISTATE_SEND_LOGOUTRSP; + if (isert_cmd->ctrl_counted) + atomic_inc(&isert_conn->ctrl_comp_cnt); + INIT_WORK(&isert_cmd->comp_work, isert_do_control_comp); queue_work(isert_comp_wq, &isert_cmd->comp_work); return; @@ -2602,6 +2620,10 @@ static void isert_wait_conn(struct iscsit_conn *conn) isert_wait4cmds(conn); isert_wait4logout(isert_conn); + /* Paired with the count taken in isert_send_done(). */ + wait_var_event(&isert_conn->ctrl_comp_cnt, + !atomic_read(&isert_conn->ctrl_comp_cnt)); + queue_work(isert_release_wq, &isert_conn->release_work); } diff --git a/drivers/infiniband/ulp/isert/ib_isert.h b/drivers/infiniband/ulp/isert/ib_isert.h index 0bac5aa66c80..519b17e54bd3 100644 --- a/drivers/infiniband/ulp/isert/ib_isert.h +++ b/drivers/infiniband/ulp/isert/ib_isert.h @@ -153,6 +153,7 @@ struct isert_cmd { struct work_struct comp_work; struct scatterlist sg; bool ctx_init_done; + bool ctrl_counted; }; static inline struct isert_cmd *tx_desc_to_cmd(struct iser_tx_desc *desc) @@ -187,6 +188,7 @@ struct isert_conn { struct mutex mutex; struct kref kref; struct work_struct release_work; + atomic_t ctrl_comp_cnt; bool logout_posted; bool snd_w_inv; wait_queue_head_t rem_wait; From 6c368f7baaea63c1c7c28c6df271511f2a1562c9 Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Fri, 21 Aug 2026 17:09:08 +0800 Subject: [PATCH 0587/2366] RDMA/bnxt_re: check create_singlethread_workqueue() in DCB setup bnxt_re_init_dcb_wq() ignores a failed allocation. The async DCB handler later calls queue_work() on the NULL pointer. Fixes: 51dc5312dcd9 ("RDMA/bnxt_re: Add support to handle DCB_CONFIG_CHANGE event") Signed-off-by: Linkai Gong Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/bnxt_re/main.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/hw/bnxt_re/main.c b/drivers/infiniband/hw/bnxt_re/main.c index ce72db1b4bc3..17654a9e23fe 100644 --- a/drivers/infiniband/hw/bnxt_re/main.c +++ b/drivers/infiniband/hw/bnxt_re/main.c @@ -356,9 +356,13 @@ static int bnxt_re_update_qp1_tos_dscp(struct bnxt_re_dev *rdev) return bnxt_qplib_modify_qp(&rdev->qplib_res, &qp->qplib_qp); } -static void bnxt_re_init_dcb_wq(struct bnxt_re_dev *rdev) +static int bnxt_re_init_dcb_wq(struct bnxt_re_dev *rdev) { rdev->dcb_wq = create_singlethread_workqueue("bnxt_re_dcb_wq"); + if (!rdev->dcb_wq) + return -ENOMEM; + + return 0; } static void bnxt_re_uninit_dcb_wq(struct bnxt_re_dev *rdev) @@ -2339,7 +2343,9 @@ static int bnxt_re_dev_init(struct bnxt_re_dev *rdev, u8 op_type) } bnxt_re_debugfs_add_pdev(rdev); - bnxt_re_init_dcb_wq(rdev); + rc = bnxt_re_init_dcb_wq(rdev); + if (rc) + goto fail; bnxt_re_net_register_async_event(rdev); if (!rdev->is_virtfn) From 99c24a8968ebef0573825b5cb89d5985d51635b9 Mon Sep 17 00:00:00 2001 From: Ryan Mehri Date: Sat, 22 Aug 2026 23:33:43 -0400 Subject: [PATCH 0588/2366] RDMA/rtrs: guard against null kobj name In the client, if `init_path()` errors, the callee tries to clean up with `rtrs_clt_close_conns()`. However, this can lead to calling the event tracing code with `clt_path->kobj->name` being `NULL` and thus causing a null pointer dereference when trying to copy from it. This just adds a guard to check that the name is not `NULL` before copying from it. The server appears to have a similar pattern. Fixes: 5a93929d9f9a1 ("RDMA/rtrs-clt: Add event tracing support") Fixes: c16762b7bf54d ("RDMA/rtrs-srv: Add event tracing support") Reported-by: syzbot+1695193198994f4e7fed@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1695193198994f4e7fed Signed-off-by: Ryan Mehri Link: https://patch.msgid.link/20260823034303.163403-1-ryan.mehri1@gmail.com Reviewed-by: Jack Wang Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h | 2 +- drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h b/drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h index 7738e2676855..29e23404bb7b 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h +++ b/drivers/infiniband/ulp/rtrs/rtrs-clt-trace.h @@ -55,7 +55,7 @@ DECLARE_EVENT_CLASS(rtrs_clt_conn_class, __entry->max_reconnect_attempts = clt->max_reconnect_attempts; __entry->fail_cnt = clt_path->stats->reconnects.fail_cnt; __entry->success_cnt = clt_path->stats->reconnects.successful_cnt; - memcpy(__entry->sessname, kobject_name(&clt_path->kobj), NAME_MAX); + strscpy(__entry->sessname, kobject_name(&clt_path->kobj) ?: "", NAME_MAX); ), TP_printk("RTRS-CLT: sess='%s' state=%s attempts='%d' max-attempts='%d' fail='%d' success='%d'", diff --git a/drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h b/drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h index 587d3e033081..a7d7b971e6c8 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h +++ b/drivers/infiniband/ulp/rtrs/rtrs-srv-trace.h @@ -61,7 +61,7 @@ TRACE_EVENT(send_io_resp_imm, __entry->msg_id = id->msg_id; __entry->wr_cnt = atomic_read(&con->c.wr_cnt); __entry->signal_interval = s->signal_interval; - memcpy(__entry->sessname, kobject_name(&srv_path->kobj), NAME_MAX); + strscpy(__entry->sessname, kobject_name(&srv_path->kobj) ?: "", NAME_MAX); ), TP_printk("sess='%s' state='%s' dir=%s err='%d' inval='%d' glob-inval='%d' msgid='%u' wrcnt='%d' sig-interval='%u'", From 23d7e03a52ece66b992620aa9b8fa5f164077c0f Mon Sep 17 00:00:00 2001 From: Sriharsha Basavapatna Date: Mon, 24 Aug 2026 22:54:43 +0530 Subject: [PATCH 0589/2366] RDMA/bnxt_re: Avoid exposing umdbr to userspace The umdbr field in struct bnxt_re_db_region returns the raw unmapped PCI BAR address of the doorbell region. Avoid sharing this field to the userspace. Change this to a reserved field and stop populating it, keeping the ABI layout and size unchanged for existing binaries. Fixes: 1234a9d8aebb ("RDMA/bnxt_re: Support doorbell extensions") Signed-off-by: Sriharsha Basavapatna Link: https://patch.msgid.link/20260824172443.33943-1-sriharsha.basavapatna@broadcom.com Reviewed-by: Selvin Xavier Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/bnxt_re/uapi.c | 6 ++---- include/uapi/rdma/bnxt_re-abi.h | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/hw/bnxt_re/uapi.c b/drivers/infiniband/hw/bnxt_re/uapi.c index feaf98631fc5..a407c6bc469b 100644 --- a/drivers/infiniband/hw/bnxt_re/uapi.c +++ b/drivers/infiniband/hw/bnxt_re/uapi.c @@ -462,7 +462,6 @@ static int UVERBS_HANDLER(BNXT_RE_METHOD_DBR_ALLOC)(struct uverbs_attr_bundle *a uobj->object = obj; uverbs_finalize_uobj_create(attrs, BNXT_RE_ALLOC_DBR_HANDLE); - dbr.umdbr = dpi->umdbr; dbr.dpi = dpi->dpi; ret = uverbs_copy_to_struct_or_zero(attrs, BNXT_RE_ALLOC_DBR_ATTR, &dbr, sizeof(dbr)); @@ -525,7 +524,6 @@ static int UVERBS_HANDLER(BNXT_RE_METHOD_GET_DEFAULT_DBR)(struct uverbs_attr_bun return PTR_ERR(ib_uctx); uctx = container_of(ib_uctx, struct bnxt_re_ucontext, ib_uctx); - dpi.umdbr = uctx->dpi.umdbr; dpi.dpi = uctx->dpi.dpi; ret = uverbs_copy_to_struct_or_zero(attrs, BNXT_RE_DEFAULT_DBR_ATTR, @@ -543,7 +541,7 @@ DECLARE_UVERBS_NAMED_METHOD(BNXT_RE_METHOD_DBR_ALLOC, UA_MANDATORY), UVERBS_ATTR_PTR_OUT(BNXT_RE_ALLOC_DBR_ATTR, UVERBS_ATTR_STRUCT(struct bnxt_re_db_region, - umdbr), + reserved2), UA_MANDATORY), UVERBS_ATTR_PTR_OUT(BNXT_RE_ALLOC_DBR_OFFSET, UVERBS_ATTR_TYPE(u64), @@ -563,7 +561,7 @@ DECLARE_UVERBS_NAMED_OBJECT(BNXT_RE_OBJECT_DBR, DECLARE_UVERBS_NAMED_METHOD(BNXT_RE_METHOD_GET_DEFAULT_DBR, UVERBS_ATTR_PTR_OUT(BNXT_RE_DEFAULT_DBR_ATTR, UVERBS_ATTR_STRUCT(struct bnxt_re_db_region, - umdbr), + reserved2), UA_MANDATORY)); DECLARE_UVERBS_GLOBAL_METHODS(BNXT_RE_OBJECT_DEFAULT_DBR, diff --git a/include/uapi/rdma/bnxt_re-abi.h b/include/uapi/rdma/bnxt_re-abi.h index 856a1b3036e9..15ed2a51f8f1 100644 --- a/include/uapi/rdma/bnxt_re-abi.h +++ b/include/uapi/rdma/bnxt_re-abi.h @@ -250,7 +250,7 @@ struct bnxt_re_query_device_ex_resp { struct bnxt_re_db_region { __u32 dpi; __u32 reserved; - __aligned_u64 umdbr; + __aligned_u64 reserved2; }; enum bnxt_re_obj_dbr_alloc_attrs { From f4a23e17d84fd2a152d9e12369761934e1af0ee8 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 2 Sep 2026 21:50:58 +0900 Subject: [PATCH 0590/2366] ALSA: rawmidi: Return the error from snd_rawmidi_input_params() The snd_rawmidi_input_params() computes err for the three invalid mode combinations and for resize_runtime_buffer(), applies the new framing and clock type only when err is zero, and then returns 0 anyway. A caller that asked for parameters the kernel rejected is told the change succeeded, and the substream keeps its old buffer. The open_mutex conversion turned the early returns into assignments. It handled the output sibling correctly, which still returns err, and left this one behind. Fixes: 94b98194b62e ("ALSA: rawmidi: Take open_mutex around parameter changes") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20260902125058.19499-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- sound/core/rawmidi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 34b4c7d6dbe6..2617bb5b4faf 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -785,7 +785,7 @@ int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, substream->framing = framing; substream->clock_type = clock_type; } - return 0; + return err; } EXPORT_SYMBOL(snd_rawmidi_input_params); From 387b1baefbb776e3f48dc2261e77a49213f470f7 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Wed, 2 Sep 2026 00:28:34 -0700 Subject: [PATCH 0591/2366] bpf: backtrack_insn(): Handle ld_{abs,ind} subprog exit edge Nicholas Carlini reported a bug in precision backtracking mechanism for BPF_LD | BPF_{IND,ABS} instructions. These instructions are modelled as two branches: - fallthrough; - implicit exit from current subprogram. The implicit exit case was not handled by the backtrack_insn() function. When backtracking such a path backtrack_insn() did not call bt_subprog_enter(), which meant that backtracking continued manipulating precision marks in a caller frame, while looking at instructions in a callee frame. This lead to segmentation faults during verification (see the selftest), or unsound state pruning. Fixes: ee861486e377 ("bpf: Fix ld_{abs,ind} failure path analysis in subprogs") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Signed-off-by: Daniel Borkmann Acked-by: Daniel Borkmann Link: https://lore.kernel.org/bpf/20260901-bug-016-backtrack-ld-abs-v1-1-59368f1be435@gmail.com --- kernel/bpf/backtrack.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/kernel/bpf/backtrack.c b/kernel/bpf/backtrack.c index a2b18a9f1694..eaf7438b9ebf 100644 --- a/kernel/bpf/backtrack.c +++ b/kernel/bpf/backtrack.c @@ -582,16 +582,29 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx, */ } } else if (class == BPF_LD) { - if (!bt_is_reg_set(bt, dreg)) - return 0; - bt_clear_reg(bt, dreg); /* It's ld_imm64 or ld_abs or ld_ind. * For ld_imm64 no further tracking of precision * into parent is necessary */ - if (mode == BPF_IND || mode == BPF_ABS) - /* to be analyzed */ - return -ENOTSUPP; + if (mode == BPF_IMM) { + bt_clear_reg(bt, dreg); + return 0; + } + /* + * BPF_{IND,ABS} are modelled as two branches: + * - fallthrough; + * - implicit subprogram exit. + * It is necessary to switch current frame if + * implicit subprogram exit branch is backtracked. + */ + if (mode == BPF_IND || mode == BPF_ABS) { + if (bt_is_reg_set(bt, dreg)) + return -ENOTSUPP; + if (subseq_idx != idx + 1) + if (bt_subprog_enter(bt)) + return -EFAULT; + return 0; + } } /* Propagate precision marks to linked registers, to account for * registers marked as precise in this function. From ce6b9e5dd873de532cd924e2abc928220cdc2738 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Wed, 2 Sep 2026 00:28:35 -0700 Subject: [PATCH 0592/2366] selftests/bpf: Precision tracking across BPF_ABS subprog exit A test case checking that the verifier properly backtracks both fallthrough and implicit subprogram exit paths modelled for BPF_LD | BPF_ABS instruction. Without the previous patch: - the verifier did not call bt_subprog_enter() on the implicit subprogram exit path; - bpf_pseudo_call() branch in backtrack_insn() executed 'bpf_bt_set_frame_reg(bt, bt->frame - 1, i);' with bt->frame == 0; - causing a segmentation fault. Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Signed-off-by: Daniel Borkmann Acked-by: Daniel Borkmann Link: https://lore.kernel.org/bpf/20260901-bug-016-backtrack-ld-abs-v1-2-59368f1be435@gmail.com --- .../bpf/progs/verifier_subprog_precision.c | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c index d21d32f6a676..e174a905c562 100644 --- a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c +++ b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c @@ -846,4 +846,55 @@ __naked int subprog_result_tail_call(void) ); } +__naked __noinline __used +static int ld_abs_subprog(void) +{ + asm volatile ( + "r6 = r1;" + "r7 = r1;" + ".8byte %[ld_abs];" + "exit;" + : + : __imm_insn(ld_abs, BPF_LD_ABS(BPF_W, 0)) + : __clobber_all); +} + +/* + * Buggy verifier did not properly backtrack early subprogram exit + * modelled for BPF_LD | BPF_ABS instruction, causing a segfault. + */ +SEC("socket") +__success +__log_level(2) +/* early exit path */ +__msg("3: (0f) r1 += r7") +__msg("mark_precise: frame0: regs=r7 stack= before 2: (bf) r1 = r10") +__msg("mark_precise: frame0: regs=r7 stack= before 9: (20) r0 = *(u32 *)skb[0]") +__msg("mark_precise: frame1: regs= stack= before 8: (bf) r7 = r1") +__msg("mark_precise: frame1: regs= stack= before 7: (bf) r6 = r1") +__msg("mark_precise: frame1: regs= stack= before 1: (85) call pc+5") +__msg("mark_precise: frame0: regs=r7 stack= before 0: (b7) r7 = -8") +/* fallthrough path */ +__msg("3: (0f) r1 += r7") +__msg("mark_precise: frame0: regs=r7 stack= before 2: (bf) r1 = r10") +__msg("mark_precise: frame0: regs=r7 stack= before 10: (95) exit") +__msg("mark_precise: frame1: regs= stack= before 9: (20) r0 = *(u32 *)skb[0]") +__msg("mark_precise: frame1: regs= stack= before 8: (bf) r7 = r1") +__msg("mark_precise: frame1: regs= stack= before 7: (bf) r6 = r1") +__msg("mark_precise: frame1: regs= stack= before 1: (85) call pc+5") +__msg("mark_precise: frame0: regs=r7 stack= before 0: (b7) r7 = -8") +__naked int ld_abs_backtrack_both_paths(void) +{ + asm volatile ( + "r7 = -8;" + "call ld_abs_subprog;" + "r1 = r10;" + "r1 += r7;" /* mark r7 as precise */ + "*(u64 *)(r1 + 0) = 0;" + "r0 = 0;" + "exit;" + ::: __clobber_all + ); +} + char _license[] SEC("license") = "GPL"; From 7d4d4f3b668d708d94f62ecdd33ac330a6fd8a84 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 2 Sep 2026 16:36:26 +0200 Subject: [PATCH 0593/2366] dm-integrity: fix NULL pointer dereference when the 'R' flag is used If the dm-integrity device has the SB_FLAG_DIRTY_BITMAP flag set and the user activates the device in the 'R' mode, a crash in dm_integrity_resume happens because the function attempts to read the journal containing the bitmap. This patch makes dm-integrity skip any writes to the device in dm_integrity_resume if the device is activated in the 'R' mode. Signed-off-by: Mikulas Patocka Fixes: 468dfca38b1a ("dm integrity: add a bitmap mode") Cc: stable@vger.kernel.org --- drivers/md/dm-integrity.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 5327d7c6a71c..92970e12267a 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -3875,6 +3875,10 @@ static void dm_integrity_resume(struct dm_target *ti) r = sync_rw_sb(ic, REQ_OP_READ); if (r) dm_integrity_io_error(ic, "reading superblock", r); + + if (ic->mode == 'R') + goto skip_writes; + if ((ic->sb->flags & flags) != flags) { ic->sb->flags |= flags; r = sync_rw_sb(ic, REQ_OP_WRITE | REQ_FUA); @@ -3984,6 +3988,7 @@ static void dm_integrity_resume(struct dm_target *ti) } } +skip_writes: ic->reboot_notifier.notifier_call = dm_integrity_reboot; ic->reboot_notifier.next = NULL; ic->reboot_notifier.priority = INT_MAX - 1; /* be notified after md and before hardware drivers */ From ece06de726737e887dc0225c8283477624f8ae21 Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Mon, 17 Aug 2026 16:07:28 +0800 Subject: [PATCH 0594/2366] scsi: bsg: Cap io_uring sense copy to max_response_len Completion copied scmd->sense_len to the user response buffer without honoring max_response_len. After a valid sense, the midlayer sets sense_len to the real length (up to SCSI_SENSE_BUFFERSIZE), so a smaller user buffer was overrun. Fixes: 7b6d3255e7f8 ("scsi: bsg: add io_uring passthrough handler") Cc: stable@vger.kernel.org Signed-off-by: Yang Xiuwei Link: https://patch.msgid.link/20260817080730.967879-2-yangxiuwei@kylinos.cn Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/scsi_bsg.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/scsi/scsi_bsg.c b/drivers/scsi/scsi_bsg.c index e80dec53174e..b3c4b1063d6f 100644 --- a/drivers/scsi/scsi_bsg.c +++ b/drivers/scsi/scsi_bsg.c @@ -18,6 +18,7 @@ struct scsi_bsg_uring_cmd_pdu { struct bio *bio; /* mapped user buffer, unmap in task work */ struct request *req; /* block request, freed in task work */ u64 response_addr; /* user space response buffer address */ + u32 max_response_len; /* user response buffer size */ }; static_assert(sizeof(struct scsi_bsg_uring_cmd_pdu) <= sizeof_field(struct io_uring_cmd, pdu)); @@ -45,8 +46,8 @@ static void scsi_bsg_uring_task_cb(struct io_tw_req tw_req, io_tw_token_t tw) if (scsi_status_is_check_condition(scmd->result)) { driver_status = DRIVER_SENSE; if (pdu->response_addr) - sense_len_wr = min_t(u8, scmd->sense_len, - SCSI_SENSE_BUFFERSIZE); + sense_len_wr = min_t(unsigned int, pdu->max_response_len, + scmd->sense_len); } if (sense_len_wr) { @@ -155,8 +156,7 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc } pdu->response_addr = cmd->response; - scmd->sense_len = cmd->max_response_len ? - min(cmd->max_response_len, SCSI_SENSE_BUFFERSIZE) : SCSI_SENSE_BUFFERSIZE; + pdu->max_response_len = cmd->max_response_len; if (cmd->dout_xfer_len || cmd->din_xfer_len) { ret = scsi_bsg_map_user_buffer(req, ioucmd, issue_flags, gfp_mask); From 4b3c5965fca99f62d31c963294bd5b23cc488e97 Mon Sep 17 00:00:00 2001 From: Rahul Chandelkar Date: Mon, 17 Aug 2026 16:07:29 +0800 Subject: [PATCH 0595/2366] scsi: bsg: Fix TOCTOU in io_uring passthrough command setup scsi_bsg_uring_cmd() reads bsg_uring_cmd from the shared mmap'd SQE. Userspace can change a field after we check it and before we use it. request_len is the sharp case: it can grow past sizeof(scmd->cmnd) after the bound check and overflow scmd->cmnd in copy_from_user(). READ_ONCE() the SQE fields we check or use into locals before use. Fixes: 7b6d3255e7f8 ("scsi: bsg: add io_uring passthrough handler") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20260527105931.3950913-1-rc@rexion.ai Signed-off-by: Rahul Chandelkar Co-developed-by: Yang Xiuwei Signed-off-by: Yang Xiuwei Link: https://patch.msgid.link/20260817080730.967879-3-yangxiuwei@kylinos.cn Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/scsi_bsg.c | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/drivers/scsi/scsi_bsg.c b/drivers/scsi/scsi_bsg.c index b3c4b1063d6f..5eec248a77a6 100644 --- a/drivers/scsi/scsi_bsg.c +++ b/drivers/scsi/scsi_bsg.c @@ -77,12 +77,10 @@ static enum rq_end_io_ret scsi_bsg_uring_cmd_done(struct request *req, static int scsi_bsg_map_user_buffer(struct request *req, struct io_uring_cmd *ioucmd, - unsigned int issue_flags, gfp_t gfp_mask) + unsigned int issue_flags, gfp_t gfp_mask, + bool is_write, u64 buf_addr, + unsigned long buf_len) { - const struct bsg_uring_cmd *cmd = io_uring_sqe128_cmd(ioucmd->sqe, struct bsg_uring_cmd); - bool is_write = cmd->dout_xfer_len > 0; - u64 buf_addr = is_write ? cmd->dout_xferp : cmd->din_xferp; - unsigned long buf_len = is_write ? cmd->dout_xfer_len : cmd->din_xfer_len; struct iov_iter iter; int ret; @@ -105,21 +103,28 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc unsigned int issue_flags, bool open_for_write) { struct scsi_bsg_uring_cmd_pdu *pdu = scsi_bsg_uring_cmd_pdu(ioucmd); - const struct bsg_uring_cmd *cmd = io_uring_sqe128_cmd(ioucmd->sqe, struct bsg_uring_cmd); + const struct bsg_uring_cmd *cmd = + io_uring_sqe128_cmd(ioucmd->sqe, struct bsg_uring_cmd); struct scsi_cmnd *scmd; struct request *req; blk_mq_req_flags_t blk_flags = 0; gfp_t gfp_mask = GFP_KERNEL; + u64 request = READ_ONCE(cmd->request); + u32 request_len = READ_ONCE(cmd->request_len); + u64 dout_xferp = READ_ONCE(cmd->dout_xferp); + u32 dout_xfer_len = READ_ONCE(cmd->dout_xfer_len); + u64 din_xferp = READ_ONCE(cmd->din_xferp); + u32 din_xfer_len = READ_ONCE(cmd->din_xfer_len); int ret; if (cmd->protocol != BSG_PROTOCOL_SCSI || cmd->subprotocol != BSG_SUB_PROTOCOL_SCSI_CMD) return -EINVAL; - if (!cmd->request || cmd->request_len == 0) + if (!request || request_len == 0) return -EINVAL; - if (cmd->dout_xfer_len && cmd->din_xfer_len) { + if (dout_xfer_len && din_xfer_len) { pr_warn_once("BIDI support in bsg has been removed.\n"); return -EOPNOTSUPP; } @@ -132,20 +137,20 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc gfp_mask = GFP_NOWAIT; } - req = scsi_alloc_request(q, cmd->dout_xfer_len ? + req = scsi_alloc_request(q, dout_xfer_len ? REQ_OP_DRV_OUT : REQ_OP_DRV_IN, blk_flags); if (IS_ERR(req)) return PTR_ERR(req); scmd = blk_mq_rq_to_pdu(req); - if (cmd->request_len > sizeof(scmd->cmnd)) { + if (request_len > sizeof(scmd->cmnd)) { ret = -EINVAL; goto out_free_req; } - scmd->cmd_len = cmd->request_len; + scmd->cmd_len = request_len; scmd->allowed = SG_DEFAULT_RETRIES; - if (copy_from_user(scmd->cmnd, uptr64(cmd->request), cmd->request_len)) { + if (copy_from_user(scmd->cmnd, uptr64(request), request_len)) { ret = -EFAULT; goto out_free_req; } @@ -158,8 +163,14 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc pdu->response_addr = cmd->response; pdu->max_response_len = cmd->max_response_len; - if (cmd->dout_xfer_len || cmd->din_xfer_len) { - ret = scsi_bsg_map_user_buffer(req, ioucmd, issue_flags, gfp_mask); + if (dout_xfer_len || din_xfer_len) { + bool is_write = dout_xfer_len > 0; + u64 buf_addr = is_write ? dout_xferp : din_xferp; + unsigned long buf_len = is_write ? dout_xfer_len : din_xfer_len; + + ret = scsi_bsg_map_user_buffer(req, ioucmd, issue_flags, + gfp_mask, is_write, buf_addr, + buf_len); if (ret) goto out_free_req; pdu->bio = req->bio; From 7ac81e2d2240f2c57bd073b0733e0b2abca38e82 Mon Sep 17 00:00:00 2001 From: Genjian Zhang Date: Fri, 7 Aug 2026 23:57:15 +0800 Subject: [PATCH 0596/2366] dm-ebs: fix incorrect device offset check in ebs_ctr() is a backing-device sector offset; ti->len is the virtual target length. Comparing them rejects valid tables, e.g.: dmsetup create ebs0 --table "0 1048576 ebs /dev/sda 2097152 1 8" -> ebs: Invalid device offset sector (-EINVAL) Drop the check. Bounds against the backing device are already enforced later by device_area_is_invalid() via ebs_iterate_devices(). Cc: stable@vger.kernel.org Fixes: d3c7b35c20d6 ("dm: add emulated block size target") Signed-off-by: Genjian Zhang Signed-off-by: Mikulas Patocka --- drivers/md/dm-ebs-target.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/md/dm-ebs-target.c b/drivers/md/dm-ebs-target.c index 1e52bde48b91..5d67c6c19d4b 100644 --- a/drivers/md/dm-ebs-target.c +++ b/drivers/md/dm-ebs-target.c @@ -265,8 +265,7 @@ static int ebs_ctr(struct dm_target *ti, unsigned int argc, char **argv) r = -EINVAL; if (sscanf(argv[1], "%llu%c", &tmp, &dummy) != 1 || - tmp != (sector_t)tmp || - (sector_t)tmp >= ti->len) { + tmp != (sector_t)tmp) { ti->error = "Invalid device offset sector"; goto bad; } From 4617721c502b2ddaa4e324e86da4997edf738fa5 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 2 Sep 2026 09:55:01 -0400 Subject: [PATCH 0597/2366] ftrace: Synchronize the initialization of ftrace_ops There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt --- kernel/trace/ftrace.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index c7cf36f2dd7b..53d5db60bfa5 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -75,6 +75,8 @@ .func_hash = &opsname.local_hash, \ .local_hash.regex_lock = __MUTEX_INITIALIZER(opsname.local_hash.regex_lock), \ .subop_list = LIST_HEAD_INIT(opsname.subop_list), +/* Used only to synchronize the initialization of ftrace_ops */ +static DEFINE_MUTEX(ops_mutex); #else #define INIT_OPS_HASH(opsname) #endif @@ -159,11 +161,18 @@ const struct ftrace_ops ftrace_nop_ops = { static inline void ftrace_ops_init(struct ftrace_ops *ops) { #ifdef CONFIG_DYNAMIC_FTRACE - if (!(ops->flags & FTRACE_OPS_FL_INITIALIZED)) { + unsigned long flags = smp_load_acquire(&ops->flags); + + if (!(flags & FTRACE_OPS_FL_INITIALIZED)) { + guard(mutex)(&ops_mutex); + /* Could have been initialized before lock taken */ + if (unlikely(ops->flags & FTRACE_OPS_FL_INITIALIZED)) + return; mutex_init(&ops->local_hash.regex_lock); INIT_LIST_HEAD(&ops->subop_list); ops->func_hash = &ops->local_hash; - ops->flags |= FTRACE_OPS_FL_INITIALIZED; + flags = ops->flags | FTRACE_OPS_FL_INITIALIZED; + smp_store_release(&ops->flags, flags); } #endif } From 90af7fde083e1b22c349c3a8b1626728e44e474c Mon Sep 17 00:00:00 2001 From: Yifei Gao Date: Tue, 4 Aug 2026 21:34:56 +0000 Subject: [PATCH 0598/2366] memstick: ms_block: destroy io_queue workqueue on removal msb_init_disk() creates the per-card ordered workqueue msb->io_queue with alloc_ordered_workqueue(). It is torn down with destroy_workqueue() only on the init error path; msb_remove() never destroys it. msb_stop() merely flushes the queue, and neither msb_data_clear() nor put_disk() free it. As a result every card insert/remove cycle leaks the workqueue and its kworker, exhausting kernel memory over repeated cycles. Destroy the workqueue in msb_remove() after the disk has been removed and the queue drained. Fixes: 0ab30494bc4f ("memstick: add support for legacy memorysticks") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Yifei Gao Signed-off-by: Ulf Hansson --- drivers/memstick/core/ms_block.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/memstick/core/ms_block.c b/drivers/memstick/core/ms_block.c index ce33907bfc24..65154e569a9e 100644 --- a/drivers/memstick/core/ms_block.c +++ b/drivers/memstick/core/ms_block.c @@ -2204,6 +2204,8 @@ static void msb_remove(struct memstick_dev *card) msb_data_clear(msb); mutex_unlock(&msb_disk_lock); + destroy_workqueue(msb->io_queue); + put_disk(msb->disk); memstick_set_drvdata(card, NULL); } From 08d4d9802d58bf032099091e6acf719f3298f28e Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Wed, 26 Aug 2026 15:31:46 +0800 Subject: [PATCH 0599/2366] RDMA/uverbs: Fix potential leak of resources->collection in flow_resources_alloc() The two array allocations are done unconditionally and only checked afterwards, so if the counters allocation fails while the collection allocation succeeds, the error path frees counters and the containing struct but never frees resources->collection, losing the only pointer to it. Fixes: de7498147d00 ("RDMA/uverbs: Refactor flow_resources_alloc() function") Signed-off-by: Li RongQing Link: https://patch.msgid.link/20260826073146.2203-1-lirongqing@baidu.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/uverbs_flow.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/infiniband/core/uverbs_flow.c b/drivers/infiniband/core/uverbs_flow.c index 1528a294f7f8..de5a2769f084 100644 --- a/drivers/infiniband/core/uverbs_flow.c +++ b/drivers/infiniband/core/uverbs_flow.c @@ -26,6 +26,7 @@ struct ib_uflow_resources *flow_resources_alloc(size_t num_specs) return resources; err: + kfree(resources->collection); kfree(resources->counters); kfree(resources); From af8c27375733fb6a6df9fa484cda77cc3dd0cb80 Mon Sep 17 00:00:00 2001 From: Thomas Lamprecht Date: Thu, 27 Aug 2026 19:24:24 +0200 Subject: [PATCH 0600/2366] scsi: megaraid_sas: Limit NVMe request size to the PRP chain frame megasas_make_prp_nvme() builds a command's PRP list in cmd->sg_frame, a DMA pool buffer of instance->max_chain_frame_sz bytes, spending one entry per NVMe page of the transfer plus one per page of the buffer for the chain pointer. The loop runs until the transfer is described and never checks the buffer bound. max_hw_sectors comes straight from the MDTS the firmware reports for the drive. On drives with a large MDTS the only thing keeping the list inside the buffer was the block layer default of 1280 KiB, which needs 320 entries, which fit into a 4 KiB frame as that holds 512. But since commit 9b8b84879d4a ("block: Increase BLK_DEF_MAX_SECTORS_CAP") that default is 4 MiB, and such a transfer needs 1025 entries, so the list runs a full page past the end of the frame: sd 1:0:1:0: [sdb] tag#630 page boundary ptr_sgl: 0x00000000ba62d13f BUG: unable to handle page fault for address: ff663bcb81e7c000 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page RIP: 0010:megasas_build_and_issue_cmd_fusion+0xeaa/0x1870 [megaraid_sas] If the page after the frame happens to be mapped, the overrun does not fault but silently corrupts the neighbouring pool entry, which is another in-flight command's PRP list. Cap max_hw_sectors at what the chain frame can describe, less one page for transfers that do not start on a page boundary and so need one entry more. This is the megaraid_sas counterpart of commit 04631f55afc5 ("scsi: mpt3sas: Limit NVMe request size to 2 MiB"), but derives the limit from max_chain_frame_sz rather than hardcoding it. Cc: stable@vger.kernel.org Fixes: 9b8b84879d4a ("block: Increase BLK_DEF_MAX_SECTORS_CAP") Reported-by: Lukasz Magiera Closes: https://lore.kernel.org/all/GPhsSM0vkgyIrs0DIZ62qeUZX7X4RxwQXVKiuvMx-lHQVSPDxpztUyQOGS0xikqvJ-Z94hMV-dW_5KN_0CX2hsfV7kTf_t0MTf6vdAAaSEc=@magik.net/ Reported-by: Mira Limbeck Closes: https://lore.kernel.org/all/d171cc76-bf25-48ce-b482-d344669dfc24@proxmox.com/ Suggested-by: Martin K. Petersen Link: https://lore.kernel.org/all/yq17bmzd5jr.fsf@ca-mkp.ca.oracle.com/ Signed-off-by: Thomas Lamprecht Closes: https://lore.kernel.org/linux-scsi/20260827182106.535D61F000E9@smtp.kernel.org Link: https://patch.msgid.link/20260827175743.734593-1-t.lamprecht@proxmox.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/megaraid/megaraid_sas_base.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/megaraid/megaraid_sas_base.c b/drivers/scsi/megaraid/megaraid_sas_base.c index f0152b043e18..b95f187297ae 100644 --- a/drivers/scsi/megaraid/megaraid_sas_base.c +++ b/drivers/scsi/megaraid/megaraid_sas_base.c @@ -1973,12 +1973,23 @@ megasas_set_nvme_device_properties(struct scsi_device *sdev, { struct megasas_instance *instance; u32 mr_nvme_pg_size; + u64 max_prp_io; instance = (struct megasas_instance *)sdev->host->hostdata; mr_nvme_pg_size = max_t(u32, instance->nvme_page_size, MR_DEFAULT_NVME_PAGE_SIZE); - lim->max_hw_sectors = max_io_size / 512; + /* + * megasas_make_prp_nvme() builds the PRP list in cmd->sg_frame without + * bounding it against that buffer, and spends one entry per page of + * it on the chain pointer. Cap the transfer at what the buffer holds, + * less one page for lists that start off a page boundary. + */ + max_prp_io = (u64)((instance->max_chain_frame_sz / sizeof(u64)) - + (instance->max_chain_frame_sz / mr_nvme_pg_size) - 1) * + mr_nvme_pg_size; + + lim->max_hw_sectors = min_t(u64, max_io_size, max_prp_io) >> SECTOR_SHIFT; lim->virt_boundary_mask = mr_nvme_pg_size - 1; } From 3476c28c9addfa253f505e6bd87f1f5598b961d0 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Wed, 26 Aug 2026 15:32:16 +0800 Subject: [PATCH 0601/2366] RDMA/mad: Fix receive buffer leak when PKey enforcement fails ib_mad_complete_recv() initializes mad_recv_wc->rmpp_list and then runs ib_mad_enforce_security() before linking recv_buf onto that list. On failure it calls ib_free_recv_mad(), which only walks rmpp_list and frees the ib_mad_private of every buffer found there. As the list is still empty at that point, nothing is freed at all. The caller cannot clean up either: ib_mad_recv_done() sets recv to NULL right after ib_mad_complete_recv() returns, assuming the MAD layer took ownership of the buffer. Every MAD that fails the PKey check therefore leaks one ib_mad_private (about 300 bytes per IB port MAD, ~2K for OPA), and a remote node can trigger this repeatedly by sending MADs with a wrong PKey. Link recv_buf onto rmpp_list right after the list is initialized, so the error path has something to free. Fixes: 47a2b338fe63 ("IB/core: Enforce security on management datagrams") Signed-off-by: Li RongQing Link: https://patch.msgid.link/20260826073216.2367-1-lirongqing@baidu.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/mad.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/core/mad.c b/drivers/infiniband/core/mad.c index e0b3b36b8b14..3c91f00d09c6 100644 --- a/drivers/infiniband/core/mad.c +++ b/drivers/infiniband/core/mad.c @@ -2059,6 +2059,8 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv, int ret; INIT_LIST_HEAD(&mad_recv_wc->rmpp_list); + list_add(&mad_recv_wc->recv_buf.list, &mad_recv_wc->rmpp_list); + ret = ib_mad_enforce_security(mad_agent_priv, mad_recv_wc->wc->pkey_index); if (ret) { @@ -2067,7 +2069,6 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv, return; } - list_add(&mad_recv_wc->recv_buf.list, &mad_recv_wc->rmpp_list); if (is_kernel_rmpp_data_response(mad_agent_priv, mad_recv_wc)) { spin_lock_irqsave(&mad_agent_priv->lock, flags); mad_send_wr = ib_find_send_mad(mad_agent_priv, mad_recv_wc); From 00baeade709fb66da647e8327e8398bb532e30f7 Mon Sep 17 00:00:00 2001 From: Cheng Xu Date: Fri, 28 Aug 2026 11:03:44 +0800 Subject: [PATCH 0602/2366] RDMA/erdma: Use IRQ-safe XArray helpers for QP and CQ tables Locked QP and CQ lookups from EQ interrupts can deadlock with create-path XArray updates. If an interrupt arrives while the create path holds the plain xa_lock, the lookup spins forever trying to acquire the same lock. Use IRQ-safe XArray helpers for all QP and CQ create-path updates, including the GSI QP store and error paths. Initialize both arrays with XA_FLAGS_LOCK_IRQ so sleeping allocations preserve interrupt state. Fixes: 98df2aee1459 ("RDMA/erdma: Hold CQ references when processing EQ events") Fixes: a52eeff32024 ("RDMA/erdma: Hold QP references for AE and CM processing") Signed-off-by: Cheng Xu Link: https://patch.msgid.link/20260828030344.88021-1-chengyou@linux.alibaba.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/erdma/erdma_main.c | 4 ++-- drivers/infiniband/hw/erdma/erdma_verbs.c | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/infiniband/hw/erdma/erdma_main.c b/drivers/infiniband/hw/erdma/erdma_main.c index 7e87a815e853..445182c6bc5d 100644 --- a/drivers/infiniband/hw/erdma/erdma_main.c +++ b/drivers/infiniband/hw/erdma/erdma_main.c @@ -572,8 +572,8 @@ static int erdma_ib_device_add(struct pci_dev *pdev) INIT_LIST_HEAD(&dev->cep_list); spin_lock_init(&dev->lock); - xa_init_flags(&dev->qp_xa, XA_FLAGS_ALLOC1); - xa_init_flags(&dev->cq_xa, XA_FLAGS_ALLOC1); + xa_init_flags(&dev->qp_xa, XA_FLAGS_ALLOC1 | XA_FLAGS_LOCK_IRQ); + xa_init_flags(&dev->cq_xa, XA_FLAGS_ALLOC1 | XA_FLAGS_LOCK_IRQ); dev->next_alloc_cqn = 1; dev->next_alloc_qpn = 1; diff --git a/drivers/infiniband/hw/erdma/erdma_verbs.c b/drivers/infiniband/hw/erdma/erdma_verbs.c index 65b1af1e6623..f18b88bba281 100644 --- a/drivers/infiniband/hw/erdma/erdma_verbs.c +++ b/drivers/infiniband/hw/erdma/erdma_verbs.c @@ -1021,15 +1021,15 @@ int erdma_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, init_completion(&qp->safe_free); if (qp->ibqp.qp_type == IB_QPT_GSI) { - old_entry = xa_store(&dev->qp_xa, 1, qp, GFP_KERNEL); + old_entry = xa_store_irq(&dev->qp_xa, 1, qp, GFP_KERNEL); if (xa_is_err(old_entry)) ret = xa_err(old_entry); else qp->ibqp.qp_num = 1; } else { - ret = xa_alloc_cyclic(&dev->qp_xa, &qp->ibqp.qp_num, qp, - XA_LIMIT(1, dev->attrs.max_qp - 1), - &dev->next_alloc_qpn, GFP_KERNEL); + ret = xa_alloc_cyclic_irq(&dev->qp_xa, &qp->ibqp.qp_num, qp, + XA_LIMIT(1, dev->attrs.max_qp - 1), + &dev->next_alloc_qpn, GFP_KERNEL); } if (ret < 0) { @@ -1089,7 +1089,7 @@ int erdma_create_qp(struct ib_qp *ibqp, struct ib_qp_init_attr *attrs, else free_kernel_qp(qp); err_out_xa: - xa_erase(&dev->qp_xa, QP_ID(qp)); + xa_erase_irq(&dev->qp_xa, QP_ID(qp)); err_out: return ret; } @@ -1993,9 +1993,9 @@ int erdma_create_cq(struct ib_cq *ibcq, const struct ib_cq_init_attr *attr, refcount_set(&cq->refcount, 1); init_completion(&cq->free); - ret = xa_alloc_cyclic(&dev->cq_xa, &cq->cqn, cq, - XA_LIMIT(1, dev->attrs.max_cq - 1), - &dev->next_alloc_cqn, GFP_KERNEL); + ret = xa_alloc_cyclic_irq(&dev->cq_xa, &cq->cqn, cq, + XA_LIMIT(1, dev->attrs.max_cq - 1), + &dev->next_alloc_cqn, GFP_KERNEL); if (ret < 0) return ret; @@ -2041,7 +2041,7 @@ int erdma_create_cq(struct ib_cq *ibcq, const struct ib_cq_init_attr *attr, } err_out_xa: - xa_erase(&dev->cq_xa, cq->cqn); + xa_erase_irq(&dev->cq_xa, cq->cqn); return ret; } From 3fb905f07ea45b31c8f67ba6e4668de46f527e65 Mon Sep 17 00:00:00 2001 From: Jacob Moroni Date: Tue, 1 Sep 2026 16:00:14 +0000 Subject: [PATCH 0603/2366] RDMA/irdma: Enforce local fence for IB_WR_REG_MR Enforce local fence for IB_WR_REG_MR to avoid spurious FASTREG_VALID_MKEY async events during heavy invalidation and registration activity. Commit 69e8e429bca2 ("RDMA/irdma: Enforce local fence for LOCAL_INV WRs") was very similar, but was not sufficient to prevent all occurrences of these async events. Fixes: b48c24c2d710 ("RDMA/irdma: Implement device supported verb APIs") Signed-off-by: Jacob Moroni Link: https://patch.msgid.link/20260901160014.2026285-1-jmoroni@google.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/irdma/verbs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/irdma/verbs.c b/drivers/infiniband/hw/irdma/verbs.c index 9cfd84dd6869..5d71300da0ff 100644 --- a/drivers/infiniband/hw/irdma/verbs.c +++ b/drivers/infiniband/hw/irdma/verbs.c @@ -4281,7 +4281,7 @@ static int irdma_post_send(struct ib_qp *ibqp, stag_info.total_len = iwmr->ibmr.length; stag_info.reg_addr_pa = *palloc->level1.addr; stag_info.first_pm_pbl_index = palloc->level1.idx; - stag_info.local_fence = ib_wr->send_flags & IB_SEND_FENCE; + stag_info.local_fence = true; if (iwmr->npages > IRDMA_MIN_PAGES_PER_FMR) stag_info.chunk_size = 1; err = irdma_sc_mr_fast_register(&iwqp->sc_qp, &stag_info, From 65e05ec252a9b79e75930d3c4dd42d8877db04c5 Mon Sep 17 00:00:00 2001 From: Anthony Krowiak Date: Tue, 18 Aug 2026 15:33:49 -0400 Subject: [PATCH 0604/2366] s390/vfio-ap: fix KVM GISC and page leak when queue removed from host config Three related problems exist in the handling of KVM interrupt and page resources when a queue is removed from the host's AP configuration while assigned to a mediated device (mdev). Problem 1: ~~~~~~~~~ AP_RESPONSE_Q_NOT_AVAIL not handled in vfio_ap_mdev_reset_queue() When the AP bus removes a queue device whose adapter or domain has been removed from the host's AP configuration, vfio_ap_mdev_remove_queue() is called. If the queue is still in the host's AP configuration at that point, it calls vfio_ap_mdev_reset_queue(), which issues a PQAP(ZAPQ). Since the adapter is already gone from the host configuration, ap_zapq() returns AP_RESPONSE_Q_NOT_AVAIL (0x01). This response code is not handled in vfio_ap_mdev_reset_queue()'s switch statement and falls through to the default case, which issues a WARN but does not call vfio_ap_free_aqic_resources(). As a result, if IRQ handling was enabled for the queue by the guest, the KVM GISC registration and the pinned guest page holding the notification indicator byte (NIB) are both leaked. This is fixed by adding AP_RESPONSE_Q_NOT_AVAIL to the same case as AP_RESPONSE_DECONFIGURED and AP_RESPONSE_CHECKSTOPPED in vfio_ap_mdev_reset_queue(). Like those response codes, Q_NOT_AVAIL indicates the queue is not operational and no further reset attempts are possible; the correct action is to free the IRQ resources immediately. Problem 2: ~~~~~~~~~ AP_RESPONSE_Q_NOT_AVAIL not handled in apq_status_check() In vfio_ap_mdev_reset_queue(), there are four cases that indicate a queue reset has not yet completed, in which case apq_reset_check() is queued to a work queue to verify completion of the reset operation. This function uses the PQAP(TAPQ) function to get the queue's status and calls apq_status_check() to verify whether the reset has completed, failed or needs to be executed again. As described in Problem #1 above, apq_reset_check() does not specifically check for AP_RESPONSE_Q_NOT_AVAIL, thereby potentially leaking KVM GISC registration and the pinned guest page holding the NIB. This is fixed by adding a case statement for AP_RESPONSE_Q_NOT_AVAIL to apq_status_check() and returning -ENODEV for that case. The caller, apq_reset_check() will then check for this return code and call vfio_ap_free_aqic_resources() to prevent the leak. Problem 3: ~~~~~~~~~ vfio_ap_free_aqic_resources() leaks saved_isc when kvm is NULL vfio_ap_free_aqic_resources() guards the call to kvm_s390_gisc_unregister() with: if (q->saved_isc != VFIO_AP_ISC_INVALID && !WARN_ON(!(q->matrix_mdev && q->matrix_mdev->kvm))) If matrix_mdev->kvm is NULL -- which can happen when vfio_ap_mdev_unset_kvm() has already run and cleared kvm before a subsequent cleanup path reaches this function -- the WARN_ON fires and the entire block is skipped. This leaves q->saved_isc set to a non-invalid value, creating a potential double-free on any subsequent call to this function. When kvm is NULL the KVM guest is already torn down, so kvm_s390_gisc_unregister() need not and cannot be called; however, q->saved_isc must always be cleared. Fix this by separating the kvm_s390_gisc_unregister() call from the q->saved_isc reset. The WARN_ON now guards only the genuinely impossible case of matrix_mdev being NULL. A NULL kvm is handled gracefully by skipping only the unregister call, and q->saved_isc = VFIO_AP_ISC_INVALID is set unconditionally whenever saved_isc was not already invalid. Additionally, add an else clause to the host-config check in vfio_ap_mdev_remove_queue() to call vfio_ap_free_aqic_resources() directly when the queue is not in the host's AP configuration. This serves as a backstop: when the AP bus fires the driver .remove callback after an adapter is removed from the host config, the queue is by definition no longer addressable, so vfio_ap_mdev_reset_queue() would always return Q_NOT_AVAIL. The else clause handles this case directly without the unnecessary ap_zapq() call, and ensures cleanup occurs even if kvm has already been set to NULL by a prior call to vfio_ap_mdev_unset_kvm(). Fixes: b9bd10c43456d ("s390/vfio-ap: do not reset queue removed from host config") Cc: stable@vger.kernel.org Signed-off-by: Anthony Krowiak Reviewed-by: Matthew Rosato Acked-by: Halil Pasic Signed-off-by: Claudio Imbrenda Message-ID: <20260818193349.1877940-2-akrowiak@linux.ibm.com> --- drivers/s390/crypto/vfio_ap_ops.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c index 940c0ff668be..4db878c18f41 100644 --- a/drivers/s390/crypto/vfio_ap_ops.c +++ b/drivers/s390/crypto/vfio_ap_ops.c @@ -277,9 +277,9 @@ static void vfio_ap_free_aqic_resources(struct vfio_ap_queue *q) { if (!q) return; - if (q->saved_isc != VFIO_AP_ISC_INVALID && - !WARN_ON(!(q->matrix_mdev && q->matrix_mdev->kvm))) { - kvm_s390_gisc_unregister(q->matrix_mdev->kvm, q->saved_isc); + if (q->saved_isc != VFIO_AP_ISC_INVALID) { + if (!WARN_ON(!q->matrix_mdev) && q->matrix_mdev->kvm) + kvm_s390_gisc_unregister(q->matrix_mdev->kvm, q->saved_isc); q->saved_isc = VFIO_AP_ISC_INVALID; } if (q->saved_iova && !WARN_ON(!q->matrix_mdev)) { @@ -1935,6 +1935,8 @@ static int apq_status_check(int apqn, struct ap_queue_status *status) * a value indicating a reset needs to be performed again. */ return -EAGAIN; + case AP_RESPONSE_Q_NOT_AVAIL: + return -ENODEV; default: WARN(true, "failed to verify reset of queue %02x.%04x: TAPQ rc=%u\n", @@ -1961,6 +1963,10 @@ static void apq_reset_check(struct work_struct *reset_work) ret = apq_status_check(q->apqn, &status); if (ret == -EIO) return; + if (ret == -ENODEV) { + vfio_ap_free_aqic_resources(q); + return; + } if (ret == -EBUSY) { pr_notice_ratelimited(WAIT_MSG, elapsed, AP_QID_CARD(q->apqn), @@ -2004,6 +2010,7 @@ static void vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q) break; case AP_RESPONSE_DECONFIGURED: case AP_RESPONSE_CHECKSTOPPED: + case AP_RESPONSE_Q_NOT_AVAIL: vfio_ap_free_aqic_resources(q); break; default: @@ -2528,12 +2535,15 @@ void vfio_ap_mdev_remove_queue(struct ap_device *apdev) /* * If the queue is not in the host's AP configuration, then resetting * it will fail with response code 01, (APQN not valid); so, let's make - * sure it is in the host's config. + * sure it is in the host's config. If it is not, free the KVM GISC + * resources. */ if (test_bit_inv(apid, (unsigned long *)matrix_dev->info.apm) && test_bit_inv(apqi, (unsigned long *)matrix_dev->info.aqm)) { vfio_ap_mdev_reset_queue(q); flush_work(&q->reset_work); + } else { + vfio_ap_free_aqic_resources(q); } done: From d12ce6bce5ec5175c3581e01c71e7a5abb286d9b Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Wed, 12 Aug 2026 17:55:19 +0200 Subject: [PATCH 0605/2366] s390/uv: Fix loop condition in uv_find_secrets Systems with more than 85 UV secrets got -ENOENT for any secret past the first page. Fix this by setting the start index at the beginning of the loop in uv_find_secret() and not at the end. First test if there are more secrets left by comparing start_idx with list->next_secret_idx, and then set the start index to the next secret index. Fixes: 7c9137af2042 ("s390/uv: Retrieve UV secrets support") Acked-by: Claudio Imbrenda Reviewed-by: Christoph Schlameuss Signed-off-by: Steffen Eiden Signed-off-by: Claudio Imbrenda Message-ID: <20260812-uv_secrets_fix-v3-1-a85bd29e0666@linux.ibm.com> --- arch/s390/kernel/uv.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/s390/kernel/uv.c b/arch/s390/kernel/uv.c index dc14ebc0105b..52003c463fad 100644 --- a/arch/s390/kernel/uv.c +++ b/arch/s390/kernel/uv.c @@ -846,11 +846,14 @@ int uv_find_secret(const u8 secret_id[UV_SECRET_ID_LEN], struct uv_secret_list *list, struct uv_secret_list_item_hdr *secret) { - u16 start_idx = 0; + u16 start_idx; u16 list_rc; int ret; + list->next_secret_idx = 0; + do { + start_idx = list->next_secret_idx; uv_list_secrets(list, start_idx, &list_rc, NULL); if (list_rc != UVC_RC_EXECUTED && list_rc != UVC_RC_MORE_DATA) { if (list_rc == UVC_RC_INV_CMD) @@ -861,7 +864,6 @@ int uv_find_secret(const u8 secret_id[UV_SECRET_ID_LEN], ret = find_secret_in_page(secret_id, list, secret); if (ret == 0) return ret; - start_idx = list->next_secret_idx; } while (list_rc == UVC_RC_MORE_DATA && start_idx < list->next_secret_idx); return -ENOENT; From f47190b08b71e8482072978373ee88cb2dfbdaf4 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Wed, 12 Aug 2026 17:55:20 +0200 Subject: [PATCH 0606/2366] s390/uv: Prevent potential out-of-bounds read When the system has more than 85 secrets, the uv_secret_list struct array only holds up to 85 items per page, resulting in an out of bounds read in find_secret_in_page if the targeted secret is in the next page or not stored at all. Fix this by looping over the number of stored secrets which is the per sub-list count of stored secrets and not the overall count. Fixes: 7c9137af2042 ("s390/uv: Retrieve UV secrets support") Signed-off-by: Steffen Eiden Reviewed-by: Christoph Schlameuss Signed-off-by: Claudio Imbrenda Message-ID: <20260812-uv_secrets_fix-v3-2-a85bd29e0666@linux.ibm.com> --- arch/s390/kernel/uv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/kernel/uv.c b/arch/s390/kernel/uv.c index 52003c463fad..8ea9dd7704ff 100644 --- a/arch/s390/kernel/uv.c +++ b/arch/s390/kernel/uv.c @@ -825,7 +825,7 @@ static int find_secret_in_page(const u8 secret_id[UV_SECRET_ID_LEN], { u16 i; - for (i = 0; i < list->total_num_secrets; i++) { + for (i = 0; i < list->num_secr_stored; i++) { if (memcmp(secret_id, list->secrets[i].id, UV_SECRET_ID_LEN) == 0) { *secret = list->secrets[i].hdr; return 0; From b94cec5761d22624d109d859467d7d4ce0a1b88b Mon Sep 17 00:00:00 2001 From: Andy Chiu Date: Tue, 1 Sep 2026 14:23:32 -0500 Subject: [PATCH 0607/2366] riscv: skip software algning code for HAVE_EFFICIENT_UNALIGNED_ACCESS We can jump straight into the copy loop if the kernel is compiled for a hardware that natively supports misaligned access. The user copy bandwidth improvement on K3 and Ascaolon is shown as below: Misaligned user copy, size: 512B (offset: [0:15] except 0, 8) BW Improvement | Write | Read | K3 | 6.19% | 3.43% | Ascalon | 10.0% | 11.4% | Aligned user copy, size: 512B (offset: 0, 8) BW Improvement | Write | Read | K3 | 1.69% | 0.90% | Ascalon | 1.25% | 3.32% | Suggested-by: Anton Blanchard Signed-off-by: Andy Chiu Link: https://patch.msgid.link/20260901192334.3543340-1-tchiu@tenstorrent.com Signed-off-by: Paul Walmsley --- arch/riscv/lib/uaccess.S | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/riscv/lib/uaccess.S b/arch/riscv/lib/uaccess.S index 4efea1b3326c..cf8586a937de 100644 --- a/arch/riscv/lib/uaccess.S +++ b/arch/riscv/lib/uaccess.S @@ -76,6 +76,7 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled) li a3, 9*SZREG-1 /* size must >= (word_copy stride + SZREG-1) */ bltu a2, a3, .Lbyte_copy_tail +#if !defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) /* * Copy first bytes until dst is aligned to word boundary. * a0 - start of dst @@ -103,7 +104,7 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled) /* a1 - start of src */ andi a3, a1, SZREG-1 bnez a3, .Lshift_copy - +#endif .Lword_copy: /* * Both src and dst are aligned, unrolled word copy @@ -137,6 +138,7 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled) addi t0, t0, 8*SZREG /* revert to original value */ j .Lbyte_copy_tail +#if !defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) .Lshift_copy: /* @@ -189,6 +191,7 @@ SYM_FUNC_START(fallback_scalar_usercopy_sum_enabled) /* Revert src to original unaligned value */ add a1, a1, a3 +#endif .Lbyte_copy_tail: /* From 8ba27b90095a4c7fcc878dd013969cd6b100cea7 Mon Sep 17 00:00:00 2001 From: Oleksandr Kovalov Date: Wed, 2 Sep 2026 20:13:05 +0300 Subject: [PATCH 0608/2366] ALSA: hda/realtek: Fix cold-boot headset misdetection on Acer Aspire A515-57G On the Acer Aspire A515-57G (PCI SSID 1025:1616), if headphones are already inserted into the combo jack before the codec powers up (a cold boot with the plug already seated), the impedance-based headset-type sensing races and misclassifies the jack. This drives the wrong output configuration and is audible as missing center-panned content (e.g. vocals) while panned content plays normally. A genuine physical unplug/replug after boot reliably fixes this by forcing a fresh sense transient, which is a strong hint about the underlying cause: the sensing hardware appears to need a settled, freshly-triggered read rather than the one-shot classification done during the normal HDA_FIXUP_ACT_INIT pass. Add a machine-specific fixup that, on cold boot only (not S3/S4 resume, which already gets its own re-check), waits briefly after the normal init-time decision and then forces a fresh headset-mode classification by resetting the cached mode and re-invoking the existing alc_fixup_headset_mode() path -- mirroring what a manual replug already does. The wait+recheck is skipped whenever the first pass already determined nothing is plugged in, to avoid adding boot latency on the common case. Chain into the existing ALC256_FIXUP_ACER_SFG16_MICMUTE_LED fixup so this quirk-table entry keeps providing mic-mute LED support alongside the cold-boot headset fix. Tested on kernel 7.1.9 by building the affected module standalone and confirming cold boot with headphones pre-inserted plays correctly from the very first sample, across multiple boots (including a full restart, and headphones inserted mid-POST rather than before power-on), with no crashes or warnings and no behavioral difference from a real post-replug recovery. Signed-off-by: Oleksandr Kovalov Link: https://patch.msgid.link/20260902171305.3955-1-oleksandr.kovalov.work@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 36 ++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index ecc9c6e6ab98..95b40a177d2b 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -2379,6 +2379,33 @@ static void alc_fixup_headset_mode_alc255_no_hp_mic(struct hda_codec *codec, } } +/* + * On the Acer Aspire A515-57G (and possibly other models sharing this + * board), if headphones are already inserted into the combo jack before + * the codec powers up (cold boot), the impedance-based headset-type + * sensing races and misclassifies the jack, driving the wrong output + * configuration (audible as missing center-panned/vocal content). A + * genuine physical unplug/replug after boot fixes it by forcing a fresh + * sense transient. Mirror that here on cold boot only: give the sense + * hardware time to settle, then force a fresh classification. + */ +static void alc_fixup_headset_mode_acer_coldboot(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + alc_fixup_headset_mode(codec, fix, action); + + if (action == HDA_FIXUP_ACT_INIT && + !is_s3_resume(codec) && !is_s4_resume(codec) && + spec->current_headset_mode != ALC_HEADSET_MODE_UNPLUGGED) { + msleep(500); + spec->current_headset_mode = ALC_HEADSET_MODE_UNKNOWN; + spec->current_headset_type = ALC_HEADSET_TYPE_UNKNOWN; + alc_fixup_headset_mode(codec, fix, action); + } +} + static void alc288_update_headset_jack_cb(struct hda_codec *codec, struct hda_jack_callback *jack) { @@ -4248,6 +4275,7 @@ enum { ALC282_FIXUP_ACER_DISABLE_LINEOUT, ALC255_FIXUP_ACER_LIMIT_INT_MIC_BOOST, ALC256_FIXUP_ACER_HEADSET_MIC, + ALC256_FIXUP_ACER_COLDBOOT, ALC285_FIXUP_IDEAPAD_S740_COEF, ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST, ALC295_FIXUP_ASUS_DACS, @@ -6311,6 +6339,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC }, + [ALC256_FIXUP_ACER_COLDBOOT] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode_acer_coldboot, + .chained = true, + .chain_id = ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, + }, [ALC285_FIXUP_IDEAPAD_S740_COEF] = { .type = HDA_FIXUP_FUNC, .v.func = alc285_fixup_ideapad_s740_coef, @@ -7148,7 +7182,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x159e, "Acer Nitro 5 AN515-46", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), - SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_COLDBOOT), SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), From e3e4f66cc4b72333d0886ae2673c360248987889 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Mon, 31 Aug 2026 18:36:09 -0700 Subject: [PATCH 0609/2366] bpf: backtracking shouldn't clear outer frame R1-R5 for callbacks When processing calls to bpf_loop() verifier marks R1 (and R4) as precise. R1 tracks loop iterations number and because of the 'callback_depth < R1' mechanics in check_helper_call() must be marked precise. However, precision propagation for R1 was broken, when bpf_loop() call was verified on a second iteration. Consider the following verification trace: - main: bpf_loop(nr_loops, callback ...) - callback: BPF_EXIT - main: bpf_loop(nr_loops, callback ...) - ... While the first visit of the call to bpf_loop() propagated R1 precision as expected, the second call to mark_chain_precision() in the check_helper_call() set R1, but it was immediately reset when backtrack_insn() processed preceding BPF_EXIT in the loop deleted in this patch. Because of that, the second visit of the call to bpf_loop() injected checkpoint with R1 not marked as precise. Which could trick the verifier into accepting unsafe programs. See the next patch for an example of such program. Commit is structured in a way to minimize conflicts when 'bpf' would be eventually merged with 'bpf-next'. Fixes: ab5cfac139ab ("bpf: verify callbacks as if they are called unknown number of times") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260831-bug-015-backtrack-cb-args-precise-v1-1-68a8e2a821e0@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/backtrack.c | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/kernel/bpf/backtrack.c b/kernel/bpf/backtrack.c index eaf7438b9ebf..47282ffeeaf9 100644 --- a/kernel/bpf/backtrack.c +++ b/kernel/bpf/backtrack.c @@ -520,24 +520,7 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx, return -EFAULT; } } else if (opcode == BPF_EXIT) { - bool r0_precise; - - /* Backtracking to a nested function call, 'idx' is a part of - * the inner frame 'subseq_idx' is a part of the outer frame. - * In case of a regular function call, instructions giving - * precision to registers R1-R5 should have been found already. - * In case of a callback, it is ok to have R1-R5 marked for - * backtracking, as these registers are set by the function - * invoking callback. - */ - if (subseq_idx >= 0 && bpf_calls_callback(env, subseq_idx)) - for (i = BPF_REG_1; i <= BPF_REG_5; i++) - bt_clear_reg(bt, i); - if (bt_reg_mask(bt) & BPF_REGMASK_ARGS) { - verifier_bug(env, "backtracking exit unexpected regs %x", - bt_reg_mask(bt)); - return -EFAULT; - } + bool from_subprog_call, r0_precise; /* BPF_EXIT in subprog or callback always returns * right after the call instruction, so by checking @@ -547,9 +530,23 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx, * case, we need to propagate r0 precision, if * necessary. In the former we never do that. */ - r0_precise = subseq_idx - 1 >= 0 && - bpf_pseudo_call(&env->prog->insnsi[subseq_idx - 1]) && - bt_is_reg_set(bt, BPF_REG_0); + from_subprog_call = subseq_idx - 1 >= 0 && + bpf_pseudo_call(&env->prog->insnsi[subseq_idx - 1]); + + r0_precise = from_subprog_call && bt_is_reg_set(bt, BPF_REG_0); + + /* Backtracking to a nested function call, 'idx' is a part of + * the inner frame 'subseq_idx' is a part of the outer frame. + * In case of a regular function call, instructions giving + * precision to registers R1-R5 should have been found already. + * In case of a callback from bpf_loop(), R{1,4} in the calling + * frame would be set as precise and that is correct. + */ + if (from_subprog_call && (bt_reg_mask(bt) & BPF_REGMASK_ARGS)) { + verifier_bug(env, "backtracking exit unexpected regs %x", + bt_reg_mask(bt)); + return -EFAULT; + } bt_clear_reg(bt, BPF_REG_0); if (bt_subprog_enter(bt)) From 7ac9662189069914a088ec61ad85dc46b5cb1563 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Mon, 31 Aug 2026 18:36:10 -0700 Subject: [PATCH 0610/2366] selftests/bpf: test case for unsafe pruning of bpf_loop checkpoints The following BPF program was erroneously accepted by the verifier: static int cb(int i, __u64 *ctx) { /* unsafe on a second iteration */ small_arr[*ctx] = i; *ctx = 100500; return 0; } int main(void *ctx) { int nr_loops = 1; u64 ctx = 0; if (unlikely(bpf_get_prandom_u32() == 42)) nr_loops = 2; bpf_loop(nr_loops, cb, &ctx, 0); return 0; } The branch with nr_loops == 1 was explored first and injected a checkpoint at the entry to 'cb', such that nr_loops in the main's frame was not marked as precise. This checkpoint pruned the state with nr_loops == 2 and the program was accepted. This test case corresponds to the program above. Entry point is written in assembly to ensure branch processing order. Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260831-bug-015-backtrack-cb-args-precise-v1-2-68a8e2a821e0@gmail.com Signed-off-by: Alexei Starovoitov --- tools/testing/selftests/bpf/progs/iters.c | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/iters.c b/tools/testing/selftests/bpf/progs/iters.c index 62d7df9e80be..c6699159dacd 100644 --- a/tools/testing/selftests/bpf/progs/iters.c +++ b/tools/testing/selftests/bpf/progs/iters.c @@ -2149,4 +2149,43 @@ __naked int stack_misc_vs_scalar_in_a_loop(void) ); } +__used +static int loop_cb5(int i, __u64 *ctx) +{ + /* unsafe on a second iteration */ + small_arr[*ctx] = i; + *ctx = 100500; + return 0; +} + +SEC("raw_tp") +__flag(BPF_F_TEST_STATE_FREQ) +__failure __msg("memory access is {{.*}} and is outside of the object of size 64") +__naked void loop_counter_precision_2nd_iter(void) +{ + asm volatile ( + "call %[bpf_get_prandom_u32];" + "*(u64 *)(r10 - 8) = 0;" + "r1 = 2;" + "if r0 == 42 goto +1;" + "r1 = 1;" + "r2 = loop_cb5 ll;" + "r3 = r10;" + "r3 += -8;" + "r4 = 0;" + /* + * Explore with nr_loops=1 on a first path and nr_loops=2 on a second path. + * Buggy verifier did not propagate r1 precision properly, + * and thus checkpoints created for nr_loops=1 case matched nr_loops=2 case. + */ + "call %[bpf_loop];" + "r0 = 0;" + "exit;" + : + : __imm(bpf_loop), + __imm(bpf_get_prandom_u32) + : __clobber_all + ); +} + char _license[] SEC("license") = "GPL"; From e6142a8bfc230c7263eb8b0475249c958ce49367 Mon Sep 17 00:00:00 2001 From: Diego Oliva Date: Wed, 2 Sep 2026 11:42:06 +0100 Subject: [PATCH 0611/2366] smb: client: reject short READ responses in CIFSSMBRead() CIFSSMBRead() reads DataLengthHigh, DataLength and DataOffset out of the READ_RSP returned by the server without first checking that a whole READ_RSP was actually received. The length of the response is recorded in rsp_iov.iov_len, but nothing constrains it to be at least read_rsp_size before those fields are dereferenced. A malicious or compromised SMB1 server can return a response shorter than the READ_RSP header, so that parsing the header itself reads past the end of the receive buffer. SMB1 is not negotiated by default; reaching this code requires an explicit vers=1.0 mount. Reject the response unless it is at least read_rsp_size bytes long. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Suggested-by: Paulo Alcantara Cc: stable@vger.kernel.org # 6.19.x Assisted-by: Bynario AI Signed-off-by: Diego Oliva Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index f8aa9e7b4bc6..be13ab37039d 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -1719,6 +1719,14 @@ CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms, pSMBr = (READ_RSP *)rsp_iov.iov_base; if (rc) { cifs_dbg(VFS, "Send error in read = %d\n", rc); + } else if (rsp_iov.iov_len < tcon->ses->server->vals->read_rsp_size) { + /* check that the received response can hold a whole READ_RSP */ + cifs_dbg(FYI, "%s: server returned short header. got=%zu expected=%zu\n", + __func__, rsp_iov.iov_len, + tcon->ses->server->vals->read_rsp_size); + rc = smb_EIO2(smb_eio_trace_read_rsp_short, + rsp_iov.iov_len, tcon->ses->server->vals->read_rsp_size); + *nbytes = 0; } else { int data_length = le16_to_cpu(pSMBr->DataLengthHigh); data_length = data_length << 16; From c3080b58d81d3699cbf4dfd5ba860630fca96f4a Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Wed, 26 Aug 2026 12:37:05 +0200 Subject: [PATCH 0612/2366] drm/atomic-state-helper: set pixel_blend_mode to prop default on reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In __drm_atomic_helper_plane_state_init(), pixel_blend_mode is always reset to DRM_MODE_BLEND_PREMULTI. That was consistent while drm_plane_create_blend_mode_property() required PREMULTI in the supported modes, but it now falls back to COVERAGE or PIXEL_NONE when the driver doesn't support PREMULTI. The hardcoded default may therefore not be a blend mode the hardware can do, nor one the property advertises. Initialize pixel_blend_mode from the blend mode property default instead, keeping DRM_MODE_BLEND_PREMULTI for planes without the property. Fixes: 9813e158d13d ("drm/drm_blend: allow blend mode property without PREMULTI") Tested-by: Mikhail Gavrilov Tested-by: Dan Wheeler Reviewed-by: Timur Kristóf Reviewed-by: Alex Hung Reviewed-by: Leandro Ribeiro Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260826104143.39077-2-mwen@igalia.com --- drivers/gpu/drm/drm_atomic_state_helper.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index d90d1d7c9cf9..a2ef272e9f27 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -278,7 +278,14 @@ void __drm_atomic_helper_plane_state_init(struct drm_plane_state *plane_state, plane_state->rotation = DRM_MODE_ROTATE_0; plane_state->alpha = DRM_BLEND_ALPHA_OPAQUE; + plane_state->pixel_blend_mode = DRM_MODE_BLEND_PREMULTI; + if (plane->blend_mode_property) { + if (!drm_object_property_get_default_value(&plane->base, + plane->blend_mode_property, + &val)) + plane_state->pixel_blend_mode = val; + } if (plane->color_encoding_property) { if (!drm_object_property_get_default_value(&plane->base, From f0c75da0a6b4084e00cd6faefebcc976ffaccf52 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Wed, 26 Aug 2026 12:37:06 +0200 Subject: [PATCH 0613/2366] drm/amd/display: fix missing blend-mode-prop warning for DCN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit validate_blend_mode_for_alpha_formats() warns when a plane supports formats with alpha but doesn't expose the blend mode property. Fix this by adding the same overlay plane blend modes to primary plane, since they are all universal planes in DCN-generation. Cursor planes support ARGB8888 format and CURSOR_MODE_COLOR_PRE_MULTIPLIED_ALPHA is set by default (other color formats are not implemented), so only expose support to PREMULTI, which is the default blend mode on DRM. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Tested-by: Mikhail Gavrilov Tested-by: Dan Wheeler Reviewed-by: Timur Kristóf Reviewed-by: Alex Hung Reviewed-by: Leandro Ribeiro Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260826104143.39077-3-mwen@igalia.com --- .../amd/display/amdgpu_dm/amdgpu_dm_plane.c | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index 824ef3ce5de0..423e3cd7b9c9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -2208,16 +2208,31 @@ int amdgpu_dm_plane_init(struct amdgpu_display_manager *dm, if (res) return res; - if (plane->type == DRM_PLANE_TYPE_OVERLAY && - plane_cap && plane_cap->per_pixel_alpha) { + /* TODO: Check which blend modes are supported in DCE-generation + * planes, i.e. DC_PLANE_TYPE_DCE_RGB/UNDERLAY and expose blend mode + * property accordingly. + */ + if ((plane->type == DRM_PLANE_TYPE_OVERLAY || + plane->type == DRM_PLANE_TYPE_PRIMARY) && + plane_cap && plane_cap->per_pixel_alpha && + plane_cap->type == DC_PLANE_TYPE_DCN_UNIVERSAL) { unsigned int blend_caps = BIT(DRM_MODE_BLEND_PIXEL_NONE) | BIT(DRM_MODE_BLEND_PREMULTI) | BIT(DRM_MODE_BLEND_COVERAGE); - drm_plane_create_alpha_property(plane); drm_plane_create_blend_mode_property(plane, blend_caps); + + if (plane->type == DRM_PLANE_TYPE_OVERLAY) + drm_plane_create_alpha_property(plane); } + /* Cursor color format is set to CURSOR_MODE_COLOR_PRE_MULTIPLIED_ALPHA + * by default, so only advertise DRM_MODE_BLEND_PREMULTI blend mode for + * this type of plane. + */ + if (plane->type == DRM_PLANE_TYPE_CURSOR) + drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_PREMULTI)); + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { /* * Allow OVERLAY planes to be used as underlays by assigning an From 332ad707e38fb82dd998b4d0782c15579e5784f1 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Wed, 26 Aug 2026 12:37:07 +0200 Subject: [PATCH 0614/2366] drm/amd/display: advertise PIXEL_NONE and PREMULTI blend mode for DCE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DCE can support PREMULTI and COVERAGE blend mode depending on its generation, however current driver implementation either doesn't expose more than primary and cursor plane, or doesn't program registers for any blend mode other than PIXEL_NONE. To fix the missing-blend-mode-prop warning according to current DCE plane caps, create blend mode property with PIXEL_NONE and PREMULTI for primary planes. As long as the background is black and there is no overlay plane, PIXEL_NONE and PREMULTI are equivalent, and PREMULTI has been the mandatory/default mode for years, so keep it to avoid regressions. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Tested-by: Viktor Jägersküpper Tested-by: Dan Wheeler #v3 Reviewed-by: Timur Kristóf Reviewed-by: Alex Hung Reviewed-by: Leandro Ribeiro #v2 Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260826104143.39077-4-mwen@igalia.com --- .../amd/display/amdgpu_dm/amdgpu_dm_plane.c | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c index 423e3cd7b9c9..e13b96358208 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c @@ -2208,14 +2208,24 @@ int amdgpu_dm_plane_init(struct amdgpu_display_manager *dm, if (res) return res; - /* TODO: Check which blend modes are supported in DCE-generation - * planes, i.e. DC_PLANE_TYPE_DCE_RGB/UNDERLAY and expose blend mode - * property accordingly. + /* Blend mode support varies on DCE generations according to HW caps + * and number of planes per CRTC. However, as current driver + * implementation only creates one primary and one cursor plane per + * CRTC for DCE (overlay is only created if + * DC_PLANE_TYPE_DCN_UNIVERSAL), the primary plane blend mode is + * ignored across DCE versions. Keep PREMULTI to avoid uAPI + * regressions: it was the default/mandatory mode for many years and, + * with no overlay plane, primary composes on top of a black + * background, where PREMULTI and PIXEL_NONE are equivalent. */ - if ((plane->type == DRM_PLANE_TYPE_OVERLAY || - plane->type == DRM_PLANE_TYPE_PRIMARY) && - plane_cap && plane_cap->per_pixel_alpha && - plane_cap->type == DC_PLANE_TYPE_DCN_UNIVERSAL) { + if (plane_cap && plane_cap->type != DC_PLANE_TYPE_DCN_UNIVERSAL) { + unsigned int blend_caps = BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI); + + drm_plane_create_blend_mode_property(plane, blend_caps); + } else if ((plane->type == DRM_PLANE_TYPE_OVERLAY || + plane->type == DRM_PLANE_TYPE_PRIMARY) && + plane_cap && plane_cap->per_pixel_alpha) { unsigned int blend_caps = BIT(DRM_MODE_BLEND_PIXEL_NONE) | BIT(DRM_MODE_BLEND_PREMULTI) | BIT(DRM_MODE_BLEND_COVERAGE); From f2951ebd15c36a1ea4820a7f0cbb0b5f1c028b73 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 2 Sep 2026 12:19:18 -0400 Subject: [PATCH 0615/2366] tracing: Take trace_array reference when opening options file The options files do not take the trace_array reference for the options they represent. This could cause a use-after-free kernel crash if one of these files is opened by one task and another task removes the instance that the option is for. Because it doesn't take a reference upon opening, it will not stop the removal which will free the options descriptor that is being used. As the options are somewhat dynamic in their creation at boot up, each file represents a flag in the trace_array. The trace_array has an array of indexes to represent each of these flags that is stored in the trace_flags_index array. The address of the index array element is used to pass to the inode->i_private pointer. Then that element is read which holds the index (which represents the flag) and then the index is used to calculate the trace_array descriptor from its trace_flags_index array. One issue is that the index element can not be referenced until the trace_array's reference is taken. To handle this, create a new helper function called: trace_array_options_get() that will iterate all the existing trace_arrays in the ftrace_trace_arrays list (under the trace_types_lock), and compare the passed in address of the index element with the entire array of the trace_array's trace_flags_index array. If it matches, then up the corresponding trace_array's reference and return. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902121918.5a9e9d1b@gandalf.local.home Fixes: 577b785f55168 ("tracing: add tracer dependent options to options directory") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-trace-kernel/20260828135858.2AC501F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 67 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index a946e0183fd1..722d0ba2d233 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -7842,11 +7842,70 @@ trace_options_core_write(struct file *filp, const char __user *ubuf, size_t cnt, return cnt; } +/* + * The tr_index is the address of a trace_array->trace_flags_index[] + * element that holds the index of the trace flag. But since the + * trace_array reference has not been taken yet, it cannot be referenced + * as it could have been freed by a rmdir of the instance the trace_array + * represents. + * + * Search the list of trace_arrays and compare the tr_index to the + * address of the entire trace_array trace_flags_index array for each + * trace_array in the list. If one is matched, then take the reference + * and return it. If not, the trace_array no longer exits. + */ +static int trace_array_options_get(void *tr_index) +{ + struct trace_array *tr; + int ret; + + ret = security_locked_down(LOCKDOWN_TRACEFS); + if (ret) + return ret; + + if (tracing_disabled) + return -ENODEV; + + guard(mutex)(&trace_types_lock); + list_for_each_entry(tr, &ftrace_trace_arrays, list) { + if (tr_index >= (void *)&tr->trace_flags_index[0] && + tr_index < (void *)&tr->trace_flags_index[TRACE_FLAGS_MAX_SIZE]) + return __trace_array_get(tr); + } + return -ENODEV; +} + +static int trace_options_open(struct inode *inode, struct file *filp) +{ + void *tr_index = inode->i_private; + + if (trace_array_options_get(tr_index) < 0) + return -ENODEV; + + filp->private_data = tr_index; + + return 0; +} + +static int trace_options_release(struct inode *inode, struct file *filp) +{ + void *tr_index = filp->private_data; + struct trace_array *tr; + unsigned int index; + + get_tr_index(tr_index, &tr, &index); + + trace_array_put(tr); + + return 0; +} + static const struct file_operations trace_options_core_fops = { - .open = tracing_open_generic, - .read = trace_options_core_read, - .write = trace_options_core_write, - .llseek = generic_file_llseek, + .open = trace_options_open, + .read = trace_options_core_read, + .write = trace_options_core_write, + .llseek = generic_file_llseek, + .release = trace_options_release, }; struct dentry *trace_create_file(const char *name, From 9e6372ec2a3990662ae0a67f56ac0aee19848d5b Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Tue, 1 Sep 2026 23:35:03 -0700 Subject: [PATCH 0616/2366] drm/pagemap: dma-unmap pages before handling migration errors drm_pagemap_migrate_unmap_pages() relies on the pages array to determine which pages require DMA unmapping. However, drm_pagemap_migration_unlock_put_pages() clears the array as part of its cleanup, leaving drm_pagemap_migrate_unmap_pages() with no valid page information if it is called afterward. Call drm_pagemap_migrate_unmap_pages() before drm_pagemap_migration_unlock_put_pages() so the pages array remains valid during DMA unmapping. Reported-by: Sashiko Fixes: f86ad0ed620c ("drm/gpusvm, drm/pagemap: Move migration functionality to drm_pagemap") Cc: stable@vger.kernel.org Signed-off-by: Matthew Brost Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260902063504.3024362-1-matthew.brost@intel.com --- drivers/gpu/drm/drm_pagemap.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index 097a900cf55d..6aa745682270 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -1283,13 +1283,13 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) goto err_finalize; err_finalize: + drm_pagemap_migrate_unmap_pages(devmem_allocation->dev, pagemap_addr, dst, npages, + DMA_FROM_DEVICE, &state); if (err) drm_pagemap_migration_unlock_put_pages(npages, dst); migrate_device_pages(src, dst, npages); drm_pagemap_retire_migrated_pages(src, npages); migrate_device_finalize(src, dst, npages); - drm_pagemap_migrate_unmap_pages(devmem_allocation->dev, pagemap_addr, dst, npages, - DMA_FROM_DEVICE, &state); err_free: kvfree(buf); @@ -1416,15 +1416,15 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas, goto err_finalize; err_finalize: + if (dev) + drm_pagemap_migrate_unmap_pages(dev, pagemap_addr, migrate.dst, + npages, DMA_FROM_DEVICE, + &state); if (err) drm_pagemap_migration_unlock_put_pages(npages, migrate.dst); migrate_vma_pages(&migrate); drm_pagemap_retire_migrated_pages(migrate.src, npages); migrate_vma_finalize(&migrate); - if (dev) - drm_pagemap_migrate_unmap_pages(dev, pagemap_addr, migrate.dst, - npages, DMA_FROM_DEVICE, - &state); err_free: kvfree(buf); err_out: From df72e55e754c8d449321ddddad19a8bd3cb8d032 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Tue, 1 Sep 2026 23:35:04 -0700 Subject: [PATCH 0617/2366] drm/pagemap: Fix folio allocation fallback and use-after-put drm_pagemap_migrate_populate_ram_pfn() had two issues when populating RAM PFNs with higher-order folios: 1. The higher-order vma_alloc_folio()/folio_alloc() calls did not pass __GFP_NOWARN, so a THP allocation failure under memory pressure would spam the kernel log, and there was no fallback path despite a TODO comment stating one was needed. Add __GFP_NOWARN to the higher-order allocation and, on failure, fall back to order-0 allocations for the entire range originally covered by the failed higher-order allocation, leaving MIGRATE_PFN_COMPOUND unset for those PFNs. 2. In the free_pages error path, order was computed via folio_order(page_folio(page)) *after* put_page(page) had already dropped the reference, resulting in a use-after-free/put when that was the last reference on the page. Compute order before releasing the page. Introducing the fallback in 1. also requires the source page array handed to ->copy_to_ram() to be built differently. Both callers only populated the entry at the head of each source folio, relying on the copy callback to derive the rest of the folio from the order recorded in the matching drm_pagemap_addr. Once the destination has been demoted to order-0 folios the drm_pagemap_addr entries are per-page, so a source page is needed for every one of them; leaving them NULL makes the copy callback stop after the first page and the remainder of the range is never copied. The source folio is only split later, by migrate_vma_pages() / migrate_device_pages(), so its order cannot be used to detect the demotion - test the destination for MIGRATE_PFN_COMPOUND instead. Factor the array population out into drm_pagemap_migrate_populate_src_pages() and use it from both drm_pagemap_evict_to_ram() and __drm_pagemap_migrate_to_ram(). Fixes: ddeda6136038 ("drm/pagemap: Allocate folios when possible") Cc: stable@vger.kernel.org Assisted-by: GitHub_Copilot:claude-opus-5 Signed-off-by: Matthew Brost Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260902063504.3024362-2-matthew.brost@intel.com --- drivers/gpu/drm/drm_pagemap.c | 128 +++++++++++++++++++++++++++------- 1 file changed, 103 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index 6aa745682270..a0546955d0b9 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -383,6 +383,58 @@ drm_pagemap_migrate_map_system_pages(struct device *dev, return 0; } +/** + * drm_pagemap_migrate_populate_src_pages() - Populate the source page array + * @pages: Array of source pages to populate + * @src_mpfn: Source array of migrate PFNs + * @dst_mpfn: Destination array of migrate PFNs + * @npages: Number of pages in the arrays + * + * Populate @pages with the device pages the copy callback is to read from. + * + * Entries are normally only populated at the head of each source folio, with + * the copy callback deriving the rest of the folio from the order recorded in + * the corresponding drm_pagemap_addr. That does not work where + * drm_pagemap_migrate_populate_ram_pfn() had to demote a higher-order source + * folio to order-0 destination folios: the drm_pagemap_addr entries are then + * per-page, and the copy callback needs a source page for each of them. + * Populate every entry for those ranges. + * + * Note that the source folio itself is only split later, by + * migrate_vma_pages() / migrate_device_pages(), so its order cannot be used to + * detect the demotion - the destination has to be inspected instead. + */ +static void drm_pagemap_migrate_populate_src_pages(struct page **pages, + unsigned long *src_mpfn, + unsigned long *dst_mpfn, + unsigned long npages) +{ + unsigned long i; + + for (i = 0; i < npages;) { + struct page *page = migrate_pfn_to_page(src_mpfn[i]); + unsigned int order = 0; + unsigned long j, nr; + + if (!page) { + i++; + continue; + } + + order = folio_order(page_folio(page)); + nr = NR_PAGES(order); + + if (order && !(dst_mpfn[i] & MIGRATE_PFN_COMPOUND)) { + for (j = 0; j < nr && i + j < npages; j++) + pages[i + j] = folio_page(page_folio(page), j); + } else { + pages[i] = page; + } + + i += nr; + } +} + /** * drm_pagemap_migrate_unmap_pages() - Unmap pages previously mapped for GPU SVM migration * @dev: The device for which the pages were mapped @@ -875,6 +927,7 @@ static int drm_pagemap_migrate_populate_ram_pfn(struct vm_area_struct *vas, struct page *page = NULL, *src_page; struct folio *folio; unsigned int order = 0; + gfp_t gfp = GFP_HIGHUSER; if (!(src_mpfn[i] & MIGRATE_PFN_MIGRATE)) goto next; @@ -891,11 +944,51 @@ static int drm_pagemap_migrate_populate_ram_pfn(struct vm_area_struct *vas, order = folio_order(page_folio(src_page)); - /* TODO: Support fallback to single pages if THP allocation fails */ + /* + * A large source folio is always collected whole, at its head + * page, PMD aligned and flagged MIGRATE_PFN_COMPOUND: anything + * else is split before it reaches us, either by + * migrate_vma_collect_pmd() or, for the eviction path, by + * migrate_device_pfns(). Both the order-0 fallback below and + * drm_pagemap_migrate_populate_src_pages() rely on that, as + * they index the folio from @i. + */ + WARN_ON_ONCE(order && + (src_page != folio_page(page_folio(src_page), 0) || + !(src_mpfn[i] & MIGRATE_PFN_COMPOUND))); + + if (order) + gfp |= __GFP_NOWARN; + if (vas) - folio = vma_alloc_folio(GFP_HIGHUSER, order, vas, addr); + folio = vma_alloc_folio(gfp, order, vas, addr); else - folio = folio_alloc(GFP_HIGHUSER, order); + folio = folio_alloc(gfp, order); + + if (!folio && order) { + /* + * Higher-order allocation failed, fall back to + * order-0 allocations for the entire range covered + * by the original higher-order allocation, without + * setting MIGRATE_PFN_COMPOUND, until we move past + * that range. + */ + unsigned long nr = NR_PAGES(order); + unsigned long j; + + gfp &= ~__GFP_NOWARN; + for (j = 0; j < nr && i < npages; j++, i++, addr += PAGE_SIZE) { + folio = vas ? + vma_alloc_folio(gfp, 0, vas, addr) : + folio_alloc(gfp, 0); + if (!folio) + goto free_pages; + + page = folio_page(folio, 0); + mpfn[i] = migrate_pfn(page_to_pfn(page)); + } + continue; + } if (!folio) goto free_pages; @@ -940,11 +1033,11 @@ static int drm_pagemap_migrate_populate_ram_pfn(struct vm_area_struct *vas, if (!page) goto next_put; + order = folio_order(page_folio(page)); + put_page(page); mpfn[i] = 0; - order = folio_order(page_folio(page)); - next_put: i += NR_PAGES(order); } @@ -1225,7 +1318,7 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) unsigned long *src, *dst; struct drm_pagemap_addr *pagemap_addr; void *buf; - int i, err = 0; + int err = 0; unsigned int retry_count = 2; npages = devmem_allocation->size >> PAGE_SHIFT; @@ -1268,15 +1361,7 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation) if (err) goto err_finalize; - for (i = 0; i < npages;) { - unsigned int order = 0; - - pages[i] = migrate_pfn_to_page(src[i]); - if (pages[i]) - order = folio_order(page_folio(pages[i])); - - i += NR_PAGES(order); - } + drm_pagemap_migrate_populate_src_pages(pages, src, dst, npages); err = ops->copy_to_ram(pages, pagemap_addr, npages, NULL); if (err) @@ -1344,7 +1429,7 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas, struct drm_pagemap_addr *pagemap_addr; unsigned long start, end; void *buf; - int i, err = 0; + int err = 0; zdd = drm_pagemap_page_zone_device_data(page); if (time_before64(get_jiffies_64(), zdd->devmem_allocation->timeslice_expiration)) @@ -1401,15 +1486,8 @@ static int __drm_pagemap_migrate_to_ram(struct vm_area_struct *vas, if (err) goto err_finalize; - for (i = 0; i < npages;) { - unsigned int order = 0; - - pages[i] = migrate_pfn_to_page(migrate.src[i]); - if (pages[i]) - order = folio_order(page_folio(pages[i])); - - i += NR_PAGES(order); - } + drm_pagemap_migrate_populate_src_pages(pages, migrate.src, migrate.dst, + npages); err = ops->copy_to_ram(pages, pagemap_addr, npages, NULL); if (err) From 33ce0aa4c57611c3a3485ec7c01ad67b3751447d Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Fri, 14 Aug 2026 12:24:05 -0400 Subject: [PATCH 0618/2366] btrfs: scrub: report the failing sector's address, not the stripe base scrub_stripe_report_errors() iterates over the sectors of a stripe, but every message it emits passes stripe->logical, the address of the first sector of the 64KiB stripe, rather than the address of the sector being reported. The physical address is likewise computed once, before the loop, from stripe->logical. This matters because scrub_print_common_warning() uses that logical address for the backref walk which produces the "root %llu inode %llu offset %llu ... (path: ...)" part of the message. As the address is always the stripe base, the reported root/inode/offset/path can identify a different file from the one whose sector actually failed. A 64KiB stripe routinely spans several extents belonging to unrelated files. On the machine where this was found, the stripe at logical 0x17D9380000 holds four sectors of /usr/share/plasma/emoji/bg.dict, then a file inside a docker volume, then sectors referenced only by snapshots. Every error anywhere in that stripe is attributed to bg.dict. The effect is visible statistically: across ten months and four kernel series that machine logged 81 distinct flagged logical addresses, and every one of them is exactly 64KiB aligned. Since BTRFS_STRIPE_LEN is 64KiB and stripe->logical is stripe aligned by construction, real failures distributed across sectors could not produce that. Report the address of the sector actually being examined. Adding the sector offset to the physical address is valid because BTRFS_STRIPE_LEN is the unit contiguous on a single device for every profile, so a stripe never crosses a device boundary. Fixes: 0096580713ff ("btrfs: scrub: introduce error reporting functionality for scrub_stripe") Reviewed-by: Qu Wenruo Signed-off-by: James C. Owens Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/scrub.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c index f209e75f0ff5..c09d4213ad89 100644 --- a/fs/btrfs/scrub.c +++ b/fs/btrfs/scrub.c @@ -1023,6 +1023,10 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, skip: for_each_set_bit(sector_nr, &extent_bitmap, stripe->nr_sectors) { + const u64 sector_logical = stripe->logical + + ((u64)sector_nr << fs_info->sectorsize_bits); + const u64 sector_physical = physical + + ((u64)sector_nr << fs_info->sectorsize_bits); bool repaired = false; if (scrub_bitmap_test_bit_is_metadata(stripe, sector_nr)) { @@ -1051,12 +1055,12 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, if (dev) { btrfs_err_rl(fs_info, "scrub: fixed up error at logical %llu on dev %s physical %llu", - stripe->logical, btrfs_dev_name(dev), - physical); + sector_logical, btrfs_dev_name(dev), + sector_physical); } else { btrfs_err_rl(fs_info, "scrub: fixed up error at logical %llu on mirror %u", - stripe->logical, stripe->mirror_num); + sector_logical, stripe->mirror_num); } continue; } @@ -1065,30 +1069,30 @@ static void scrub_stripe_report_errors(struct scrub_ctx *sctx, if (dev) { btrfs_err_rl(fs_info, "scrub: unable to fixup (regular) error at logical %llu on dev %s physical %llu", - stripe->logical, btrfs_dev_name(dev), - physical); + sector_logical, btrfs_dev_name(dev), + sector_physical); } else { btrfs_err_rl(fs_info, "scrub: unable to fixup (regular) error at logical %llu on mirror %u", - stripe->logical, stripe->mirror_num); + sector_logical, stripe->mirror_num); } if (scrub_bitmap_test_bit_io_error(stripe, sector_nr)) if (__ratelimit(&rs) && dev) scrub_print_common_warning("i/o error", dev, false, - stripe->logical, physical); + sector_logical, sector_physical); if (scrub_bitmap_test_bit_csum_error(stripe, sector_nr)) if (__ratelimit(&rs) && dev) scrub_print_common_warning("checksum error", dev, false, - stripe->logical, physical); + sector_logical, sector_physical); if (scrub_bitmap_test_bit_meta_error(stripe, sector_nr)) if (__ratelimit(&rs) && dev) scrub_print_common_warning("header error", dev, false, - stripe->logical, physical); + sector_logical, sector_physical); if (scrub_bitmap_test_bit_meta_gen_error(stripe, sector_nr)) if (__ratelimit(&rs) && dev) scrub_print_common_warning("generation error", dev, false, - stripe->logical, physical); + sector_logical, sector_physical); } /* Update the device stats. */ From a8813a923f9e43f788b357fb55c35f7f6ed6f98c Mon Sep 17 00:00:00 2001 From: Shuangpeng Bai Date: Sun, 16 Aug 2026 22:15:12 -0400 Subject: [PATCH 0619/2366] btrfs: fix transaction use-after-free in raid stripe insertion If allocation of a RAID stripe extent fails, btrfs_insert_one_raid_extent() aborts and ends the transaction before returning -ENOMEM. btrfs_finish_one_ordered(), the production caller through btrfs_insert_raid_extent(), still owns the transaction handle. It handles the error by aborting the transaction and then reaches the common exit path, which ends the transaction again. The premature end can free the handle and drop its transaction reference. Transaction cleanup can then free the transaction before the caller's second abort accesses the handle and transaction, resulting in use-after-free. Keep the abort at the failure site, but let the caller's common exit path end the transaction once, after it has finished using both objects. Fixes: 02c372e1f016 ("btrfs: add support for inserting raid stripe extents") Assisted-by: Codex:GPT-5 Reviewed-by: Qu Wenruo Signed-off-by: Shuangpeng Bai Signed-off-by: David Sterba --- fs/btrfs/raid-stripe-tree.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/btrfs/raid-stripe-tree.c b/fs/btrfs/raid-stripe-tree.c index b210371ce91e..89e259a47d8d 100644 --- a/fs/btrfs/raid-stripe-tree.c +++ b/fs/btrfs/raid-stripe-tree.c @@ -337,7 +337,6 @@ int btrfs_insert_one_raid_extent(struct btrfs_trans_handle *trans, stripe_extent = kzalloc(item_size, GFP_NOFS); if (unlikely(!stripe_extent)) { btrfs_abort_transaction(trans, -ENOMEM); - btrfs_end_transaction(trans); return -ENOMEM; } From afbe73778338e6d1ac8c4486fbdf33f0cc1f2624 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 17 Aug 2026 14:43:53 +0930 Subject: [PATCH 0620/2366] btrfs: fix the possible bioc_list memory leak during error There are two possible ways to leak bioc memory on btrfs_ordered_extent::bioc_list: - An error occurred for btrfs_insert_one_raid_extent() Then the function btrfs_insert_raid_extent() immediately return without freeing any bioc in the bioc_list. - An ordered extent hit an IO error In that case the ordered extent will have BTRFS_ORDERED_IOERR set, and skip the call on btrfs_insert_raid_extent() completely. Fix the problem by: - Introduce a new helper, btrfs_cleanup_ordered_bioc_list() Which will remove all bioc from the bioc_list, and release the bioc. - Call the above helper for btrfs_insert_raid_extent() So that the cleanup helper is always called no matter what. - Call the above helper for btrfs_finish_one_ordered() This is called just before the final release on the ordered extent. This was reported by Sashiko when reviewing another patch. Link: https://sashiko.dev/#/patchset/20260817021512.3010812-1-shuangpeng.kernel%40gmail.com Fixes: 02c372e1f016 ("btrfs: add support for inserting raid stripe extents") Reviewed-by: Johannes Thumshirn Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/inode.c | 3 +++ fs/btrfs/raid-stripe-tree.c | 18 ++++++++++++------ fs/btrfs/raid-stripe-tree.h | 1 + 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 50c6640543b9..3668cbc7598e 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -3436,6 +3436,9 @@ int btrfs_finish_one_ordered(struct btrfs_ordered_extent *ordered_extent) */ btrfs_remove_ordered_extent(ordered_extent); + /* Cleanup any remaining biocs attached to the OE. */ + btrfs_cleanup_ordered_bioc_list(ordered_extent); + /* once for us */ btrfs_put_ordered_extent(ordered_extent); /* once for the tree */ diff --git a/fs/btrfs/raid-stripe-tree.c b/fs/btrfs/raid-stripe-tree.c index 89e259a47d8d..6291775dbe0e 100644 --- a/fs/btrfs/raid-stripe-tree.c +++ b/fs/btrfs/raid-stripe-tree.c @@ -373,7 +373,7 @@ int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, struct btrfs_ordered_extent *ordered_extent) { struct btrfs_io_context *bioc; - int ret; + int ret = 0; if (!btrfs_fs_incompat(trans->fs_info, RAID_STRIPE_TREE)) return 0; @@ -381,17 +381,23 @@ int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, list_for_each_entry(bioc, &ordered_extent->bioc_list, rst_ordered_entry) { ret = btrfs_insert_one_raid_extent(trans, bioc); if (ret) - return ret; + break; } - while (!list_empty(&ordered_extent->bioc_list)) { - bioc = list_first_entry(&ordered_extent->bioc_list, + btrfs_cleanup_ordered_bioc_list(ordered_extent); + return ret; +} + +void btrfs_cleanup_ordered_bioc_list(struct btrfs_ordered_extent *ordered) +{ + while (!list_empty(&ordered->bioc_list)) { + struct btrfs_io_context *bioc; + + bioc = list_first_entry(&ordered->bioc_list, typeof(*bioc), rst_ordered_entry); list_del(&bioc->rst_ordered_entry); btrfs_put_bioc(bioc); } - - return 0; } int btrfs_get_raid_extent_offset(struct btrfs_fs_info *fs_info, diff --git a/fs/btrfs/raid-stripe-tree.h b/fs/btrfs/raid-stripe-tree.h index 69942ad43140..eb02cf48511b 100644 --- a/fs/btrfs/raid-stripe-tree.h +++ b/fs/btrfs/raid-stripe-tree.h @@ -28,6 +28,7 @@ int btrfs_get_raid_extent_offset(struct btrfs_fs_info *fs_info, u32 stripe_index, struct btrfs_io_stripe *stripe); int btrfs_insert_raid_extent(struct btrfs_trans_handle *trans, struct btrfs_ordered_extent *ordered_extent); +void btrfs_cleanup_ordered_bioc_list(struct btrfs_ordered_extent *ordered); #ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS int btrfs_insert_one_raid_extent(struct btrfs_trans_handle *trans, From a03fa65184545837d6461413275da71f30527385 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 17 Aug 2026 14:43:54 +0930 Subject: [PATCH 0621/2366] btrfs: return proper negative error code for update_raid_extent_item() The function btrfs_abort_transaction() only accepts negative error code, and have the macro VERIFY_NEGATIVE_ERROR() to verify that error code. But inside update_raid_extent_item(), if there is such key found, we return 1, breaking the negative error code scheme. Furthermore if we hit some real error during the tree search, e.g. -EIO, then the error code is always over-written to -EINVAL. Fix both problems by following other call sites by overwriting @ret to -ENOENT if the btrfs_search_slot() failed to locate the key. This is very unlikely to hit, as we only enter update_raid_extent_item() if there is a conflicting key already in the raid stripe tree. This was reported by Sashiko when reviewing another patch. Link: https://sashiko.dev/#/patchset/20260817021512.3010812-1-shuangpeng.kernel%40gmail.com Fixes: 8c4cba2adbb0 ("btrfs: update stripe extents for existing logical addresses") Reviewed-by: Johannes Thumshirn Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/raid-stripe-tree.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/raid-stripe-tree.c b/fs/btrfs/raid-stripe-tree.c index 6291775dbe0e..d9e660447205 100644 --- a/fs/btrfs/raid-stripe-tree.c +++ b/fs/btrfs/raid-stripe-tree.c @@ -310,8 +310,10 @@ static int update_raid_extent_item(struct btrfs_trans_handle *trans, ret = btrfs_search_slot(trans, trans->fs_info->stripe_root, key, path, 0, 1); - if (ret) - return (ret == 1 ? ret : -EINVAL); + if (ret > 0) + ret = -ENOENT; + if (ret < 0) + return ret; leaf = path->nodes[0]; slot = path->slots[0]; From 0853dc4f2678bbb21ff3d7572b0e9b812985bd65 Mon Sep 17 00:00:00 2001 From: ZhengYuan Huang Date: Mon, 17 Aug 2026 21:20:51 +0800 Subject: [PATCH 0622/2366] btrfs: send: reject extents for non-regular inodes [BUG] A corrupted subvolume tree can leave an EXTENT_DATA item attached to an inode whose mode is not S_IFREG or S_IFLNK. During send, such an item can be treated as file data and crash through a NULL address_space operation: BUG: kernel NULL pointer dereference, address: 0000000000000000 #PF: supervisor instruction fetch in kernel mode #PF: error_code(0x0010) - not-present page Call Trace: read_pages+0x80b/0xb30 mm/readahead.c:173 page_cache_ra_unbounded+0x40d/0x890 mm/readahead.c:302 do_page_cache_ra mm/readahead.c:332 [inline] page_cache_ra_order+0xa16/0xcd0 mm/readahead.c:535 page_cache_sync_ra+0x5ce/0x9d0 mm/readahead.c:626 page_cache_sync_readahead include/linux/pagemap.h:1379 [inline] put_file_data fs/btrfs/send.c:5224 [inline] send_write fs/btrfs/send.c:5291 [inline] send_extent_data+0x16b2/0x29b0 fs/btrfs/send.c:5715 send_write_or_clone fs/btrfs/send.c:6135 [inline] process_extent+0x5d4/0x17b0 fs/btrfs/send.c:6504 changed_extent fs/btrfs/send.c:7079 [inline] changed_cb+0x22f9/0x3cd0 fs/btrfs/send.c:7245 full_send_tree fs/btrfs/send.c:7318 [inline] send_subvol fs/btrfs/send.c:7910 [inline] btrfs_ioctl_send+0x46a9/0x57f0 fs/btrfs/send.c:8248 ... [CAUSE] process_extent() skips extent items for symlinks but assumes every other inode with an extent item is a regular file. For a corrupted non-regular inode, btrfs_iget() does not install the regular file address_space operations. The readahead fallback can then call a NULL read_folio callback before the existing validation in btrfs_get_extent() can run. [FIX] Reject extent items for inode types other than regular files and symlinks at the common send extent-processing boundary. Symlink handling is left unchanged because send emits symlink data from read_symlink(). This covers full, incremental and new-generation sends without adding a check to the regular I/O path. Reviewed-by: Qu Wenruo Signed-off-by: ZhengYuan Huang Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/send.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c index dca3570168c7..f88623bbc491 100644 --- a/fs/btrfs/send.c +++ b/fs/btrfs/send.c @@ -6417,6 +6417,13 @@ static int process_extent(struct send_ctx *sctx, if (S_ISLNK(sctx->cur_inode_mode)) return 0; + if (unlikely(!S_ISREG(sctx->cur_inode_mode))) { + btrfs_crit(sctx->send_root->fs_info, + "send: extent for non-regular inode %llu root %llu mode 0%llo", + key->objectid, btrfs_root_id(sctx->send_root), + sctx->cur_inode_mode & S_IFMT); + return -EUCLEAN; + } if (sctx->parent_root && !sctx->cur_inode_new) { ret = is_extent_unchanged(sctx, path, key); From a18a6b93a2843b9d103d3456bbd4b3f90282a379 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Wed, 19 Aug 2026 12:26:36 +0200 Subject: [PATCH 0623/2366] btrfs: zoned: finish active block group cleanup if call_zone_finish() fails do_zone_finish() clears BLOCK_GROUP_FLAG_ZONE_IS_ACTIVE before finishing the zones. If call_zone_finish() then fails it returned early, leaving the now inactive block group on fs_info->zone_active_bgs, leaking its reference, the BTRFS_FS_NEED_ZONE_FINISH waiters are never woken, and as its alloc_offset equals the zone capacity btrfs_zone_finish_one_bg() keeps selecting it, spinning btrfs_zoned_activate_one_bg(). Fall through to the cleanup on failure too and return the error, but keep the block group read-only as its zones are left inconsistent. Fixes: d70cbdda75da ("btrfs: zoned: consolidate zone finish functions") Link: https://sashiko.dev/#/patchset/20260818100037.1366563-1-johannes.thumshirn%40wdc.com Reviewed-by: Qu Wenruo Signed-off-by: Johannes Thumshirn Signed-off-by: David Sterba --- fs/btrfs/zoned.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c index a016cb471beb..7f0dde6398d4 100644 --- a/fs/btrfs/zoned.c +++ b/fs/btrfs/zoned.c @@ -2626,16 +2626,13 @@ static int do_zone_finish(struct btrfs_block_group *block_group, bool fully_writ down_read(&dev_replace->rwsem); map = block_group->physical_map; for (i = 0; i < map->num_stripes; i++) { - ret = call_zone_finish(block_group, &map->stripes[i]); - if (ret) { - up_read(&dev_replace->rwsem); - return ret; - } + if (ret) + break; } up_read(&dev_replace->rwsem); - if (!fully_written) + if (!ret && !fully_written) btrfs_dec_block_group_ro(block_group); spin_lock(&fs_info->zone_active_bgs_lock); @@ -2648,7 +2645,7 @@ static int do_zone_finish(struct btrfs_block_group *block_group, bool fully_writ clear_and_wake_up_bit(BTRFS_FS_NEED_ZONE_FINISH, &fs_info->flags); - return 0; + return ret; } int btrfs_zone_finish(struct btrfs_block_group *block_group) From c428b763f29bf2d7c67b69e5be964c7fb4eee282 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Tue, 18 Aug 2026 12:00:37 +0200 Subject: [PATCH 0624/2366] btrfs: zoned: propagate do_zone_finish() error in btrfs_zone_finish_endio() btrfs_zone_finish_endio() ignored the return value of do_zone_finish() and always returned 0, silently dropping a failed zone finish. Instead propagate any error from do_zone_finish() as the caller btrfs_finish_ordered_io() already handles it. Reviewed-by: Qu Wenruo Signed-off-by: Johannes Thumshirn Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/zoned.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c index 7f0dde6398d4..9cc2c9c1a606 100644 --- a/fs/btrfs/zoned.c +++ b/fs/btrfs/zoned.c @@ -2710,6 +2710,7 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng { struct btrfs_block_group *block_group; u64 min_alloc_bytes; + int ret = 0; if (!btrfs_is_zoned(fs_info)) return 0; @@ -2729,11 +2730,11 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng block_group->start + block_group->zone_capacity) goto out; - do_zone_finish(block_group, true); + ret = do_zone_finish(block_group, true); out: btrfs_put_block_group(block_group); - return 0; + return ret; } static void btrfs_zone_finish_endio_workfn(struct work_struct *work) From 529c01c3dc0d322c103611c35b01d71ea04562b2 Mon Sep 17 00:00:00 2001 From: Leo Martins Date: Tue, 18 Aug 2026 17:40:10 -0700 Subject: [PATCH 0625/2366] btrfs: abort transaction before releasing tree_log_mutex on commit failure When transaction metadata writeout fails in btrfs_commit_transaction(), the current code only logs the error, drops tree_log_mutex and then goes through cleanup_transaction(), which aborts the transaction and records the fs error. That is too late for the tree log side. A log sync can already be waiting on tree_log_mutex, because the committing transaction is moved to TRANS_STATE_UNBLOCKED while that mutex is held, which lets fsyncs join the next transaction and queue up in btrfs_sync_log(). Once the failed commit drops tree_log_mutex, such a log sync acquires it, sees BTRFS_FS_ERROR() still clear, and writes super_for_commit. That superblock holds the roots prepared for the transaction that has just failed to write out its metadata, so it can point at tree blocks that never reached the disk, and the next mount fails with a parent transid mismatch. Commit 165ea85f1483 ("btrfs: do not write supers if we have an fs error") fixed this class of problem by making btrfs_sync_log() check for an fs error right after taking tree_log_mutex. That check only works if the commit path publishes the fs error before it releases the same mutex, and commit 68d4ece9c30e ("btrfs: don't call btrfs_handle_fs_error() in btrfs_commit_transaction()") removed the only thing that did so. Restore the ordering by aborting the transaction while tree_log_mutex is still held. We have a transaction handle here, so this does not need to bring back the btrfs_handle_fs_error() call: __btrfs_abort_transaction() records the fs error itself, which is all btrfs_sync_log() looks at, and the error message put in its place is kept. This is what commit 3810ab40afa5 ("btrfs: abort transaction on error in write_all_supers()") already does for the next call in this function. This is reproducible on an unmodified kernel by failing the first couple of bios of a transaction commit with fail_make_request while a concurrent fsync workload keeps log syncs queued on tree_log_mutex. Fixes: 68d4ece9c30e ("btrfs: don't call btrfs_handle_fs_error() in btrfs_commit_transaction()") CC: stable@vger.kernel.org # 7.0+ Reviewed-by: Boris Burkov Reviewed-by: jlayton@meta.com Signed-off-by: Leo Martins Reviewed-by: Filipe Manana Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/transaction.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index bafc62cf5ebc..39909d363591 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -2583,6 +2583,12 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) ret = btrfs_write_and_wait_transaction(trans); if (unlikely(ret)) { btrfs_err(fs_info, "error while writing out transaction: %pe", ERR_PTR(ret)); + /* + * Abort before releasing tree_log_mutex, so a log sync waiting + * on it sees the fs error and skips writing super_for_commit + * for this failed transaction. See btrfs_sync_log(). + */ + btrfs_abort_transaction(trans, ret); mutex_unlock(&fs_info->tree_log_mutex); goto scrub_continue; } From d0285dfbc3b46f41395b26ee2f4a16d99fb3e736 Mon Sep 17 00:00:00 2001 From: Avi Weiss Date: Mon, 10 Aug 2026 12:47:01 +0300 Subject: [PATCH 0626/2366] btrfs: send: fix lost error return value in will_overwrite_ref() The direct-return refactoring in commit b3047a42f55d ("btrfs: send: directly return from will_overwrite_ref() and simplify it") changed will_overwrite_ref() to return directly instead of going through the common out label. That resulted in a negative return value from is_inode_existent() to start being converted to 0, making lookup errors unable to be distinguished from the inode not existing. process_recorded_refs() expects negative errors from will_overwrite_ref() and aborts processing when it receives one. Return the value from is_inode_existent() to restore the previous error propagation behavior as it was before the refactor. Fixes: b3047a42f55d ("btrfs: send: directly return from will_overwrite_ref() and simplify it") Signed-off-by: Avi Weiss Reviewed-by: Filipe Manana Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/send.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c index f88623bbc491..5c59b9abedcd 100644 --- a/fs/btrfs/send.c +++ b/fs/btrfs/send.c @@ -2065,7 +2065,7 @@ static int will_overwrite_ref(struct send_ctx *sctx, u64 dir, u64 dir_gen, ret = is_inode_existent(sctx, dir, dir_gen, NULL, &parent_root_dir_gen); if (ret <= 0) - return 0; + return ret; /* * If we have a parent root we need to verify that the parent dir was From cacf35832292997018837e484283f95a9301ebf5 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 20 Aug 2026 18:28:48 +0930 Subject: [PATCH 0627/2366] btrfs: do not force reloc root creation during qgroup_account_snapshot() [BUG] When running btrfs/252 with quota enabled through MKFS_OPTIONS="-O quota", it has a high chance to trigger the following kernel warning and flips the fs RO: BTRFS info (device dm-2): relocating block group 30408704 flags metadata|dup ------------[ cut here ]------------ WARNING: fs/btrfs/extent-tree.c:879 at lookup_inline_extent_backref+0x74b/0x960 [btrfs], CPU#4: btrfs/2173 CPU: 4 UID: 0 PID: 2173 Comm: btrfs Not tainted 7.2.0-rc6-custom+ #457 PREEMPT(full) 3adc6528fb66f7a55fe1095385818e742f200aab Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS unknown 02/02/2022 RIP: 0010:lookup_inline_extent_backref+0x74b/0x960 [btrfs] Call Trace: insert_inline_extent_backref+0x7c/0x160 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] __btrfs_inc_extent_ref+0xa9/0x270 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] __btrfs_run_delayed_refs+0x4af/0x11c0 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] btrfs_run_delayed_refs+0x9d/0xf0 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] create_pending_snapshot+0x39d/0xf00 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] create_pending_snapshots+0x9b/0xc0 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] btrfs_commit_transaction+0x280/0xeb0 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] prepare_to_relocate+0x147/0x200 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] relocate_block_group+0x6b/0x5e0 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] btrfs_relocate_block_group+0x92c/0x2380 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] btrfs_relocate_chunk+0x3f/0x1a0 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] btrfs_balance+0xa2c/0x19c0 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] btrfs_ioctl+0x2839/0x2d30 [btrfs 32f09462c54d9c922fca74a3e4866f4aa7737b72] __x64_sys_ioctl+0x416/0x9a0 do_syscall_64+0xe1/0x790 entry_SYSCALL_64_after_hwframe+0x4b/0x53 ---[ end trace 0000000000000000 ]--- BTRFS info (device dm-2): leaf 4593991680 gen 233 total ptrs 175 free space 5953 owner 2 BTRFS info (device dm-2): refs 3 lock_owner 2173 current 2173 item 0 key (166772736 METADATA_ITEM 1) itemoff 16250 itemsize 33 extent refs 1 gen 222 flags 2 ref#0: tree block backref root 266 [ Skip the tree dump ] item 174 key (263225344 METADATA_ITEM 0) itemoff 10328 itemsize 33 extent refs 1 gen 162 flags 258 ref#0: tree block backref root 267 BTRFS error (device dm-2): extent item not found for insert, bytenr 179847168 num_bytes 16384 parent 4594335744 root_objectid 273 owner 0 offset 0 BTRFS error (device dm-2): failed to run delayed ref for logical 179847168 num_bytes 16384 type 182 action 1 ref_mod 1: -117 [CAUSE] The above error is showing that there is a tree reference to a metadata extent that is no longer there. With "ref_verify" mount option (requires CONFIG_BTRFS_DEBUG), there is some extra debug output: BTRFS error (device dm-2): dumping block entry [180961280 16384], num_refs 0, metadata 1, from disk 0 BTRFS error (device dm-2): root entry 256, num_refs 18446744073709551615 BTRFS error (device dm-2): root entry 273, num_refs 18446744073709551615 BTRFS error (device dm-2): Ref action 3, root 273, ref_root 273, parent 0, owner 0, offset 0, num_refs 1 btrfs_force_cow_block+0x129/0x7d0 [btrfs] btrfs_cow_block+0x10a/0x250 [btrfs] btrfs_search_slot+0x5eb/0xf40 [btrfs] btrfs_insert_empty_items+0x3a/0x70 [btrfs] insert_with_overflow+0x53/0x130 [btrfs] btrfs_insert_dir_item+0x125/0x290 [btrfs] btrfs_add_link+0xaa/0x410 [btrfs] btrfs_rename+0x5ea/0xcd0 [btrfs] btrfs_rename2+0x28/0x60 [btrfs] vfs_rename+0x5b2/0xe10 filename_renameat2+0x244/0x430 __x64_sys_rename+0x48/0x70 do_syscall_64+0xe1/0x790 entry_SYSCALL_64_after_hwframe+0x4b/0x53 BTRFS error (device dm-2): Ref action 2, root 273, ref_root 273, parent 0, owner 0, offset 0, num_refs 18446744073709551615 btrfs_force_cow_block+0x327/0x7d0 [btrfs] btrfs_cow_block+0x10a/0x250 [btrfs] btrfs_search_slot+0x5eb/0xf40 [btrfs] btrfs_lookup_file_extent+0x4d/0x70 [btrfs] btrfs_drop_extents+0x151/0xf00 [btrfs] insert_reserved_file_extent+0xfe/0x3e0 [btrfs] btrfs_finish_one_ordered+0x549/0xc40 [btrfs] btrfs_work_helper+0xde/0x350 [btrfs] process_one_work+0x198/0x380 worker_thread+0x1c8/0x330 kthread+0xee/0x120 ret_from_fork+0x28f/0x310 ret_from_fork_asm+0x11/0x20 BTRFS error (device dm-2): Ref action 1, root 273, ref_root 0, parent 4594335744, owner 0, offset 0, num_refs 1 __btrfs_mod_ref+0x1c5/0x2d0 [btrfs] btrfs_copy_root+0x262/0x390 [btrfs] create_reloc_root+0xb9/0x370 [btrfs] btrfs_init_reloc_root+0xb0/0x1b0 [btrfs] record_root_in_trans+0xa6/0xd0 [btrfs] create_pending_snapshot+0x383/0xf00 [btrfs] create_pending_snapshots+0x9b/0xc0 [btrfs] btrfs_commit_transaction+0x280/0xeb0 [btrfs] prepare_to_relocate+0x147/0x200 [btrfs] relocate_block_group+0x6b/0x5e0 [btrfs] btrfs_relocate_block_group+0x92c/0x2380 [btrfs] btrfs_relocate_chunk+0x3f/0x1a0 [btrfs] btrfs_balance+0xa2c/0x19c0 [btrfs] btrfs_ioctl+0x2839/0x2d30 [btrfs] __x64_sys_ioctl+0x416/0x9a0 do_syscall_64+0xe1/0x790 The above shows the direct cause, Ref action 3 is the oldest operation, which shows the tree block is created by COW. Then ref action 2 shows it's COWed away, by a metadata update, meaning the tree block is already released, should not be referred any more. Then the final one, is trying to create a reloc tree for subvolume 273, and that reloc root creation is referring to the already dropped tree block. The root cause is that, during qgroup_account_snapshot(), we are calling record_root_in_trans() with "force = true". So if the root has no reloc root, we will create one, but at that timing it's already too late. Normally reloc root should be created before the commit and current roots diverge, to avoid the same problem we are hitting. But during relocation initialization, we are committing the current running transaction, with a new reloc_control attached halfway. And if qgroup is enabled, the record_root_in_trans() with "force = true" calls will force reloc root creation even if we do not and should not create reloc root at that timing. [FIX] Do not force reloc root creation during record_root_in_trans() with "force = true" cases, which is only called by qgroup_account_snapshot(). If we're really under relocation, the reloc root should be created way early, before the commit and current root diverge. If the root has no reloc tree yet, it means we're still initializing the reloc, and do not need a reloc root. So skipping the reloc tree creation in qgroup_account_snapshot() should be safe. Link: https://bugzilla.suse.com/show_bug.cgi?id=1275740 Fixes: 4d31778aa2fa ("btrfs: qgroup: Fix root item corruption when multiple same source snapshots are created with quota enabled") Assisted-by: LLM (initial analysis, but incorrect conclusion with too many burnt tokens) Tested-by: Disha Goel Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/transaction.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 39909d363591..6802b94ed76f 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -458,8 +458,19 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans, * through btrfs_record_root_in_trans without having to take the * lock. smp_wmb() makes sure that all the writes above are * done before we pop in the zero below + * + * If @force is true, it means the call is from + * qgroup_account_snapshot(), which only requires radix tree + * tracking. + * We should not force reloc root creation here, as the root + * may have already been modified, and in that case + * root->commit_root has already been dropped. + * + * Using that commit root will cause the reloc root to refer + * to a deleted extent, causing extent tree corruption. */ - ret = btrfs_init_reloc_root(trans, root); + if (!force) + ret = btrfs_init_reloc_root(trans, root); smp_mb__before_atomic(); clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state); } From 2acb9f3d1cc8f65dc81ed55e238cbf8e5b60bff7 Mon Sep 17 00:00:00 2001 From: FAN YE Date: Fri, 21 Aug 2026 17:50:09 +0000 Subject: [PATCH 0628/2366] btrfs: zstd: fix lost wakeup when waiting for a workspace A writer can sleep forever in zstd_get_workspace() even though a workspace is free. When zstd_alloc_workspace() fails, the task is queued on zwsm->wait and schedules unconditionally, never re-testing the pool. zstd_put_workspace() publishes the workspace and then calls cond_wake_up(), which only wakes when a sleeper is already visible, so a workspace returned between the failed allocation and prepare_to_wait() wakes nobody. The window is wide: zstd_alloc_workspace() goes through kvmalloc() and may enter reclaim. Only a max level workspace triggers the wakeup and one is deliberately kept allocated as the fallback every waiter waits for, so once its wakeup is lost the writer stays in TASK_UNINTERRUPTIBLE until some other task happens to return one. Re-check the pool after prepare_to_wait() has published the waiter, and use the workspace if one turned up. Fixes: 3f93aef535c8 ("btrfs: add zstd compression level support") Assisted-by: Claude:claude-opus-5 Reviewed-by: Qu Wenruo Signed-off-by: FAN YE Signed-off-by: David Sterba --- fs/btrfs/zstd.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/zstd.c b/fs/btrfs/zstd.c index 86919293fd54..58d9ff76fe07 100644 --- a/fs/btrfs/zstd.c +++ b/fs/btrfs/zstd.c @@ -307,8 +307,17 @@ struct list_head *zstd_get_workspace(struct btrfs_fs_info *fs_info, int level) DEFINE_WAIT(wait); prepare_to_wait(&zwsm->wait, &wait, TASK_UNINTERRUPTIBLE); - schedule(); + /* + * Re-check after being queued: zstd_put_workspace() only wakes + * a queue that already has a sleeper, so a workspace returned + * since the failed allocation woke nobody. + */ + ws = zstd_find_workspace(fs_info, level); + if (!ws) + schedule(); finish_wait(&zwsm->wait, &wait); + if (ws) + return ws; goto again; } From 0c1032c8c3e9dc8b9a9fa5f6ef23966e236466ed Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 11 Aug 2026 15:31:49 +0930 Subject: [PATCH 0629/2366] btrfs: tests: do not touch page cache if root/inode allocation failed Inside test_find_delalloc() of extent-io-tests.c, if we fail to allocate a dummy root or the test inode, we go to out label to clean up. But at that stage, @inode is still NULL and we will call process_page_range() to access the page cache of the inode, this will cause NULL pointer dereference. This is a very minor bug, as it only affects selftests which are not compiled in by default for most distros, and very hard to trigger. Fix it by adding a new out_root_info label to handle root and inode allocation failure. This is a pre-existing bug reported by Sashiko while reviewing another patch. Link: https://sashiko.dev/#/patchset/cover.1786095309.git.wqu%40suse.com Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tests/extent-io-tests.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/tests/extent-io-tests.c b/fs/btrfs/tests/extent-io-tests.c index b2aacf846c8b..23459cd4e503 100644 --- a/fs/btrfs/tests/extent-io-tests.c +++ b/fs/btrfs/tests/extent-io-tests.c @@ -133,14 +133,14 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) if (IS_ERR(root)) { test_std_err(TEST_ALLOC_ROOT); ret = PTR_ERR(root); - goto out; + goto out_root_info; } inode = btrfs_new_test_inode(); if (!inode) { test_std_err(TEST_ALLOC_INODE); ret = -ENOMEM; - goto out; + goto out_root_info; } tmp = &BTRFS_I(inode)->io_tree; BTRFS_I(inode)->root = root; @@ -333,6 +333,7 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) process_page_range(inode, 0, total_dirty - 1, PROCESS_UNLOCK | PROCESS_RELEASE); iput(inode); +out_root_info: btrfs_free_dummy_root(root); btrfs_free_dummy_fs_info(fs_info); return ret; From 6a7a45b1d94799a5eb8e6d26e65cf31a3fcda9e5 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Thu, 27 Aug 2026 12:29:16 -0700 Subject: [PATCH 0630/2366] MAINTAINERS: update Chris Mason's email address David Sterba has been doing the Btrfs maintainership work for years, and my email update to mason@kernel.org seems like a good time to make the MAINTAINERS file a little more accurate. Link: https://lore.kernel.org/all/20260827193032.786461-1-clm@meta.com/ Signed-off-by: Chris Mason Signed-off-by: David Sterba --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 58a0875d4f01..3f09f9cabe59 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5616,8 +5616,8 @@ W: http://bu3sch.de/btgpio.php F: drivers/gpio/gpio-bt8xx.c BTRFS FILE SYSTEM -M: Chris Mason M: David Sterba +R: Chris Mason L: linux-btrfs@vger.kernel.org S: Maintained W: https://btrfs.readthedocs.io From 94e25cb6ab7f4f025bcdcd8ea79fda30f12843a4 Mon Sep 17 00:00:00 2001 From: Priya Hosur Date: Thu, 27 Aug 2026 15:02:46 +0530 Subject: [PATCH 0631/2366] drm/amdkfd: Add TLB flush after MES queue eviction/suspension MES (Micro Engine Scheduler) does not perform heavy-weight TLB invalidation after unmapping queues, unlike HWS which does this automatically. This causes a race condition where in-flight DMA descriptors can access memory that has been unmapped, leading to page faults and GPU queue hangs during SVM page migration. The issue manifests as KFDSVMRangeTest.MultiThreadMigrationTest failures on gfx1151 (Strix Point) with XNACK mode 1 enabled - the GPU compute queue hangs with packets submitted but never consumed. Add kfd_flush_tlb() calls after MES queue removal in two locations: - evict_process_queues_cpsch(): after all queues removed during eviction - suspend_queues(): after debug/criu queue suspension (with mem_fence barrier) This ensures all in-flight memory accesses from unmapped queues are flushed before memory is freed or migrated. Signed-off-by: Priya Hosur Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher (cherry picked from commit f5c4f88e0f9c45a8fb9dfac0c1df726c95e41b77) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 4bc947c3bd0d..9811e4e10291 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -1455,6 +1455,14 @@ static int evict_process_queues_cpsch(struct device_queue_manager *dqm, dqm_evict_mqd_bo(dqm, q); } + /* + * Heavy-weight TLB flush after MES removes queues to ensure + * in-flight memory accesses complete before memory is freed/migrated. + * HWS does this automatically, MES does not. + */ + if (dqm->dev->kfd->shared_resources.enable_mes) + kfd_flush_tlb(pdd); + if (!dqm->dev->kfd->shared_resources.enable_mes) { pdd->last_evict_timestamp = get_jiffies_64(); retval = execute_queues_cpsch(dqm, @@ -3746,8 +3754,11 @@ int suspend_queues(struct kfd_process *p, if (!per_device_suspended) { dqm_unlock(dqm); mutex_unlock(&p->event_mutex); - if (total_suspended) + if (total_suspended) { amdgpu_amdkfd_debug_mem_fence(dqm->dev->adev); + /* Heavy-weight TLB flush after MES suspends queues */ + kfd_flush_tlb(pdd); + } continue; } From bd1f08246b8a2564d8ac61df715b6bcd5f994729 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 26 Aug 2026 13:51:02 -0500 Subject: [PATCH 0632/2366] drm/amdgpu: restrict BAR0 fallback read to SR-IOV VFs only The BAR0 fallback read path was introduced as a workaround for SR-IOV VFs where the VRAM aperture is not available during early init. Restrict this workaround to only SR-IOV VFs where it's needed. Reported-by: gloveless@jqluv.com Fixes: cba4928cdffa ("drm/amdgpu: reduce early full GPU access during SR-IOV init") Acked-by: Alex Deucher Link: https://patch.msgid.link/20260826185102.2269511-1-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit d8a0affd207c813bd063fa2c27786f449eaf92b8) --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 44bed0ba64a3..104d1d2cbad9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -771,6 +771,9 @@ static int amdgpu_device_read_fb_via_bar0(struct amdgpu_device *adev, if (!buf || !size) return -EINVAL; + if (!amdgpu_sriov_vf(adev)) + return -EINVAL; + flags = pci_resource_flags(adev->pdev, 0); if ((flags & IORESOURCE_UNSET) || !(flags & IORESOURCE_MEM)) return -EINVAL; From 7346a046c6a9b9f30cb1f7d301449300a9174c71 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 20 Aug 2026 09:02:01 -0400 Subject: [PATCH 0633/2366] drm/amdgpu/gfx8: only apply compute quantums to KCQs Don't apply to KIQ. Seems to cause problems on KIQ on some ARM platforms. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5658 Fixes: 91cf34bc5a55 ("drm/amdgpu/gfx8: align mqd settings with KFD") Reviewed-by: Jesse Zhang Reviewed-by: Kent Russell Signed-off-by: Alex Deucher (cherry picked from commit 6aae7bab029cdccae9a7157facfe36bfc35fc940) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index 6cf427995078..7f91186ef1d1 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -4546,9 +4546,11 @@ static int gfx_v8_0_mqd_init(struct amdgpu_ring *ring) /* set static priority for a queue/ring */ gfx_v8_0_mqd_set_priority(ring, mqd); tmp = RREG32(mmCP_HQD_QUANTUM); - tmp = REG_SET_FIELD(tmp, CP_HQD_QUANTUM, QUANTUM_EN, 1); - tmp = REG_SET_FIELD(tmp, CP_HQD_QUANTUM, QUANTUM_SCALE, 1); - tmp = REG_SET_FIELD(tmp, CP_HQD_QUANTUM, QUANTUM_DURATION, 10); + if (ring != &adev->gfx.kiq[0].ring) { + tmp = REG_SET_FIELD(tmp, CP_HQD_QUANTUM, QUANTUM_EN, 1); + tmp = REG_SET_FIELD(tmp, CP_HQD_QUANTUM, QUANTUM_SCALE, 1); + tmp = REG_SET_FIELD(tmp, CP_HQD_QUANTUM, QUANTUM_DURATION, 10); + } mqd->cp_hqd_quantum = tmp; /* map_queues packet doesn't need activate the queue, From b428f83c7c2542837c15231c519583cc26b60156 Mon Sep 17 00:00:00 2001 From: Amber Lin Date: Thu, 27 Aug 2026 15:02:53 -0400 Subject: [PATCH 0634/2366] drm/amdgpu: Update queue reset support version Update queue reset required MES version for MES 12.1 to 0x7b since we change the implementation from detect-and-reset method to per-queue-reset method. Signed-off-by: Amber Lin Reviewed-by: Michael Chen Signed-off-by: Alex Deucher (cherry picked from commit 2160a5cbf0b7917adce4b55421306b614b4a2c8f) --- drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c index b96f94e5169f..1a86a47406b1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mes.c @@ -879,7 +879,7 @@ bool amdgpu_mes_queue_reset_by_mes_supported(struct amdgpu_device *adev) return (ip_maj == 11 && mes_sched >= 0x8c) || ((ip_maj == 12 && ip_min == 0) && mes_sched >= 0x8d) || - ((ip_maj == 12 && ip_min == 1) && mes_sched >= 0x73); + ((ip_maj == 12 && ip_min == 1) && mes_sched >= 0x7b); } /* Fix me -- node_id is used to identify the correct MES instances in the future */ From d6e16df7df4d2c39e2b04b355d0434fb90e2d62c Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 27 Aug 2026 20:33:35 +0530 Subject: [PATCH 0635/2366] drm/amdgpu: use AMDGPU_GPU_PAGE_SHIFT instead of PAGE_SHIFT For different address types the variable PAGE_SHIFT might not work well and it's better to use the GPU specific one Signed-off-by: Sunil Khatri Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 3494b77d10375e0f9ab784e9b20763339844b55b) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index aedf72c2333e..28f29f2d7c14 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -2090,7 +2090,7 @@ int amdgpu_vm_bo_clear_mappings(struct amdgpu_device *adev, after->start = eaddr + 1; after->last = tmp->last; after->offset = tmp->offset; - after->offset += (after->start - tmp->start) << PAGE_SHIFT; + after->offset += (after->start - tmp->start) << AMDGPU_GPU_PAGE_SHIFT; after->flags = tmp->flags; after->bo_va = tmp->bo_va; list_add(&after->list, &tmp->bo_va->invalids); From 90ce19bd11b2864e26e4b43e7acbffabf037b69b Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 27 Aug 2026 21:00:22 +0530 Subject: [PATCH 0636/2366] drm/amdgpu: fix Idle BOs list in VM debugfs status info amdgpu_debugfs_vm_bo_status_info() prints the "Idle BOs" section by iterating lists->needs_update, the same list already printed just above under "Moved BOs". struct amdgpu_vm_bo_status has a dedicated idle list, populated whenever a BO's state machine settles, but it was never read here, so genuinely idle BOs never show up in the debugfs output and the "Idle BOs" section duplicates "Moved BOs" instead. Iterate lists->idle for the "Idle BOs" section. Fixes: 4cdbba5a16aa ("drm/amdgpu: restructure VM state machine v4") Signed-off-by: Sunil Khatri Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 451bfc778a8c364841837def00ba15936f72762b) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index 28f29f2d7c14..bb04101b0fb5 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -3122,7 +3122,7 @@ static void amdgpu_debugfs_vm_bo_status_info(struct seq_file *m, id = 0; seq_puts(m, "\tIdle BOs:\n"); - list_for_each_entry(base, &lists->needs_update, vm_status) { + list_for_each_entry(base, &lists->idle, vm_status) { if (!base->bo) continue; From ef0e9d12727d0fec762eea5053ff02ba555b8895 Mon Sep 17 00:00:00 2001 From: Ivan Lipski Date: Wed, 12 Aug 2026 23:15:18 -0400 Subject: [PATCH 0637/2366] drm/amd/display: Fix DCN5/6 DML2 compilation warnings [WHY] A kernel compilation warning was reported caused by upstream of DCN5/6. [HOW] Using plain integer as NULL pointer. Assign NULL to the VActiveLatencyHidingMargin/VActiveLatencyHidingUs pointer members in dml2_core_dcn5_funcs_mode_programming.c, and pass NULL for the pointer arguments to calculate_first_second_splitting() in dml2_pmo_dcn6_stage_optimizers.c. Fixes: 7f7d7ea1fa51 ("drm/amd/display: Add new sources for DCN6") Reviewed-by: Dillon Varone Signed-off-by: Ivan Lipski Signed-off-by: Alex Hung Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit d96880560e9f35ba7f8de1b3f90032c8c3eaea88) --- .../src/dml2_core/dml2_core_dcn5_funcs_mode_programming.c | 4 ++-- .../dml21/src/dml2_pmo/dml2_pmo_dcn6_stage_optimizers.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_funcs_mode_programming.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_funcs_mode_programming.c index 8497eaea012e..297e21e7c68d 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_funcs_mode_programming.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_dcn5_funcs_mode_programming.c @@ -1128,8 +1128,8 @@ static bool dcn5_mode_programming(struct dml2_core_calcs_mode_programming_ex *in CalculateWatermarks_params->USRRetrainingSupport = &mode_lib->mp.USRRetrainingSupport; CalculateWatermarks_params->temp_read_or_ppt_support = mode_lib->mp.temp_read_or_ppt_support; CalculateWatermarks_params->global_temp_read_or_ppt_supported = &mode_lib->mp.global_temp_read_or_ppt_supported; - CalculateWatermarks_params->VActiveLatencyHidingMargin = 0; - CalculateWatermarks_params->VActiveLatencyHidingUs = 0; + CalculateWatermarks_params->VActiveLatencyHidingMargin = NULL; + CalculateWatermarks_params->VActiveLatencyHidingUs = NULL; dcn5_calculate_watermarks_and_dram_speed_change_support(&mode_lib->scratch, CalculateWatermarks_params); diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn6_stage_optimizers.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn6_stage_optimizers.c index 0b884a8661c8..6d6611a6b5a6 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn6_stage_optimizers.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_pmo/dml2_pmo_dcn6_stage_optimizers.c @@ -1414,7 +1414,7 @@ static bool find_shift_for_valid_cache_id_assignment(const int *mcache_boundarie success = true; for (pipe_index = 0; pipe_index < pipe_count; pipe_index++) { if (!calculate_first_second_splitting(mcache_boundaries, num_boundaries, *shift, - pipe_vp_startx[pipe_index], pipe_vp_endx[pipe_index], 0, 0)) { + pipe_vp_startx[pipe_index], pipe_vp_endx[pipe_index], NULL, NULL)) { success = false; break; } From 9ce3169430f1db035d491481086e2fae2552569c Mon Sep 17 00:00:00 2001 From: Roman Li Date: Fri, 14 Aug 2026 18:03:17 -0400 Subject: [PATCH 0638/2366] drm/amd/display: Set gpuvm min page size to 4K on dcn35/36 [WHY] Splash screen corruption on some 8K monitors. [HOW] Set GPUVM min page size to 4K for DCN35/36 to use the correct DML2 calculations, avoiding the corruption path observed during splash. Fixes: 115009d11ccf ("drm/amd/display: Add DCN35 DML2 support") Cc: Mario Limonciello Cc: Alex Deucher Reviewed-by: Alex Hung Signed-off-by: Roman Li Signed-off-by: Alex Hung Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 2cbfb03dead5088a7bdfe2ce392a5caa3d1b3719) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/dml2_0/dml2_translation_helper.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml2_translation_helper.c b/drivers/gpu/drm/amd/display/dc/dml2_0/dml2_translation_helper.c index 166f10b8862f..c82886323a51 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml2_translation_helper.c +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml2_translation_helper.c @@ -301,6 +301,7 @@ void dml2_init_socbb_params(struct dml2_context *dml2, const struct dc *in_dc, s out->smn_latency_us = 2; out->dispclk_dppclk_vco_speed_mhz = 3600; out->pct_ideal_dram_bw_after_urgent_pixel_only = 65.0; + out->gpuvm_min_page_size_kbytes = 4; break; From 93a77d353cb26772ae2fba50ae7321ae996b7f00 Mon Sep 17 00:00:00 2001 From: Austin Zheng Date: Wed, 19 Aug 2026 09:33:57 -0400 Subject: [PATCH 0639/2366] drm/amd/display: Remove const Qualifier From Non-Pointer Fields [WHY/HOW] Integer values for dml2_core_calcs_CalculateWatermarksMALLUseAndDRAMSpeedChangeSupport_params should not have the const qualifier. This prevents using different values of the inputs when the function is called again. Reviewed-by: Dillon Varone Signed-off-by: Austin Zheng Signed-off-by: Alex Hung Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 342280aae4f33816e8d07c15cb538a3b375a7f8f) Cc: stable@vger.kernel.org --- .../dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h index 8a371bd1a7a5..28f4a53d0617 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/dml21/src/dml2_core/dml2_core_shared_types.h @@ -1819,8 +1819,8 @@ struct dml2_core_calcs_CalculateWatermarksMALLUseAndDRAMSpeedChangeSupport_param bool UnboundedRequestEnabled; unsigned int CompressedBufferSizeInkByte; bool max_outstanding_when_urgent_expected; - const unsigned int max_outstanding_requests; - const unsigned int max_request_size_bytes; + unsigned int max_outstanding_requests; + unsigned int max_request_size_bytes; const unsigned int *meta_row_height_l; const unsigned int *meta_row_height_c; const enum dml2_pstate_method *uclk_pstate_switch_modes; From 5a67d2e055897a36acb4e48082986fb4a4eebdd3 Mon Sep 17 00:00:00 2001 From: Roman Li Date: Fri, 21 Aug 2026 12:19:10 -0400 Subject: [PATCH 0640/2366] drm/amd/display: Fix backlight control for luminance-capable OLED [WHY] For some eDP panels VESA aux backlight control is necessary, otherwise they stay black. [HOW] When AUX backlight control is used, select BACKLIGHT_CONTROL_VESA_AUX for panels that advertise panel_luminance_control. Reviewed-by: Hansen Dsouza Signed-off-by: Roman Li Signed-off-by: Alex Hung Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 42f698bd061d76d5f4c84a195e465cfbeec775e4) --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index e61bbc310f33..b9e90ea449ca 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -534,8 +534,12 @@ void amdgpu_dm_update_connector_ext_caps(struct amdgpu_dm_connector *aconnector) else if (!IS_ERR_OR_NULL(panel_backlight_quirk) && panel_backlight_quirk->force_pwm) caps->aux_support = false; - if (caps->aux_support) - aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; + if (caps->aux_support) { + if (aconnector->dc_link->dpcd_caps.panel_luminance_control) + aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_VESA_AUX; + else + aconnector->dc_link->backlight_control_type = BACKLIGHT_CONTROL_AMD_AUX; + } luminance_range = &conn_base->display_info.luminance_range; From 4278d65a41a2f6530437738f158f69e379bddb1c Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Thu, 20 Aug 2026 11:26:37 +0200 Subject: [PATCH 0641/2366] drm/amd/display: use halving distribution for all encode-to-linear curves In encode-to-linear conversions, LUT entries should be uniformly distributed across the input range: non-linear encodings are already approximately perceptually uniform, so every input code carries the same weight. A fixed count per region does the opposite, concentrating entries on the darker values and leaving few for the bright end, whereas halving distribution spaces all 256 entries uniformly. This holds for any encoded input, so remove the PQ/sRGB condition from commit "drm/amd/display: use halving distribution for PQ/sRGB linearizing LUT" and apply halving to all encode-to-linear operations (pre-defined TF or user LUTs). It fixes the following IGT kms_colorop subtests: - plane-XR30-XR30-srgb_inv_eotf_lut-srgb_eotf_lut - plane-XR30-XR30-gamma_2_2-gamma_2_2_inv-gamma_2_2 Fixes: a71d2b051f33 ("drm/amd/display: use halving distribution for PQ/sRGB linearizing LUT") Reviewed-by: Alex Hung Reviewed-by: Harry Wentland Signed-off-by: Melissa Wen Signed-off-by: Alex Deucher (cherry picked from commit 6df7c9c307e72e7f13829e94edc89134f0764775) --- .../amd/display/dc/dcn30/dcn30_cm_common.c | 34 ++++++------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c index 66fe7f313ea3..62ca235cd649 100644 --- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c +++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c @@ -320,6 +320,8 @@ static struct fixed31_32 interp_tf_pts(const struct fixed31_32 *output_tf_channe return value; } +#define NUM_DEGAMMA_REGIONS 9 + bool cm3_helper_translate_curve_to_degamma_hw_format( const struct dc_transfer_func *output_tf, struct pwl_params *lut_params) @@ -343,31 +345,15 @@ bool cm3_helper_translate_curve_to_degamma_hw_format( memset(lut_params, 0, sizeof(struct pwl_params)); memset(seg_distr, 0, sizeof(seg_distr)); - if (output_tf->tf == TRANSFER_FUNCTION_PQ || - output_tf->tf == TRANSFER_FUNCTION_SRGB) { - /* 9 segments - * segments are from 2^-9 to 0 - */ - const uint8_t SEG_COUNT = 9; - seg_distr[0] = 0; // Since we only have one point in darkest region - for (k = 1; k < SEG_COUNT; k++) - seg_distr[k] = k - 1; // 2^(k-1) points per region; halves as k decreases + /* 9 segments + * segments are from 2^-9 to 2^0 + */ + seg_distr[0] = 0; // Since we only have one point in darkest region + for (k = 1; k < NUM_DEGAMMA_REGIONS; k++) + seg_distr[k] = k - 1; // 2^(k-1) points per region; halves as k decreases - region_start = -SEG_COUNT; - region_end = 0; - } else { - /* 12 segments - * segments are from 2^-12 to 2^0 - * There are less than 256 points, for optimization - */ - const uint8_t SEG_COUNT = 12; - - for (i = 0; i < SEG_COUNT; i++) - seg_distr[i] = 4; - - region_start = -SEG_COUNT; - region_end = 0; - } + region_start = -NUM_DEGAMMA_REGIONS; + region_end = 0; for (i = region_end - region_start; i < MAX_REGIONS_NUMBER ; i++) seg_distr[i] = -1; From f63de9054da858d57054474c32464106f8375e0d Mon Sep 17 00:00:00 2001 From: Hari Mishal Date: Sat, 22 Aug 2026 16:57:51 +0200 Subject: [PATCH 0642/2366] drm/amd/display: fix division by zero in get_estimated_bw() get_estimated_bw() divides by link->dpia_bw_alloc_config.bw_granularity, which is zeroed by reset_bw_alloc_struct() and only populated once DP_TUNNELING_BW_ALLOC_CAP_CHANGED has been handled. link_dp_dpia_handle_bw_alloc_status(), the DPCD interrupt handler, calls get_estimated_bw() whenever DP_TUNNELING_ESTIMATED_BW_CHANGED is set, independently of whether DP_TUNNELING_BW_ALLOC_CAP_CHANGED has ever fired for that link. A connected USB4/DPIA tunneling device that reports an estimated-bandwidth change before ever reporting a capability change drives a division by zero in this IRQ path. link_dpia_send_bw_alloc_request() already guards the same bw_granularity division; add the identical guard here rather than introducing a new pattern. Fixes: 8e5cfe547bf3 ("drm/amd/display: upstream link_dp_dpia_bw.c") Reviewed-by: Alex Hung Assisted-by: gkh_clanker_t1000 Signed-off-by: Hari Mishal Signed-off-by: Alex Deucher (cherry picked from commit f2a961457c33dc34223aad5c9e8971de34a4eed3) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/display/dc/link/protocols/link_dp_dpia_bw.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_dpia_bw.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_dpia_bw.c index dd854d992692..f43fc4b78a8d 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_dpia_bw.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_dpia_bw.c @@ -103,6 +103,11 @@ static int get_estimated_bw(struct dc_link *link) { uint8_t bw_estimated_bw = 0; + if (link->dpia_bw_alloc_config.bw_granularity == 0) { + DC_LOG_ERROR("%s: BW granularity is zero!\n", __func__); + return 0; + } + core_link_read_dpcd( link, ESTIMATED_BW, From 012a026bae0212952b423a842b7e2c0bf21f8e7a Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 31 Aug 2026 08:00:51 -0500 Subject: [PATCH 0643/2366] drm/amdkfd: fix scope of mqd_mgr dereference in pqm_debugfs_mqds Reading /sys/kernel/debug/kfd/mqds while a process holds an active KFD queue triggers a NULL pointer dereference because the for loop that calls mqd_mgr->debugfs_show_mqd() is incorrectly placed outside the if (pqn->q) block that initializes mqd_mgr. The queue list can contain entries where pqn->q is NULL (kernel queues where only pqn->kq is valid). In the original code: if (pqn->q) { ... mqd_mgr = q->device->dqm->mqd_mgrs[mqd_type]; size = mqd_mgr->mqd_stride(...); } for (xcc = 0; xcc < num_xccs; xcc++) { // WRONG: outside if block mqd = q->mqd + size * xcc; r = mqd_mgr->debugfs_show_mqd(m, mqd); } When iterating over a queue node where pqn->q is NULL: 1. The if (pqn->q) block is skipped 2. mqd_mgr remains uninitialized (NULL from declaration) 3. The for loop executes anyway 4. mqd_mgr->debugfs_show_mqd(m, mqd) dereferences NULL The crash manifests as: BUG: kernel NULL pointer dereference, address: 0000000000000000 #PF: supervisor instruction fetch in kernel mode RIP: 0010:0x0 Call Trace: pqm_debugfs_mqds+0x10c/0x1d0 [amdgpu] kfd_debugfs_mqds_by_process+0x9b/0x110 [amdgpu] seq_read_iter+0x132/0x4b0 ... Fix by moving the for loop inside the if (pqn->q) block, so mqd_mgr and related variables are only used when properly initialized. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5689 Reviewed-by: Alex Deucher Link: https://patch.msgid.link/20260831130051.2031435-1-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 8bfe29d5c798940f797aa24135d2734c3ffce9de) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/amdkfd/kfd_process_queue_manager.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c index ef1d1cb46152..4fe40e9fcfc8 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process_queue_manager.c @@ -1169,13 +1169,13 @@ int pqm_debugfs_mqds(struct seq_file *m, void *data) mqd_mgr = q->device->dqm->mqd_mgrs[mqd_type]; size = mqd_mgr->mqd_stride(mqd_mgr, &q->properties); - } - for (xcc = 0; xcc < num_xccs; xcc++) { - mqd = q->mqd + size * xcc; - r = mqd_mgr->debugfs_show_mqd(m, mqd); - if (r != 0) - break; + for (xcc = 0; xcc < num_xccs; xcc++) { + mqd = q->mqd + size * xcc; + r = mqd_mgr->debugfs_show_mqd(m, mqd); + if (r != 0) + break; + } } } From 3b5c4f4a479d0e58a7500cbd2b09d62f719f48b8 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Mon, 31 Aug 2026 21:01:39 +0530 Subject: [PATCH 0644/2366] drm/amdgpu: fix byte/dword unit mismatch in coredump IB dump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In amdgpu_devcoredump_print_ibs(), the NO_CPU_ACCESS VRAM path passed cursor.start/4 and cursor.size/4 to amdgpu_device_mm_access(), but that function's pos/size parameters are byte offsets/lengths (confirmed by amdgpu_ttm_vram_mm_access() and leading to wrong size calculation. Similarly with that change the off index needs to be calculated based on dword since that is a u32 type. Fixes: 7b15fc2d1f1a ("drm/amdgpu: dump job ibs in the devcoredump") Signed-off-by: Sunil Khatri Reviewed-by: Vitaly Prosyak Acked-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 1bd613b0ed98a23575b18674c94b8b3392614681) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index 87e15e39eb30..ec6e5bde7f80 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -299,10 +299,10 @@ amdgpu_devcoredump_print_ibs(struct drm_printer *p, amdgpu_res_first(abo->tbo.resource, offset, coredump->ibs[i].ib_size_dw * 4, &cursor); while (cursor.remaining) { - amdgpu_device_mm_access(adev, cursor.start / 4, - &ib_content[off], cursor.size / 4, + amdgpu_device_mm_access(adev, cursor.start, + &ib_content[off], cursor.size, false); - off += cursor.size; + off += cursor.size / 4; amdgpu_res_next(&cursor, cursor.size); } emit_content = true; From c748dd03df33360549ad60cdccee13570e9c0f90 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Mon, 31 Aug 2026 20:17:17 +0530 Subject: [PATCH 0645/2366] drm/amdgpu: update the fw version for gfx11 userqueues Update to the latest stable fw versions where userqueues is working as it is expected with major fixes. Signed-off-by: Sunil Khatri Acked-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit d50201b891604ab97f305d4a20d888ba93305b48) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 69776dbe188d..0ff5a80aa918 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -1651,10 +1651,10 @@ static int gfx_v11_0_sw_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(11, 0, 2): case IP_VERSION(11, 0, 3): if (!adev->gfx.disable_uq && - adev->gfx.me_fw_version >= 2420 && - adev->gfx.pfp_fw_version >= 2580 && - adev->gfx.mec_fw_version >= 2650 && - adev->mes.fw_version[0] >= 120) { + adev->gfx.me_fw_version >= 3090 && + adev->gfx.pfp_fw_version >= 3190 && + adev->gfx.mec_fw_version >= 3450 && + adev->mes.fw_version[0] >= 147) { adev->userq_funcs[AMDGPU_HW_IP_GFX] = &userq_mes_funcs; adev->userq_funcs[AMDGPU_HW_IP_COMPUTE] = &userq_mes_funcs; } From 49a74a2388528c1a2e96f01114c4513e635605fe Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Mon, 31 Aug 2026 20:18:31 +0530 Subject: [PATCH 0646/2366] drm/amdgpu: update the fw version for gfx12 userqueues Update to the latest stable fw versions where userqueues is working as it is expected with major fixes. Signed-off-by: Sunil Khatri Acked-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 69fa36e3ac92f2544ee7a1b719ec212b8247a2da) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index 1e5fd1ef8f1d..e2a81a55c63b 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -1436,10 +1436,10 @@ static int gfx_v12_0_sw_init(struct amdgpu_ip_block *ip_block) case IP_VERSION(12, 0, 0): case IP_VERSION(12, 0, 1): if (!adev->gfx.disable_uq && - adev->gfx.me_fw_version >= 2780 && - adev->gfx.pfp_fw_version >= 2840 && - adev->gfx.mec_fw_version >= 3050 && - adev->mes.fw_version[0] >= 123) { + adev->gfx.me_fw_version >= 3090 && + adev->gfx.pfp_fw_version >= 3190 && + adev->gfx.mec_fw_version >= 3450 && + adev->mes.fw_version[0] >= 147) { adev->userq_funcs[AMDGPU_HW_IP_GFX] = &userq_mes_funcs; adev->userq_funcs[AMDGPU_HW_IP_COMPUTE] = &userq_mes_funcs; } From a26301203a196a991527f7b1ab884d4dd0e7c95e Mon Sep 17 00:00:00 2001 From: Kanala Ramalingeswara Reddy Date: Mon, 31 Aug 2026 19:59:11 +0530 Subject: [PATCH 0647/2366] drm/amdgpu: Skip accessing psp rum time db for APUs Psp runtime DB is for dGPUs only. Signed-off-by: Kanala Ramalingeswara Reddy Acked-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit dce8195027f146467c9378efb2bb1b0859cb735e) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c index 04f6ebf31cca..42adc8e738d8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_psp.c @@ -396,6 +396,12 @@ static bool psp_get_runtime_db_entry(struct amdgpu_device *adev, bool ret = false; int i; + /* + * Runtime DB is for dGPUs only. + */ + if (adev->flags & AMD_IS_APU) + return false; + if (amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 6) || amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 12) || amdgpu_ip_version(adev, MP0_HWIP, 0) == IP_VERSION(13, 0, 14) || From 8b4a4193f3c0b532054990783f40930083c37618 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Tue, 1 Sep 2026 16:30:35 +0530 Subject: [PATCH 0648/2366] drm/amdgpu/userq: dont overwrite the error of subsequent map call If a queue fails to map that we need to return the error code back to the caller and not overwrite with a success specifically. Accumulate the failure and return that. Signed-off-by: Sunil Khatri Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 42a0197d10039e9518c0324c43331eb22b44d5f8) --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 0a816b3c5ff9..e43bda0cab3f 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -1544,7 +1544,7 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost) struct amdgpu_usermode_queue *queue; const struct amdgpu_userq_funcs *userq_funcs; unsigned long queue_id; - int r = 0; + int ret = 0, r; xa_for_each(&adev->userq_doorbell_xa, queue_id, queue) { if (queue->state == AMDGPU_USERQ_STATE_HUNG && !vram_lost) { @@ -1555,6 +1555,7 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost) r = userq_funcs->map(queue); if (r) { dev_err(adev->dev, "Failed to remap queue %ld\n", queue_id); + ret = r; continue; } trace_amdgpu_userq_state_changed(queue, AMDGPU_USERQ_STATE_MAPPED); @@ -1562,5 +1563,5 @@ int amdgpu_userq_post_reset(struct amdgpu_device *adev, bool vram_lost) } } - return r; + return ret; } From 6293f2e1439fb440be1b606e647ce88e65562a38 Mon Sep 17 00:00:00 2001 From: Yuling Li Date: Tue, 1 Sep 2026 15:37:18 +0800 Subject: [PATCH 0649/2366] drm/amd/display: Fix cursor disable with horizontally split planes [WHY] resource_can_pipe_disable_cursor() disables the hardware cursor on a pipe when a higher layer fully covers that pipe's recout, to avoid double-cursor and scaling artifacts. When merging pipe-split halves of the same overlay layer, the inner loop walks every pipe above the current one and looks for siblings sharing test_pipe's layer_index. Because test_pipe itself satisfies that condition, it can be treated as its own split partner. That incorrectly doubles r2.width and makes the covering check succeed even when the overlay does not fully contain the underlying pipe. On horizontally split or multi-quadrant layouts this causes the cursor to disappear over overlay regions while input/coordinate mapping remains correct. [HOW] Skip test_pipe when searching for a pipe-split sibling on the same layer, so only the other half of the split plane is merged into r2. Signed-off-by: Yuling Li Reviewed-by: Leo Li Signed-off-by: Alex Deucher (cherry picked from commit 85ccd2c39cca9351d4db393e24acea8bf943d350) --- drivers/gpu/drm/amd/display/dc/core/dc_resource.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c index 7eaaf38cd9ab..fc9080f0c093 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c @@ -1797,7 +1797,11 @@ bool resource_can_pipe_disable_cursor(struct pipe_ctx *pipe_ctx) * pipe-split, merge together per same height. */ for (split_pipe = pipe_ctx->top_pipe; split_pipe; - split_pipe = split_pipe->top_pipe) + split_pipe = split_pipe->top_pipe) { + + if (split_pipe == test_pipe) + continue; + if (split_pipe->plane_state->layer_index == test_pipe->plane_state->layer_index) { struct rect r2_half; @@ -1809,6 +1813,7 @@ bool resource_can_pipe_disable_cursor(struct pipe_ctx *pipe_ctx) r2_bottom = min(r2_bottom, r2_half.y + r2_half.height); break; } + } if (r1.x >= r2.x && r1.y >= r2.y && r1_right <= r2_right && r1_bottom <= r2_bottom) return true; From 13af55f71399f5e562f6cb59ad413476e513c4d4 Mon Sep 17 00:00:00 2001 From: Yogesh Mohan Marimuthu Date: Thu, 20 Aug 2026 09:52:50 +0530 Subject: [PATCH 0650/2366] drm/amdgpu/userq: fix struct drm_amdgpu_info_device padding for 32bit compile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit need to pad before __u64 tcc_disabled_mask variable. This patch fixes 64bit Kernel + 32 bit mesa combination. But at the same time it will break 32bit Kernel(using this patch) + older 32bit mesa(not using this patch). This issue was discussd with alexander.deucher@amd.com, christian.koenig@amd.com and pierre-eric.pelloux-prayer@amd.com. Currently today 32 bit kernel + 32 bit userspace and 64 bit kernel and 64 bit userspace work. Mixed 64 bit kernel and 32 bit userspace is currently broken. Since 32 bit kernel and userspace is probably pretty rare these days and the data affected by this is not critical, Hence we can go ahead with this patch. Fixes: cf21e76a6005 ("drm/amdgpu: return tcc_disabled_mask to userspace") Signed-off-by: Yogesh Mohan Marimuthu Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 497b5090f2857ef8ad9a162aa31ada0de5814663) --- include/uapi/drm/amdgpu_drm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/uapi/drm/amdgpu_drm.h b/include/uapi/drm/amdgpu_drm.h index b32c72a662b6..42a5fa8ad6b0 100644 --- a/include/uapi/drm/amdgpu_drm.h +++ b/include/uapi/drm/amdgpu_drm.h @@ -1512,6 +1512,7 @@ struct drm_amdgpu_info_device { __u64 high_va_max; /* gfx10 pa_sc_tile_steering_override */ __u32 pa_sc_tile_steering_override; + __u32 pad; /* disabled TCCs */ __u64 tcc_disabled_mask; __u64 min_engine_clock; @@ -1536,7 +1537,6 @@ struct drm_amdgpu_info_device { __u32 csa_alignment; /* Userq IP mask (1 << AMDGPU_HW_IP_*) */ __u32 userq_ip_mask; - __u32 pad; }; struct drm_amdgpu_info_hw_ip { From 5be5bdda5863eacc964b609ba927764f253431b3 Mon Sep 17 00:00:00 2001 From: Diego Oliva Date: Wed, 2 Sep 2026 11:42:07 +0100 Subject: [PATCH 0651/2366] smb: client: reject out-of-bounds DataOffset in CIFSSMBRead() The SMB1 synchronous read helper CIFSSMBRead() validates the server's DataLength against CIFSMaxBufSize and the caller's count, but never validates DataOffset. The copy source is formed as &pSMBr->hdr.Protocol + le16_to_cpu(pSMBr->DataOffset) and memcpy()'d for DataLength bytes with no check that the [DataOffset, DataOffset + DataLength) range lies within the response actually received from the server. A malicious or compromised SMB1 server can return a response carrying an in-range DataLength and a large DataOffset, driving the source pointer past the end of the response buffer. The memcpy() then copies adjacent kernel heap into the caller's read buffer (information disclosure), or reads unmapped memory and oopses (denial of service). SMB1 is not negotiated by default; reaching this code requires an explicit vers=1.0 mount. Both DataOffset and the received response length recorded in rsp_iov.iov_len are relative to the start of the SMB header, so reject the response unless DataOffset + DataLength fits within that length, using overflow-safe arithmetic, before forming the source pointer. The response length has been validated by the previous patch, so the DataOffset and DataLength fields can be read safely here. While here, make data_length unsigned. It holds a length derived from unsigned on-the-wire fields and is only ever compared against unsigned quantities; print it with %u accordingly, and add __func__ to the cifs_dbg() calls in this function. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org # 6.19.x Assisted-by: Bynario AI Signed-off-by: Diego Oliva Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 18 +++++++++++++----- fs/smb/client/trace.h | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index be13ab37039d..f9aff0712794 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -1728,7 +1728,8 @@ CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms, rsp_iov.iov_len, tcon->ses->server->vals->read_rsp_size); *nbytes = 0; } else { - int data_length = le16_to_cpu(pSMBr->DataLengthHigh); + unsigned int data_length = le16_to_cpu(pSMBr->DataLengthHigh); + __u16 data_offset = le16_to_cpu(pSMBr->DataOffset); data_length = data_length << 16; data_length += le16_to_cpu(pSMBr->DataLength); *nbytes = data_length; @@ -1736,14 +1737,21 @@ CIFSSMBRead(const unsigned int xid, struct cifs_io_parms *io_parms, /*check that DataLength would not go beyond end of SMB */ if ((data_length > CIFSMaxBufSize) || (data_length > count)) { - cifs_dbg(FYI, "bad length %d for count %d\n", - data_length, count); + cifs_dbg(FYI, "%s: bad length %u for count %u\n", + __func__, data_length, count); rc = smb_EIO2(smb_eio_trace_read_overlarge, data_length, count); *nbytes = 0; + } else if (data_offset < sizeof(*pSMBr) || + (size_t)data_offset + data_length > rsp_iov.iov_len) { + /* check that the data lies within the received response */ + cifs_dbg(FYI, "%s: bad data offset %u length %u for response of %zu\n", + __func__, data_offset, data_length, rsp_iov.iov_len); + rc = smb_EIO2(smb_eio_trace_read_bad_offset, + data_offset, data_length); + *nbytes = 0; } else { - pReadData = (char *) (&pSMBr->hdr.Protocol) + - le16_to_cpu(pSMBr->DataOffset); + pReadData = (char *) (&pSMBr->hdr.Protocol) + data_offset; /* if (rc = copy_to_user(buf, pReadData, data_length)) { cifs_dbg(VFS, "Faulting on read rc = %d\n",rc); rc = -EFAULT; diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index 12241abb8e2e..b442cccd1530 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -79,6 +79,7 @@ EM(smb_eio_trace_qreparse_setup_count, "qreparse_setup_count") \ EM(smb_eio_trace_qreparse_sizes_wrong, "qreparse_sizes_wrong") \ EM(smb_eio_trace_qsym_bcc_too_small, "qsym_bcc_too_small") \ + EM(smb_eio_trace_read_bad_offset, "read_bad_offset") \ EM(smb_eio_trace_read_mid_state_unknown, "read_mid_state_unknown") \ EM(smb_eio_trace_read_overlarge, "read_overlarge") \ EM(smb_eio_trace_read_rsp_malformed, "read_rsp_malformed") \ From d9d7eeb0cea5b55b82888f443622fd8d4ee064f3 Mon Sep 17 00:00:00 2001 From: Aohan Mei Date: Wed, 2 Sep 2026 20:52:13 +0800 Subject: [PATCH 0652/2366] smb: client: reject userspace cifs.idmap descriptions cifs.idmap key descriptions carry authority-bearing fields (owner and group SIDs and uid/gid values in "os:"/"gs:"/"oi:"/"gi:" form) that the cifs.idmap upcall helper treats as kernel-originating inputs. Unlike its sibling cifs.spnego, the cifs.idmap key type has no vet_description hook, so userspace can create keys of this type through request_key(2)/add_key(2) and supply those fields without CIFS origin. A request_key(2) call with a non-NULL callout then drives a root usermodehelper upcall (/sbin/request-key -> cifs.idmap) that consumes the unvetted description in root context. Only accept cifs.idmap descriptions while CIFS is using its private root_cred to request the key. id_to_sid()/sid_to_id() already run under override_creds(root_cred), so the kernel-originated path is unaffected. This mirrors commit 3da1fdf4efbc ("smb: client: reject userspace cifs.spnego descriptions"), which applied the same restriction to cifs.spnego. Fixes: 4d79dba0e007 ("cifs: Add idmap key and related data structures and functions (try #17 repost)") Reported-by: TencentOS Corvus AI Cc: stable@vger.kernel.org Assisted-by: CodeBuddy:Kimi-K3 Signed-off-by: Aohan Mei Acked-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsacl.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index 12005f46307d..213a421bf8e9 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -100,8 +100,23 @@ cifs_idmap_key_destroy(struct key *key) kfree(key->payload.data[0]); } +static int +cifs_idmap_key_vet_description(const char *description) +{ + /* + * cifs.idmap descriptions are authority-bearing inputs to the + * cifs.idmap upcall helper. Only allow the kernel to create this + * type of key using the private root_cred installed in + * init_cifs_idmap; reject userspace request_key(2)/add_key(2). + */ + if (current_cred() != root_cred) + return -EPERM; + return 0; +} + static struct key_type cifs_idmap_key_type = { .name = "cifs.idmap", + .vet_description = cifs_idmap_key_vet_description, .instantiate = cifs_idmap_key_instantiate, .destroy = cifs_idmap_key_destroy, .describe = user_describe, From d806d5a85dcbe2a0f181b2f0f9f61ddfbefa1818 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 2 Sep 2026 20:28:14 +0200 Subject: [PATCH 0653/2366] smb: client: pin DFS superblock in iterator callback tcon_super_cb() stores a raw superblock pointer, but __cifs_get_super() takes its active reference only after iterate_supers_type() has dropped s_umount and its passive reference. Concurrent DFS automount expiry can therefore free the superblock before cifs_sb_active() uses it. A deterministic KASAN test reproduces the race as: BUG: KASAN: slab-use-after-free in cifs_sb_active+0x77/0x80 The same test passes with this change applied. Take the active reference in the callback while iterate_supers_type() still holds s_umount shared. cifs_put_tcp_super() remains the matching release. Fixes: bacd704a95ad ("cifs: handle prefix paths in reconnect") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Paulo Alcantara --- fs/smb/client/misc.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index 46e1382e8e04..d4db3f91a91f 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -891,8 +891,14 @@ static void tcon_super_cb(struct super_block *sb, void *arg) t1->ses->dfs_root_ses == t2->ses->dfs_root_ses) && t1->ses->server == t2->ses->server && t2->origin_fullpath && - dfs_src_pathname_equal(t2->origin_fullpath, t1->origin_fullpath)) + dfs_src_pathname_equal(t2->origin_fullpath, t1->origin_fullpath)) { + /* + * Take the active reference while iterate_supers_type() still + * holds s_umount shared. + */ + cifs_sb_active(sb); sd->sb = sb; + } spin_unlock(&t2->tc_lock); } @@ -909,15 +915,8 @@ static struct super_block *__cifs_get_super(void (*f)(struct super_block *, void for (; *fs_type; fs_type++) { iterate_supers_type(*fs_type, f, &sd); - if (sd.sb) { - /* - * Grab an active reference in order to prevent automounts (DFS links) - * of expiring and then freeing up our cifs superblock pointer while - * we're doing failover. - */ - cifs_sb_active(sd.sb); + if (sd.sb) return sd.sb; - } } pr_warn_once("%s: could not find dfs superblock\n", __func__); return ERR_PTR(-EINVAL); From 74cb39735b6cd0aff4b5584158f09376fd97aadf Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Mon, 10 Aug 2026 15:10:34 -0700 Subject: [PATCH 0654/2366] ipvs: reject invalid states in connection template sync records IPVS sync receivers validate protocol states before creating or updating a connection. For connection templates, however, they only log states outside the template state range and still store the value in the connection. A template can be returned by ordinary connection lookup. TCP and SCTP then use the invalid state as an index into their transition tables. Reject invalid template states in both sync protocol versions before looking up or modifying a connection. The version 1 path handles both IPv4 and IPv6 records. Fixes: 275411430f89 ("ipvs: add assured state for conn templates") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Kyle Zeng Acked-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_sync.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c index ea5fdd4f4ce7..1deb063cd72c 100644 --- a/net/netfilter/ipvs/ip_vs_sync.c +++ b/net/netfilter/ipvs/ip_vs_sync.c @@ -999,10 +999,10 @@ static void ip_vs_process_message_v0(struct netns_ipvs *ipvs, const char *buffer pp->name, state); continue; } - } else { - if (state >= IP_VS_CTPL_S_LAST) - IP_VS_DBG(7, "BACKUP v0, Invalid tpl state %u\n", - state); + } else if (state >= IP_VS_CTPL_S_LAST) { + IP_VS_DBG(7, "BACKUP v0, Invalid tpl state %u\n", + state); + continue; } ip_vs_conn_fill_param(ipvs, AF_INET, s->protocol, @@ -1159,10 +1159,10 @@ static inline int ip_vs_proc_sync_conn(struct netns_ipvs *ipvs, __u8 *p, __u8 *m retc = 40; goto out; } - } else { - if (state >= IP_VS_CTPL_S_LAST) - IP_VS_DBG(7, "BACKUP, Invalid tpl state %u\n", - state); + } else if (state >= IP_VS_CTPL_S_LAST) { + IP_VS_DBG(7, "BACKUP, Invalid tpl state %u\n", state); + retc = 40; + goto out; } if (ip_vs_conn_fill_param_sync(ipvs, af, s, ¶m, pe_data, pe_data_len, pe_name, pe_name_len)) { From b04578b74f2d3755548fe9e829e3b2a6c6f966a1 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Mon, 10 Aug 2026 15:13:47 -0700 Subject: [PATCH 0655/2366] ipvs: fix reversed sequence option serialization hton_seq() expects the host-order source first and the unaligned network-order destination second. The version 1 sync sender passes these arguments in reverse for both sequence blocks. This leaves 24 bytes of the kmalloc-backed message unwritten. It may disclose stale heap data and replace the live connection sequence state with values read from the buffer. Pass the connection sequence state as the source and the message payload as the destination for both blocks. Fixes: 986a07579533 ("IPVS: Backup, Change sending to Version 1 format") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Kyle Zeng Acked-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_sync.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c index 1deb063cd72c..5383aeafb0ae 100644 --- a/net/netfilter/ipvs/ip_vs_sync.c +++ b/net/netfilter/ipvs/ip_vs_sync.c @@ -747,9 +747,9 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts) if (cp->flags & IP_VS_CONN_F_SEQ_MASK) { *(p++) = IPVS_OPT_SEQ_DATA; *(p++) = sizeof(struct ip_vs_sync_conn_options); - hton_seq((struct ip_vs_seq *)p, &cp->in_seq); + hton_seq(&cp->in_seq, (struct ip_vs_seq *)p); p += sizeof(struct ip_vs_seq); - hton_seq((struct ip_vs_seq *)p, &cp->out_seq); + hton_seq(&cp->out_seq, (struct ip_vs_seq *)p); p += sizeof(struct ip_vs_seq); } /* Handle pe data */ From e8f8231824b5815f57ce62cba116e511b10196de Mon Sep 17 00:00:00 2001 From: Joas Antonio dos Santos Date: Tue, 18 Aug 2026 06:31:43 -0700 Subject: [PATCH 0656/2366] netfilter: nf_conntrack_sip: fix OOB read in sip_skip_whitespace() sip_skip_whitespace() returns dptr unchanged when its own loop exhausts the buffer (dptr == limit), instead of NULL like its sibling sip_follow_continuation() returns on its own "no more data" path. ct_sip_get_header() only checks for NULL after calling it: dptr = sip_skip_whitespace(dptr, limit); if (dptr == NULL) break; if (*dptr != ':' || ++dptr >= limit) break; so a recognized header name followed only by spaces/tabs running to the exact end of the SIP payload, with no colon, makes the very next statement read one byte past the buffer. Make both "no more data" outcomes return NULL, matching the convention sip_follow_continuation() already uses and that both existing callers already check for. Fixes: ea45f12a2766d ("[NETFILTER]: nf_conntrack_sip: parse SIP headers properly") Signed-off-by: Joas Antonio dos Santos Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conntrack_sip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index 3ccf34fc1c53..64bc440b1181 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c @@ -423,7 +423,7 @@ static const char *sip_skip_whitespace(const char *dptr, const char *limit) dptr = sip_follow_continuation(dptr, limit); break; } - return dptr; + return dptr < limit ? dptr : NULL; } /* Search within a SIP header value, dealing with continuation lines */ From fec9b1de0d02de8dafa3cc344bcb91cf28660643 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 24 Aug 2026 20:12:38 +0800 Subject: [PATCH 0657/2366] netfilter: cttimeout: prevent UAF during module unload nf_ct_set_timeout() protects the timeout hook dereference and policy lookup with rcu_read_lock(). cttimeout_exit(), however, unregisters the per-net operations before it clears the hook. This allows the following interleaving: CPU 0 CPU 1 cttimeout_exit() nf_ct_set_timeout() unregister_pernet_subsys() rcu_read_lock() kfree(pernet) h = nf_ct_timeout_hook h->timeout_find_get() nfct_timeout_pernet() The hook still points to ctnl_timeout_find_get() when CPU 1 looks up the already freed per-net timeout list. KASAN reported: BUG: KASAN: slab-use-after-free in ctnl_timeout_find_get Read of size 8 by task poc/90 Call Trace: ctnl_timeout_find_get+0x271/0x2a0 [nfnetlink_cttimeout] nf_ct_set_timeout+0x7b/0x3c0 xt_ct_tg_check+0x724/0xb20 xt_check_target+0x234/0xa90 do_ipt_set_ctl+0x570/0x1270 Allocated by task 89: __kmalloc_noprof+0x16e/0x460 ops_init+0x6d/0x420 register_pernet_operations+0x2f6/0x670 Freed by task 91: kfree+0x131/0x390 ops_undo_list+0x3d4/0x730 unregister_pernet_operations+0x232/0x490 unregister_pernet_subsys+0x1c/0x30 cttimeout_exit+0x52/0x970 [nfnetlink_cttimeout] Clear the hook and wait for existing readers before unregistering the per-net operations. This blocks new policy lookups and ensures readers that observed the hook finish before the per-net storage is freed. Fixes: ebfbe67568a7 ("netfilter: cttimeout: use net_generic infra") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nfnetlink_cttimeout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/nfnetlink_cttimeout.c b/net/netfilter/nfnetlink_cttimeout.c index 66c2016f6049..132c02ac7c4e 100644 --- a/net/netfilter/nfnetlink_cttimeout.c +++ b/net/netfilter/nfnetlink_cttimeout.c @@ -652,9 +652,9 @@ static void __exit cttimeout_exit(void) { nfnetlink_subsys_unregister(&cttimeout_subsys); - unregister_pernet_subsys(&cttimeout_ops); RCU_INIT_POINTER(nf_ct_timeout_hook, NULL); synchronize_net(); + unregister_pernet_subsys(&cttimeout_ops); } module_init(cttimeout_init); From 2c018cc4842c33f0c732962e2ab58635e8ae5823 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 24 Aug 2026 01:05:38 +0800 Subject: [PATCH 0658/2366] netfilter: nf_log: unregister loggers before per-net teardown nf_log_syslog and nfnetlink_log unregister their per-network namespace operations before unregistering their global logger backends. This leaves a window where a sysctl or netlink writer can rebind the still- registered logger after the per-net pre-exit callback cleared the old selection. The race looks like this: CPU 0 CPU 1 ---- ---- unregister_pernet_subsys() nf_log_unset(net, logger) net->nf.nf_loggers[pf] = NULL lock nf_log_mutex find logger in loggers[][] net->nf.nf_loggers[pf] = logger unlock nf_log_mutex nf_log_unregister(logger) lock nf_log_mutex loggers[pf][type] = NULL unlock nf_log_mutex synchronize_rcu() module exit returns module core frees backend memory Later, a sysctl read or packet logging operation can dereference the stale per-net logger pointer. Fix this by unregistering the global logger backends before tearing down per-net state. Once the global registrations are gone, later writers can no longer rebind the logger. unregister_pernet_subsys() already waits for an RCU grace period after the pre-exit callback clears the per-net selection, while nf_log_unregister() continues to cover readers of the global logger table. Apply this ordering fix to both nf_log backends that combine per-net teardown with global logger registration. Fixes: 5b023fc8d8e0 ("netfilter: enable per netns support for nf_loggers") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_log_syslog.c | 2 +- net/netfilter/nfnetlink_log.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_log_syslog.c b/net/netfilter/nf_log_syslog.c index f24288088c0d..c3fd398ffcd7 100644 --- a/net/netfilter/nf_log_syslog.c +++ b/net/netfilter/nf_log_syslog.c @@ -1073,12 +1073,12 @@ static int __init nf_log_syslog_init(void) static void __exit nf_log_syslog_exit(void) { - unregister_pernet_subsys(&nf_log_syslog_net_ops); nf_log_unregister(&nf_ip_logger); nf_log_unregister(&nf_arp_logger); nf_log_unregister(&nf_ip6_logger); nf_log_unregister(&nf_netdev_logger); nf_log_unregister(&nf_bridge_logger); + unregister_pernet_subsys(&nf_log_syslog_net_ops); } module_init(nf_log_syslog_init); diff --git a/net/netfilter/nfnetlink_log.c b/net/netfilter/nfnetlink_log.c index 6c7fa2ed34f5..9d7fec570abe 100644 --- a/net/netfilter/nfnetlink_log.c +++ b/net/netfilter/nfnetlink_log.c @@ -1233,8 +1233,8 @@ static void __exit nfnetlink_log_fini(void) { nfnetlink_subsys_unregister(&nfulnl_subsys); netlink_unregister_notifier(&nfulnl_rtnl_notifier); - unregister_pernet_subsys(&nfnl_log_net_ops); nf_log_unregister(&nfulnl_logger); + unregister_pernet_subsys(&nfnl_log_net_ops); } MODULE_DESCRIPTION("netfilter userspace logging"); From f36d94a20ca185bcadef3a10b980cd2cfd72d53a Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Tue, 1 Sep 2026 23:21:37 +0900 Subject: [PATCH 0659/2366] tracing/probes: Fix anon_stack check for unnamed bitfields in btf_find_struct_member btf_find_struct_member() traverses into nested anonymous structures and unions by pushing members with !member->name_off onto anon_stack. However, it does not consider the unnamed bitfields (e.g. `int : 5` or `unsigned int : 0`) which also have member->name_off == 0. If such an unnamed bitfield is pushed to anon_stack, the btf_find_struct_member() return an error even if there are other valid entries in anon_stack. To fix this, only push unnamed struct/union members to anon_stack. Also move the btf_type_is_struct() check to the entry of this function because now it is sure only struct/union are pushed to anon_stack. Link: https://lore.kernel.org/all/178827249775.123716.7813217688423513612.stgit@devnote2/ Fixes: 302db0f5b3d8 ("tracing/probes: Add a function to search a member of a struct/union") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260830143859.D56991F00A3D@smtp.kernel.org/ Signed-off-by: Masami Hiramatsu (Google) Reviewed-by: Steven Rostedt --- kernel/trace/trace_btf.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kernel/trace/trace_btf.c b/kernel/trace/trace_btf.c index 00172f301f25..d3ba356d5503 100644 --- a/kernel/trace/trace_btf.c +++ b/kernel/trace/trace_btf.c @@ -74,24 +74,24 @@ const struct btf_member *btf_find_struct_member(struct btf *btf, { struct btf_anon_stack *anon_stack; const struct btf_member *member; + const struct btf_type *mtype; u32 tid, cur_offset = 0; const char *name; int i, top = 0; + if (!btf_type_is_struct(type)) + return ERR_PTR(-EINVAL); + anon_stack = kzalloc_objs(*anon_stack, BTF_ANON_STACK_MAX); if (!anon_stack) return ERR_PTR(-ENOMEM); retry: - if (!btf_type_is_struct(type)) { - member = ERR_PTR(-EINVAL); - goto out; - } - for_each_member(i, type, member) { if (!member->name_off) { /* Anonymous union/struct: push it for later use */ - if (btf_type_skip_modifiers(btf, member->type, &tid) && + mtype = btf_type_skip_modifiers(btf, member->type, &tid); + if (mtype && btf_type_is_struct(mtype) && top < BTF_ANON_STACK_MAX) { anon_stack[top].tid = tid; anon_stack[top++].offset = From 47e93045a2db80d24f5fef65adecc6b2b32efa23 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Tue, 1 Sep 2026 23:21:49 +0900 Subject: [PATCH 0660/2366] tracing/probes: Fix BTF kflag check for anonymous struct member access btf_find_struct_member() traverses into nested anonymous structures and unions to find a struct member. However, get_bitoffset_of_field() in trace_probe.c checked btf_type_kflag(type) using the outer parent type instead of the actual anonymous structure/union that directly contains the found member. If the parent structure and anonymous structure have mismatched kflags (e.g., the parent has kflag=0 while the anonymous structure has kflag=1 because it contains bitfields), the bitfield size encoded in the upper 8 bits of member->offset is erroneously treated as part of the byte/bit offset, corrupting the resolved offset and failing to set last_bitsize. Similarly, btf_find_struct_member() pushed anonymous member offsets onto anon_stack without masking BTF_MEMBER_BIT_OFFSET() when kflag is set. To fix this problem, update btf_find_struct_member() to return actual containing structure/union type via member_type, use appropriate __btf_member_bit_offset() to get bit offset, and use member_type for btf_type_kflag() in get_bitoffset_of_field(). Link: https://lore.kernel.org/all/178827250904.123716.17452648791331881284.stgit@devnote2/ Fixes: c440adfbe302 ("tracing/probes: Support BTF based data structure field access") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260822095110.0772E1F000E9@smtp.kernel.org/ Assisted-by: Antigravity:gemini-3.7-flash Signed-off-by: Masami Hiramatsu (Google) Reviewed-by: Steven Rostedt --- kernel/trace/trace_btf.c | 19 +++++++++++-------- kernel/trace/trace_btf.h | 3 ++- kernel/trace/trace_probe.c | 5 +++-- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/kernel/trace/trace_btf.c b/kernel/trace/trace_btf.c index d3ba356d5503..ee7a04886bf6 100644 --- a/kernel/trace/trace_btf.c +++ b/kernel/trace/trace_btf.c @@ -61,16 +61,17 @@ struct btf_anon_stack { /* * Find a member of data structure/union by name and return it. - * Return NULL if not found, or -EINVAL if parameter is invalid. - * If the member is an member of anonymous union/structure, the offset - * of that anonymous union/structure is stored into @anon_offset. Caller - * can calculate the correct offset from the root data structure by - * adding anon_offset to the member's offset. + * Return NULL if not found, or ERR_PTR(-EINVAL) if parameter is invalid. + * If the member is a member of an anonymous union/structure, the bit offset + * of that anonymous union/structure is stored into @anon_offset. + * If @member_type is non-NULL, the actual containing structure/union type + * of the found member is stored into @member_type. */ const struct btf_member *btf_find_struct_member(struct btf *btf, const struct btf_type *type, const char *member_name, - u32 *anon_offset) + u32 *anon_offset, + const struct btf_type **member_type) { struct btf_anon_stack *anon_stack; const struct btf_member *member; @@ -94,14 +95,16 @@ const struct btf_member *btf_find_struct_member(struct btf *btf, if (mtype && btf_type_is_struct(mtype) && top < BTF_ANON_STACK_MAX) { anon_stack[top].tid = tid; - anon_stack[top++].offset = - cur_offset + member->offset; + anon_stack[top++].offset = cur_offset + + __btf_member_bit_offset(type, member); } } else { name = btf_name_by_offset(btf, member->name_off); if (name && !strcmp(member_name, name)) { if (anon_offset) *anon_offset = cur_offset; + if (member_type) + *member_type = type; goto out; } } diff --git a/kernel/trace/trace_btf.h b/kernel/trace/trace_btf.h index 4bc44bc261e6..4bd26bceae23 100644 --- a/kernel/trace/trace_btf.h +++ b/kernel/trace/trace_btf.h @@ -8,4 +8,5 @@ const struct btf_param *btf_get_func_param(const struct btf_type *func_proto, const struct btf_member *btf_find_struct_member(struct btf *btf, const struct btf_type *type, const char *member_name, - u32 *anon_offset); + u32 *anon_offset, + const struct btf_type **member_type); diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index c4163904ba74..144e790077c6 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -625,6 +625,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty { const struct btf_type *type = *ptype; const struct btf_member *field; + const struct btf_type *mtype; struct btf *btf = ctx_btf(ctx); char *fieldname = *pfieldname; int bitoffs = 0; @@ -640,7 +641,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty anon_offs = 0; field = btf_find_struct_member(btf, type, fieldname, - &anon_offs); + &anon_offs, &mtype); if (IS_ERR(field)) { trace_probe_log_err(ctx->offset, BAD_BTF_TID); return PTR_ERR(field); @@ -653,7 +654,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty bitoffs += anon_offs; /* Accumulate the bit-offsets of the dot-connected fields */ - if (btf_type_kflag(type)) { + if (btf_type_kflag(mtype)) { bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset); ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset); } else { From 871e07b6e3841cc9a258572c9ce8d1ea65f6ce7b Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Tue, 1 Sep 2026 23:22:00 +0900 Subject: [PATCH 0661/2366] tracing/probes: Fix code indent in get_bitoffset_of_field() Fix code block indentation introduced by commit f21834524025 ("tracing/probes: Support field specifier option for typecast"). Link: https://lore.kernel.org/all/178827252027.123716.7095571176291547259.stgit@devnote2/ Signed-off-by: Masami Hiramatsu (Google) Reviewed-by: Steven Rostedt --- kernel/trace/trace_probe.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 144e790077c6..908b4b6bc2df 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -662,11 +662,11 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty ctx->last_bitsize = 0; } - type = btf_type_skip_modifiers(btf, field->type, NULL); - if (!type) { - trace_probe_log_err(ctx->offset, BAD_BTF_TID); - return -EINVAL; - } + type = btf_type_skip_modifiers(btf, field->type, NULL); + if (!type) { + trace_probe_log_err(ctx->offset, BAD_BTF_TID); + return -EINVAL; + } if (next) ctx->offset += next - fieldname; From 86b7a239ec6b14a7544200ede85474c6f5526049 Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Wed, 26 Aug 2026 11:00:09 +0800 Subject: [PATCH 0662/2366] tracing/probes: Fix use-after-free on field name/type of events with multiple probes The fields of a probe-based dynamic event (kprobe, uprobe, eprobe and fprobe events) are created in traceprobe_define_arg_fields() by handing the probe_arg name/type strings to trace_define_field(), which only stores the pointers without copying. Those strings are owned by the trace_probe and are freed when that probe is removed. An event can have several probes attached. The field list is defined only once, by the first probe that registers the event, but it is kept alive by any surviving sibling probe. Deleting just that first probe by symbol - # primary A: fields are defined from A's args echo 'p:kprobes/ev vfs_read a1=$arg1' > kprobe_events # append B: shares A's event call echo 'p:kprobes/ev vfs_write a1=$arg1' >> kprobe_events # delete only A (matched by symbol), B survives echo '-:kprobes/ev vfs_read' >> kprobe_events frees A's args (trace_probe_cleanup() -> traceprobe_free_probe_arg()), but trace_probe_unlink() keeps the trace_probe_event because the probe list is not empty. The event call stays registered via B while its fields now reference freed memory. Any field lookup then reads it, e.g. echo 'a1 == 1' > events/kprobes/ev/filter BUG: KASAN: slab-use-after-free in strcmp+0xa7/0xb0 Call Trace: strcmp trace_find_event_field parse_pred process_preds create_filter apply_event_filter event_filter_write field->name references parg->name (kstrdup'd, freed with the probe) and, for array arguments, field->type references parg->fmt (kmalloc'd, freed with the probe) - the scalar type otherwise points at the static fmttype rodata, which is safe. Have traceprobe_define_arg_fields() duplicate the name and type strings and anchor the copies on the trace_probe_event, which embeds the event call and outlives every individual probe; trace_probe_event_free() releases them. The reproducer above triggers reliably; the field lookup and the delete both run under event_mutex, so this is a dangling reference after removal rather than a race. The issue was found by the autokbug dynamic kernel fuzzer at Tencent Yunding Lab. Link: https://lore.kernel.org/all/20260826030009.1855331-1-bsdhenrymartin@gmail.com/ Fixes: ca89bc071d5e4 ("tracing/kprobe: Add multi-probe per event support") Signed-off-by: Henry Martin Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/trace_probe.c | 48 +++++++++++++++++++++++++++++++++++++- kernel/trace/trace_probe.h | 2 ++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 908b4b6bc2df..804442b2f7d2 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -2553,19 +2553,60 @@ int traceprobe_set_print_fmt(struct trace_probe *tp, enum probe_print_type ptype int traceprobe_define_arg_fields(struct trace_event_call *event_call, size_t offset, struct trace_probe *tp) { + struct trace_probe_event *tpe = trace_probe_event_from_call(event_call); int ret, i; + /* + * A field created by trace_define_field() only stores the name and + * type pointers, it does not copy the strings. Here they point into + * the probe_arg of @tp, which is freed when @tp is removed. For an + * event with multiple probes attached, the field list is defined + * once by the first probe but kept alive by the surviving siblings, + * so removing that first probe would leave the fields referencing + * freed memory. Duplicate the strings and anchor the copies on the + * trace_probe_event, which lives as long as the field list itself. + * + * event_define_fields() ignores the return value of this hook, so + * if a previous attempt failed before creating any field, it may + * call here again. Release duplicates left behind by such an + * attempt before starting over. + */ + for (i = 0; i < tpe->nr_field_strings; i++) + kfree(tpe->field_strings[i]); + kfree(tpe->field_strings); + tpe->field_strings = NULL; + tpe->nr_field_strings = 0; + + if (tp->nr_args) { + tpe->field_strings = kcalloc(tp->nr_args * 2, sizeof(char *), + GFP_KERNEL); + if (!tpe->field_strings) + return -ENOMEM; + } + /* Set argument names as fields */ for (i = 0; i < tp->nr_args; i++) { struct probe_arg *parg = &tp->args[i]; const char *fmt = parg->type->fmttype; int size = parg->type->size; + char *name, *type; if (parg->fmt) fmt = parg->fmt; if (parg->count) size *= parg->count; - ret = trace_define_field(event_call, fmt, parg->name, + + name = kstrdup(parg->name, GFP_KERNEL); + type = kstrdup(fmt, GFP_KERNEL); + if (!name || !type) { + kfree(name); + kfree(type); + return -ENOMEM; + } + tpe->field_strings[tpe->nr_field_strings++] = name; + tpe->field_strings[tpe->nr_field_strings++] = type; + + ret = trace_define_field(event_call, type, name, offset + parg->offset, size, parg->type->is_signed, FILTER_OTHER); @@ -2577,6 +2618,11 @@ int traceprobe_define_arg_fields(struct trace_event_call *event_call, static void trace_probe_event_free(struct trace_probe_event *tpe) { + int i; + + for (i = 0; i < tpe->nr_field_strings; i++) + kfree(tpe->field_strings[i]); + kfree(tpe->field_strings); kfree(tpe->class.system); kfree(tpe->call.name); kfree(tpe->call.print_fmt); diff --git a/kernel/trace/trace_probe.h b/kernel/trace/trace_probe.h index fba1af092a9b..d1fb3520700f 100644 --- a/kernel/trace/trace_probe.h +++ b/kernel/trace/trace_probe.h @@ -264,6 +264,8 @@ struct trace_probe_event { struct trace_event_call call; struct list_head files; struct list_head probes; + char **field_strings; + int nr_field_strings; struct trace_uprobe_filter filter[]; }; From 0c4256196b3a105307e2235fbfd85e768bbcdd0f Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Sun, 30 Aug 2026 23:27:23 +0900 Subject: [PATCH 0663/2366] kprobes: Protect kprobe_blacklist with RCU __within_kprobe_blacklist() traverses kprobe_blacklist without holding kprobe_mutex. When a module is unloaded, kprobe_remove_area_blacklist() removes blacklist entries and immediately frees them with kfree(). A concurrent call to within_kprobe_blacklist() can therefore dereference freed memory. Furthermore, within_kprobe_blacklist() can be called in atomic or non-preemptible contexts where the sleeping kprobe_mutex cannot be taken. Protect kprobe_blacklist with RCU. Use guard(rcu)() and list_for_each_entry_rcu() for traversal, list_add_tail_rcu() for insertions, list_del_rcu() for deletions, and kfree_rcu() to reclaim entries safely after a grace period. Link: https://lore.kernel.org/all/178810004323.64882.16493230858653316962.stgit@devnote2/ Fixes: 376e242429bf ("kprobes: Introduce NOKPROBE_SYMBOL() macro to maintain kprobes blacklist") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260807155802.F06041F000E9@smtp.kernel.org/ Assisted-by: Antigravity:gemini-3.7-flash Signed-off-by: Masami Hiramatsu (Google) --- include/linux/kprobes.h | 1 + kernel/kprobes.c | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/include/linux/kprobes.h b/include/linux/kprobes.h index 8c4f3bb24429..e6de7ae55bda 100644 --- a/include/linux/kprobes.h +++ b/include/linux/kprobes.h @@ -181,6 +181,7 @@ struct kprobe_blacklist_entry { struct list_head list; unsigned long start_addr; unsigned long end_addr; + struct rcu_head rcu; }; #ifdef CONFIG_KPROBES diff --git a/kernel/kprobes.c b/kernel/kprobes.c index bfc89083daa9..6337da5cab9e 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c @@ -1447,8 +1447,14 @@ static bool __within_kprobe_blacklist(unsigned long addr) /* * If 'kprobe_blacklist' is defined, check the address and * reject any probe registration in the prohibited area. + * Note: this can return true during transition period where + * (start_addr, end_addr) in the black list is shrinking + * but old entry has not been removed yet. This is acceptable + * because the worst case is that we reject more probes than + * we should. */ - list_for_each_entry(ent, &kprobe_blacklist, list) { + guard(rcu)(); + list_for_each_entry_rcu(ent, &kprobe_blacklist, list) { if (addr >= ent->start_addr && addr < ent->end_addr) return true; } @@ -2509,7 +2515,7 @@ int kprobe_add_ksym_blacklist(unsigned long entry) ent->start_addr = entry; ent->end_addr = entry + size; INIT_LIST_HEAD(&ent->list); - list_add_tail(&ent->list, &kprobe_blacklist); + list_add_tail_rcu(&ent->list, &kprobe_blacklist); return (int)size; } @@ -2603,8 +2609,8 @@ static void kprobe_remove_area_blacklist(unsigned long start, unsigned long end) list_for_each_entry_safe(ent, n, &kprobe_blacklist, list) { if (ent->start_addr < start || ent->start_addr >= end) continue; - list_del(&ent->list); - kfree(ent); + list_del_rcu(&ent->list); + kfree_rcu(ent, rcu); } } From 374b2c5561db80fcdd7cdce44af37a49416f61c7 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Wed, 2 Sep 2026 16:36:57 -0700 Subject: [PATCH 0664/2366] bpf: reject BPF_PSEUDO_FUNC reference to the main program fixups.c:jit_subprogs() rewrites BPF_PSEUDO_FUNC loads to contain real function addresses. This function is invoked from bpf_jit_subprogs() only when env->subprog_cnt > 1. Meaning that for any program like below: int main(void *ctx) { void *ptr = main; ... bpf_timer_set_callback(..., ptr); ... } The 'ptr' won't be ever converted to contain an address. In combination with e.g. bpf_timer_set_callback() this would lead to a function call at a bogus address. Instead of complicating the implementation, just assume that no useful program needs main to be a sync or async callback and reject BPF_PSEUDO_FUNC loads for the main subprogram. Fixes: 69c087ba6225 ("bpf: Add bpf_for_each_map_elem() helper") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260902233658.1186477-1-eddyz87@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index e64035683795..7d8ddb1bee00 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -17089,6 +17089,15 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn) verbose(env, "callback function not static\n"); return -EINVAL; } + /* + * When env->subprog_cnt == 1 this instruction won't be rewritten + * to hold a real function address. Assume that no usable program + * combines e.g. main and timer callback and just reject here. + */ + if (subprogno == 0) { + verbose(env, "callback function cannot be the main program\n"); + return -EINVAL; + } dst_reg->type = PTR_TO_FUNC; dst_reg->subprogno = subprogno; From ac0aaef0aa997fcdcb2458bd584539ba8608d33e Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Wed, 2 Sep 2026 16:36:58 -0700 Subject: [PATCH 0665/2366] selftests/bpf: BPF_PSEUDO_FUNC reference to the main program Add a test case for a BPF_PSEUDO_FUNC load instruction that references the entry function of the program it belongs to. W/o the previous patch the verifier accepts this program thus allowing a runtime call at a bogus address. See previous patch for detailed description. Main function needs to be marked with BTF_FUNC_STATIC for the test to trigger the bug, the patch uses test_verifier harness instead of test_prog because libbpf has no way to convey this. Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260902233658.1186477-2-eddyz87@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/verifier/pseudo_func.c | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tools/testing/selftests/bpf/verifier/pseudo_func.c diff --git a/tools/testing/selftests/bpf/verifier/pseudo_func.c b/tools/testing/selftests/bpf/verifier/pseudo_func.c new file mode 100644 index 000000000000..63c5c67d51de --- /dev/null +++ b/tools/testing/selftests/bpf/verifier/pseudo_func.c @@ -0,0 +1,45 @@ +/* + * Buggy verifier accepted the program below while not patching BPF_PSEUDO_FUNC + * load instruction to contain a real address. Which resulted in a function call + * to a bogus address. + */ +{ + "BPF_PSEUDO_FUNC reference to the main program", + .insns = { + /* r6 = bpf_map_lookup_elem(&timer_map, &(int){0}); */ + BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0), + BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), + BPF_LD_MAP_FD(BPF_REG_1, 0), + BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), + BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 10), + BPF_MOV64_REG(BPF_REG_6, BPF_REG_0), + /* bpf_timer_init(r6, &timer_map, 0); */ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), + BPF_LD_MAP_FD(BPF_REG_2, 0), + BPF_MOV64_IMM(BPF_REG_3, 0), + BPF_EMIT_CALL(BPF_FUNC_timer_init), + /* bpf_timer_set_callback(r6, ); */ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), + BPF_RAW_INSN(BPF_LD | BPF_IMM | BPF_DW, BPF_REG_2, BPF_PSEUDO_FUNC, 0, -15), + BPF_RAW_INSN(0, 0, 0, 0, 0), + BPF_EMIT_CALL(BPF_FUNC_timer_set_callback), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .prog_type = BPF_PROG_TYPE_TRACEPOINT, + .fixup_map_timer = { 3, 9 }, + .result = REJECT, + .errstr = "callback function cannot be the main program", + .func_info = { { 0, 4 /* main_prog */ } }, + .func_info_cnt = 1, + .btf_strings = "\0int\0ctx\0main_prog", + .btf_types = { + /* 1: int */ BTF_TYPE_INT_ENC(1, BTF_INT_SIGNED, 0, 32, 4), + /* 2: void* */ BTF_PTR_ENC(0), + /* 3: int __(void *) */ BTF_FUNC_PROTO_ENC(1, 1), + BTF_FUNC_PROTO_ARG_ENC(5, 2), + /* 4: main_prog */ BTF_FUNC_ENC(9, 3), + BTF_END_RAW + } +}, From af602c7aa5fedc9be3043244017aef4f26c96b70 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 31 Aug 2026 20:30:42 +0000 Subject: [PATCH 0666/2366] bonding: do not clear curr_active_slave prematurely when releasing all slaves When releasing all slaves during bond destruction (all == true), __bond_release_one() unconditionally clears bond->curr_active_slave to NULL in every iteration. If a backup slave is released before the active slave, bond_alb_deinit_slave() triggers rlb_teach_disabled_mac_on_primary(), which increments the active slave dev promiscuity counter and sets bond_info->primary_is_promisc = 1. Because bond->curr_active_slave was prematurely cleared to NULL when releasing the backup slave, the subsequent iteration releasing the active slave evaluates oldcurrent as NULL, so bond_change_active_slave(bond, NULL) is skipped. Consequently, bond_alb_handle_active_change() is never called to decrement the promiscuity counter, permanently leaking promiscuous mode on the physical device after bond teardown. When oldcurrent == slave, bond_change_active_slave(bond, NULL) already sets bond->curr_active_slave to NULL. We only need to avoid selecting a new active slave when all == true. Replace the if (all) branch with if (!all && oldcurrent == slave). Fixes: 0896341a44bf ("bonding: fix bond_release_all inconsistencies") Signed-off-by: Eric Dumazet Acked-by: Jay Vosburgh Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260831203042.164466-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index 947d92a669b6..a9bff7663eec 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -2517,9 +2517,7 @@ static int __bond_release_one(struct net_device *bond_dev, bond_alb_deinit_slave(bond, slave); } - if (all) { - RCU_INIT_POINTER(bond->curr_active_slave, NULL); - } else if (oldcurrent == slave) { + if (!all && oldcurrent == slave) { /* Note that we hold RTNL over this sequence, so there * is no concern that another slave add/remove event * will interfere. From 5a3f7a683aee56e1f15c9d53041f3236767eaae7 Mon Sep 17 00:00:00 2001 From: Jun Yang Date: Mon, 31 Aug 2026 19:13:04 +0800 Subject: [PATCH 0667/2366] net: bridge: mcast: don't truncate the port group walk on teardown __br_multicast_disable_port_ctx() and br_multicast_del_port() walk port->mglist with hlist_for_each_entry_safe(). However, br_multicast_find_del_pg() can also delete other entries from the same list through br_multicast_fwd_src_remove() or __fwd_del_star_excl(). If such an entry is the iterator's saved next node, hlist_del_init() clears its ->next and terminates the walk early. The reproducer triggers this in both teardown walks, leaving port groups in the bridge mdb with a dangling ->key.port after del_nbp() frees the port: BUG: KASAN: slab-use-after-free in __mdb_fill_info+0x1191/0x1320 __mdb_fill_info+0x1191/0x1320 br_mdb_dump+0x594/0xe40 rtnl_mdb_dump+0x1cf/0x5d0 Use hlist_del_init_rcu() to unlink the group while preserving ->next. br_multicast_del_pg() and the teardown walks run under br->multicast_lock. The GC worker must acquire the same lock before detaching the group for destruction, so the node remains alive while the walk uses the preserved pointer. Preserving ->next means a walk can now reach a group that an earlier iteration already deleted as a side effect. That group is off mp->ports, so br_multicast_find_del_pg() would fall through its port scan and hit the trailing WARN_ON(1). Skip such groups at the top of that helper: a port group is put on port->mglist when it is created and only unlinked when it is deleted, so hlist_unhashed() identifies exactly this case. Fixes: b08123684bd5 ("net: bridge: mcast: install S,G entries automatically based on reports") Cc: stable@vger.kernel.org Suggested-by: Nikolay Aleksandrov Reported-by: TencentOS Corvus AI Signed-off-by: Jun Yang Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260831111330.199543-1-junvyyang@tencent.com Signed-off-by: Jakub Kicinski --- net/bridge/br_multicast.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 3ef5d8bbf552..3e9b10f8abf1 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -808,7 +808,11 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, struct hlist_node *tmp; rcu_assign_pointer(*pp, pg->next); - hlist_del_init(&pg->mglist); + /* Keep ->next (held under multicast_lock, freed later by the GC work): + * a port->mglist teardown walk may have latched this node as its next, + * and deleting other groups of the same port must not truncate it. + */ + hlist_del_init_rcu(&pg->mglist); br_multicast_eht_clean_sets(pg); hlist_for_each_entry_safe(ent, tmp, &pg->src_list, node) br_multicast_del_group_src(ent, false); @@ -835,6 +839,13 @@ static void br_multicast_find_del_pg(struct net_bridge *br, struct net_bridge_mdb_entry *mp; struct net_bridge_port_group *p; + /* A teardown walk over port->mglist can reach a group that an earlier + * iteration already deleted as a side effect. It is off mp->ports by + * now, so skip it instead of falling through to the WARN_ON() below. + */ + if (hlist_unhashed(&pg->mglist)) + return; + mp = br_mdb_ip_get(br, &pg->key.addr); if (WARN_ON(!mp)) return; From debac3a20dec524a59625cf10fa2f18571127824 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Tue, 1 Sep 2026 00:55:44 +0000 Subject: [PATCH 0668/2366] net: Remove conflicting altnames for dying netns in __dev_change_net_namespace(). syzbot reported the warning in cfg80211_pernet_exit(). [0] The repro does the following: 1. create two device in root netns and non-root netns 2. assign the same altname for the two devices 3. remove the non-root netns Since commit 7663d522099e ("net: check for altname conflicts when changing netdev's netns"), cfg80211_switch_netns() and cfg802154_switch_netns() fail if init_net has a device with the conflicting altname. default_device_exit_net() had the same issue and commit d09486a04f5d ("net: fix removing a namespace with conflicting altnames") fixed it. cfg80211_pernet_exit() and cfg802154_pernet_exit() need the same fix. Let's generalise the fix by removing conflicting altnames for dying netns in __dev_change_net_namespace(). [0]: cfg80211_switch_netns(rdev, &init_net) WARNING: net/wireless/core.c:1871 at cfg80211_pernet_exit+0xd5/0x120 net/wireless/core.c:1871, CPU#1: kworker/u8:9/1160 Modules linked in: CPU: 1 UID: 0 PID: 1160 Comm: kworker/u8:9 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026 Workqueue: netns cleanup_net RIP: 0010:cfg80211_pernet_exit+0xd5/0x120 net/wireless/core.c:1871 Code: e8 03 42 80 3c 20 00 74 08 4c 89 f7 e8 b4 ef 0e f7 4d 8b 36 49 81 fe 20 10 4a 90 74 12 e8 03 3d 9f f6 eb 85 e8 fc 3c 9f f6 90 <0f> 0b 90 eb cc e8 f1 3c 9f f6 eb 05 e8 ea 3c 9f f6 5b 41 5c 41 5e RSP: 0018:ffffc900057a78f0 EFLAGS: 00010293 RAX: ffffffff8b287154 RBX: ffff88807ba72780 RCX: ffff8880213e8000 RDX: 0000000000000000 RSI: 00000000ffffffef RDI: 0000000000000000 RBP: 00000000ffffffef R08: ffffffff9024cc67 R09: 0000000000000000 R10: fffff52000af4eb0 R11: fffffbfff204998d R12: dffffc0000000000 R13: ffffffff904a1080 R14: ffff888144ed0008 R15: ffff888144ed0e20 FS: 0000000000000000(0000) GS:ffff888124de6000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00005642de0a8a70 CR3: 000000007a40c000 CR4: 00000000003526f0 Call Trace: ops_exit_list net/core/net_namespace.c:200 [inline] ops_undo_list+0x43d/0x8d0 net/core/net_namespace.c:253 cleanup_net+0x572/0x810 net/core/net_namespace.c:706 process_one_work kernel/workqueue.c:3387 [inline] process_scheduled_works+0xc3d/0x1630 kernel/workqueue.c:3470 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3551 kthread+0x38b/0x480 kernel/kthread.c:436 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Fixes: 36fbf1e52bd3 ("net: rtnetlink: add linkprop commands to add and delete alternative ifnames") Reported-by: syzbot+74f338e09f1ef3ee6457@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a96219e.04428c52.29b18.0001.GAE@google.com/T/ Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260901005550.2042357-1-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index 38336858c168..290e0f099e6b 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -12703,7 +12703,7 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net, const char *pat, int new_ifindex, struct netlink_ext_ack *extack) { - struct netdev_name_node *name_node; + struct netdev_name_node *name_node, *tmp; struct net *net_old = dev_net(dev); char new_name[IFNAMSIZ] = {}; int err, new_nsid; @@ -12749,13 +12749,19 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net, } /* Check that none of the altnames conflicts. */ err = -EEXIST; - netdev_for_each_altname(dev, name_node) { - if (netdev_name_in_use(net, name_node->name)) { - NL_SET_ERR_MSG_FMT(extack, - "An interface with the altname %s exists in the target netns", - name_node->name); - goto out; + netdev_for_each_altname_safe(dev, name_node, tmp) { + if (!netdev_name_in_use(net, name_node->name)) + continue; + + if (!check_net(net_old)) { + __netdev_name_node_alt_destroy(name_node); + continue; } + + NL_SET_ERR_MSG_FMT(extack, + "An interface with the altname %s exists in the target netns", + name_node->name); + goto out; } /* Check that new_ifindex isn't used yet. */ @@ -13210,7 +13216,6 @@ static struct pernet_operations __net_initdata netdev_net_ops = { static void __net_exit default_device_exit_net(struct net *net) { - struct netdev_name_node *name_node, *tmp; struct net_device *dev, *aux; /* * Push all migratable network devices back to the @@ -13234,10 +13239,6 @@ static void __net_exit default_device_exit_net(struct net *net) if (netdev_name_in_use(&init_net, fb_name)) snprintf(fb_name, IFNAMSIZ, "dev%%d"); - netdev_for_each_altname_safe(dev, name_node, tmp) - if (netdev_name_in_use(&init_net, name_node->name)) - __netdev_name_node_alt_destroy(name_node); - err = dev_change_net_namespace(dev, &init_net, fb_name); if (err) { pr_emerg("%s: failed to move %s to init_net: %d\n", From d85f521a9afb786b1d95bbcb218d3afdf3fe73ab Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Mon, 31 Aug 2026 13:31:28 +0200 Subject: [PATCH 0669/2366] net: macb: exclude software FCS from TX byte statistics Frames for which macb_pad_and_fcs() supplies the FCS have four FCS bytes appended, and TX completion then accounts the grown skb->len. tx_bytes is defined to exclude the FCS, so these frames are reported four bytes too large. Track only the number of FCS bytes appended in software, 0 or ETH_FCS_LEN, and subtract that from skb->len at completion. skb->len already reflects the padded length by then, so there is nothing else to store. macb_pad_and_fcs() already returns 0 on every non-error path. Return the FCS length from there instead, rather than recomputing the same check in the caller. BQL stays on the padded skb->len that netdev_tx_sent_queue() saw. Fixes: 653e92a9175e ("net: macb: add support for padding and fcs computation") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260831113128.1678674-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb.h | 3 +++ drivers/net/ethernet/cadence/macb_main.c | 21 +++++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h index 1e1f52285a39..d6931c41f39d 100644 --- a/drivers/net/ethernet/cadence/macb.h +++ b/drivers/net/ethernet/cadence/macb.h @@ -968,6 +968,8 @@ struct macb_dma_desc_ptp { * of the frame * @mapping: DMA address of the skb's fragment buffer * @size: size of the DMA mapped buffer + * @fcs_len: FCS bytes appended in software, 0 or ETH_FCS_LEN, only + * set for the last buffer of the frame * @mapped_as_page: true when buffer was mapped with skb_frag_dma_map(), * false when buffer was mapped with dma_map_single() */ @@ -975,6 +977,7 @@ struct macb_tx_skb { struct sk_buff *skb; dma_addr_t mapping; size_t size; + u8 fcs_len; bool mapped_as_page; }; diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 76ee4f506033..b1939da4c95a 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -1322,8 +1322,8 @@ static void macb_tx_error_task(struct work_struct *work) bp->netdev->stats.tx_packets++; queue->stats.tx_packets++; packets++; - bp->netdev->stats.tx_bytes += skb->len; - queue->stats.tx_bytes += skb->len; + bp->netdev->stats.tx_bytes += skb->len - tx_skb->fcs_len; + queue->stats.tx_bytes += skb->len - tx_skb->fcs_len; bytes += skb->len; } } else { @@ -1450,8 +1450,8 @@ static int macb_tx_complete(struct macb_queue *queue, int budget) skb->data); bp->netdev->stats.tx_packets++; queue->stats.tx_packets++; - bp->netdev->stats.tx_bytes += skb->len; - queue->stats.tx_bytes += skb->len; + bp->netdev->stats.tx_bytes += skb->len - tx_skb->fcs_len; + queue->stats.tx_bytes += skb->len - tx_skb->fcs_len; packets++; bytes += skb->len; } @@ -2199,7 +2199,8 @@ static void macb_poll_controller(struct net_device *netdev) static unsigned int macb_tx_map(struct macb *bp, struct macb_queue *queue, struct sk_buff *skb, - unsigned int hdrlen) + unsigned int hdrlen, + u8 fcs_len) { unsigned int f, nr_frags = skb_shinfo(skb)->nr_frags; unsigned int len, i, tx_head = queue->tx_head; @@ -2284,6 +2285,7 @@ static unsigned int macb_tx_map(struct macb *bp, /* This is the last buffer of the frame: save socket buffer */ tx_skb->skb = skb; + tx_skb->fcs_len = fcs_len; /* Update TX ring: update buffer descriptors in reverse order * to avoid race condition @@ -2417,6 +2419,7 @@ static inline int macb_clear_csum(struct sk_buff *skb) return 0; } +/* Returns a negative errno, or the FCS bytes appended (0 or ETH_FCS_LEN). */ static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) { bool cloned = skb_cloned(*skb) || skb_header_cloned(*skb) || @@ -2465,7 +2468,7 @@ static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) skb_put_u8(*skb, (fcs >> 16) & 0xff); skb_put_u8(*skb, (fcs >> 24) & 0xff); - return 0; + return ETH_FCS_LEN; } static netdev_tx_t macb_start_xmit(struct sk_buff *skb, @@ -2478,6 +2481,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, netdev_tx_t ret = NETDEV_TX_OK; unsigned int hdrlen; unsigned long flags; + int fcs_len; bool is_lso; if (macb_clear_csum(skb)) { @@ -2485,7 +2489,8 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, return ret; } - if (macb_pad_and_fcs(&skb, netdev)) { + fcs_len = macb_pad_and_fcs(&skb, netdev); + if (fcs_len < 0) { dev_kfree_skb_any(skb); return ret; } @@ -2548,7 +2553,7 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, } /* Map socket buffer for DMA transfer */ - if (macb_tx_map(bp, queue, skb, hdrlen)) { + if (macb_tx_map(bp, queue, skb, hdrlen, fcs_len)) { dev_kfree_skb_any(skb); goto unlock; } From 08710f033e3e35704e45adf8a95b5043ece34899 Mon Sep 17 00:00:00 2001 From: Ian Lin Date: Mon, 31 Aug 2026 16:41:24 +0800 Subject: [PATCH 0670/2366] net: usb: qmi_wwan: add Compal EXM-G1x support The Compal EXM-G1x is a Qualcomm SDX12-based LTE modem. Add support for its QMI WWAN interface 8 using the DTR quirk. Tested on a Compal EXM-G1x modem. Signed-off-by: Ian Lin Link: https://patch.msgid.link/20260831084124.65074-1-jisayme@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/qmi_wwan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index 8178a8758cd3..fdfdcf24ddcf 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -1446,6 +1446,7 @@ static const struct usb_device_id products[] = { {QMI_QUIRK_SET_DTR(0x2c7c, 0x0316, 3)}, /* Quectel RG255C */ {QMI_QUIRK_SET_DTR(0x2cb7, 0x0104, 4)}, /* Fibocom NL678 series */ {QMI_QUIRK_SET_DTR(0x2cb7, 0x0112, 0)}, /* Fibocom FG132 */ + {QMI_QUIRK_SET_DTR(0x04b7, 0x8217, 8)}, /* Compal EXM-G1x */ {QMI_FIXED_INTF(0x0489, 0xe0b4, 0)}, /* Foxconn T77W968 LTE */ {QMI_FIXED_INTF(0x0489, 0xe0b5, 0)}, /* Foxconn T77W968 LTE with eSIM support*/ {QMI_FIXED_INTF(0x2692, 0x9025, 4)}, /* Cellient MPL200 (rebranded Qualcomm 05c6:9025) */ From 6d0c8b7073913011459cf968cbbadd341e166bc3 Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Fri, 28 Aug 2026 15:39:15 -0700 Subject: [PATCH 0671/2366] net/rds: use wq_has_sleeper() in release_in_xmit() release_in_xmit() clears RDS_IN_XMIT with clear_bit_unlock() and then checks waitqueue_active() to decide whether anyone needs waking. clear_bit_unlock() is only a release operation: it orders the critical section before the bit clear, but does not order the subsequent plain load of the wait queue head after it. The waiter side does the mirror image - it adds itself to the wait queue and then tests the bit. That is the classic store-buffering pattern: the releasing CPU can read the wait queue as empty while the waiting CPU still reads the bit as set, so the sleeper is never woken. The waiters are rds_conn_shutdown() and rds_tcp_reset_callbacks(), both in uninterruptible wait_event() with no timeout. A lost wake-up strands the shutdown worker on its single-threaded workqueue until some other sender releases the bit again - and on a connection that is being torn down precisely because it failed, there may never be another sender. The barrier used to be there: release_in_xmit() did clear_bit() followed by smp_mb__after_atomic() until commit 1422f28826d2 ("rds: introduce acquire/release ordering in acquire/release_in_xmit()") folded both into clear_bit_unlock(), which strengthened the lock hand-off but silently dropped the full barrier the wake-up check depends on. The refill counterpart, release_refill() in net/rds/ib_recv.c, still carries its smp_mb__after_atomic() for exactly this reason. Use wq_has_sleeper(), which is waitqueue_active() preceded by the required full barrier. Fixes: 1422f28826d2 ("rds: introduce acquire/release ordering in acquire/release_in_xmit()") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-2-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/send.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/rds/send.c b/net/rds/send.c index 15a1b97f13e7..8aad185e4b1a 100644 --- a/net/rds/send.c +++ b/net/rds/send.c @@ -114,8 +114,13 @@ static void release_in_xmit(struct rds_conn_path *cp) * hot path and finding waiters is very rare. We don't want to walk * the system-wide hashed waitqueue buckets in the fast path only to * almost never find waiters. + * + * wq_has_sleeper() supplies the full barrier that orders the wait + * queue read after the bit clear; clear_bit_unlock() alone is only + * a release and would let this check read a stale empty queue, + * losing the wake-up. */ - if (waitqueue_active(&cp->cp_waitq)) + if (wq_has_sleeper(&cp->cp_waitq)) wake_up_all(&cp->cp_waitq); } From 17c4476dbb9c3bfd34193a6c22f2c3da8747134a Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Fri, 28 Aug 2026 15:39:16 -0700 Subject: [PATCH 0672/2366] net/rds: use clear_bit_unlock() in release_refill() release_refill() drops the RDS_RECV_REFILL bit with a plain clear_bit(). clear_bit() has no ordering semantics, and the smp_mb__after_atomic() that follows it sits on the wrong side for a lock release: it orders the clear against the waitqueue_active() load below it, but does nothing to order the refill critical section's ring and descriptor stores before the clear itself. That matters once connection teardown owns RDS_RECV_REFILL as a lock across the transport shutdown and path reset, rather than sampling it clear, which "net/rds: acquire the fastpath locks in rds_conn_shutdown()" later in this series arranges: on a weakly ordered architecture the teardown can win the bit and start the shutdown and reset while some of the refill's stores are not yet visible to it. The same gap existed under the sample-based scheme - a waiter that saw the bit clear had no guarantee it also observed the refill's stores - but taking the bit as a lock makes the missing release pairing load-bearing. Switch to clear_bit_unlock(), which orders the critical section before the release, and replace the open-coded barrier-plus-waitqueue_active() with wq_has_sleeper(), whose internal full barrier keeps the store-buffering guarantee between clearing the bit and checking for sleepers. This mirrors what "net/rds: use wq_has_sleeper() in release_in_xmit()" does for RDS_IN_XMIT. The fast-path acquire side, acquire_refill(), uses test_and_set_bit(), a full-barrier RMW that pairs with this release. The teardown at this point in the series still samples the bit, so on its own this change is release-side hardening; the shutdown-conversion patch named above makes the teardown acquire the bit with the same RMW, completing the pairing at the end of the series. Fixes: 73ce4317bf98 ("RDS: make sure we post recv buffers") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-3-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/ib_recv.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/net/rds/ib_recv.c b/net/rds/ib_recv.c index 357128d34a54..a6983861eec7 100644 --- a/net/rds/ib_recv.c +++ b/net/rds/ib_recv.c @@ -363,15 +363,14 @@ static int acquire_refill(struct rds_connection *conn) static void release_refill(struct rds_connection *conn) { - clear_bit(RDS_RECV_REFILL, &conn->c_flags); - smp_mb__after_atomic(); + clear_bit_unlock(RDS_RECV_REFILL, &conn->c_flags); /* We don't use wait_on_bit()/wake_up_bit() because our waking is in a * hot path and finding waiters is very rare. We don't want to walk * the system-wide hashed waitqueue buckets in the fast path only to * almost never find waiters. */ - if (waitqueue_active(&conn->c_waitq)) + if (wq_has_sleeper(&conn->c_waitq)) wake_up_all(&conn->c_waitq); } From 103c4b13c4f50322910078d1c02f29334a574122 Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Fri, 28 Aug 2026 15:39:17 -0700 Subject: [PATCH 0673/2366] net/rds: clear cp_flags bits individually in rds_conn_path_reset() rds_conn_path_reset() wipes the whole flag word with a plain cp->cp_flags = 0 store. Every other accessor of that word uses atomic bitops, and some of them can run concurrently with the reset: RDS_LL_SEND_FULL is set from rds_send_xmit() and cleared from the transport completion paths, neither of which holds anything that excludes the shutdown worker. A plain store racing an atomic read-modify-write on the same word is a data race, and whichever side loses has its update silently discarded. Clear the two bits the reset is actually responsible for instead. RDS_IN_XMIT and RDS_RECV_REFILL need no store at all here: they belong to the caller, rds_conn_shutdown(), which waits for both to be clear before calling the transport shutdown and this reset. This also gives every bit in cp_flags a single well-defined writer discipline, which the following patches rely on when they turn RDS_IN_XMIT and RDS_RECV_REFILL into bit locks held across the teardown: a blanket store mid-teardown would destroy lock ownership that an atomic clear preserves. Oracle UEK carries the same conversion ("net/rds: Preserve essential connection state flags"), motivated by its asynchronous shutdown state machine, whose progress and destroy flags must survive the reset. UEK's variant also clears RDS_IN_XMIT and RDS_RECV_REFILL because there the reset runs as the final step of a teardown that owns both bits, making those clears its unlock. Upstream that release belongs in rds_conn_shutdown(): once a later patch in this series turns the two bits into locks held across the teardown, ending ownership needs release semantics and a wake-up that a plain clear inside the reset would not provide. Based on Oracle UEK commit "net/rds: Preserve essential connection state flags" by Gerd Rausch. Fixes: 00e0f34c6166 ("RDS: Connection handling") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-4-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/connection.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/rds/connection.c b/net/rds/connection.c index 7c8ab8e973e1..46ac72088f84 100644 --- a/net/rds/connection.c +++ b/net/rds/connection.c @@ -120,7 +120,15 @@ static void rds_conn_path_reset(struct rds_conn_path *cp) rds_stats_inc(s_conn_reset); rds_send_path_reset(cp); - cp->cp_flags = 0; + + /* Clear the bits the reset is responsible for individually: a + * blanket cp_flags = 0 is a plain store that can clobber a + * concurrent atomic read-modify-write on the same word. + * RDS_IN_XMIT and RDS_RECV_REFILL belong to the caller, + * rds_conn_shutdown(), and are left alone here. + */ + clear_bit(RDS_LL_SEND_FULL, &cp->cp_flags); + clear_bit(RDS_RECONNECT_PENDING, &cp->cp_flags); /* Do not clear next_rx_seq here, else we cannot distinguish * retransmitted packets from new packets, and will hand all From e8e60d74fec49ccae2aea9b04a6eb162feb8d9af Mon Sep 17 00:00:00 2001 From: Gerd Rausch Date: Fri, 28 Aug 2026 15:39:18 -0700 Subject: [PATCH 0674/2366] net/rds: tcp: don't force RDS_CONN_RESETTING over a concurrent shutdown rds_tcp_reset_callbacks() resolves a duelling SYN by storing RDS_CONN_RESETTING into cp_state unconditionally. Nothing serializes that store against the shutdown path: rds_tcp_accept_one() checks for RDS_CONN_CONNECTING or RDS_CONN_ERROR under t_conn_path_lock, but neither rds_conn_path_drop(), which forces RDS_CONN_ERROR, nor rds_conn_shutdown(), which moves the path to RDS_CONN_DISCONNECTING under cp_cm_lock, takes that lock. The store can therefore land on top of a shutdown that is already in progress, or that gets queued right after the accept-side check. When it does, the shutdown worker's final DISCONNECTING -> DOWN transition fails and the path goes through rds_conn_path_error() and a second drop/shutdown cycle instead of a clean reconnect, tearing down the socket the accept path has just installed. Before commit ad22d24be635 ("net/rds: No shortcut out of RDS_CONN_ERROR") a path found in RDS_CONN_RESETTING even made rds_conn_shutdown() bail out altogether. Make the transition conditional: move CONNECTING -> RESETTING (or stay in RESETTING from an earlier duel), and drop the path in any other state. The drop has side effects of its own: it replaces the shutdown's RDS_CONN_DISCONNECTING (or RDS_CONN_ERROR) with RDS_CONN_ERROR and queues one more cp_down_w run. The difference is that rds_conn_shutdown() accepts RDS_CONN_ERROR in its final transition to RDS_CONN_DOWN, so the shutdown in flight completes normally instead of through rds_conn_path_error(); the extra down-work pass then finds the path already down and falls through to the reconnect check, or catches a reconnect that has already started and restarts it. The accept path still installs the new socket, rds_connect_path_complete() then fails its RESETTING -> UP transition and drops it: the raced socket ends up torn down as it does today. The comment at that call site, which promised that rds_connect_path_complete() marks the path RDS_CONN_UP, is updated to name this outcome as well. The state can change again between the failed transitions and the drop. That is inherent to rds_conn_path_drop(), which the socket state-change callbacks also call unconditionally, and costs at most one extra drop/reconnect cycle. Based on Oracle UEK commit "net/rds: Don't force state RDS_CONN_RESETTING" by Gerd Rausch. Fixes: 9c79440e2c5e ("RDS: TCP: fix race windows in send-path quiescence by rds_tcp_accept_one()") Signed-off-by: Gerd Rausch [achender: port to net-next: use the two-argument rds_conn_path_transition()/rds_conn_path_drop() and rewrite the changelog for the upstream shutdown path] Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-5-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/tcp.c | 17 +++++++++++++++-- net/rds/tcp_listen.c | 6 +++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/net/rds/tcp.c b/net/rds/tcp.c index b263634ac750..ad14217867a4 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -150,9 +150,22 @@ void rds_tcp_reset_callbacks(struct socket *sock, * end up deadlocking with tcp_sendmsg(), and the RDS_IN_XMIT * would not get set. As a result, we set c_state to * RDS_CONN_RESETTTING, to ensure that rds_tcp_state_change - * cannot mark rds_conn_path_up() in the window before lock_sock() + * cannot mark rds_conn_path_up() in the window before lock_sock(). + * + * Only make that transition if the path is still connecting + * (or already resetting from an earlier duel). A path in any + * other state - typically RDS_CONN_DISCONNECTING or + * RDS_CONN_ERROR with a shutdown in flight - is dropped + * instead. That still replaces its state, with RDS_CONN_ERROR, + * and queues one more shutdown pass, but rds_conn_shutdown() + * accepts RDS_CONN_ERROR in its final transition to + * RDS_CONN_DOWN, so the shutdown in flight completes normally. */ - atomic_set(&cp->cp_state, RDS_CONN_RESETTING); + if (!rds_conn_path_transition(cp, RDS_CONN_CONNECTING, + RDS_CONN_RESETTING) && + !rds_conn_path_transition(cp, RDS_CONN_RESETTING, + RDS_CONN_RESETTING)) + rds_conn_path_drop(cp, 0); wait_event(cp->cp_waitq, !test_bit(RDS_IN_XMIT, &cp->cp_flags)); /* reset receive side state for rds_tcp_data_recv() for osock */ cancel_delayed_work_sync(&cp->cp_send_w); diff --git a/net/rds/tcp_listen.c b/net/rds/tcp_listen.c index a3db9b057084..13fa60c1985b 100644 --- a/net/rds/tcp_listen.c +++ b/net/rds/tcp_listen.c @@ -295,7 +295,11 @@ int rds_tcp_accept_one(struct rds_tcp_net *rtn) if (rs_tcp->t_sock) { /* Duelling SYN has been handled in rds_tcp_accept_one() */ rds_tcp_reset_callbacks(new_sock, cp); - /* rds_connect_path_complete() marks RDS_CONN_UP */ + /* rds_connect_path_complete() marks RDS_CONN_UP, or, + * if a concurrent shutdown won the duel, drops the + * path again and the pass that drop queues reaps the + * socket installed above. + */ rds_connect_path_complete(cp, RDS_CONN_RESETTING); } else { rds_tcp_set_callbacks(new_sock, cp); From 02c5f9dc2efd823e061954d564ce00bacd1bebeb Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Fri, 28 Aug 2026 15:39:19 -0700 Subject: [PATCH 0675/2366] net/rds: acquire RDS_IN_XMIT in rds_tcp_reset_callbacks() rds_tcp_reset_callbacks() quiesces the transmit path by setting the path state to RDS_CONN_RESETTING and then waiting for RDS_IN_XMIT to be sampled clear before swapping the underlying socket and calling rds_send_path_reset(). Sampling the bit clear is not the same as owning it: rds_send_xmit() can re-acquire RDS_IN_XMIT right after the wait_event() returns. Its state recheck after taking the lock is a store-buffering pattern (the resetter writes the state and reads the bit, the sender writes the bit and reads the state) and acquire_in_xmit() is only an acquire operation, so on weakly ordered architectures both sides can miss each other's write and the transmit path then runs concurrently with rds_send_path_reset() rewriting cp_xmit_* state - which is exactly what the comment above rds_send_path_reset() tells its callers to prevent. Take the lock instead, hold it across the socket swap and rds_send_path_reset(), and release it with a wake-up at the end. The lock-ordering constraint documented above the wait still holds: the lock is acquired before lock_sock(), so a sender inside tcp_sendmsg() can never be waited on while we hold the socket lock. Two details of the old code go away with the same change: - t_sock is now read only after the lock is acquired. The old code cached it before waiting; the teardown in rds_conn_shutdown() releases that socket and clears t_sock, so a pointer cached before the wait can be stale by the time the accept path resumes. Reading it under RDS_IN_XMIT is what makes the exclusion complete once the teardown owns the same lock, which the next patch arranges; until then the teardown still only samples the bit, and the two paths remain as exposed to each other as they are today. - The old !osock early path called rds_send_path_reset() with no serialization at all. It now runs under the lock like the normal path. The conditional RDS_CONN_RESETTING transition of the previous patch happens before the socket check either way: a path found without a socket is either still connecting (its reconnect worker blocked on t_conn_path_lock) and legitimately goes RESETTING -> UP on the new socket, or it has been torn down meanwhile and is dropped. The in-function comment describing the old wait-based quiesce is rewritten to describe the lock-based one, and the stale block comment above the function (which still described a return value and an incomplete list of t_sock writers) is refreshed to name all four writers - the connect, accept, teardown and swap paths - and what serializes each of them. Fixes: 335b48d980f6 ("RDS: TCP: Add/use rds_tcp_reset_callbacks to reset tcp socket safely") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-6-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/tcp.c | 70 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/net/rds/tcp.c b/net/rds/tcp.c index ad14217867a4..f4c83e368390 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -115,42 +115,48 @@ void rds_tcp_restore_callbacks(struct socket *sock, } /* - * rds_tcp_reset_callbacks() switches the to the new sock and - * returns the existing tc->t_sock. + * rds_tcp_reset_callbacks() switches a path to a new socket and + * releases the old one it finds in tc->t_sock, resolving a duelling + * SYN. * - * The only functions that set tc->t_sock are rds_tcp_set_callbacks - * and rds_tcp_reset_callbacks. Send and receive trust that - * it is set. The absence of RDS_CONN_UP bit protects those paths - * from being called while it isn't set. + * tc->t_sock is set by rds_tcp_set_callbacks() and cleared by + * rds_tcp_restore_callbacks(). Four paths write it: the active + * connect in rds_tcp_conn_path_connect(), which sets it and clears it + * again on failure; the accept path in rds_tcp_accept_one(), which + * sets it for a path with no socket yet; the teardown in + * rds_tcp_conn_path_shutdown(), which clears it; and the swap done + * here, which does both. The connect and accept paths are serialized + * against each other by t_conn_path_lock. Send and receive trust + * that it is set: the absence of RDS_CONN_UP protects those paths + * from being called while it isn't, and the swap done here runs under + * RDS_IN_XMIT so that it cannot interleave with a sender already + * inside rds_send_xmit(). */ void rds_tcp_reset_callbacks(struct socket *sock, struct rds_conn_path *cp) { struct rds_tcp_connection *tc = cp->cp_transport_data; - struct socket *osock = tc->t_sock; - - if (!osock) - goto newsock; + struct socket *osock; /* Need to resolve a duelling SYN between peers. * We have an outstanding SYN to this peer, which may * potentially have transitioned to the RDS_CONN_UP state, * so we must quiesce any send threads before resetting - * cp_transport_data. We quiesce these threads by setting - * cp_state to something other than RDS_CONN_UP, and then - * waiting for any existing threads in rds_send_xmit to - * complete release_in_xmit(). (Subsequent threads entering - * rds_send_xmit() will bail on !rds_conn_up(). + * cp_transport_data. Setting cp_state to something other + * than RDS_CONN_UP stops new senders, and owning RDS_IN_XMIT + * excludes any thread already inside rds_send_xmit() for the + * whole socket swap and the rds_send_path_reset() below. * - * However an incoming syn-ack at this point would end up - * marking the conn as RDS_CONN_UP, and would again permit - * rds_send_xmi() threads through, so ideally we would - * synchronize on RDS_CONN_UP after lock_sock(), but cannot - * do that: waiting on !RDS_IN_XMIT after lock_sock() may - * end up deadlocking with tcp_sendmsg(), and the RDS_IN_XMIT - * would not get set. As a result, we set c_state to - * RDS_CONN_RESETTTING, to ensure that rds_tcp_state_change - * cannot mark rds_conn_path_up() in the window before lock_sock(). + * An incoming syn-ack at this point would end up marking the + * conn as RDS_CONN_UP, and would again permit rds_send_xmit() + * threads through, so ideally we would synchronize on + * RDS_CONN_UP after lock_sock(), but cannot do that: acquiring + * RDS_IN_XMIT after lock_sock() may end up deadlocking with + * tcp_sendmsg(), which takes the socket lock while holding + * RDS_IN_XMIT. As a result, we set c_state to + * RDS_CONN_RESETTING, to ensure that rds_tcp_state_change + * cannot mark rds_conn_path_up() in the window before + * lock_sock(). * * Only make that transition if the path is still connecting * (or already resetting from an earlier duel). A path in any @@ -166,7 +172,18 @@ void rds_tcp_reset_callbacks(struct socket *sock, !rds_conn_path_transition(cp, RDS_CONN_RESETTING, RDS_CONN_RESETTING)) rds_conn_path_drop(cp, 0); - wait_event(cp->cp_waitq, !test_bit(RDS_IN_XMIT, &cp->cp_flags)); + wait_event(cp->cp_waitq, + !test_and_set_bit_lock(RDS_IN_XMIT, &cp->cp_flags)); + + /* Read t_sock only while owning RDS_IN_XMIT, never before the + * wait: the teardown in rds_conn_shutdown() releases the old + * socket and clears t_sock, so a pointer sampled earlier can + * be stale by the time we wake up. + */ + osock = tc->t_sock; + if (!osock) + goto newsock; + /* reset receive side state for rds_tcp_data_recv() for osock */ cancel_delayed_work_sync(&cp->cp_send_w); cancel_delayed_work_sync(&cp->cp_recv_w); @@ -185,6 +202,9 @@ void rds_tcp_reset_callbacks(struct socket *sock, lock_sock(sock->sk); rds_tcp_set_callbacks(sock, cp); release_sock(sock->sk); + + clear_bit_unlock(RDS_IN_XMIT, &cp->cp_flags); + wake_up_all(&cp->cp_waitq); } /* Add tc to rds_tcp_tc_list and set tc->t_sock. See comments From 813f3582ac7ae9f60f917937d54660e0952d5f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Bugge?= Date: Fri, 28 Aug 2026 15:39:20 -0700 Subject: [PATCH 0676/2366] net/rds: acquire the fastpath locks in rds_conn_shutdown() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rds_conn_shutdown() quiesces the transmit and receive-refill paths by waiting for RDS_IN_XMIT and RDS_RECV_REFILL to be sampled clear, and then runs the transport shutdown and rds_conn_path_reset(). Sampling the bits clear is not the same as owning them: the moment after the wait_event() returns, rds_send_xmit() can re-acquire RDS_IN_XMIT (or rds_ib_recv_refill() can re-acquire RDS_RECV_REFILL) and run concurrently with the teardown. The sender does recheck the connection state after taking the lock, but that recheck is a classic store-buffering pattern: teardown writes the state and reads the bit while the sender writes the bit and reads the state. acquire_in_xmit() is only an acquire operation, so on weakly ordered architectures both sides can miss each other's write, and the transmit path then runs while the transport zeroes its rings (e.g. rds_ib_ring_init()) and rds_send_path_reset() rewrites the transmit state under it. Oracle UEK fixed the same class of crashes - a 14-year tail of BUG_ON()s in rds_ib_sub_signaled(), unexpected op-codes and NULL dereferences in rds_ib_send_cqe_handler() during failover testing - by making the teardown path *acquire* the fastpath bit locks instead of testing them ("rds: Make sure transmit path and connection tear-down does not run concurrently"). Ownership of a single word is decided by RMW atomicity, so no cross-variable ordering is needed. Do the same here: take both locks before calling the transport shutdown, hold them across rds_conn_path_reset(), and release them explicitly with a wake-up afterwards. Both are released with clear_bit_unlock(), so that the ring re-initialization done by the transport shutdown and the transmit state rewritten by rds_send_path_reset() are ordered before either bit is seen clear by the next acquire_in_xmit() or acquire_refill(). The fastpath users of these bits - rds_send_xmit() and rds_ib_recv_refill() - are trylock style and back off while teardown owns the locks, so no new lock dependency is introduced for them. rds_tcp_reset_callbacks() is different: since the previous patch it acquires RDS_IN_XMIT as well, and it blocks doing so, so its wait now spans the teardown instead of at most one send batch. That waiter runs from rds_tcp_accept_one() on the single-threaded krdsd workqueue and holds rds_tcp_accept_lock and t_conn_path_lock while it waits, so a duelling SYN accepted while its path is being torn down parks accept processing for the duration of the teardown - for TCP bounded by the (up to 5 s) drain loop in rds_tcp_conn_path_shutdown(). An IB path's drain in rds_ib_conn_path_shutdown() has no round cap, but no blocking waiter either: rds_tcp_reset_callbacks() is the only blocking acquirer of these bits and waits only on its own TCP path, and the fastpaths are trylock-and-back-off on both transports, so a long IB drain lengthens only that path's own quiesce. The window is narrow: the accept-side state check has to pass before the teardown moves the path to RDS_CONN_DISCONNECTING. Because krdsd is a single global workqueue, everything else queued there - accept processing for other connections and network namespaces, and the flush_workqueue(rds_wq) in rds_tcp_listen_stop() during namespace teardown - waits behind the parked accept worker for that time. It cannot deadlock, although the waits do point at each other: the teardown blocks until the bit's holder releases it, and the holder may be that krdsd accept worker. The holder finishes without needing anything the teardown owns: the sync cancels rds_tcp_reset_callbacks() issues target cp_send_w and cp_recv_w on the path's ordered cp_wq, whose only execution slot is occupied by the blocked cp_down_w itself, so they are pending at most and cancel without flushing - a reliance on cp_wq being ordered that is now noted next to those cancels (on the allocation-failure fallback where a path shares rds_wq, the work items simply serialize). Nor is the blocking wait itself new: rds_tcp_reset_callbacks() has waited on RDS_IN_XMIT from the krdsd work item since commit 335b48d980f6 ("RDS: TCP: Add/use rds_tcp_reset_callbacks to reset tcp socket safely"); this patch stretches its worst case from a sender's batch to the teardown's drain. The alternative to parking is the accept path racing the teardown, which is what these patches close; making the teardown itself non-blocking is a separate item. One observable side effect: the SENDING flag reported by rds-info has always mirrored RDS_IN_XMIT, so it now also covers the window where teardown owns the bit. The comments that describe the old sample-based handshake or name rds_send_xmit() as the only other holder of these bits - in rds_send_xmit(), above rds_conn_path_reset(), in rds_ib_recv_refill() and in rds_tcp_reset_callbacks() - are updated to match. For anyone backporting this patch standalone: it depends on "net/rds: clear cp_flags bits individually in rds_conn_path_reset()" and "net/rds: acquire RDS_IN_XMIT in rds_tcp_reset_callbacks()" earlier in this series. Without the former, the blanket cp_flags clear in rds_conn_path_reset() would drop both held bits in the middle of the teardown; without the latter, rds_tcp_reset_callbacks() would still sample t_sock without owning RDS_IN_XMIT. "net/rds: use clear_bit_unlock() in release_refill()" is needed for the refill side's release to pair with the acquire added here, and the follow-up "net/rds: don't let rds_conn_shutdown() consume a concurrent drop" completes the teardown-state handling for the waiter this patch parks; a backport should carry all four. Fixes: 0f4b1c7e89e6 ("rds: fix rds_send_xmit() serialization") Signed-off-by: Håkon Bugge [achender: reimplement for net-next shutdown path: acquire the existing RDS_IN_XMIT/RDS_RECV_REFILL bit locks in rds_conn_shutdown() and release after teardown; update comments and commit message] Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-7-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/connection.c | 40 ++++++++++++++++++++++++++++++++-------- net/rds/ib_recv.c | 4 +++- net/rds/send.c | 7 +++++-- net/rds/tcp.c | 19 +++++++++++++++---- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/net/rds/connection.c b/net/rds/connection.c index 46ac72088f84..fbbac55a0e81 100644 --- a/net/rds/connection.c +++ b/net/rds/connection.c @@ -106,10 +106,12 @@ static struct rds_connection *rds_conn_lookup(struct net *net, } /* - * This is called by transports as they're bringing down a connection. - * It clears partial message state so that the transport can start sending - * and receiving over this connection again in the future. It is up to - * the transport to have serialized this call with its send and recv. + * This is called by rds_conn_shutdown() once the transport has brought + * a path down. It clears partial message state so that the transport + * can start sending and receiving over this path again in the future. + * The caller owns RDS_IN_XMIT and RDS_RECV_REFILL across this call, + * which is what serializes it against the send and receive-refill + * paths. */ static void rds_conn_path_reset(struct rds_conn_path *cp) { @@ -124,8 +126,9 @@ static void rds_conn_path_reset(struct rds_conn_path *cp) /* Clear the bits the reset is responsible for individually: a * blanket cp_flags = 0 is a plain store that can clobber a * concurrent atomic read-modify-write on the same word. - * RDS_IN_XMIT and RDS_RECV_REFILL belong to the caller, - * rds_conn_shutdown(), and are left alone here. + * RDS_IN_XMIT and RDS_RECV_REFILL are held as locks by the + * caller, rds_conn_shutdown(), which releases them once the + * teardown is complete. */ clear_bit(RDS_LL_SEND_FULL, &cp->cp_flags); clear_bit(RDS_RECONNECT_PENDING, &cp->cp_flags); @@ -414,14 +417,35 @@ void rds_conn_shutdown(struct rds_conn_path *cp) } mutex_unlock(&cp->cp_cm_lock); + /* Quiesce the transmit and receive-refill paths by + * acquiring their bit locks, not merely waiting for + * them to be released: with a plain wait, either path + * can re-take its lock the instant after we sample it + * clear and then run concurrently with the transport + * shutdown and the path reset below. Holding both + * locks across the teardown makes that structurally + * impossible. + */ wait_event(cp->cp_waitq, - !test_bit(RDS_IN_XMIT, &cp->cp_flags)); + !test_and_set_bit_lock(RDS_IN_XMIT, &cp->cp_flags)); wait_event(cp->cp_waitq, - !test_bit(RDS_RECV_REFILL, &cp->cp_flags)); + !test_and_set_bit(RDS_RECV_REFILL, &cp->cp_flags)); conn->c_trans->conn_path_shutdown(cp); rds_conn_path_reset(cp); + /* Release the two locks and wake any waiter (e.g. + * rds_tcp_reset_callbacks()) that blocked on them while + * we held them. The unlock orders the transport's ring + * re-initialization and the path reset above before + * either bit is seen clear. rds_conn_path_reset() leaves + * both bits alone: ownership ends here, not inside the + * reset. + */ + clear_bit_unlock(RDS_IN_XMIT, &cp->cp_flags); + clear_bit_unlock(RDS_RECV_REFILL, &cp->cp_flags); + wake_up_all(&cp->cp_waitq); + if (!rds_conn_path_transition(cp, RDS_CONN_DISCONNECTING, RDS_CONN_DOWN) && !rds_conn_path_transition(cp, RDS_CONN_ERROR, diff --git a/net/rds/ib_recv.c b/net/rds/ib_recv.c index a6983861eec7..bd6cb3ffaa57 100644 --- a/net/rds/ib_recv.c +++ b/net/rds/ib_recv.c @@ -391,7 +391,9 @@ void rds_ib_recv_refill(struct rds_connection *conn, int prefill, gfp_t gfp) /* the goal here is to just make sure that someone, somewhere * is posting buffers. If we can't get the refill lock, - * let them do their thing + * let them do their thing. The holder may also be + * rds_conn_shutdown() tearing the path down, in which case + * there is nothing to post. */ if (!acquire_refill(conn)) return; diff --git a/net/rds/send.c b/net/rds/send.c index 8aad185e4b1a..1afa981e5c06 100644 --- a/net/rds/send.c +++ b/net/rds/send.c @@ -244,8 +244,11 @@ int rds_send_xmit(struct rds_conn_path *cp) WRITE_ONCE(cp->cp_send_gen, send_gen); /* - * rds_conn_shutdown() sets the conn state and then tests RDS_IN_XMIT, - * we do the opposite to avoid races. + * rds_conn_shutdown() sets the conn state and then acquires + * RDS_IN_XMIT; we take the lock first and then check the state. + * Ownership is decided by the atomic RMW on the cp_flags word: + * if the teardown won the bit we back off here, and if we won + * it the teardown waits until we release it. */ if (!rds_conn_path_up(cp)) { release_in_xmit(cp); diff --git a/net/rds/tcp.c b/net/rds/tcp.c index f4c83e368390..69c6d3145b5a 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -144,8 +144,10 @@ void rds_tcp_reset_callbacks(struct socket *sock, * so we must quiesce any send threads before resetting * cp_transport_data. Setting cp_state to something other * than RDS_CONN_UP stops new senders, and owning RDS_IN_XMIT - * excludes any thread already inside rds_send_xmit() for the - * whole socket swap and the rds_send_path_reset() below. + * excludes any thread already inside rds_send_xmit() - or a + * teardown in rds_conn_shutdown(), which holds the same lock + * for the duration of the transport shutdown - for the whole + * socket swap and the rds_send_path_reset() below. * * An incoming syn-ack at this point would end up marking the * conn as RDS_CONN_UP, and would again permit rds_send_xmit() @@ -178,13 +180,22 @@ void rds_tcp_reset_callbacks(struct socket *sock, /* Read t_sock only while owning RDS_IN_XMIT, never before the * wait: the teardown in rds_conn_shutdown() releases the old * socket and clears t_sock, so a pointer sampled earlier can - * be stale by the time we wake up. + * be stale by the time we wake up. The teardown holds the + * same lock while it does so, so what we read here cannot + * change under us until we release it. */ osock = tc->t_sock; if (!osock) goto newsock; - /* reset receive side state for rds_tcp_data_recv() for osock */ + /* reset receive side state for rds_tcp_data_recv() for osock. + * + * The sync cancels while owning RDS_IN_XMIT rely on cp_wq + * being ordered: a teardown blocked on the bit occupies + * cp_wq's only execution slot, so cp_send_w and cp_recv_w are + * pending at most and the cancels never flush. Nothing here + * may flush or wait on cp_wq itself. + */ cancel_delayed_work_sync(&cp->cp_send_w); cancel_delayed_work_sync(&cp->cp_recv_w); lock_sock(osock->sk); From 260c6308fe2e19ad519389d44d582e292aecc3af Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Fri, 28 Aug 2026 15:39:21 -0700 Subject: [PATCH 0677/2366] net/rds: don't let rds_conn_shutdown() consume a concurrent drop rds_conn_shutdown() finishes by moving the path from RDS_CONN_DISCONNECTING to RDS_CONN_DOWN, and also accepts RDS_CONN_ERROR as the starting state of that final transition, so that a FIN processed in softirq context during the teardown does not derail the shutdown into a noisy error path. But consuming that RDS_CONN_ERROR also consumes the shutdown pass that came with it: rds_conn_path_drop() sets RDS_CONN_ERROR and then queues cp_down_w, and a pass that starts on a path already in RDS_CONN_DOWN is a no-op. For the FIN case that is harmless - the socket the FIN arrived on is the very socket the teardown just released. It is not harmless for a dropper that attached something to the path first. rds_tcp_accept_one() is such a dropper. Its path claim in rds_tcp_accept_one_path() transitions RDS_CONN_DOWN -> RDS_CONN_CONNECTING, and a concurrent drop - a FIN on a previous socket in softirq context, an administrative reset - can put the path into RDS_CONN_ERROR between that claim and the state check that follows, which accepts RDS_CONN_ERROR. The accept then installs the freshly accepted socket with rds_tcp_set_callbacks() while the queued teardown - which sampled tc->t_sock before this socket existed - is still running. rds_connect_path_complete() fails its transition to RDS_CONN_UP and drops the path again, queueing the pass that should reap the socket it just installed. If the in-flight shutdown's final transition consumes that drop's RDS_CONN_ERROR, the queued pass finds the path in RDS_CONN_DOWN and does nothing. The installed socket is never torn down: it sits established with its callbacks armed and its rds_tcp_connection on rds_tcp_tc_list, the peer sees a connection that nothing ever reads, and the path is wedged in RDS_CONN_DOWN until some later event drops it again. Reproduced with widened race windows as an ever-growing receive queue on a socket owned by a path stuck in RDS_CONN_DOWN, with the peer's send path wedged behind it. Make the final transition only DISCONNECTING -> DOWN. If it fails because the path is in RDS_CONN_ERROR, a drop raced the teardown: cancel the reconnect timer and clear RDS_RECONNECT_PENDING - the one piece of the skipped tail that must not be left behind - and return, letting the pass the drop queued finish the job: it tears down whatever attached to the path in the meantime, completes the transition to RDS_CONN_DOWN, and re-arms the reconnect from its own tail. The timer quiesce in that branch matters because the racing drop does not always queue that pass: rds_conn_path_drop() returns without queueing when a destroy is pending - exactly the situation during a netns teardown or module unload, when a FIN on the dying socket is processed while rds_conn_path_destroy() flushes cp_down_w. If the flushed pass is the one that takes this return, no later pass exists, and rds_conn_path_destroy() would find cp_conn_w still armed (WARN_ON) and then free a path whose reconnect timer can still fire. With the cancel in the branch, every exit of a shutdown pass leaves the timer quiesced no matter which pass completes the transition. The FIN case keeps making progress, one pass later and still without noisy logging. Any other state keeps today's rds_conn_path_error() handling; no current cp_state writer can leave a DISCONNECTING path in anything but RDS_CONN_ERROR (every other writer is a cmpxchg from a non-DISCONNECTING state), so that branch is defensive. On kernels without the preceding patches the same hazard exists with the sample-based quiesce; the fix applies there equally. Fixes: e97656d03ca0 ("rds: tcp: allow progress of rds_conn_shutdown if the rds_connection is marked ERROR by an intervening FIN") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260828223921.202913-8-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/connection.c | 43 ++++++++++++++++++++++++++++++++----------- net/rds/tcp.c | 9 ++++++--- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/net/rds/connection.c b/net/rds/connection.c index fbbac55a0e81..b6c4beb50eaf 100644 --- a/net/rds/connection.c +++ b/net/rds/connection.c @@ -447,19 +447,40 @@ void rds_conn_shutdown(struct rds_conn_path *cp) wake_up_all(&cp->cp_waitq); if (!rds_conn_path_transition(cp, RDS_CONN_DISCONNECTING, - RDS_CONN_DOWN) && - !rds_conn_path_transition(cp, RDS_CONN_ERROR, RDS_CONN_DOWN)) { - /* This can happen - eg when we're in the middle of tearing - * down the connection, and someone unloads the rds module. - * Quite reproducible with loopback connections. - * Mostly harmless. + /* The path was dropped again while we tore it + * down: by a socket state-change callback in + * irq context on receipt of a FIN, or by an + * accept that claimed the path just before a + * drop put it back to RDS_CONN_ERROR and then + * installed a fresh socket on it. Unless a + * pending destroy suppressed it, the drop also + * queued another shutdown pass, and that pass + * must run, because it is what tears down + * whatever attached to the path after the + * transport shutdown above sampled its state. + * Consuming the RDS_CONN_ERROR here would turn + * that pass into a no-op: leave the state + * alone, and let the pass finish the job. * - * Note that this also happens with rds-tcp because - * we could have triggered rds_conn_path_drop in irq - * mode from rds_tcp_state change on the receipt of - * a FIN, thus we need to recheck for RDS_CONN_ERROR - * here. + * Quiesce the reconnect timer before bailing + * out, though. When a pending destroy did + * suppress the queue, no later pass runs, and + * rds_conn_path_destroy() is about to flush + * cp_down_w and free the path: it must not + * find cp_conn_w still armed. A successor + * pass, when there is one, re-arms the + * reconnect from its own tail. + */ + cancel_delayed_work_sync(&cp->cp_conn_w); + clear_bit(RDS_RECONNECT_PENDING, &cp->cp_flags); + + if (rds_conn_path_state(cp) == RDS_CONN_ERROR) + return; + /* No current cp_state writer leaves a + * DISCONNECTING path in any state but + * RDS_CONN_ERROR; report loudly if one ever + * does. */ rds_conn_path_error(cp, "%s: failed to transition " "to state DOWN, current state " diff --git a/net/rds/tcp.c b/net/rds/tcp.c index 69c6d3145b5a..774a71f88d37 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -165,9 +165,12 @@ void rds_tcp_reset_callbacks(struct socket *sock, * other state - typically RDS_CONN_DISCONNECTING or * RDS_CONN_ERROR with a shutdown in flight - is dropped * instead. That still replaces its state, with RDS_CONN_ERROR, - * and queues one more shutdown pass, but rds_conn_shutdown() - * accepts RDS_CONN_ERROR in its final transition to - * RDS_CONN_DOWN, so the shutdown in flight completes normally. + * and, unless a pending destroy is about to reap the whole + * connection anyway, queues one more shutdown pass. A shutdown + * already in flight leaves that RDS_CONN_ERROR alone when it + * finishes; the queued pass then completes the transition to + * RDS_CONN_DOWN and tears down anything that attached to the + * path in the meantime. */ if (!rds_conn_path_transition(cp, RDS_CONN_CONNECTING, RDS_CONN_RESETTING) && From 88f8113ab118ed0e187331324d6b60c694b2e0e2 Mon Sep 17 00:00:00 2001 From: Melissa Wen Date: Fri, 7 Aug 2026 13:56:21 +0200 Subject: [PATCH 0678/2366] drm/amd/display: use plane color_mgmt_changed to track colorop changes This is a resubmission of commit d79716401a95 ("drm/amd/display: use plane color_mgmt_changed to track colorop changes") whose change was reverted by commit 0461ba9a7994 ("Merge tag 'amd-drm-next-7.3-2026-07-02' of https://gitlab.freedesktop.org/agd5f/linux into drm-next") during a merge conflict resolution. Original commit message: ``` Ensure the driver tracks changes in any colorop property of a plane color pipeline by using the same mechanism of CRTC color management and update plane color blocks when any colorop property changes. It fixes an issue observed on gamescope settings for night mode which is done via shaper/3D-LUT updates. ``` Fixes: 0461ba9a7994 ("Merge tag 'amd-drm-next-7.3-2026-07-02' of https://gitlab.freedesktop.org/agd5f/linux into drm-next") Acked-by: Alex Deucher Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260807115712.22423-1-mwen@igalia.com --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index ec483276d753..2fe934036e36 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -3879,7 +3879,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, continue; bundle->surface_updates[planes_count].surface = dc_plane; - if (new_pcrtc_state->color_mgmt_changed) { + if (new_pcrtc_state->color_mgmt_changed || new_plane_state->color_mgmt_changed) { bundle->surface_updates[planes_count].gamma = &dc_plane->gamma_correction; bundle->surface_updates[planes_count].in_transfer_func = &dc_plane->in_transfer_func; bundle->surface_updates[planes_count].gamut_remap_matrix = &dc_plane->gamut_remap_matrix; @@ -5698,6 +5698,10 @@ static bool should_reset_plane(struct drm_atomic_commit *state, if (new_crtc_state->color_mgmt_changed) return true; + /* Plane color pipeline or its colorop changes. */ + if (new_plane_state->color_mgmt_changed) + return true; + /* * On zpos change, planes need to be reordered by removing and re-adding * them one by one to the dc state, in order of descending zpos. From 115bf3e51538e74159e9fa46199468b69fd5df70 Mon Sep 17 00:00:00 2001 From: Sun Jian Date: Tue, 1 Sep 2026 04:40:11 -0700 Subject: [PATCH 0679/2366] exec: Drop bprm loader before closing bprm->file free_bprm() currently drops what may be the final reference to bprm->file before calling bprm_drop_loader(). Since bprm_drop_loader() is attachable via BPF fentry and bprm->file is exposed as a BTF_TYPE_SAFE_TRUSTED pointer, the file can be observed after its reference has been released. Move bprm_drop_loader() before do_close_execat(bprm->file), keeping the file reference held while the hook runs. This preserves the existing trusted BTF contract without changing verifier behavior. The loader file and bprm->file have independent references, so this reordering does not change their required teardown ordering. Link: https://sashiko.dev/#/patchset/20260831092305.42062-1-tasos.papagiannnis@gmail.com?part=3 Signed-off-by: Sun Jian Link: https://patch.msgid.link/20260901114011.112375-1-sun.jian.kdev@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/exec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/exec.c b/fs/exec.c index a14f28b15607..263b1f67f1f8 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1469,9 +1469,9 @@ static void free_bprm(struct linux_binprm *bprm) /* exec swapped the mm but failed before setup_new_exec() freed it */ if (bprm->old_mm) exec_mm_put_old(bprm->old_mm); - do_close_execat(bprm->file); /* An unconsumed PT_INTERP substitute from a binfmt_misc loader entry. */ bprm_drop_loader(bprm); + do_close_execat(bprm->file); do_close_execat(bprm->executable); /* If a binfmt changed the interp, free it. */ if (bprm->interp != bprm->filename) From d3609b540838945ab2ca5b65f32a2eb67bb284c8 Mon Sep 17 00:00:00 2001 From: Aditya Garg Date: Thu, 23 Jul 2026 10:01:36 +0000 Subject: [PATCH 0680/2366] MAINTAINERS, mailmap: use Aditya Garg's linux.dev account Due to non standard IMAP and SMTP protocols by Proton Mail, the account was giving trouble. Since my linux.dev account has been approved, all communication related to Linux development shall now be done there. Signed-off-by: Aditya Garg Acked-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260723100136.14467-1-aditya.garg@linux.dev --- .mailmap | 3 ++- MAINTAINERS | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index 6803f3bd2865..08c84f5c90d2 100644 --- a/.mailmap +++ b/.mailmap @@ -19,7 +19,8 @@ Abhinav Kumar Ahmad Masri Adam Oldham Adam Radford -Aditya Garg +Aditya Garg +Aditya Garg Adriana Reus Adrian Bunk Ajay Kaher diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..c7e62407de32 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8039,7 +8039,7 @@ F: drivers/gpu/drm/sun4i/sun8i* DRM DRIVER FOR APPLE TOUCH BARS M: Aun-Ali Zaidi -M: Aditya Garg +M: Aditya Garg L: dri-devel@lists.freedesktop.org S: Maintained T: git https://gitlab.freedesktop.org/drm/misc/kernel.git From 2f37fba846c9fdff5fc15b6d93656057ccd13031 Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Sat, 25 Jul 2026 15:51:54 +0200 Subject: [PATCH 0681/2366] mac802154: fix use-after-free of sdata via queued RX frames The RX softirq producer ieee802154_subif_frame() queues received beacon and MAC-command frames onto local->rx_beacon_list / rx_mac_cmd_list and schedules a process-context worker, storing a raw mac_pkt->sdata (and skb->dev == sdata->dev) with neither a reference nor any locking: - the lists have no lock: the softirq producer list_add_tail()s while the mac_wq worker list_del()s, so sibling interfaces on the same phy corrupt the list; - the workers dereference the interface after it may have been freed. mac802154_rx_mac_cmd_worker() touches mac_pkt->sdata directly, and mac802154_rx_beacon_worker() -> mac802154_process_beacon() dereferences skb->dev (== sdata->dev). Removing an interface frees its sdata (netdev_priv) while a queued frame still points at it, so a later worker run is a use-after-free. Reproduced under KASAN by flooding a victim interface with MAC command frames and removing it (the beacon path is the same class via skb->dev): BUG: KASAN: slab-use-after-free in mac802154_rx_mac_cmd_worker+0x463/0x630 [mac802154] Read of size 4 at addr ffff888002f9ea18 by task kworker/u8:1/31 Workqueue: phy0-mac-cmds mac802154_rx_mac_cmd_worker [mac802154] Call Trace: mac802154_rx_mac_cmd_worker+0x463/0x630 [mac802154] process_one_work+0x611/0xe80 worker_thread+0x52e/0xdc0 kthread+0x30c/0x630 ret_from_fork+0x2fd/0x3e0 Fix both lists together: - add local->rx_lock and take it around every list access: the softirq producer (plain spin_lock, softirq context) and the workers and flush (spin_lock_bh, process context); - pin the interface for the lifetime of a queued frame with netdev_hold()/netdev_put(), so the worker can safely dereference sdata / skb->dev even while the interface is being removed; - dequeue under the lock at the head and loop-drain the whole list in the workers (they previously processed one frame per run and relied on a later enqueue to drain the rest); - drop not-yet-started frames of an interface before it is unregistered, from ieee802154_if_remove() (after the RCU grace period) and from the ieee802154_remove_interfaces() loop -- the latter is the whole-phy teardown path, which does not go through ieee802154_if_remove(). An in-flight worker that already dequeued a frame keeps its own netdev reference; unregister_netdevice() then waits it out in netdev_run_todo(), which runs at rtnl_unlock() (rtnl released) and after the interface has been closed, so it does not pin rtnl. A worker blocked in an association TX only delays that one interface's unregister (the usual "waiting for %s to become free"), it does not hold rtnl. netdev_hold() is used for this reason instead of a cancel_work_sync() under rtnl, which would block on the worker's unbounded MLME TX wait via ieee802154_sync_queue(). The mac-command worker additionally skips processing for a stopped interface (ieee802154_sdata_running()), avoiding a needless association response during teardown. Fixes: 57588c71177f ("mac802154: Handle passive scanning") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Hashimov Assisted-by: AuditCode-AI:2026.07 Reviewed-by: Miquel Raynal Link: https://lore.kernel.org/20260725135154.99876-1-security@auditcode.ai Signed-off-by: Stefan Schmidt --- include/net/cfg802154.h | 1 + net/mac802154/ieee802154_i.h | 8 +++ net/mac802154/iface.c | 6 ++ net/mac802154/main.c | 1 + net/mac802154/rx.c | 120 ++++++++++++++++++++++++++++------- net/mac802154/scan.c | 10 +-- 6 files changed, 117 insertions(+), 29 deletions(-) diff --git a/include/net/cfg802154.h b/include/net/cfg802154.h index 76d2cd2e2b30..2e960441ea49 100644 --- a/include/net/cfg802154.h +++ b/include/net/cfg802154.h @@ -376,6 +376,7 @@ struct cfg802154_mac_pkt { struct list_head node; struct sk_buff *skb; struct ieee802154_sub_if_data *sdata; + netdevice_tracker dev_tracker; u8 page; u8 channel; }; diff --git a/net/mac802154/ieee802154_i.h b/net/mac802154/ieee802154_i.h index c53aa293a222..992ce6698c20 100644 --- a/net/mac802154/ieee802154_i.h +++ b/net/mac802154/ieee802154_i.h @@ -74,6 +74,10 @@ struct ieee802154_local { struct work_struct rx_beacon_work; struct list_head rx_mac_cmd_list; struct work_struct rx_mac_cmd_work; + /* Serializes rx_beacon_list and rx_mac_cmd_list against the RX + * softirq producer, the mac_wq workers and the teardown flush. + */ + spinlock_t rx_lock; /* Association */ /* assoc_lock protects assoc_dev_extended_addr, assoc_addr, @@ -305,6 +309,10 @@ static inline bool mac802154_is_beaconing(struct ieee802154_local *local) } void mac802154_rx_mac_cmd_worker(struct work_struct *work); +void mac802154_flush_list(struct list_head *list, + struct ieee802154_sub_if_data *sdata); +void mac802154_flush_queued_pkts(struct ieee802154_local *local, + struct ieee802154_sub_if_data *sdata); int mac802154_perform_association(struct ieee802154_sub_if_data *sdata, struct ieee802154_pan_device *coord, diff --git a/net/mac802154/iface.c b/net/mac802154/iface.c index b823720630e7..31353795fa24 100644 --- a/net/mac802154/iface.c +++ b/net/mac802154/iface.c @@ -694,6 +694,7 @@ void ieee802154_if_remove(struct ieee802154_sub_if_data *sdata) mutex_unlock(&sdata->local->iflist_mtx); synchronize_rcu(); + mac802154_flush_queued_pkts(sdata->local, sdata); unregister_netdevice(sdata->dev); } @@ -705,6 +706,11 @@ void ieee802154_remove_interfaces(struct ieee802154_local *local) list_for_each_entry_safe(sdata, tmp, &local->interfaces, list) { list_del_rcu(&sdata->list); + /* Best-effort: a frame the RX softirq queues for this sdata + * after the flush still pins the netdev, so the + * unregister_netdevice() below waits it out. + */ + mac802154_flush_queued_pkts(local, sdata); unregister_netdevice(sdata->dev); } mutex_unlock(&local->iflist_mtx); diff --git a/net/mac802154/main.c b/net/mac802154/main.c index 63e89bd586e3..8ed6de111f5a 100644 --- a/net/mac802154/main.c +++ b/net/mac802154/main.c @@ -91,6 +91,7 @@ ieee802154_alloc_hw(size_t priv_data_len, const struct ieee802154_ops *ops) INIT_LIST_HEAD(&local->interfaces); INIT_LIST_HEAD(&local->rx_beacon_list); INIT_LIST_HEAD(&local->rx_mac_cmd_list); + spin_lock_init(&local->rx_lock); mutex_init(&local->iflist_mtx); tasklet_setup(&local->tasklet, ieee802154_tasklet_handler); diff --git a/net/mac802154/rx.c b/net/mac802154/rx.c index cd8f2a11920d..19b5382e85a8 100644 --- a/net/mac802154/rx.c +++ b/net/mac802154/rx.c @@ -35,16 +35,23 @@ void mac802154_rx_beacon_worker(struct work_struct *work) container_of(work, struct ieee802154_local, rx_beacon_work); struct cfg802154_mac_pkt *mac_pkt; - mac_pkt = list_first_entry_or_null(&local->rx_beacon_list, - struct cfg802154_mac_pkt, node); - if (!mac_pkt) - return; + for (;;) { + spin_lock_bh(&local->rx_lock); + mac_pkt = list_first_entry_or_null(&local->rx_beacon_list, + struct cfg802154_mac_pkt, node); + if (mac_pkt) + list_del(&mac_pkt->node); + spin_unlock_bh(&local->rx_lock); + if (!mac_pkt) + break; - mac802154_process_beacon(local, mac_pkt->skb, mac_pkt->page, mac_pkt->channel); + mac802154_process_beacon(local, mac_pkt->skb, + mac_pkt->page, mac_pkt->channel); - list_del(&mac_pkt->node); - kfree_skb(mac_pkt->skb); - kfree(mac_pkt); + netdev_put(mac_pkt->sdata->dev, &mac_pkt->dev_tracker); + kfree_skb(mac_pkt->skb); + kfree(mac_pkt); + } } static bool mac802154_should_answer_beacon_req(struct ieee802154_local *local) @@ -68,22 +75,15 @@ static bool mac802154_should_answer_beacon_req(struct ieee802154_local *local) return interval == IEEE802154_ACTIVE_SCAN_DURATION; } -void mac802154_rx_mac_cmd_worker(struct work_struct *work) +static void mac802154_rx_mac_cmd(struct ieee802154_local *local, + struct cfg802154_mac_pkt *mac_pkt) { - struct ieee802154_local *local = - container_of(work, struct ieee802154_local, rx_mac_cmd_work); - struct cfg802154_mac_pkt *mac_pkt; u8 mac_cmd; int rc; - mac_pkt = list_first_entry_or_null(&local->rx_mac_cmd_list, - struct cfg802154_mac_pkt, node); - if (!mac_pkt) - return; - rc = ieee802154_get_mac_cmd(mac_pkt->skb, &mac_cmd); if (rc) - goto out; + return; switch (mac_cmd) { case IEEE802154_CMD_BEACON_REQ: @@ -121,11 +121,81 @@ void mac802154_rx_mac_cmd_worker(struct work_struct *work) default: break; } +} -out: - list_del(&mac_pkt->node); - kfree_skb(mac_pkt->skb); - kfree(mac_pkt); +void mac802154_rx_mac_cmd_worker(struct work_struct *work) +{ + struct ieee802154_local *local = + container_of(work, struct ieee802154_local, rx_mac_cmd_work); + struct cfg802154_mac_pkt *mac_pkt; + + for (;;) { + spin_lock_bh(&local->rx_lock); + mac_pkt = list_first_entry_or_null(&local->rx_mac_cmd_list, + struct cfg802154_mac_pkt, node); + if (mac_pkt) + list_del(&mac_pkt->node); + spin_unlock_bh(&local->rx_lock); + if (!mac_pkt) + break; + + /* A stopped interface cannot transmit; skipping avoids a + * needless association response (and the !netif_running() + * warning it would trip) during teardown. The beacon worker + * needs no such check as it never transmits. + */ + if (ieee802154_sdata_running(mac_pkt->sdata)) + mac802154_rx_mac_cmd(local, mac_pkt); + + netdev_put(mac_pkt->sdata->dev, &mac_pkt->dev_tracker); + kfree_skb(mac_pkt->skb); + kfree(mac_pkt); + } +} + +/** + * mac802154_flush_list - free queued RX frames on @list + * @list: rx_beacon_list or rx_mac_cmd_list + * @sdata: only free frames received on this interface, or %NULL for all + * + * Each frame pins the net_device it was received on (via netdev_hold()), + * so release that reference as the frame is dropped. Caller must hold + * local->rx_lock. + */ +void mac802154_flush_list(struct list_head *list, + struct ieee802154_sub_if_data *sdata) +{ + struct cfg802154_mac_pkt *mac_pkt, *tmp; + + list_for_each_entry_safe(mac_pkt, tmp, list, node) { + if (sdata && mac_pkt->sdata != sdata) + continue; + list_del(&mac_pkt->node); + netdev_put(mac_pkt->sdata->dev, &mac_pkt->dev_tracker); + kfree_skb(mac_pkt->skb); + kfree(mac_pkt); + } +} + +/** + * mac802154_flush_queued_pkts - drop queued RX work referencing @sdata + * @local: the mac802154 device + * @sdata: interface being removed + * + * The workers dereference the queued frame's interface directly + * (mac_pkt->sdata) or through skb->dev in mac802154_process_beacon(). Drop + * the not-yet-started entries belonging to @sdata before it is unregistered + * so their netdev reference is released; an entry already dequeued by a + * running worker keeps its own reference until the worker completes, which + * unregister_netdevice() then waits out. + */ +void mac802154_flush_queued_pkts(struct ieee802154_local *local, + struct ieee802154_sub_if_data *sdata) +{ + spin_lock_bh(&local->rx_lock); + mac802154_flush_list(&local->rx_beacon_list, sdata); + mac802154_flush_list(&local->rx_mac_cmd_list, sdata); + spin_unlock_bh(&local->rx_lock); } static int @@ -221,7 +291,10 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata, mac_pkt->sdata = sdata; mac_pkt->page = sdata->local->scan_page; mac_pkt->channel = sdata->local->scan_channel; + netdev_hold(sdata->dev, &mac_pkt->dev_tracker, GFP_ATOMIC); + spin_lock(&sdata->local->rx_lock); list_add_tail(&mac_pkt->node, &sdata->local->rx_beacon_list); + spin_unlock(&sdata->local->rx_lock); queue_work(sdata->local->mac_wq, &sdata->local->rx_beacon_work); return NET_RX_SUCCESS; @@ -233,7 +306,10 @@ ieee802154_subif_frame(struct ieee802154_sub_if_data *sdata, mac_pkt->skb = skb_get(skb); mac_pkt->sdata = sdata; + netdev_hold(sdata->dev, &mac_pkt->dev_tracker, GFP_ATOMIC); + spin_lock(&sdata->local->rx_lock); list_add_tail(&mac_pkt->node, &sdata->local->rx_mac_cmd_list); + spin_unlock(&sdata->local->rx_lock); queue_work(sdata->local->mac_wq, &sdata->local->rx_mac_cmd_work); return NET_RX_SUCCESS; diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c index dd156c01ac49..d393b1f4e74e 100644 --- a/net/mac802154/scan.c +++ b/net/mac802154/scan.c @@ -104,13 +104,9 @@ static unsigned int mac802154_scan_get_channel_time(u8 duration_order, static void mac802154_flush_queued_beacons(struct ieee802154_local *local) { - struct cfg802154_mac_pkt *mac_pkt, *tmp; - - list_for_each_entry_safe(mac_pkt, tmp, &local->rx_beacon_list, node) { - list_del(&mac_pkt->node); - kfree_skb(mac_pkt->skb); - kfree(mac_pkt); - } + spin_lock_bh(&local->rx_lock); + mac802154_flush_list(&local->rx_beacon_list, NULL); + spin_unlock_bh(&local->rx_lock); } static void From 2c1dde8a69a3a7d64425a6de0add6e289a5a402f Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Fri, 28 Aug 2026 11:08:11 +0530 Subject: [PATCH 0682/2366] powerpc/entry: Clear TIF_SYSCALL_RET before syscall error return Shivaprasad reported a boot failure due to userspace processes crash on abort() from libc.so.6. It was bisected to merge request commit '3424d8c18a7d ("Merge tag 'core-entry-2026-08-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip")' Upon checking the merge, when syscall_enter_from_user_mode_randomize_stack fails, which could happen when a tracer like seccomp or ptrace intercepts and skips the syscall, the code returns to userspace immediately without clearing the intermediate flag which was set. When the next syscall is made, it immediately aborts the valid syscall since the flag is still set. Hence clear the flag on occurrence of first failure. Reported-by: Shivaprasad G Bhat Closes: https://lore.kernel.org/all/e301014d-568f-4ed5-bc64-b8a85ca0b1e1@linux.ibm.com/ Fixes: 3424d8c18a7d ("Merge tag 'core-entry-2026-08-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip") Signed-off-by: Shrikanth Hegde Tested-by: Venkat Rao Bagalkote Tested-by: Shivaprasad G Bhat Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260828053811.1042300-1-sshegde@linux.ibm.com --- arch/powerpc/kernel/syscall.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/kernel/syscall.c b/arch/powerpc/kernel/syscall.c index 4916c205c4bb..fbefe1927b10 100644 --- a/arch/powerpc/kernel/syscall.c +++ b/arch/powerpc/kernel/syscall.c @@ -18,8 +18,10 @@ notrace long system_call_exception(struct pt_regs *regs, unsigned long r0) long ret; syscall_fn f; - if (unlikely(!syscall_enter_from_user_mode_randomize_stack(regs, &r0))) + if (unlikely(!syscall_enter_from_user_mode_randomize_stack(regs, &r0))) { + clear_thread_flag(TIF_SYSCALL_RET); return syscall_get_error(current, regs); + } if (unlikely(test_and_clear_thread_flag(TIF_SYSCALL_RET))) return syscall_get_error(current, regs); From c7585b8e99ad97a0f5dd21e45c90a33aeab0d92b Mon Sep 17 00:00:00 2001 From: "Ritesh Harjani (IBM)" Date: Sat, 29 Aug 2026 09:49:00 +0530 Subject: [PATCH 0683/2366] powerpc: Don't drop _TIF_RESTOREALL on syscall restart So the syscall return sequence is as follows: A syscall return to userspace is prepared and then a short asm sequence that actually does the RFI. Note that this asm range is restartable i.e. EE is still on, so an interrupt (e.g. decrementer or external interrupt) can hit while SRR/GPRs are being loaded. This is defined via: RESTART_TABLE(.Lsyscall_rst_start, .Lsyscall_rst_end, syscall_restart) This restart table then sends us to syscall_restart rather than resuming in the middle of the RFI. The same stub is also used if irq_happened already has a pending bit (soft-masked irq that has not been replayed yet (PowerPC special case of local_irq_disable())). Here is a bit of a flow of sequence of code to visualize: syscall_exit_prepare decide full-GPR restore (_TIF_RESTOREALL) for signal, rt_sigreturn or syscall trace save that in regs->exit_result and return it in r3 | v .Lsyscall_rst_start .. _end EE still on irq_happened set or interrupt in this range? | no | yes v v cmpdi r3,0 syscall_exit_restart restore all / zero replay irq, try exit again volatiles; RFI must return flags in r3 again for the same cmpdi Now r3 after prepare is the flags word, not the actual syscall return. A nested interrupt clobbers it, so the restart stub reloads RESULT into r3 and the C handler (syscall_exit_restart()) should put the flags back (because later asm checks whether r3 returned from C has _TIF_RESTOREALL set or not): cmpdi r3, 0 bne .Lsyscall_restore_regs Note that syscall_exit_restart() already ORs any new _TIF_RESTOREALL into exit_result, but then it only returns the new sample and not the full regs->exit_result. That sample could be often 0 even when restore-all is still required: - rt_sigreturn / syscall trace set the bit in prepare's local ret and in exit_result. They never set exit_flags, which is what restart samples. - a signal does set exit_flags but restart clears it. A second pass through the stub then returns 0 while exit_result still has the bit. The asm as mentioned earlier then treats r3==0 as the fast path and zeros r0/r4-r12. That means the userspace that needed the full register set could SIGSEGVs, (which could happen often in ld64.so.2 like while doing a parallel kernel build as reported by Venkat). So we should instead return the accumulated exit_result, like how we do in interrupt_exit_user_restart(). Note that prior to this commit 263e5159e00a ("powerpc: Fix exit_flags field placement in pt_regs for ptrace") we were returning regs->exit_result from syscall_exit_restart(), but this commit changed that behaviour. Fixes: 263e5159e00a ("powerpc: Fix exit_flags field placement in pt_regs for ptrace") Reported-by: Venkat Rao Bagalkote Closes: https://lore.kernel.org/all/75419f88-eab9-444b-bf97-28a9765819ad@linux.ibm.com/ Signed-off-by: Ritesh Harjani (IBM) Tested-by: Amit Machhiwal Tested-by: Shrikanth Hegde Tested-by: Venkat Rao Bagalkote Reviewed-by: Amit Machhiwal Reviewed-by: Shrikanth Hegde Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/10c86c909f870d90b3094f76b692b44ebe9caeac.1787976185.git.ritesh.list@gmail.com --- arch/powerpc/kernel/interrupt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/kernel/interrupt.c b/arch/powerpc/kernel/interrupt.c index 5b88bf72786c..55f9c0c9922a 100644 --- a/arch/powerpc/kernel/interrupt.c +++ b/arch/powerpc/kernel/interrupt.c @@ -175,7 +175,7 @@ notrace unsigned long syscall_exit_restart(unsigned long r3, struct pt_regs *reg current_thread_info()->exit_flags &= ~_TIF_RESTOREALL; regs->exit_result |= ret; - return ret; + return regs->exit_result; } #endif From c2549d749539487239475fbc8c614a1f9244d655 Mon Sep 17 00:00:00 2001 From: "Ritesh Harjani (IBM)" Date: Sun, 30 Aug 2026 20:24:30 +0530 Subject: [PATCH 0684/2366] powerpc: Do not restore KUAP in arch_exit_to_user_mode_prepare() KUAP means kernel cannot touch user memory unless it explicitly is enabled. In the kernel it should stay AMR_KUAP_BLOCKED. While returning to userspace just before RFI, kernel should restore the user AMR value back. Looks like GENERIC_ENTRY might be treating arch_exit_to_user_mode_prepare() as the last architecture step before returning to userspace. commit bee25f97ad24 ("powerpc: Enable GENERIC_ENTRY feature") therefore called kuap_user_restore() from that hook. But on PowerPC that is too early. After irqentry_exit() / syscall_exit_to_user_mode() we still run platform specific exit routines. e.g. code snippets showing both exception handling and system call handling as the callers of function arch_exit_to_user_mode_prepare() which does kuap_user_restore(). The below path shows that calling kuap_user_restore() is too early when called from arch_exit_to_user_mode_prepare(). Exception handling in exceptions-64s.S ======================================= bl CFUNC(do_page_fault) ..DEFINE_INTERRUPT_HANDLER_ASYNC(do_page_fault) arch_interrupt_async_enter_prepare(regs); state = irqentry_enter(regs); instrumentation_begin(); irq_enter_rcu(); handler(regs); nap_adjust_return(regs); irq_exit_rcu(); instrumentation_end(); arch_interrupt_async_exit_prepare(regs); irqentry_exit(regs, state); <<< too early irqentry_exit_to_user_mode() __exit_to_user_mode_prepare(regs, EXIT_TO_USER_MODE_WORK_IRQ); arch_exit_to_user_mode_prepare(regs, ti_work); <<< too early b interrupt_return_srr .. bl CFUNC(interrupt_exit_user_prepare) <<< already calls kuap_user_restore prep_irq_for_enabled_exit() retry can run kernel code with IRQs on. So only when that routine is fully finished is when the user KUAP should be fully restored which interrupt_exit_user_prepare() already takes care of before returning. Similarly for system call handling in interrupt_64.S ====================================================== bl CFUNC(system_call_exception) .Lsyscall_exit: addi r4,r1,STACK_INT_FRAME_REGS li r5,0 /* !scv */ bl CFUNC(syscall_exit_prepare) .. kuap_assert_locked(); syscall_exit_to_user_mode(regs); <<< too early syscall_exit_to_user_mode_prepare(regs); <<< too early kuap_user_restore(regs); <<< already calls syscall_exit_prepare(), which can enable IRQs, replay a pending interrupt, and only then rfi. Those functions already restore KUAP immediately before rfi. Note that if we restore the user AMR too early like in the current code as shown from the code snippets above, then we get the following warning when CONFIG_PPC_KUAP_DEBUG is enabled: WARNING: arch/powerpc/include/asm/book3s/64/kup.h:293 at interrupt_exit_user_prepare+0x1a0/0x1c0 Hardware name: IBM pSeries (emulated by qemu) POWER10 (architected) TRAP: 0700 LR: c00000000000d8d4 CTR: c0000000021fe500 MSR: CR: 44000804 XER: 20040000 interrupt_exit_user_prepare+0x1a0/0x1c0 interrupt_return_srr_user+0x8/0x12c Fixes: bee25f97ad24 ("powerpc: Enable GENERIC_ENTRY feature") Fixes: 02565a782c1ee ("powerpc: Introduce syscall exit arch functions") Signed-off-by: Ritesh Harjani (IBM) Tested-by: Venkat Rao Bagalkote Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/52fee44fd23acf8e1c024ace668728e626a783a8.1788101609.git.ritesh.list@gmail.com --- arch/powerpc/include/asm/entry-common.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/arch/powerpc/include/asm/entry-common.h b/arch/powerpc/include/asm/entry-common.h index c5adb5006361..94083516df57 100644 --- a/arch/powerpc/include/asm/entry-common.h +++ b/arch/powerpc/include/asm/entry-common.h @@ -515,8 +515,14 @@ static inline void arch_exit_to_user_mode_prepare(struct pt_regs *regs, #ifdef CONFIG_PPC_TRANSACTIONAL_MEM local_paca->tm_scratch = regs->msr; #endif - /* Restore user access locks last */ - kuap_user_restore(regs); + /* + * Do not restore KUAP here. Generic entry might treat this as the last + * arch step before userspace but PowerPC still has kernel work after + * irqentry_exit()/syscall_exit_to_user_mode() i.e. in + * interrupt_exit_user_prepare() / syscall_exit_prepare() may enable + * IRQs and retry. Those functions restore KUAP immediately before rfi, + * which is where it should belong. + */ } #define arch_exit_to_user_mode_prepare arch_exit_to_user_mode_prepare From 7db28abbea0f7dc1ec4fdfdc149db5fbd9e4c994 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 30 Aug 2026 14:28:27 +0200 Subject: [PATCH 0685/2366] net: airoha: enable RX_DONE interrupt for RX queue 31 RX queue 31 has always been allocated and filled by airoha_qdma_init_rx() since RX_DONE_INT_MASK spans queues 0-31, but none of the RX_IRQ* _BANK_PIN_MASK values covered BIT(31). As a consequence the RX_DONE interrupt for queue 31 was never enabled, airoha_qdma_rx_process() never ran on that queue and its buffers were never reaped. Route RX queue 31's RX_DONE interrupt to IRQ bank 1 so that the queue is drained and its buffers returned to the page pool. Fixes: f252493e1835 ("net: airoha: Enable multiple IRQ lines support in airoha_eth driver.") Signed-off-by: Lorenzo Bianconi Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260830-airoha-rxdone-rxq31-v1-1-830a91503f2f@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/airoha/airoha_eth.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index fa9a8edce22f..8277c1c87bb3 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -538,7 +538,7 @@ struct airoha_wdma_info { /* RX queue to IRQ mapping: BIT(q) in IRQ(n) */ #define RX_IRQ0_BANK_PIN_MASK 0x839f -#define RX_IRQ1_BANK_PIN_MASK 0x7fe00000 +#define RX_IRQ1_BANK_PIN_MASK 0xffe00000 #define RX_IRQ2_BANK_PIN_MASK 0x20 #define RX_IRQ3_BANK_PIN_MASK 0x40 #define RX_IRQ_BANK_PIN_MASK(_n) \ From 6b8fed2675fb75d23e6cf2b7e49c94926e884b34 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 31 Aug 2026 19:06:38 +0200 Subject: [PATCH 0686/2366] net: stmmac: reconfigure RX packet parser table in stmmac_hw_setup() after reset The core software reset issued in stmmac_init_dma_engine() during ndo_open() callback clears the MTL RX packet parser registers, but stmmac_rxp_config() is only invoked from the cls_u32 add/delete paths. After an ifdown/ifup cycle the hardware therefore runs with the default all-pass table while priv->tc_entries still reports the filters as installed. Re-apply the RX packet parser table from priv->tc_entries in stmmac_hw_setup(), right after the software reset, so the filters are restored when the interface is brought up again. Fixes: 4dbbe8dde848 ("net: stmmac: Add support for U32 TC filter using Flexible RX Parser") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260831-stmmac_tc_cls32_reconfigure-v1-1-21cb459e64ae@oss.qualcomm.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index d576059c04df..24656b35350b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3676,6 +3676,14 @@ static int stmmac_hw_setup(struct net_device *dev) /* Initialize MTL*/ stmmac_mtl_configuration(priv); + /* Apply the RX packet parser table */ + if (priv->tc_entries) { + ret = stmmac_rxp_config(priv, priv->hw->pcsr, priv->tc_entries, + priv->tc_entries_max); + if (ret) + return ret; + } + /* Initialize Safety Features */ stmmac_safety_feat_configuration(priv); From 2ae16aaa78b5edc6e6d0904c84fd9cdfb762bcda Mon Sep 17 00:00:00 2001 From: Quanye Yang Date: Sun, 30 Aug 2026 15:09:55 +0800 Subject: [PATCH 0687/2366] RDMA/rtrs-clt: Fix CQ pool leak when connect is interrupted The client borrows shared CQ credits in the ADDR_RESOLVED handler via ib_cq_pool_get(), before the peer is connected. create_cm() can return -ERESTARTSYS from wait_event_interruptible_timeout() without destroying the CM ID. The init_conns() and stop-and-destroy paths then call destroy_con_cq_qp() while cq is still NULL (no PUT) and only afterwards rdma_destroy_id(). CMA serializes the handler against rdma_destroy_id() with handler_mutex, but that does not order the GET against destroy_con_cq_qp(). If ADDR_RESOLVED has already passed the DESTROYING check, it can take con_mutex, GET credits, and then lose the con to kfree. Device unregister later hits WARN_ON(cq->cqe_used) in ib_cq_pool_cleanup(). Set a per-connection flag under con_mutex before CQ/QP teardown so a racing ADDR_RESOLVED cannot borrow credits after teardown has begun. Reported-by: syzbot+d396918a29afb8543e1c@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d396918a29afb8543e1c Fixes: 3b89e92c2a95 ("RDMA/rtrs: Use new shared CQ mechanism") Signed-off-by: Quanye Yang Link: https://patch.msgid.link/20260830-rdma-rtrs-clt-cq-pool-leak-v1-1-b169434fd3df@proton.me Reviewed-by: Jack Wang Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/rtrs/rtrs-clt.c | 8 ++++++++ drivers/infiniband/ulp/rtrs/rtrs-clt.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/drivers/infiniband/ulp/rtrs/rtrs-clt.c b/drivers/infiniband/ulp/rtrs/rtrs-clt.c index 7b2c51ae614f..eac38b57b00d 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-clt.c +++ b/drivers/infiniband/ulp/rtrs/rtrs-clt.c @@ -1732,6 +1732,8 @@ static void destroy_con_cq_qp(struct rtrs_clt_con *con) /* * Be careful here: destroy_con_cq_qp() can be called even * create_con_cq_qp() failed, see comments there. + * Caller must set con->destroyed under this lock first so a + * racing ADDR_RESOLVED cannot ib_cq_pool_get() after we PUT/SKIP. */ lockdep_assert_held(&con->con_mutex); rtrs_cq_qp_destroy(&con->c); @@ -1766,6 +1768,10 @@ static int rtrs_rdma_addr_resolved(struct rtrs_clt_con *con) int err; mutex_lock(&con->con_mutex); + if (con->destroyed) { + mutex_unlock(&con->con_mutex); + return -ECONNABORTED; + } err = create_con_cq_qp(con); mutex_unlock(&con->con_mutex); if (err) { @@ -2221,6 +2227,7 @@ static void rtrs_clt_stop_and_destroy_conns(struct rtrs_clt_path *clt_path) break; con = to_clt_con(clt_path->s.con[cid]); mutex_lock(&con->con_mutex); + con->destroyed = true; destroy_con_cq_qp(con); mutex_unlock(&con->con_mutex); destroy_cm(con); @@ -2387,6 +2394,7 @@ static int init_conns(struct rtrs_clt_path *clt_path) if (con->c.cm_id) { stop_cm(con); mutex_lock(&con->con_mutex); + con->destroyed = true; destroy_con_cq_qp(con); mutex_unlock(&con->con_mutex); destroy_cm(con); diff --git a/drivers/infiniband/ulp/rtrs/rtrs-clt.h b/drivers/infiniband/ulp/rtrs/rtrs-clt.h index 1305601a6251..ad64f4517c4b 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-clt.h +++ b/drivers/infiniband/ulp/rtrs/rtrs-clt.h @@ -75,6 +75,8 @@ struct rtrs_clt_con { unsigned int cpu; struct mutex con_mutex; int cm_err; + /* Set under con_mutex before CQ/QP teardown. */ + bool destroyed; }; /** From 66817a9794263cd2a5dc4e99bf8e5fcc5ff7181e Mon Sep 17 00:00:00 2001 From: HW He Date: Tue, 1 Sep 2026 16:23:12 +0800 Subject: [PATCH 0688/2366] net: gro: Fix nesting of TCP GSO SKBs in skb_gro_receive_list() Fraglist GRO and hardware GRO can create an fraglist of HW-GRO packets. This cannot be segmented back into the original form on TCP tethering scenario. Avoid constructing such a GSO packet, by flushing an already built fraglist GRO packet if a hardware GRO packet arrives. Scenario (Tethering/Forwarding): 1.Driver submits a single TCP packet, P1. P1 is kept in the gro_list as the first packet. 2. The driver submits a TCP GSO skb, P2. P2 has already aggregated multiple TCP packets by HW_GRO, and its non-linear data is stored in frags[]. 3. P1 and P2 match the GRO rules, and since there is no local socket, they are aggregated by skb_gro_receive_list(). The resulting skb, P3, has a frag_list entry that still contains frags[]: P3: [ Linear Data ] -> frag_list -> [ Linear Data ] [ frag[1] ] [ frag[2] ] ... 4. Later, tcp4_gso_segment() or tcp6_gso_segment() calls skb_segment_list() to segment P3. However, skb_segment_list() only segments the entries in frag_list. It does not segment the frags[] inside P2, so P3 is not restored to the original packets, which leads to IP fragmentation or packet drop in the following path. Check skb_is_gso(skb) and current GRO method, make sure fraglist GRO applies to consecutive non-GSO skb, others adopt regular GRO path. Fixes: 8d95dc474f85 ("net: add code for TCP fraglist GRO") Signed-off-by: Zhaoping Shu Signed-off-by: HW He Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260901082312.14596-1-zhaoping.shu@mediatek.com Signed-off-by: Paolo Abeni --- net/ipv4/tcp_offload.c | 22 ++++++++++++++++------ net/ipv6/tcpv6_offload.c | 15 +++++++++++++-- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/net/ipv4/tcp_offload.c b/net/ipv4/tcp_offload.c index 3b1fdcd3cb29..e74d99ca9fac 100644 --- a/net/ipv4/tcp_offload.c +++ b/net/ipv4/tcp_offload.c @@ -332,6 +332,7 @@ struct sk_buff *tcp_gro_receive(struct list_head *head, struct sk_buff *skb, flush |= skb->ip_summed != p->ip_summed; flush |= skb->csum_level != p->csum_level; flush |= NAPI_GRO_CB(p)->count >= 64; + flush |= NAPI_GRO_CB(p)->is_flist != NAPI_GRO_CB(skb)->is_flist; skb_set_network_header(skb, skb_gro_receive_network_offset(skb)); if (flush || skb_gro_receive_list(p, skb)) @@ -395,12 +396,20 @@ static void tcp4_check_fraglist_gro(struct list_head *head, struct sk_buff *skb, struct net *net; int iif, sdif; - if (likely(!(skb->dev->features & NETIF_F_GRO_FRAGLIST))) - return; - p = tcp_gro_lookup(head, th); if (p) { - NAPI_GRO_CB(skb)->is_flist = NAPI_GRO_CB(p)->is_flist; + /* flist GRO applies to consecutive non-GSO skbs */ + if (!skb_is_gso(skb) || !NAPI_GRO_CB(p)->is_flist) { + NAPI_GRO_CB(skb)->is_flist = NAPI_GRO_CB(p)->is_flist; + return; + } + + /* Fall back to the regular GRO path */ + if (NAPI_GRO_CB(p)->count == 1) + NAPI_GRO_CB(p)->is_flist = 0; + + NAPI_GRO_CB(skb)->is_flist = 0; + return; } @@ -410,7 +419,7 @@ static void tcp4_check_fraglist_gro(struct list_head *head, struct sk_buff *skb, sk = __inet_lookup_established(net, iph->saddr, th->source, iph->daddr, ntohs(th->dest), iif, sdif); - NAPI_GRO_CB(skb)->is_flist = !sk; + NAPI_GRO_CB(skb)->is_flist = !sk && !skb_is_gso(skb); if (sk) sock_gen_put(sk); } @@ -430,7 +439,8 @@ struct sk_buff *tcp4_gro_receive(struct list_head *head, struct sk_buff *skb) if (!th) goto flush; - tcp4_check_fraglist_gro(head, skb, th); + if (unlikely(skb->dev->features & NETIF_F_GRO_FRAGLIST)) + tcp4_check_fraglist_gro(head, skb, th); return tcp_gro_receive(head, skb, th); diff --git a/net/ipv6/tcpv6_offload.c b/net/ipv6/tcpv6_offload.c index f2a659cd6183..eec3778855eb 100644 --- a/net/ipv6/tcpv6_offload.c +++ b/net/ipv6/tcpv6_offload.c @@ -26,7 +26,18 @@ static void tcp6_check_fraglist_gro(struct list_head *head, struct sk_buff *skb, p = tcp_gro_lookup(head, th); if (p) { - NAPI_GRO_CB(skb)->is_flist = NAPI_GRO_CB(p)->is_flist; + /* flist GRO applies to consecutive non-GSO skbs */ + if (!skb_is_gso(skb) || !NAPI_GRO_CB(p)->is_flist) { + NAPI_GRO_CB(skb)->is_flist = NAPI_GRO_CB(p)->is_flist; + return; + } + + /* Fall back to the regular GRO path */ + if (NAPI_GRO_CB(p)->count == 1) + NAPI_GRO_CB(p)->is_flist = 0; + + NAPI_GRO_CB(skb)->is_flist = 0; + return; } @@ -36,7 +47,7 @@ static void tcp6_check_fraglist_gro(struct list_head *head, struct sk_buff *skb, sk = __inet6_lookup_established(net, &hdr->saddr, th->source, &hdr->daddr, ntohs(th->dest), iif, sdif); - NAPI_GRO_CB(skb)->is_flist = !sk; + NAPI_GRO_CB(skb)->is_flist = !sk && !skb_is_gso(skb); if (sk) sock_gen_put(sk); #endif /* IS_ENABLED(CONFIG_IPV6) */ From a77644d009dece1104b6fcc6e322b0e4503db0d6 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 28 Aug 2026 19:41:31 +0200 Subject: [PATCH 0689/2366] arm64: mm: Fix the lockless page-table walk in show_pte() show_pte() walks page tables locklessly and can run with interrupts enabled. A concurrent teardown can free a table page while it is being walked. It can also clear a parent entry after show_pte() checked it; the regular pXd_offset() helpers then reread the cleared entry and can derive a bogus lower-level pointer and fault again. Use the lockless offset helpers with the saved parent entries, as gup_fast() does, and pass the saved PMD to pte_offset_map(). For task page tables, arm64 selects MMU_GATHER_RCU_TABLE_FREE. Disable local interrupts around the walk to hold off RCU-deferred table frees and block the tlb_remove_table_sync_one() IPI until the walk is finished. Place the IRQ guard after the header print. This does not make the output a consistent snapshot, but prevents the task page-table walk from dereferencing a released table page or deriving a pointer from a different parent value. Fixes: 1d18c47c735e ("arm64: MMU fault handling and page table management") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Will Deacon --- arch/arm64/mm/fault.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c index 0b52557652be..75c3e463df2e 100644 --- a/arch/arm64/mm/fault.c +++ b/arch/arm64/mm/fault.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -154,6 +155,9 @@ static void show_pte(unsigned long addr) pr_alert("%s pgtable: %luk pages, %llu-bit VAs, pgdp=%016lx\n", mm == &init_mm ? "swapper" : "user", PAGE_SIZE / SZ_1K, vabits_actual, mm_to_pgd_phys(mm)); + + guard(irqsave)(); + pgdp = pgd_offset(mm, addr); pgd = READ_ONCE(*pgdp); pr_alert("[%016lx] pgd=%016llx", addr, pgd_val(pgd)); @@ -167,25 +171,25 @@ static void show_pte(unsigned long addr) if (pgd_none(pgd) || pgd_bad(pgd)) break; - p4dp = p4d_offset(pgdp, addr); + p4dp = p4d_offset_lockless(pgdp, pgd, addr); p4d = READ_ONCE(*p4dp); pr_cont(", p4d=%016llx", p4d_val(p4d)); if (p4d_none(p4d) || p4d_bad(p4d)) break; - pudp = pud_offset(p4dp, addr); + pudp = pud_offset_lockless(p4dp, p4d, addr); pud = READ_ONCE(*pudp); pr_cont(", pud=%016llx", pud_val(pud)); if (pud_none(pud) || pud_bad(pud)) break; - pmdp = pmd_offset(pudp, addr); + pmdp = pmd_offset_lockless(pudp, pud, addr); pmd = READ_ONCE(*pmdp); pr_cont(", pmd=%016llx", pmd_val(pmd)); if (pmd_none(pmd) || pmd_bad(pmd)) break; - ptep = pte_offset_map(pmdp, addr); + ptep = pte_offset_map(&pmd, addr); if (!ptep) break; From 1537e55728ec2bc506c74ea69b93cd859da58fb8 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 28 Aug 2026 02:28:18 -0700 Subject: [PATCH 0690/2366] arm64: trans_pgd: clone only the linear map that exists at runtime kexec_file_load() fails on arm64 if we have CONFIG_ARM64_VA_BITS_52 but it runs on a !FEAT_LPA2 host (such as my loving Grace machine). That is because trans_pgd_create_copy() uses the compile time PAGE_OFFSET (VA 52) instead of the actual VA size (48 -- due to the lack of LPA2). With the fifth level folded, pgd_none() is always false, so the walk cannot skip the 15 extra PGDIR_SIZE slots, and they all alias back to the same table: the whole kernel page table gets cloned 16 times, KASAN shadow included. Without KASAN it does not blow up, it just wastes ~RAM/32 in page tables. Fix it by copying the linear map that is the actual one, not the compiled one. Fixes: a6bbf5d4d9d1 ("arm64: mm: Add definitions to support 5 levels of paging") Signed-off-by: Breno Leitao Tested-by: Yury Smirnov Signed-off-by: Will Deacon --- arch/arm64/kernel/machine_kexec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kernel/machine_kexec.c b/arch/arm64/kernel/machine_kexec.c index c5693a32e49b..8f9bc2327dc8 100644 --- a/arch/arm64/kernel/machine_kexec.c +++ b/arch/arm64/kernel/machine_kexec.c @@ -129,7 +129,8 @@ int machine_kexec_post_load(struct kimage *kimage) } /* Create a copy of the linear map */ - rc = trans_pgd_create_copy(&info, &trans_pgd, PAGE_OFFSET, PAGE_END); + rc = trans_pgd_create_copy(&info, &trans_pgd, + _PAGE_OFFSET(vabits_actual), PAGE_END); if (rc) return rc; kimage->arch.ttbr1 = __pa(trans_pgd); From 5541432e09dc2031978188f3e8a00b9fc78cf097 Mon Sep 17 00:00:00 2001 From: Khushit Shah Date: Mon, 31 Aug 2026 10:54:44 +0000 Subject: [PATCH 0691/2366] arm64: errata: pass REVIDR when matching target implementation CPUs When target implementation CPUs are provided, is_affected_midr_range() accidentally passed the MIDR as both arguments to __is_affected_midr_range(), so the REVIDR mask check operated on the wrong register. Pass REVIDR as intended. Fixes: 86edf6bdcf05 ("smccc/kvm_guest: Enable errata based on implementation CPUs") Cc: stable@vger.kernel.org Signed-off-by: Khushit Shah Reviewed-by: Zenghui Yu (Huawei) Acked-by: Marc Zyngier Reviewed-by: Shameer Kolothum Signed-off-by: Will Deacon --- arch/arm64/kernel/cpu_errata.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c index 5db8f0619e4b..b33dccfafaf8 100644 --- a/arch/arm64/kernel/cpu_errata.c +++ b/arch/arm64/kernel/cpu_errata.c @@ -82,7 +82,7 @@ is_affected_midr_range(const struct arm64_cpu_capabilities *entry, int scope) for (i = 0; i < target_impl_cpu_num; i++) { if (__is_affected_midr_range(entry, target_impl_cpus[i].midr, - target_impl_cpus[i].midr)) + target_impl_cpus[i].revidr)) return true; } return false; From 5445d64199626974269fcdf347769ad44b0bb53b Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Thu, 27 Aug 2026 19:59:37 +0100 Subject: [PATCH 0692/2366] arm64: Don't read GMID_EL1 when MTE is disabled __cpuinfo_store_cpu() gates the GMID_EL1 read on the raw ID_AA64PFR1_EL1, so it reads the register even when the kernel has disabled MTE (CONFIG_ARM64_MTE=n or arm64.nomte). KVM sets HCR_EL2.TID5 in that case, and pKVM injects an UNDEF the host cannot handle: Internal error: Oops - Undefined instruction: 0000000002000000 [#1] SMP pc : __cpuinfo_store_cpu+0xf4/0x264 Kernel panic - not syncing: Attempted to kill the idle task! Only pKVM reaches it, and only after a CPU is offlined and brought back online: its CPU_ON relay sets the host HCR before the CPU enters EL1, while plain nVHE sets it at CPUHP_AP_KVM_ONLINE. Gate the read on the CPU's own ID_AA64PFR1_EL1 with the command-line override applied, and on CONFIG_ARM64_MTE, which no register reflects. The boot CPU stores its registers before init_cpu_features() strips an unsafe override, so clamp against the hardware value here too. Fixes: f35abcbb8a084 ("KVM: arm64: Trap MTE access and discovery when MTE is disabled") Cc: stable@vger.kernel.org Signed-off-by: Fuad Tabba Reviewed-by: Catalin Marinas Signed-off-by: Will Deacon --- arch/arm64/include/asm/cpu.h | 1 + arch/arm64/include/asm/cpufeature.h | 7 ------ arch/arm64/kernel/cpufeature.c | 33 +++++++++++++++++++++++++---- arch/arm64/kernel/cpuinfo.c | 2 +- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/arch/arm64/include/asm/cpu.h b/arch/arm64/include/asm/cpu.h index 71493b760b83..3c008821219c 100644 --- a/arch/arm64/include/asm/cpu.h +++ b/arch/arm64/include/asm/cpu.h @@ -78,5 +78,6 @@ void __init cpuinfo_store_boot_cpu(void); void __init init_cpu_features(struct cpuinfo_arm64 *info); void update_cpu_features(int cpu, struct cpuinfo_arm64 *info, struct cpuinfo_arm64 *boot); +bool gmid_el1_accessible(const struct cpuinfo_arm64 *info); #endif /* __ASM_CPU_H */ diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h index 7404a6e83a93..4f04ad82ea34 100644 --- a/arch/arm64/include/asm/cpufeature.h +++ b/arch/arm64/include/asm/cpufeature.h @@ -627,13 +627,6 @@ static inline bool id_aa64pfr1_mpamfrac(u64 pfr1) return val > 0; } -static inline bool id_aa64pfr1_mte(u64 pfr1) -{ - u32 val = cpuid_feature_extract_unsigned_field(pfr1, ID_AA64PFR1_EL1_MTE_SHIFT); - - return val >= ID_AA64PFR1_EL1_MTE_MTE2; -} - void __init setup_boot_cpu_features(void); void __init setup_system_features(void); void __init setup_user_features(void); diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 17b83a2518a8..32102c3912fa 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -1178,6 +1178,33 @@ static bool detect_ftr_has_mpam(void) return id_aa64pfr0_mpam(pfr0) || id_aa64pfr1_mpamfrac(pfr1); } +bool gmid_el1_accessible(const struct cpuinfo_arm64 *info) +{ + const struct arm64_ftr_bits *ftrp; + s64 mte, ovr; + u64 ftr_mask; + + /* No ID register reflects CONFIG_ARM64_MTE. */ + if (!IS_ENABLED(CONFIG_ARM64_MTE)) + return false; + + for (ftrp = ftr_id_aa64pfr1; ftrp->width; ftrp++) { + if (ftrp->shift == ID_AA64PFR1_EL1_MTE_SHIFT) + break; + } + + ftr_mask = arm64_ftr_mask(ftrp); + mte = arm64_ftr_value(ftrp, info->reg_id_aa64pfr1); + + /* The boot CPU runs before init_cpu_ftr_reg() strips unsafe overrides. */ + if ((id_aa64pfr1_override.mask & ftr_mask) == ftr_mask) { + ovr = arm64_ftr_value(ftrp, id_aa64pfr1_override.val); + mte = arm64_ftr_safe_value(ftrp, ovr, mte); + } + + return mte >= ID_AA64PFR1_EL1_MTE_MTE2; +} + void __init init_cpu_features(struct cpuinfo_arm64 *info) { /* Before we start using the tables, make sure it is sorted */ @@ -1230,7 +1257,7 @@ void __init init_cpu_features(struct cpuinfo_arm64 *info) init_cpu_ftr_reg(SYS_MPAMIDR_EL1, info->reg_mpamidr); } - if (id_aa64pfr1_mte(info->reg_id_aa64pfr1)) + if (gmid_el1_accessible(info)) init_cpu_ftr_reg(SYS_GMID_EL1, info->reg_gmid); } @@ -1492,11 +1519,9 @@ void update_cpu_features(int cpu, * they read/write depends on the GMID_EL1.BS field. Check that the * value is the same on all CPUs. */ - if (IS_ENABLED(CONFIG_ARM64_MTE) && - id_aa64pfr1_mte(info->reg_id_aa64pfr1)) { + if (gmid_el1_accessible(info)) taint |= check_update_ftr_reg(SYS_GMID_EL1, cpu, info->reg_gmid, boot->reg_gmid); - } /* * If we don't have AArch32 at all then skip the checks entirely diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c index d50e2a9b066b..45c63f3d75c5 100644 --- a/arch/arm64/kernel/cpuinfo.c +++ b/arch/arm64/kernel/cpuinfo.c @@ -502,7 +502,7 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info) info->reg_id_aa64smfr0 = read_cpuid(ID_AA64SMFR0_EL1); info->reg_id_aa64fpfr0 = read_cpuid(ID_AA64FPFR0_EL1); - if (id_aa64pfr1_mte(info->reg_id_aa64pfr1)) + if (gmid_el1_accessible(info)) info->reg_gmid = read_cpuid(GMID_EL1); if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) From bc69439d983cc491cc86e01fafc1deb94e1bb85e Mon Sep 17 00:00:00 2001 From: Yudi Yang <2000jedi@gmail.com> Date: Tue, 1 Sep 2026 14:55:11 -0500 Subject: [PATCH 0693/2366] drm/rockchip: analogix_dp: fix unchecked bound endpoint name length rockchip_dp_drm_encoder_enable() uses sprintf() to format a device tree path into a 32-byte stack buffer. Device tree paths are not limited to this size, so a sufficiently long path can overflow the buffer. Use snprintf() with the destination size to truncate the generated name and keep the writes within bounds. Fixes: 729f8eefdcad ("drm/rockchip: analogix_dp: Add support for RK3588") Cc: stable@vger.kernel.org Signed-off-by: Yudi Yang <2000jedi@gmail.com> Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260901195511.2761251-1-2000jedi@gmail.com --- drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index 587e60232ec7..efd5a98e80bd 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -241,10 +241,11 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder, of_graph_get_remote_port(endpoint.local_node); of_property_read_u32(remote_port, "reg", &port_id); - sprintf(name, "%s vp%d", remote_port_parent->full_name, port_id); + snprintf(name, sizeof(name), "%s vp%d", + remote_port_parent->full_name, port_id); } else { - sprintf(name, "%s %s", - remote_port_parent->full_name, endpoint.id ? "vopl" : "vopb"); + snprintf(name, sizeof(name), "%s %s", + remote_port_parent->full_name, endpoint.id ? "vopl" : "vopb"); } DRM_DEV_DEBUG(dp->dev, "vop %s output to dp\n", (ret) ? "LIT" : "BIG"); From 6d81700ad7c4871f94fb72e469cb0f3f55843ef7 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Wed, 2 Sep 2026 11:08:28 +0900 Subject: [PATCH 0694/2366] ata: libata-scsi: do not raise UA for storage element depopulation and restoration The libata command completion for the ATA commands REMOVE ELEMENT AND TRUNCATE and RESTORE ELEMENTS AND REBUILD is handled using the function ata_scsi_depop_ua_cap_changed_complete(). This completion function raises a UNIT ATTENTION with the additional sense code CAPACITY DATA HAS CHANGED. But doing so, the scsi layer seeing the UNIT ATTENTION sense key ends up failing the command, even if the command result is in fact OK. The SAT specifications do provide more details about the capacity change should be notified, and that relies on the ACCESSIBLE CAPACITY field of the IDENTIFY DATA retrieved before or after the command is issued, and then raising a UNIT ATTENTION if the capacity has really changed. However, we do not have any simple mean to raise a unit attention from libata-scsi. So rather than seeing the REMOVE ELEMENT AND TRUNCATE and RESTORE ELEMENTS AND REBUILD commands failing, remove the function ata_scsi_depop_ua_cap_changed_complete() and rely on the regular completion callback. Since for now these commands can only be issued as passthrough commands, the user is responsible for revalidating the device capacity after executing these commands. Fixes: db496721cb0d ("ata: libata-scsi: add support for the REMOVE ELEMENT AND TRUNCATE command") Fixes: 1e307ca61a9c ("ata: libata-scsi: add support for the RESTORE ELEMENTS AND REBUILD command") Signed-off-by: Damien Le Moal Link: https://lore.kernel.org/r/20260902020828.1436048-1-dlemoal@kernel.org Signed-off-by: Niklas Cassel --- drivers/ata/libata-scsi.c | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index a7d667cfcfec..b3666519b648 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -4823,28 +4823,6 @@ ata_scsi_get_phys_element_status_xlat(struct ata_queued_cmd *qc) return 0; } -static void ata_scsi_depop_ua_cap_changed_complete(struct ata_queued_cmd *qc) -{ - struct scsi_cmnd *scmd = qc->scsicmd; - u8 *cdb = scmd->cmnd; - bool is_ata_passthru = cdb[0] == ATA_16 || cdb[0] == ATA_12; - bool is_success = qc->err_mask == 0; - - /* - * For successful non-passthrough commands, raise a UNIT ATTENTION with - * the additional sense code set to CAPACITY DATA HAS CHANGED to be - * raised. Note that this should be done only if the capacity has - * actually changed, which may not be the case if the element that was - * specified for depopulation was already depopulated, or we did not - * restore any removed element. But a capacity change unit attention is - * harmless, so always raise the unit attention. - */ - if (is_success && !is_ata_passthru) - ata_scsi_set_sense(qc->dev, scmd, UNIT_ATTENTION, - UA_CHANGED_ASC, CAPACITY_CHANGED_ASCQ); - ata_scsi_qc_complete(qc); -} - static unsigned int ata_scsi_remove_element_and_truncate_xlat(struct ata_queued_cmd *qc) { @@ -4884,7 +4862,6 @@ ata_scsi_remove_element_and_truncate_xlat(struct ata_queued_cmd *qc) tf->flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE | ATA_TFLAG_LBA48; qc->flags |= ATA_QCFLAG_RESULT_TF; - qc->complete_fn = ata_scsi_depop_ua_cap_changed_complete; return 0; } @@ -4937,7 +4914,6 @@ ata_scsi_restore_elements_and_rebuild_xlat(struct ata_queued_cmd *qc) tf->flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE | ATA_TFLAG_LBA48; qc->flags |= ATA_QCFLAG_RESULT_TF; - qc->complete_fn = ata_scsi_depop_ua_cap_changed_complete; return 0; } From 6365c44a824ff138e7926413932bb5c2e28a4c8c Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Tue, 1 Sep 2026 16:54:42 +0100 Subject: [PATCH 0695/2366] ring-buffer: Allow splice reads on static buffers ring_buffer_read_page() rejects splice (full=1) reads on static buffers (that is user-mapped, persistent or remote) because !read check assumes unread pages must be swapped. However for those buffers we have no other choice than memcpy the data. For the memcpy case, only return an error when the writer is still on the reader page for the splice interface to wait. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260901155445.1475405-2-vdonnefort@google.com Fixes: 117c39200d9d ("ring-buffer: Introducing ring-buffer mapping functions") Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index b0963ac6fd16..84fd4cdd486f 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -7193,15 +7193,8 @@ int ring_buffer_read_page(struct trace_buffer *buffer, unsigned int event_size; unsigned int flags = 0; - /* - * If a full page is expected, this can still be returned - * if there's been a previous partial read and the - * rest of the page can be read and the commit page is off - * the reader page. - */ - if (full && - (!read || (len < (size - read)) || - cpu_buffer->reader_page == cpu_buffer->commit_page)) + /* If a full page is requested, it cannot be the commit page */ + if (full && cpu_buffer->reader_page == cpu_buffer->commit_page) return -1; if (len > (size - read)) From 42d3358bf145f27d32350037e67d3527e9098c1e Mon Sep 17 00:00:00 2001 From: Fredric Cover Date: Wed, 2 Sep 2026 17:49:01 -0700 Subject: [PATCH 0696/2366] smb: client: fill cache fields after populating cache in copy_ref_data() In copy_ref_data(), struct cache_entry *ce has its fields populated at the beginning of the function. Later, if alloc_target fails with an ERR_PTR, free_tgts() is called on the cache, leaving the cache metadata populated without any targets. Critically, this extends ce->etime, making the cache appear valid for longer without any targets. Also, free_tgts() does not set ce->numtgts to zero. On error, when the cache is freed, ce->numtgts is not zeroed, and other cache users may attempt to access nonexistent entries. Update fields after copying targets to prevent partial-state updates. Set ce->numtgts to zero at the end of free_tgts(). Signed-off-by: Fredric Cover Signed-off-by: Paulo Alcantara --- fs/smb/client/dfs_cache.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c index 86dba25b7a5a..611e18fe8204 100644 --- a/fs/smb/client/dfs_cache.c +++ b/fs/smb/client/dfs_cache.c @@ -123,6 +123,7 @@ static inline void free_tgts(struct cache_entry *ce) kfree(t); } + ce->numtgts = 0; WRITE_ONCE(ce->tgthint, NULL); } @@ -388,13 +389,6 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs, struct cache_dfs_tgt *target; int i; - ce->ttl = max_t(int, refs[0].ttl, CACHE_MIN_TTL); - ce->etime = get_expire_time(ce->ttl); - ce->srvtype = refs[0].server_type; - ce->hdr_flags = refs[0].flags; - ce->ref_flags = refs[0].ref_flag; - ce->path_consumed = refs[0].path_consumed; - for (i = 0; i < numrefs; i++) { struct cache_dfs_tgt *t; @@ -409,12 +403,19 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs, } else { list_add_tail(&t->list, &ce->tlist); } - ce->numtgts++; } target = list_first_entry_or_null(&ce->tlist, struct cache_dfs_tgt, list); + WRITE_ONCE(ce->tgthint, target); + ce->ttl = max_t(int, refs[0].ttl, CACHE_MIN_TTL); + ce->etime = get_expire_time(ce->ttl); + ce->srvtype = refs[0].server_type; + ce->hdr_flags = refs[0].flags; + ce->ref_flags = refs[0].ref_flag; + ce->path_consumed = refs[0].path_consumed; + ce->numtgts = numrefs; return 0; } @@ -634,7 +635,6 @@ static int update_cache_entry_locked(struct cache_entry *ce, const struct dfs_in } free_tgts(ce); - ce->numtgts = 0; rc = copy_ref_data(refs, numrefs, ce, th); From 82e664cf1219c459c33aae931b222cf951af9cb7 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Thu, 3 Sep 2026 22:28:41 +0800 Subject: [PATCH 0697/2366] erofs: disable LZ4 rolling decompression for now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LZ4 rolling decompression [1] was introduced to reduce the memory footprint of temporary pages: For many cases, it is needed for users to read small data within a compressed extent (pcluster), either due to random small read, or since uptodate folios (typically order-0) cannot be reused for decompression again since decompression algorithm refills already-uptodate folios. Rolling decompression works because LZ4 is LZ77-based and only refers to the most recent 64 KiB of decompressed data, so in theory only a bounded rolling window of temporary pages is needed when decompressing. It can save a lot of temporary memory, e.g. 601,960-byte data can be compressed into a 256k LZ4 compressed extent, which means it needs 146 extra pages per request in the worst case if rolling decompression is disabled. However, the upstream LZ4 implementation is not under EROFS' control: For example, the literal copy memmove() may still **copy long literals backward** on x86 based on the address comparison even when the source and destination ranges do not overlap (IOWs, inline decompression doesn't need to be considered here). That breaks the rolling assumption and makes the optimization broken. Disable it for now to make sure the data correctness first since EROFS is used everywhere now: The rolling window approach can be revived once we either ensure that the official LZ4 code always copies forward for non-overlapping ranges or maintain our own LZ4 implementation in EROFS. The main impact is a higher runtime memory footprint; However, recent commit 0f6273ab4637 ("erofs: add a reserved buffer pool for lz4 decompression") helps mitigate this when enabled but it's still not perfect. [1] https://www.usenix.org/conference/atc19/presentation/gao § 3.3 Decompression Reported-by: "Walther, Jens-Uwe" Closes: https://lore.kernel.org/r/BEZP281MB2102E57CD31862B8D958B33DD2AC2@BEZP281MB2102.DEUP281.PROD.OUTLOOK.COM Fixes: 8e6c8fa9f2e9 ("erofs: enable big pcluster feature") Cc: Yann Collet Signed-off-by: Gao Xiang --- fs/erofs/decompressor.c | 55 +++++++++-------------------------------- fs/erofs/internal.h | 6 +---- fs/erofs/zdata.c | 18 +++----------- 3 files changed, 16 insertions(+), 63 deletions(-) diff --git a/fs/erofs/decompressor.c b/fs/erofs/decompressor.c index 27caf4bebddc..d387b27c4ee2 100644 --- a/fs/erofs/decompressor.c +++ b/fs/erofs/decompressor.c @@ -7,8 +7,6 @@ #include "compress.h" #include -#define LZ4_MAX_DISTANCE_PAGES (DIV_ROUND_UP(LZ4_DISTANCE_MAX, PAGE_SIZE) + 1) - static int z_erofs_load_lz4_config(struct super_block *sb, struct erofs_super_block *dsb, void *data, int size) { @@ -21,8 +19,6 @@ static int z_erofs_load_lz4_config(struct super_block *sb, erofs_err(sb, "invalid lz4 cfgs, size=%u", size); return -EINVAL; } - distance = le16_to_cpu(lz4->max_distance); - sbi->lz4.max_pclusterblks = le16_to_cpu(lz4->max_pclusterblks); if (!sbi->lz4.max_pclusterblks) { sbi->lz4.max_pclusterblks = 1; /* reserved case */ @@ -39,45 +35,25 @@ static int z_erofs_load_lz4_config(struct super_block *sb, sbi->lz4.max_pclusterblks = 1; sbi->available_compr_algs = 1 << Z_EROFS_COMPRESSION_LZ4; } - - sbi->lz4.max_distance_pages = distance ? - DIV_ROUND_UP(distance, PAGE_SIZE) + 1 : - LZ4_MAX_DISTANCE_PAGES; return z_erofs_gbuf_growsize(sbi->lz4.max_pclusterblks); } /* - * Fill all gaps with bounce pages if it's a sparse page list. Also check if - * all physical pages are consecutive, which can be seen for moderate CR. + * Fill all gaps with bounce pages if it's a sparse page list (for example some + * folios are already uptodate and thus can be mapped into userspace). Also + * check if pages are physically consecutive, which can be seen for moderate CR. */ -static int z_erofs_lz4_prepare_dstpages(struct z_erofs_decompress_req *rq, - struct page **pagepool) +static int z_erofs_oneshot_prepare_dstpages(struct z_erofs_decompress_req *rq, + struct page **pagepool) { - struct page *availables[LZ4_MAX_DISTANCE_PAGES] = { NULL }; - unsigned long bounced[DIV_ROUND_UP(LZ4_MAX_DISTANCE_PAGES, - BITS_PER_LONG)] = { 0 }; - unsigned int lz4_max_distance_pages = - EROFS_SB(rq->sb)->lz4.max_distance_pages; void *kaddr = NULL; - unsigned int i, j, top; + unsigned int i; - top = 0; - for (i = j = 0; i < rq->outpages; ++i, ++j) { - struct page *const page = rq->out[i]; - struct page *victim; - - if (j >= lz4_max_distance_pages) - j = 0; - - /* 'valid' bounced can only be tested after a complete round */ - if (!rq->fillgaps && test_bit(j, bounced)) { - DBG_BUGON(i < lz4_max_distance_pages); - DBG_BUGON(top >= lz4_max_distance_pages); - availables[top++] = rq->out[i - lz4_max_distance_pages]; - } + for (i = 0; i < rq->outpages; ++i) { + struct page *page, *victim; + page = rq->out[i]; if (page) { - __clear_bit(j, bounced); if (!PageHighMem(page)) { if (!i) { kaddr = page_address(page); @@ -89,21 +65,14 @@ static int z_erofs_lz4_prepare_dstpages(struct z_erofs_decompress_req *rq, continue; } } - kaddr = NULL; - continue; - } - kaddr = NULL; - __set_bit(j, bounced); - - if (top) { - victim = availables[--top]; } else { victim = __erofs_allocpage(pagepool, rq->gfp, true); if (!victim) return -ENOMEM; set_page_private(victim, Z_EROFS_SHORTLIVED_PAGE); + rq->out[i] = victim; } - rq->out[i] = victim; + kaddr = NULL; } return kaddr ? 1 : 0; } @@ -266,7 +235,7 @@ static const char *z_erofs_lz4_decompress(struct z_erofs_decompress_req *rq, dst_maptype = 0; } else { /* general decoding path which can be used for all cases */ - ret = z_erofs_lz4_prepare_dstpages(rq, pagepool); + ret = z_erofs_oneshot_prepare_dstpages(rq, pagepool); if (ret < 0) return ERR_PTR(ret); if (ret > 0) { diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h index 65974e57aebf..12e3a5b80a5a 100644 --- a/fs/erofs/internal.h +++ b/fs/erofs/internal.h @@ -71,12 +71,8 @@ struct erofs_dev_context { bool flatdev; }; -/* all filesystem-wide lz4 configurations */ struct erofs_sb_lz4_info { - /* # of pages needed for EROFS lz4 rolling decompression */ - u16 max_distance_pages; - /* maximum possible blocks for pclusters in the filesystem */ - u16 max_pclusterblks; + u16 max_pclusterblks; /* maximum physical blocks for LZ4 pclusters */ }; struct erofs_xattr_prefix_item { diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c index e1e25ca0d190..6b07e73ee2aa 100644 --- a/fs/erofs/zdata.c +++ b/fs/erofs/zdata.c @@ -1259,7 +1259,7 @@ static int z_erofs_decompress_pcluster(struct z_erofs_backend *be, bool eio) const struct z_erofs_decompressor *alg = z_erofs_decomp[pcl->algorithmformat]; bool try_free = true; - int i, j, jtop, err2, err = eio ? -EIO : 0; + int i, err2, err = eio ? -EIO : 0; struct page *page; bool overlapped; const char *reason; @@ -1348,7 +1348,6 @@ static int z_erofs_decompress_pcluster(struct z_erofs_backend *be, bool eio) be->compressed_pages >= be->onstack_pages + Z_EROFS_ONSTACK_PAGES) kvfree(be->compressed_pages); - jtop = 0; z_erofs_fill_other_copies(be, err); for (i = 0; i < be->nr_pages; ++i) { page = be->decompressed_pages[i]; @@ -1356,22 +1355,11 @@ static int z_erofs_decompress_pcluster(struct z_erofs_backend *be, bool eio) continue; DBG_BUGON(z_erofs_page_is_invalidated(page)); - if (!z_erofs_is_shortlived_page(page)) { + if (!z_erofs_is_shortlived_page(page)) erofs_onlinefolio_end(page_folio(page), err, true); - continue; - } - if (pcl->algorithmformat != Z_EROFS_COMPRESSION_LZ4) { + else erofs_pagepool_add(be->pagepool, page); - continue; - } - for (j = 0; j < jtop && be->decompressed_pages[j] != page; ++j) - ; - if (j >= jtop) /* this bounce page is newly detected */ - be->decompressed_pages[jtop++] = page; } - while (jtop) - erofs_pagepool_add(be->pagepool, - be->decompressed_pages[--jtop]); if (be->decompressed_pages != be->onstack_pages) kvfree(be->decompressed_pages); From d1fc569fcbc7be6de06b034cf954a95e30ca1fb2 Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Mon, 27 Jul 2026 01:26:15 -0700 Subject: [PATCH 0698/2366] dmaengine: mmp_pdma: fix wrong extended DRCMR base for SpacemiT K3 The extended DRCMR window on SpacemiT K3 starts at 0x1100. Commit 6587b8661a0b ("dmaengine: mmp_pdma: add SpacemiT K3 support") incorrectly set it to 0x1000, causing DRCMR accesses for request IDs >= 64 to target offsets 0x100 too low. The 0x1100 base has been verified on K3 silicon using real SPI and QSPI DMA transactions. The K3 DMA documentation [1] was updated on June 24, 2026, to reflect the corrected register addresses. Drop the bogus DRCMR_EXT_BASE_K3 macro and reuse DRCMR_EXT_BASE_DEFAULT for the K3 ops. Fixes: 6587b8661a0b ("dmaengine: mmp_pdma: add SpacemiT K3 support") Link: https://www.spacemit.com/community/document/info?nodepath=hardware/key_stone/k3/k3_docs/k3_usermanual/16_peripherals/dma.md&lang=en [1] Signed-off-by: Troy Mitchell Reviewed-by: Frank Li Link: https://patch.msgid.link/20260727-k3-pdma-fix-drcmr-base-v2-1-afba55cba1f3@linux.spacemit.com Signed-off-by: Vinod Koul --- drivers/dma/mmp_pdma.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c index 386e85cd4882..78e3e07e681d 100644 --- a/drivers/dma/mmp_pdma.c +++ b/drivers/dma/mmp_pdma.c @@ -52,7 +52,6 @@ #define DCSR_EORINTR BIT(9) /* The end of Receive */ #define DRCMR_BASE 0x0100 -#define DRCMR_EXT_BASE_K3 0x1000 #define DRCMR_EXT_BASE_DEFAULT 0x1100 #define DRCMR_REQ_LIMIT 64 #define DRCMR_MAPVLD BIT(7) /* Map Valid (read / write) */ @@ -1219,7 +1218,7 @@ static const struct mmp_pdma_ops spacemit_k3_pdma_ops = { .get_desc_dst_addr = get_desc_dst_addr_64, .run_bits = (DCSR_RUN | DCSR_LPAEEN | DCSR_EORIRQEN | DCSR_EORSTOPEN), .dma_width = 64, - .drcmr_ext_base = DRCMR_EXT_BASE_K3, + .drcmr_ext_base = DRCMR_EXT_BASE_DEFAULT, }; static const struct of_device_id mmp_pdma_dt_ids[] = { From 0294b6dd515256c03ea2dbf508ddd3826d788579 Mon Sep 17 00:00:00 2001 From: Alexander Chesnokov Date: Wed, 12 Aug 2026 08:34:26 +0300 Subject: [PATCH 0699/2366] dmaengine: ti: k3-udma-glue: fix NULL dereference in k3_udma_glue_release_rx_chn() If devm_kcalloc() for rx_chn->flows fails in a channel request function, the error path calls k3_udma_glue_release_rx_chn(), which dereferences the NULL rx_chn->flows pointer in k3_udma_glue_release_rx_flow(). Skip the flow release loop in k3_udma_glue_release_rx_chn() when rx_chn->flows is not allocated. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: d70241913413 ("dmaengine: ti: k3-udma: Add glue layer for non DMAengine users") Cc: stable@vger.kernel.org Reported-by: Pavel Zhigulin Signed-off-by: Alexander Chesnokov Reviewed-by: Frank Li Link: https://patch.msgid.link/20260812053426.3521589-1-Alexander.Chesnokov@kaspersky.com Signed-off-by: Vinod Koul --- drivers/dma/ti/k3-udma-glue.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/dma/ti/k3-udma-glue.c b/drivers/dma/ti/k3-udma-glue.c index 686dc140293e..70eaf7ee57e6 100644 --- a/drivers/dma/ti/k3-udma-glue.c +++ b/drivers/dma/ti/k3-udma-glue.c @@ -1243,8 +1243,9 @@ void k3_udma_glue_release_rx_chn(struct k3_udma_glue_rx_channel *rx_chn) rx_chn->psil_paired = false; } - for (i = 0; i < rx_chn->flow_num; i++) - k3_udma_glue_release_rx_flow(rx_chn, i); + if (rx_chn->flows) + for (i = 0; i < rx_chn->flow_num; i++) + k3_udma_glue_release_rx_flow(rx_chn, i); if (xudma_rflow_is_gp(rx_chn->common.udmax, rx_chn->flow_id_base)) xudma_free_gp_rflow_range(rx_chn->common.udmax, From a7df136ec529ee49a789c5029bc37b98b0d4bedd Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 13 Aug 2026 23:31:49 +0800 Subject: [PATCH 0700/2366] dmaengine: sprd: Fix runtime PM reference leak in probe pm_runtime_get_sync() increments a device's usage counter even when it fails. sprd_dma_probe() currently jumps directly to controller clock cleanup on that error, bypassing both pm_runtime_put_noidle() and pm_runtime_disable(). This can happen if the preceding unchecked pm_runtime_set_active() fails and the following runtime-resume attempt also returns an error. Enter the existing runtime-PM unwind path instead. This drops the reference without idling the partially initialized device, disables runtime PM, and then releases the controller clocks. The success path and propagated error code are unchanged. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 9b3b8171f7f4 ("dmaengine: sprd: Add Spreadtrum DMA driver") Signed-off-by: Ruoyu Wang Reviewed-by: Frank Li Reviewed-by: Baolin Wang Link: https://patch.msgid.link/20260813153149.3953497-1-ruoyuw560@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/sprd-dma.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dma/sprd-dma.c b/drivers/dma/sprd-dma.c index 087fea3af2e4..19b32a23c882 100644 --- a/drivers/dma/sprd-dma.c +++ b/drivers/dma/sprd-dma.c @@ -1212,7 +1212,7 @@ static int sprd_dma_probe(struct platform_device *pdev) ret = pm_runtime_get_sync(&pdev->dev); if (ret < 0) - goto err_rpm; + goto err_register; ret = dma_async_device_register(&sdev->dma_dev); if (ret < 0) { @@ -1234,7 +1234,6 @@ static int sprd_dma_probe(struct platform_device *pdev) err_register: pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); -err_rpm: sprd_dma_disable(sdev); return ret; } From 8efd5f623c63584c2e284a837a7795d95a0491cb Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Thu, 3 Sep 2026 21:38:32 +0900 Subject: [PATCH 0701/2366] selftests/alsa: Fix the step check for INTEGER controls The modulo sits inside the subtraction, so the check evaluates int_val - (min % step) rather than (int_val - min) % step. The INTEGER64 branch below it is parenthesised correctly. The written form passes only when the value equals min % step, and such a value is always on a step boundary, so it never misses a real violation. It only reports valid values as invalid. snd-aloop declares step 1 on four controls, so every non-zero value on them is reported. Before: # PCM Rate Shift 100000.0 value 100000 invalid for step 1 minimum 80000 # Totals: pass:660 fail:101 xfail:0 xpass:0 skip:296 error:0 After, same card, nothing else changed: # Totals: pass:740 fail:21 xfail:0 xpass:0 skip:296 error:0 Eighteen files under sound/ declare a non-zero step. Fixes: 5aaf9efffc57 ("kselftest: alsa: Add simplistic test for ALSA mixer controls kselftest") Signed-off-by: HyeongJun An Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260903123832.97377-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/mixer-test.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index a329f901c5ed..0857d64c322a 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -319,8 +319,8 @@ static bool ctl_value_index_valid(struct ctl_data *ctl, /* Only check step size if there is one and we're in bounds */ if (snd_ctl_elem_info_get_step(ctl->info) && - (int_val - snd_ctl_elem_info_get_min(ctl->info) % - snd_ctl_elem_info_get_step(ctl->info))) { + (int_val - snd_ctl_elem_info_get_min(ctl->info)) % + snd_ctl_elem_info_get_step(ctl->info)) { ksft_print_msg("%s.%d value %ld invalid for step %ld minimum %ld\n", ctl->name, index, int_val, snd_ctl_elem_info_get_step(ctl->info), From 3b26ceef88c110f4d188387cffa0df78657be904 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 Sep 2026 12:38:51 +0200 Subject: [PATCH 0702/2366] ALSA: caiaq: Fix potential double-free at error path The fix for caiaq driver's resource management to handle the errors tries to release the resources in a common destructor call, but as a sashiko review for another patch suggested, some of the audio resources such as URBs have been already freed, and this may lead to a double-free. For addressing the double-free, call the common destructor function from each place, and assure that the resource pointers get cleared. Link: https://sashiko.dev/#/patchset/20260903084747.535367-1-eadavis%40sina.com Fixes: 28abd224db4a ("ALSA: caiaq: Handle probe errors properly") Link: https://patch.msgid.link/20260903103855.1807838-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/caiaq/audio.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/usb/caiaq/audio.c b/sound/usb/caiaq/audio.c index ba3f73455ebe..bb6280aa3533 100644 --- a/sound/usb/caiaq/audio.c +++ b/sound/usb/caiaq/audio.c @@ -828,16 +828,13 @@ int snd_usb_caiaq_audio_init(struct snd_usb_caiaqdev *cdev) cdev->data_urbs_in = alloc_urbs(cdev, SNDRV_PCM_STREAM_CAPTURE, &ret); if (ret < 0) { - kfree(cdev->data_cb_info); - free_urbs(cdev->data_urbs_in); + snd_usb_caiaq_audio_free(cdev); return ret; } cdev->data_urbs_out = alloc_urbs(cdev, SNDRV_PCM_STREAM_PLAYBACK, &ret); if (ret < 0) { - kfree(cdev->data_cb_info); - free_urbs(cdev->data_urbs_in); - free_urbs(cdev->data_urbs_out); + snd_usb_caiaq_audio_free(cdev); return ret; } @@ -858,6 +855,9 @@ void snd_usb_caiaq_audio_free(struct snd_usb_caiaqdev *cdev) dev_dbg(dev, "%s(%p)\n", __func__, cdev); free_urbs(cdev->data_urbs_in); + cdev->data_urbs_in = NULL; free_urbs(cdev->data_urbs_out); + cdev->data_urbs_out = NULL; kfree(cdev->data_cb_info); + cdev->data_cb_info = NULL; } From c83e3e806d4c115f1dad9ef756db4cd91448a262 Mon Sep 17 00:00:00 2001 From: Igor Paunovic Date: Thu, 13 Aug 2026 16:40:18 +0200 Subject: [PATCH 0703/2366] drm/rockchip: dw_dp: Select DRM_BRIDGE_CONNECTOR dw_dp-rockchip.c calls drm_bridge_connector_init(), but ROCKCHIP_DW_DP does not select DRM_BRIDGE_CONNECTOR. A configuration with ROCKCHIP_DW_DP as the only enabled Rockchip output option fails to link: aarch64-linux-gnu-ld: drivers/gpu/drm/rockchip/dw_dp-rockchip.o: in function `dw_dp_rockchip_bind': dw_dp-rockchip.c:(.text+0x1d4): undefined reference to `drm_bridge_connector_init' Five other Rockchip encoder options that call drm_bridge_connector_init() (ROCKCHIP_ANALOGIX_DP, ROCKCHIP_CDN_DP, ROCKCHIP_DW_HDMI_QP, ROCKCHIP_LVDS, ROCKCHIP_RGB) already select it, which masks the gap in any configuration that enables one of them. ROCKCHIP_INNO_HDMI is covered through its DRM_INNO_HDMI core option. The same change was posted by Marius Dinu in March and dropped when the failure stopped reproducing in his build. The failure is configuration-dependent - any other enabled option that selects DRM_BRIDGE_CONNECTOR hides it - and it still reproduces on current drm-misc-next with the configuration described above. Select DRM_BRIDGE_CONNECTOR like the other users do. Fixes: d68ba7bac955 ("drm/rockchip: Add RK3588 DPTX output support") Link: https://lore.kernel.org/r/aneNCDU12OzG99UX@venus # ack to handle this apart from the dw-dp series Link: https://lore.kernel.org/r/20260319155051.1944-1-m95d+git@psihoexpert.ro # earlier submission by Marius Dinu Signed-off-by: Igor Paunovic Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260813144019.12089-2-royalnet026@gmail.com --- drivers/gpu/drm/rockchip/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index e7f49fe845ea..697c0748eeca 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -68,6 +68,7 @@ config ROCKCHIP_CDN_DP config ROCKCHIP_DW_DP bool "Rockchip specific extensions for Synopsys DW DP" + select DRM_BRIDGE_CONNECTOR help This selects support for Rockchip SoC specific extensions to enable Synopsys DesignWare Cores based DisplayPort transmit From d72aa5cf045a69d5fd433cde5dd4e113d9558fd1 Mon Sep 17 00:00:00 2001 From: Igor Paunovic Date: Thu, 13 Aug 2026 16:40:19 +0200 Subject: [PATCH 0704/2366] drm/rockchip: rk3066_hdmi: Add missing Kconfig selects rk3066_hdmi.c calls drm_bridge_connector_init(), but ROCKCHIP_RK3066_HDMI selects neither DRM_BRIDGE_CONNECTOR nor DRM_DISPLAY_HELPER, whose module carries the bridge-connector code. A configuration with ROCKCHIP_RK3066_HDMI as the only enabled Rockchip output option fails to link: aarch64-linux-gnu-ld: drivers/gpu/drm/rockchip/rk3066_hdmi.o: in function `rk3066_hdmi_bind': rk3066_hdmi.c:(.text+0x7a4): undefined reference to `drm_bridge_connector_init' aarch64-linux-gnu-ld: drivers/gpu/drm/rockchip/rk3066_hdmi.o: in function `rk3066_hdmi_bridge_atomic_enable': rk3066_hdmi.c:(.text+0xe74): undefined reference to `drm_atomic_helper_connector_hdmi_update_infoframes' Select both, like ROCKCHIP_CDN_DP, ROCKCHIP_LVDS and ROCKCHIP_RGB do. DRM_BRIDGE_CONNECTOR in turn selects DRM_DISPLAY_HDMI_STATE_HELPER, which resolves the second symbol. Fixes: 57d6811e8a6d ("drm/rockchip: rk3066_hdmi: switch to drm bridge") Signed-off-by: Igor Paunovic Signed-off-by: Heiko Stuebner Link: https://patch.msgid.link/20260813144019.12089-3-royalnet026@gmail.com --- drivers/gpu/drm/rockchip/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 697c0748eeca..4e58685f58ff 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -146,6 +146,8 @@ config ROCKCHIP_RGB config ROCKCHIP_RK3066_HDMI bool "Rockchip specific extensions for RK3066 HDMI" depends on DRM_ROCKCHIP + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR help This selects support for Rockchip SoC specific extensions for the RK3066 HDMI driver. If you want to enable From 0895a0c0734703be5532f3883c42db95615fd98b Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Tue, 1 Sep 2026 18:47:35 +0800 Subject: [PATCH 0705/2366] bpf: Reject key-less BTF for hash maps map_check_btf() allows a key-less BTF (btf_key_type_id == 0) only for maps that have a ->map_check_btf callback, and leaves the actual decision to that callback. Hash maps used to have no ->map_check_btf, so a key-less BTF was rejected outright. That changed when htab and rhtab gained a ->map_check_btf to register a dtor - htab in commit 1df97a7453ee ("bpf: Register dtor for freeing special fields") and rhtab in commit 6905f8601298 ("bpf: Allow special fields in resizable hashtab"). Neither looks at the key, so a key-less hash map now passes map_check_btf() and gets created. Reading it back through bpffs feeds the key type_id 0 into btf_type_seq_show(); btf_type_by_id() returns the void type, kind_ops[BTF_KIND_UNKN] is NULL, and btf_type_show() dereferences it: RIP: 0010:btf_type_show+0x223/0x2e0 kernel/bpf/btf.c:8232 RSP: 0018:ffffc9000399f868 EFLAGS: 00010206 RAX: dffffc0000000000 RBX: 0000000000000000 RCX: 0000000000000000 RDX: 0000000000000005 RSI: 0000000000000000 RDI: 0000000000000028 RBP: 0000000000000000 R08: 0000000000000001 R09: 0000000000000000 R10: ffffc9000399f970 R11: 0000000000000001 R12: ffffffff9b96b140 R13: ffffc9000399f8e0 R14: ffff88803d393c00 R15: 0000000000000003 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000200000000000 CR3: 000000003d213000 CR4: 0000000000352ef0 DR0: 0000000039ae8f55 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400 Call Trace: btf_type_seq_show_flags+0xca/0x120 kernel/bpf/btf.c:8250 htab_map_seq_show_elem+0x12e/0x350 kernel/bpf/hashtab.c:1669 map_seq_show+0x13d/0x1e0 kernel/bpf/inode.c:293 traverse.part.0.constprop.0+0x107/0x650 fs/seq_file.c:112 traverse fs/seq_file.c:99 [inline] seq_read_iter+0x93f/0x1270 fs/seq_file.c:196 seq_read+0x344/0x4d0 fs/seq_file.c:163 vfs_read+0x1e4/0xb40 fs/read_write.c:572 ksys_pread64 fs/read_write.c:764 [inline] __do_sys_pread64 fs/read_write.c:772 [inline] __se_sys_pread64 fs/read_write.c:769 [inline] __x64_sys_pread64+0x1eb/0x250 fs/read_write.c:769 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline] do_syscall_64+0x123/0x790 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f Reject a key-less BTF in htab_map_check_btf() and rhtab_map_check_btf(), restoring the previous behavior. Fixes: 1df97a7453ee ("bpf: Register dtor for freeing special fields") Fixes: 6905f8601298 ("bpf: Allow special fields in resizable hashtab") Reported-by: syzbot+37b56485bbbf90ad8489@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a8f4e88.27659fcc.2ceef7.0008.GAE@google.com/T/ Signed-off-by: Jiayuan Chen Acked-by: Ihor Solodrai Link: https://lore.kernel.org/r/20260901104924.346187-2-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- kernel/bpf/hashtab.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index d8db1cebc193..e89fde188389 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -530,6 +530,9 @@ static int htab_map_check_btf(struct bpf_map *map, const struct btf *btf, { struct bpf_htab *htab = container_of(map, struct bpf_htab, map); + if (btf_type_is_void(key_type)) + return -EINVAL; + if (htab_is_prealloc(htab)) return 0; /* @@ -3111,6 +3114,9 @@ static int rhtab_map_check_btf(struct bpf_map *map, const struct btf *btf, { struct bpf_rhtab *rhtab = container_of(map, struct bpf_rhtab, map); + if (btf_type_is_void(key_type)) + return -EINVAL; + return bpf_ma_set_dtor(map, &rhtab->ma, rhtab_mem_dtor); } From 4ea508b9ebd78bce7f212166d2e2cba66b875f08 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Tue, 1 Sep 2026 18:47:36 +0800 Subject: [PATCH 0706/2366] bpf: Fix NULL-ptr-deref when showing a void BTF type btf_modifier_show() resolves the modifier and then calls btf_type_ops(t)->show() unconditionally. For the void type (type_id 0, BTF_KIND_UNKN) kind_ops[] has no entry, so ->show is NULL. A "const void" (a modifier resolving to void) cannot be a map key or value - map_check_btf() rejects it because void has no size - so the map dump path does not reach it. But bpf_snprintf_btf() takes a type_id straight from the BPF program, and passing such a "const void" from the vmlinux BTF NULL-derefs: KASAN: null-ptr-deref in range [0x0000000000000028-0x000000000000002f] RIP: 0010:btf_modifier_show (kernel/bpf/btf.c:2914) Call Trace: btf_type_show (kernel/bpf/btf.c:8251) btf_type_snprintf_show (kernel/bpf/btf.c:8321) bpf_snprintf_btf (kernel/trace/bpf_trace.c:1047) bpf_prog_test_run_raw_tp (net/bpf/test_run.c:829) __sys_bpf (kernel/bpf/syscall.c:4804) do_syscall_64 (arch/x86/entry/syscall_64.c:94) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Fall back to btf_df_show() when the resolved type has no show op; it emits the "" placeholder already used for kinds like FWD and FUNC. bpf_snprintf_btf() then returns the length as usual. Fixes: c4d0bfb45068 ("bpf: Add bpf_snprintf_btf helper") Signed-off-by: Jiayuan Chen Acked-by: Ihor Solodrai Link: https://lore.kernel.org/r/20260901104924.346187-3-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- kernel/bpf/btf.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index da36d4b9d31a..df5f0d059ad1 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -2911,7 +2911,14 @@ static void btf_modifier_show(const struct btf *btf, else t = btf_type_skip_modifiers(btf, type_id, NULL); - btf_type_ops(t)->show(btf, t, type_id, data, bits_offset, show); + /* + * A modifier can resolve to void, which has no show op; print a + * placeholder rather than dereferencing NULL. + */ + if (!btf_type_ops(t)) + btf_df_show(btf, t, type_id, data, bits_offset, show); + else + btf_type_ops(t)->show(btf, t, type_id, data, bits_offset, show); } static void btf_var_show(const struct btf *btf, const struct btf_type *t, From 5403a383f52fc0905703b488f7c3db4b2447dc58 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Tue, 1 Sep 2026 18:47:37 +0800 Subject: [PATCH 0707/2366] bpf: Fix NULL-ptr-deref in btf_var_show() btf_var_show() calls btf_type_id_resolve() unconditionally, which dereferences btf->resolved_ids. That is NULL for a base BTF - e.g. the vmlinux BTF that bpf_snprintf_btf() renders against - since base BTF is not resolved during parsing. btf_modifier_show() guards this with 'if (btf->resolved_ids)', but btf_var_show() does not. A BPF program that passes the type_id of a BTF_KIND_VAR from the vmlinux BTF to bpf_snprintf_btf() thus NULL-derefs: KASAN: probably user-memory-access in range [0x46638-0x4663f] RIP: 0010:btf_var_show (kernel/bpf/btf.c:2929) Call Trace: btf_type_show (kernel/bpf/btf.c:8259) btf_type_snprintf_show (kernel/bpf/btf.c:8329) bpf_snprintf_btf (kernel/trace/bpf_trace.c:1047) bpf_prog_test_run_raw_tp (net/bpf/test_run.c:829) __sys_bpf (kernel/bpf/syscall.c:4804) do_syscall_64 (arch/x86/entry/syscall_64.c:84) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Resolve the var's type directly with btf_type_skip_modifiers() when resolved_ids is NULL, mirroring btf_modifier_show(). Fixes: c4d0bfb45068 ("bpf: Add bpf_snprintf_btf helper") Signed-off-by: Jiayuan Chen Acked-by: Ihor Solodrai Link: https://lore.kernel.org/r/20260901104924.346187-4-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- kernel/bpf/btf.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index df5f0d059ad1..85ae92c920e4 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -2925,7 +2925,15 @@ static void btf_var_show(const struct btf *btf, const struct btf_type *t, u32 type_id, void *data, u8 bits_offset, struct btf_show *show) { - t = btf_type_id_resolve(btf, &type_id); + /* + * btf_type_id_resolve() dereferences btf->resolved_ids, which is NULL + * for a base BTF (e.g. the vmlinux BTF that bpf_snprintf_btf() uses). + * Resolve the var's type directly in that case. + */ + if (btf->resolved_ids) + t = btf_type_id_resolve(btf, &type_id); + else + t = btf_type_skip_modifiers(btf, t->type, &type_id); btf_type_ops(t)->show(btf, t, type_id, data, bits_offset, show); } From 6265b44f2c3bb2839a306d6088d6e65a58d7e80e Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Tue, 1 Sep 2026 18:47:38 +0800 Subject: [PATCH 0708/2366] selftests/bpf: Add test for key-less BTF hash map Create a hash and an rhash map with btf_key_type_id == 0 and expect bpf_map_create() to fail with -EINVAL; a positive control with a real key type confirms the rejection is about the key-less BTF and not some unrelated failure. Such a map used to be accepted and then NULL-deref in btf_type_show() when dumped through bpffs. Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260901104924.346187-5-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- .../bpf/prog_tests/btf_map_keyless.c | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c diff --git a/tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c b/tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c new file mode 100644 index 000000000000..3248bccc3557 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include + +/* + * A hash map with a key-less BTF (btf_key_type_id == 0) used to be accepted + * and then NULL-deref in btf_type_show() when dumped through bpffs. A fixed + * kernel rejects it at creation; verify that rejection, with a keyed positive + * control so the -EINVAL is about the missing key type and not some unrelated + * failure. + */ +static void check_keyless(int map_type, __u32 map_flags, int btf_fd, int val_id) +{ + LIBBPF_OPTS(bpf_map_create_opts, opts); + int map_fd; + + opts.map_flags = map_flags; + opts.btf_fd = btf_fd; + opts.btf_value_type_id = val_id; + + /* Positive control: the same map with a real key type is accepted. */ + opts.btf_key_type_id = val_id; + map_fd = bpf_map_create(map_type, "keyed_map", 4, 4, 8, &opts); + if (!ASSERT_GE(map_fd, 0, "keyed create is accepted")) + return; + close(map_fd); + + /* A key-less BTF must be rejected. */ + opts.btf_key_type_id = 0; + map_fd = bpf_map_create(map_type, "keyless_map", 4, 4, 8, &opts); + ASSERT_EQ(map_fd, -EINVAL, "key-less create is rejected"); + if (map_fd >= 0) + close(map_fd); +} + +void test_btf_map_keyless(void) +{ + int btf_fd, val_id; + struct btf *btf; + + btf = btf__new_empty(); + if (!ASSERT_OK_PTR(btf, "btf__new_empty")) + return; + + val_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED); + if (!ASSERT_GT(val_id, 0, "btf__add_int")) + goto out; + + if (!ASSERT_OK(btf__load_into_kernel(btf), "btf__load_into_kernel")) + goto out; + btf_fd = btf__fd(btf); + + if (test__start_subtest("hash")) + check_keyless(BPF_MAP_TYPE_HASH, 0, btf_fd, val_id); + if (test__start_subtest("rhash")) + check_keyless(BPF_MAP_TYPE_RHASH, BPF_F_NO_PREALLOC, btf_fd, val_id); +out: + btf__free(btf); +} From 1ae6aa61958a0ee6f254cefbee20663ffbadb195 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Tue, 1 Sep 2026 18:47:39 +0800 Subject: [PATCH 0709/2366] selftests/bpf: Add test for showing a void BTF type Extend the snprintf_btf test with type_ids from the vmlinux BTF that used to NULL-deref in the BTF show path: a "const void", checked to render the "" placeholder, and a BTF_KIND_VAR, checked to resolve and render without error. The program renders from its own buffer and the test picks a VAR whose resolved type fits it, so the render stays in bounds. Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260901104924.346187-6-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/snprintf_btf.c | 79 +++++++++++++++++++ .../selftests/bpf/progs/snprintf_btf_void.c | 24 ++++++ 2 files changed, 103 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/snprintf_btf_void.c diff --git a/tools/testing/selftests/bpf/prog_tests/snprintf_btf.c b/tools/testing/selftests/bpf/prog_tests/snprintf_btf.c index dd41b826be30..edce9c1b54fb 100644 --- a/tools/testing/selftests/bpf/prog_tests/snprintf_btf.c +++ b/tools/testing/selftests/bpf/prog_tests/snprintf_btf.c @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 #include #include +#include #include "netif_receive_skb.skel.h" +#include "snprintf_btf_void.skel.h" /* Demonstrate that bpf_snprintf_btf succeeds and that various data types * are formatted correctly. @@ -58,3 +60,80 @@ void serial_test_snprintf_btf(void) cleanup: netif_receive_skb__destroy(skel); } + +/* + * bpf_snprintf_btf() renders a type_id taken straight from the vmlinux BTF. + * Two such type_ids used to NULL-deref in the BTF show path: + * - a "const void" (a modifier resolving to void) in btf_modifier_show() + * - a BTF_KIND_VAR in btf_var_show() (base BTF has no resolved_ids) + * A fixed kernel renders both without crashing. + */ +static long run(struct snprintf_btf_void *skel, __u32 type_id) +{ + LIBBPF_OPTS(bpf_test_run_opts, topts); + char ctx[8] = {}; + + skel->bss->type_id = type_id; + topts.ctx_in = ctx; + topts.ctx_size_in = sizeof(ctx); + if (!ASSERT_OK(bpf_prog_test_run_opts(bpf_program__fd(skel->progs.dump_type), + &topts), "test_run")) + return -1; + return skel->bss->ret; +} + +void test_snprintf_btf_void(void) +{ + const struct btf_type *t; + struct snprintf_btf_void *skel; + int i, n, cv = 0, var = 0; + struct btf *btf; + + btf = btf__parse("/sys/kernel/btf/vmlinux", NULL); + if (!btf) { + test__skip(); + return; + } + + skel = snprintf_btf_void__open_and_load(); + if (!ASSERT_OK_PTR(skel, "skel_open_and_load")) + goto out_btf; + + n = btf__type_cnt(btf); + for (i = 1; i < n && !(cv && var); i++) { + t = btf__type_by_id(btf, i); + if (!cv && btf_kind(t) == BTF_KIND_CONST && t->type == 0) + cv = i; + /* Pick a VAR small enough to render from the program's buffer. */ + if (!var && btf_kind(t) == BTF_KIND_VAR) { + long sz = btf__resolve_size(btf, t->type); + + if (sz > 0 && sz <= (long)sizeof(skel->bss->obj)) + var = i; + } + } + + /* "const void" renders the "" placeholder. */ + if (test__start_subtest("const_void")) { + if (cv) { + ASSERT_EQ(run(skel, cv), + sizeof("") - 1, "ret"); + ASSERT_STREQ(skel->bss->out, "", + "placeholder"); + } else { + test__skip(); + } + } + + /* A BTF_KIND_VAR must resolve and render without error. */ + if (test__start_subtest("var")) { + if (var) + ASSERT_GT(run(skel, var), 0, "ret"); + else + test__skip(); + } + + snprintf_btf_void__destroy(skel); +out_btf: + btf__free(btf); +} diff --git a/tools/testing/selftests/bpf/progs/snprintf_btf_void.c b/tools/testing/selftests/bpf/progs/snprintf_btf_void.c new file mode 100644 index 000000000000..44af80fbbb80 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/snprintf_btf_void.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "btf_ptr.h" +#include + +__u32 type_id; +/* A buffer we own to render the selected type from, kept in bounds. */ +char obj[256]; +char out[64]; +long ret; + +SEC("raw_tp/sys_enter") +int dump_type(void *ctx) +{ + struct btf_ptr ptr = { + .ptr = obj, + .type_id = type_id, + .flags = 0, + }; + + ret = bpf_snprintf_btf(out, sizeof(out), &ptr, sizeof(ptr), 0); + return 0; +} + +char _license[] SEC("license") = "GPL"; From 77515ab12e4983e6416f8c35039a3f0c0822ac70 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 16:44:19 +0200 Subject: [PATCH 0710/2366] bpf: Mark signal tracepoint siginfo arguments as scalar The signal_generate and signal_deliver tracepoints declare their info argument as a struct kernel_siginfo pointer. btf_ctx_access() therefore treats it as a trusted pointer for tp_btf programs. Signal delivery also uses SEND_SIG_NOINFO and SEND_SIG_PRIV as special values for this argument. Those values are zero and one respectively, and are not pointers. A tp_btf program can currently dereference either value and fault the kernel. In particular, signal_generate can run from timer interrupt context, turning the fault into a kernel panic. Record both tracepoints in raw_tp_null_args[] and mark argument one as a non-pointer. This preserves scalar access to the cookie while rejecting direct and helper-mediated pointer use. Merely marking it nullable would not suffice because SEND_SIG_PRIV is nonzero. Fixes: 838a10bd2ebf ("bpf: Augment raw_tp arguments with PTR_MAYBE_NULL") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903144433.1716731-2-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/btf.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 85ae92c920e4..b5aa802451bd 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -6732,6 +6732,9 @@ static const struct bpf_raw_tp_null_args raw_tp_null_args[] = { { "rxrpc_resend", 0x10 }, { "rxrpc_tq", 0x10 }, { "rxrpc_client", 0x1 }, + /* signal */ + { "signal_generate", 0x20 }, + { "signal_deliver", 0x20 }, /* skb */ {"kfree_skb", 0x1000}, /* sunrpc */ From d7719a1736e6be77d0682f7395acd3701949f1fa Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 16:44:20 +0200 Subject: [PATCH 0711/2366] selftests/bpf: Cover signal tracepoint siginfo sentinels Add load-only verifier coverage for the signal_generate and signal_deliver info arguments. The signal_generate case performs a NULL check before dereferencing info, ensuring that merely making it nullable cannot satisfy the test when the nonzero SEND_SIG_PRIV sentinel is used. Both programs load successfully without the verifier fix, contrary to their expected-failure annotations. With the fix, info is a scalar and the attempted dereferences are rejected. Also add success cases showing that plain raw tracepoint and tp_btf programs can continue to read and compare the context word as a scalar. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903144433.1716731-3-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/raw_tp_null_fail.c | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c index 0d58114a4955..7e8842bf9000 100644 --- a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c +++ b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c @@ -22,3 +22,39 @@ int test_raw_tp_null_sched_pi_setprio_arg_2(void *ctx) { asm volatile("r1 = *(u64 *)(r1 +8); r1 = *(u64 *)(r1 +0);" ::: __clobber_all); return 0; } + +/* Plain raw tracepoint arguments remain scalar values. */ +SEC("raw_tp/signal_generate") +__success +int test_raw_tp_signal_generate_info_scalar(void *ctx) +{ + asm volatile("r1 = *(u64 *)(r1 +8); if r1 != 1 goto +0;" ::: __clobber_all); + return 0; +} + +/* tp_btf programs may inspect the sentinel as a scalar value. */ +SEC("tp_btf/signal_generate") +__success +int test_tp_btf_signal_generate_info_scalar(void *ctx) +{ + asm volatile("r1 = *(u64 *)(r1 +8); if r1 != 1 goto +0;" ::: __clobber_all); + return 0; +} + +/* SEND_SIG_PRIV is non-NULL, so a NULL check cannot make info safe. */ +SEC("tp_btf/signal_generate") +__failure __msg("R1 invalid mem access 'scalar'") +int test_tp_btf_signal_generate_info_no_deref(void *ctx) +{ + asm volatile("r1 = *(u64 *)(r1 +8); if r1 == 0 goto +1; " + "r1 = *(u32 *)(r1 +0);" ::: __clobber_all); + return 0; +} + +SEC("tp_btf/signal_deliver") +__failure __msg("R1 invalid mem access 'scalar'") +int test_tp_btf_signal_deliver_info_no_deref(void *ctx) +{ + asm volatile("r1 = *(u64 *)(r1 +8); r1 = *(u32 *)(r1 +0);" ::: __clobber_all); + return 0; +} From 266aa4ad0b2e82397cd9045752c9bff03d98eddd Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 16:44:21 +0200 Subject: [PATCH 0712/2366] bpf: Reject tail calls directly from callback frames A tail call from a non-zero frame is modeled as a return from that frame. The verifier makes R0 unknown and calls prepare_func_exit() for the taken branch. When the current frame is a synchronous callback, prepare_func_exit() enforces the callback return-value contract and marks R0 precise. Since the tail-call path synthesized R0 rather than deriving it from an instruction, precision backtracking reaches the callback-calling instruction with R0 still requested and triggers the "callback unexpected regs" verifier bug. A CAP_BPF task can therefore cause a WARN and an -EFAULT BPF_PROG_LOAD. Tail calls reachable from callbacks are already rejected later by check_max_stack_depth(). Reject a tail call made directly by a callback before constructing the inconsistent return state, using the existing diagnostic. Tail calls from ordinary subprograms keep their current behavior. Fixes: e3245f899043 ("bpf: properly verify tail call behavior") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903144433.1716731-4-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 7d8ddb1bee00..f540279ff4ab 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -11228,6 +11228,17 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn if (env->cur_state->curframe) { struct bpf_verifier_state *branch; + /* + * A taken tail call is modeled as a return from the current + * frame. A callback frame cannot be left that way because + * prepare_func_exit() would apply its return contract to the + * unknown R0 synthesized below. Stack-depth validation rejects + * this construct anyway. + */ + if (cur_func(env)->in_callback_fn) { + verbose(env, "cannot tail call within callback\n"); + return -EINVAL; + } mark_reg_scratched(env, BPF_REG_0); branch = push_stack(env, env->insn_idx + 1, env->insn_idx, false); if (IS_ERR(branch)) From d9ae3e4c7fb5bfaccc9ca295692d54130862f3b2 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 16:44:22 +0200 Subject: [PATCH 0713/2366] selftests/bpf: Test direct tail calls from callbacks tailcall_callback tests a tail call one static subprogram below a callback. That reaches the later stack-depth rejection, but it does not exercise the tail-call helper while the current frame is itself a callback. Add a callback that calls bpf_tail_call directly and expect the existing "cannot tail call within callback" diagnostic. On an affected kernel, the load instead reaches the "callback unexpected regs" verifier bug, so the expected message is absent and the test fails. The existing ordinary subprogram case remains a success control for legitimate tail calls. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903144433.1716731-5-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/tailcall_callback.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/tailcall_callback.c b/tools/testing/selftests/bpf/progs/tailcall_callback.c index c41632cf423b..14fa7a87028e 100644 --- a/tools/testing/selftests/bpf/progs/tailcall_callback.c +++ b/tools/testing/selftests/bpf/progs/tailcall_callback.c @@ -44,6 +44,13 @@ int callback_loop(int index, void **cb_ctx) return ret ? 1 : 0; } +static __noinline +int callback_tail(int index, void **cb_ctx) +{ + bpf_tail_call_static(*cb_ctx, &jmp_table, 0); + return 0; +} + static __noinline int callback_empty(int index, void *data) { @@ -78,4 +85,13 @@ int tailcall_callback_2(struct __sk_buff *skb) return 0; } +/* callback with a direct tail call is rejected without a verifier bug */ +SEC("tc") +__failure __msg("cannot tail call within callback") +int tailcall_callback_3(struct __sk_buff *skb) +{ + bpf_loop(1, callback_tail, &skb, 0); + return 0; +} + char __license[] SEC("license") = "GPL"; From 7b7b8b5960102566bd625ae829d1f330c5b5d104 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 16:44:23 +0200 Subject: [PATCH 0714/2366] bpf: Reject resilient lock operations in rbtree callbacks __bpf_rbtree_add() keeps parent and link pointers live across calls to the program-supplied comparison callback. The verifier therefore requires the root's lock to remain held throughout the callback. The helper path enforces this rule for bpf_spin_lock() and bpf_spin_unlock(), but the resilient lock kfunc argument path does not. Since resilient locks may protect BPF rbtree roots, a callback can release the root lock and let another CPU remove and free the node referenced by the in-progress tree walk. The walk then resumes using freed pointers. Reject resilient lock kfuncs in an rbtree comparison callback, matching the existing policy for the spin lock helpers. Resilient-lock-protected trees remain valid when their comparison callbacks leave lock state alone. Fixes: 0de2046137f9 ("bpf: Implement verifier support for rqspinlock") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903144433.1716731-6-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index f540279ff4ab..32d31fa67036 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -13241,6 +13241,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me { int flags = PROCESS_RES_LOCK; + if (in_rbtree_lock_required_cb(env)) { + verbose(env, "can't res_spin_{lock,unlock} in rbtree cb\n"); + return -EACCES; + } + if (reg->type != PTR_TO_MAP_VALUE && reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) { verbose(env, "%s doesn't point to map value or allocated object\n", reg_arg_name(env, argno)); From 08b4dc83d981bf9136d37aaa4f5cd021ba0d8f2b Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 16:44:24 +0200 Subject: [PATCH 0715/2366] selftests/bpf: Reject resilient unlock in rbtree callback Add a load-only verifier regression for a resilient lock operation in an rbtree comparison callback. The program holds the rbtree's regular spin lock and a separate resilient lock, then releases the resilient lock from the callback. This isolates the missing kfunc policy check without running a concurrent tree mutation. Release the resilient lock before the regular lock on the outer fall-through. The broken verifier therefore accepts the balanced program, while the fixed verifier rejects the resilient unlock specifically while verifying the callback. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903144433.1716731-7-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../testing/selftests/bpf/progs/rbtree_fail.c | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/rbtree_fail.c b/tools/testing/selftests/bpf/progs/rbtree_fail.c index 555379952dcc..803419a47c62 100644 --- a/tools/testing/selftests/bpf/progs/rbtree_fail.c +++ b/tools/testing/selftests/bpf/progs/rbtree_fail.c @@ -16,6 +16,7 @@ struct node_data { private(A) struct bpf_spin_lock glock; private(A) struct bpf_rb_root groot __contains(node_data, node); private(A) struct bpf_rb_root groot2 __contains(node_data, node); +private(B) struct bpf_res_spin_lock res_glock; static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b) { @@ -265,6 +266,12 @@ static bool less__bad_fn_call_first_unlock_after(struct bpf_rb_node *a, const st return node_a->key < node_b->key; } +static bool less__bad_res_spin_unlock(struct bpf_rb_node *a, const struct bpf_rb_node *b) +{ + bpf_res_spin_unlock(&res_glock); + return false; +} + static __always_inline long add_with_cb(bool (cb)(struct bpf_rb_node *a, const struct bpf_rb_node *b)) { @@ -301,4 +308,26 @@ long rbtree_api_add_bad_cb_bad_fn_call_first_unlock_after(void *ctx) return add_with_cb(less__bad_fn_call_first_unlock_after); } +SEC("?tc") +__failure __msg("can't res_spin_{lock,unlock} in rbtree cb") +long rbtree_api_add_bad_cb_res_spin_unlock(void *ctx) +{ + struct node_data *n; + + n = bpf_obj_new(typeof(*n)); + if (!n) + return 1; + + bpf_spin_lock(&glock); + if (bpf_res_spin_lock(&res_glock)) { + bpf_spin_unlock(&glock); + bpf_obj_drop(n); + return 1; + } + bpf_rbtree_add(&groot, &n->node, less__bad_res_spin_unlock); + bpf_res_spin_unlock(&res_glock); + bpf_spin_unlock(&glock); + return 0; +} + char _license[] SEC("license") = "GPL"; From a453d6e3b8e8e1a321c8744d6189d763af9287d0 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 16:44:25 +0200 Subject: [PATCH 0716/2366] bpf: Mark sched_process_wait argument as nullable do_wait() passes wo->wo_pid to the sched_process_wait tracepoint. kernel_wait4() leaves wo_pid NULL for wait4(-1), and kernel_waitid_prepare() does likewise for waitid(P_ALL). btf_ctx_access() currently types argument 0 as PTR_TO_BTF_ID | PTR_TRUSTED. Without PTR_MAYBE_NULL, the verifier accepts an unchecked dereference. Trusted pointer loads have no fault protection, so a wait for any child can then cause a NULL pointer dereference in JITed BPF code. Add sched_process_wait to raw_tp_null_args[] with argument 0 marked nullable. The verifier rejects an unchecked dereference while preserving access after the program checks the pointer for NULL. Fixes: 838a10bd2ebf ("bpf: Augment raw_tp arguments with PTR_MAYBE_NULL") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903144433.1716731-8-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/btf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index b5aa802451bd..5d93fd82e764 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -6672,6 +6672,10 @@ struct bpf_raw_tp_null_args { static const struct bpf_raw_tp_null_args raw_tp_null_args[] = { /* sched */ { "sched_pi_setprio", 0x10 }, + /* + * do_wait() passes NULL for wait4(-1) and waitid(P_ALL). + */ + { "sched_process_wait", 0x1 }, /* ... from sched_numa_pair_template event class */ { "sched_stick_numa", 0x100 }, { "sched_swap_numa", 0x100 }, From c1992ba73b0339166906eb2224494e04052a8150 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 16:44:26 +0200 Subject: [PATCH 0717/2366] selftests/bpf: Test sched_process_wait nullable argument Add a load-time verifier test that dereferences argument 0 of the sched_process_wait tp_btf program without checking it. The test expects the nullable-pointer diagnostic, so it is accepted unexpectedly before the fix and rejected as expected after it. Add a successful control that checks the argument for NULL before the dereference. This ensures the nullable marking preserves legitimate access to the pid when the tracepoint supplies one. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903144433.1716731-9-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/raw_tp_null_fail.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c index 7e8842bf9000..725d73c9ffe1 100644 --- a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c +++ b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c @@ -58,3 +58,20 @@ int test_tp_btf_signal_deliver_info_no_deref(void *ctx) asm volatile("r1 = *(u64 *)(r1 +8); r1 = *(u32 *)(r1 +0);" ::: __clobber_all); return 0; } + +SEC("tp_btf/sched_process_wait") +__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'") +int test_raw_tp_null_sched_process_wait_arg_1(void *ctx) +{ + asm volatile("r1 = *(u64 *)(r1 +0); r1 = *(u32 *)(r1 +0);" ::: __clobber_all); + return 0; +} + +SEC("tp_btf/sched_process_wait") +__success +int test_raw_tp_null_sched_process_wait_arg_1_checked(void *ctx) +{ + asm volatile("r1 = *(u64 *)(r1 +0); if r1 == 0 goto +1; " + "r1 = *(u32 *)(r1 +0);" ::: __clobber_all); + return 0; +} From d05524794240b52fdc3b6c1220dd05505715824d Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 16:44:27 +0200 Subject: [PATCH 0718/2366] bpf: Mark syscall helpers as sleepable bpf_sys_bpf() executes the bpf(2) syscall body, which can take mutexes, allocate with GFP_KERNEL, and wait for an RCU grace period. bpf_sys_close() reaches close_fd() and filp_close(), which can sleep as well. Both helpers are limited to BPF_PROG_TYPE_SYSCALL, whose main program is sleepable. That does not make every callback sleepable: a syscall program can register a bpf_timer callback, and the verifier checks that callback in a non-sleepable context while retaining the syscall helper set. Without .might_sleep on the prototypes, such a callback can invoke bpf_sys_bpf() from hrtimer softirq context and trigger a scheduling-while-atomic failure. bpf_sys_close() is exposed through the same missing context check. Set .might_sleep on both prototypes so the existing helper-context check rejects them from timer callbacks and other atomic regions. Calls from the sleepable main body remain valid. Fixes: 79a7f8bdb159 ("bpf: Introduce bpf_sys_bpf() helper and program type.") Fixes: 3abea089246f ("bpf: Add bpf_sys_close() helper.") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903144433.1716731-10-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/syscall.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 6874ba1424af..c7bc9ba9b331 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -6568,6 +6568,7 @@ EXPORT_SYMBOL_NS(kern_sys_bpf, "BPF_INTERNAL"); static const struct bpf_func_proto bpf_sys_bpf_proto = { .func = bpf_sys_bpf, .gpl_only = false, + .might_sleep = true, .ret_type = RET_INTEGER, .arg1_type = ARG_ANYTHING, .arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY, @@ -6593,6 +6594,7 @@ BPF_CALL_1(bpf_sys_close, u32, fd) static const struct bpf_func_proto bpf_sys_close_proto = { .func = bpf_sys_close, .gpl_only = false, + .might_sleep = true, .ret_type = RET_INTEGER, .arg1_type = ARG_ANYTHING, }; From 26a3a510cd3433e15f37ea1d5a6f2c17a0170316 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 16:44:28 +0200 Subject: [PATCH 0719/2366] selftests/bpf: Check syscall helpers in timer callbacks A BPF_PROG_TYPE_SYSCALL program is sleepable, but its bpf_timer callbacks run in a non-sleepable hrtimer softirq context. Add verifier cases that call bpf_sys_bpf() and bpf_sys_close() from timer callbacks. Without the syscall helper prototype annotations these programs load, so their failure expectations expose the bug. Also add successful controls that call each helper from the syscall program main body, ensuring that the intended sleepable use remains accepted. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903144433.1716731-11-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../bpf/progs/verifier_async_cb_context.c | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c index 6bf95550a024..a7c84d3fa4c7 100644 --- a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c +++ b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c @@ -62,6 +62,70 @@ int timer_sleepable_prog(void *ctx) return 0; } +static int timer_sys_bpf_cb(void *map, int *key, struct bpf_timer *timer) +{ + __u64 attr = 0; + + bpf_sys_bpf(BPF_MAP_FREEZE, &attr, sizeof(attr)); + return 0; +} + +SEC("syscall") +__failure __msg("sleepable helper bpf_sys_bpf#{{[0-9]+}} in non-sleepable prog") +int timer_sys_bpf_prog(void *ctx) +{ + struct timer_elem *val; + int key = 0; + + val = bpf_map_lookup_elem(&timer_map, &key); + if (!val) + return 0; + + bpf_timer_init(&val->t, &timer_map, 0); + bpf_timer_set_callback(&val->t, timer_sys_bpf_cb); + return 0; +} + +static int timer_sys_close_cb(void *map, int *key, struct bpf_timer *timer) +{ + bpf_sys_close(0); + return 0; +} + +SEC("syscall") +__failure __msg("sleepable helper bpf_sys_close#{{[0-9]+}} in non-sleepable prog") +int timer_sys_close_prog(void *ctx) +{ + struct timer_elem *val; + int key = 0; + + val = bpf_map_lookup_elem(&timer_map, &key); + if (!val) + return 0; + + bpf_timer_init(&val->t, &timer_map, 0); + bpf_timer_set_callback(&val->t, timer_sys_close_cb); + return 0; +} + +SEC("syscall") +__success +int syscall_sys_bpf_prog(void *ctx) +{ + __u64 attr = 0; + + bpf_sys_bpf(BPF_MAP_FREEZE, &attr, sizeof(attr)); + return 0; +} + +SEC("syscall") +__success +int syscall_sys_close_prog(void *ctx) +{ + bpf_sys_close(0); + return 0; +} + /* Workqueue tests */ struct wq_elem { From b3c8d4672f7a8735e2884cefcd89286268e88b2e Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 27 Aug 2026 15:33:00 -0500 Subject: [PATCH 0720/2366] accel: ethosu: Fix ethosu_job_open() return value A WARN_ON() returns a 0 or 1, not the original negative errno. Just drop the WARN_ON() as the FD open will pass the return code to userspace and there's only one possible source of the error (drm_sched_entity_init()). Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Link: https://patch.msgid.link/20260827-ethosu-fixes-v1-1-346f9ea8791c@kernel.org Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_job.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c index 4ced44a65f23..6aa305b1dce3 100644 --- a/drivers/accel/ethosu/ethosu_job.c +++ b/drivers/accel/ethosu/ethosu_job.c @@ -374,12 +374,10 @@ int ethosu_job_open(struct ethosu_file_priv *ethosu_priv) { struct ethosu_device *dev = ethosu_priv->edev; struct drm_gpu_scheduler *sched = &dev->sched; - int ret; - ret = drm_sched_entity_init(ðosu_priv->sched_entity, - DRM_SCHED_PRIORITY_NORMAL, - &sched, 1, NULL); - return WARN_ON(ret); + return drm_sched_entity_init(ðosu_priv->sched_entity, + DRM_SCHED_PRIORITY_NORMAL, + &sched, 1, NULL); } void ethosu_job_close(struct ethosu_file_priv *ethosu_priv) From 2cbd3691565f7c86c0eaca305f5beb3435b4ba70 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 27 Aug 2026 15:33:01 -0500 Subject: [PATCH 0721/2366] accel: ethosu: Drop IRQF_SHARED flag The IRQF_SHARED flag doesn't work with runtime-pm as the IRQ handler could run without resuming the device. This could also be fixed with runtime-pm calls in the IRQ handler, but there is no known need for a shared IRQ. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Link: https://patch.msgid.link/20260827-ethosu-fixes-v1-2-346f9ea8791c@kernel.org Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_job.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c index 6aa305b1dce3..4532ff13edb6 100644 --- a/drivers/accel/ethosu/ethosu_job.c +++ b/drivers/accel/ethosu/ethosu_job.c @@ -343,7 +343,7 @@ int ethosu_job_init(struct ethosu_device *edev) ret = devm_request_threaded_irq(dev, edev->irq, ethosu_job_irq_handler, ethosu_job_irq_handler_thread, - IRQF_SHARED, KBUILD_MODNAME, + 0, KBUILD_MODNAME, edev); if (ret) { dev_err(dev, "failed to request irq\n"); From eb3a41fd35e352fba387c4320a1fa0352f3e551c Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 27 Aug 2026 15:33:02 -0500 Subject: [PATCH 0722/2366] accel: ethosu: Ensure cmd stream ends with a stop op While the QSIZE register setting should prevent an out of bounds access of the command stream, it is not clear whether the h/w generates an interrupt in this case as is required (to prevent a timeout). As a stop op is expected end of the command stream, let's just ensure it is present. A stop op in the middle of the command stream also makes no sense. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Link: https://patch.msgid.link/20260827-ethosu-fixes-v1-3-346f9ea8791c@kernel.org Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_device.h | 1 + drivers/accel/ethosu/ethosu_gem.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/drivers/accel/ethosu/ethosu_device.h b/drivers/accel/ethosu/ethosu_device.h index d4458eac8447..1eca8590e68d 100644 --- a/drivers/accel/ethosu/ethosu_device.h +++ b/drivers/accel/ethosu/ethosu_device.h @@ -87,6 +87,7 @@ struct gen_pool; #define PMU_EV_TYPE_IDLE 0x20 enum ethosu_cmds { + NPU_OP_STOP = 0x0, NPU_OP_CONV = 0x2, NPU_OP_DEPTHWISE = 0x3, NPU_OP_POOL = 0x5, diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c index fa37a190e9ff..9afe2549ec84 100644 --- a/drivers/accel/ethosu/ethosu_gem.c +++ b/drivers/accel/ethosu/ethosu_gem.c @@ -390,6 +390,7 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev, struct ethosu_validated_cmdstream_info __free(kfree) *info = kzalloc_obj(*info); struct ethosu_device *edev = to_ethosu_device(ddev); u32 *bocmds = bo->base.vaddr; + bool ends_with_stop = false; struct cmd_state st; int i, ret; @@ -426,6 +427,11 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev, } switch (cmd) { + case NPU_OP_STOP: + if (i != size / 4 - 1) + return -EINVAL; + ends_with_stop = true; + break; case NPU_OP_DMA_START: srclen = dma_length(info, &st.dma, &st.dma.src); dstlen = dma_length(info, &st.dma, &st.dma.dst); @@ -688,6 +694,9 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev, } } + if (!ends_with_stop) + return -EINVAL; + for (i = 0; i < NPU_BASEP_REGION_MAX; i++) { if (!info->region_size[i]) continue; From f5376d7e0fb703876199d3b6f9f97e128fa2f8a4 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 27 Aug 2026 15:33:03 -0500 Subject: [PATCH 0723/2366] accel: ethosu: Ensure SRAM size is 0 on mapping failure On a mapping failure of the SRAM, the SRAM size is left as non-zero. The probe will succeed as the error return is not checked since having SRAM is not a hard requirement. The non-zero size allows jobs to access SRAM which is left pointing to physical base address 0x0. Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Link: https://patch.msgid.link/20260827-ethosu-fixes-v1-4-346f9ea8791c@kernel.org Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_drv.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c index 1cf284e7f300..8108622de258 100644 --- a/drivers/accel/ethosu/ethosu_drv.c +++ b/drivers/accel/ethosu/ethosu_drv.c @@ -281,8 +281,6 @@ static int ethosu_device_suspend(struct device *dev) static int ethosu_sram_init(struct ethosu_device *ethosudev) { - ethosudev->npu_info.sram_size = 0; - ethosudev->srampool = of_gen_pool_get(ethosudev->base.dev->of_node, "sram", 0); if (!ethosudev->srampool) return 0; @@ -293,6 +291,7 @@ static int ethosu_sram_init(struct ethosu_device *ethosudev) ethosudev->npu_info.sram_size, ðosudev->sramphys); if (!ethosudev->sram) { + ethosudev->npu_info.sram_size = 0; dev_err(ethosudev->base.dev, "failed to allocate from SRAM pool\n"); return -ENOMEM; } From 2b39d680c9e0fb4d625f2916980977622e84248c Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Thu, 27 Aug 2026 15:33:04 -0500 Subject: [PATCH 0724/2366] accel: ethosu: Ensure SRAM region size matches job It is possible for userspace to set the job SRAM size to 0, but then still have SRAM accesses in the command stream. When the job SRAM size is 0, setting the region base register is skipped and a stale base address from a prior job is used. Check the region size against the job's SRAM size instead of just the size of the SRAM. The job's SRAM size was already checked against the total SRAM size. Fixes: 9cff90774872 ("accel: ethosu: Validate SRAM size on submit") Cc: stable@vger.kernel.org Reviewed-by: Frank Li Link: https://patch.msgid.link/20260827-ethosu-fixes-v1-5-346f9ea8791c@kernel.org Signed-off-by: Rob Herring (Arm) --- drivers/accel/ethosu/ethosu_job.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c index 4532ff13edb6..8dce74db0cb4 100644 --- a/drivers/accel/ethosu/ethosu_job.c +++ b/drivers/accel/ethosu/ethosu_job.c @@ -447,13 +447,13 @@ static int ethosu_ioctl_submit_job(struct drm_device *dev, struct drm_file *file if (!cmd_info->region_size[i]) continue; if (i == ETHOSU_SRAM_REGION) { - if (cmd_info->region_size[i] <= edev->npu_info.sram_size) + if (cmd_info->region_size[i] <= ejob->sram_size) continue; dev_err(dev->dev, - "cmd stream region %d size greater than SRAM size (%llu > %u)\n", + "cmd stream region %d size greater than job SRAM size (%llu > %u)\n", i, cmd_info->region_size[i], - edev->npu_info.sram_size); + ejob->sram_size); ret = -EINVAL; goto out_cleanup_job; } From 0a85182723b65ad8bee8131bc38fcf0347d6679b Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Thu, 3 Sep 2026 14:06:26 +0800 Subject: [PATCH 0725/2366] sched_ext: Fix NULL sched deref in kfunc sub-sched error paths When the root scheduler has sub-scheds attached, the COMPAT kfunc wrappers scx_bpf_select_cpu_and() and scx_bpf_dsq_insert_vtime() refuse the call and report to @p's scheduler: scx_error(scx_task_sched(p), "... must be used"); The wrappers are reachable with tasks that have no scheduler. scx_bpf_select_cpu_and() is in the select_cpu kfunc group, which scx_kfunc_context_filter() opens to BPF_PROG_TYPE_SYSCALL programs; scx_bpf_dsq_insert_vtime() is in the enqueue_dispatch group, which ops.enqueue() and ops.dispatch() may call with any KF_RCU task -- the group has no kf_tasks validation, and scx_dsq_insert_preamble() checks task ownership with scx_task_on_sched() precisely because @p may be an arbitrary task. scx_task_sched(p) is p->scx.sched, which is NULL for tasks past sched_ext_dead() -- which clears it via scx_disable_and_exit_task() on exit -- and for idle tasks, which the enable paths skip as they are never scheduled through SCX. It is also an rcu_dereference_protected() that expects @p's pi_lock or rq lock, which neither wrapper holds. Passing NULL to scx_error() reaches scx_vexit(), which dereferences sch->exit_info, oopsing the kernel. One concrete trigger exercised while developing the fix: a BPF_PROG_TYPE_SYSCALL program calling the select_cpu_and wrapper on an exited-but-not-reaped task while a sub-scheduler was attached (its pid stays findable while the zombie is unreaped; faulting instruction is the scx_vexit() prologue "mov r15,[rdi+0x398]" with RDI=NULL and 0x398 the offset of sch->exit_info): sched_ext: BPF scheduler "kfunc_subsched_null" enabled sched_ext: BPF sub-scheduler "kfunc_subsched_null" enabled sched_ext: Unassociated program run_select_cpu_ (id 76) BUG: kernel NULL pointer dereference, address: 0000000000000398 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page Oops: Oops: 0000 [#1] SMP NOPTI CPU: 7 UID: 0 PID: 8201 Comm: kfunc_test_runn Tainted: G W RIP: 0010:scx_vexit+0x25/0xa0 Code: ... <4c> 8b bf 98 03 00 00 ... CR2: 0000000000000398 Call Trace: __scx_exit+0x4f/0x70 scx_bpf_select_cpu_and+0xab/0xb0 bpf_prog_430ed61a7b66e03a_run_select_cpu_and+0x9c/0xe7 ? __x64_sys_bpf+0x2c/0x40 bpf_prog_test_run_syscall+0x130/0x2f0 __sys_bpf+0x930/0x10d0 ? __x64_sys_bpf+0x2c/0x40 __x64_sys_bpf+0x2c/0x40 do_syscall_64+0xbc/0x460 entry_SYSCALL_64_after_hwframe+0x76/0x7e Read @p's scheduler under RCU instead, which the wrappers can do from their guard(rcu)(): fault it when it can be determined, and when it can't be determined -- @p is a task past sched_ext_dead() or an idle task -- there is nothing obviously wrong to report, so just refuse the call as before without faulting any scheduler. These COMPAT wrappers are scheduled for eventual removal once the deprecation grace period elapses, but until then -- and regardless of their removal timeline -- they must not oops the kernel on a task they are handed. Cc: stable@vger.kernel.org # v7.1+ Fixes: a5fa0708cbfd ("sched_ext: Enforce scheduling authority in dispatch and select_cpu operations") Suggested-by: Andrea Righi Signed-off-by: Wanwu Li Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 11 +++++++++-- kernel/sched/ext/idle.c | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 5f242ab69cf4..7e414a7c53fc 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -8949,10 +8949,17 @@ __bpf_kfunc void scx_bpf_dsq_insert_vtime(struct task_struct *p, u64 dsq_id, #ifdef CONFIG_EXT_SUB_SCHED /* * Disallow if any sub-scheds are attached. There is no way to tell - * which scheduler called us, just error out @p's scheduler. + * which scheduler called us, so error out @p's scheduler -- read it + * under RCU as @p's locks aren't necessarily held here. @p may be a + * task past sched_ext_dead() or an idle task, in which case its + * scheduler can't be determined and there is nothing obviously wrong + * to report; just refuse the call. */ if (unlikely(!list_empty(&sch->children))) { - scx_error(scx_task_sched(p), "__scx_bpf_dsq_insert_vtime() must be used"); + struct scx_sched *tsch = scx_task_sched_rcu(p); + + if (tsch) + scx_error(tsch, "__scx_bpf_dsq_insert_vtime() must be used"); return; } #endif diff --git a/kernel/sched/ext/idle.c b/kernel/sched/ext/idle.c index d2973fb3af6d..aa9fb6de0ad6 100644 --- a/kernel/sched/ext/idle.c +++ b/kernel/sched/ext/idle.c @@ -1142,10 +1142,17 @@ __bpf_kfunc s32 scx_bpf_select_cpu_and(struct task_struct *p, s32 prev_cpu, u64 #ifdef CONFIG_EXT_SUB_SCHED /* * Disallow if any sub-scheds are attached. There is no way to tell - * which scheduler called us, just error out @p's scheduler. + * which scheduler called us, so error out @p's scheduler -- read it + * under RCU as @p's locks aren't necessarily held here. @p may be a + * task past sched_ext_dead() or an idle task, in which case its + * scheduler can't be determined and there is nothing obviously wrong + * to report; just refuse the call. */ if (unlikely(!list_empty(&sch->children))) { - scx_error(scx_task_sched(p), "__scx_bpf_select_cpu_and() must be used"); + struct scx_sched *tsch = scx_task_sched_rcu(p); + + if (tsch) + scx_error(tsch, "__scx_bpf_select_cpu_and() must be used"); return -EINVAL; } #endif From 70ded7a57443f41075625f29b9eb88dca154decb Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 27 Aug 2026 09:20:56 +0200 Subject: [PATCH 0726/2366] MAINTAINERS: cover all of RAID While commit 3626738bc7147d52 ("raid6: move to lib/raid/") handled the move of RAID6, it didn't take into account there was already more RAID code under lib/raid/, as XOR got moved over in commit 9e229025e2474115 ("xor: move to lib/raid/") before. Link: https://lore.kernel.org/7a2e5de234cc0286e3fe9bc11b810433775f2280.1787815121.git.geert+renesas@glider.be Signed-off-by: Geert Uytterhoeven Reported-by: Andrew Morton Closes: https://lore.kernel.org/20260826205058.a6ff019d0584f75c7f50430b@linux-foundation.org Cc: Christoph Hellwig Cc: Song Liu Cc: Yu Kuai Cc: Li Nan Cc: Xiao Ni Signed-off-by: Andrew Morton --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 85cc77fe75b7..90ce4def17d9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25405,7 +25405,7 @@ F: drivers/md/md* F: drivers/md/raid* F: include/linux/raid/ F: include/uapi/linux/raid/ -F: lib/raid/raid6/ +F: lib/raid/ SOLIDRUN CLEARFOG SUPPORT M: Russell King From ed334880e5e6855820855d76f594d973747cbf8a Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Thu, 27 Aug 2026 11:34:35 +0100 Subject: [PATCH 0727/2366] MAINTAINERS: add Kiryl as a THP reviewer I have been working on transparent hugepages since 2012, starting with the huge zero page and file-backed THP. A lot of the code that causes pain now traces back to me. It is only fair if I share the review load for THP. Add myself to the reviewer list so get_maintainer.pl puts me on Cc: as well. It is also my commitment to be more active in reviewing this code. Link: https://lore.kernel.org/20260827103435.1371882-1-kas@kernel.org Signed-off-by: Kiryl Shutsemau (Meta) Acked-by: David Hildenbrand (Arm) Acked-by: Lorenzo Stoakes (ARM) Reviewed-by: Barry Song Acked-by: Zi Yan Reviewed-by: Lance Yang Acked-by: Usama Arif Acked-by: Baolin Wang Acked-by: SJ Park Cc: Dev Jain Cc: Liam R. Howlett Cc: Ryan Roberts Signed-off-by: Andrew Morton --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 90ce4def17d9..2133aec4a200 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17421,6 +17421,7 @@ R: Dev Jain R: Barry Song R: Lance Yang R: Usama Arif +R: Kiryl Shutsemau L: linux-mm@kvack.org S: Maintained W: http://www.linux-mm.org From 35b0fb391b0df57383bc15985bb769f4555c97ba Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Tue, 25 Aug 2026 08:55:26 +0100 Subject: [PATCH 0728/2366] mm/mremap: reset unfaulted VMA page offset for MREMAP_DONTUNMAP Uniquely an mremap() invocation using the MREMAP_DONTUNMAP flag can reset a faulted VMA into an unfaulted one. It does so after the page tables have been moved to the copied VMA with MREMAP_DONTUNMAP leaving the old VMA in place which is naturally unfaulted as the page tables it had are no longer present. However, in doing so, it violates the invariant that the anonymous page offset of an unfaulted VMA is vma->vm_start >> PAGE_SHIFT. This is because a VMA may have been faulted in, mremap()'d (causing a delta between its page offset and vma->vm_start >> PAGE_SHIFT), and then mremap()'d again with MREMAP_DONTUNMAP resulting in the unfaulting. This condition is a violation of a fundamental assumption in mm, but now also triggers an assert in assert_sane_pgoff() which explicitly checks for this condition. Correct it by resetting the VMA's page offset at the point of completing the MREMAP_DONTUNMAP operation. Link: https://lore.kernel.org/20260825-fix-mremap-dontunmap-pgoff-v1-1-39a40b2c98b3@kernel.org Fixes: 1583aa278f5f ("mm: mremap: unlink anon_vmas when mremap with MREMAP_DONTUNMAP success") Signed-off-by: Lorenzo Stoakes (ARM) Reported-by: syzbot+f12658786a4153df5113@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/6a87853b.ae6ddae5.3da009.0023.GAE@google.com/ Tested-by: syzbot+f12658786a4153df5113@syzkaller.appspotmail.com Acked-by: Vlastimil Babka (SUSE) Reviewed-by: Kunwu Chan Reviewed-by: Pedro Falcato Cc: Jann Horn Cc: Liam R. Howlett Cc: Li Xinhai Cc: Signed-off-by: Andrew Morton --- mm/mremap.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/mm/mremap.c b/mm/mremap.c index e8df5cdb0ac9..2b4b523a86b8 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -1331,18 +1331,30 @@ static void dontunmap_complete(struct vma_remap_struct *vrm, { unsigned long start = vrm->addr; unsigned long end = vrm->addr + vrm->old_len; - unsigned long old_start = vrm->vma->vm_start; - unsigned long old_end = vrm->vma->vm_end; + struct vm_area_struct *vma = vrm->vma; + unsigned long old_start = vma->vm_start; + unsigned long old_end = vma->vm_end; /* We always clear VMA_LOCKED[ONFAULT]_BIT on the old VMA. */ - vma_clear_flags_mask(vrm->vma, VMA_LOCKED_MASK); + vma_clear_flags_mask(vma, VMA_LOCKED_MASK); /* * anon_vma links of the old vma is no longer needed after its page * table has been moved. */ - if (new_vma != vrm->vma && start == old_start && end == old_end) - unlink_anon_vmas(vrm->vma); + if (new_vma != vma && start == old_start && end == old_end) { + const pgoff_t pgoff_unfaulted = vma->vm_start >> PAGE_SHIFT; + + unlink_anon_vmas(vma); + /* + * The VMA is now unfaulted and it is an invariant that + * unfaulted anonymous VMAs have page offset equal to + * vma->vm_start >> PAGE_SHIFT. + */ + vma_set_anon_pgoff(vma, pgoff_unfaulted); + if (vma_is_anonymous(vma) && !vma->vm_file) + vma_set_pgoff(vma, pgoff_unfaulted); + } /* Because we won't unmap we don't need to touch locked_vm. */ } From 97d34aa65c29cca85e3e9050f4c936389b38a054 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 26 Aug 2026 17:30:35 +0100 Subject: [PATCH 0729/2366] mm/secretmem: properly account locked pages secretmem accounts folios by treating memory as if it were mlock()'d and thus limited by the RLIMIT_MEMLOCK limit. However the folios are unevictable and remain so until the inode is evicted, eliminating usual mlock() semantics - mapping folios then unmapping them does not clear their unevictable state, since it depends on AS_UNEVICTABLE, not PG_mlocked. A user can therefore easily work around the RLIMIT_MEMLOCK limit - simply map then unmap and VmLck no longer counts the secretmem range. Worse, folios are not accounted in the process's RSS, meaning the OOM killer won't know to kill the process. Repeatedly mapping/unmapping (or forking) can then result in the consumption of all available system memory with unevictable folios and cause system instability. A secretmem fd can be passed between processes and over fork so a per-process limit simply does not make sense, so follow the precedent set by io_uring, perf, skbuff, iommufd and xdp by tracking the number of locked pages in user_struct->locked_vm. Since the scope tracked is actually inode lifetime, the RLIMIT_MEMLOCK applies per-user not per-process, so it doesn't make sense to bypass for users with CAP_IPC_LOCK, therefore remove this bypass. There is simply no reason to carry on marking the mapping as mlock()'d since it's misleading and the lifecycle is now correctly handled, so remove this too. Note that secretmem does not support any form of truncation (including hole punching) and the folios are unreclaimable, so the folios need only be accounted on fault and unaccounted on inode destruction. __secretmem_account_pages() is more or less a duplicate of the code that io_uring etc. use, but since this is a bug fix that needs backporting, defer any de-duplication efforts to a follow-up. test_mlock_limit() asserts mlock_future_ok() on mmap(), however this has been removed, so remove the test altogether for the fix. A new test will be sent separately for upstream. Link: https://lore.kernel.org/20260826-secretmem-accounting-v3-1-94cb04399510@kernel.org Fixes: 1507f51255c9 ("mm: introduce memfd_secret system call to create "secret" memory areas") Signed-off-by: Lorenzo Stoakes (ARM) Reported-by: Daehyeon Ko <4ncienth@gmail.com> Closes: https://lore.kernel.org/linux-mm/20260813225328.2010303-1-4ncienth@gmail.com/ Reviewed-by: Mike Rapoport (Microsoft) Acked-by: David Hildenbrand (Arm) Tested-by: Daehyeon Ko <4ncienth@gmail.com> Cc: Alexei Starovoitov Cc: David Hildenbrand Cc: David S. Miller Cc: Hagen Paul Pfeifer Cc: Jakub Kacinski Cc: James Bottomley Cc: Jesper Dangaard Brouer Cc: John Fastabend Cc: Liam R. Howlett Cc: Michal Hocko Cc: Stanislav Fomichev Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- include/linux/sched/user.h | 3 +- mm/secretmem.c | 116 ++++++++++++++++++++-- tools/testing/selftests/mm/memfd_secret.c | 30 +----- 3 files changed, 110 insertions(+), 39 deletions(-) diff --git a/include/linux/sched/user.h b/include/linux/sched/user.h index 4cc52698e214..8d7e5521f7cd 100644 --- a/include/linux/sched/user.h +++ b/include/linux/sched/user.h @@ -25,7 +25,8 @@ struct user_struct { #if defined(CONFIG_PERF_EVENTS) || defined(CONFIG_BPF_SYSCALL) || \ defined(CONFIG_NET) || defined(CONFIG_IO_URING) || \ - defined(CONFIG_VFIO_PCI_ZDEV_KVM) || IS_ENABLED(CONFIG_IOMMUFD) + defined(CONFIG_VFIO_PCI_ZDEV_KVM) || IS_ENABLED(CONFIG_IOMMUFD) || \ + defined(CONFIG_SECRETMEM) atomic_long_t locked_vm; #endif #ifdef CONFIG_WATCH_QUEUE diff --git a/mm/secretmem.c b/mm/secretmem.c index d29865075b6e..384f5cfc457f 100644 --- a/mm/secretmem.c +++ b/mm/secretmem.c @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include @@ -47,10 +49,69 @@ bool secretmem_active(void) return !!atomic_read(&secretmem_users); } +struct secretmem_inode_state { + struct user_struct *user; + atomic_long_t nr_pages_accounted; +}; + +static bool __secretmem_account_pages(struct user_struct *user, + unsigned long nr_pages) +{ + unsigned long page_limit, cur_pages, new_pages; + + if (!nr_pages) + return true; + + page_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; + + cur_pages = atomic_long_read(&user->locked_vm); + do { + new_pages = cur_pages + nr_pages; + if (new_pages > page_limit) + return false; + } while (!atomic_long_try_cmpxchg(&user->locked_vm, + &cur_pages, new_pages)); + return true; +} + +static bool secretmem_account_folio(struct secretmem_inode_state *state, + const struct folio *folio) +{ + const unsigned long nr_pages = folio_nr_pages(folio); + + if (!__secretmem_account_pages(state->user, nr_pages)) + return false; + + atomic_long_add(nr_pages, &state->nr_pages_accounted); + return true; +} + +static void __secretmem_unaccount_pages(struct secretmem_inode_state *state, + unsigned long nr_pages) +{ + atomic_long_sub(nr_pages, &state->user->locked_vm); + atomic_long_sub(nr_pages, &state->nr_pages_accounted); +} + +static void secretmem_unaccount_folio(struct secretmem_inode_state *state, + struct folio *folio) +{ + __secretmem_unaccount_pages(state, folio_nr_pages(folio)); +} + +static void secretmem_unaccount_all_folios(struct secretmem_inode_state *state) +{ + const unsigned long nr_pages_accounted = + atomic_long_read(&state->nr_pages_accounted); + + __secretmem_unaccount_pages(state, nr_pages_accounted); +} + static vm_fault_t secretmem_fault(struct vm_fault *vmf) { struct address_space *mapping = vmf->vma->vm_file->f_mapping; struct inode *inode = file_inode(vmf->vma->vm_file); + struct secretmem_inode_state *state = inode->i_private; pgoff_t offset = vmf->pgoff; gfp_t gfp = vmf->gfp_mask; unsigned long addr; @@ -72,8 +133,15 @@ static vm_fault_t secretmem_fault(struct vm_fault *vmf) goto out; } + if (!secretmem_account_folio(state, folio)) { + folio_put(folio); + ret = VM_FAULT_SIGBUS; + goto out; + } + err = set_direct_map_invalid_noflush(folio_page(folio, 0)); if (err) { + secretmem_unaccount_folio(state, folio); folio_put(folio); ret = vmf_error(err); goto out; @@ -82,6 +150,7 @@ static vm_fault_t secretmem_fault(struct vm_fault *vmf) __folio_mark_uptodate(folio); err = filemap_add_folio(mapping, folio, offset, gfp); if (unlikely(err)) { + secretmem_unaccount_folio(state, folio); /* * If a split of large page was required, it * already happened when we marked the page invalid @@ -112,22 +181,30 @@ static const struct vm_operations_struct secretmem_vm_ops = { .fault = secretmem_fault, }; +static void secretmem_destroy_inode_priv(struct inode *inode) +{ + struct secretmem_inode_state *state = inode->i_private; + + secretmem_unaccount_all_folios(state); + free_uid(state->user); + kfree(state); + inode->i_private = NULL; +} + static int secretmem_release(struct inode *inode, struct file *file) { atomic_dec(&secretmem_users); + secretmem_destroy_inode_priv(inode); + return 0; } static int secretmem_mmap_prepare(struct vm_area_desc *desc) { - const unsigned long len = vma_desc_size(desc); - if (!vma_desc_test_any(desc, VMA_SHARED_BIT, VMA_MAYSHARE_BIT)) return -EINVAL; - vma_desc_set_flags(desc, VMA_LOCKED_BIT, VMA_DONTDUMP_BIT); - if (!mlock_future_ok(desc->mm, /*is_vma_locked=*/ true, len)) - return -EAGAIN; + vma_desc_set_flags(desc, VMA_DONTDUMP_BIT); desc->vm_ops = &secretmem_vm_ops; return 0; @@ -187,20 +264,40 @@ static const struct inode_operations secretmem_iops = { static struct vfsmount *secretmem_mnt; +static int secretmem_init_inode_priv(struct inode *inode) +{ + struct secretmem_inode_state *state; + + state = kzalloc_obj(*state); + if (!state) + return -ENOMEM; + + state->user = get_uid(current_user()); + inode->i_private = state; + return 0; +} + static struct file *secretmem_file_create(unsigned long flags) { struct file *file; struct inode *inode; const char *anon_name = "[secretmem]"; + int err; inode = anon_inode_make_secure_inode(secretmem_mnt->mnt_sb, anon_name, NULL); if (IS_ERR(inode)) return ERR_CAST(inode); + err = secretmem_init_inode_priv(inode); + if (err) + goto err_free_inode; + file = alloc_file_pseudo(inode, secretmem_mnt, "secretmem", O_RDWR | O_LARGEFILE, &secretmem_fops); - if (IS_ERR(file)) - goto err_free_inode; + if (IS_ERR(file)) { + err = PTR_ERR(file); + goto err_free_priv; + } mapping_set_gfp_mask(inode->i_mapping, GFP_USER); mapping_set_unevictable(inode->i_mapping); @@ -215,10 +312,11 @@ static struct file *secretmem_file_create(unsigned long flags) atomic_inc(&secretmem_users); return file; - +err_free_priv: + secretmem_destroy_inode_priv(inode); err_free_inode: iput(inode); - return file; + return ERR_PTR(err); } SYSCALL_DEFINE1(memfd_secret, unsigned int, flags) diff --git a/tools/testing/selftests/mm/memfd_secret.c b/tools/testing/selftests/mm/memfd_secret.c index aac4f795c327..c55d84c5e613 100644 --- a/tools/testing/selftests/mm/memfd_secret.c +++ b/tools/testing/selftests/mm/memfd_secret.c @@ -57,33 +57,6 @@ static void test_file_apis(int fd) pass("file IO is blocked as expected\n"); } -static void test_mlock_limit(int fd) -{ - size_t len; - char *mem; - - len = mlock_limit_cur; - if (len % page_size != 0) - len = (len/page_size) * page_size; - - mem = mmap(NULL, len, prot, mode, fd, 0); - if (mem == MAP_FAILED) { - fail("unable to mmap secret memory\n"); - return; - } - munmap(mem, len); - - len = mlock_limit_max * 2; - mem = mmap(NULL, len, prot, mode, fd, 0); - if (mem != MAP_FAILED) { - fail("unexpected mlock limit violation\n"); - munmap(mem, len); - return; - } - - pass("mlock limit is respected\n"); -} - static void test_vmsplice(int fd, const char *desc) { ssize_t transferred; @@ -297,7 +270,7 @@ static void prepare(void) strerror(errno)); } -#define NUM_TESTS 6 +#define NUM_TESTS 5 int main(int argc, char *argv[]) { @@ -319,7 +292,6 @@ int main(int argc, char *argv[]) if (ftruncate(fd, page_size)) ksft_exit_fail_msg("ftruncate failed: %s\n", strerror(errno)); - test_mlock_limit(fd); test_file_apis(fd); /* * We have to run the first vmsplice test before any secretmem page was From 6c001a62c34f13fe1c6a24304c289b387d9e697d Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Thu, 3 Sep 2026 13:27:28 -0400 Subject: [PATCH 0730/2366] ring-buffer: Add checking nr_subbufs to persistent ring buffer validation Sashiko reported that the code was using meta->nr_subbufs without making sure that it matched the nr_pages + 1 on data that was assuming the two were the same. Add a check to the persistent ring buffer validation code to make sure that the saved nr_subbufs matches what we expect. Link: https://patch.msgid.link/20260903132728.7fb27d34@gandalf.local.home Fixes: f5b95f1fa2ef3 ("ring-buffer: Validate the persistent meta data subbuf array") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/all/20260901164836.D962D1F000E9@smtp.kernel.org/ Reviewed-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 84fd4cdd486f..ff0a44aa578d 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -1856,6 +1856,11 @@ static bool rb_cpu_meta_valid(struct ring_buffer_cpu_meta *meta, int cpu, return false; } + if (meta->nr_subbufs != nr_pages + 1) { + pr_info("Ring buffer boot meta [%d] invalid nr_subbufs\n", cpu); + return false; + } + buffers_start = meta->first_buffer; buffers_end = meta->first_buffer + (subbuf_size * meta->nr_subbufs); From 0e68c74e44da81a4599c52437ee1f63a2c234470 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Wed, 2 Sep 2026 13:41:20 +0100 Subject: [PATCH 0731/2366] drm/xe/vram: report FLAT_CCS base misalignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So we can easily check if a machine had the CCS bug, when looking back over bug reports where we have the same machine with newer kernel. Example print for a machine with the CCS bug: FLAT_CCS base:27bbff800, aligned:no v2 (Matt B): - Unconditionally print the base + alignment Fixes: 37173392741c ("drm/xe/vram: fix ccs offset calculation") Signed-off-by: Matthew Auld Cc: Thomas Hellström Cc: Matthew Brost Cc: Rodrigo Vivi Cc: stable@kernel.org Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260902124117.918018-9-matthew.auld@intel.com (cherry picked from commit d00b7f4f03bbeb2efad872f1686130e18c2b4141) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_vram.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_vram.c b/drivers/gpu/drm/xe/xe_vram.c index 7b4478fb1697..56cff1e44530 100644 --- a/drivers/gpu/drm/xe/xe_vram.c +++ b/drivers/gpu/drm/xe/xe_vram.c @@ -90,6 +90,9 @@ static int get_flat_ccs_offset(struct xe_gt *gt, u64 tile_size, u64 *poffset) offset |= offset_lo << 6; /* HW view bits 31:6 */ offset *= num_enabled; /* convert to SW view */ + drm_info(&xe->drm, "FLAT_CCS base:%llx, aligned:%s\n", offset, + str_yes_no(IS_ALIGNED(offset, SZ_128K))); + /* * Everything below this offset is handed to the VRAM * allocator, so it has to be the *first* address the From 4299767d772d4e498998e32157e45841178ab192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Mosn=C3=A1=C4=8Dek?= Date: Thu, 3 Sep 2026 17:56:15 +0200 Subject: [PATCH 0732/2366] =?UTF-8?q?MAINTAINERS,=20mailmap:=20update=20em?= =?UTF-8?q?ail=20address=20for=20Ondrej=20Mosn=C3=A1=C4=8Dek?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I'm going to use my personal email for kernel contributions from now on. Update MAINTAINERS and .mailmap to reflect this. Also switch to use proper spelling with diacritics, since I normally use the full Unicode name with my personal email address. I'm leaving in-code occurences unchanged though, as that would be just unnecessary churn. Link: https://lore.kernel.org/lkml/CAFqZXNvOGbzy8-ZnJtKi94jfu2H173Tz7VYpK8KuseMQS-9tNA@mail.gmail.com/ Signed-off-by: Ondrej Mosnáček Signed-off-by: Paul Moore --- .mailmap | 1 + MAINTAINERS | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 6803f3bd2865..0e672a60afdc 100644 --- a/.mailmap +++ b/.mailmap @@ -699,6 +699,7 @@ Oliver Hartkopp Oliver Hartkopp Oliver Upton Oliver Upton +Ondrej Mosnáček Ondřej Jirman Oza Pawandeep Pali Rohár diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..5dcc75e75aa2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24778,7 +24778,7 @@ K: \bsecurity_[a-z_0-9]\+\b SELINUX SECURITY MODULE M: Paul Moore M: Stephen Smalley -R: Ondrej Mosnacek +R: Ondrej Mosnáček L: selinux@vger.kernel.org S: Supported W: https://github.com/SELinuxProject From afdee49a1b88ed9bb44e2b30e855297c169bcc53 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Thu, 13 Aug 2026 16:31:07 +0800 Subject: [PATCH 0733/2366] nvme-fabrics: fix DHCHAP secret leak on parse failure nvmf_parse_options() duplicates dhchap_secret and dhchap_ctrl_secret with match_strdup() before validating the DHHC-1: representation. If validation fails, the parser returns -EINVAL before the temporary string in p is assigned to opts->dhchap_secret or opts->dhchap_ctrl_secret. nvmf_create_ctrl() subsequently frees opts, but nvmf_free_options() cannot release the unassigned temporary string. Each rejected option therefore leaks one allocation. This is easy to miss because valid secrets transfer ownership to opts and are freed normally, while the malformed-secret path still returns the expected -EINVAL to userspace. With CONFIG_NVME_HOST_AUTH enabled, the leak is reachable before the required-option checks and transport lookup. No NVMe-oF target or working transport connection is required; for example, repeatedly writing dhchap_secret=BAD or dhchap_ctrl_secret=BAD to /dev/nvme-fabrics deterministically takes the leaking parse path. Free the temporary string before leaving both validation error paths. Use kfree_sensitive() because the copied option may contain secret material even when its representation is rejected, matching the sensitive cleanup used for stored DHCHAP secrets. Fixes: f50fff73d620 ("nvme: implement In-Band authentication") Cc: stable@vger.kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Xu Rao Signed-off-by: Keith Busch --- drivers/nvme/host/fabrics.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/nvme/host/fabrics.c b/drivers/nvme/host/fabrics.c index fd5abd04e080..59f823dfbbcc 100644 --- a/drivers/nvme/host/fabrics.c +++ b/drivers/nvme/host/fabrics.c @@ -1028,6 +1028,7 @@ static int nvmf_parse_options(struct nvmf_ctrl_options *opts, } if (strlen(p) < 11 || strncmp(p, "DHHC-1:", 7)) { pr_err("Invalid DH-CHAP secret %s\n", p); + kfree_sensitive(p); ret = -EINVAL; goto out; } @@ -1042,6 +1043,7 @@ static int nvmf_parse_options(struct nvmf_ctrl_options *opts, } if (strlen(p) < 11 || strncmp(p, "DHHC-1:", 7)) { pr_err("Invalid DH-CHAP secret %s\n", p); + kfree_sensitive(p); ret = -EINVAL; goto out; } From ef248d5de4469fb6bbaf8dbe0c4c47800080d648 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Sat, 15 Aug 2026 00:14:27 +0000 Subject: [PATCH 0734/2366] nvme: add missing SRCU grace period in error path nvme_alloc_ns() error path at out_unlink_ns removes ns from the namespace head siblings list with list_del_rcu(&ns->siblings) but does not wait for SRCU readers before freeing the namespace struct. Multipath code iterates the head->list under srcu_read_lock() in nvme_find_path() and nvme_mpath_revalidate_paths(), so a concurrent reader can still hold a reference to ns when kfree(ns) runs. The normal removal path in nvme_ns_remove() correctly calls synchronize_srcu(&ns->head->srcu) after list_del_rcu() to wait for in-progress readers. Add the same grace period in the error path. Fixes: ed754e5deeb1 ("nvme: track shared namespaces") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani Reviewed-by: Sagi Grimberg Reviewed-by: John Garry Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 5f2744be7388..9739ce38b73a 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -4345,6 +4345,9 @@ static void nvme_alloc_ns(struct nvme_ctrl *ctrl, struct nvme_ns_info *info) last_path = true; } mutex_unlock(&ctrl->subsys->lock); + + /* guarantee not available in head->list */ + synchronize_srcu(&ns->head->srcu); if (last_path) nvme_put_ns_head(ns->head); nvme_put_ns_head(ns->head); From 4ed7f3d7d435bf5b63da2814dc9270f5ba896011 Mon Sep 17 00:00:00 2001 From: Mohamed Khalfella Date: Sat, 22 Aug 2026 17:46:41 -0700 Subject: [PATCH 0735/2366] nvme: remove stale namespaces by NSID range during scan nvme_scan_ns_list() drops the stale namespaces in each gap in the reported NSID list one NSID at a time. Every iteration calls nvme_find_get_ns() to look the namespace up and removes it if it is present. The loop runs once per NSID in the gap rather than once per namespace actually present. NSIDs are 32-bit, so a target with a sparse NSID space can make a single gap spin the loop billions of times with nothing to remove. watchdog: BUG: soft lockup - CPU#4 stuck for 26s! Workqueue: nvme-wq nvme_scan_work [nvme_core] RIP: 0010:__srcu_read_unlock+0xb/0x20 Call Trace: nvme_find_get_ns+0x7d/0xb0 [nvme_core] nvme_scan_ns_list+0xe8/0x280 [nvme_core] nvme_scan_work+0x18a/0x280 [nvme_core] process_one_work+0x197/0x380 worker_thread+0x2fe/0x410 kthread+0xe0/0x100 Rename nvme_remove_invalid_namespaces() to nvme_remove_nsid_range() and give it an open (start, end) NSID range. ctrl->namespaces is sorted by NSID, so the whole gap is dropped in a single walk that stops once end is reached. This bounds the work by the namespaces that are present instead of by the size of the gap. Fixes: 540c801c65eb ("NVMe: Implement namespace list scanning") Signed-off-by: Mohamed Khalfella Reviewed-by: Sagi Grimberg Reviewed-by: Randy Jennings Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 9739ce38b73a..32cd1e9a1193 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -155,8 +155,6 @@ static const struct class nvme_ns_chr_class = { }; static void nvme_put_subsystem(struct nvme_subsystem *subsys); -static void nvme_remove_invalid_namespaces(struct nvme_ctrl *ctrl, - unsigned nsid); static void nvme_update_keep_alive(struct nvme_ctrl *ctrl, struct nvme_command *cmd); static int nvme_get_log_lsi(struct nvme_ctrl *ctrl, u32 nsid, u8 log_page, @@ -4516,15 +4514,16 @@ static void nvme_scan_ns_async(void *data, async_cookie_t cookie) nvme_scan_ns(scan_info->ctrl, nsid); } -static void nvme_remove_invalid_namespaces(struct nvme_ctrl *ctrl, - unsigned nsid) +static void nvme_remove_nsid_range(struct nvme_ctrl *ctrl, u32 start, u32 end) { struct nvme_ns *ns, *next; LIST_HEAD(rm_list); mutex_lock(&ctrl->namespaces_lock); list_for_each_entry_safe(ns, next, &ctrl->namespaces, list) { - if (ns->head->ns_id > nsid) { + if (ns->head->ns_id >= end) + break; + if (ns->head->ns_id > start) { list_del_rcu(&ns->list); synchronize_srcu(&ctrl->srcu); list_add_tail_rcu(&ns->list, &rm_list); @@ -4574,13 +4573,14 @@ static int nvme_scan_ns_list(struct nvme_ctrl *ctrl) goto out; async_schedule_domain(nvme_scan_ns_async, &scan_info, &domain); - while (++prev < nsid) - nvme_ns_remove_by_nsid(ctrl, prev); + if (prev + 1 < nsid) + nvme_remove_nsid_range(ctrl, prev, nsid); + prev = max(prev + 1, nsid); } async_synchronize_full_domain(&domain); } out: - nvme_remove_invalid_namespaces(ctrl, prev); + nvme_remove_nsid_range(ctrl, prev, UINT_MAX); free: async_synchronize_full_domain(&domain); kfree(ns_list); @@ -4600,7 +4600,7 @@ static void nvme_scan_ns_sequential(struct nvme_ctrl *ctrl) for (i = 1; i <= nn; i++) nvme_scan_ns(ctrl, i); - nvme_remove_invalid_namespaces(ctrl, nn); + nvme_remove_nsid_range(ctrl, nn, UINT_MAX); } static void nvme_clear_changed_ns_log(struct nvme_ctrl *ctrl) From b2d8f2a3723103abd0f8b388691ad95817d4fff4 Mon Sep 17 00:00:00 2001 From: Mohamed Khalfella Date: Fri, 21 Aug 2026 16:03:09 -0700 Subject: [PATCH 0736/2366] nvme: print namespace IDs as unsigned 32bit value NSIDs are 32-bit unsigned values, but a number of log messages print them with %d. An NSID larger than 0x7fffffff is rendered as a negative number, which is confusing in the kernel log and makes the message hard to correlate with the namespace it talks about. Sparse NSID spaces where high NSIDs are common are the most likely to hit this. The nsid sysfs attribute has the same problem, and there it is worse because userspace parses the value. For example: $ grep . /sys/class/block/nvme0*/nsid /sys/class/block/nvme0c0n1/nsid:10 /sys/class/block/nvme0c0n2/nsid:-16 /sys/class/block/nvme0c0n3/nsid:11 /sys/class/block/nvme0c0n4/nsid:-2000000016 /sys/class/block/nvme0n1/nsid:10 /sys/class/block/nvme0n2/nsid:-16 /sys/class/block/nvme0n3/nsid:11 /sys/class/block/nvme0n4/nsid:-2000000016 $ Print all of them with %u. Several messages in these files, including two in zns.c right next to the ones being changed, already use %u, so this only makes the rest consistent with them. No functional change other than how the NSID is formatted. Fixes: 2b9b6e86bca7 ("NVMe: Export namespace attributes to sysfs") Fixes: 1d5df6af8c74 ("nvme: don't blindly overwrite identifiers on disk revalidate") Fixes: ed754e5deeb1 ("nvme: track shared namespaces") Fixes: 9ad1927a3bc2 ("nvme: always search for namespace head") Fixes: 71010c309454 ("nvme: implement multiple I/O Command Set support") Fixes: 2f4c9ba23b88 ("nvme: export zoned namespaces without Zone Append support read-only") Fixes: 0ec84df4953b ("nvme-core: check ctrl css before setting up zns") Fixes: 2079f41ec6ff ("nvme: check that EUI/GUID/UUID are globally unique") Fixes: ce8d78616a6b ("nvme: warn about shared namespaces without CONFIG_NVME_MULTIPATH") Fixes: ac522fc6c316 ("nvme: don't reject probe due to duplicate IDs for single-ported PCIe devices") Signed-off-by: Mohamed Khalfella Reviewed-by: Sagi Grimberg Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 18 +++++++++--------- drivers/nvme/host/sysfs.c | 2 +- drivers/nvme/host/zns.c | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 32cd1e9a1193..758245c799a1 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -1610,7 +1610,7 @@ static int nvme_identify_ns_descs(struct nvme_ctrl *ctrl, } if (nvme_multi_css(ctrl) && !csi_seen) { - dev_warn(ctrl->device, "Command set not reported for nsid:%d\n", + dev_warn(ctrl->device, "Command set not reported for nsid:%u\n", info->nsid); status = -EINVAL; } @@ -4126,13 +4126,13 @@ static int nvme_init_ns_head(struct nvme_ns *ns, struct nvme_ns_info *info) ((ns->ctrl->subsys->cmic & NVME_CTRL_CMIC_MULTI_CTRL) && info->is_shared)) { dev_err(ctrl->device, - "ignoring nsid %d because of duplicate IDs\n", + "ignoring nsid %u because of duplicate IDs\n", info->nsid); return ret; } dev_err(ctrl->device, - "clearing duplicate IDs for nsid %d\n", info->nsid); + "clearing duplicate IDs for nsid %u\n", info->nsid); dev_err(ctrl->device, "use of /dev/disk/by-id/ may cause data corruption\n"); memset(&info->ids.nguid, 0, sizeof(info->ids.nguid)); @@ -4147,7 +4147,7 @@ static int nvme_init_ns_head(struct nvme_ns *ns, struct nvme_ns_info *info) ret = nvme_subsys_check_duplicate_ids(ctrl->subsys, &info->ids); if (ret) { dev_err(ctrl->device, - "duplicate IDs in subsystem for nsid %d\n", + "duplicate IDs in subsystem for nsid %u\n", info->nsid); goto out_unlock; } @@ -4161,20 +4161,20 @@ static int nvme_init_ns_head(struct nvme_ns *ns, struct nvme_ns_info *info) if ((!info->is_shared || !head->shared) && !list_empty(&head->list)) { dev_err(ctrl->device, - "Duplicate unshared namespace %d\n", + "Duplicate unshared namespace %u\n", info->nsid); goto out_put_ns_head; } if (!nvme_ns_ids_equal(&head->ids, &info->ids)) { dev_err(ctrl->device, - "IDs don't match for shared namespace %d\n", + "IDs don't match for shared namespace %u\n", info->nsid); goto out_put_ns_head; } if (!multipath) { dev_warn(ctrl->device, - "Found shared namespace %d, but multipathing not supported.\n", + "Found shared namespace %u, but multipathing not supported.\n", info->nsid); dev_warn_once(ctrl->device, "Shared namespace support requires core_nvme.multipath=Y.\n"); @@ -4423,7 +4423,7 @@ static void nvme_validate_ns(struct nvme_ns *ns, struct nvme_ns_info *info) if (!nvme_ns_ids_equal(&ns->head->ids, &info->ids)) { dev_err(ns->ctrl->device, - "identifiers changed for nsid %d\n", ns->head->ns_id); + "identifiers changed for nsid %u\n", ns->head->ns_id); goto out; } @@ -4450,7 +4450,7 @@ static void nvme_scan_ns(struct nvme_ctrl *ctrl, unsigned nsid) if (info.ids.csi != NVME_CSI_NVM && !nvme_multi_css(ctrl)) { dev_warn(ctrl->device, - "command set not reported for nsid: %d\n", nsid); + "command set not reported for nsid: %u\n", nsid); return; } diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index abf8edaae371..02a2490a9ed7 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -166,7 +166,7 @@ static DEVICE_ATTR_RO(eui); static ssize_t nsid_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sysfs_emit(buf, "%d\n", dev_to_ns_head(dev)->ns_id); + return sysfs_emit(buf, "%u\n", dev_to_ns_head(dev)->ns_id); } static DEVICE_ATTR_RO(nsid); diff --git a/drivers/nvme/host/zns.c b/drivers/nvme/host/zns.c index 2a152e87bd76..e31ec6f4f94f 100644 --- a/drivers/nvme/host/zns.c +++ b/drivers/nvme/host/zns.c @@ -48,12 +48,12 @@ int nvme_query_zone_info(struct nvme_ns *ns, unsigned lbaf, NVME_CMD_EFFECTS_CSUPP)) { if (test_and_clear_bit(NVME_NS_FORCE_RO, &ns->flags)) dev_warn(ns->ctrl->device, - "Zone Append supported for zoned namespace:%d. Remove read-only mode\n", + "Zone Append supported for zoned namespace:%u. Remove read-only mode\n", ns->head->ns_id); } else { set_bit(NVME_NS_FORCE_RO, &ns->flags); dev_warn(ns->ctrl->device, - "Zone Append not supported for zoned namespace:%d. Forcing to read-only mode\n", + "Zone Append not supported for zoned namespace:%u. Forcing to read-only mode\n", ns->head->ns_id); } From 59fe1cbc57235495a5f08dd53db176e3e3250356 Mon Sep 17 00:00:00 2001 From: Mohamed Khalfella Date: Fri, 21 Aug 2026 16:03:10 -0700 Subject: [PATCH 0737/2366] nvmet: print namespace IDs as unsigned 32bit value struct nvmet_ns.nsid is a u32, but a few messages print it with %d. An NSID larger than 0x7fffffff is rendered as a negative number, which is misleading in general and particularly so for the configfs messages that echo back the NSID the user just asked for. For example: [ T200] nvmet: adding nsid -16 to subsystem mysubsystem Print them with %u. The invalid-NSID error in nvmet_ns_make() keeps its %#x because the two values it rejects, 0 and NVME_NSID_ALL, are more readable in hex format. No functional change other than how the NSID is formatted. Fixes: a07b4970f464 ("nvmet: add a generic NVMe target") Fixes: c6925093d0b2 ("nvmet: Optionally use PCI P2P memory") Fixes: 5a47c2080a73 ("nvmet: support reservation feature") Signed-off-by: Mohamed Khalfella Reviewed-by: Sagi Grimberg Signed-off-by: Keith Busch --- drivers/nvme/target/configfs.c | 4 ++-- drivers/nvme/target/core.c | 2 +- drivers/nvme/target/pr.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c index 413ee2d16d29..6286e38436dd 100644 --- a/drivers/nvme/target/configfs.c +++ b/drivers/nvme/target/configfs.c @@ -814,7 +814,7 @@ static ssize_t nvmet_ns_resv_enable_store(struct config_item *item, mutex_lock(&ns->subsys->lock); if (ns->enabled) { - pr_err("the ns:%d is already enabled.\n", ns->nsid); + pr_err("the ns:%u is already enabled.\n", ns->nsid); mutex_unlock(&ns->subsys->lock); return -EINVAL; } @@ -880,7 +880,7 @@ static struct config_group *nvmet_ns_make(struct config_group *group, goto out; config_group_init_type_name(&ns->group, name, &nvmet_ns_type); - pr_info("adding nsid %d to subsystem %s\n", nsid, subsys->subsysnqn); + pr_info("adding nsid %u to subsystem %s\n", nsid, subsys->subsysnqn); return &ns->group; out: diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index d74c01c98f19..ad60b91ced6c 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -558,7 +558,7 @@ static void nvmet_p2pmem_ns_add_p2p(struct nvmet_ctrl *ctrl, if (ret < 0) pci_dev_put(p2p_dev); - pr_info("using p2pmem on %s for nsid %d\n", pci_name(p2p_dev), + pr_info("using p2pmem on %s for nsid %u\n", pci_name(p2p_dev), ns->nsid); } diff --git a/drivers/nvme/target/pr.c b/drivers/nvme/target/pr.c index 0948a690a1c0..09d8c63f5680 100644 --- a/drivers/nvme/target/pr.c +++ b/drivers/nvme/target/pr.c @@ -145,7 +145,7 @@ static void nvmet_pr_add_resv_log(struct nvmet_ctrl *ctrl, u8 log_type, log.nsid = cpu_to_le32(nsid); if (!kfifo_put(&log_mgr->log_queue, log)) { - pr_info("a reservation log lost, cntlid:%d, log_type:%d, nsid:%d\n", + pr_info("a reservation log lost, cntlid:%d, log_type:%d, nsid:%u\n", ctrl->cntlid, log_type, nsid); log_mgr->lost_count++; } From df7197ebc7280be9f34dfee9757a933ef0b18741 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Sun, 23 Aug 2026 16:46:16 +0900 Subject: [PATCH 0738/2366] nvme-tcp: return -EPROTO for a C2HData on a write The direction check in nvme_tcp_handle_c2h_data() returns -EIO. A C2HData PDU naming a command that did not ask for data is a protocol violation, and the check that rejects a PDU on those grounds a few lines below it - SUCCESS set without LAST - returns -EPROTO. No caller distinguishes the two, so this changes the error code alone. Suggested-by: Sagi Grimberg Signed-off-by: Yehyeong Lee Reviewed-by: Sagi Grimberg Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 643fc503a477..2a15c6143f2a 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -688,7 +688,7 @@ static int nvme_tcp_handle_c2h_data(struct nvme_tcp_queue *queue, dev_err(queue->ctrl->ctrl.device, "queue %d tag %#x unexpected data for a write\n", nvme_tcp_queue_id(queue), rq->tag); - return -EIO; + return -EPROTO; } req = blk_mq_rq_to_pdu(rq); From 14cc5a7e77731497d5bea70f3bb05df7eda982e4 Mon Sep 17 00:00:00 2001 From: Shivam Kumar Date: Fri, 14 Aug 2026 15:48:11 -0400 Subject: [PATCH 0739/2366] nvmet-tcp: fix out-of-bounds write when receiving an over-long PDU nvmet_tcp_try_recv_pdu() reads a PDU header into the fixed 128-byte queue->pdu union, then computes the remaining payload length as queue->left = hdr->hlen - queue->offset + hdgst; and reads that many more bytes into &queue->pdu + queue->offset, without ever bounding the result against sizeof(queue->pdu). A struct nvme_tcp_icreq_pdu is itself 128 bytes, exactly the size of the union. Once a header digest has been negotiated (hdgst = 4), a second ICReq passes the hlen == nvmet_tcp_pdu_size() check but yields queue->left = 128 - 8 + 4 = 124, so bytes 8..132 are written into the 128-byte buffer -- 4 bytes past its end, over queue->hdr_digest and queue->data_digest. Those bytes are attacker-controlled (an ICReq carries no digest), and the duplicate ICReq is only rejected later, after the overflow. A remote unauthenticated host can thus corrupt kernel memory adjacent to the receive buffer. Reject any PDU whose declared length would read past the end of queue->pdu before the second recv. Fixes: 872d26a391da ("nvmet-tcp: add NVMe over TCP target driver") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Shivam Kumar Cc: stable@vger.kernel.org Reviewed-by: Sagi Grimberg Signed-off-by: Keith Busch --- drivers/nvme/target/tcp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index e4f603b2ace7..1e2346ede900 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -1244,6 +1244,8 @@ static int nvmet_tcp_try_recv_pdu(struct nvmet_tcp_queue *queue) } queue->left = hdr->hlen - queue->offset + hdgst; + if (queue->left > sizeof(queue->pdu) - queue->offset) + return -EPROTO; goto recv; } From 08acb54b063a33730eb1ae1e0f89bf36542bac9f Mon Sep 17 00:00:00 2001 From: Xixin Liu Date: Wed, 19 Aug 2026 08:50:00 +0800 Subject: [PATCH 0740/2366] nvme-tcp: defer TLS inline send to io_work blk_mq holds set->srcu while queuing and running requests. The kTLS software send path takes ctx->tx_lock. lockdep knows that tx_lock nests under elevator_lock which then waits on srcu, so an inline send from that path under TLS triggers circular locking. Skip the inline send optimization for TLS queues so the send runs from the workqueue instead. The same workqueue already retries TLS sends on write-space notifications. Plain TCP keeps the inline path. Fixes: be8e82caa685 ("nvme-tcp: enable TLS handshake upcall") Reviewed-by: Hannes Reinecke Signed-off-by: Xixin Liu Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 2a15c6143f2a..921934028e0b 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -413,8 +413,13 @@ static inline void nvme_tcp_queue_request(struct nvme_tcp_request *req, * if we're the first on the send_list and we can try to send * directly, otherwise queue io_work. Also, only do that if we * are on the same cpu, so we don't introduce contention. + * + * TLS kTLS send takes ctx->tx_lock while blk_mq holds set->srcu. + * lockdep reports circular locking via elevator_lock. Defer TLS + * sends to the io workqueue instead of inline from this path. */ if (queue->io_cpu == raw_smp_processor_id() && + !nvme_tcp_queue_tls(queue) && empty && mutex_trylock(&queue->send_mutex)) { nvme_tcp_send_all(queue); mutex_unlock(&queue->send_mutex); From db62b35cbca052860c519cbcabe7650708528738 Mon Sep 17 00:00:00 2001 From: Shivam Kumar Date: Thu, 27 Aug 2026 15:24:55 -0400 Subject: [PATCH 0741/2366] nvmet-tcp: reject unsolicited H2CData PDUs nvmet_tcp_handle_h2c_data_pdu() accepts an H2CData PDU after only checking that its TTAG is a valid in-range command index and that the command's data buffers are mapped. It never checks that the target has actually solicited that data by sending an R2T for the command. A remote host can abuse this. It submits a write command that takes the R2T path and, before the target transmits the R2T, sends an H2CData PDU for that command's tag. The data completes the command early, and when the command then fails synchronously (e.g. a length mismatch caught by nvmet_check_transfer_len()), it is completed a second time. Each completion calls nvmet_tcp_queue_response(), so the same command is added to queue->resp_list twice while it is still linked; the second llist_add() makes the node point to itself (lentry->next == lentry). nvmet_tcp_process_resp_list() then walks that self-referential node and adds the command to resp_send_list twice. With CONFIG_DEBUG_LIST this trips the "list_add double add" check (kernel BUG); without it the loop never terminates and the nvmet_tcp workqueue wedges (soft-lockup). It is remotely triggerable and needs no authentication on an allow_any_host subsystem. Track whether an R2T has been transmitted for a command and reject an H2CData PDU that arrives before it. The flag is cleared on command reuse (nvmet_tcp_get_cmd() zeroes cmd->flags) and stays set across the multiple H2CData PDUs of a single solicited transfer. Fixes: 872d26a391da ("nvmet-tcp: add NVMe over TCP target driver") Cc: stable@vger.kernel.org Reviewed-by: Sagi Grimberg Signed-off-by: Shivam Kumar Signed-off-by: Keith Busch --- drivers/nvme/target/tcp.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c index 1e2346ede900..e59810175262 100644 --- a/drivers/nvme/target/tcp.c +++ b/drivers/nvme/target/tcp.c @@ -103,6 +103,7 @@ enum nvmet_tcp_recv_state { enum { NVMET_TCP_F_INIT_FAILED = (1 << 0), + NVMET_TCP_F_R2T_SENT = (1 << 1), }; struct nvmet_tcp_cmd { @@ -776,6 +777,7 @@ static int nvmet_try_send_r2t(struct nvmet_tcp_cmd *cmd, bool last_in_batch) return -EAGAIN; cmd->queue->snd_cmd = NULL; + cmd->flags |= NVMET_TCP_F_R2T_SENT; return 1; } @@ -1009,6 +1011,12 @@ static int nvmet_tcp_handle_h2c_data_pdu(struct nvmet_tcp_queue *queue) cmd = &queue->connect; } + if (unlikely(!(cmd->flags & NVMET_TCP_F_R2T_SENT))) { + pr_err("queue %d: unsolicited H2CData (ttag %u)\n", + queue->idx, data->ttag); + goto err_proto; + } + if (le32_to_cpu(data->data_offset) != cmd->rbytes_done) { pr_err("ttag %u unexpected data offset %u (expected %u)\n", data->ttag, le32_to_cpu(data->data_offset), From 5cdd07a6882504c4b6e61169cce79e0720f77fca Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Tue, 1 Sep 2026 09:46:31 -0700 Subject: [PATCH 0742/2366] MAINTAINERS: update nvme entry Update Jens' entry to match the mail address of his other entries. Acked-by: Jens Axboe Signed-off-by: Keith Busch --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 46d98b53729d..91d6086196e2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19366,7 +19366,7 @@ F: include/linux/platform_data/x86/nvidia-wmi-ec-backlight.h NVM EXPRESS DRIVER M: Keith Busch -M: Jens Axboe +M: Jens Axboe M: Christoph Hellwig M: Sagi Grimberg L: linux-nvme@lists.infradead.org From eaa948c0e19b1bb2d93262207bca0c3d19cc3406 Mon Sep 17 00:00:00 2001 From: Kazuki Hanai Date: Sun, 30 Aug 2026 22:11:05 +0900 Subject: [PATCH 0743/2366] nvmet-auth: Synchronize timeout work during SQ teardown nvmet_auth_sq_free() cancels auth_expired_work with cancel_delayed_work(). If the work has already started, cancellation does not wait for the callback. Transport teardown can consequently free or reuse the queue containing struct nvmet_sq while nvmet_auth_expired_work() still accesses that SQ. Add a teardown-specific helper that synchronously drains the delayed work before freeing authentication state, and use it from nvmet_sq_destroy(). Keep the non-synchronous helper for in-band authentication state cleanup, where the SQ owner remains alive. Fixes: 1a70200f404a ("nvmet-auth: expire authentication sessions") Cc: stable@vger.kernel.org Signed-off-by: Kazuki Hanai Reviewed-by: Sagi Grimberg Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/target/auth.c | 6 ++++++ drivers/nvme/target/core.c | 2 +- drivers/nvme/target/nvmet.h | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/target/auth.c b/drivers/nvme/target/auth.c index edb9627d97b0..a55319bcdbd1 100644 --- a/drivers/nvme/target/auth.c +++ b/drivers/nvme/target/auth.c @@ -238,6 +238,12 @@ void nvmet_auth_sq_free(struct nvmet_sq *sq) sq->dhchap_skey = NULL; } +void nvmet_auth_sq_destroy(struct nvmet_sq *sq) +{ + cancel_delayed_work_sync(&sq->auth_expired_work); + nvmet_auth_sq_free(sq); +} + void nvmet_destroy_auth(struct nvmet_ctrl *ctrl) { ctrl->shash_id = 0; diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index ad60b91ced6c..1663ab7ac607 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -980,7 +980,7 @@ void nvmet_sq_destroy(struct nvmet_sq *sq) wait_for_completion(&sq->confirm_done); wait_for_completion(&sq->free_done); percpu_ref_exit(&sq->ref); - nvmet_auth_sq_free(sq); + nvmet_auth_sq_destroy(sq); nvmet_cq_put(sq->cq); /* diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h index e362d7913a38..dbda55895f4f 100644 --- a/drivers/nvme/target/nvmet.h +++ b/drivers/nvme/target/nvmet.h @@ -924,6 +924,7 @@ u8 nvmet_setup_auth(struct nvmet_ctrl *ctrl, struct nvmet_sq *sq, bool reset); void nvmet_auth_sq_init(struct nvmet_sq *sq); void nvmet_destroy_auth(struct nvmet_ctrl *ctrl); void nvmet_auth_sq_free(struct nvmet_sq *sq); +void nvmet_auth_sq_destroy(struct nvmet_sq *sq); int nvmet_setup_dhgroup(struct nvmet_ctrl *ctrl, u8 dhgroup_id); bool nvmet_check_auth_status(struct nvmet_req *req); int nvmet_auth_host_hash(struct nvmet_req *req, u8 *response, @@ -950,6 +951,7 @@ static inline void nvmet_auth_sq_init(struct nvmet_sq *sq) } static inline void nvmet_destroy_auth(struct nvmet_ctrl *ctrl) {}; static inline void nvmet_auth_sq_free(struct nvmet_sq *sq) {}; +static inline void nvmet_auth_sq_destroy(struct nvmet_sq *sq) {}; static inline bool nvmet_check_auth_status(struct nvmet_req *req) { return true; From 09d0c07bd9ce3b2f2d993f672698d32a17543c32 Mon Sep 17 00:00:00 2001 From: Seokgyu Choi Date: Thu, 27 Aug 2026 07:52:21 +0000 Subject: [PATCH 0744/2366] nvmet: reject namespace enable without device path A newly allocated namespace has a NULL device_path until userspace configures the device_path attribute. If buffered_io is enabled before device_path is configured, nvmet_bdev_ns_enable() returns -ENOTBLK and nvmet_ns_enable() falls back to nvmet_file_ns_enable(). The latter passes the NULL device_path to filp_open(), causing a NULL pointer dereference in getname_kernel(). Reject namespace enable when device_path has not been configured. Reported-by: syzbot+f613f9f010ec98eb9d86@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f613f9f010ec98eb9d86 Signed-off-by: Seokgyu Choi Reviewed-by: Sagi Grimberg Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/target/core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 1663ab7ac607..43871a8f56ca 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -591,6 +591,11 @@ int nvmet_ns_enable(struct nvmet_ns *ns) if (ns->enabled) goto out_unlock; + if (!ns->device_path) { + ret = -EINVAL; + goto out_unlock; + } + ret = nvmet_bdev_ns_enable(ns); if (ret == -ENOTBLK) ret = nvmet_file_ns_enable(ns); From 56e6279266f6962bb2d38a54397e3c605165b0c5 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 14 Aug 2026 16:38:34 +0200 Subject: [PATCH 0745/2366] nvme-fc: fix double free of fabrics options when nvme_add_ctrl() fails nvmf_create_ctrl() owns the fabrics options and frees them whenever ->create_ctrl() returns an error, so a transport must not free them on its own error paths. nvme-fc tracks this by testing ctrl->ctrl.opts in nvme_fc_ctrl_free(), which requires nvme_fc_init_ctrl() to clear that pointer on every error exit. The coupling is implicit, and commit 1a9e218195a5 ("nvme: split device add from initialization") broke it by adding a second error exit. When nvme_add_ctrl() fails, nvme_fc_init_ctrl() jumps to out_put_ctrl:, past the "ctrl->ctrl.opts = NULL" that only sits on the fail_ctrl: path, so nvme_fc_ctrl_free() frees the options and nvmf_create_ctrl() frees them a second time: BUG: KASAN: slab-use-after-free in nvmf_free_options+0x30/0x190 nvmf_free_options+0x30/0x190 drivers/nvme/host/fabrics.c:1284 nvmf_create_ctrl drivers/nvme/host/fabrics.c:1374 [inline] Freed by task 5534: nvme_fc_ctrl_free drivers/nvme/host/fc.c:2374 [inline] nvme_fc_init_ctrl+0xe17/0x1450 drivers/nvme/host/fc.c:3605 nvme_add_ctrl() fails when dev_set_name() cannot allocate, so this is reachable under memory pressure or fault injection. Without KASAN the options are freed twice. Rather than clear the pointer on the second exit as well, derive ownership the way nvme-tcp, nvme-rdma and nvme-loop do, from list membership: their free_ctrl leaves the options alone unless the controller made it onto the transport list. The list cannot simply be populated on the success path as it is there. nvme-fc runs the initial connect synchronously via flush_delayed_work(), and the controller has to be reachable on rport->ctrl_list for the whole of it: nvme_fc_unregister_remoteport() needs to find it to signal connectivity loss, nvme_fc_match_disconn_ls() matches an incoming Disconnect Association LS against ctrl->association_id, which is only assigned during that window, nvme_fc_resume_controller() needs it on remoteport re-registration, and nvme_fc_existing_controller() uses it to reject a duplicate connect racing the one in flight. Keep the insertion where it is and add a fail_unlist: label, falling into fail_ctrl:, for the error paths that run after it. The earlier error paths never reach the insertion and keep using fail_ctrl: directly, so the list is only touched where the controller is actually on it. nvme_fc_ctrl_free() cannot use the plain "goto free_ctrl" the other transports use, because it still has to put_device(), release the rport reference and free the ida entry for resources taken before the insertion. Sample list_empty() under rport->lock instead. ctrl->ctrl.opts also stays valid for the whole teardown now. That is not the bug being fixed, but it removes some fragility around the old idiom: nvme_free_ctrl() calls nvme_auth_free() before ->free_ctrl(), and ctrl_max_dhchaps() dereferences ctrl->opts without a NULL check when ctrl->dhchap_ctxs is set, which nvme-fc permits since NVMF_ALLOWED_OPTS allows the dhchap options. The nvme sysfs attributes that dereference ctrl->opts, such as hostnqn and address, evaluate their is_visible() test once at device_add() time and stay readable until cdev_device_del(). Fixes: 1a9e218195a5 ("nvme: split device add from initialization") Cc: stable@vger.kernel.org Reported-by: syzbot+f58e57380a6083c4041d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f58e57380a6083c4041d Signed-off-by: Niklas Cassel Tested-by: Rihyeon Kim Reviewed-by: Hannes Reinecke Signed-off-by: Keith Busch --- drivers/nvme/host/fc.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index 023710e08e0d..48454cb7a0fc 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -2364,9 +2364,15 @@ nvme_fc_ctrl_free(struct kref *ref) struct nvme_fc_ctrl *ctrl = container_of(ref, struct nvme_fc_ctrl, ref); unsigned long flags; + bool owns_opts; - /* remove from rport list */ + /* + * Presence on the rport list means nvme_fc_init_ctrl() completed, + * and with it ownership of the fabrics options passed to it. If it + * failed instead, the options still belong to nvmf_create_ctrl(). + */ spin_lock_irqsave(&ctrl->rport->lock, flags); + owns_opts = !list_empty(&ctrl->ctrl_list); list_del(&ctrl->ctrl_list); spin_unlock_irqrestore(&ctrl->rport->lock, flags); @@ -2376,7 +2382,7 @@ nvme_fc_ctrl_free(struct kref *ref) nvme_fc_rport_put(ctrl->rport); ida_free(&nvme_fc_ctrl_cnt, ctrl->cnum); - if (ctrl->ctrl.opts) + if (owns_opts) nvmf_free_options(ctrl->ctrl.opts); kfree(ctrl); } @@ -3575,14 +3581,14 @@ nvme_fc_init_ctrl(struct device *dev, struct nvmf_ctrl_options *opts, if (!nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_CONNECTING)) { dev_err(ctrl->ctrl.device, "NVME-FC{%d}: failed to init ctrl state\n", ctrl->cnum); - goto fail_ctrl; + goto fail_unlist; } if (!queue_delayed_work(nvme_wq, &ctrl->connect_work, 0)) { dev_err(ctrl->ctrl.device, "NVME-FC{%d}: failed to schedule initial connect\n", ctrl->cnum); - goto fail_ctrl; + goto fail_unlist; } flush_delayed_work(&ctrl->connect_work); @@ -3593,14 +3599,22 @@ nvme_fc_init_ctrl(struct device *dev, struct nvmf_ctrl_options *opts, return &ctrl->ctrl; +fail_unlist: + /* + * Leaving the list hands the options back to nvmf_create_ctrl(); + * see nvme_fc_ctrl_free(). Re-init so that list_empty() there + * reports the controller as unlisted. + */ + spin_lock_irqsave(&rport->lock, flags); + list_del_init(&ctrl->ctrl_list); + spin_unlock_irqrestore(&rport->lock, flags); + fail_ctrl: nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_DELETING); cancel_work_sync(&ctrl->ioerr_work); cancel_work_sync(&ctrl->ctrl.reset_work); cancel_delayed_work_sync(&ctrl->connect_work); - ctrl->ctrl.opts = NULL; - if (ctrl->ctrl.admin_tagset) nvme_remove_admin_tag_set(&ctrl->ctrl); /* initiate nvme ctrl ref counting teardown */ From fd9beb8870736e1c6a0b2351d88a161aaeb2b326 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 30 Aug 2026 23:05:01 -0700 Subject: [PATCH 0746/2366] nvme-tcp.h: drop kernel-doc comments, fix a few descriptions Expand @fei into @feil and @feih because the field was split due to it not being 32-bit aligned. Struct member @hdr was described twice in struct nvme_tcp_rsp_pdu, so drop one of them. These structs are defined in a spec outside of the kernel, so kernel-doc comments for them aren't needed here as well. This avoids kernel-doc warnings: Warning: include/linux/nvme-tcp.h:95 struct member 'rsvd2' not described in 'nvme_tcp_icreq_pdu' Warning: include/linux/nvme-tcp.h:113 struct member 'rsvd' not described in 'nvme_tcp_icresp_pdu' Warning: include/linux/nvme-tcp.h:128 struct member 'feil' not described in 'nvme_tcp_term_pdu' Warning: include/linux/nvme-tcp.h:128 struct member 'feiu' not described in 'nvme_tcp_term_pdu' Warning: include/linux/nvme-tcp.h:128 struct member 'rsvd' not described in 'nvme_tcp_term_pdu' Warning: include/linux/nvme-tcp.h:169 struct member 'rsvd' not described in 'nvme_tcp_r2t_pdu' Warning: include/linux/nvme-tcp.h:187 struct member 'rsvd' not described in 'nvme_tcp_data_pdu' Signed-off-by: Randy Dunlap Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- include/linux/nvme-tcp.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/linux/nvme-tcp.h b/include/linux/nvme-tcp.h index e435250fcb4d..859338da8573 100644 --- a/include/linux/nvme-tcp.h +++ b/include/linux/nvme-tcp.h @@ -77,7 +77,7 @@ struct nvme_tcp_hdr { __le32 plen; }; -/** +/* * struct nvme_tcp_icreq_pdu - nvme tcp initialize connection request pdu * * @hdr: pdu generic header @@ -95,7 +95,7 @@ struct nvme_tcp_icreq_pdu { __u8 rsvd2[112]; }; -/** +/* * struct nvme_tcp_icresp_pdu - nvme tcp initialize connection response pdu * * @hdr: pdu common header @@ -113,12 +113,13 @@ struct nvme_tcp_icresp_pdu { __u8 rsvd[112]; }; -/** +/* * struct nvme_tcp_term_pdu - nvme tcp terminate connection pdu * * @hdr: pdu common header * @fes: fatal error status - * @fei: fatal error information + * @feil: fatal error information (low 16 bits) + * @feih: fatal error information (high 16 bits) */ struct nvme_tcp_term_pdu { struct nvme_tcp_hdr hdr; @@ -128,7 +129,7 @@ struct nvme_tcp_term_pdu { __u8 rsvd[10]; }; -/** +/* * struct nvme_tcp_cmd_pdu - nvme tcp command capsule pdu * * @hdr: pdu common header @@ -139,10 +140,9 @@ struct nvme_tcp_cmd_pdu { struct nvme_command cmd; }; -/** +/* * struct nvme_tcp_rsp_pdu - nvme tcp response capsule pdu * - * @hdr: pdu common header * @hdr: nvme-tcp generic header * @cqe: nvme completion queue entry */ @@ -151,7 +151,7 @@ struct nvme_tcp_rsp_pdu { struct nvme_completion cqe; }; -/** +/* * struct nvme_tcp_r2t_pdu - nvme tcp ready-to-transfer pdu * * @hdr: pdu common header @@ -169,7 +169,7 @@ struct nvme_tcp_r2t_pdu { __u8 rsvd[4]; }; -/** +/* * struct nvme_tcp_data_pdu - nvme tcp data pdu * * @hdr: pdu common header From 35af118326b22d840a3be271daa080728844cc4d Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:05 +0200 Subject: [PATCH 0747/2366] ALSA: hda: ext: Clean up links if their initialization fails snd_hdac_ext_bus_get_ml_capabilities() allocates the hlink nodes one-by-one but the procedure may fails due to -ENOMEM in the middle of it. Clean up the list before returning the error code to simply the function usage. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-2-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/hda/core/ext/controller.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/hda/core/ext/controller.c b/sound/hda/core/ext/controller.c index b1f1eff1d181..6d0af7e6f63c 100644 --- a/sound/hda/core/ext/controller.c +++ b/sound/hda/core/ext/controller.c @@ -90,8 +90,10 @@ int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_bus *bus) for (idx = 0; idx < link_count; idx++) { hlink = kzalloc_obj(*hlink); - if (!hlink) + if (!hlink) { + snd_hdac_ext_link_free_all(bus); return -ENOMEM; + } hlink->index = idx; hlink->bus = bus; hlink->ml_addr = bus->mlcap + AZX_ML_BASE + From 48afc07c4f1b9c3cdbe66ddc3e7677ed395ce75f Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:06 +0200 Subject: [PATCH 0748/2366] ALSA: hda: ext: Clean up streams if their initialization fails snd_hdac_ext_stream_init_all() does not rollback changes done when the allocation fails. Fix that to simplify its usage. Signed-off-by: Cezary Rojewski Acked-by: Takashi Iwai Link: https://patch.msgid.link/20260902081814.1590883-3-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/hda/core/ext/stream.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/hda/core/ext/stream.c b/sound/hda/core/ext/stream.c index 517bd151fcc3..1f96e0484660 100644 --- a/sound/hda/core/ext/stream.c +++ b/sound/hda/core/ext/stream.c @@ -102,8 +102,10 @@ int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, for (i = 0; i < num_stream; i++) { struct hdac_ext_stream *hext_stream = kzalloc_obj(*hext_stream); - if (!hext_stream) + if (!hext_stream) { + snd_hdac_ext_stream_free_all(bus); return -ENOMEM; + } tag = ++stream_tag; snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag); idx++; @@ -111,7 +113,6 @@ int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, } return 0; - } EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all); From ce4d7356660ab7f8fd7c3119c16fef5c9b9adec9 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:07 +0200 Subject: [PATCH 0749/2366] ASoC: Intel: avs: Clean up the bus when its initialization fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_hdac_i915_init() which is part of the initialization may return -EPROBE_DEFER what fails the procedure and the existing avs_bus_init() and avs_pci_probe() do not clean up the bus fields with snd_hdac_ext_bus_exit() when that happens. Fix avs_bus_init() by rearranging the initialization blocks: allocations first, snd_hdac_ext_bus_init() last. Such approach generates no error-path whilst still achieving the goal of cleaning up the bus. For avs_pci_probe() update the existing error-path instead. Co-developed-by: Amadeusz Sławiński Signed-off-by: Amadeusz Sławiński Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-4-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/core.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c index 2afe59646896..f45256ff5bac 100644 --- a/sound/soc/intel/avs/core.c +++ b/sound/soc/intel/avs/core.c @@ -383,6 +383,18 @@ static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct struct device *dev = &pci->dev; int ret; + ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return -ENOMEM; + + adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); + if (!adev->modcfg_buf) + return -ENOMEM; + + ret = avs_ipc_init(ipc, dev); + if (ret < 0) + return ret; + ret = snd_hdac_ext_bus_init(&bus->core, dev, NULL, &soc_hda_ext_bus_ops); if (ret < 0) return ret; @@ -394,17 +406,6 @@ static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct bus->mixer_assigned = -1; mutex_init(&bus->prepare_mutex); - ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); - if (!ipc) - return -ENOMEM; - ret = avs_ipc_init(ipc, dev); - if (ret < 0) - return ret; - - adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); - if (!adev->modcfg_buf) - return -ENOMEM; - adev->dev = dev; adev->spec = (const struct avs_spec *)id->driver_data; adev->ipc = ipc; @@ -456,13 +457,14 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) ret = pcim_request_all_regions(pci, "AVS HDAudio"); if (ret < 0) - return ret; + goto err_request_regions; bus->addr = pci_resource_start(pci, 0); bus->remap_addr = pci_ioremap_bar(pci, 0); if (!bus->remap_addr) { dev_err(bus->dev, "ioremap error\n"); - return -ENXIO; + ret = -ENXIO; + goto err_request_regions; } adev->dsp_ba = pci_ioremap_bar(pci, 4); @@ -519,6 +521,8 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) iounmap(adev->dsp_ba); err_remap_bar4: iounmap(bus->remap_addr); +err_request_regions: + snd_hdac_ext_bus_exit(bus); return ret; } From 559ea14b7ae7c7562b48759fa545b64958f35b73 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:08 +0200 Subject: [PATCH 0750/2366] ASoC: Intel: avs: Clean up the bus when fetching ML caps fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_hdac_ext_bus_get_ml_capabilities() may fail and its return code shall be checked and accounted for. Address the issue by updating the error-path for avs_pci_probe(). At the same time, if the function in question succeeds but the next part of avs_pci_probe() fails, the hlink list shall be cleaned up before leaving the scope. Fixes: 1affc44ea5dd ("ASoC: Intel: avs: PCI driver implementation") Co-developed-by: Amadeusz Sławiński Signed-off-by: Amadeusz Sławiński Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-5-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/core.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c index f45256ff5bac..8f27a7d43718 100644 --- a/sound/soc/intel/avs/core.c +++ b/sound/soc/intel/avs/core.c @@ -475,8 +475,13 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) } snd_hdac_bus_parse_capabilities(bus); - if (bus->mlcap) - snd_hdac_ext_bus_get_ml_capabilities(bus); + if (bus->mlcap) { + ret = snd_hdac_ext_bus_get_ml_capabilities(bus); + if (ret < 0) { + dev_err(dev, "failed to get ml capabilities: %d\n", ret); + goto err_ml_cap; + } + } if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); @@ -518,6 +523,8 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) snd_hdac_bus_free_stream_pages(bus); snd_hdac_ext_stream_free_all(bus); err_init_streams: + snd_hdac_ext_link_free_all(bus); +err_ml_cap: iounmap(adev->dsp_ba); err_remap_bar4: iounmap(bus->remap_addr); From f4ba00bb56a8511bafbd19826501d27157141c6d Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:09 +0200 Subject: [PATCH 0751/2366] ASoC: Intel: avs: Clean up streams if their initialization fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When streams are being initialized the memory allocation may fail. Have an error path and return early if that is the case. Fixes: 1affc44ea5dd ("ASoC: Intel: avs: PCI driver implementation") Co-developed-by: Amadeusz Sławiński Signed-off-by: Amadeusz Sławiński Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-6-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/core.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c index 8f27a7d43718..611ae9f034d4 100644 --- a/sound/soc/intel/avs/core.c +++ b/sound/soc/intel/avs/core.c @@ -92,16 +92,28 @@ static int avs_hdac_bus_init_streams(struct hdac_bus *bus) { unsigned int cp_streams, pb_streams; unsigned int gcap; + int ret; gcap = snd_hdac_chip_readw(bus, GCAP); cp_streams = (gcap >> 8) & 0x0F; pb_streams = (gcap >> 12) & 0x0F; bus->num_streams = cp_streams + pb_streams; - snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); - snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); + ret = snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + ret = snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto err; - return snd_hdac_bus_alloc_stream_pages(bus); + ret = snd_hdac_bus_alloc_stream_pages(bus); + if (ret) + goto err; + + return 0; +err: + snd_hdac_ext_stream_free_all(bus); + return ret; } static bool avs_hdac_bus_init_chip(struct hdac_bus *bus, bool full_reset) From c6dceca9f78fbd478c41735457c4de9c25a6b1c8 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:10 +0200 Subject: [PATCH 0752/2366] ASoC: Intel: avs: Do not ignore -ENOENT when loading a topology avs_load_topology() combines request_firmware() and snd_soc_tplg_component_load(). The fallback mechanism introduced for the HDAudio based boards honors -ENOENT and checks for a generic topology if no specific is found before giving up and failing the component probing. However, if -ENOENT is returned by the latter function - snd_soc_tplg_component_load() - is shall not be ignored. That means there is an actual problem with the topology file and no fallback shall be attempted. Fixes: 739c031110da ("ASoC: Intel: avs: Provide support for fallback topology") Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-7-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/pcm.c | 34 ++++++++++++++++++++-------------- sound/soc/intel/avs/topology.c | 2 +- sound/soc/intel/avs/topology.h | 1 + 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c index 2b886fae8209..ad25bd355769 100644 --- a/sound/soc/intel/avs/pcm.c +++ b/sound/soc/intel/avs/pcm.c @@ -6,6 +6,7 @@ // Amadeusz Slawinski // +#include #include #include #include @@ -987,13 +988,25 @@ static int avs_component_load_libraries(struct avs_soc_component *acomp) return ret; } +static int avs_request_topology(struct snd_soc_component *component, const char *name, + const struct firmware **fw) +{ + char *fullname __free(kfree) = NULL; + + fullname = kasprintf(GFP_KERNEL, "%s/%s", component->driver->topology_name_prefix, name); + if (!fullname) + return -ENOMEM; + + return request_firmware(fw, fullname, component->dev); +} + static int avs_component_probe(struct snd_soc_component *component) { struct snd_soc_card *card = component->card; struct snd_soc_acpi_mach *mach; struct avs_soc_component *acomp; + const struct firmware *fw; struct avs_dev *adev; - char *filename; int ret; dev_dbg(card->dev, "probing %s card %s\n", component->name, card->name); @@ -1009,13 +1022,7 @@ static int avs_component_probe(struct snd_soc_component *component) goto finalize; /* Load specified topology and create debugfs for it. */ - filename = kasprintf(GFP_KERNEL, "%s/%s", component->driver->topology_name_prefix, - mach->tplg_filename); - if (!filename) - return -ENOMEM; - - ret = avs_load_topology(component, filename); - kfree(filename); + ret = avs_request_topology(component, mach->tplg_filename, &fw); if (ret == -ENOENT && !strncmp(mach->tplg_filename, "hda-", 4)) { unsigned int vendor_id; @@ -1030,18 +1037,17 @@ static int avs_component_probe(struct snd_soc_component *component) "hda-generic-tplg.bin"); if (!mach->tplg_filename) return -ENOMEM; - filename = kasprintf(GFP_KERNEL, "%s/%s", component->driver->topology_name_prefix, - mach->tplg_filename); - if (!filename) - return -ENOMEM; dev_info(card->dev, "trying to load fallback topology %s\n", mach->tplg_filename); - ret = avs_load_topology(component, filename); - kfree(filename); + ret = avs_request_topology(component, mach->tplg_filename, &fw); } if (ret < 0) return ret; + ret = snd_soc_tplg_component_load(component, &avs_tplg_ops, fw); + if (ret) + return ret; + ret = avs_component_load_libraries(acomp); if (ret < 0) { dev_err(card->dev, "libraries loading failed: %d\n", ret); diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c index 673ac31f2fea..d5e641c73faf 100644 --- a/sound/soc/intel/avs/topology.c +++ b/sound/soc/intel/avs/topology.c @@ -2194,7 +2194,7 @@ avs_control_load(struct snd_soc_component *comp, int index, struct snd_kcontrol_ return 0; } -static const struct snd_soc_tplg_ops avs_tplg_ops = { +const struct snd_soc_tplg_ops avs_tplg_ops = { .io_ops = avs_control_ops, .io_ops_count = ARRAY_SIZE(avs_control_ops), .control_load = avs_control_load, diff --git a/sound/soc/intel/avs/topology.h b/sound/soc/intel/avs/topology.h index 1cf7455b6c01..b5799c994b88 100644 --- a/sound/soc/intel/avs/topology.h +++ b/sound/soc/intel/avs/topology.h @@ -230,6 +230,7 @@ struct avs_tplg_module { struct list_head node; }; +extern const struct snd_soc_tplg_ops avs_tplg_ops; struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp); int avs_load_topology(struct snd_soc_component *comp, const char *filename); From 363a6969f7f875b461236bf520d35f35c3b0e5b1 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:11 +0200 Subject: [PATCH 0753/2366] ASoC: Intel: avs: Cancel d0ix_work asynchrounously during recovery Tests with corrupted firmware binaries prove that the recovery procedure can hit deadlock with d0ix_work if the work has been scheduled shortly before the event that triggered the recovery e.g.: timeouts on communication with a dead AudioDSP firmware. At the same time, the ready-check shall be done after acquiring the msg_mutex as the flag might have been modified by the time the lock is granted. The recovery case is one of such examples. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-8-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/ipc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c index 39b0de9831da..5010b0f5be47 100644 --- a/sound/soc/intel/avs/ipc.c +++ b/sound/soc/intel/avs/ipc.c @@ -172,7 +172,7 @@ static void avs_dsp_exception_caught(struct avs_dev *adev, union avs_notify_msg /* Avoid deadlock as the exception may be the response to SET_D0IX. */ if (current_work() != &ipc->d0ix_work.work) - cancel_delayed_work_sync(&ipc->d0ix_work); + cancel_delayed_work(&ipc->d0ix_work); ipc->in_d0ix = false; /* Re-enabled on recovery completion. */ pm_runtime_disable(adev->dev); @@ -395,11 +395,11 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request struct avs_ipc *ipc = adev->ipc; int ret; + guard(mutex)(&ipc->msg_mutex); + if (!ipc->ready) return -EPERM; - guard(mutex)(&ipc->msg_mutex); - spin_lock(&ipc->rx_lock); avs_ipc_msg_init(ipc, reply); avs_dsp_send_tx(adev, request, true); From d4fa6f94b91137e329ea3f5b360e140b227bb696 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:12 +0200 Subject: [PATCH 0754/2366] ASoC: Intel: avs: Fix unbalanced module reference count strace_open() invokes try_module_get() which on success takes the module reference. If any follow up operation causes strace_open() to fail, the refcount shall be put down. Fixes: 0a5fb3cc28fd ("ASoC: Intel: avs: Keep module refcount up when gathering traces") Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-9-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/debugfs.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sound/soc/intel/avs/debugfs.c b/sound/soc/intel/avs/debugfs.c index 9ab503da3b75..bc02737720ab 100644 --- a/sound/soc/intel/avs/debugfs.c +++ b/sound/soc/intel/avs/debugfs.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -236,15 +237,20 @@ static int strace_open(struct inode *inode, struct file *file) if (!try_module_get(adev->dev->driver->owner)) return -ENODEV; - if (kfifo_initialized(&adev->trace_fifo)) - return -EBUSY; + if (kfifo_initialized(&adev->trace_fifo)) { + ret = -EBUSY; + goto err; + } ret = kfifo_alloc(&adev->trace_fifo, PAGE_SIZE, GFP_KERNEL); if (ret < 0) - return ret; + goto err; file->private_data = adev; return 0; +err: + module_put(adev->dev->driver->owner); + return ret; } static int strace_release(struct inode *inode, struct file *file) From 681e91035dc794896a904852040837190e5041f5 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:13 +0200 Subject: [PATCH 0755/2366] ASoC: Intel: avs: Refactor and fix init_config access Existing code accesses enties found in ->init_configs array through indexes that are part of ->config_ids array. Those two are limited by: ->num_init_configs and ->num_config_ids respectively. Using ID larger or equal to ->num_init_configs leads to out-of-bounds access: avs_path_module_send_init_configs() loop: (...) &acomp->tplg->init_configs[ids[i]] ^ out-of-bounds candidate Rather than adding another if-statement, refactor the code. There is no need to store the IDs, have a list of pointers to actual config-entries instead. As the verification of ->init_config entries does not differ from verification of other types that are part of the topology.c file, simply reuse the code. Fixes: 8a49ef789b1b ("ASoC: Intel: avs: Send initial config to module if present") Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-10-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/path.c | 11 +++----- sound/soc/intel/avs/topology.c | 47 +++++++++++++++++++--------------- sound/soc/intel/avs/topology.h | 4 +-- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c index 213d6ecdd7cc..a8a2b3484338 100644 --- a/sound/soc/intel/avs/path.c +++ b/sound/soc/intel/avs/path.c @@ -836,15 +836,10 @@ static int avs_path_module_type_create(struct avs_dev *adev, struct avs_path_mod static int avs_path_module_send_init_configs(struct avs_dev *adev, struct avs_path_module *mod) { - struct avs_soc_component *acomp; + struct avs_tplg_module *template = mod->template; - acomp = to_avs_soc_component(mod->template->owner->owner->owner->owner->comp); - - u32 num_ids = mod->template->num_config_ids; - u32 *ids = mod->template->config_ids; - - for (int i = 0; i < num_ids; i++) { - struct avs_tplg_init_config *config = &acomp->tplg->init_configs[ids[i]]; + for (int i = 0; i < template->num_init_configs; i++) { + struct avs_tplg_init_config *config = template->init_configs[i]; size_t len = config->length; void *data = config->data; u32 param = config->param; diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c index d5e641c73faf..5d70be63a4a7 100644 --- a/sound/soc/intel/avs/topology.c +++ b/sound/soc/intel/avs/topology.c @@ -350,6 +350,7 @@ AVS_DEFINE_PTR_PARSER(modcfg_base, struct avs_tplg_modcfg_base, modcfgs_base); AVS_DEFINE_PTR_PARSER(modcfg_ext, struct avs_tplg_modcfg_ext, modcfgs_ext); AVS_DEFINE_PTR_PARSER(pplcfg, struct avs_tplg_pplcfg, pplcfgs); AVS_DEFINE_PTR_PARSER(binding, struct avs_tplg_binding, bindings); +AVS_DEFINE_PTR_PARSER(init_config, struct avs_tplg_init_config, init_configs); AVS_DEFINE_PTR_PARSER(nhlt_config, struct avs_tplg_nhlt_config, nhlt_configs); static int @@ -1198,7 +1199,7 @@ static const struct avs_tplg_token_parser module_parsers[] = { { .token = AVS_TKN_MOD_INIT_CONFIG_NUM_IDS_U32, .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, - .offset = offsetof(struct avs_tplg_module, num_config_ids), + .offset = offsetof(struct avs_tplg_module, num_init_configs), .parse = avs_parse_byte_token, }, { @@ -1214,10 +1215,32 @@ static const struct avs_tplg_token_parser init_config_parsers[] = { .token = AVS_TKN_MOD_INIT_CONFIG_ID_U32, .type = SND_SOC_TPLG_TUPLE_TYPE_WORD, .offset = 0, - .parse = avs_parse_word_token, + .parse = avs_parse_init_config_ptr, }, }; +static int avs_tplg_module_init_configs(struct snd_soc_component *comp, + struct avs_tplg_module *module, + struct snd_soc_tplg_vendor_array *tuples, u32 block_size) +{ + struct avs_tplg_init_config **cfgs; + int ret; + + if (!module->num_init_configs) + return -EINVAL; + + cfgs = devm_kcalloc(comp->card->dev, module->num_init_configs, sizeof(*cfgs), GFP_KERNEL); + if (!cfgs) + return -ENOMEM; + + ret = parse_dictionary_entries(comp, tuples, block_size, cfgs, module->num_init_configs, + sizeof(*cfgs), AVS_TKN_MOD_INIT_CONFIG_ID_U32, + init_config_parsers, ARRAY_SIZE(init_config_parsers)); + if (!ret) + module->init_configs = cfgs; + return ret; +} + static struct avs_tplg_module * avs_tplg_module_create(struct snd_soc_component *comp, struct avs_tplg_pipeline *owner, struct snd_soc_tplg_vendor_array *tuples, u32 block_size) @@ -1244,27 +1267,11 @@ avs_tplg_module_create(struct snd_soc_component *comp, struct avs_tplg_pipeline block_size -= esize; /* Parse trailing config ids if any. */ if (block_size) { - u32 num_config_ids = module->num_config_ids; - u32 *config_ids; - - if (!num_config_ids) - return ERR_PTR(-EINVAL); - - config_ids = devm_kcalloc(comp->card->dev, num_config_ids, sizeof(*config_ids), - GFP_KERNEL); - if (!config_ids) - return ERR_PTR(-ENOMEM); - tuples = avs_tplg_vendor_array_at(tuples, esize); - ret = parse_dictionary_entries(comp, tuples, block_size, - config_ids, num_config_ids, sizeof(*config_ids), - AVS_TKN_MOD_INIT_CONFIG_ID_U32, - init_config_parsers, - ARRAY_SIZE(init_config_parsers)); + + ret = avs_tplg_module_init_configs(comp, module, tuples, block_size); if (ret) return ERR_PTR(ret); - - module->config_ids = config_ids; } module->owner = owner; diff --git a/sound/soc/intel/avs/topology.h b/sound/soc/intel/avs/topology.h index b5799c994b88..189984ce7b51 100644 --- a/sound/soc/intel/avs/topology.h +++ b/sound/soc/intel/avs/topology.h @@ -221,8 +221,8 @@ struct avs_tplg_module { u8 domain; struct avs_tplg_modcfg_ext *cfg_ext; u32 ctl_id; - u32 num_config_ids; - u32 *config_ids; + u32 num_init_configs; + struct avs_tplg_init_config **init_configs; struct avs_tplg_nhlt_config *nhlt_config; struct avs_tplg_pipeline *owner; From 99f5566a1a884de4a400230835658ada4af6e001 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Wed, 2 Sep 2026 10:18:14 +0200 Subject: [PATCH 0756/2366] ASoC: Intel: avs: hda: Constrain MSBs on startup Front-end DAI links are marked as dynamic for the card thus the __soc_pcm_open() function never gets to soc_pcm_apply_msb() step which performs MSBs-constraint rule. Do that on link startup instead. Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260902081814.1590883-11-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/hdaudio.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c index 03cfd91202d3..2e15a293298d 100644 --- a/sound/soc/intel/avs/boards/hdaudio.c +++ b/sound/soc/intel/avs/boards/hdaudio.c @@ -15,6 +15,22 @@ #include "../../../codecs/hda.h" #include "../utils.h" +static int avs_link_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + const struct snd_soc_pcm_stream *stream_info; + struct snd_soc_dai *codec_dai; + + codec_dai = snd_soc_rtd_to_codec(rtd, 0); + stream_info = snd_soc_dai_get_pcm_stream(codec_dai, substream->stream); + + return snd_pcm_hw_constraint_msbits(substream->runtime, 0, 0, stream_info->sig_bits); +} + +static const struct snd_soc_ops avs_link_ops = { + .startup = avs_link_startup, +}; + static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int pcm_count, struct snd_soc_dai_link **links) { @@ -43,6 +59,7 @@ static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int dl[i].platforms = platform; dl[i].num_platforms = 1; dl[i].ignore_pmdown_time = 1; + dl[i].ops = &avs_link_ops; dl[i].codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); dl[i].cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); From 97f8cb91a8c5658fe2ae6f5c2ff6e95474a5eb2f Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Wed, 26 Aug 2026 16:56:42 +0200 Subject: [PATCH 0757/2366] drm/panic: clean new `clippy::needless_range_loop` lint for Rust 1.100.0 Starting with Rust 1.100.0 (expected 2026-11-12), Clippy warns: warning: the loop variable `i` is only used to index `self.decimals` --> drivers/gpu/drm/drm_panic_qr.rs:410:18 | 410 | for i in 0..len { | ^^^^^^ | note: for this index operation --> drivers/gpu/drm/drm_panic_qr.rs:411:13 | 411 | self.decimals[i] = (chunk % 10) as u8; | ^^^^^^^^^^^^^^^^ = help: for further information visit https://rust-lang.github.io/rust-clippy/main/index.html#needless_range_loop = note: `-W clippy::needless-range-loop` implied by `-W clippy::all` = help: to override `-W clippy::all` add `#[allow(clippy::needless_range_loop)]` help: consider using an iterator | 410 - for i in 0..len { 410 + for in self.decimals.iter_mut().take(len) { | The lint did not trigger here before because it could not handle arrays behind a field access such as `self.decimals` -- Clippy was improved to catch those cases [1][2]. Thus clean the warning by iterating over a slice rather than using `take()` so that an out-of-range `len` still triggers the same bounds check as the indexed loop. Cc: stable@vger.kernel.org # Needed in 6.18.y and later. Link: https://github.com/rust-lang/rust-clippy/issues/16631 [1] Link: https://github.com/rust-lang/rust-clippy/pull/16634 [2] Assisted-by: LLM Reviewed-by: Alexandre Courbot Reviewed-by: Jocelyn Falempe Link: https://patch.msgid.link/20260826145642.43807-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- drivers/gpu/drm/drm_panic_qr.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_panic_qr.rs b/drivers/gpu/drm/drm_panic_qr.rs index ac27e86c601c..4d7eb75a3afc 100644 --- a/drivers/gpu/drm/drm_panic_qr.rs +++ b/drivers/gpu/drm/drm_panic_qr.rs @@ -407,8 +407,8 @@ fn push(&mut self, data: u64, len: usize) { for i in (0..self.len).rev() { self.decimals[i + len] = self.decimals[i]; } - for i in 0..len { - self.decimals[i] = (chunk % 10) as u8; + for decimal in &mut self.decimals[..len] { + *decimal = (chunk % 10) as u8; chunk = div10(chunk); } self.len += len; From 18e5e0ec0e9282c897e2aa81a3e43ccaee03b003 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 31 Aug 2026 11:42:34 -0700 Subject: [PATCH 0758/2366] net: bcmasp: clear txcb->last before writing each descriptor bcmasp_xmit() only wrote txcb->last = true for the final fragment of an SKB; non-final fragments left the field untouched. If a descriptor slot was reused while it still held a stale true from a previous SKB (possible when tx_spb_ring_full() underreported fullness), bcmasp_tx_reclaim() would see last == true mid-SKB and call dev_consume_skb_any() prematurely, freeing the sk_buff while its remaining fragments were still in flight. Unconditionally clear txcb->last before the conditional set so every descriptor slot starts from a known false state regardless of what a prior transmission left behind. Fixes: 490cb412007d ("net: bcmasp: Add support for ASP2.0 Ethernet controller") Signed-off-by: Justin Chen Signed-off-by: Danesh Petigara Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260831184235.4133351-2-danesh.petigara@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c index ed0977832ce4..2bd035f74fa2 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c @@ -301,6 +301,7 @@ static netdev_tx_t bcmasp_xmit(struct sk_buff *skb, struct net_device *dev) txcb->bytes_sent = total_bytes; dma_unmap_addr_set(txcb, dma_addr, mapping); dma_unmap_len_set(txcb, dma_len, size); + txcb->last = false; if (!i) { desc->flags |= DESC_SOF; if (csum_hw) From 0c5cf62e72d7a666ee4da757e122dc1600df1ecc Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 31 Aug 2026 11:42:35 -0700 Subject: [PATCH 0759/2366] net: bcmasp: fix tx_spb_ring_full() checking same slot cnt times The loop initialised next_index from intf->tx_spb_index on every iteration, so incr_ring() always produced the same result and only one slot was ever tested. Move the initialisation before the loop so each iteration advances next_index and the function correctly checks that cnt consecutive descriptor slots are available before allowing a new transmission. Fixes: 490cb412007d ("net: bcmasp: Add support for ASP2.0 Ethernet controller") Signed-off-by: Justin Chen Signed-off-by: Danesh Petigara Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260831184235.4133351-3-danesh.petigara@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c index 2bd035f74fa2..f2176ef3a127 100644 --- a/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c +++ b/drivers/net/ethernet/broadcom/asp2/bcmasp_intf.c @@ -148,8 +148,9 @@ static int tx_spb_ring_full(struct bcmasp_intf *intf, int cnt) int next_index, i; /* Check if we have enough room for cnt descriptors */ + next_index = intf->tx_spb_index; for (i = 0; i < cnt; i++) { - next_index = incr_ring(intf->tx_spb_index, DESC_RING_COUNT); + next_index = incr_ring(next_index, DESC_RING_COUNT); if (next_index == intf->tx_spb_clean_index) return 1; } From efdfb1e27a3328085b79540dfe781d537b576ea1 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Tue, 1 Sep 2026 10:59:04 +0000 Subject: [PATCH 0760/2366] ipv4: fib: bound automatic table ID allocation fib_empty_table() probes every table ID from 1 until it finds a free one. IPv4 tables are stored in a 256-bucket hash table, so a dense set of IDs makes each probe walk a growing hash chain while RTNL is held. Automatic table assignment ("ip rule ... table 0") is an IPv4-only legacy path. Bound the automatically allocated ID to 4096 so the RTNL hold stays bounded, without changing lookups of explicitly specified table IDs. This changes user-visible behavior. A table-0 rule previously received the lowest free ID in 1..RT_TABLE_MAX (0xFFFFFFFF). After this patch the search stops at 4096 and the rule add fails with ENOBUFS if that range is fully occupied. Explicit table IDs above 4096 remain usable. The automatic path is unused in practice: it is IPv4-only, not documented by ip-rule, uncovered by kernel selftests, and both NetworkManager and systemd refuse table 0. Fixes: b801f54917b7 ("[NET]: Increate RT_TABLE_MAX to 2^32") Cc: stable@vger.kernel.org Reported-by: Vega Suggested-by: Ido Schimmel Signed-off-by: Zihan Xi Reviewed-by: Ido Schimmel Reviewed-by: Petr Vorel Link: https://patch.msgid.link/6f2f2a7a136aee005512a2e1ac8ede62ac8c7bb6.1788258884.git.zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv4/fib_rules.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/ipv4/fib_rules.c b/net/ipv4/fib_rules.c index 4edb0dca7be8..060501b376a8 100644 --- a/net/ipv4/fib_rules.c +++ b/net/ipv4/fib_rules.c @@ -214,6 +214,8 @@ INDIRECT_CALLABLE_SCOPE int fib4_rule_match(struct fib_rule *rule, return 1; } +#define FIB_MAX_AUTO_TABLE_ID 4096 + static struct fib_table *fib_empty_table(struct net *net) { u32 id = 1; @@ -222,7 +224,7 @@ static struct fib_table *fib_empty_table(struct net *net) if (!fib_get_table(net, id)) return fib_new_table(net, id); - if (id++ == RT_TABLE_MAX) + if (id++ == FIB_MAX_AUTO_TABLE_ID) break; } return NULL; From 2ac174dfcdde399fa95ba889541fb5e688d8bb35 Mon Sep 17 00:00:00 2001 From: Taylor Bates Date: Tue, 1 Sep 2026 22:49:46 -0400 Subject: [PATCH 0761/2366] mlxsw: spectrum_ptp: Fix napi_gro_receive() call from GC workqueue context Currently mlxsw_sp1_ptp_ht_gc_collect() is run from the PTP garbage-collection workqueue, rather than the NAPI poll context. For any unmatched PTP entries carrying an SKB, it calls mlxsw_sp1_ptp_unmatched_finish() -> mlxsw_sp1_ptp_packet_finish(). For ingress packets, this calls mlxsw_sp_rx_listener_no_mark_func(). The end of that function is the following: skb->protocol = eth_type_trans(skb, skb->dev); napi_gro_receive(mlxsw_skb_cb(skb)->rx_md_info.napi, skb); The napi pointer is one that was placed in the SKB control block when the trapped packet was received in the NAPI context. Later, when the GC reaps the unmatched entry (up to MLXSW_SP1_PTP_HT_GC_TIMEOUT later), the call to napi_gro_receive() mutates the NAPI instance's GRO list, which is unsafe if the poll is running concurrently on another CPU. In mlxsw_sp1_ptp_ht_gc_collect(), local_bh_disable() is called to prevent softirq processing, but this only applies to the local CPU. Additionally, its comment is stale. It states that mlxsw_sp1_ptp_unmatched_finish() invokes netif_receive_skb(). This has not been accurate since the referenced commit; this patch makes that comment accurate again. mlxsw_pci_napi_devs_init() calls netif_threaded_enable() on the NAPI RX net_device without any conditions. The NAPI instance's poll, which may be running concurrent to the GC, is running as an independently-scheduled kthread which may be on a different CPU. The call to local_bh_disable() does not guard against this. If a tx-timestamp timeout produces an unmatched entry (which can be easily reproduced by running ptp4l and waiting for a port to reach the UNCALIBRATED/SLAVE state) while the owning NAPI thread is in the middle of a poll on another CPU, both sides mutate the GRO list concurrently, as shown below: [39.846] port 1 (swp1): MASTER to UNCALIBRATED on RS_SLAVE list_add corruption. next->prev should be prev (ffff8d620faf4138), but was ffff8d624150f700. (next=ffff8d620faf4138). kernel BUG at lib/list_debug.c:29! Oops: invalid opcode: 0000 [#1] SMP PTI CPU: 1 UID: 0 PID: 539 Comm: napi/mlxsw_rx-0 Not tainted 6.18.48 #1-NixOS PREEMPT(lazy) Hardware name: Mellanox Technologies Ltd. MSN2410/VMOD0001, BIOS 4.6.5 09/13/2018 RIP: 0010:__list_add_valid_or_report+0x79/0xb0 RSP: 0018:ffffcdf8c0f27c08 EFLAGS: 00010246 RAX: 0000000000000075 RBX: ffff8d624150fd00 RCX: 0000000000000000 RDX: 0000000000000000 RSI: 0000000000000001 RDI: ffff8d6315d1e540 RBP: ffff8d620faf4070 R08: 0000000000000000 R09: 00000000ffffdfff R10: ffffffffa5c60fe0 R11: ffffcdf8c0f27ab8 R12: 0000000000000003 R13: 000000000000003d R14: 00000000000001bc R15: 0000000000000001 FS: 0000000000000000(0000) GS:ffff8d636f63f000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000562689a60c24 CR3: 000000015f224004 CR4: 00000000001726f0 Call Trace: gro_receive_skb+0xee/0x230 mlxsw_sp1_ptp_got_packet+0x61/0x140 [mlxsw_spectrum] mlxsw_core_skb_receive+0xdf/0x1b0 [mlxsw_core] mlxsw_pci_napi_poll_cq_rx+0x780/0x9d0 [mlxsw_pci] __napi_poll+0x31/0x1e0 napi_threaded_poll_loop+0x16b/0x1c0 napi_threaded_poll+0x71/0xa0 kthread+0xfb/0x260 ret_from_fork+0x22d/0x260 ret_from_fork_asm+0x1a/0x30 Kernel panic - not syncing: Fatal exception in interrupt The machinery that leads to this kernel panic has not been changed between 6.18.48 and mainline. This patch adds an ingress-delivery helper for the PTP packet_finish() path that calls netif_receive_skb() instead of napi_gro_receive(). netif_receive_skb(), unlike napi_gro_receive(), can be called from outside of the NAPI instance's poll context, which can occur at the call site for this path. RX stats accounting and the skb->dev assignment are still preserved; the only change is the delivery call itself. This removes GRO batching for any PTP event traffic received by the mlxsw trap, but given the relatively low volume of traffic characteristic of the protocol, and impact limited to only Spectrum-1 ASICs, this is an acceptable solution. Fixes: 1ba06ca96ca2 ("mlxsw: Switch to napi_gro_receive()") Signed-off-by: Taylor Bates Reviewed-by: Petr Machata Link: https://patch.msgid.link/20260902024949.2273997-1-tmbates12@gmail.com Signed-off-by: Jakub Kicinski --- .../ethernet/mellanox/mlxsw/spectrum_ptp.c | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_ptp.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_ptp.c index 9939749c47bc..9c5862f4e16a 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_ptp.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_ptp.c @@ -572,6 +572,38 @@ mlxsw_sp1_ptp_unmatched_remove(struct mlxsw_sp *mlxsw_sp, mlxsw_sp1_ptp_unmatched_ht_params); } +/* mlxsw_sp1_ptp_packet_finish() is reached both from the NAPI poll context + * (mlxsw_sp1_ptp_got_packet(), mlxsw_sp1_ptp_got_piece() and + * mlxsw_sp1_packet_timestamp()) and from process context, by way of the GC + * workqueue (mlxsw_sp1_ptp_ht_gc_collect() -> + * mlxsw_sp1_ptp_unmatched_finish()). + * + * mlxsw_sp_rx_listener_no_mark_func() ends in napi_gro_receive(), using the + * NAPI pointer that was placed in the SKB control block when the trapped + * packet was received in the NAPI context. That pointer may only be used + * from its own poll context, which this call site cannot guarantee. + * + * netif_receive_skb(), unlike napi_gro_receive(), can be called from outside + * of the NAPI instance's poll context. RX stats accounting and the skb->dev + * assignment are still preserved; the only change is the delivery call. + */ +static void mlxsw_sp1_ptp_rx_finish(struct mlxsw_sp_port *mlxsw_sp_port, + struct sk_buff *skb) +{ + struct mlxsw_sp_port_pcpu_stats *pcpu_stats; + + skb->dev = mlxsw_sp_port->dev; + + pcpu_stats = this_cpu_ptr(mlxsw_sp_port->pcpu_stats); + u64_stats_update_begin(&pcpu_stats->syncp); + pcpu_stats->rx_packets++; + pcpu_stats->rx_bytes += skb->len; + u64_stats_update_end(&pcpu_stats->syncp); + + skb->protocol = eth_type_trans(skb, skb->dev); + netif_receive_skb(skb); +} + /* This function is called in the following scenarios: * * 1) When a packet is matched with its timestamp. @@ -600,7 +632,7 @@ static void mlxsw_sp1_ptp_packet_finish(struct mlxsw_sp *mlxsw_sp, if (ingress) { if (hwtstamps) *skb_hwtstamps(skb) = *hwtstamps; - mlxsw_sp_rx_listener_no_mark_func(skb, local_port, mlxsw_sp); + mlxsw_sp1_ptp_rx_finish(mlxsw_sp_port, skb); } else { /* skb_tstamp_tx() allows hwtstamps to be NULL. */ skb_tstamp_tx(skb, hwtstamps); From 6a1094c34d176827b2b173e163dcc964a13af93f Mon Sep 17 00:00:00 2001 From: XingWang Xiang Date: Wed, 2 Sep 2026 17:43:17 +0900 Subject: [PATCH 0762/2366] genetlink: pin family module during policy dump The generic netlink controller's policy dump keeps pointers to the target family's operation and policy tables in its callback state. A dump may be split across multiple skbs and remain pending after the initial request. Netlink pins the module which owns the dump callback, but in this case that is the controller's owner rather than the target family's owner. The target family can consequently be unregistered and its module unloaded while a policy dump is pending. Advancing the dump then dereferences policy memory from the unloaded module. Take a reference to the target family's module when the dump starts. Drop it from the error and done paths. This matches the lifetime for which the dump context retains the family and policy pointers. Fixes: d07dcf9aadd6 ("netlink: add infrastructure to expose policies to userspace") Cc: stable@vger.kernel.org Signed-off-by: XingWang Xiang Link: https://patch.msgid.link/20260902084317.4092542-1-v3rdant.xiang@gmail.com Signed-off-by: Jakub Kicinski --- net/netlink/genetlink.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c index 0da39eaed255..41d37442f186 100644 --- a/net/netlink/genetlink.c +++ b/net/netlink/genetlink.c @@ -1513,6 +1513,7 @@ struct ctrl_dump_policy_ctx { struct netlink_policy_dump_state *state; const struct genl_family *rt; struct genl_op_iter *op_iter; + struct module *owner; u32 op; u16 fam_id; u8 dump_map:1, @@ -1555,6 +1556,9 @@ static int ctrl_dumppolicy_start(struct netlink_callback *cb) return -ENOENT; ctx->rt = rt; + ctx->owner = rt->module; + if (!try_module_get(ctx->owner)) + return -ENOENT; if (tb[CTRL_ATTR_OP]) { struct genl_split_ops doit, dump; @@ -1565,7 +1569,7 @@ static int ctrl_dumppolicy_start(struct netlink_callback *cb) err = genl_get_cmd_both(ctx->op, rt, &doit, &dump); if (err) { NL_SET_BAD_ATTR(cb->extack, tb[CTRL_ATTR_OP]); - return err; + goto err_put_owner; } if (doit.policy) { @@ -1583,16 +1587,20 @@ static int ctrl_dumppolicy_start(struct netlink_callback *cb) goto err_free_state; } - if (!ctx->state) - return -ENODATA; + if (!ctx->state) { + err = -ENODATA; + goto err_put_owner; + } ctx->dump_map = 1; return 0; } ctx->op_iter = kmalloc_obj(*ctx->op_iter); - if (!ctx->op_iter) - return -ENOMEM; + if (!ctx->op_iter) { + err = -ENOMEM; + goto err_put_owner; + } genl_op_iter_init(rt, ctx->op_iter); ctx->dump_map = genl_op_iter_next(ctx->op_iter); @@ -1624,6 +1632,8 @@ static int ctrl_dumppolicy_start(struct netlink_callback *cb) netlink_policy_dump_free(ctx->state); err_free_op_iter: kfree(ctx->op_iter); +err_put_owner: + module_put(ctx->owner); return err; } @@ -1760,6 +1770,7 @@ static int ctrl_dumppolicy_done(struct netlink_callback *cb) kfree(ctx->op_iter); netlink_policy_dump_free(ctx->state); + module_put(ctx->owner); return 0; } From b58d749633203d92c265317b45fccee555090352 Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Wed, 2 Sep 2026 22:01:12 +0300 Subject: [PATCH 0763/2366] tunnels: Drop stale dst when building an ICMP error for PMTUD Bridged UDP tunnels such as VXLAN and GENEVE build an ICMP error packet around an overlay packet if the packet is going to exceed the underlay path MTU. The ICMP error packet is then injected back into the Rx path with the source and destination addresses swapped, so that it will be delivered to the overlay source. If the overlay packet was routed to the UDP tunnel or locally generated, then it is already carrying a valid dst entry and this entry is not dropped when transforming the packet to an ICMP error packet. This causes the IP layer to reuse the dst entry, leading to the ICMP error packet being dropped or routed out of the UDP tunnel interface in case of forwarding. Prior to the blamed commit this could not happen, as skb_tunnel_check_pmtu() did not build ICMP errors for PACKET_HOST packets. Such packets were instead encapsulated and, unless the DF bit was set in the outer header, fragmented by the underlay. Fix this by making sure that the ICMP error packet does not have a valid dst entry, thereby forcing the IP layer to perform a route lookup. Adjust the bridged PMTU exception selftests accordingly. When the local sender in ns_a pings the overlay destination with a deadline (-w), ping exits on the first socket error before any reply is received and returns a non-zero exit code. The test therefore only passed because the ICMP error was never delivered. Use a packet count (-c) like the ns_c line above it, so that the ICMP error counts against the packet budget and the exit code depends on whether echo replies were received. This passes with and without the fix. Fixes: 8930424777e4 ("tunnels: Accept PACKET_HOST in skb_tunnel_check_pmtu().") Cc: stable@vger.kernel.org Reported-by: Laika Price Closes: https://lore.kernel.org/netdev/20260614-master-v3-1-9f5060ba1ed1@gmail.com/ Reported-by: Yaroslav Dudkov Closes: https://lore.kernel.org/netdev/20260901081825.287173-1-aroslavdudkov622@gmail.com/ Reported-by: Charles Bordet Closes: https://lore.kernel.org/netdev/aHVhQLPJIhq-SYPM@eldamar.lan/ Signed-off-by: Ido Schimmel Tested-by: Yaroslav Dudkov Reviewed-by: David Ahern Reviewed-by: Stefano Brivio Reviewed-by: Guillaume Nault Link: https://patch.msgid.link/20260902190112.4126199-1-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv4/ip_tunnel_core.c | 6 ++++++ tools/testing/selftests/net/pmtu.sh | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/net/ipv4/ip_tunnel_core.c b/net/ipv4/ip_tunnel_core.c index d3c677e9bff2..5168d546ea2f 100644 --- a/net/ipv4/ip_tunnel_core.c +++ b/net/ipv4/ip_tunnel_core.c @@ -268,6 +268,9 @@ static int iptunnel_pmtud_build_icmp(struct sk_buff *skb, int mtu) eth_header(skb, skb->dev, ntohs(eh.h_proto), eh.h_source, eh.h_dest, 0); skb_reset_mac_header(skb); + if (skb_valid_dst(skb)) + skb_dst_drop(skb); + return skb->len; } @@ -371,6 +374,9 @@ static int iptunnel_pmtud_build_icmpv6(struct sk_buff *skb, int mtu) eth_header(skb, skb->dev, ntohs(eh.h_proto), eh.h_source, eh.h_dest, 0); skb_reset_mac_header(skb); + if (skb_valid_dst(skb)) + skb_dst_drop(skb); + return skb->len; } diff --git a/tools/testing/selftests/net/pmtu.sh b/tools/testing/selftests/net/pmtu.sh index a3323c21f001..c7cd271714ef 100755 --- a/tools/testing/selftests/net/pmtu.sh +++ b/tools/testing/selftests/net/pmtu.sh @@ -1457,7 +1457,7 @@ test_pmtu_ipvX_over_bridged_vxlanY_or_geneveY_exception() { mtu "${ns_b}" ${type}_b $((${ll_mtu} + 1000)) run_cmd ${ns_c} ${ping} -q -M want -i 0.1 -c 10 -s $((${ll_mtu} + 500)) ${dst} || return 1 - run_cmd ${ns_a} ${ping} -q -M want -i 0.1 -w 1 -s $((${ll_mtu} + 500)) ${dst} || return 1 + run_cmd ${ns_a} ${ping} -q -M want -i 0.1 -c 10 -s $((${ll_mtu} + 500)) ${dst} || return 1 # Check that exceptions were created pmtu="$(route_get_dst_pmtu_from_exception "${ns_c}" ${dst})" From a09ceadff95b0075a9b6a5d9dbeb6c1c5f311c60 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 1 Sep 2026 11:21:24 +0200 Subject: [PATCH 0764/2366] mailmap: add entries for Lorenzo Bianconi Add the active email address for Lorenzo Bianconi and map the old, no-longer-used addresses to it, so that git can attribute his contributions to a single identity. This is done to avoid bouncing emails sent to email addresses that are no longer active. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260901-lorenzo-mailmap-v2-1-0ee832de0caf@kernel.org Signed-off-by: Jakub Kicinski --- .mailmap | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.mailmap b/.mailmap index 6803f3bd2865..5fd5c834e0ac 100644 --- a/.mailmap +++ b/.mailmap @@ -550,6 +550,9 @@ Li Yang Lior David Loic Poulain Loic Poulain +Lorenzo Bianconi +Lorenzo Bianconi +Lorenzo Bianconi Lorenzo Pieralisi Lorenzo Stoakes Lorenzo Stoakes From 39b23c1c40e1f73d2b94a09282cc476af647e438 Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Wed, 2 Sep 2026 14:39:54 -0700 Subject: [PATCH 0765/2366] bnxt_en: Prevent queue stop with deferred completions When the driver receives a burst of packets, it can mark a BD with the NO_CMPL bit to defer completions. The expectation is that the last packet in the ring will have this bit unset and the completion generated by that packet will cleanup that packet and the ones preceding it. This helps to reduce the number of completions fired. The suppressed completions are controlled by the driver and the number of packets with suppressed completions scales with the size of the ring. SW USO packets, on the other hand, have an upper bound on the maximum number of BDs which can be consumed which does not scale with the ring size. So, for small rings it is possible that: a burst of packets is handed to the driver, the driver defers completions for all of the packets because the number of free descriptors stays above the threshold in the driver. Then, a USO packet arrives, but the number of BDs available is not enough and the USO code exits early. In this case, you end up in a state where the ring is full of packets with their completions suppressed, which can cause the queue to stop and never be restarted. Assuming default CONFIG_MAX_SKB_FRAGS, this is only possible for small rings (<= 457 descriptors, below the driver default value) when a burst of packets fills the ring, followed by a large USO packet that can't fit. For larger rings, the delta between the completion suppression threshold and the BDs required for SW USO is large enough that completions will fire and this case is unreachable. This issue was pointed out by Sashiko and while it seems fairly unlikely given that the queue size must be small to trigger this, it is indeed possible. Fix this by tracking the last BD which deferred completions and centralizing the logic for deciding when to ring the doorbell. The NO_CMPL bit is now cleared in bnxt_txr_db_kick(), so every doorbell site is covered, including the SW USO early exit. This guarantees the ring always ends in a BD which generates a completion to clean it and wake the queue. Fixes: cc5d90667db8 ("net: bnxt: Implement software USO") Cc: # v7.1+: 4e15e89faac9: net: bnxt: ring the doorbell when SW USO exits early Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902213956.4160615-1-joe@dama.to Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 42 +++++++++++++++---- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 1 + drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c | 21 ++++++---- drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h | 6 +-- 4 files changed, 48 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index d59bcca73a2b..8c6e2ee6bee4 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -462,6 +462,16 @@ u16 bnxt_xmit_get_cfa_action(struct sk_buff *skb) static void bnxt_txr_db_kick(struct bnxt *bp, struct bnxt_tx_ring_info *txr, u16 prod) { + /* If the most recent BD has its completion suppressed, unset the bit + * so that a completion is generated, otherwise nothing is left to + * clean the ring and wake the queue. + */ + if (txr->kick_txbd0) { + txr->kick_txbd0->tx_bd_len_flags_type &= + cpu_to_le32(~TX_BD_FLAGS_NO_CMPL); + txr->kick_txbd0 = NULL; + } + /* Sync BD data before updating doorbell */ wmb(); bnxt_db_write(bp, &txr->tx_db, prod); @@ -485,7 +495,6 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) struct bnxt_sw_tx_bd *tx_buf; __le32 lflags = 0; skb_frag_t *frag; - netdev_tx_t ret; i = skb_get_queue_mapping(skb); if (unlikely(i >= bp->tx_nr_rings)) { @@ -509,11 +518,22 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) if (skb_is_gso(skb) && (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4) && !(bp->flags & BNXT_FLAG_UDP_GSO_CAP)) { - ret = bnxt_sw_udp_gso_xmit(bp, txr, txq, skb); - if (txr->kick_pending) + int rc = bnxt_sw_udp_gso_xmit(bp, txr, txq, skb); + + /* if SW USO queued a packet, the doorbell will be written + * below and there is no reason to track the last BD with + * suppressed completions + */ + if (rc > 0) + txr->kick_txbd0 = NULL; + + /* if a packet was queued by SW USO or a doorbell was pending + * from a previous xmit that was deferred, write the doorbell. + */ + if (rc > 0 || txr->kick_pending) bnxt_txr_db_kick(bp, txr, txr->tx_prod); - return ret; + return rc < 0 ? NETDEV_TX_BUSY : NETDEV_TX_OK; } free_size = bnxt_tx_avail(bp, txr); @@ -751,23 +771,23 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) prod = NEXT_TX(prod); WRITE_ONCE(txr->tx_prod, prod); + txr->kick_txbd0 = NULL; if (!netdev_xmit_more() || netif_xmit_stopped(txq)) { bnxt_txr_db_kick(bp, txr, prod); } else { - if (free_size >= bp->tx_wake_thresh) + if (free_size >= bp->tx_wake_thresh) { txbd0->tx_bd_len_flags_type |= cpu_to_le32(TX_BD_FLAGS_NO_CMPL); + txr->kick_txbd0 = txbd0; + } txr->kick_pending = 1; } tx_done: if (unlikely(bnxt_tx_avail(bp, txr) <= MAX_SKB_FRAGS + 1)) { - if (netdev_xmit_more() && !tx_buf->is_push) { - txbd0->tx_bd_len_flags_type &= - cpu_to_le32(~TX_BD_FLAGS_NO_CMPL); + if (txr->kick_pending) bnxt_txr_db_kick(bp, txr, prod); - } netif_txq_try_stop(txq, bnxt_tx_avail(bp, txr), bp->tx_wake_thresh); @@ -5427,6 +5447,8 @@ static void bnxt_clear_ring_indices(struct bnxt *bp) txr->tx_prod = 0; txr->tx_cons = 0; txr->tx_hw_cons = 0; + txr->kick_pending = 0; + txr->kick_txbd0 = NULL; } rxr = bnapi->rx_ring; @@ -11772,6 +11794,8 @@ static int bnxt_tx_queue_start(struct bnxt *bp, int idx) txr->tx_prod = 0; txr->tx_cons = 0; txr->tx_hw_cons = 0; + txr->kick_pending = 0; + txr->kick_txbd0 = NULL; start_tx: WRITE_ONCE(txr->dev_state, 0); synchronize_net(); diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index ab894f8addef..dc5a16ec5943 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -993,6 +993,7 @@ struct bnxt_tx_ring_info { u16 txq_index; u8 tx_napi_idx; u8 kick_pending; + struct tx_bd *kick_txbd0; struct bnxt_db_info tx_db; struct tx_bd *tx_desc_ring[MAX_TX_PAGES]; diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c index f7e18bea0fb8..6c1060fa2ea5 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c @@ -31,10 +31,14 @@ static u32 bnxt_sw_gso_lhint(unsigned int len) return TX_BD_FLAGS_LHINT_2048_AND_LARGER; } -netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp, - struct bnxt_tx_ring_info *txr, - struct netdev_queue *txq, - struct sk_buff *skb) +/* Transmit an skb requiring software UDP segmentation. + * + * Returns 1 if the skb was queued and new BDs were produced, 0 if the skb + * was dropped, or -1 if the ring is full and the skb should be retried. + * The caller owns the doorbell for all three cases. + */ +int bnxt_sw_udp_gso_xmit(struct bnxt *bp, struct bnxt_tx_ring_info *txr, + struct netdev_queue *txq, struct sk_buff *skb) { unsigned int last_unmap_len __maybe_unused = 0; dma_addr_t last_unmap_addr __maybe_unused = 0; @@ -69,7 +73,7 @@ netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp, if (unlikely(bnxt_tx_avail(bp, txr) < bds_needed)) { netif_txq_try_stop(txq, bnxt_tx_avail(bp, txr), bp->tx_wake_thresh); - return NETDEV_TX_BUSY; + return -1; } /* BD backpressure alone cannot prevent overwriting in-flight @@ -77,7 +81,7 @@ netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp, */ if (!netif_txq_maybe_stop(txq, bnxt_inline_avail(txr), num_segs, num_segs)) - return NETDEV_TX_BUSY; + return -1; if (unlikely(tso_dma_map_init(&map, &pdev->dev, skb, hdr_len))) goto drop; @@ -223,16 +227,15 @@ netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp, netdev_tx_sent_queue(txq, skb->len); WRITE_ONCE(txr->tx_prod, prod); - txr->kick_pending = 1; if (unlikely(bnxt_tx_avail(bp, txr) <= bp->tx_wake_thresh)) netif_txq_try_stop(txq, bnxt_tx_avail(bp, txr), bp->tx_wake_thresh); - return NETDEV_TX_OK; + return 1; drop: dev_kfree_skb_any(skb); dev_core_stats_tx_dropped_inc(bp->dev); - return NETDEV_TX_OK; + return 0; } diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h index 47528c20f311..77d9af97cc22 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h @@ -38,9 +38,7 @@ static inline int bnxt_min_tx_desc_cnt(struct bnxt *bp, return BNXT_MIN_TX_DESC_CNT; } -netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp, - struct bnxt_tx_ring_info *txr, - struct netdev_queue *txq, - struct sk_buff *skb); +int bnxt_sw_udp_gso_xmit(struct bnxt *bp, struct bnxt_tx_ring_info *txr, + struct netdev_queue *txq, struct sk_buff *skb); #endif From 4814ed6406f3493bd554ad046da5f7fc04833571 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Thu, 3 Sep 2026 10:15:39 -0700 Subject: [PATCH 0766/2366] bpf: zero extend the result of an arena 32-bit cmpxchg bpf_convert_ctx_accesses() rewrites an atomic on an arena pointer from BPF_STX | BPF_ATOMIC to BPF_STX | BPF_PROBE_ATOMIC, and it runs before bpf_opt_subreg_zext_lo32_rnd_hi32(). That pass emits an explicit zero extension for a 32-bit cmpxchg even when bpf_jit_needs_zext() is false. This is done because on some architectures 32-bit cmpxchg requires explicit zero extension for the dst register. E.g. on x86-64 'lock cmpxchg' does not change the %eax if comparison is successful, while BPF semantics declare that each operation on a 32-bit register zero extends it's upper half. is_cmpxchg_insn() matches BPF_MODE == BPF_ATOMIC only, so an arena cmpxchg misses said zero extension adjustment. This patch adjusts is_cmpxchg_insn() to match BPF_PROBE_ATOMIC alongside BPF_ATOMIC. Fixes: d503a04f8bc0 ("bpf: Add support for certain atomics in bpf_arena to x86 JIT") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260903171542.1438050-1-eddyz87@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/fixups.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c index 65b441e4a351..52d3cec33672 100644 --- a/kernel/bpf/fixups.c +++ b/kernel/bpf/fixups.c @@ -13,10 +13,15 @@ #define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args) +/* + * Matches BPF_PROBE_ATOMIC too: bpf_convert_ctx_accesses() rewrites arena + * atomics before bpf_opt_subreg_zext_lo32_rnd_hi32() runs. + */ static bool is_cmpxchg_insn(const struct bpf_insn *insn) { return BPF_CLASS(insn->code) == BPF_STX && - BPF_MODE(insn->code) == BPF_ATOMIC && + (BPF_MODE(insn->code) == BPF_ATOMIC || + BPF_MODE(insn->code) == BPF_PROBE_ATOMIC) && insn->imm == BPF_CMPXCHG; } From 1f3cd9719c40715a7d6328bdbef817d5731bf61c Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Thu, 3 Sep 2026 10:15:40 -0700 Subject: [PATCH 0767/2366] bpf: update disasm.c to print BPF_PROBE_ATOMIC as atomics bpf_convert_ctx_accesses() rewrites an atomic on an arena pointer from BPF_STX | BPF_ATOMIC to BPF_STX | BPF_PROBE_ATOMIC, this patch adjusts print_bpf_insn() to print such instructions as regular atomics with a 'probe_' prefix (instead of printing them as BUG_XX). Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260903171542.1438050-2-eddyz87@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/disasm.c | 47 ++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/kernel/bpf/disasm.c b/kernel/bpf/disasm.c index b1a3fbe3fda5..3ce8d74b0e40 100644 --- a/kernel/bpf/disasm.c +++ b/kernel/bpf/disasm.c @@ -7,6 +7,9 @@ #include "disasm.h" +/* Only defined by the non-UAPI linux/filter.h, which this file cannot use. */ +#define BPF_PROBE_ATOMIC 0xe0 + #define __BPF_FUNC_STR_FN(x) [BPF_FUNC_ ## x] = __stringify(bpf_ ## x) static const char * const func_id_str[] = { __BPF_FUNC_MAPPER(__BPF_FUNC_STR_FN) @@ -226,57 +229,57 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs, insn->imm); } } else if (class == BPF_STX) { + const char *probe_pfx = BPF_MODE(insn->code) == BPF_PROBE_ATOMIC ? "probe " : ""; + bool atomic = BPF_MODE(insn->code) == BPF_ATOMIC || + BPF_MODE(insn->code) == BPF_PROBE_ATOMIC; + if (BPF_MODE(insn->code) == BPF_MEM) verbose(cbs->private_data, "(%02x) *(%s *)(r%d %+d) = r%d", insn->code, bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->dst_reg, insn->off, insn->src_reg); - else if (BPF_MODE(insn->code) == BPF_ATOMIC && + else if (atomic && (insn->imm == BPF_ADD || insn->imm == BPF_AND || insn->imm == BPF_OR || insn->imm == BPF_XOR)) { - verbose(cbs->private_data, "(%02x) lock *(%s *)(r%d %+d) %s r%d", - insn->code, + verbose(cbs->private_data, "(%02x) %slock *(%s *)(r%d %+d) %s r%d", + insn->code, probe_pfx, bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->dst_reg, insn->off, bpf_alu_string[BPF_OP(insn->imm) >> 4], insn->src_reg); - } else if (BPF_MODE(insn->code) == BPF_ATOMIC && + } else if (atomic && (insn->imm == (BPF_ADD | BPF_FETCH) || insn->imm == (BPF_AND | BPF_FETCH) || insn->imm == (BPF_OR | BPF_FETCH) || insn->imm == (BPF_XOR | BPF_FETCH))) { - verbose(cbs->private_data, "(%02x) r%d = atomic%s_fetch_%s((%s *)(r%d %+d), r%d)", - insn->code, insn->src_reg, + verbose(cbs->private_data, "(%02x) %sr%d = atomic%s_fetch_%s((%s *)(r%d %+d), r%d)", + insn->code, probe_pfx, insn->src_reg, BPF_SIZE(insn->code) == BPF_DW ? "64" : "", bpf_atomic_alu_string[BPF_OP(insn->imm) >> 4], bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->dst_reg, insn->off, insn->src_reg); - } else if (BPF_MODE(insn->code) == BPF_ATOMIC && - insn->imm == BPF_CMPXCHG) { - verbose(cbs->private_data, "(%02x) r0 = atomic%s_cmpxchg((%s *)(r%d %+d), r0, r%d)", - insn->code, + } else if (atomic && insn->imm == BPF_CMPXCHG) { + verbose(cbs->private_data, "(%02x) %sr0 = atomic%s_cmpxchg((%s *)(r%d %+d), r0, r%d)", + insn->code, probe_pfx, BPF_SIZE(insn->code) == BPF_DW ? "64" : "", bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->dst_reg, insn->off, insn->src_reg); - } else if (BPF_MODE(insn->code) == BPF_ATOMIC && - insn->imm == BPF_XCHG) { - verbose(cbs->private_data, "(%02x) r%d = atomic%s_xchg((%s *)(r%d %+d), r%d)", - insn->code, insn->src_reg, + } else if (atomic && insn->imm == BPF_XCHG) { + verbose(cbs->private_data, "(%02x) %sr%d = atomic%s_xchg((%s *)(r%d %+d), r%d)", + insn->code, probe_pfx, insn->src_reg, BPF_SIZE(insn->code) == BPF_DW ? "64" : "", bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->dst_reg, insn->off, insn->src_reg); - } else if (BPF_MODE(insn->code) == BPF_ATOMIC && - insn->imm == BPF_LOAD_ACQ) { - verbose(cbs->private_data, "(%02x) r%d = load_acquire((%s *)(r%d %+d))", - insn->code, insn->dst_reg, + } else if (atomic && insn->imm == BPF_LOAD_ACQ) { + verbose(cbs->private_data, "(%02x) %sr%d = load_acquire((%s *)(r%d %+d))", + insn->code, probe_pfx, insn->dst_reg, bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->src_reg, insn->off); - } else if (BPF_MODE(insn->code) == BPF_ATOMIC && - insn->imm == BPF_STORE_REL) { - verbose(cbs->private_data, "(%02x) store_release((%s *)(r%d %+d), r%d)", - insn->code, + } else if (atomic && insn->imm == BPF_STORE_REL) { + verbose(cbs->private_data, "(%02x) %sstore_release((%s *)(r%d %+d), r%d)", + insn->code, probe_pfx, bpf_ldst_string[BPF_SIZE(insn->code) >> 3], insn->dst_reg, insn->off, insn->src_reg); } else { From 54ed91950363c116bec9be1b7015ff2bfa989950 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Thu, 3 Sep 2026 10:15:41 -0700 Subject: [PATCH 0768/2366] selftests/bpf: check zero extension of an arena 32-bit cmpxchg Add a test to verify that destination register of a 32-bit cmpxchg operating on an arena pointer is explicitly zero extended. W/o patch #1 this did not happen. Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260903171542.1438050-3-eddyz87@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/verifier_zext.c | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_zext.c b/tools/testing/selftests/bpf/progs/verifier_zext.c index 8f2362da91d6..572017fe28fb 100644 --- a/tools/testing/selftests/bpf/progs/verifier_zext.c +++ b/tools/testing/selftests/bpf/progs/verifier_zext.c @@ -356,6 +356,32 @@ __naked void arena_ptr(void) : __clobber_all); } +/* + * Result of a 32-bit cmpxchg is always explicitly zero extended. + * Check that this holds for arenas (BPF_PROBE_ATOMIC instruction flavor). + */ +SEC("socket") +__success +__xlated("probe r0 = atomic_cmpxchg((u32 *)(r1 +0), r0, r2)") +__xlated("w0 = w0") +__naked void zext_arena_cmpxchg32(void) +{ + asm volatile (" \ + r9 = %[arena] ll; /* associate the arena with the program */ \ + r1 = 0; \ + r1 = addr_space_cast(r1, 0, 1); \ + r0 = 0; \ + r2 = 0; \ + .8byte %[cmpxchg32]; \ + r0 >>= 32; /* make the upper half live */ \ + exit; \ +" : + : __imm_addr(arena), + __imm_insn(cmpxchg32, + BPF_ATOMIC_OP(BPF_W, BPF_CMPXCHG, BPF_REG_1, BPF_REG_2, 0)) + : __clobber_all); +} + #endif /* Check if probe mem loads keep their zero extension. */ From 0b1c83dc3c4401cd7e846548f62e3caf3d06742e Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Thu, 3 Sep 2026 13:58:19 -0700 Subject: [PATCH 0769/2366] bpf: don't rewrite bpf_fastcall patterns entered by a jump mark_fastcall_pattern_for_call() must ensure that matched "spill; call; fill" instruction series is not interrupted by a jump. Otherwise the rewrite applied by bpf_remove_fastcall_spills_fills() is not sound. Record the instructions targeted by jumps in insn_aux_data[*].jump_target when the CFG is built and use this flag to stop growing a pattern at such an instruction. Jumps to the first spill are fine. Note that existing insn_aux_data[*].jmp_point field can't be reused, as it marks subprogram return instructions. Fixes: 5b5f51bff1b6 ("bpf: no_caller_saved_registers attribute for helper calls") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260903205820.1743087-1-eddyz87@gmail.com Signed-off-by: Alexei Starovoitov --- include/linux/bpf_verifier.h | 12 ++++++++++++ kernel/bpf/cfg.c | 3 +++ kernel/bpf/verifier.c | 8 ++++++++ 3 files changed, 23 insertions(+) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 5fad59fdab0d..1339c2f028db 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -706,6 +706,8 @@ struct bpf_insn_aux_data { */ u32 calls_callback:1; u32 indirect_target:1; /* if it is an indirect jump target */ + /* true if some jump or call instruction targets this instruction */ + u32 jump_target:1; /* * CFG strongly connected component this instruction belongs to, * zero if it is a singleton SCC. @@ -1142,6 +1144,16 @@ static inline void mark_jmp_point(struct bpf_verifier_env *env, int idx) env->insn_aux_data[idx].jmp_point = true; } +static inline void mark_jump_target(struct bpf_verifier_env *env, int idx) +{ + env->insn_aux_data[idx].jump_target = true; +} + +static inline bool bpf_is_jump_target(struct bpf_verifier_env *env, int insn_idx) +{ + return env->insn_aux_data[insn_idx].jump_target; +} + static inline struct bpf_func_state *cur_func(struct bpf_verifier_env *env) { struct bpf_verifier_state *cur = env->cur_state; diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c index 0f13c13f4133..842c7d1eabcc 100644 --- a/kernel/bpf/cfg.c +++ b/kernel/bpf/cfg.c @@ -125,6 +125,7 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env) /* mark branch target for state pruning */ mark_prune_point(env, w); mark_jmp_point(env, w); + mark_jump_target(env, w); } if (insn_state[w] == 0) { @@ -403,6 +404,7 @@ static int visit_gotox_insn(int t, struct bpf_verifier_env *env) } mark_jmp_point(env, w); + mark_jump_target(env, w); /* EXPLORED || DISCOVERED */ if (insn_state[w]) @@ -564,6 +566,7 @@ static int visit_insn(int t, struct bpf_verifier_env *env) mark_prune_point(env, t + off + 1); mark_jmp_point(env, t + off + 1); + mark_jump_target(env, t + off + 1); return ret; diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 32d31fa67036..2ed17edf77f2 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -17656,6 +17656,10 @@ bool bpf_get_call_summary(struct bpf_verifier_env *env, struct bpf_insn *call, * r0 = *(u64 *)(r10 - 8); r0 += r1; * r0 += r1; exit; * exit; + * + * Both uses of the marks assume that a pattern is entered at its first + * spill and thus executes as a unit, hence a pattern is not grown past + * an instruction targeted by a jump. */ static void mark_fastcall_pattern_for_call(struct bpf_verifier_env *env, struct bpf_subprog_info *subprog, @@ -17694,6 +17698,10 @@ static void mark_fastcall_pattern_for_call(struct bpf_verifier_env *env, for (i = 1, off = lowest_off; i <= ARRAY_SIZE(caller_saved); ++i, off += BPF_REG_SIZE) { if (insn_idx - i < 0 || insn_idx + i >= env->prog->len) break; + /* stx/ldx/call must not be a jump targets, a jump to the first stx is fine */ + if (bpf_is_jump_target(env, insn_idx - i + 1) || + bpf_is_jump_target(env, insn_idx + i)) + break; stx = &insns[insn_idx - i]; ldx = &insns[insn_idx + i]; /* must be a stack spill/fill pair */ From 65b1518c995c590ab01f1e87f37d4eb47e8d050f Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Thu, 3 Sep 2026 13:58:20 -0700 Subject: [PATCH 0770/2366] selftests/bpf: bpf_fastcall patterns entered by a jump Check bpf_fastcall pattern detection when the pattern is entered at an instruction other than the first spill: - a jump to the first spill allows the rewrite; - conditional/unconditional a jump to the call or to the fill does not allow the rewrite. Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260903205820.1743087-2-eddyz87@gmail.com Signed-off-by: Alexei Starovoitov --- .../bpf/progs/verifier_bpf_fastcall.c | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c b/tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c index 328cf630210a..a73b837553fb 100644 --- a/tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c +++ b/tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c @@ -621,6 +621,116 @@ __naked void helper_call_does_not_prevent_bpf_fastcall(void) : __clobber_all); } +/* A jump to the first spill executes the whole pattern, rewrite is safe. */ +SEC("raw_tp") +__arch_x86_64 +__log_level(4) +__msg("subprog 0 (jump_to_first_spill) main {{.*}} stack 0") +__xlated("2: if r0 == 0x2a goto pc+0") +__xlated("3: r0 = ") +__xlated("4: r0 = &(void __percpu *)(r0)") +__success +__naked void jump_to_first_spill(void) +{ + asm volatile ( + "call %[bpf_get_prandom_u32];" + "r1 = 1;" + "if r0 == 42 goto l0_%=;" +"l0_%=:" + "*(u64 *)(r10 - 8) = r1;" + "call %[bpf_get_smp_processor_id];" + "r1 = *(u64 *)(r10 - 8);" + "exit;" + : + : __imm(bpf_get_prandom_u32), + __imm(bpf_get_smp_processor_id) + : __clobber_all); +} + +/* A jump to the call skips the spill, the pattern must be kept. */ +SEC("raw_tp") +__arch_x86_64 +__log_level(4) +__msg("subprog 0 (jump_to_call) main {{.*}} stack 8") +__xlated("2: if r0 == 0x2a goto pc+1") +__xlated("3: *(u64 *)(r10 -8) = r1") +__xlated("...") +__xlated("7: r1 = *(u64 *)(r10 -8)") +__success +__naked void jump_to_call(void) +{ + asm volatile ( + "call %[bpf_get_prandom_u32];" + "r1 = 1;" + "if r0 == 42 goto l0_%=;" + "*(u64 *)(r10 - 8) = r1;" +"l0_%=:" + "call %[bpf_get_smp_processor_id];" + "r1 = *(u64 *)(r10 - 8);" + "exit;" + : + : __imm(bpf_get_prandom_u32), + __imm(bpf_get_smp_processor_id) + : __clobber_all); +} + +/* A jump to the fill skips the spill, the pattern must be kept. */ +SEC("raw_tp") +__arch_x86_64 +__log_level(4) +__msg("subprog 0 (jump_to_fill) main {{.*}} stack 8") +__xlated("2: if r0 == 0x2a goto pc+4") +__xlated("3: *(u64 *)(r10 -8) = r1") +__xlated("...") +__xlated("7: r1 = *(u64 *)(r10 -8)") +__success +__naked void jump_to_fill(void) +{ + asm volatile ( + "call %[bpf_get_prandom_u32];" + "r1 = 1;" + "if r0 == 42 goto l0_%=;" + "*(u64 *)(r10 - 8) = r1;" + "call %[bpf_get_smp_processor_id];" +"l0_%=:" + "r1 = *(u64 *)(r10 - 8);" + "exit;" + : + : __imm(bpf_get_prandom_u32), + __imm(bpf_get_smp_processor_id) + : __clobber_all); +} + +/* Same as above, but the fill is entered by an unconditional jump. */ +SEC("raw_tp") +__arch_x86_64 +__log_level(4) +__msg("subprog 0 (unconditional_jump_to_fill) main {{.*}} stack 8") +__xlated("3: *(u64 *)(r10 -8) = r1") +__xlated("...") +__xlated("7: r1 = *(u64 *)(r10 -8)") +__xlated("8: exit") +__xlated("9: goto pc-3") +__success +__naked void unconditional_jump_to_fill(void) +{ + asm volatile ( + "call %[bpf_get_prandom_u32];" + "r1 = 1;" + "if r0 == 42 goto l1_%=;" + "*(u64 *)(r10 - 8) = r1;" + "call %[bpf_get_smp_processor_id];" +"l0_%=:" + "r1 = *(u64 *)(r10 - 8);" + "exit;" +"l1_%=:" + "goto l0_%=;" + : + : __imm(bpf_get_prandom_u32), + __imm(bpf_get_smp_processor_id) + : __clobber_all); +} + SEC("raw_tp") __arch_x86_64 __log_level(4) From b752e041d5845d03f285cf7a1f30b38ca7ef92bf Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Sun, 30 Aug 2026 15:22:52 -0700 Subject: [PATCH 0771/2366] enic: preserve V2 VF carrier across netdev reopen A V2 VF receives carrier state only from PF MBOX notifications. enic_stop() forces carrier off, but enic_open() does not request a fresh notification or restore the previous one. An ordinary down/up cycle therefore leaves the VF in NO-CARRIER and unable to pass traffic until the PF repeats the link-state command, even when the physical link remained up. Cache each valid PF link-state notification. Serialize updates with the V2 VF datapath running state. Keep carrier off while the netdev is stopped. Restore the cached state after an ordinary open. Before either internal reset reopens the datapath, invalidate the cache. Carrier then remains off until re-registration receives a fresh PF link-state notification. Fixes: 72b65c94058e ("enic: add MBOX VF handlers for capability, register and link state") Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260830-b4-enic-v2-mbox-fixes-net-v1-1-23adf9bfd426@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic.h | 13 ++++++++ drivers/net/ethernet/cisco/enic/enic_main.c | 12 ++++++- drivers/net/ethernet/cisco/enic/enic_mbox.c | 37 ++++++++++++++++++--- drivers/net/ethernet/cisco/enic/enic_mbox.h | 2 ++ 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h index 4a67947cfb9f..2822fbfb6474 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -137,6 +137,12 @@ struct enic_port_profile { u8 mac_addr[ETH_ALEN]; }; +enum enic_vf_link_state { + ENIC_VF_LINK_STATE_UNKNOWN, + ENIC_VF_LINK_STATE_DOWN, + ENIC_VF_LINK_STATE_UP, +}; + /* enic_rfs_fltr_node - rfs filter node in hash table * @@keys: IPv4 5 tuple * @flow_id: flow_id of clsf filter provided by kernel @@ -312,6 +318,13 @@ struct enic { unsigned int admin_msg_count; /* current depth of admin_msg_list */ void (*admin_rq_handler)(struct enic *enic, void *buf, unsigned int len); + /* The PF is authoritative for a V2 VF's carrier. Keep the last + * notification across an ordinary netdev close/open and serialize it + * against the open/stop carrier transition. + */ + spinlock_t vf_link_state_lock; + enum enic_vf_link_state vf_link_state; + bool vf_link_running; /* MBOX protocol state — mbox_lock serializes admin WQ sends */ struct mutex mbox_lock; diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c index 0baef7a120ec..48d16ef18c49 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -1800,6 +1800,8 @@ static int enic_open(struct net_device *netdev) enic_notify_timer_start(enic); enic_rfs_timer_start(enic); + if (enic_is_sriov_vf_v2(enic)) + enic_mbox_vf_link_state_set_running(enic, true); return 0; @@ -1853,7 +1855,10 @@ static int enic_stop(struct net_device *netdev) for (i = 0; i < enic->rq_count; i++) napi_disable(&enic->napi[i]); - netif_carrier_off(netdev); + if (enic_is_sriov_vf_v2(enic)) + enic_mbox_vf_link_state_set_running(enic, false); + else + netif_carrier_off(netdev); if (vnic_dev_get_intr_mode(enic->vdev) == VNIC_DEV_INTR_MODE_MSIX) for (i = 0; i < enic->wq_count; i++) napi_disable(&enic->napi[enic_cq_wq(enic, i)]); @@ -2271,6 +2276,8 @@ static void enic_reset(struct work_struct *work) enic_admin_channel_close(enic); enic_stop(enic->netdev); + if (enic_is_sriov_vf_v2(enic)) + enic_mbox_vf_link_state_reset(enic); enic_dev_soft_reset(enic); enic_reset_addr_lists(enic); @@ -2315,6 +2322,8 @@ static void enic_tx_hang_reset(struct work_struct *work) enic_dev_hang_notify(enic); enic_stop(enic->netdev); + if (enic_is_sriov_vf_v2(enic)) + enic_mbox_vf_link_state_reset(enic); enic_dev_hang_reset(enic); enic_reset_addr_lists(enic); @@ -3015,6 +3024,7 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) enic = netdev_priv(netdev); enic->netdev = netdev; enic->pdev = pdev; + spin_lock_init(&enic->vf_link_state_lock); /* Setup PCI resources */ diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c index 2fb0f1e2ff50..ad79d3951f3d 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.c +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -396,25 +396,32 @@ static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload) { struct enic_mbox_pf_link_state_notif_msg *notif = payload; struct enic_mbox_pf_link_state_ack_msg ack = {}; + u32 link_state = le32_to_cpu(notif->link_state); int err; - switch (le32_to_cpu(notif->link_state)) { + spin_lock_bh(&enic->vf_link_state_lock); + switch (link_state) { case ENIC_MBOX_LINK_STATE_ENABLE: - if (!netif_carrier_ok(enic->netdev)) + enic->vf_link_state = ENIC_VF_LINK_STATE_UP; + if (enic->vf_link_running && + !netif_carrier_ok(enic->netdev)) netif_carrier_on(enic->netdev); netdev_dbg(enic->netdev, "MBOX: link state -> UP\n"); break; case ENIC_MBOX_LINK_STATE_DISABLE: - if (netif_carrier_ok(enic->netdev)) + enic->vf_link_state = ENIC_VF_LINK_STATE_DOWN; + if (enic->vf_link_running && + netif_carrier_ok(enic->netdev)) netif_carrier_off(enic->netdev); netdev_dbg(enic->netdev, "MBOX: link state -> DOWN\n"); break; default: netdev_warn(enic->netdev, "MBOX: unknown link state %u\n", - le32_to_cpu(notif->link_state)); + link_state); ack.ack.ret_major = cpu_to_le16(ENIC_MBOX_ERR_GENERIC); break; } + spin_unlock_bh(&enic->vf_link_state_lock); err = enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_ACK, ENIC_MBOX_DST_PF, &ack, sizeof(ack)); @@ -423,6 +430,28 @@ static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload) "MBOX: failed to send link state ACK: %d\n", err); } +void enic_mbox_vf_link_state_reset(struct enic *enic) +{ + spin_lock_bh(&enic->vf_link_state_lock); + enic->vf_link_state = ENIC_VF_LINK_STATE_UNKNOWN; + if (enic->vf_link_running && netif_carrier_ok(enic->netdev)) + netif_carrier_off(enic->netdev); + spin_unlock_bh(&enic->vf_link_state_lock); +} + +void enic_mbox_vf_link_state_set_running(struct enic *enic, bool running) +{ + spin_lock_bh(&enic->vf_link_state_lock); + enic->vf_link_running = running; + if (running && enic->vf_link_state == ENIC_VF_LINK_STATE_UP) { + if (!netif_carrier_ok(enic->netdev)) + netif_carrier_on(enic->netdev); + } else if (netif_carrier_ok(enic->netdev)) { + netif_carrier_off(enic->netdev); + } + spin_unlock_bh(&enic->vf_link_state_lock); +} + static bool enic_mbox_vf_payload_ok(struct enic *enic, u8 msg_type, u16 payload_len, size_t min_len) { diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h index 15e30ee2b0ed..60409bad2f28 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.h +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h @@ -88,6 +88,8 @@ void enic_mbox_init(struct enic *enic); int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, void *payload, u16 payload_len); int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state); +void enic_mbox_vf_link_state_reset(struct enic *enic); +void enic_mbox_vf_link_state_set_running(struct enic *enic, bool running); int enic_mbox_vf_capability_check(struct enic *enic); int enic_mbox_vf_register(struct enic *enic); int enic_mbox_vf_unregister(struct enic *enic); From 8972d252f495d4be4bbfb32d4d9d6c2ff778fea4 Mon Sep 17 00:00:00 2001 From: Satish Kharat Date: Sun, 30 Aug 2026 15:22:53 -0700 Subject: [PATCH 0772/2366] enic: match mailbox replies to request numbers The version-1 VF mailbox protocol identifies every message with a message number, and a reply or acknowledgment echoes the number of the message it answers. ENIC instead generates a new number for outgoing replies and accepts a VF reply by message type alone. If a request times out, a delayed reply can therefore satisfy a subsequent request of the same type and cause the VF to consume the result of the old request. Allow replies to reuse the initiating message number. Make the in-tree PF handlers and the VF link-state acknowledgment echo that number. Record the expected reply type and message number on the VF, and require both values to match before accepting a reply. Protect expected-reply state with a lock so reply acceptance and timeout invalidation cannot race. Keep message numbers monotonic across an admin- channel reopen so a delayed reply from an earlier channel generation cannot match a new request. Reply-number echo is part of the established version-1 protocol, so this remains compatible with deployed V2-capable PF implementations that already echo msg_num. Fixes: 72b65c94058e ("enic: add MBOX VF handlers for capability, register and link state") Signed-off-by: Satish Kharat Link: https://patch.msgid.link/20260830-b4-enic-v2-mbox-fixes-net-v1-2-23adf9bfd426@cisco.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cisco/enic/enic.h | 13 +- drivers/net/ethernet/cisco/enic/enic_main.c | 13 +- drivers/net/ethernet/cisco/enic/enic_mbox.c | 284 ++++++++++++-------- 3 files changed, 180 insertions(+), 130 deletions(-) diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h index 2822fbfb6474..7a509a056990 100644 --- a/drivers/net/ethernet/cisco/enic/enic.h +++ b/drivers/net/ethernet/cisco/enic/enic.h @@ -329,15 +329,14 @@ struct enic { /* MBOX protocol state — mbox_lock serializes admin WQ sends */ struct mutex mbox_lock; u64 mbox_msg_num; - /* MBOX request-reply state. mbox_expected_reply is written and - * cleared by the process-context request helpers (capability/register/ - * unregister) and only read by the admin_msg_work receive handlers, so - * it is annotated with READ_ONCE()/WRITE_ONCE() rather than locked: - * only one request is in flight at a time (requesters run under RTNL or - * single-threaded probe/remove), so each request is serialized and its - * reply completes mbox_comp before the next request is issued. + /* MBOX request-reply state. Existing request callers allow only one + * request in flight. The state lock arbitrates reply acceptance against + * timeout invalidation, while mbox_comp publishes the accepted result to + * the requester. */ struct completion mbox_comp; + spinlock_t mbox_state_lock; /* protects expected reply state */ + u64 mbox_expected_msg_num; u8 mbox_expected_reply; bool mbox_initialized; diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c index 48d16ef18c49..14d1637a4342 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -2206,9 +2206,10 @@ static void enic_admin_chan_reopen(struct enic *enic) { int err; - /* Install the MBOX receive handler and reset the sequence number - * before opening the channel, so the handler is in place before the - * admin interrupt is unmasked and no early completion is dropped. + /* Install the MBOX receive handler and clear pending reply state before + * opening the channel, so the handler is in place before the admin + * interrupt is unmasked and no early completion is dropped. Keep the + * sequence number monotonic across channel generations. */ enic_mbox_init(enic); @@ -2220,7 +2221,7 @@ static void enic_admin_chan_reopen(struct enic *enic) * registration over a dead channel. */ if (enic_is_sriov_vf_v2(enic)) - enic->vf_registered = false; + WRITE_ONCE(enic->vf_registered, false); err = enic_admin_channel_open(enic); if (err) { @@ -3349,7 +3350,7 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) err_out_admin_close: if (enic_is_sriov_vf_v2(enic)) { - if (enic->vf_registered) { + if (READ_ONCE(enic->vf_registered)) { int unreg_err = enic_mbox_vf_unregister(enic); if (unreg_err) @@ -3402,7 +3403,7 @@ static void enic_remove(struct pci_dev *pdev) * touching a netdev that is being torn down. */ if (enic_is_sriov_vf_v2(enic)) { - if (enic->vf_registered) { + if (READ_ONCE(enic->vf_registered)) { int unreg_err = enic_mbox_vf_unregister(enic); if (unreg_err) diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c index ad79d3951f3d..5c93ca49552a 100644 --- a/drivers/net/ethernet/cisco/enic/enic_mbox.c +++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c @@ -18,22 +18,25 @@ #define ENIC_MBOX_POLL_TIMEOUT_US 5000000 #define ENIC_MBOX_POLL_INTERVAL_US 100 -static void enic_mbox_fill_hdr(struct enic *enic, struct enic_mbox_hdr *hdr, - u8 msg_type, u16 dst_vnic_id, u16 msg_len) +static void enic_mbox_fill_hdr(struct enic_mbox_hdr *hdr, u8 msg_type, + u16 dst_vnic_id, u16 msg_len, u64 msg_num) { memset(hdr, 0, sizeof(*hdr)); hdr->dst_vnic_id = cpu_to_le16(dst_vnic_id); hdr->msg_type = msg_type; hdr->msg_len = cpu_to_le16(msg_len); - hdr->msg_num = cpu_to_le64(++enic->mbox_msg_num); + hdr->msg_num = cpu_to_le64(msg_num); } -int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, - void *payload, u16 payload_len) +static int enic_mbox_send_msg_id(struct enic *enic, u8 msg_type, + u16 dst_vnic_id, void *payload, + u16 payload_len, u64 msg_num, bool reuse_msg_num, + u8 expected_reply) { size_t total_len = sizeof(struct enic_mbox_hdr) + payload_len; struct vnic_wq *wq = &enic->admin_wq; struct wq_enet_desc *desc; + bool reply_expected = false; unsigned long timeout; dma_addr_t dma_addr; u16 vlan_tag; @@ -68,7 +71,21 @@ int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, goto unlock; } - enic_mbox_fill_hdr(enic, buf, msg_type, dst_vnic_id, total_len); + /* Replies reuse the initiating message number. Requests and + * notifications allocate a new one. + */ + if (!reuse_msg_num) + msg_num = ++enic->mbox_msg_num; + if (expected_reply) { + reinit_completion(&enic->mbox_comp); + spin_lock_bh(&enic->mbox_state_lock); + enic->mbox_expected_reply = expected_reply; + enic->mbox_expected_msg_num = msg_num; + spin_unlock_bh(&enic->mbox_state_lock); + reply_expected = true; + } + + enic_mbox_fill_hdr(buf, msg_type, dst_vnic_id, total_len, msg_num); if (payload_len) { void *dst = buf + sizeof(struct enic_mbox_hdr); @@ -139,18 +156,66 @@ int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, "MBOX send msg_type %u dst %u vlan %u err %d\n", msg_type, dst_vnic_id, vlan_tag, err); unlock: + if (err && reply_expected) { + spin_lock_bh(&enic->mbox_state_lock); + if (enic->mbox_expected_reply == expected_reply && + enic->mbox_expected_msg_num == msg_num) { + enic->mbox_expected_reply = 0; + enic->mbox_expected_msg_num = 0; + } + spin_unlock_bh(&enic->mbox_state_lock); + } mutex_unlock(&enic->mbox_lock); return err; } +int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id, + void *payload, u16 payload_len) +{ + return enic_mbox_send_msg_id(enic, msg_type, dst_vnic_id, payload, + payload_len, 0, false, 0); +} + +static int enic_mbox_send_reply(struct enic *enic, u8 msg_type, + u16 dst_vnic_id, void *payload, u16 payload_len, + u64 msg_num) +{ + return enic_mbox_send_msg_id(enic, msg_type, dst_vnic_id, payload, + payload_len, msg_num, true, 0); +} + +static int enic_mbox_vf_send_request(struct enic *enic, u8 request_type, + u8 expected_reply, void *payload, + u16 payload_len) +{ + return enic_mbox_send_msg_id(enic, request_type, ENIC_MBOX_DST_PF, + payload, payload_len, 0, false, + expected_reply); +} + static int enic_mbox_wait_reply(struct enic *enic, unsigned long timeout_ms) { unsigned long left; + int err = 0; left = wait_for_completion_timeout(&enic->mbox_comp, msecs_to_jiffies(timeout_ms)); + if (left) + return 0; - return left ? 0 : -ETIMEDOUT; + /* Invalidate a request that the handler has not already accepted. A + * delayed reply cannot match a later request because message numbers are + * monotonic across channel reopen. + */ + spin_lock_bh(&enic->mbox_state_lock); + if (enic->mbox_expected_reply) { + enic->mbox_expected_reply = 0; + enic->mbox_expected_msg_num = 0; + err = -ETIMEDOUT; + } + spin_unlock_bh(&enic->mbox_state_lock); + + return err; } int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state) @@ -178,8 +243,8 @@ static int enic_mbox_pf_handle_capability(struct enic *enic, void *msg, reply.reply.ret_major = cpu_to_le16(0); reply.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1); - return enic_mbox_send_msg(enic, ENIC_MBOX_VF_CAPABILITY_REPLY, vf_id, - &reply, sizeof(reply)); + return enic_mbox_send_reply(enic, ENIC_MBOX_VF_CAPABILITY_REPLY, vf_id, + &reply, sizeof(reply), msg_num); } static int enic_mbox_pf_handle_register(struct enic *enic, void *msg, @@ -208,8 +273,8 @@ static int enic_mbox_pf_handle_register(struct enic *enic, void *msg, } reply.reply.ret_major = cpu_to_le16(0); - err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_REGISTER_REPLY, vf_id, - &reply, sizeof(reply)); + err = enic_mbox_send_reply(enic, ENIC_MBOX_VF_REGISTER_REPLY, vf_id, + &reply, sizeof(reply), msg_num); if (err) return err; @@ -253,8 +318,8 @@ static int enic_mbox_pf_handle_unregister(struct enic *enic, void *msg, enic->vf_state[vf_id].registered = false; reply.reply.ret_major = cpu_to_le16(0); - err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_UNREGISTER_REPLY, vf_id, - &reply, sizeof(reply)); + err = enic_mbox_send_reply(enic, ENIC_MBOX_VF_UNREGISTER_REPLY, vf_id, + &reply, sizeof(reply), msg_num); if (net_ratelimit()) netdev_info(enic->netdev, @@ -324,75 +389,57 @@ static void enic_mbox_pf_process_msg(struct enic *enic, hdr->msg_type, vf_id, err); } -static void enic_mbox_vf_handle_capability_reply(struct enic *enic, - void *payload) +static void enic_mbox_vf_handle_reply(struct enic *enic, u8 reply_type, + void *payload, u64 msg_num) { - struct enic_mbox_vf_capability_reply_msg *reply = payload; + struct enic_mbox_generic_reply *reply = payload; + u16 ret_major = le16_to_cpu(reply->ret_major); + u64 expected_msg_num; + u8 expected_type; - if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_CAPABILITY_REPLY) { + spin_lock_bh(&enic->mbox_state_lock); + expected_type = enic->mbox_expected_reply; + expected_msg_num = enic->mbox_expected_msg_num; + if (expected_type != reply_type || expected_msg_num != msg_num) { + spin_unlock_bh(&enic->mbox_state_lock); netdev_warn(enic->netdev, - "MBOX: stale capability reply (expected %u), drop\n", - READ_ONCE(enic->mbox_expected_reply)); + "MBOX: stale reply %u/%llu (expected %u/%llu), drop\n", + reply_type, (unsigned long long)msg_num, + expected_type, (unsigned long long)expected_msg_num); return; } - if (le16_to_cpu(reply->reply.ret_major) == 0) - enic->pf_cap_version = le32_to_cpu(reply->version); - else - netdev_warn(enic->netdev, - "MBOX: PF rejected capability request: %u/%u\n", - le16_to_cpu(reply->reply.ret_major), - le16_to_cpu(reply->reply.ret_minor)); + if (!ret_major) { + switch (reply_type) { + case ENIC_MBOX_VF_CAPABILITY_REPLY: { + struct enic_mbox_vf_capability_reply_msg *cap = payload; + + WRITE_ONCE(enic->pf_cap_version, + le32_to_cpu(cap->version)); + break; + } + case ENIC_MBOX_VF_REGISTER_REPLY: + WRITE_ONCE(enic->vf_registered, true); + break; + case ENIC_MBOX_VF_UNREGISTER_REPLY: + WRITE_ONCE(enic->vf_registered, false); + break; + } + } + enic->mbox_expected_reply = 0; + enic->mbox_expected_msg_num = 0; complete(&enic->mbox_comp); + spin_unlock_bh(&enic->mbox_state_lock); + + if (ret_major) + netdev_warn(enic->netdev, + "MBOX: PF rejected reply type %u: %u/%u\n", + reply_type, ret_major, + le16_to_cpu(reply->ret_minor)); } -static void enic_mbox_vf_handle_register_reply(struct enic *enic, - void *payload) -{ - struct enic_mbox_vf_register_reply_msg *reply = payload; - - if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_REGISTER_REPLY) { - netdev_warn(enic->netdev, - "MBOX: stale register reply (expected %u), drop\n", - READ_ONCE(enic->mbox_expected_reply)); - return; - } - - if (le16_to_cpu(reply->reply.ret_major)) { - netdev_warn(enic->netdev, - "MBOX: VF register rejected by PF: %u/%u\n", - le16_to_cpu(reply->reply.ret_major), - le16_to_cpu(reply->reply.ret_minor)); - } else { - enic->vf_registered = true; - } - complete(&enic->mbox_comp); -} - -static void enic_mbox_vf_handle_unregister_reply(struct enic *enic, - void *payload) -{ - struct enic_mbox_vf_register_reply_msg *reply = payload; - - if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_UNREGISTER_REPLY) { - netdev_warn(enic->netdev, - "MBOX: stale unregister reply (expected %u), drop\n", - READ_ONCE(enic->mbox_expected_reply)); - return; - } - - if (le16_to_cpu(reply->reply.ret_major)) { - netdev_warn(enic->netdev, - "MBOX: VF unregister rejected by PF: %u/%u\n", - le16_to_cpu(reply->reply.ret_major), - le16_to_cpu(reply->reply.ret_minor)); - } else { - enic->vf_registered = false; - } - complete(&enic->mbox_comp); -} - -static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload) +static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload, + u64 msg_num) { struct enic_mbox_pf_link_state_notif_msg *notif = payload; struct enic_mbox_pf_link_state_ack_msg ack = {}; @@ -423,8 +470,8 @@ static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload) } spin_unlock_bh(&enic->vf_link_state_lock); - err = enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_ACK, - ENIC_MBOX_DST_PF, &ack, sizeof(ack)); + err = enic_mbox_send_reply(enic, ENIC_MBOX_PF_LINK_STATE_ACK, + ENIC_MBOX_DST_PF, &ack, sizeof(ack), msg_num); if (err && net_ratelimit()) netdev_warn(enic->netdev, "MBOX: failed to send link state ACK: %d\n", err); @@ -468,6 +515,8 @@ static void enic_mbox_vf_process_msg(struct enic *enic, struct enic_mbox_hdr *hdr, void *payload, u16 payload_len) { + u64 msg_num = le64_to_cpu(hdr->msg_num); + switch (hdr->msg_type) { case ENIC_MBOX_VF_CAPABILITY_REPLY: { size_t exp = sizeof(struct enic_mbox_vf_capability_reply_msg); @@ -475,7 +524,7 @@ static void enic_mbox_vf_process_msg(struct enic *enic, if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, payload_len, exp)) return; - enic_mbox_vf_handle_capability_reply(enic, payload); + enic_mbox_vf_handle_reply(enic, hdr->msg_type, payload, msg_num); break; } case ENIC_MBOX_VF_REGISTER_REPLY: { @@ -484,7 +533,7 @@ static void enic_mbox_vf_process_msg(struct enic *enic, if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, payload_len, exp)) return; - enic_mbox_vf_handle_register_reply(enic, payload); + enic_mbox_vf_handle_reply(enic, hdr->msg_type, payload, msg_num); break; } case ENIC_MBOX_VF_UNREGISTER_REPLY: { @@ -493,7 +542,7 @@ static void enic_mbox_vf_process_msg(struct enic *enic, if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, payload_len, exp)) return; - enic_mbox_vf_handle_unregister_reply(enic, payload); + enic_mbox_vf_handle_reply(enic, hdr->msg_type, payload, msg_num); break; } case ENIC_MBOX_PF_LINK_STATE_NOTIF: { @@ -502,7 +551,7 @@ static void enic_mbox_vf_process_msg(struct enic *enic, if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type, payload_len, exp)) return; - enic_mbox_vf_handle_link_state(enic, payload); + enic_mbox_vf_handle_link_state(enic, payload, msg_num); break; } default: @@ -571,32 +620,31 @@ static void enic_mbox_recv_handler(struct enic *enic, void *buf, int enic_mbox_vf_capability_check(struct enic *enic) { struct enic_mbox_vf_capability_msg req = {}; + u32 version; int err; - enic->pf_cap_version = 0; - reinit_completion(&enic->mbox_comp); - WRITE_ONCE(enic->mbox_expected_reply, ENIC_MBOX_VF_CAPABILITY_REPLY); + WRITE_ONCE(enic->pf_cap_version, 0); req.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1); - err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_CAPABILITY_REQUEST, - ENIC_MBOX_DST_PF, &req, sizeof(req)); - if (err) { - WRITE_ONCE(enic->mbox_expected_reply, 0); + err = enic_mbox_vf_send_request(enic, + ENIC_MBOX_VF_CAPABILITY_REQUEST, + ENIC_MBOX_VF_CAPABILITY_REPLY, + &req, sizeof(req)); + if (err) return err; - } err = enic_mbox_wait_reply(enic, 3000); - WRITE_ONCE(enic->mbox_expected_reply, 0); + version = READ_ONCE(enic->pf_cap_version); if (err) { netdev_warn(enic->netdev, "MBOX: no capability reply from PF\n"); return err; } - if (enic->pf_cap_version < ENIC_MBOX_CAP_VERSION_1) { + if (version < ENIC_MBOX_CAP_VERSION_1) { netdev_warn(enic->netdev, "MBOX: PF rejected capability request or reported unsupported version %u\n", - enic->pf_cap_version); + version); return -EOPNOTSUPP; } @@ -605,28 +653,25 @@ int enic_mbox_vf_capability_check(struct enic *enic) int enic_mbox_vf_register(struct enic *enic) { + bool registered; int err; - enic->vf_registered = false; - reinit_completion(&enic->mbox_comp); - WRITE_ONCE(enic->mbox_expected_reply, ENIC_MBOX_VF_REGISTER_REPLY); + WRITE_ONCE(enic->vf_registered, false); - err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_REGISTER_REQUEST, - ENIC_MBOX_DST_PF, NULL, 0); - if (err) { - WRITE_ONCE(enic->mbox_expected_reply, 0); + err = enic_mbox_vf_send_request(enic, ENIC_MBOX_VF_REGISTER_REQUEST, + ENIC_MBOX_VF_REGISTER_REPLY, NULL, 0); + if (err) return err; - } err = enic_mbox_wait_reply(enic, 3000); - WRITE_ONCE(enic->mbox_expected_reply, 0); + registered = READ_ONCE(enic->vf_registered); if (err) { netdev_warn(enic->netdev, "MBOX: VF registration with PF timed out\n"); return err; } - if (!enic->vf_registered) + if (!registered) return -ENODEV; return 0; @@ -634,43 +679,48 @@ int enic_mbox_vf_register(struct enic *enic) int enic_mbox_vf_unregister(struct enic *enic) { + bool registered; int err; - if (!enic->vf_registered) + if (!READ_ONCE(enic->vf_registered)) return 0; - reinit_completion(&enic->mbox_comp); - WRITE_ONCE(enic->mbox_expected_reply, ENIC_MBOX_VF_UNREGISTER_REPLY); - - err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_UNREGISTER_REQUEST, - ENIC_MBOX_DST_PF, NULL, 0); - if (err) { - WRITE_ONCE(enic->mbox_expected_reply, 0); - return err; - } - - err = enic_mbox_wait_reply(enic, 3000); - WRITE_ONCE(enic->mbox_expected_reply, 0); + err = enic_mbox_vf_send_request(enic, + ENIC_MBOX_VF_UNREGISTER_REQUEST, + ENIC_MBOX_VF_UNREGISTER_REPLY, + NULL, 0); if (err) return err; - if (enic->vf_registered) + + err = enic_mbox_wait_reply(enic, 3000); + registered = READ_ONCE(enic->vf_registered); + if (err) + return err; + if (registered) return -EACCES; return 0; } void enic_mbox_init(struct enic *enic) { - /* mbox_lock and mbox_comp must be initialized exactly once per + bool reinit = enic->mbox_initialized; + + /* MBOX locks and mbox_comp must be initialized exactly once per * device lifetime; the PF sriov_configure path can re-enter this * on each enable cycle where these primitives are already set up. */ - if (!enic->mbox_initialized) { + if (!reinit) { mutex_init(&enic->mbox_lock); init_completion(&enic->mbox_comp); + spin_lock_init(&enic->mbox_state_lock); + enic->mbox_msg_num = 0; enic->mbox_initialized = true; } else { reinit_completion(&enic->mbox_comp); } - enic->mbox_msg_num = 0; + spin_lock_bh(&enic->mbox_state_lock); + enic->mbox_expected_reply = 0; + enic->mbox_expected_msg_num = 0; + spin_unlock_bh(&enic->mbox_state_lock); enic->admin_rq_handler = enic_mbox_recv_handler; } From 369f4ce734570bdfedaa4b5ca50e2a3f6a892728 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 23:47:47 +0200 Subject: [PATCH 0773/2366] bpf: Check ancestor frames for rbtree callbacks bpf_rbtree_add() invokes its comparator while the caller holds the root lock. The native insertion code retains raw parent and link pointers across the callback, so the verifier prohibits unlocking, consuming tree nodes, or changing RCU state from that callback. in_rbtree_lock_required_cb() only checks the innermost verifier frame. Static subprogram calls are permitted while holding a spin lock, and such a call pushes a frame without in_callback_fn set. Consequently, all callback restrictions disappear in the nested frame. The subprogram can unlock the tree, remove and drop the node being compared, then relock. Native insertion resumes with the stale parent pointer and links freed memory into the tree. Walk all active frames for the rbtree callback instead. Benign static subprograms remain permitted, while callback restrictions follow execution into nested frames. Fixes: a44b1334aadd ("bpf: Allow calling static subprogs while holding a bpf_spin_lock") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260903214758.2727663-2-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 2ed17edf77f2..2b7e5c9b3ffc 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -10235,9 +10235,10 @@ static void account_current_path(struct bpf_verifier_env *env) frame ? state->frame[frame - 1] : NULL); } -/* Are we currently verifying the callback for a rbtree helper that must - * be called with lock held? If so, no need to complain about unreleased - * lock +/* + * Are we currently verifying the callback for an rbtree kfunc that must + * be called with a lock held, or one of that callback's subprogs? If so, + * no need to complain about an unreleased lock. */ static bool in_rbtree_lock_required_cb(struct bpf_verifier_env *env) { @@ -10245,17 +10246,19 @@ static bool in_rbtree_lock_required_cb(struct bpf_verifier_env *env) struct bpf_insn *insn = env->prog->insnsi; struct bpf_func_state *callee; int kfunc_btf_id; + u32 frame; - if (!state->curframe) - return false; + for (frame = state->curframe; frame; frame--) { + callee = state->frame[frame]; + if (!callee->in_callback_fn) + continue; - callee = state->frame[state->curframe]; + kfunc_btf_id = insn[callee->callsite].imm; + if (is_rbtree_lock_required_kfunc(kfunc_btf_id)) + return true; + } - if (!callee->in_callback_fn) - return false; - - kfunc_btf_id = insn[callee->callsite].imm; - return is_rbtree_lock_required_kfunc(kfunc_btf_id); + return false; } static bool retval_range_within(struct bpf_retval_range range, const struct bpf_reg_state *reg) From 22ab49afe1c901b2c0b9482f38bdb647d46e6a34 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 23:47:48 +0200 Subject: [PATCH 0774/2366] selftests/bpf: Check rbtree callback restrictions in subprogs Add a verifier failure case where an rbtree comparator enters two nested static subprograms and the innermost subprogram unlocks and relocks the tree. Restoring the lock keeps the surrounding callback state balanced, so the test specifically exercises whether the callback restriction follows the nested calls. Also add a load-only positive control whose comparator calls a harmless static subprogram. This preserves the intended support for verified static subprogram calls while holding the tree lock. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903214758.2727663-3-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../testing/selftests/bpf/progs/rbtree_fail.c | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/rbtree_fail.c b/tools/testing/selftests/bpf/progs/rbtree_fail.c index 803419a47c62..4504608196ab 100644 --- a/tools/testing/selftests/bpf/progs/rbtree_fail.c +++ b/tools/testing/selftests/bpf/progs/rbtree_fail.c @@ -272,6 +272,47 @@ static bool less__bad_res_spin_unlock(struct bpf_rb_node *a, const struct bpf_rb return false; } +static __noinline void rbtree_cb_unlock_relock(void) +{ + bpf_spin_unlock(&glock); + bpf_spin_lock(&glock); +} + +static __noinline void rbtree_cb_nested_unlock(void) +{ + rbtree_cb_unlock_relock(); + asm volatile (""); +} + +static bool less__bad_subprog_unlock(struct bpf_rb_node *a, const struct bpf_rb_node *b) +{ + struct node_data *node_a; + struct node_data *node_b; + + node_a = container_of(a, struct node_data, node); + node_b = container_of(b, struct node_data, node); + rbtree_cb_nested_unlock(); + + return node_a->key < node_b->key; +} + +static __noinline void rbtree_cb_noop(void) +{ + asm volatile (""); +} + +static bool less__subprog_allowed(struct bpf_rb_node *a, const struct bpf_rb_node *b) +{ + struct node_data *node_a; + struct node_data *node_b; + + node_a = container_of(a, struct node_data, node); + node_b = container_of(b, struct node_data, node); + rbtree_cb_noop(); + + return node_a->key < node_b->key; +} + static __always_inline long add_with_cb(bool (cb)(struct bpf_rb_node *a, const struct bpf_rb_node *b)) { @@ -330,4 +371,18 @@ long rbtree_api_add_bad_cb_res_spin_unlock(void *ctx) return 0; } +SEC("?tc") +__failure __msg("can't spin_{lock,unlock} in rbtree cb") +long rbtree_api_add_bad_cb_subprog_unlock(void *ctx) +{ + return add_with_cb(less__bad_subprog_unlock); +} + +SEC("?tc") +__success +long rbtree_api_add_cb_subprog_allowed(void *ctx) +{ + return add_with_cb(less__subprog_allowed); +} + char _license[] SEC("license") = "GPL"; From 620614bf7672130c43b3cff375525a2202f61979 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 23:47:49 +0200 Subject: [PATCH 0775/2366] bpf: Mark bpf_btf_find_by_name_kind() as sleepable When bpf_btf_find_by_name_kind() finds a type in module BTF, it returns a new BTF object fd through __btf_new_fd(). This reaches anon_inode_getfd(), which can sleep while allocating or expanding the current task fd table. The helper prototype does not set might_sleep, so the verifier allows the helper in non-sleepable contexts such as BPF timer callbacks. The fd allocation can then sleep in softirq context and install the fd into the interrupted task. Mark the helper as sleepable. This preserves calls from the main body of a sleepable syscall program while rejecting calls from its non-sleepable regions. Fixes: 3d78417b60fb ("bpf: Add bpf_btf_find_by_name_kind() helper.") Reported-by: Sashiko Link: https://lore.kernel.org/bpf/20260903155150.D57251F000E9@smtp.kernel.org Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260903214758.2727663-4-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/btf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 5d93fd82e764..9f33e95d5741 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -8749,6 +8749,7 @@ BPF_CALL_4(bpf_btf_find_by_name_kind, char *, name, int, name_sz, u32, kind, int const struct bpf_func_proto bpf_btf_find_by_name_kind_proto = { .func = bpf_btf_find_by_name_kind, .gpl_only = false, + .might_sleep = true, .ret_type = RET_INTEGER, .arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY, .arg2_type = ARG_MEM_SIZE, From 687b2729ce4c90a3cec76db85d3649e8f5086f85 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 23:47:50 +0200 Subject: [PATCH 0776/2366] selftests/bpf: Test btf lookup helper sleepability Add an expected failure case which calls bpf_btf_find_by_name_kind() from a BPF timer callback. Without the helper prototype being marked sleepable, the verifier accepts the program and the load unexpectedly succeeds. Also add a positive control which calls the helper directly from a syscall program. This verifies that marking the helper sleepable only rejects it in non-sleepable regions. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903214758.2727663-5-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../bpf/progs/verifier_async_cb_context.c | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c index a7c84d3fa4c7..e0926767bbd3 100644 --- a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c +++ b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c @@ -108,6 +108,30 @@ int timer_sys_close_prog(void *ctx) return 0; } +static int timer_btf_find_cb(void *map, int *key, struct bpf_timer *timer) +{ + char name[] = "task_struct"; + + bpf_btf_find_by_name_kind(name, sizeof(name), BTF_KIND_STRUCT, 0); + return 0; +} + +SEC("syscall") +__failure __msg("sleepable helper bpf_btf_find_by_name_kind#{{[0-9]+}} in non-sleepable prog") +int timer_btf_find_prog(void *ctx) +{ + struct timer_elem *val; + int key = 0; + + val = bpf_map_lookup_elem(&timer_map, &key); + if (!val) + return 0; + + bpf_timer_init(&val->t, &timer_map, 0); + bpf_timer_set_callback(&val->t, timer_btf_find_cb); + return 0; +} + SEC("syscall") __success int syscall_sys_bpf_prog(void *ctx) @@ -126,6 +150,16 @@ int syscall_sys_close_prog(void *ctx) return 0; } +SEC("syscall") +__success +int syscall_btf_find_prog(void *ctx) +{ + char name[] = "task_struct"; + + bpf_btf_find_by_name_kind(name, sizeof(name), BTF_KIND_STRUCT, 0); + return 0; +} + /* Workqueue tests */ struct wq_elem { From 9d02927fdf4e930893c92e35fed01a2704496900 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 23:47:51 +0200 Subject: [PATCH 0777/2366] bpf: Mark faultable stack helpers as sleepable The faultable variants of bpf_get_stack() and bpf_get_task_stack() pass may_fault=true into the common stack collection code. Resolving user-space build IDs may then call build_id_parse_file() and block on filesystem reads. Neither helper prototype sets might_sleep. Since prototype selection uses the sleepability of the whole program, the verifier can still allow these helpers from a non-sleepable region within that program, such as an explicit RCU or preemption-disabled region. The task-stack helper can also be called from a non-sleepable timer callback of a sleepable program. Mark both faultable prototypes as sleepable. The existing helper context check then rejects these calls while continuing to allow them in genuinely sleepable contexts. Fixes: d4dd9775ec24 ("bpf: wire up sleepable bpf_get_stack() and bpf_get_task_stack() helpers") Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260903214758.2727663-6-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/stackmap.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/bpf/stackmap.c b/kernel/bpf/stackmap.c index a839041e0d00..d09d4c3fe547 100644 --- a/kernel/bpf/stackmap.c +++ b/kernel/bpf/stackmap.c @@ -875,6 +875,7 @@ BPF_CALL_4(bpf_get_stack_sleepable, struct pt_regs *, regs, void *, buf, u32, si const struct bpf_func_proto bpf_get_stack_sleepable_proto = { .func = bpf_get_stack_sleepable, .gpl_only = true, + .might_sleep = true, .ret_type = RET_INTEGER, .arg1_type = ARG_PTR_TO_CTX, .arg2_type = ARG_PTR_TO_UNINIT_MEM, @@ -928,6 +929,7 @@ BPF_CALL_4(bpf_get_task_stack_sleepable, struct task_struct *, task, void *, buf const struct bpf_func_proto bpf_get_task_stack_sleepable_proto = { .func = bpf_get_task_stack_sleepable, .gpl_only = false, + .might_sleep = true, .ret_type = RET_INTEGER, .arg1_type = ARG_PTR_TO_BTF_ID, .arg1_btf_id = &btf_tracing_ids[BTF_TRACING_TYPE_TASK], From 1ba0d0d8b6757eaf107f1f0fa0830c9585853db7 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 23:47:52 +0200 Subject: [PATCH 0778/2366] selftests/bpf: Check faultable stack helper contexts Add verifier coverage for the sleepable bpf_get_stack() and bpf_get_task_stack() implementations. Call each helper while preemption is disabled and require the verifier to reject it as sleepable. Both programs load when the prototypes lack might_sleep, so the expected-failure tests fail. Keep success controls outside the non-preemptible region to ensure ordinary calls from sleepable uprobes remain valid. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903214758.2727663-7-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/preempt_lock.c | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/preempt_lock.c b/tools/testing/selftests/bpf/progs/preempt_lock.c index 6d5fce7e6ffc..81c459435680 100644 --- a/tools/testing/selftests/bpf/progs/preempt_lock.c +++ b/tools/testing/selftests/bpf/progs/preempt_lock.c @@ -115,6 +115,58 @@ int preempt_sleepable_helper(void *ctx) return 0; } +SEC("?uprobe.s") +__failure __msg("sleepable helper bpf_get_stack#") +int preempt_sleepable_get_stack(struct pt_regs *ctx) +{ + struct bpf_stack_build_id stack; + + bpf_preempt_disable(); + bpf_get_stack(ctx, &stack, sizeof(stack), + BPF_F_USER_STACK | BPF_F_USER_BUILD_ID); + bpf_preempt_enable(); + return 0; +} + +SEC("?uprobe.s") +__failure __msg("sleepable helper bpf_get_task_stack#") +int preempt_sleepable_get_task_stack(void *ctx) +{ + struct bpf_stack_build_id stack; + struct task_struct *task; + + task = bpf_get_current_task_btf(); + bpf_preempt_disable(); + bpf_get_task_stack(task, &stack, sizeof(stack), + BPF_F_USER_STACK | BPF_F_USER_BUILD_ID); + bpf_preempt_enable(); + return 0; +} + +SEC("?uprobe.s") +__success +int sleepable_get_stack(struct pt_regs *ctx) +{ + struct bpf_stack_build_id stack; + + bpf_get_stack(ctx, &stack, sizeof(stack), + BPF_F_USER_STACK | BPF_F_USER_BUILD_ID); + return 0; +} + +SEC("?uprobe.s") +__success +int sleepable_get_task_stack(void *ctx) +{ + struct bpf_stack_build_id stack; + struct task_struct *task; + + task = bpf_get_current_task_btf(); + bpf_get_task_stack(task, &stack, sizeof(stack), + BPF_F_USER_STACK | BPF_F_USER_BUILD_ID); + return 0; +} + SEC("?fentry.s/" SYS_PREFIX "sys_getpgid") __failure __msg("kernel func bpf_copy_from_user_str is sleepable within non-preemptible region") int preempt_sleepable_kfunc(void *ctx) From e7d28823c662128caae63f14e16bd394916c139b Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 23:47:53 +0200 Subject: [PATCH 0779/2366] bpf: Reject legacy packet loads from callbacks check_ld_abs() models a failed BPF_LD_ABS or BPF_LD_IND in a subprogram as an implicit return with R0 set to zero. It calls prepare_func_exit() to explore this synthesized path. When the load is reached directly from a synchronous callback, prepare_func_exit() enforces the callback return contract and marks R0 precise. R0 is not derived from a real instruction on this path, so precision backtracking reaches the callback call with R0 still requested and triggers the "callback unexpected regs" verifier bug. A privileged program loader can therefore cause a verifier warning and an -EFAULT BPF_PROG_LOAD. These legacy packet-load instructions are deprecated. Reject them from callbacks rather than complicating their implicit-return model. Check all active frames before constructing the implicit return so nested static subprograms cannot hide the callback context. Global functions are verified independently with a fresh frame zero, so an active-frame check cannot identify a global function called from a callback. Also check the complete subprogram call graph during stack-depth validation and reject a function containing a legacy load when any caller is a callback. This covers global and static descendants without making has_ld_abs transitive, preserving its per-function BTF return-type check. Ordinary uses outside callbacks remain supported. Fixes: ee861486e377 ("bpf: Fix ld_{abs,ind} failure path analysis in subprogs") Reported-by: Sashiko Link: https://lore.kernel.org/bpf/20260903152147.C0E241F00A3A@smtp.kernel.org Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903214758.2727663-8-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 2b7e5c9b3ffc..d7dd0befbd10 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -5302,6 +5302,15 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx, if (!priv_stack_supported) subprog[idx].priv_stack_mode = NO_PRIV_STACK; process_func: + if (subprog[idx].has_ld_abs) { + for (tmp = idx; tmp >= 0; tmp = dinfo[tmp].caller) { + if (subprog[tmp].is_cb) { + verbose(env, "cannot use BPF_LD_[ABS|IND] within callback\n"); + return -EINVAL; + } + } + } + /* protect against potential stack overflow that might happen when * bpf2bpf calls get combined with tailcalls. Limit the caller's stack * depth for such case down to 256 so that the worst case scenario @@ -17182,6 +17191,7 @@ static bool may_access_skb(enum bpf_prog_type type) */ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn) { + struct bpf_verifier_state *state = env->cur_state; struct bpf_reg_state *regs = cur_regs(env); static const int ctx_reg = BPF_REG_6; u8 mode = BPF_MODE(insn->code); @@ -17192,6 +17202,13 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn) return -EINVAL; } + for (i = state->curframe; i; i--) { + if (state->frame[i]->in_callback_fn) { + verbose(env, "cannot use BPF_LD_[ABS|IND] within callback\n"); + return -EINVAL; + } + } + if (!env->ops->gen_ld_abs) { verifier_bug(env, "gen_ld_abs is null"); return -EFAULT; From 23724e009f65838bd8e1b42bed69e3daa4ecfdab Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 23:47:54 +0200 Subject: [PATCH 0780/2366] selftests/bpf: Reject legacy packet loads from callbacks Add verifier coverage for the callback restriction on legacy packet loads. Exercise BPF_LD_ABS directly in a bpf_loop callback and BPF_LD_IND from a static subprogram called by the callback, ensuring that callback context follows nested static calls. Also exercise a callback which reaches BPF_LD_IND through a global function and its static descendant. A sibling success case calls the same global chain outside a callback, preserving support for ordinary global packet loads. Existing success cases continue to cover loads from ordinary static subprograms. The failure cases expect the policy-specific rejection instead of reaching the implicit-return path, triggering a verifier warning, or being accepted through a function boundary. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260903214758.2727663-9-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/verifier_ld_ind.c | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_ld_ind.c b/tools/testing/selftests/bpf/progs/verifier_ld_ind.c index 09e81b99eecb..32989f981fb6 100644 --- a/tools/testing/selftests/bpf/progs/verifier_ld_ind.c +++ b/tools/testing/selftests/bpf/progs/verifier_ld_ind.c @@ -194,6 +194,102 @@ __naked void ld_ind_subprog_both_paths_safe(void) ::: __clobber_all); } +__naked __noinline __used +static int ld_abs_callback(void) +{ + asm volatile ( + "r6 = *(u64 *)(r2 + 0);" + ".8byte %[ld_abs];" + "r0 = 0;" + "exit;" + : + : __imm_insn(ld_abs, BPF_LD_ABS(BPF_W, 0)) + : __clobber_all); +} + +SEC("socket") +__description("ld_abs: reject in callback") +__failure __msg("cannot use BPF_LD_[ABS|IND] within callback") +int ld_abs_callback_reject(struct __sk_buff *skb) +{ + bpf_loop(1, ld_abs_callback, &skb, 0); + return 0; +} + +__naked __noinline __used +static int ld_ind_callback_subprog(void) +{ + asm volatile ( + "r6 = r1;" + "r7 = 0;" + ".8byte %[ld_ind];" + "r0 = 0;" + "exit;" + : + : __imm_insn(ld_ind, BPF_LD_IND(BPF_W, BPF_REG_7, 0)) + : __clobber_all); +} + +__naked __noinline __used +static int ld_ind_callback(void) +{ + asm volatile ( + "r1 = *(u64 *)(r2 + 0);" + "call ld_ind_callback_subprog;" + "exit;" + ::: __clobber_all); +} + +SEC("socket") +__description("ld_ind: reject in callback subprog") +__failure __msg("cannot use BPF_LD_[ABS|IND] within callback") +int ld_ind_callback_subprog_reject(struct __sk_buff *skb) +{ + bpf_loop(1, ld_ind_callback, &skb, 0); + return 0; +} + +static __noinline int ld_ind_global_static(struct __sk_buff *skb) +{ + asm volatile ( + "r6 = %[skb];" + "r7 = 0;" + ".8byte %[ld_ind];" + : + : [skb] "r"(skb), + __imm_insn(ld_ind, BPF_LD_IND(BPF_W, BPF_REG_7, 0)) + : __clobber_common, "r6", "r7"); + return skb->mark; +} + +__noinline int ld_ind_global(struct __sk_buff *skb) +{ + return ld_ind_global_static(skb); +} + +static int ld_ind_global_callback(__u32 index, struct __sk_buff **ctx) +{ + ld_ind_global(*ctx); + return 0; +} + +SEC("socket") +__description("ld_ind: reject in callback global subprog") +__failure __msg("cannot use BPF_LD_[ABS|IND] within callback") +int ld_ind_global_callback_reject(struct __sk_buff *skb) +{ + bpf_loop(1, ld_ind_global_callback, &skb, 0); + return 0; +} + +SEC("socket") +__description("ld_ind: allow in non-callback global subprog") +__success +int ld_ind_global_subprog_ok(struct __sk_buff *skb) +{ + return ld_ind_global(skb); +} + /* * ld_{abs,ind} in subprogs require scalar (int) return type in BTF. * A test with void return must be rejected. From 254c881fe0554c5efb16d355c273702a27a32a20 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 3 Sep 2026 08:58:45 +0200 Subject: [PATCH 0781/2366] selftests/bpf: Add tests to assert that netfilter progs cannot write to skb The netfilter framework is allergic to ip header changing after validation done by ip/ipv6 stack. Assert that bpf netfilter programs do not allow skb write access. Following additional tests are expected to be rejected by verifier: 1. alter skb->len. 2. alter skb->data. 3. prog calls bpf_dynptr_slice_rdwr. 4. alter location returned by dynptr API. Add following test case for bpf runtime: - alter skb data via bpf_dynptr_write() Test checks via __retval() that bpf_dynptr_write() returned nonzero value. Signed-off-by: Florian Westphal Reviewed-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260903065845.22762-1-fw@strlen.de Signed-off-by: Alexei Starovoitov --- .../bpf/progs/verifier_netfilter_ctx.c | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_netfilter_ctx.c b/tools/testing/selftests/bpf/progs/verifier_netfilter_ctx.c index e2cbc5bda65e..b5d7f567d0d4 100644 --- a/tools/testing/selftests/bpf/progs/verifier_netfilter_ctx.c +++ b/tools/testing/selftests/bpf/progs/verifier_netfilter_ctx.c @@ -113,4 +113,82 @@ int with_valid_ctx_access_test6(struct bpf_nf_ctx *ctx) return th->dest == bpf_htons(22) ? NF_ACCEPT : NF_DROP; } +SEC("netfilter") +__description("netfilter test prog with skb write access") +__failure __msg("only read is supported") +int skb_len_write(struct bpf_nf_ctx *ctx) +{ + ctx->skb->len = 1; + return 1; +} + +SEC("netfilter") +__description("netfilter test prog with skb data write access") +__failure __msg("cannot write into rdonly_untrusted_mem") +int skb_data_write(struct bpf_nf_ctx *ctx) +{ + ctx->skb->data[0] = 0; + return 1; +} + +SEC("netfilter") +__description("netfilter test prog with bpf_dynptr_write") +__success __failure_unpriv +__retval(0) +int with_dynptr_write(struct bpf_nf_ctx *ctx) +{ + struct __sk_buff *skb = (struct __sk_buff *)ctx->skb; + struct bpf_dynptr ptr; + u8 buffer[1] = {}; + + if (bpf_dynptr_from_skb(skb, 0, &ptr)) + return 1; + + if (bpf_dynptr_write(&ptr, 0, buffer, sizeof(buffer), 0)) + return 0; /* must always fail */ + + return 1; +} + +SEC("netfilter") +__description("netfilter test prog with bpf_dynptr_slice_rdwr") +__failure __msg("the prog does not allow writes to packet data") +int with_dynptr_rdwr(struct bpf_nf_ctx *ctx) +{ + struct __sk_buff *skb = (struct __sk_buff *)ctx->skb; + u8 buffer_iph[20] = {}; + struct bpf_dynptr ptr; + struct iphdr *iph; + + if (bpf_dynptr_from_skb(skb, 0, &ptr)) + return 1; + + iph = bpf_dynptr_slice_rdwr(&ptr, 0, buffer_iph, sizeof(buffer_iph)); + if (!iph) + return 0; + + return 1; +} + +SEC("netfilter") +__description("netfilter test prog with bpf_dynptr_slice + write") +__failure __msg("cannot write into rdonly_mem") +int with_dynptr_store(struct bpf_nf_ctx *ctx) +{ + struct __sk_buff *skb = (struct __sk_buff *)ctx->skb; + u8 buffer_iph[20] = {}; + struct bpf_dynptr ptr; + struct iphdr *iph; + + if (bpf_dynptr_from_skb(skb, 0, &ptr)) + return 1; + + iph = bpf_dynptr_slice(&ptr, 0, buffer_iph, sizeof(buffer_iph)); + if (!iph) + return 0; + iph->protocol = 42; + + return 1; +} + char _license[] SEC("license") = "GPL"; From 912edebe8501a36c6bedcef03bd238ab90a7e060 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Tue, 1 Sep 2026 15:54:51 +0200 Subject: [PATCH 0782/2366] futex: Provide rt_mutex_.*_schedule() equivalents for futex scheduling There is rt_mutex_{pre|post}_schedule() around rt_mutex_wait_proxy_lock() to ensure that sched_submit_work()/ sched_update_worker() is invoked before we schedule out and block on rt_mutex while waiting for it become available. The reason is that blocking on rt_mutex assigns a pi_waiter for the PI chain and sched_submit_work() will also assign a pi_waiter if it blocks on lock but a this point we already have a waiter assigned. We can't skip sched_submit_work() entirely because I/O relies on the fact that I/O queue is flushed while it blocks on a sleeping lock. Therefore sched_submit_work() is moved before we block on the lock. Sleeping lock in this context means mutex or rw_semaphore not spinlock_t on PREEMPT_RT. Because the mutex abstraction on PREEMPT_RT uses the same abstraction as the futex proxy lock, the futex code ended up using rt_mutex_{pre|post}_schedule(), too. Using it is/ was just to keep the task_struct::sched_rt_mutex assertion happy. Futex proxy lock is used only in the syscall context of a task. At this point it never got any I/O that needs to be flushed and it can't be a workqueue that needs to notify that it will be scheduled out. Therefore sched_submit_work() does nothing here. By mistake futex_wait_requeue_pi() -> rt_mutex_wait_proxy_lock() did not get the rt_mutex_{pre|post}_schedule() annotation. This was not noticed because in this callchain the lock is (usually) not contended and so rt_mutex_slowlock_block() does not schedule, triggering the assert. Adding rt_mutex_pre_schedule() here looks wrong (as noted by PeterZ) because at this point there is a pi_waiter recorded and invoking sched_submit_work() with a possible lock contention would be wrong. Add rt_mutex_futex_{pre|post}_schedule() which toggles the sched_rt_mutex assert and does not involve sched_submit_work(). Add asserts here to ensure that sched_submit_work() would do nothing. Use it only in futex proxy lock case which is rt_mutex_wait_proxy_lock(). Remove it from futex_lock_pi(). Fixes: d14f9e930b90 ("locking/rtmutex: Use rt_mutex specific scheduler helpers") Reported-by: Yao Kai Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260901135453.3121948-2-bigeasy@linutronix.de Closes: https://lore.kernel.org/all/20260717084922.4153317-2-yaokai34@huawei.com --- include/linux/sched/rt.h | 2 ++ kernel/futex/pi.c | 16 +++------------- kernel/locking/rtmutex_api.c | 2 ++ kernel/sched/core.c | 16 ++++++++++++++++ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/include/linux/sched/rt.h b/include/linux/sched/rt.h index 4e3338103654..922935cc3383 100644 --- a/include/linux/sched/rt.h +++ b/include/linux/sched/rt.h @@ -52,8 +52,10 @@ static inline bool rt_or_dl_task_policy(struct task_struct *tsk) #ifdef CONFIG_RT_MUTEXES extern void rt_mutex_pre_schedule(void); +extern void rt_mutex_futex_pre_schedule(void); extern void rt_mutex_schedule(void); extern void rt_mutex_post_schedule(void); +extern void rt_mutex_futex_post_schedule(void); /* * Must hold either p->pi_lock or task_rq(p)->lock. diff --git a/kernel/futex/pi.c b/kernel/futex/pi.c index 88788e584ec8..98f1b962e59a 100644 --- a/kernel/futex/pi.c +++ b/kernel/futex/pi.c @@ -1070,17 +1070,11 @@ int futex_lock_pi(u32 __user *uaddr, unsigned int flags, ktime_t *time, int tryl * Caution; releasing @hb in-scope. The hb->lock is still locked * while the reference is dropped. The reference can not be dropped * after the unlock because if a user initiated resize is in progress - * then we might need to wake him. This can not be done after the - * rt_mutex_pre_schedule() invocation. The hb will remain valid because - * the thread, performing resize, will block on hb->lock during - * the requeue. + * then we might need to wake him. The hb will remain valid + * because the thread, performing resize, will block on + * hb->lock during the requeue. */ futex_private_hash_put(no_free_ptr(hbr.fph)); - /* - * Must be done before we enqueue the waiter, here is unfortunately - * under the hb lock, but that *should* work because it does nothing. - */ - rt_mutex_pre_schedule(); rt_mutex_init_waiter(&rt_waiter); @@ -1146,10 +1140,6 @@ int futex_lock_pi(u32 __user *uaddr, unsigned int flags, ktime_t *time, int tryl * the */ futex_q_lockptr_lock(&q); - /* - * Waiter is unqueued. - */ - rt_mutex_post_schedule(); no_block: /* * Fixup the pi_state owner and possibly acquire the lock if we diff --git a/kernel/locking/rtmutex_api.c b/kernel/locking/rtmutex_api.c index 5d48d64725b1..eb18b094473c 100644 --- a/kernel/locking/rtmutex_api.c +++ b/kernel/locking/rtmutex_api.c @@ -423,6 +423,7 @@ int __sched rt_mutex_wait_proxy_lock(struct rt_mutex_base *lock, { int ret; + rt_mutex_futex_pre_schedule(); raw_spin_lock_irq(&lock->wait_lock); /* sleep on the mutex */ set_current_state(TASK_INTERRUPTIBLE); @@ -433,6 +434,7 @@ int __sched rt_mutex_wait_proxy_lock(struct rt_mutex_base *lock, */ fixup_rt_mutex_waiters(lock, true); raw_spin_unlock_irq(&lock->wait_lock); + rt_mutex_futex_post_schedule(); return ret; } diff --git a/kernel/sched/core.c b/kernel/sched/core.c index f78275192036..449ccd871be8 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -7637,6 +7637,17 @@ void rt_mutex_pre_schedule(void) sched_submit_work(current); } +/* + * Used within the futex syscall context, skips sched_submit_work() because none + * its work will be done. Asserts ensure that it is indeed the case. + */ +void rt_mutex_futex_pre_schedule(void) +{ + lockdep_assert(!(current->flags & (PF_WQ_WORKER | PF_IO_WORKER))); + lockdep_assert(!current->plug); + lockdep_assert(!fetch_and_set(current->sched_rt_mutex, 1)); +} + void rt_mutex_schedule(void) { lockdep_assert(current->sched_rt_mutex); @@ -7649,6 +7660,11 @@ void rt_mutex_post_schedule(void) lockdep_assert(fetch_and_set(current->sched_rt_mutex, 0)); } +void rt_mutex_futex_post_schedule(void) +{ + lockdep_assert(fetch_and_set(current->sched_rt_mutex, 0)); +} + /* * rt_mutex_setprio - set the current priority of a task * @p: task to boost From a3b8d46fe401cba3a5c46dea610e6eb3dc15370e Mon Sep 17 00:00:00 2001 From: Yao Kai Date: Tue, 1 Sep 2026 15:54:52 +0200 Subject: [PATCH 0783/2366] futex: Prevent rcuwait use-after-free during requeue PI On PREEMPT_RT, FUTEX_CMP_REQUEUE_PI can trigger a KASAN report (slab-out-of-bounds) in futex_requeue_pi_complete() invocation of rcuwait_wake_up(). The futex_q used by futex_wait_requeue_pi() is allocated on the waiter's stack. An early wakeup can race with a PI requeue as follows: waiter requeue task ------ ------------ futex_wait_requeue_pi() futex_do_wait() schedule() futex_requeue futex_proxy_trylock_atomic() futex_requeue_pi_prepare() Q_REQUEUE_PI_NONE -> Q_REQUEUE_PI_IN_PROGRESS * timeout/ signal wakes waiter * futex_requeue_pi_wakeup_sync() Q_REQUEUE_PI_IN_PROGRESS -> Q_REQUEUE_PI_WAIT requeue_pi_wake_futex futex_requeue_pi_complete() cmpxchg Q_REQUEUE_PI_WAIT -> Q_REQUEUE_PI_LOCKED rcuwait_wait_event() if (atomic_read(&q->requeue_state) != Q_REQUEUE_PI_WAIT) break /* no schedule() */ /* q.pi_state->owner == current */ futex_private_hash_put() /* return from syscall */ rcuwait_wake_up(&q->requeue_wait) /* q is gone */ futex_requeue_pi_complete() publishes Q_REQUEUE_PI_LOCKED before calling rcuwait_wake_up(). The waiter observes this state in rcuwait_wait_event() before invoking schedule() in rcuwait_wait_event(). Here, the waiter is free leave the syscall before requeue task can complete the wake. To address this race skip rcuwait_wake_up() in the Q_REQUEUE_PI_LOCKED case. This state is only published by requeue_pi_wake_futex(), which saves q->task before futex_requeue_pi_complete() and wakes the waiter via wake_up_state(). This wake is intended to wake the waiter from its futex_do_wait() sleep. If the waiter is still sleeping there, it can not get into the Q_REQUEUE_PI_WAIT state (and require this removed wake). Should the waiter be woken up from futex_do_wait() by other means (as in this example) and sleep in futex_requeue_pi_wakeup_sync() then the wake_up_state() from requeue_pi_wake_futex() will wake it, too. Should the waiter task terminate before wake_up_state() had a chance to wake the task then the task pointer does not become invalid because the futex_hash_bucket::lock is held and the task pointer is RCU protected. [bigeasy: Updated comment and commit message] Fixes: 07d91ef510fb1 ("futex: Prevent requeue_pi() lock nesting issue on RT") Signed-off-by: Yao Kai Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Reviewed-by: Sebastian Andrzej Siewior Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260901135453.3121948-3-bigeasy@linutronix.de --- kernel/futex/requeue.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c index 79823ad13683..b3f4a4bccb12 100644 --- a/kernel/futex/requeue.c +++ b/kernel/futex/requeue.c @@ -154,8 +154,16 @@ static inline void futex_requeue_pi_complete(struct futex_q *q, int locked) } while (!atomic_try_cmpxchg(&q->requeue_state, &old, new)); #ifdef CONFIG_PREEMPT_RT - /* If the waiter interleaved with the requeue let it know */ - if (unlikely(old == Q_REQUEUE_PI_WAIT)) + /* + * The waiter in futex_requeue_pi_wakeup_sync() can interleave with the + * wake below: It will assign Q_REQUEUE_PI_IN_PROGRESS and here it will + * be updated to Q_REQUEUE_PI_LOCKED (locked = 1). The rcuwait_wait_event() + * will already read Q_REQUEUE_PI_LOCKED and skip the schedule() invocation, + * leading to an access of futex_q::requeue_wait after the waiter returned. + * In this case only we skip the wake here and rely on following wake in + * requeue_pi_wake_futex() to perform the wake if needed. + */ + if (unlikely(old == Q_REQUEUE_PI_WAIT) && new != Q_REQUEUE_PI_LOCKED) rcuwait_wake_up(&q->requeue_wait); #endif } From 797b13a7de957792c1b4773aa2cc3dab4621fd9c Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Tue, 1 Sep 2026 09:04:48 +0200 Subject: [PATCH 0784/2366] irqdomain: Delete irq_domain_add_linear() 7.3-rc1 is free of calls to irq_domain_add_linear(), so it can be finally deleted. According to Dongliang Mu, the related paragraph in the Chinese docs is now obsolete. So drop it completely. Signed-off-by: Jiri Slaby (SUSE) Signed-off-by: Thomas Gleixner Reviewed-by: Dongliang Mu Reviewed-by: Yanteng Si Link: https://patch.msgid.link/20260901070450.255507-1-jirislaby@kernel.org --- .../zh_CN/core-api/irq/irq-domain.rst | 4 ---- include/linux/irqdomain.h | 18 ------------------ 2 files changed, 22 deletions(-) diff --git a/Documentation/translations/zh_CN/core-api/irq/irq-domain.rst b/Documentation/translations/zh_CN/core-api/irq/irq-domain.rst index aaefeda0e164..7317cf5355c9 100644 --- a/Documentation/translations/zh_CN/core-api/irq/irq-domain.rst +++ b/Documentation/translations/zh_CN/core-api/irq/irq-domain.rst @@ -90,10 +90,6 @@ irq_domain映射的类型 映射的优点是固定时间查找IRQ号,而且irq_descs只分配给在用的IRQ。 缺点是该表 必须尽可能大的hwirq号。 -irq_domain_add_linear()和irq_domain_create_linear()在功能上是等价的, -除了第一个参数不同--前者接受一个Open Firmware特定的 'struct device_node' 而 -后者接受一个更通用的抽象 'struct fwnode_handle' 。 - 大多数驱动应该使用线性映射 树状映射 diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 73c25d40846c..3ba75a4ed3da 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -752,24 +752,6 @@ static inline void msi_device_domain_free_wired(struct irq_domain *domain, unsig } #endif -static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node, - unsigned int size, - const struct irq_domain_ops *ops, - void *host_data) -{ - struct irq_domain_info info = { - .fwnode = of_fwnode_handle(of_node), - .size = size, - .hwirq_max = size, - .ops = ops, - .host_data = host_data, - }; - struct irq_domain *d; - - d = irq_domain_instantiate(&info); - return IS_ERR(d) ? NULL : d; -} - #else /* CONFIG_IRQ_DOMAIN */ static inline void irq_dispose_mapping(unsigned int virq) { } static inline struct irq_domain *irq_find_matching_fwnode(struct fwnode_handle *fwnode, From 5ab54837fce04a1c9923d0bfd3d5de51fdc768b3 Mon Sep 17 00:00:00 2001 From: Jeffin Philip Date: Thu, 3 Sep 2026 13:40:48 +0530 Subject: [PATCH 0785/2366] fs: autofs: fix memory leak in autofs_fill_super() In autofs_fill_super(), we create a new inode using autofs_new_ino(), however, if we fail to create root_inode, (that is, root_inode failure path), we return -ENOMEM without freeing the new inode(ino) that we created causing a memory leak. Fix this by adding autofs_free_ino() to free the inode we created in root_inode failure path before returning ENOMEM. Reported-by: syzbot+df1db6e034b3953e19f5@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=df1db6e034b3953e19f5 Fixes: 66917f85db60 ("autofs: add: new_inode check in autofs_fill_super()") Cc: stable@vger.kernel.org Signed-off-by: Jeffin Philip Link: https://patch.msgid.link/20260903081048.132524-1-jeffinphilip14@gmail.com Signed-off-by: Ian Kent Signed-off-by: Christian Brauner (Amutable) --- fs/autofs/inode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/autofs/inode.c b/fs/autofs/inode.c index c1e210cec436..6b15a3717ba7 100644 --- a/fs/autofs/inode.c +++ b/fs/autofs/inode.c @@ -323,8 +323,10 @@ static int autofs_fill_super(struct super_block *s, struct fs_context *fc) return -ENOMEM; root_inode = autofs_get_inode(s, S_IFDIR | 0755); - if (!root_inode) + if (!root_inode) { + autofs_free_ino(ino); return -ENOMEM; + } root_inode->i_uid = ctx->uid; root_inode->i_gid = ctx->gid; From af72b5946d493cecced27d0951ea37c1d178601e Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Thu, 27 Aug 2026 15:43:02 +1000 Subject: [PATCH 0786/2366] wifi: mac80211: include TIM bitmap control for buffered S1G mcast traffic Currently when building the S1G TIM element, we only build the bitmap control if we have buffered unicast traffic. Since AID 0 sits within the bitmap control if we have buffered multicast traffic with no buffered unicast traffic the bitmap control won't be emitted and dozing stations will be unaware of buffered multicast. To fix, only exclude the bitmap control byte when we don't have both buffered unicast and multicast traffic. Fixes: ee6360945483 ("wifi: mac80211: support block bitmap S1G TIM encoding") Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260827054302.254124-1-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 3a1e2c9e1565..3896c7b2c4e5 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -5089,10 +5089,18 @@ static void ieee80211_beacon_add_tim_pvb(struct ps_data *ps, */ static void ieee80211_s1g_beacon_add_tim_pvb(struct ps_data *ps, struct sk_buff *skb, - bool mcast_traffic) + bool mcast_traffic, + bool ucast_traffic) { int blk; + /* + * if no unicast and multicast traffic don't emit a bitmap control + * or pvb + */ + if (!mcast_traffic && !ucast_traffic) + return; + /* * Emit a bitmap control block with a page slice number of 31 and a * page index of 0 which indicates as per IEEE80211-2024 9.4.2.5.1 @@ -5101,6 +5109,10 @@ static void ieee80211_s1g_beacon_add_tim_pvb(struct ps_data *ps, */ skb_put_u8(skb, mcast_traffic | (31 << 1)); + /* If there's no unicast traffic we don't need to include a PVB. */ + if (!ucast_traffic) + return; + /* Emit an encoded block for each non-zero sub-block */ for (blk = 0; blk < IEEE80211_MAX_SUPPORTED_S1G_TIM_BLOCKS; blk++) { u8 blk_bmap = 0; @@ -5182,25 +5194,16 @@ static void __ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata, ps->dtim_bc_mc = mcast_traffic; - if (have_bits) { - if (s1g) - ieee80211_s1g_beacon_add_tim_pvb(ps, skb, - mcast_traffic); - else - ieee80211_beacon_add_tim_pvb(ps, skb, mcast_traffic); + if (s1g) { + ieee80211_s1g_beacon_add_tim_pvb(ps, skb, mcast_traffic, + have_bits); + } else if (have_bits) { + ieee80211_beacon_add_tim_pvb(ps, skb, mcast_traffic); } else { - /* - * If there is no buffered unicast traffic for an S1G - * interface, we can exclude the bitmap control. This is in - * contrast to other phy types as they do include the bitmap - * control and pvb even when there is no buffered traffic. - */ - if (!s1g) { - /* Bitmap control */ - skb_put_u8(skb, mcast_traffic); - /* Part Virt Bitmap */ - skb_put_u8(skb, 0); - } + /* Bitmap control */ + skb_put_u8(skb, mcast_traffic); + /* Part Virt Bitmap */ + skb_put_u8(skb, 0); } tim->datalen = skb_tail_pointer(skb) - tim->data; From e6031f02269c0f51cf67886d177f02bc300b47cd Mon Sep 17 00:00:00 2001 From: Ivan Pustogarov Date: Thu, 3 Sep 2026 17:26:16 +0200 Subject: [PATCH 0787/2366] wifi: mac80211: avoid out-of-bounds read for empty PREQ elements ieee80211_mesh_preq_size_ok() derives the location of the PREQ bottom fields before checking whether the element contains even the fixed header. ieee80211_mesh_hwmp_preq_get_bottom() reads the flags byte to account for the optional Address Extension field. Consequently, an empty PREQ element causes a one-byte read beyond its declared payload. Move the helper call after both size checks, so the bottom fields are only accessed when they are present. Fixes: 8b40b1d24a60 ("wifi: mac80211: Fix overread in PREQ frame processing") Cc: stable@vger.kernel.org Signed-off-by: Ivan Pustogarov Link: https://patch.msgid.link/20260903152616.1646637-1-ivan@ipust.net Signed-off-by: Johannes Berg --- include/linux/ieee80211-mesh.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/ieee80211-mesh.h b/include/linux/ieee80211-mesh.h index 7eb15834531c..9e548b9173df 100644 --- a/include/linux/ieee80211-mesh.h +++ b/include/linux/ieee80211-mesh.h @@ -361,8 +361,7 @@ ieee80211_mesh_hwmp_perr_get_rcode(const u8 *ie, u8 dst_idx) /* IEEE Std 802.11-2016 9.4.2.113 PREQ element */ static inline bool ieee80211_mesh_preq_size_ok(const u8 *pos, u8 elen) { - struct ieee80211_mesh_hwmp_preq_bottom *preq_elem_bottom = - ieee80211_mesh_hwmp_preq_get_bottom(pos); + struct ieee80211_mesh_hwmp_preq_bottom *preq_elem_bottom; u8 target_count; int needed; @@ -378,6 +377,7 @@ static inline bool ieee80211_mesh_preq_size_ok(const u8 *pos, u8 elen) if (elen < needed) return false; + preq_elem_bottom = ieee80211_mesh_hwmp_preq_get_bottom(pos); target_count = preq_elem_bottom->target_count; /* IEEE Std 802.11-2016 Table 14-10 to 14-16 */ if (target_count < 1) From b5526b780f8b297a76030410b96ba29153afb98f Mon Sep 17 00:00:00 2001 From: Stanislaw Gruszka Date: Thu, 20 Aug 2026 11:30:59 +0200 Subject: [PATCH 0788/2366] wifi: iwlegacy: fix broadcast stations deallocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the error path of __il4965_up(), il_dealloc_bcast_stations() clears only IL_STA_UCODE_ACTIVE, leaving IL_STA_BCAST set. This causes the same broadcast stations to be deallocated again by __il4965_down(). This can occur when RF_KILL is toggled during driver startup. To fix clear the entire 'used' field, since we will not do any other operations on the station. Reported-and-tested-by: Martin-Éric Racine Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221733 Fixes: c2fd34469d16 ("iwl4965: Fix a memory leak in error handling code of __il4965_up") Cc: # 7.1.x: 57aa1718d595 wifi: iwlegacy: replace BUG_ON() with WARN_ON() on num_stations check Cc: # 6.x.x: 57aa1718d595 wifi: iwlegacy: replace BUG_ON() with WARN_ON() on num_stations check Cc: # 5.x.x: 57aa1718d595 wifi: iwlegacy: replace BUG_ON() with WARN_ON() on num_stations check Signed-off-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260820093059.18779-1-stf_xl@wp.pl Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/iwlegacy/common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/iwlegacy/common.c b/drivers/net/wireless/intel/iwlegacy/common.c index 0bb807ff8edf..e5113c6b2d5c 100644 --- a/drivers/net/wireless/intel/iwlegacy/common.c +++ b/drivers/net/wireless/intel/iwlegacy/common.c @@ -2326,7 +2326,7 @@ il_dealloc_bcast_stations(struct il_priv *il) if (!(il->stations[i].used & IL_STA_BCAST)) continue; - il->stations[i].used &= ~IL_STA_UCODE_ACTIVE; + il->stations[i].used = 0; il->num_stations--; if (WARN_ON(il->num_stations < 0)) il->num_stations = 0; From 8a1f3cf89ddcc700e25afe42cfad333059adcc94 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Thu, 20 Aug 2026 20:51:26 +0800 Subject: [PATCH 0789/2366] wifi: wlcore: release runtime PM ref on regdomain config failure wlcore_regdomain_config() gets a runtime PM reference before sending the regulatory-domain command. When wlcore_cmd_regdomain_config_locked() fails, the function queues recovery and returns without dropping that reference. Release the reference after handling the command result so both success and failure paths balance the preceding pm_runtime_resume_and_get(). The recovery worker takes a separate runtime PM reference and cannot release the reference held here. Fixes: fa2648a34e73 ("wlcore: Add support for runtime PM") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260820125126.12757-1-runyu.xiao@seu.edu.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/ti/wlcore/main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c index 5595f7a1fc0c..edf6ca23c6c3 100644 --- a/drivers/net/wireless/ti/wlcore/main.c +++ b/drivers/net/wireless/ti/wlcore/main.c @@ -3724,10 +3724,8 @@ void wlcore_regdomain_config(struct wl1271 *wl) goto out; ret = wlcore_cmd_regdomain_config_locked(wl); - if (ret < 0) { + if (ret < 0) wl12xx_queue_recovery_work(wl); - goto out; - } pm_runtime_put_autosuspend(wl->dev); out: From ba6cb7c0868a412c2eb68e8efd5aa38bfb258a14 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Fri, 7 Aug 2026 11:52:30 +0000 Subject: [PATCH 0790/2366] wifi: wilc1000: fix out-of-bounds read in P2P public action frames wilc_wfi_p2p_rx() and mgmt_tx() start parsing a frame once ieee80211_is_public_action() returns true. That helper only verifies the frame is long enough for the action category field, that is offsetofend(struct ieee80211_mgmt, u.action.category), 25 bytes. Both functions then read the P2P public action header up to oui_subtype at offset 30 and pass "size - ie_offset" to cfg80211_find_vendor_ie(), where ie_offset is offsetof(struct ieee80211_mgmt, u) + sizeof(*d), i.e. 32. A public action frame of 25 to 31 bytes passes the check but is shorter than that 32 byte header, so oui_subtype can be read out of bounds, and because the length is unsigned, "size - ie_offset" underflows to a value close to 4 GiB. cfg80211_find_vendor_ie() takes an unsigned int length, so even the size_t subtraction in mgmt_tx() is truncated to the same value. It then walks far past the buffer searching for a vendor element until it reaches unmapped memory. In the receive path the frame arrives over the air and needs no association, so a nearby unauthenticated device can crash the host while it is in P2P listen. Reject frames shorter than the P2P public action header in both paths before dereferencing it. Fixes: 4fb8b5aa2a11 ("staging: wilc1000: refactor p2p action frames handling API's") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis Link: https://patch.msgid.link/20260807115230.136767-1-ali@iusegentoo.com Signed-off-by: Johannes Berg --- drivers/net/wireless/microchip/wilc1000/cfg80211.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/net/wireless/microchip/wilc1000/cfg80211.c b/drivers/net/wireless/microchip/wilc1000/cfg80211.c index bb2748a19329..9ad21d41e999 100644 --- a/drivers/net/wireless/microchip/wilc1000/cfg80211.c +++ b/drivers/net/wireless/microchip/wilc1000/cfg80211.c @@ -1058,6 +1058,13 @@ void wilc_wfi_p2p_rx(struct wilc_vif *vif, u8 *buff, u32 size) if (!ieee80211_is_public_action((struct ieee80211_hdr *)buff, size)) goto out_rx_mgmt; + /* ieee80211_is_public_action() only validates up to the category + * byte, so reject frames too short for the P2P public action header + * before dereferencing it or computing size - ie_offset. + */ + if (size < ie_offset) + goto out_rx_mgmt; + d = (struct wilc_p2p_pub_act_frame *)(&mgmt->u.action); if (d->oui_subtype != GO_NEG_REQ && d->oui_subtype != GO_NEG_RSP && d->oui_subtype != P2P_INV_REQ && d->oui_subtype != P2P_INV_RSP) @@ -1200,6 +1207,13 @@ static int mgmt_tx(struct wiphy *wiphy, goto out_set_timeout; } + /* ieee80211_is_public_action() only validates up to the category + * byte, so reject frames too short for the P2P public action header + * before dereferencing it or computing len - ie_offset. + */ + if (len < ie_offset) + goto out_set_timeout; + d = (struct wilc_p2p_pub_act_frame *)(&mgmt->u.action); if (d->oui_type != WLAN_OUI_TYPE_WFA_P2P || d->oui_subtype != GO_NEG_CONF) { From f9edf7cf63b96d2b776fca8d258d3c5256e40c8e Mon Sep 17 00:00:00 2001 From: Mariano Baragiola Date: Sun, 9 Aug 2026 09:49:47 -0300 Subject: [PATCH 0791/2366] wifi: virt_wifi: free skb when disconnected When the simulated link is disconnected, virt_wifi_start_xmit() returns NET_XMIT_DROP without freeing the skb. dev_hard_start_xmit() treats this return value as consumed, so every packet sent while disconnected leaks its skb. Free the skb before returning the drop status. Fixes: c7cdba31ed8b ("mac80211-next: rtnetlink wifi simulation device") Signed-off-by: Mariano Baragiola Link: https://patch.msgid.link/20260809124947.3590270-1-mbaragiola@linux.com Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/virt_wifi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/virtual/virt_wifi.c b/drivers/net/wireless/virtual/virt_wifi.c index 2335e45db8b8..b69a4650fba8 100644 --- a/drivers/net/wireless/virtual/virt_wifi.c +++ b/drivers/net/wireless/virtual/virt_wifi.c @@ -434,6 +434,7 @@ static netdev_tx_t virt_wifi_start_xmit(struct sk_buff *skb, priv->tx_packets++; if (!priv->is_connected) { priv->tx_failed++; + dev_kfree_skb_any(skb); return NET_XMIT_DROP; } From bbb9a0ab96d44a64529aafc7a16de460a1712f6a Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Sat, 15 Aug 2026 19:57:24 +0800 Subject: [PATCH 0792/2366] wifi: libertas_tf: fix UAF in lbtf_free_adapter() lbtf_free_adapter() calls lbtf_free_cmd_buffer() to free the command buffers before calling timer_delete_sync() to wait for the command timer callback. If the timer callback (command_timer_fn) is already running when lbtf_free_cmd_buffer() frees the command array, the callback dereferences priv->cur_cmd->cmdbuf which points to freed memory. Swap the order so that timer_delete_sync() runs first, ensuring any in-flight callback has completed before the command buffers are freed. Fixes: 06b16ae53192 ("libertas_tf: main.c, data paths and mac80211 handlers") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Link: https://patch.msgid.link/20260815115724.920628-1-yijiangshan@kylinos.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas_tf/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/libertas_tf/main.c b/drivers/net/wireless/marvell/libertas_tf/main.c index 42be6fa22f9c..411f075b6186 100644 --- a/drivers/net/wireless/marvell/libertas_tf/main.c +++ b/drivers/net/wireless/marvell/libertas_tf/main.c @@ -173,8 +173,8 @@ static int lbtf_init_adapter(struct lbtf_private *priv) static void lbtf_free_adapter(struct lbtf_private *priv) { lbtf_deb_enter(LBTF_DEB_MAIN); - lbtf_free_cmd_buffer(priv); timer_delete_sync(&priv->command_timer); + lbtf_free_cmd_buffer(priv); lbtf_deb_leave(LBTF_DEB_MAIN); } From a3d722190cdef18da4878b5efc27c3c386dda248 Mon Sep 17 00:00:00 2001 From: Peng Hao Date: Fri, 28 Aug 2026 19:15:31 +0800 Subject: [PATCH 0793/2366] wifi: mwifiex: fix IRQ leak using wrong index in MSI-X error path mwifiex_pcie_request_irq() registers each MSI-X vector with a per-index dev_id (&card->msix_ctx[i]). On a request_irq() failure the cleanup loop "for (j = 0; j < i; j++)" frees msix_entries[j].vector but passes the failed index's &card->msix_ctx[i] as the dev_id. free_irq() matches on (irq, dev_id), so it fails to find the action registered with &card->msix_ctx[j]: the already-requested IRQ j is not freed (leaked) and free_irq() warns about freeing a non-existent IRQ. Use &card->msix_ctx[j]. Fixes: 99074fc1e67b ("mwifiex: enable pcie MSIx interrupt mode support") Signed-off-by: Peng Hao Link: https://patch.msgid.link/20260828111531.56723-1-flyingpeng@tencent.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c index a760de191fce..a9425e9a94f4 100644 --- a/drivers/net/wireless/marvell/mwifiex/pcie.c +++ b/drivers/net/wireless/marvell/mwifiex/pcie.c @@ -3068,7 +3068,7 @@ static int mwifiex_pcie_request_irq(struct mwifiex_adapter *adapter) ret); for (j = 0; j < i; j++) free_irq(card->msix_entries[j].vector, - &card->msix_ctx[i]); + &card->msix_ctx[j]); pci_disable_msix(pdev); } else { mwifiex_dbg(adapter, MSG, "MSIx enabled!"); From fa00193eb991f92b007aefe7afb6a7566976dacf Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 20 Aug 2026 14:21:55 +0800 Subject: [PATCH 0794/2366] wifi: mwifiex: prevent authentication frame length truncation mwifiex_cfg80211_authenticate() derives the authentication frame length from req->ie_len and req->auth_data_len, both of type size_t, but stores it in a u16. NL80211_ATTR_AUTH_DATA only has a minimum length policy. Since nla_len is a u16, a single attribute can carry up to 65531 bytes of payload, so the sum can exceed U16_MAX before it is assigned to pkt_len. The truncated pkt_len determines the skb frame area, while the copy length remains req->auth_data_len - 4, resulting in a heap buffer overflow. For example, with auth_data_len equal to 65510 and no IEs, the sum is 65546. It is truncated to 10 and then reduced by four to 6. The driver appends only six bytes to the skb with skb_put(), but then copies 65506 user-provided bytes into the authentication body. Reaching this path requires CAP_NET_ADMIN in the user namespace owning the network namespace, an up station netdev, and a suitable BSS/SAE authentication request. Compute the length in size_t, reject values that cannot be represented by the firmware's u16 frame length field, and only then assign it to pkt_len. Fixes: 36995892c271 ("wifi: mwifiex: add host mlme for client mode") Cc: stable@vger.kernel.org # 6.12+ Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260820062155.3981976-1-lilinmao@kylinos.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/cfg80211.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c index 7a1ba32f1fb3..936939ea9c47 100644 --- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c +++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c @@ -4277,6 +4277,7 @@ mwifiex_cfg80211_authenticate(struct wiphy *wiphy, struct mwifiex_adapter *adapter = priv->adapter; struct sk_buff *skb; u16 pkt_len, auth_alg; + size_t frame_len; int ret; struct mwifiex_ieee80211_mgmt *mgmt; struct mwifiex_txinfo *tx_info; @@ -4349,10 +4350,17 @@ mwifiex_cfg80211_authenticate(struct wiphy *wiphy, mwifiex_cancel_scan(adapter); - pkt_len = (u16)req->ie_len + req->auth_data_len + + frame_len = req->ie_len + req->auth_data_len + MWIFIEX_MGMT_HEADER_LEN + MWIFIEX_AUTH_BODY_LEN; if (req->auth_data_len >= 4) - pkt_len -= 4; + frame_len -= 4; + + if (frame_len > U16_MAX) { + mwifiex_dbg(priv->adapter, ERROR, + "auth frame too long: %zu bytes\n", frame_len); + return -EINVAL; + } + pkt_len = frame_len; skb = dev_alloc_skb(MWIFIEX_MIN_DATA_HEADER_LEN + MWIFIEX_MGMT_FRAME_HEADER_SIZE + From e2de8d5eb2984416affdd9559e55f37c7f1bbf47 Mon Sep 17 00:00:00 2001 From: Bogdan Nicolae Date: Fri, 7 Aug 2026 11:34:18 -0500 Subject: [PATCH 0795/2366] wifi: brcmfmac: cyw: pass PMKID to firmware if present Zero out auth_status on initialization. Otherwise, garbage will leak from the stack to the firmware (when ssid is less than 32 bytes and/or when params->pmkid is set). Then, pass the params->pmkid to the firmware (without it, the firmware caches a garbage PMKID on successful authentication and denies a subsequent association request that includes the PMKID). Fixes: 66f909308a7c ("wifi: brcmfmac: cyw: support external SAE authentication in station mode") Signed-off-by: Bogdan Nicolae Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260807163418.487508-1-bogdan.nicolae@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c index 545eb9aae966..6d5098f6f00f 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cyw/core.c @@ -198,7 +198,7 @@ brcmf_cyw_external_auth(struct wiphy *wiphy, struct net_device *dev, { struct brcmf_if *ifp; struct brcmf_pub *drvr; - struct brcmf_auth_req_status_le auth_status; + struct brcmf_auth_req_status_le auth_status = {}; int ret = 0; brcmf_dbg(TRACE, "Enter\n"); @@ -206,6 +206,9 @@ brcmf_cyw_external_auth(struct wiphy *wiphy, struct net_device *dev, ifp = netdev_priv(dev); drvr = ifp->drvr; if (params->status == WLAN_STATUS_SUCCESS) { + if (params->pmkid) + memcpy(auth_status.pmkid, params->pmkid, + WLAN_PMKID_LEN); auth_status.flags = cpu_to_le16(BRCMF_EXTAUTH_SUCCESS); } else { bphy_err(drvr, "External authentication failed: status=%d\n", From e667aee1c192d67d27c803007bfa9c6e0873e959 Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Fri, 14 Aug 2026 15:47:04 +0200 Subject: [PATCH 0796/2366] wifi: mwifiex: bound the pairwise-cipher OUI walk to the IE length mwifiex_search_oui_in_ie() reads a pairwise-cipher (PTK) count from a beacon/probe-response RSN or WPA information element and then walks that many 4-byte OUIs, comparing each with memcmp(). The count comes straight from the (attacker-supplied) IE and is never checked against the element's own length, and the callers admit the element on element_id alone (has_ieee_hdr() / has_vendor_hdr(), no length check). A crafted RSN/WPA IE with a large pairwise count therefore makes the walk read up to 255 * 4 bytes past the element -- an out-of-bounds read of the kmemdup()'d beacon buffer, reachable from any AP whose beacon/probe response is processed during scan-result parsing. Pass the number of IE bytes available at the OUI list and bound the walk to the element. Keep the length signed and reject a negative value before any unsigned arithmetic, so a small or zero IE length cannot underflow to a large size_t and defeat the bound. Found by 0sec automated security-research tooling (https://0sec.ai). Fixes: 5e6e3a92b9a4 ("wireless: mwifiex: initial commit for Marvell mwifiex driver") Cc: stable@vger.kernel.org Assisted-by: 0sec:multi-model Signed-off-by: Doruk Tan Ozturk Link: https://patch.msgid.link/20260814134704.85902-1-doruk@0sec.ai Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/scan.c | 25 ++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/scan.c b/drivers/net/wireless/marvell/mwifiex/scan.c index 97c0ec3b822e..473f4623ea19 100644 --- a/drivers/net/wireless/marvell/mwifiex/scan.c +++ b/drivers/net/wireless/marvell/mwifiex/scan.c @@ -104,12 +104,24 @@ has_vendor_hdr(struct ieee_types_vendor_specific *ie, u8 key) * a given oui in PTK. */ static u8 -mwifiex_search_oui_in_ie(struct ie_body *iebody, u8 *oui) +mwifiex_search_oui_in_ie(struct ie_body *iebody, u8 *oui, int ie_len) { + const size_t ptk_body_offset = offsetof(struct ie_body, ptk_body); u8 count; + /* ie_len is the number of bytes available at iebody. Keep it signed + * and reject a negative (underflowed) length before the unsigned + * comparisons below, so a small or zero IE length cannot wrap. + */ + if (ie_len < 0 || (size_t)ie_len < ptk_body_offset) + return MWIFIEX_OUI_NOT_PRESENT; + count = iebody->ptk_cnt[0]; + /* Reject an OUI count whose list would run past the element. */ + if (ptk_body_offset + count * sizeof(iebody->ptk_body) > (size_t)ie_len) + return MWIFIEX_OUI_NOT_PRESENT; + /* There could be multiple OUIs for PTK hence 1) Take the length. 2) Check all the OUIs for AES. @@ -143,11 +155,14 @@ mwifiex_is_rsn_oui_present(struct mwifiex_bssdescriptor *bss_desc, u32 cipher) u8 ret = MWIFIEX_OUI_NOT_PRESENT; if (has_ieee_hdr(bss_desc->bcn_rsn_ie, WLAN_EID_RSN)) { + int ie_len = (int)bss_desc->bcn_rsn_ie->ieee_hdr.len - + RSN_GTK_OUI_OFFSET; + iebody = (struct ie_body *) (((u8 *) bss_desc->bcn_rsn_ie->data) + RSN_GTK_OUI_OFFSET); oui = &mwifiex_rsn_oui[cipher][0]; - ret = mwifiex_search_oui_in_ie(iebody, oui); + ret = mwifiex_search_oui_in_ie(iebody, oui, ie_len); if (ret) return ret; } @@ -169,10 +184,14 @@ mwifiex_is_wpa_oui_present(struct mwifiex_bssdescriptor *bss_desc, u32 cipher) u8 ret = MWIFIEX_OUI_NOT_PRESENT; if (has_vendor_hdr(bss_desc->bcn_wpa_ie, WLAN_EID_VENDOR_SPECIFIC)) { + int ie_len = (int)bss_desc->bcn_wpa_ie->vend_hdr.len - + (int)sizeof(bss_desc->bcn_wpa_ie->vend_hdr.oui) - + WPA_GTK_OUI_OFFSET; + iebody = (struct ie_body *)((u8 *)bss_desc->bcn_wpa_ie->data + WPA_GTK_OUI_OFFSET); oui = &mwifiex_wpa_oui[cipher][0]; - ret = mwifiex_search_oui_in_ie(iebody, oui); + ret = mwifiex_search_oui_in_ie(iebody, oui, ie_len); if (ret) return ret; } From 1c25bfad93e69ce13f744a2fb919f02ea396a985 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Tue, 25 Aug 2026 19:25:23 +0800 Subject: [PATCH 0797/2366] wifi: mwifiex: validate action frame fixed fields mwifiex_process_mgmt_packet() accepts an rx_pkt_length as small as a four-address struct ieee80211_hdr plus the two-byte firmware length prefix. After stripping the prefix, mwifiex_parse_mgmt_packet() can receive a frame equal to sizeof(struct ieee80211_hdr). For action frames, the parser reads the category byte immediately after that header and, for a public action frame, reads the following action code byte without verifying that either field is present. A truncated frame can therefore make the parser consume up to two bytes past the firmware-declared frame length. If those bytes look like a TDLS discovery response, the malformed frame can spuriously update peer signal state. Require the category and public action-code fields before reading them. Use sizeof(*ieee_hdr) so the checks and field accesses directly match the firmware four-address layout being parsed before address4 is removed. Suggested-by: Johannes Berg Suggested-by: Brian Norris Fixes: 72e5aa8d2a6d ("mwifiex: support for parsing TDLS discovery frames") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/66f148d83eb9f0970b9abbccc85d1b61244e54ad.camel@sipsolutions.net/ Link: https://lore.kernel.org/all/20260708195911.84365-8-enderaoelyther@gmail.com/ Link: https://lore.kernel.org/all/20260723011013.76968-1-enderaoelyther@gmail.com/ Link: https://lore.kernel.org/all/20260723202257.688-1-enderaoelyther@gmail.com/ Link: https://lore.kernel.org/all/anuWyiPQja6_5vly@google.com/ Assisted-by: Codex:gpt-5 Assisted-by: Kimi:K3 Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260825112523.95774-1-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/util.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/util.c b/drivers/net/wireless/marvell/mwifiex/util.c index 7d3631d21223..71305efb77ac 100644 --- a/drivers/net/wireless/marvell/mwifiex/util.c +++ b/drivers/net/wireless/marvell/mwifiex/util.c @@ -317,10 +317,16 @@ mwifiex_parse_mgmt_packet(struct mwifiex_private *priv, u8 *payload, u16 len, switch (stype) { case IEEE80211_STYPE_ACTION: - category = *(payload + sizeof(struct ieee80211_hdr)); + if (len < sizeof(*ieee_hdr) + 1) + return -1; + + category = *(payload + sizeof(*ieee_hdr)); switch (category) { case WLAN_CATEGORY_PUBLIC: - action_code = *(payload + sizeof(struct ieee80211_hdr) + if (len < sizeof(*ieee_hdr) + 2) + return -1; + + action_code = *(payload + sizeof(*ieee_hdr) + 1); if (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) { addr2 = ieee_hdr->addr2; From 3687d7d48070838cc2953431b3a27717cab0aaf6 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 15 Aug 2026 21:52:27 +0800 Subject: [PATCH 0798/2366] wifi: mwifiex: validate scan response extents mwifiex_ret_802_11_scan() subtracts the fixed response fields and the firmware-provided BSS length from resp->size without first proving that either extent fits. A short response or oversized BSS length can therefore underflow tlv_buf_size and make the TLV parser walk beyond the command response. Compute the fixed extent from the selected normal or background scan response. Validate that the fixed fields and BSS data fit before deriving the TLV extent and entering the parser. Fixes: 5e6e3a92b9a4 ("wireless: mwifiex: initial commit for Marvell mwifiex driver") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5 Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260815135227.50392-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/scan.c | 29 ++++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/scan.c b/drivers/net/wireless/marvell/mwifiex/scan.c index 473f4623ea19..bdd4b8465863 100644 --- a/drivers/net/wireless/marvell/mwifiex/scan.c +++ b/drivers/net/wireless/marvell/mwifiex/scan.c @@ -2115,6 +2115,7 @@ int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, u32 bytes_left; u32 idx; u32 tlv_buf_size; + size_t fixed_size; struct mwifiex_ie_types_chan_band_list_param_set *chan_band_tlv; struct chan_band_param_set *chan_band; u8 is_bgscan_resp; @@ -2130,6 +2131,14 @@ int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, else scan_rsp = &resp->params.scan_resp; + scan_resp_size = le16_to_cpu(resp->size); + fixed_size = scan_rsp->bss_desc_and_tlv_buffer - (u8 *)resp; + if (scan_resp_size < fixed_size) { + mwifiex_dbg(adapter, ERROR, + "SCAN_RESP: response is too short\n"); + ret = -1; + goto check_next_scan; + } if (scan_rsp->number_of_sets > MWIFIEX_MAX_AP) { mwifiex_dbg(adapter, ERROR, @@ -2147,8 +2156,6 @@ int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, "info: SCAN_RESP: bss_descript_size %d\n", bytes_left); - scan_resp_size = le16_to_cpu(resp->size); - mwifiex_dbg(adapter, INFO, "info: SCAN_RESP: returned %d APs before parsing\n", scan_rsp->number_of_sets); @@ -2156,15 +2163,17 @@ int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, bss_info = scan_rsp->bss_desc_and_tlv_buffer; /* - * The size of the TLV buffer is equal to the entire command response - * size (scan_resp_size) minus the fixed fields (sizeof()'s), the - * BSS Descriptions (bss_descript_size as bytesLef) and the command - * response header (S_DS_GEN) + * The TLV buffer follows the command-specific fixed fields and the BSS + * descriptions. Background-scan responses have an additional fixed + * field before scan_rsp, which is included in fixed_size. */ - tlv_buf_size = scan_resp_size - (bytes_left - + sizeof(scan_rsp->bss_descript_size) - + sizeof(scan_rsp->number_of_sets) - + S_DS_GEN); + if (bytes_left > scan_resp_size - fixed_size) { + mwifiex_dbg(adapter, ERROR, + "SCAN_RESP: BSS data exceeds response\n"); + ret = -1; + goto check_next_scan; + } + tlv_buf_size = scan_resp_size - fixed_size - bytes_left; tlv_data = (struct mwifiex_ie_types_data *) (scan_rsp-> bss_desc_and_tlv_buffer + From 5ce5721e8cbe3e80db8f43851cc2a2a92485ef4b Mon Sep 17 00:00:00 2001 From: Shmulik Cohen Date: Wed, 12 Aug 2026 22:04:10 +0300 Subject: [PATCH 0799/2366] wifi: libipw: reject too-short beacon and probe responses libipw_process_probe_response() and the libipw_network_init() call it makes assume the frame contains the full 36-byte beacon and probe response prefix, but the ipw2100 and ipw2200 receive paths only establish that a management frame carries the generic 24-byte three-address header. libipw_network_init() then computes the information element length as stats->len - sizeof(*beacon) stats->len is a u16 and sizeof() has type size_t, so the subtraction is evaluated as size_t and wraps instead of going negative. Truncating that to the u16 length parameter of libipw_parse_info_param() yields 65524 for a 24-byte beacon, and the parser then walks the receive buffer as if it held almost 64 KiB of information elements, reading past the allocation. Reject the frame before any fixed field is touched. Found by an AI-assisted review of length arithmetic in management frame parsers. Verified with a KUnit case under Generic KASAN on arm64 under QEMU; I do not have the hardware, so it is not tested on a real device. Fixes: b453872c35cf ("[NET] ieee80211 subsystem") Assisted-by: Claude:claude-opus-5 Signed-off-by: Shmulik Cohen Link: https://patch.msgid.link/20260812190412.18333-2-anuk909@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/libipw_rx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c index c8841f9b9ad9..2661dac6985e 100644 --- a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c +++ b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c @@ -1421,6 +1421,9 @@ static void libipw_process_probe_response(struct libipw_device #endif unsigned long flags; + if (stats->len < sizeof(*beacon)) + return; + LIBIPW_DEBUG_SCAN("'%*pE' (%pM): %c%c%c%c %c%c%c%c-%c%c%c%c %c%c%c%c\n", info_element->len, info_element->data, beacon->header.addr3, From adb7118b7d2cfd7e8213c17d7d2829f353017754 Mon Sep 17 00:00:00 2001 From: Shmulik Cohen Date: Wed, 12 Aug 2026 22:04:11 +0300 Subject: [PATCH 0800/2366] wifi: libipw: reject too-short association responses libipw_handle_assoc_resp() reads the capability, status and aid fields of the 30-byte association response prefix and then computes the information element length as stats->len - sizeof(*frame) stats->len is a u16 and sizeof() has type size_t, so the subtraction is evaluated as size_t and wraps instead of going negative. Truncating that to the u16 length parameter of libipw_parse_info_param() turns a frame shorter than the fixed fields into a length near 64 KiB, and the parser then reads past the receive buffer. Both the ipw2100 and ipw2200 management receive paths reach this function having established only that the frame carries the generic 24-byte three-address header. Reject the frame before any fixed field is touched. Found by an AI-assisted review of length arithmetic in management frame parsers. Verified with a KUnit case under Generic KASAN on arm64 under QEMU; I do not have the hardware, so it is not tested on a real device. Fixes: 9e8571affd1c ("[PATCH] ieee80211: Add QoS (WME) support to the ieee80211 subsystem") Assisted-by: Claude:claude-opus-5 Signed-off-by: Shmulik Cohen Link: https://patch.msgid.link/20260812190412.18333-3-anuk909@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/libipw_rx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c index 2661dac6985e..424349a6935e 100644 --- a/drivers/net/wireless/intel/ipw2x00/libipw_rx.c +++ b/drivers/net/wireless/intel/ipw2x00/libipw_rx.c @@ -1209,6 +1209,9 @@ static int libipw_handle_assoc_resp(struct libipw_device *ieee, struct libipw_as struct libipw_network *network = &network_resp; struct net_device *dev = ieee->dev; + if (stats->len < sizeof(*frame)) + return 1; + network->flags = 0; network->qos_data.active = 0; network->qos_data.supported = 0; From c46cfaf8db42de0806076139fb40744d23041377 Mon Sep 17 00:00:00 2001 From: Shmulik Cohen Date: Wed, 12 Aug 2026 22:04:12 +0300 Subject: [PATCH 0801/2366] wifi: ipw2x00: bound management frame length to the receive buffer Both management receive paths establish a lower bound on the frame length and no upper bound, even though the length originates from the device. ipw2100_corruption_check() returns 0 without inspecting frame_size for management frames, and __ipw2100_rx_process() only rejects a frame smaller than the three-address header, so any reported size up to the u32 limit reaches libipw_rx_mgt() against a receive allocation of IPW_RX_NIC_BUFFER_LENGTH bytes. Check frame_size itself rather than stats.len, which is a u16: a size of 65566 truncates to 30 on assignment and would pass a check made afterwards. ipw_rx() likewise only rejects a frame shorter than the header length. Bound it against the DMA mapped receive buffer. The size passed to alloc_skb() is rounded up by the allocator, so skb_tailroom() can exceed IPW_RX_BUF_SIZE and is not a usable bound here; the existing uses of that idiom in the data paths are too permissive for the same reason. libipw then hands the remainder to libipw_parse_info_param(), which walks information elements for as long as the length allows, so an over-long reported length reads past the receive buffer without any wraparound being involved. The length is device-reported, so per Documentation/process/threat-model.rst this is a robustness fix rather than a vulnerability. Found by an AI-assisted review of length arithmetic in management frame parsers. Compile-tested only for these two hunks; I do not have the hardware, so they are not tested on a real device. Assisted-by: Claude:claude-opus-5 Signed-off-by: Shmulik Cohen Link: https://patch.msgid.link/20260812190412.18333-4-anuk909@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/ipw2x00/ipw2100.c | 4 +++- drivers/net/wireless/intel/ipw2x00/ipw2200.c | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intel/ipw2x00/ipw2100.c b/drivers/net/wireless/intel/ipw2x00/ipw2100.c index 2b8a23865bfb..43b4e432956b 100644 --- a/drivers/net/wireless/intel/ipw2x00/ipw2100.c +++ b/drivers/net/wireless/intel/ipw2x00/ipw2100.c @@ -2712,7 +2712,9 @@ static void __ipw2100_rx_process(struct ipw2100_priv *priv) break; } #endif - if (stats.len < sizeof(struct libipw_hdr_3addr)) + if (sq->drv[i].frame_size < + sizeof(struct libipw_hdr_3addr) || + sq->drv[i].frame_size > IPW_RX_NIC_BUFFER_LENGTH) break; switch (WLAN_FC_GET_TYPE(le16_to_cpu(u->rx_data.header.frame_ctl))) { case IEEE80211_FTYPE_MGMT: diff --git a/drivers/net/wireless/intel/ipw2x00/ipw2200.c b/drivers/net/wireless/intel/ipw2x00/ipw2200.c index 4bc9bb406e8e..8249d493ee22 100644 --- a/drivers/net/wireless/intel/ipw2x00/ipw2200.c +++ b/drivers/net/wireless/intel/ipw2x00/ipw2200.c @@ -8322,6 +8322,15 @@ static void ipw_rx(struct ipw_priv *priv) break; } + if (unlikely(le16_to_cpu(pkt->u.frame.length) > + IPW_RX_BUF_SIZE - + IPW_RX_FRAME_SIZE)) { + IPW_DEBUG_DROP("Received oversized packet. Dropping.\n"); + priv->net_dev->stats.rx_errors++; + priv->wstats.discard.misc++; + break; + } + switch (WLAN_FC_GET_TYPE (le16_to_cpu(header->frame_ctl))) { From ce858fa6b8a214dee5adb82358885fa024cdd887 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Mon, 31 Aug 2026 02:42:12 +0800 Subject: [PATCH 0802/2366] wifi: p54: validate curve data length in the calibration curve converters p54_convert_rev0() and p54_convert_rev1() read calibration curve data from the device-supplied EEPROM entry using channel and points-per-channel counts taken verbatim from that same entry, so an entry that declares more data than it carries drives an out-of-bounds read past the EEPROM buffer (verified with a KASAN reproducer of the conversion loop). The sibling converters p54_convert_output_limits() and p54_convert_db() already validate their counts against the entry length; this path was missed. Reject the entry when the counts do not fit in the entry data. Fixes: eff1a59c48e3 ("[P54]: add mac80211-based driver for prism54 softmac hardware") Cc: stable@vger.kernel.org Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Link: https://patch.msgid.link/20260831-p54-pda-validation-v2-1-dae566b388c8@cherr.cc Signed-off-by: Johannes Berg --- drivers/net/wireless/intersil/p54/eeprom.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/intersil/p54/eeprom.c b/drivers/net/wireless/intersil/p54/eeprom.c index 95580921d933..0dc848d77c5e 100644 --- a/drivers/net/wireless/intersil/p54/eeprom.c +++ b/drivers/net/wireless/intersil/p54/eeprom.c @@ -414,17 +414,22 @@ static int p54_generate_channel_lists(struct ieee80211_hw *dev) } static int p54_convert_rev0(struct ieee80211_hw *dev, - struct pda_pa_curve_data *curve_data) + struct pda_pa_curve_data *curve_data, size_t len) { struct p54_common *priv = dev->priv; struct p54_pa_curve_data_sample *dst; struct pda_pa_curve_data_sample_rev0 *src; + size_t needed = curve_data->channels * + (sizeof(*src) * curve_data->points_per_channel + 2); size_t cd_len = sizeof(*curve_data) + (curve_data->points_per_channel*sizeof(*dst) + 2) * curve_data->channels; unsigned int i, j; void *source, *target; + if (len < sizeof(*curve_data) + needed) + return -EINVAL; + priv->curve_data = kmalloc(sizeof(*priv->curve_data) + cd_len, GFP_KERNEL); if (!priv->curve_data) @@ -466,17 +471,22 @@ static int p54_convert_rev0(struct ieee80211_hw *dev, } static int p54_convert_rev1(struct ieee80211_hw *dev, - struct pda_pa_curve_data *curve_data) + struct pda_pa_curve_data *curve_data, size_t len) { struct p54_common *priv = dev->priv; struct p54_pa_curve_data_sample *dst; struct pda_pa_curve_data_sample_rev1 *src; + size_t needed = curve_data->channels * + (sizeof(*src) * curve_data->points_per_channel + 3); size_t cd_len = sizeof(*curve_data) + (curve_data->points_per_channel*sizeof(*dst) + 2) * curve_data->channels; unsigned int i, j; void *source, *target; + if (len < sizeof(*curve_data) + needed) + return -EINVAL; + priv->curve_data = kzalloc(cd_len + sizeof(*priv->curve_data), GFP_KERNEL); if (!priv->curve_data) @@ -763,6 +773,7 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) case PDR_PRISM_PA_CAL_CURVE_DATA: { struct pda_pa_curve_data *curve_data = (struct pda_pa_curve_data *)entry->data; + if (data_len < sizeof(*curve_data)) { err = -EINVAL; goto err; @@ -770,10 +781,10 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) switch (curve_data->cal_method_rev) { case 0: - err = p54_convert_rev0(dev, curve_data); + err = p54_convert_rev0(dev, curve_data, data_len); break; case 1: - err = p54_convert_rev1(dev, curve_data); + err = p54_convert_rev1(dev, curve_data, data_len); break; default: wiphy_err(dev->wiphy, From d8efd84f49379ed28624098821f80e992657d935 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Mon, 31 Aug 2026 02:42:13 +0800 Subject: [PATCH 0803/2366] wifi: p54: require a full exp_if record in PDR_INTERFACE_LIST The PDR_INTERFACE_LIST loop only checks that the record start is within the entry before reading an entire struct exp_if from it. A truncated trailing record makes the if_id/variant reads cross the entry boundary into the heap beyond the EEPROM buffer (verified with a KASAN reproducer of the loop). The variant also feeds the synth front-end selection, so this is not only a leak. Advance only while a full record still fits in the entry. Fixes: eff1a59c48e3 ("[P54]: add mac80211-based driver for prism54 softmac hardware") Cc: stable@vger.kernel.org Acked-by: Christian Lamparter Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Link: https://patch.msgid.link/20260831-p54-pda-validation-v2-2-dae566b388c8@cherr.cc Signed-off-by: Johannes Berg --- drivers/net/wireless/intersil/p54/eeprom.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/intersil/p54/eeprom.c b/drivers/net/wireless/intersil/p54/eeprom.c index 0dc848d77c5e..0475222d54fc 100644 --- a/drivers/net/wireless/intersil/p54/eeprom.c +++ b/drivers/net/wireless/intersil/p54/eeprom.c @@ -812,7 +812,8 @@ int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) break; case PDR_INTERFACE_LIST: tmp = entry->data; - while ((u8 *)tmp < entry->data + data_len) { + while ((u8 *)tmp + sizeof(struct exp_if) <= + entry->data + data_len) { struct exp_if *exp_if = tmp; if (exp_if->if_id == cpu_to_le16(IF_ID_ISL39000)) synth = le16_to_cpu(exp_if->variant); From 6feadbecdae60a6324c967f3b1493741083793a3 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 6 Aug 2026 13:02:33 +0000 Subject: [PATCH 0804/2366] mmc: core: Cancel SDIO IRQ work before freeing host A host controller that uses sdio_signal_irq() schedules host->sdio_irq_work from its interrupt handler. That work is only cancelled on the suspend path (mmc_sdio_suspend()), not on the remove/free path, so a worker armed just before the controller freed its IRQ can run after mmc_host_classdev_release() has freed the host and dereference it through container_of(). Cancel host->sdio_irq_work in mmc_free_host(), like the existing host->detect drain added by commit 1036f69e2513 ("mmc: core: Cancel delayed work before releasing host"). This issue was found by an in-house static analysis tool. Fixes: 682696605c70 ("mmc: sdio: Add API to manage SDIO IRQs from a workqueue") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Ulf Hansson --- drivers/mmc/core/host.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index b7ce3137d452..828078b3f962 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -698,6 +698,7 @@ EXPORT_SYMBOL(mmc_remove_host); void mmc_free_host(struct mmc_host *host) { cancel_delayed_work_sync(&host->detect); + cancel_work_sync(&host->sdio_irq_work); mmc_pwrseq_free(host); put_device(&host->class_dev); } From 2b19cf3e50cddaff07b657dae1a8f30f06032852 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 14 Aug 2026 08:25:50 +0000 Subject: [PATCH 0805/2366] mmc: mmci: Fix use-after-free in busy-timeout work ux500_busy_complete() can queue ux500_busy_timeout_work for an R1b command, but mmci_remove() never cancels it. The work can subsequently dereference the devm-allocated mmci_host after it has been released. Mask the controller interrupts and disable the delayed work during removal. This drains any queued instance and stops an IRQ handler that is still in progress from queueing the work again once it has been disabled. This issue was found by an in-house static analysis tool. Fixes: b1a665932dc2 ("mmc: mmci: Add support for SW busy-end timeouts") Cc: stable@vger.kernel.org # v6.10+ Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Reviewed-by: Linus Walleij Signed-off-by: Ulf Hansson --- drivers/mmc/host/mmci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index e500051bd572..416bdb184ed4 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -2511,6 +2511,9 @@ static void mmci_remove(struct amba_device *dev) writel(0, host->base + MMCICOMMAND); writel(0, host->base + MMCIDATACTRL); + if (variant->busy_detect) + disable_delayed_work_sync(&host->ux500_busy_timeout_work); + mmci_dma_release(host); clk_disable_unprepare(host->clk); } From d3a421c82412344022982d5b91ba23194a0a6f29 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 7 Aug 2026 03:26:54 +0000 Subject: [PATCH 0806/2366] mmc: mxcmmc: cancel data work and watchdog on remove mxcmci_remove() frees the host through the devm tail, but neither it nor mmc_remove_host() drains the driver's own asynchronous state. host->watchdog, a 10 s timer armed on the DMA path in mxcmci_setup_data(), is deleted only by the DMA- and IRQ-complete paths, which the remove path does not explicitly drain; it can therefore fire after the host is freed and dereference it in mxcmci_watchdog(). host->datawork, armed from the IRQ handler on the PIO path, is not cancelled by the remove path either. Free the devm-registered IRQ, then cancel datawork and delete the watchdog in mxcmci_remove(), before dma_release_channel(). Freeing the IRQ first keeps a trailing handler from re-arming datawork between the cancel and the host free. Both callbacks are non-self-rearming. This issue was found by an in-house static analysis tool. Fixes: f6ad0a481342 ("mmc: mxcmmc: fix bug that may block a data transfer forever") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Ulf Hansson --- drivers/mmc/host/mxcmmc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mmc/host/mxcmmc.c b/drivers/mmc/host/mxcmmc.c index c405cfb8b269..097498a3f8ff 100644 --- a/drivers/mmc/host/mxcmmc.c +++ b/drivers/mmc/host/mxcmmc.c @@ -1173,6 +1173,10 @@ static void mxcmci_remove(struct platform_device *pdev) mmc_remove_host(mmc); + devm_free_irq(&pdev->dev, platform_get_irq(pdev, 0), host); + cancel_work_sync(&host->datawork); + timer_delete_sync(&host->watchdog); + if (host->pdata && host->pdata->exit) host->pdata->exit(&pdev->dev, mmc); From 5d132990475f02cfa1debe03d50b479432864ebd Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 14 Aug 2026 08:23:54 +0000 Subject: [PATCH 0807/2366] mmc: hsq: Fix use-after-free in retry work mmc_hsq_pump_requests() queues retry_work when request_atomic() returns -EBUSY; today sdhci-sprd is the only consumer that implements request_atomic(). The work is embedded in a devm-allocated mmc_hsq, but is never cancelled during driver removal. Work still pending at unbind can therefore run after the devm allocation has been released and dereference hsq->mmc and hsq->mrq. Use devm_work_autocancel() to cancel and drain retry_work before the devm allocation is released. By the time devres cleanup begins, mmc_remove_host() has already stopped the host, so no new requests can arm the work. This issue was found by an in-house static analysis tool. Fixes: 6db96e5810e0 ("mmc: host: Introduce the request_atomic() for the host") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Ulf Hansson --- drivers/mmc/host/mmc_hsq.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/mmc_hsq.c b/drivers/mmc/host/mmc_hsq.c index 79836705c176..57e172bd3487 100644 --- a/drivers/mmc/host/mmc_hsq.c +++ b/drivers/mmc/host/mmc_hsq.c @@ -7,6 +7,7 @@ * Author: Baolin Wang */ +#include #include #include #include @@ -345,6 +346,7 @@ static const struct mmc_cqe_ops mmc_hsq_ops = { int mmc_hsq_init(struct mmc_hsq *hsq, struct mmc_host *mmc) { + int ret; int i; hsq->num_slots = HSQ_NUM_SLOTS; hsq->next_tag = HSQ_INVALID_TAG; @@ -363,7 +365,11 @@ int mmc_hsq_init(struct mmc_hsq *hsq, struct mmc_host *mmc) for (i = 0; i < HSQ_NUM_SLOTS; i++) hsq->tag_slot[i] = HSQ_INVALID_TAG; - INIT_WORK(&hsq->retry_work, mmc_hsq_retry_handler); + ret = devm_work_autocancel(mmc_dev(mmc), &hsq->retry_work, + mmc_hsq_retry_handler); + if (ret) + return ret; + spin_lock_init(&hsq->lock); init_waitqueue_head(&hsq->wait_queue); From ac53977611428db3bc0b4ac0225e19c3e08ae50b Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 31 Aug 2026 14:17:15 -0700 Subject: [PATCH 0808/2366] crypto: x86/aria - add missing vzeroupper in AVX2 code Since the AVX2 optimized ARIA code uses YMM registers, execute vzeroupper before returning from it. This is needed to avoid degrading the performance of any later SSE code that may happen to be executed. Fixes: 37d8d3ae7a58 ("crypto: x86/aria - implement aria-avx2") Cc: stable@vger.kernel.org Cc: Taehee Yoo Signed-off-by: Eric Biggers Signed-off-by: Herbert Xu --- arch/x86/crypto/aria-aesni-avx2-asm_64.S | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/x86/crypto/aria-aesni-avx2-asm_64.S b/arch/x86/crypto/aria-aesni-avx2-asm_64.S index ed53d4f46bd7..fda8cb8a99a8 100644 --- a/arch/x86/crypto/aria-aesni-avx2-asm_64.S +++ b/arch/x86/crypto/aria-aesni-avx2-asm_64.S @@ -982,6 +982,7 @@ SYM_TYPED_FUNC_START(aria_aesni_avx2_encrypt_32way) %ymm8, %ymm9, %ymm10, %ymm11, %ymm12, %ymm13, %ymm14, %ymm15, %rax); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_aesni_avx2_encrypt_32way) @@ -1007,6 +1008,7 @@ SYM_TYPED_FUNC_START(aria_aesni_avx2_decrypt_32way) %ymm8, %ymm9, %ymm10, %ymm11, %ymm12, %ymm13, %ymm14, %ymm15, %rax); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_aesni_avx2_decrypt_32way) @@ -1209,6 +1211,7 @@ SYM_TYPED_FUNC_START(aria_aesni_avx2_ctr_crypt_32way) %ymm8, %ymm9, %ymm10, %ymm11, %ymm12, %ymm13, %ymm14, %ymm15, %r10); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_aesni_avx2_ctr_crypt_32way) @@ -1359,6 +1362,7 @@ SYM_TYPED_FUNC_START(aria_aesni_avx2_gfni_encrypt_32way) %ymm8, %ymm9, %ymm10, %ymm11, %ymm12, %ymm13, %ymm14, %ymm15, %rax); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_aesni_avx2_gfni_encrypt_32way) @@ -1384,6 +1388,7 @@ SYM_TYPED_FUNC_START(aria_aesni_avx2_gfni_decrypt_32way) %ymm8, %ymm9, %ymm10, %ymm11, %ymm12, %ymm13, %ymm14, %ymm15, %rax); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_aesni_avx2_gfni_decrypt_32way) @@ -1428,6 +1433,7 @@ SYM_TYPED_FUNC_START(aria_aesni_avx2_gfni_ctr_crypt_32way) %ymm8, %ymm9, %ymm10, %ymm11, %ymm12, %ymm13, %ymm14, %ymm15, %r10); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_aesni_avx2_gfni_ctr_crypt_32way) From 60892a384aa1e65d0e703e1c513417bdf0c80777 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 31 Aug 2026 14:18:12 -0700 Subject: [PATCH 0809/2366] crypto: x86/aria - add missing vzeroupper in AVX-512 code Since the AVX-512 optimized ARIA code uses ZMM registers, execute vzeroupper before returning from it. This is needed to avoid degrading the performance of any later SSE code that may happen to be executed. Fixes: c970d42001f2 ("crypto: x86/aria - implement aria-avx512") Cc: stable@vger.kernel.org Cc: Taehee Yoo Signed-off-by: Eric Biggers Signed-off-by: Herbert Xu --- arch/x86/crypto/aria-gfni-avx512-asm_64.S | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/x86/crypto/aria-gfni-avx512-asm_64.S b/arch/x86/crypto/aria-gfni-avx512-asm_64.S index 860887e5d02e..ca83eb126e06 100644 --- a/arch/x86/crypto/aria-gfni-avx512-asm_64.S +++ b/arch/x86/crypto/aria-gfni-avx512-asm_64.S @@ -800,6 +800,7 @@ SYM_TYPED_FUNC_START(aria_gfni_avx512_encrypt_64way) %zmm9, %zmm8, %zmm11, %zmm10, %zmm12, %zmm13, %zmm14, %zmm15, %rax); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_gfni_avx512_encrypt_64way) @@ -825,6 +826,7 @@ SYM_TYPED_FUNC_START(aria_gfni_avx512_decrypt_64way) %zmm9, %zmm8, %zmm11, %zmm10, %zmm12, %zmm13, %zmm14, %zmm15, %rax); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_gfni_avx512_decrypt_64way) @@ -966,6 +968,7 @@ SYM_TYPED_FUNC_START(aria_gfni_avx512_ctr_crypt_64way) %zmm9, %zmm8, %zmm11, %zmm10, %zmm12, %zmm13, %zmm14, %zmm15, %r10); + vzeroupper; FRAME_END RET; SYM_FUNC_END(aria_gfni_avx512_ctr_crypt_64way) From ff894dced1a7ad7523f9c65dbdb53d02474cca0f Mon Sep 17 00:00:00 2001 From: "Diogo Ivo (Schneider Electric)" Date: Fri, 7 Aug 2026 13:06:57 +0200 Subject: [PATCH 0810/2366] mmc: sdhci_am654: Move tuning_loop to local variable The tuning_loop field in struct sdhci_am654_data is only used within sdhci_am654_platform_execute_tuning() as a loop counter that is initialized to 0 in sdhci_am654_init(). Since it shouldn't persist across function calls, otherwise every failure expends its "budget", move it to a local variable and remove the struct field along with the now-unnecessary initialization. Signed-off-by: Diogo Ivo (Schneider Electric) Reviewed-by: Judith Mendez Acked-by: Adrian Hunter Fixes: de31f6ab68a3 ("mmc: sdhci_am654: Reset Command and Data line after tuning") Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 2a27db2f558b..4b74a4115509 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -151,7 +151,6 @@ struct sdhci_am654_data { u32 flags; u32 quirks; bool dll_enable; - u32 tuning_loop; #define SDHCI_AM654_QUIRK_FORCE_CDTEST BIT(0) #define SDHCI_AM654_QUIRK_SUPPRESS_V1P8_ENA BIT(1) @@ -576,13 +575,14 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host); unsigned char timing = host->mmc->ios.timing; struct device *dev = mmc_dev(host->mmc); + unsigned int tuning_loop = 0; int itapdly; do { itapdly = sdhci_am654_do_tuning(host, opcode); if (itapdly >= 0) break; - } while (++sdhci_am654->tuning_loop < RETRY_TUNING_MAX); + } while (++tuning_loop < RETRY_TUNING_MAX); if (itapdly < 0) { dev_err(dev, "Failed to find itapdly, fail tuning\n"); @@ -806,9 +806,6 @@ static int sdhci_am654_init(struct sdhci_host *host) regmap_update_bits(sdhci_am654->base, CTL_CFG_3, TUNINGFORSDR50_MASK, TUNINGFORSDR50_MASK); - /* Use to re-execute tuning */ - sdhci_am654->tuning_loop = 0; - ret = sdhci_setup_host(host); if (ret) return ret; From 7197d9107d9545730153b82ea5a411c5208b443f Mon Sep 17 00:00:00 2001 From: "Diogo Ivo (Schneider Electric)" Date: Fri, 7 Aug 2026 13:06:58 +0200 Subject: [PATCH 0811/2366] mmc: sdhci_am654: Reset command and data lines on failed tuning The CMD/DATA reset after tuning should be performed regardless of whether tuning succeeded or failed, since tuning data may remain in the buffer in either case. Move the error return after the reset so that the controller is always cleaned up. Fixes: de31f6ab68a3 ("mmc: sdhci_am654: Reset Command and Data line after tuning") Cc: stable@vger.kernel.org Signed-off-by: Diogo Ivo (Schneider Electric) Reviewed-by: Judith Mendez Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 4b74a4115509..6abe206ed292 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -442,15 +442,13 @@ static int sdhci_am654_execute_tuning(struct mmc_host *mmc, u32 opcode) struct sdhci_host *host = mmc_priv(mmc); int err = sdhci_execute_tuning(mmc, opcode); - if (err) - return err; /* * Tuning data remains in the buffer after tuning. * Do a command and data reset to get rid of it */ sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); - return 0; + return err; } static u32 sdhci_am654_cqhci_irq(struct sdhci_host *host, u32 intmask) From c9f47cc8c37f7659897142ffe216c250fbc1d4ed Mon Sep 17 00:00:00 2001 From: "Diogo Ivo (Schneider Electric)" Date: Fri, 7 Aug 2026 13:06:59 +0200 Subject: [PATCH 0812/2366] mmc: sdhci_am654: Clear ITAPDLY on tuning failure When tuning fails, stale ITAPDLY values can persist and interfere with subsequent I/O accesses, for example in DDR50 mode in cards with no tuning support. Move the ITAPDLY enable setting out of the tuning loop to after successful tuning, and explicitly clear ITAPDLY (delay and enable) when tuning fails so that we are sure only working values are actually left in hardware. Fixes: 901d16e46296 ("mmc: sdhci_am654: Add retry tuning") Cc: stable@vger.kernel.org Signed-off-by: Diogo Ivo (Schneider Electric) Reviewed-by: Judith Mendez Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 6abe206ed292..49a46583c8bc 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -527,7 +527,6 @@ static int sdhci_am654_do_tuning(struct sdhci_host *host, { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host); - unsigned char timing = host->mmc->ios.timing; struct window fail_window[ITAPDLY_LENGTH]; struct device *dev = mmc_dev(host->mmc); u8 curr_pass, itap; @@ -536,11 +535,8 @@ static int sdhci_am654_do_tuning(struct sdhci_host *host, memset(fail_window, 0, sizeof(fail_window)); - /* Enable ITAPDLY */ - sdhci_am654->itap_del_ena[timing] = 0x1; - for (itap = 0; itap < ITAPDLY_LENGTH; itap++) { - sdhci_am654_write_itapdly(sdhci_am654, itap, sdhci_am654->itap_del_ena[timing]); + sdhci_am654_write_itapdly(sdhci_am654, itap, 0x1); curr_pass = !mmc_send_tuning(host->mmc, opcode, NULL); @@ -584,10 +580,16 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, if (itapdly < 0) { dev_err(dev, "Failed to find itapdly, fail tuning\n"); + sdhci_am654_write_itapdly(sdhci_am654, 0, 0); + sdhci_am654->itap_del_ena[timing] = 0; + sdhci_am654->itap_del_sel[timing] = 0; return -1; } dev_dbg(dev, "Passed tuning, final itapdly=%d\n", itapdly); + + /* Enable ITAPDLY */ + sdhci_am654->itap_del_ena[timing] = 0x1; sdhci_am654_write_itapdly(sdhci_am654, itapdly, sdhci_am654->itap_del_ena[timing]); /* Save ITAPDLY */ sdhci_am654->itap_del_sel[timing] = itapdly; From 308d05225281d86150d88141990d6caf8c902349 Mon Sep 17 00:00:00 2001 From: "Diogo Ivo (Schneider Electric)" Date: Fri, 7 Aug 2026 13:07:00 +0200 Subject: [PATCH 0813/2366] mmc: sdhci_am654: Fallback to DT-provided itap delay on DDR50 tuning failure DDR50 mode is not required to support the tuning command CMD19, meaning that calibration may fail on cards that do not implement it, in which case a known-good itap delay value should be programmed into the host controller. Do this by reading the (already defined) itap delay DT property for DDR50 and, if tuning fails for this mode, fall back to the DT-provided itap delay value. If the DT does not provide a value for DDR50 fallback then this simply disables using itapdly. Fixes: 901d16e46296 ("mmc: sdhci_am654: Add retry tuning") Cc: stable@vger.kernel.org Signed-off-by: Diogo Ivo (Schneider Electric) Acked-by: Adrian Hunter Reviewed-by: Judith Mendez Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 49a46583c8bc..2e332f52c301 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -126,7 +126,7 @@ static const struct timing_data td[] = { NULL, MMC_CAP_UHS_SDR104}, [MMC_TIMING_UHS_DDR50] = {"ti,otap-del-sel-ddr50", - NULL, + "ti,itap-del-sel-ddr50", MMC_CAP_UHS_DDR50}, [MMC_TIMING_MMC_DDR52] = {"ti,otap-del-sel-ddr52", "ti,itap-del-sel-ddr52", @@ -144,6 +144,8 @@ struct sdhci_am654_data { u32 otap_del_sel[ARRAY_SIZE(td)]; u32 itap_del_sel[ARRAY_SIZE(td)]; u32 itap_del_ena[ARRAY_SIZE(td)]; + u32 itap_del_sel_dt_ddr50; + u32 itap_del_ena_dt_ddr50; int clkbuf_sel; int trm_icp; int drv_strength; @@ -579,10 +581,19 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, } while (++tuning_loop < RETRY_TUNING_MAX); if (itapdly < 0) { - dev_err(dev, "Failed to find itapdly, fail tuning\n"); - sdhci_am654_write_itapdly(sdhci_am654, 0, 0); - sdhci_am654->itap_del_ena[timing] = 0; - sdhci_am654->itap_del_sel[timing] = 0; + if (timing == MMC_TIMING_UHS_DDR50) { + dev_dbg(dev, "Failed DDR50 tuning, fallback to DT ITAP\n"); + sdhci_am654->itap_del_sel[timing] = sdhci_am654->itap_del_sel_dt_ddr50; + sdhci_am654->itap_del_ena[timing] = sdhci_am654->itap_del_ena_dt_ddr50; + } else { + dev_err(dev, "Failed to find itapdly, fail tuning\n"); + sdhci_am654->itap_del_ena[timing] = 0; + sdhci_am654->itap_del_sel[timing] = 0; + } + + sdhci_am654_write_itapdly(sdhci_am654, + sdhci_am654->itap_del_sel[timing], + sdhci_am654->itap_del_ena[timing]); return -1; } @@ -758,6 +769,11 @@ static int sdhci_am654_get_otap_delay(struct sdhci_host *host, } } + sdhci_am654->itap_del_sel_dt_ddr50 = + sdhci_am654->itap_del_sel[MMC_TIMING_UHS_DDR50]; + sdhci_am654->itap_del_ena_dt_ddr50 = + sdhci_am654->itap_del_ena[MMC_TIMING_UHS_DDR50]; + return 0; } From cdd812d0683dee14ead02c9eded568685e61b23f Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Mon, 31 Aug 2026 09:12:21 +0900 Subject: [PATCH 0814/2366] exit: hold a reference to thread_pid across proc_flush_pid Commit 0a36bad01731 ("release_task: kill the no longer needed get/put_pid(thread_pid)") removed the reference around proc_flush_pid(). It assumed that free_pids(post.pids) at the end of release_task() would keep thread_pid alive until then. That assumption is wrong. __change_pid() only records a detached PID in post.pids when pid_has_task() is false for every PIDTYPE. If another task still uses the exiting task's PID as its process group or session ID, __unhash_process() removes the exiting task's PIDTYPE_PID link but leaves the PID out of post.pids. release_task() therefore holds no reference to it after dropping tasklist_lock. The other task can then remove the remaining PIDTYPE links. Its free_pids() call schedules delayed_put_pid(), and the RCU callback can free the PID before the first release_task() reaches proc_flush_pid(). An unprivileged reproducer races wait4(-1) against setsid() to trigger this ordering. Three of three fresh v7.2 KASAN boots reported: BUG: KASAN: slab-use-after-free in proc_invalidate_siblings_dcache+0x3e2/0x3f0 Read of size 8 by task h7_pid_reaper/1921 Call Trace: proc_invalidate_siblings_dcache release_task wait_consider_task __do_wait do_wait kernel_wait4 Freed by task 0: kmem_cache_free put_pid delayed_put_pid rcu_core Last potentially related work creation: __call_rcu_common free_pids ksys_setsid KASAN identified a 144-byte object from the pid cache and located the bad read 80 bytes into the freed object, matching pid->inodes. With an explicit reference, three of three fresh boots completed without a KASAN report. The concurrent RCU callback dropped its reference while proc_flush_pid() was protected, and the balancing put_pid() performed the final free afterward. Take a reference before __unhash_process() clears p->thread_pid and release it after proc_flush_pid() completes. A tested source reproducer is available privately on request. No controlled read or write, information leak, or privilege escalation is claimed. The mainline patch applies directly to v6.19.y and newer; v6.16.y through v6.18.y need a context-adjusted backport. Fixes: 0a36bad01731 ("release_task: kill the no longer needed get/put_pid(thread_pid)") Reported-by: syzbot+0aee5e8066eddbbe7397@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0aee5e8066eddbbe7397 Reported-by: syzbot+e8b3520b53e78e90034e@syzkaller.appspotmail.com Link: https://syzkaller.appspot.com/bug?extid=e8b3520b53e78e90034e Cc: stable@vger.kernel.org # see patch description, needs adjustments for 6.16.y-6.18.y Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/20260831001221.3755948-1-4ncienth@gmail.com Acked-by: Oleg Nesterov Reviewed-by: Bradley Morgan Signed-off-by: Christian Brauner (Amutable) --- kernel/exit.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kernel/exit.c b/kernel/exit.c index 182c06671c78..e12072f1507c 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -261,8 +261,11 @@ void release_task(struct task_struct *p) pidfs_exit(p); cgroup_task_release(p); - /* Retrieve @thread_pid before __unhash_process() may set it to NULL. */ - thread_pid = task_pid(p); + /* + * Pin @thread_pid before __unhash_process() clears it. The last + * PIDTYPE detach can otherwise free it before proc_flush_pid(). + */ + thread_pid = get_pid(task_pid(p)); write_lock_irq(&tasklist_lock); ptrace_release_task(p); @@ -291,8 +294,8 @@ void release_task(struct task_struct *p) } write_unlock_irq(&tasklist_lock); - /* @thread_pid can't go away until free_pids() below */ proc_flush_pid(thread_pid); + put_pid(thread_pid); exit_cred_namespaces(p); add_device_randomness(&p->se.sum_exec_runtime, sizeof(p->se.sum_exec_runtime)); From 5541d897584127b795dbbdc51a78a2348b4eaaa4 Mon Sep 17 00:00:00 2001 From: "Vlastimil Babka (SUSE)" Date: Mon, 31 Aug 2026 18:02:38 +0200 Subject: [PATCH 0815/2366] mm/slab: disallow kfree_rcu_sheaf() on PREEMPT_RT again This partially reverts commit 2a8bb29ec9b2 ("mm/slab: allow kfree_rcu_sheaf() on PREEMPT_RT"). It was based on the assumption that local_trylock() is safe on PREEMPT_RT from any context. However kvfree_rcu() is also called by set_cpus_allowed_force() with task_struct::pi_lock acquired and there it's not safe, as syzbot has reported. For the immediate fix, skip kfree_rcu_sheaf() on PREEMPT_RT again from kvfree_call_rcu(). In theory, kfree_rcu_nolock() would have the same problem when called from under pi_lock on PREEMPT_RT but that can be addressed if such a caller is proposed. Add an explanation comment, courtesy of Sebastian. Reported-by: syzbot+acf142088e0182172e58@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=acf142088e0182172e58 Reported-by: ThangNN99 Fixes: 2a8bb29ec9b2 ("mm/slab: allow kfree_rcu_sheaf() on PREEMPT_RT") Reviewed-by: Sebastian Andrzej Siewior Link: https://patch.msgid.link/20260831-b4-kfree_rcu_hotfix-v1-1-4f0fb882638b@kernel.org Reviewed-by: Harry Yoo (Meta) Signed-off-by: Vlastimil Babka (SUSE) --- mm/slab_common.c | 18 ++++++++---------- mm/slub.c | 5 +++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index b19ba1b31484..7223a7596dab 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -1667,14 +1667,6 @@ static bool kfree_rcu_sheaf(void *obj) { struct kmem_cache *s; struct slab *slab; - unsigned int free_flags = SLAB_FREE_DEFAULT; - - /* - * It is not safe to spin on PREEMPT_RT because the kernel might be - * holding a raw spinlock and slab acquires sleeping locks. - */ - if (IS_ENABLED(CONFIG_PREEMPT_RT)) - free_flags = SLAB_FREE_NOLOCK; if (is_vmalloc_addr(obj)) return false; @@ -1685,7 +1677,7 @@ static bool kfree_rcu_sheaf(void *obj) s = slab->slab_cache; if (likely(!IS_ENABLED(CONFIG_NUMA) || slab_nid(slab) == numa_mem_id())) - return __kfree_rcu_sheaf(s, obj, free_flags); + return __kfree_rcu_sheaf(s, obj, SLAB_FREE_DEFAULT); return false; } @@ -2034,7 +2026,13 @@ void kvfree_call_rcu(struct kvfree_rcu_head *head, void *ptr) if (!head) might_sleep(); - if (kfree_rcu_sheaf(ptr)) + /* + * kvfree_rcu() is called by set_cpus_allowed_force() with + * task_struct::pi_lock acquired. On PREEMPT_RT the local_trylock() + * usage below will acquire the waitlock which must be avoided. + * Therefore avoid it on PREEMPT_RT. + */ + if (!IS_ENABLED(CONFIG_PREEMPT_RT) && kfree_rcu_sheaf(ptr)) return; // Queue the object but don't yet schedule the batch. diff --git a/mm/slub.c b/mm/slub.c index f9b56cb439e7..7a7e906a0e44 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -6088,8 +6088,9 @@ static void rcu_free_sheaf(struct rcu_head *head) /* * kvfree_call_rcu() can be called while holding a raw_spinlock_t. Since * __kfree_rcu_sheaf() may acquire a spinlock_t (sleeping lock on PREEMPT_RT), - * this would violate lock nesting rules. Therefore, kvfree_call_rcu() avoids - * this problem by passing SLAB_FREE_NOLOCK on PREEMPT_RT. + * this would violate lock nesting rules. Therefore, kfree_call_rcu_nolock() + * avoids this problem by passing SLAB_FREE_NOLOCK. kvfree_call_rcu() is + * bypassing the sheaves layer completely on PREEMPT_RT. * * However, lockdep still complains that it is invalid to acquire spinlock_t * while holding raw_spinlock_t, even on !PREEMPT_RT where spinlock_t is a From 4a724bcf5d703e18957397914d79156fa2cf1174 Mon Sep 17 00:00:00 2001 From: "Harry Yoo (Meta)" Date: Thu, 3 Sep 2026 15:32:26 +0100 Subject: [PATCH 0816/2366] mm/slab: take n->list_lock in __slab_try_return_freelist() to avoid race Commit ba7425312607 ("mm, slab: add an optimistic __slab_try_return_freelist()") incorrectly assumed that nobody has freed an object to the slab as long as slab->freelist is NULL and cmpxchg succeeds. However, as reported by Hyunwoo Kim [1], other CPUs might have freed an object to the slab, insert the slab to the partial list, then allocated an object from the slab, and be in the middle of removing the slab from the list under n->list_lock. Since __refill_objects_node() puts the slab back on pc.slabs outside n->list_lock, it might insert the slab into that list while the slab is concurrently being removed from n->partial. This led to a list corruption [1]: list_add corruption. next->prev should be prev (ffff888100000248), but was dead000000000122. (next=ffffea000416e410). kernel BUG at lib/list_debug.c:29! Oops: invalid opcode: 0000 [#1] SMP NOPTI CPU: 1 UID: 65534 PID: 144 Comm: poc Not tainted 7.2.0-16172-gcf72cbb39da8-dirty #1 PREEMPT(lazy) RIP: 0010:__list_add_valid_or_report+0x80/0xd0 ... Call Trace: alloc_from_new_slab+0x183/0x300 ___slab_alloc+0x31c/0x890 __kmalloc_noprof+0x3d4/0x800 lsm_blob_alloc+0x2d/0x50 security_msg_msg_alloc+0x26/0x90 load_msg+0x1aa/0x210 do_msgsnd+0x91/0x800 do_syscall_64+0x109/0x5d0 entry_SYSCALL_64_after_hwframe+0x77/0x7f ... Kernel panic - not syncing: Fatal exception This is a classic ABA problem where cmpxchg succeeds but the state has changed since __refill_objects_node() took the freelist from the slab. As Vlastimil Babka mentioned [2], it should be rare to return more than one slab (due to the racy read of slab->counters in get_partial_node_bulk()). Therefore, instead of introducing additional complexity, acquire and release n->list_lock twice in the worst case. Return the slab directly to the partial list and hold n->list_lock across the cmpxchg and add_partial(). This is similar to the initial version of commit ba7425312607 [3]. This is enough to avoid the race as the list manipulation is serialized by n->list_lock. While at it, bring back unlikely() hint now that the condition is unlikely. Reported-by: Hyunwoo Kim Closes: https://lore.kernel.org/linux-mm/apPa-cGLcyt90l-E@v4bel [1] Link: https://lore.kernel.org/linux-mm/ae25c193-b95f-40c1-83b6-1c2546467e41@kernel.org [2] Link: https://lore.kernel.org/all/20260421-b4-refill-optimistic-return-v1-1-24f0bfc1acff@kernel.org [3] Fixes: ba7425312607 ("mm, slab: add an optimistic __slab_try_return_freelist()") Cc: stable@vger.kernel.org Signed-off-by: Harry Yoo (Meta) Link: https://patch.msgid.link/20260903-slab-fix-aba-v3-1-b44cb6badd54@kernel.org Reviewed-by: Hao Li Signed-off-by: Vlastimil Babka (SUSE) --- mm/slub.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 7a7e906a0e44..54ec12503357 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -5680,10 +5680,12 @@ static noinline void free_to_partial_list( * * Fail if the slab isn't full anymore due to a concurrent free. */ -static bool __slab_try_return_freelist(struct kmem_cache *s, struct slab *slab, - void *head, int cnt) +static bool __slab_try_return_freelist(struct kmem_cache *s, + struct kmem_cache_node *n, + struct slab *slab, void *head, int cnt) { struct freelist_counters old, new; + unsigned long flags; old.freelist = slab->freelist; old.counters = slab->counters; @@ -5695,9 +5697,15 @@ static bool __slab_try_return_freelist(struct kmem_cache *s, struct slab *slab, new.counters = old.counters; new.inuse -= cnt; - if (!slab_update_freelist(s, slab, &old, &new, "__slab_try_return_freelist")) - return false; + spin_lock_irqsave(&n->list_lock, flags); + if (!slab_update_freelist(s, slab, &old, &new, "__slab_try_return_freelist")) { + spin_unlock_irqrestore(&n->list_lock, flags); + return false; + } + + add_partial(n, slab, ADD_TO_TAIL); + spin_unlock_irqrestore(&n->list_lock, flags); return true; } @@ -7297,10 +7305,8 @@ __refill_objects_node(struct kmem_cache *s, void **p, gfp_t gfp, unsigned int mi void *head = object; void *tail; - if (__slab_try_return_freelist(s, slab, head, count)) { - list_add(&slab->slab_list, &pc.slabs); + if (__slab_try_return_freelist(s, n, slab, head, count)) break; - } do { tail = object; @@ -7313,7 +7319,7 @@ __refill_objects_node(struct kmem_cache *s, void **p, gfp_t gfp, unsigned int mi break; } - if (!list_empty(&pc.slabs)) { + if (unlikely(!list_empty(&pc.slabs))) { spin_lock_irqsave(&n->list_lock, flags); list_for_each_entry(slab, &pc.slabs, slab_list) From da2ca406f45a6e21760243152ed8d2e8e72915c2 Mon Sep 17 00:00:00 2001 From: Rik van Riel Date: Sat, 8 Aug 2026 10:47:55 -0400 Subject: [PATCH 0817/2366] wifi: mac80211: avoid WARN in set_bitrate_mask when sdata not in driver ieee80211_set_bitrate_mask() checks if the interface is running via ieee80211_sdata_running(), but it does not check if the interface is still present in the driver. When sdata is running but IEEE80211_SDATA_IN_DRIVER is not set, the call reaches drv_set_bitrate_mask() in driver-ops.h which hits wlan1: Failed check-sdata-in-driver check, flags: 0x0 WARNING: net/mac80211/driver-ops.h:884 at drv_set_bitrate_mask Syzkaller triggers this via wext SIOCSIWRATE ioctl. The Call Trace shows wext_ioctl_dispatch() in wext-core.c dispatching the ioctl, calling ioctl_standard_call() for SIOCSIWRATE, which calls cfg80211_wext_siwrate() in wext-compat.c. That builds a bitrate mask and calls rdev_set_bitrate_mask() which ends up in ieee80211_set_bitrate_mask() in cfg.c. The interface is marked running via SDATA_STATE_RUNNING but flags is 0, so check_sdata_in_driver() fails. When the interface is being torn down, or when wext ioctl is issued during interface bringup before drv_add_interface() sets IN_DRIVER, the running check passes while IN_DRIVER is clear. Check IEEE80211_SDATA_IN_DRIVER in ieee80211_set_bitrate_mask() before calling the driver, returning -ENETDOWN. This avoids the WARN_ONCE in driver-ops.h and matches other cfg.c operations that bail early when not in driver. This change should be safe because wiphy mutex is held in cfg80211_wext_siwrate() via guard(wiphy), and IN_DRIVER is set/cleared under RTNL and wiphy paths in drv_add_interface() and drv_remove_interface() in driver-ops.c, so the check is race-free against driver add/remove. Returning -ENETDOWN is the same error other not-running paths use and does not introduce new locking. Reported-by: syzbot+af177aa139efdd13a9da@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=af177aa139efdd13a9da Link: https://lore.kernel.org/all/6a75205c.59b6c763.2bba34.00c3.GAE@google.com/ Fixes: 554a43d5e77e ("mac80211: check sdata_running on ieee80211_set_bitrate_mask") Cc: stable@vger.kernel.org Assisted-by: Hermes:muse-spark-1.2 syzkaller Signed-off-by: Rik van Riel Link: https://patch.msgid.link/20260808104755.319c686e@fangorn Reported-by: syzbot+dcaca020ca8377e7ced0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=dcaca020ca8377e7ced0 [also add second syzbot report] Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 23f4f9ec86d0..1f074799f85f 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -4113,6 +4113,9 @@ static int ieee80211_set_bitrate_mask(struct wiphy *wiphy, if (!ieee80211_sdata_running(sdata)) return -ENETDOWN; + if (!(sdata->flags & IEEE80211_SDATA_IN_DRIVER)) + return -ENETDOWN; + /* * If active validate the setting and reject it if it doesn't leave * at least one basic rate usable, since we really have to be able From 67b529f521a6676cdfc78b91b0217d7eaa84216b Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 01:33:18 -0700 Subject: [PATCH 0818/2366] bpf: Don't infer non-NULL from a pointer with an unbounded offset reg_not_null() decides that a register holds a non-NULL value by looking at its type alone. For pointer types that allow arithmetic the type only guarantees a non-NULL base, in case of an unbound offset the runtime offset value might still add up to NULL. Consider the followng program: r6 = bpf_map_lookup_elem(map, &0); /* present */ if (r6 == 0) return 0; r7 = bpf_map_lookup_elem(map, &1); /* absent, NULL at runtime */ r8 = r7; r8 -= r6; /* pointer - pointer: unknown scalar, -r6 */ r8 <<= 1; r8 >>= 1; /* any non-negative offset is accepted by */ /* check_reg_sane_offset_ptr() */ r6 += r8; /* verifier: map value; runtime: zero */ if (r7 != r6) return 0; *(u8 *)(r7 + 0); /* r7 is inferred non-NULL, both are zero */ At runtime both registers are zero, the comparison is true and the load faults with NULL pointer dereference. Require the offset to be within +-BPF_MAX_VAR_OFF in reg_not_null(). Fixes: cac616db39c2 ("bpf: Verifier track null pointer branch_taken with JNE and JEQ") Reported-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260904083325.2083493-1-eddyz87@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- kernel/bpf/verifier.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index d7dd0befbd10..faf1c8ff243d 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -352,6 +352,13 @@ static bool reg_not_null(struct bpf_verifier_env *env, const struct bpf_reg_stat if (type_may_be_null(type)) return false; + /* + * The types below guarantee a non-NULL base, an unbounded offset can + * still wrap base + offset to zero. + */ + if (reg_smin(reg) <= -BPF_MAX_VAR_OFF || reg_smax(reg) >= BPF_MAX_VAR_OFF) + return false; + type = base_type(type); return type == PTR_TO_SOCKET || type == PTR_TO_TCP_SOCK || From 6752b90ccfb378e311e428932facf37c8625abae Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 01:33:19 -0700 Subject: [PATCH 0819/2366] selftests/bpf: No non-NULL inference from unbounded offset pointers Check that a comparison against a pointer whose offset is not bounded from above does not make the verifier infer that a nullable pointer is not NULL, and that a bounded offset still does. W/o the previous patch the first test is accepted. Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260904083325.2083493-2-eddyz87@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- .../bpf/progs/verifier_jeq_infer_not_null.c | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c b/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c index b412a542ef76..8657e4a0d601 100644 --- a/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c +++ b/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c @@ -311,6 +311,86 @@ __naked void untrusted_mem_does_not_infer_map_value_non_null(void) : __clobber_all); } +/* + * A pointer with an offset that is not bounded from above may be null at + * runtime, hence it is not a witness for the pointer it is compared with. + */ +SEC("socket") +__failure +__msg("error: invalid dereference of R7 (a nullable map value pointer)") +__naked void unbounded_offset_does_not_infer_map_value_non_null(void) +{ + asm volatile (" \ + /* r6 = bpf_map_lookup_elem(map_hash, &0); */ \ + *(u64 *)(r10 - 8) = 0; \ + r1 = %[map_hash] ll; \ + r2 = r10; \ + r2 += -8; \ + call %[bpf_map_lookup_elem]; \ + if r0 == 0 goto 1f; \ + r6 = r0; \ + /* r7 = bpf_map_lookup_elem(map_hash, &1); */ \ + *(u64 *)(r10 - 8) = 1; \ + r1 = %[map_hash] ll; \ + r2 = r10; \ + r2 += -8; \ + call %[bpf_map_lookup_elem]; \ + r7 = r0; \ + /* pointer - pointer is an unknown scalar */ \ + r8 = r7; \ + r8 -= r6; \ + /* r8 is in [0, S64_MAX] */ \ + r8 <<= 1; \ + r8 >>= 1; \ + /* r6 may wrap to zero at runtime */ \ + r6 += r8; \ + if r7 != r6 goto 1f; \ + r0 = *(u8 *)(r7 + 0); \ +1: r0 = 0; \ + exit; \ +" : + : __imm(bpf_map_lookup_elem), + __imm_addr(map_hash) + : __clobber_all); +} + +/* Same, but the offset is bounded, so the inference is still done. */ +SEC("socket") +__success +__naked void bounded_offset_infers_map_value_non_null(void) +{ + asm volatile (" \ + /* r6 = bpf_map_lookup_elem(map_hash, &0); */ \ + *(u64 *)(r10 - 8) = 0; \ + r1 = %[map_hash] ll; \ + r2 = r10; \ + r2 += -8; \ + call %[bpf_map_lookup_elem]; \ + if r0 == 0 goto 1f; \ + r6 = r0; \ + /* r7 = bpf_map_lookup_elem(map_hash, &1); */ \ + *(u64 *)(r10 - 8) = 1; \ + r1 = %[map_hash] ll; \ + r2 = r10; \ + r2 += -8; \ + call %[bpf_map_lookup_elem]; \ + r7 = r0; \ + /* pointer - pointer is an unknown scalar */ \ + r8 = r7; \ + r8 -= r6; \ + /* r8 is in [0, 3] */ \ + r8 &= 3; \ + r6 += r8; \ + if r7 != r6 goto 1f; \ + r0 = *(u8 *)(r7 + 0); \ +1: r0 = 0; \ + exit; \ +" : + : __imm(bpf_map_lookup_elem), + __imm_addr(map_hash) + : __clobber_all); +} + void kfunc_root(void) { bpf_rdonly_cast(0, 0); From 73a98f96811e2cb0f4210b1caa8cb322f92f2a2b Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 01:33:20 -0700 Subject: [PATCH 0820/2366] bpf: Don't resurrect a scalar id dropped by collect_linked_regs() check_cond_jmp_op() copies the compared registers into env->{false,true}_reg{1,2} before collect_linked_regs() runs and copies those snapshots back into both branch states afterwards. collect_linked_regs() records at most LINKED_REGS_MAX members of a linked registers group in the jump history and calls clear_scalar_id() for every member that does not fit. The compared register is not exempt from that. As a consequence, sync_linked_regs() might adjust ranges for more registers than bpf_bt_sync_linked_regs() can propagate precision to. Collect the linked registers before the snapshots are taken instead. This might lead to some unnecessary clear_scalar_id's, but from previous testing situations with many linked registers are extremely rare. Fixes: ec1d77cb0ee9 ("bpf: Use bpf_verifier_env buffers for reg_set_min_max") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260904083325.2083493-3-eddyz87@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- kernel/bpf/verifier.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index faf1c8ff243d..1fb0c832611c 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -16898,6 +16898,16 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, return err; } + /* + * Collect the linked registers before env->{true,false}_reg{1,2} setup, + * otherwise ids dropped by collect_linked_regs() would be resurrected + * when env->{true,false}_reg{1,2} are copied back. + */ + if (BPF_SRC(insn->code) == BPF_X && src_reg->type == SCALAR_VALUE && src_reg->id) + collect_linked_regs(env, this_branch, src_reg->id, &linked_regs); + if (dst_reg->type == SCALAR_VALUE && dst_reg->id) + collect_linked_regs(env, this_branch, dst_reg->id, &linked_regs); + is_jmp32 = BPF_CLASS(insn->code) == BPF_JMP32; env->false_reg1 = *dst_reg; env->false_reg2 = *src_reg; @@ -16952,10 +16962,6 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, * 'this_branch' and 'other_branch' share this history * if parent state is created. */ - if (BPF_SRC(insn->code) == BPF_X && src_reg->type == SCALAR_VALUE && src_reg->id) - collect_linked_regs(env, this_branch, src_reg->id, &linked_regs); - if (dst_reg->type == SCALAR_VALUE && dst_reg->id) - collect_linked_regs(env, this_branch, dst_reg->id, &linked_regs); if (linked_regs.cnt > 1) { err = bpf_push_jmp_history(env, this_branch, 0, 0, 0, linked_regs_pack(&linked_regs)); if (err) From bc412b3fb185540112fcc99ac91a14e57418d28e Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 01:33:21 -0700 Subject: [PATCH 0821/2366] selftests/bpf: Check the linked regs cap for the compared register linked_regs_too_many_regs checks that collect_linked_regs() ties at most LINKED_REGS_MAX registers for a single jump. Compare r5 instead of r0, so that the register the jump compares is itself the member that does not fit, and check that it comes out of the jump unlinked. W/o the previous patch env->{false,true}_reg{1,2} bring r5's id back and insn 7 is logged as "R5=scalar(id=1,...)". Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260904083325.2083493-4-eddyz87@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- .../selftests/bpf/progs/verifier_scalar_ids.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c index 663d15fc5fd2..256547048cc4 100644 --- a/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c +++ b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c @@ -380,13 +380,14 @@ SEC("socket") __success __log_level(2) __flag(BPF_F_TEST_STATE_FREQ) /* - * check that r0 and r5 have different IDs after 'if', - * collect_linked_regs() can't tie more than 5 registers for a single insn. + * check that r5 is unlinked after 'if', collect_linked_regs() can't tie + * more than 5 registers for a single insn and the register compared by + * the jump is not exempt from that. */ -__msg("7: (25) if r0 > 0x7 goto pc+0 ; R0=scalar(id=1") +__msg("7: (25) if r5 > 0x7 goto pc+0 ; R5=scalar(smin=") __msg("12: (bf) r5 = r5 ; R5=scalar(id=2") /* check that r{0-4} are marked precise after 'if' */ -__msg("frame0: regs=r0 stack= before 7: (25) if r0 > 0x7 goto pc+0") +__msg("frame0: regs=r0 stack= before 7: (25) if r5 > 0x7 goto pc+0") __msg("frame0: parent state regs=r0,r1,r2,r3,r4 stack=:") __naked void linked_regs_too_many_regs(void) { @@ -400,8 +401,8 @@ __naked void linked_regs_too_many_regs(void) "r3 = r0;" "r4 = r0;" "r5 = r0;" - /* propagate range for r{0-5} */ - "if r0 > 7 goto +0;" + /* r{0-4} fill the record, r5 does not fit and is unlinked */ + "if r5 > 7 goto +0;" /* keep r{1-4} live */ "r1 = r1;" "r2 = r2;" From e51179a4e09846f8fd0f26a05068520de2b301bf Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 01:33:22 -0700 Subject: [PATCH 0822/2366] bpf: Don't predict JMP32 pointer vs zero comparisons Consider the following program: r1 = map_value; /* low 32 bits are zero at runtime */ r6 = 0xdead000000000000; if w1 != 0 goto l1; l0: r1 += r6; r2 = *(u64 *)(r1 + 0); exit; l1: r6 = 0; goto l0; At the moment is_branch_taken() reports the jump as always taken, because it does not distinguish between BPF_JMP and BPF_JMP32 comparisons when processing 'if w1 != 0 ...'. Fixes: cac616db39c2 ("bpf: Verifier track null pointer branch_taken with JNE and JEQ") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260904083325.2083493-5-eddyz87@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- kernel/bpf/verifier.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 1fb0c832611c..303368460ec1 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -16337,6 +16337,13 @@ static int is_branch_taken(struct bpf_verifier_env *env, struct bpf_reg_state *r if (__is_pointer_value(false, reg1) || __is_pointer_value(false, reg2)) { u64 val; + /* + * The low 32 bits of a valid pointer may well be zero, hence + * nothing below applies to a 32-bit comparison. + */ + if (is_jmp32) + return -1; + /* arrange that reg2 is a scalar, and reg1 is a pointer */ if (!is_reg_const(reg2, is_jmp32)) { opcode = flip_opcode(opcode); From 836b2fe544a5e9b5ce116622cb36fba33838c6fd Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 01:33:23 -0700 Subject: [PATCH 0823/2366] selftests/bpf: Check that JMP32 pointer vs zero jumps are not predicted Add jmp32_ptr_vs_zero_jne: the fall-through of the 32-bit compare, which the verifier used to skip, contains an out of bounds map value access, hence w/o the previous patch the program is accepted. See previous patch for detailed description. Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260904083325.2083493-6-eddyz87@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- .../bpf/progs/verifier_jeq_infer_not_null.c | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c b/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c index 8657e4a0d601..410acbf658c7 100644 --- a/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c +++ b/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c @@ -391,6 +391,33 @@ __naked void bounded_offset_infers_map_value_non_null(void) : __clobber_all); } +/* + * The low 32 bits of a map value pointer may be zero, hence a 32-bit + * compare with zero cannot be predicted from the pointer being non-NULL + * and both successors of such a jump have to be verified. + */ +SEC("socket") +__failure __msg("invalid access to map value, value_size=4 off=32 size=4") +__naked void jmp32_ptr_vs_zero_jne(void) +{ + asm volatile (" \ + /* r0 = bpf_map_lookup_elem(map_hash, &key); */ \ + *(u64 *)(r10 - 8) = 0; \ + r1 = %[map_hash] ll; \ + r2 = r10; \ + r2 += -8; \ + call %[bpf_map_lookup_elem]; \ + if r0 == 0 goto 1f; \ + if w0 != 0 goto 1f; \ + r0 = *(u32 *)(r0 + 32); \ +1: r0 = 0; \ + exit; \ +" : + : __imm(bpf_map_lookup_elem), + __imm_addr(map_hash) + : __clobber_all); +} + void kfunc_root(void) { bpf_rdonly_cast(0, 0); From 6aed0134d3cda6382385a734ae0158eb7df6b142 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 01:33:24 -0700 Subject: [PATCH 0824/2366] bpf: Mark the zero register precise for a register-form NULL check check_cond_jmp_op() accepts "if rA rB" as a NULL check for a nullable pointer rA when rB is a scalar known to be zero, lifts PTR_MAYBE_NULL from rA in the corresponding branch and does not mark rB precise. Consider the following program: r0 = bpf_get_prandom_u32(); r6 = 1; /* the r6 == 0 path is explored first */ if (r0 == 0) goto 1f; r6 = 0; 1: r0 = bpf_map_lookup_elem(map, &0); /* absent, NULL at runtime */ if (r0 == r6) goto 2f; /* taken as a NULL check for r0 */ *(u8 *)(r0 + 0); /* verifier: map value; runtime: zero */ 2: return 0; The r6 == 0 path is explored first and the dereference is accepted. The r6 == 1 path is pruned at the checkpoint recorded for (1), so the comparison is never verified with a non-zero r6. At runtime a failed lookup returns NULL, NULL != 1 takes the non-NULL edge and the program dereferences a pointer that is zero. Fixes: 2f4cb53eed44 ("bpf: detect non null pointer with register operand in JEQ/JNE.") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260904083325.2083493-7-eddyz87@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- kernel/bpf/verifier.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 303368460ec1..fde5d046b6e3 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -17052,6 +17052,15 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, type_may_be_null(dst_reg->type) && ((BPF_SRC(insn->code) == BPF_K && insn->imm == 0) || (BPF_SRC(insn->code) == BPF_X && bpf_register_is_null(src_reg)))) { + /* + * For BPF_X the zero is a property of this execution path, + * hence src_reg has to be precise. + */ + if (BPF_SRC(insn->code) == BPF_X) { + err = mark_chain_precision(env, insn->src_reg); + if (err) + return err; + } /* Mark all identical registers in each branch as either * safe or unknown depending R == 0 or R != 0 conditional. */ From 6b31560c6bc1a8a7a70792c7b3ca4c1ea322063b Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 01:33:25 -0700 Subject: [PATCH 0825/2366] selftests/bpf: No non-NULL inference from an imprecise zero register Check that a register-form NULL check does not lift PTR_MAYBE_NULL on a path where the compared register is non-zero. W/o the previous patch the program is accepted. Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/bpf/20260904083325.2083493-8-eddyz87@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- .../bpf/progs/verifier_jeq_infer_not_null.c | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c b/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c index 410acbf658c7..3c789c565b18 100644 --- a/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c +++ b/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c @@ -418,6 +418,40 @@ __naked void jmp32_ptr_vs_zero_jne(void) : __clobber_all); } +/* + * The below program is explored in two paths: r6 == 0 and r6 == 1. + * On the first path comparison "if r0 == r6 goto 2f" should mark r6 as precise, + * otherwise unsafe path with r6 == 1 would be incorrectly pruned. + */ +SEC("socket") +__failure +__flag(BPF_F_TEST_STATE_FREQ) +__msg("error: invalid dereference of R0 (a nullable map value pointer)") +__naked void imprecise_zero_does_not_infer_map_value_non_null(void) +{ + asm volatile (" \ + call %[bpf_get_prandom_u32]; \ + /* r6 is 0 on the path explored first, 1 on the other */\ + r6 = 1; \ + if r0 == 0 goto 1f; \ + r6 = 0; \ + /* r0 = bpf_map_lookup_elem(map_hash, &0); */ \ +1: *(u64 *)(r10 - 8) = 0; \ + r1 = %[map_hash] ll; \ + r2 = r10; \ + r2 += -8; \ + call %[bpf_map_lookup_elem]; \ + if r0 == r6 goto 2f; \ + r0 = *(u8 *)(r0 + 0); \ +2: r0 = 0; \ + exit; \ +" : + : __imm(bpf_get_prandom_u32), + __imm(bpf_map_lookup_elem), + __imm_addr(map_hash) + : __clobber_all); +} + void kfunc_root(void) { bpf_rdonly_cast(0, 0); From 44caf1844a258534e891d2c4071b011097e19235 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:07:31 +0530 Subject: [PATCH 0826/2366] firmware: arm_scmi: Fix typo "upto" in comment Correct "upto" to "up to", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260904103732.7320-1-hemanth.selam@gmail.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index ef29fd223287..fb45b8e6459d 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -477,7 +477,7 @@ void *scmi_notification_instance_data_get(const struct scmi_handle *handle) * - exactly 'next_token' may be NOT available so pick xfer_id >= next_token * using find_next_zero_bit() starting from candidate next_token bit * - * - all tokens ahead upto (MSG_TOKEN_ID_MASK - 1) are used in-flight but we + * - all tokens ahead up to (MSG_TOKEN_ID_MASK - 1) are used in-flight but we * are plenty of free tokens at start, so try a second pass using * find_next_zero_bit() and starting from 0. * From 63b6a48c951d63bf39d44603ada48a987ccf66eb Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Fri, 4 Sep 2026 21:44:23 +0800 Subject: [PATCH 0827/2366] LoongArch: Do not select HAVE_RUST when KASAN is enabled After commit 2625480a1bf7 ("hardening: Default randstruct off with rust for better allmodconfig support"), which allows Rust to be enabled for allmodconfig, ARCH=loongarch allmodconfig starts failing with: error: kernel-address sanitizer is not supported for this target error: aborting due to 1 previous error make[4]: *** [rust/Makefile:741: rust/core.o] Error 1 For the same reason as the commit 84a0f7caafc679f7 ("ARM: Do not select HAVE_RUST when KASAN is enabled"), do not select HAVE_RUST when KASAN is enabled until the loongarch64-unknown-none-softfloat target in rustc supports KASAN. Cc: stable@vger.kernel.org Fixes: 90868ff9cade ("LoongArch: Enable initial Rust support") Acked-by: Miguel Ojeda Signed-off-by: Nathan Chancellor Signed-off-by: Huacai Chen --- arch/loongarch/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index a21f51e5815e..2067d1f2ad7a 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -175,7 +175,7 @@ config LOONGARCH select HAVE_RELIABLE_STACKTRACE if UNWINDER_ORC select HAVE_RETHOOK select HAVE_RSEQ - select HAVE_RUST + select HAVE_RUST if !KASAN select HAVE_SAMPLE_FTRACE_DIRECT select HAVE_SAMPLE_FTRACE_DIRECT_MULTI select HAVE_SETUP_PER_CPU_AREA if NUMA From 20a9e97137caaa3fbb27f22f83a5ad80cbd03b01 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 21:44:24 +0800 Subject: [PATCH 0828/2366] LoongArch: Fix typo "avaliable" in comment of vmlinux.lds.S Correct "avaliable" to "available", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. It only touches the comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Huacai Chen --- arch/loongarch/kernel/vmlinux.lds.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/loongarch/kernel/vmlinux.lds.S b/arch/loongarch/kernel/vmlinux.lds.S index 840d944c2f73..ce097e25881f 100644 --- a/arch/loongarch/kernel/vmlinux.lds.S +++ b/arch/loongarch/kernel/vmlinux.lds.S @@ -23,7 +23,7 @@ #include "image-vars.h" /* - * Max avaliable Page Size is 64K, so we set SectionAlignment + * Max available Page Size is 64K, so we set SectionAlignment * field of EFI application to 64K. */ PECOFF_FILE_ALIGN = 0x200; From 3e1b64bd8cd2bc15c514d90b3dd0a55c53302f3a Mon Sep 17 00:00:00 2001 From: Anthony Iliopoulos Date: Fri, 4 Sep 2026 21:44:43 +0800 Subject: [PATCH 0829/2366] LoongArch: Remove unused setup_profiling_timer() function setup_profiling_timer() is not used by any code at this point. Since a default weak implementation exists, there is no need to still keep this arch-specific definition around. Remove it along with the now-redundant profile header includes. Signed-off-by: Anthony Iliopoulos Signed-off-by: Huacai Chen --- arch/loongarch/kernel/smp.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c index d4b5d1b6bb01..11f54837f56c 100644 --- a/arch/loongarch/kernel/smp.c +++ b/arch/loongarch/kernel/smp.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -716,13 +715,6 @@ void smp_send_stop(void) smp_call_function(stop_this_cpu, NULL, 0); } -#ifdef CONFIG_PROFILING -int setup_profiling_timer(unsigned int multiplier) -{ - return 0; -} -#endif - static void flush_tlb_all_ipi(void *info) { local_flush_tlb_all(); From c3f2feace5e4f4b01b68b9f947b19adb4155c32e Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Fri, 4 Sep 2026 21:44:43 +0800 Subject: [PATCH 0830/2366] LoongArch: Do not save/restore percpu base register in rethook trampoline The rethook trampoline saves $r21 ($u0), the percpu base, into its frame at entry and restores it at exit. Inbetween rethook_trampoline_handler() may schedule via preempt_enable_notrace(). If the task migrates to another CPU, the frame's $r21 holds the old CPU's percpu base, and restoring it poisons $r21 on the new CPU. Until the next user->kernel transition heals $r21, all this_cpu_*() accesses (runqueues, RCU per-CPU data, timer tick programming, FPU ownership) hit the wrong CPU's percpu area. Under kretprobe-heavy preemptible load this can corrupt scheduler and timer state: scheduling-while-atomic splats, wrong-CPU RCU warnings, WARN_ON_ONCE(rq != this_rq()) in nohz_balance_exit_idle(), and CPUs parking in the idle loop with the constant timer never re-armed (hard lockup). Reproduces on a Loongson-3A6000 with kretprobes on VFS paths plus heavy file churn (OS install / unsquashfs). By convention $r21 always holds the current CPU's percpu base in kernel mode: SAVE_SOME() at exception entry reloads it only when coming from user mode, and RESTORE_SOME() restores it only when returning to user mode; the context-switch path never writes it. Therefore the live $r21 at trampoline exit is already correct, and nothing inbetween can change it legitimately (kernel C code cannot write a global register variable). The same flaw existed even in the pre-rethook kretprobe trampoline since v6.3; it was carried over when rethook replaced it. Drop both the save and the restore here. Drop the restore is enough to solve the issue, and drop the save is to keep the code tidy and no need to clear it. Cc: stable@vger.kernel.org # v6.3+ Fixes: 3f5536860086d ("LoongArch: Add kretprobes support") Assisted-by: Kimi:Kimi-K3 # debug and root-cause analysis Signed-off-by: Wentao Guan Signed-off-by: Huacai Chen --- arch/loongarch/kernel/rethook_trampoline.S | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/loongarch/kernel/rethook_trampoline.S b/arch/loongarch/kernel/rethook_trampoline.S index 2e009fbea53f..160189444684 100644 --- a/arch/loongarch/kernel/rethook_trampoline.S +++ b/arch/loongarch/kernel/rethook_trampoline.S @@ -24,7 +24,6 @@ cfi_st t6, PT_R18 cfi_st t7, PT_R19 cfi_st t8, PT_R20 - cfi_st u0, PT_R21 cfi_st fp, PT_R22 cfi_st s0, PT_R23 cfi_st s1, PT_R24 @@ -59,7 +58,6 @@ cfi_ld t6, PT_R18 cfi_ld t7, PT_R19 cfi_ld t8, PT_R20 - cfi_ld u0, PT_R21 cfi_ld fp, PT_R22 cfi_ld s0, PT_R23 cfi_ld s1, PT_R24 From 72ce4b24676e8b3b75376c4c559dd81c1ac52d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Fri, 4 Sep 2026 21:44:43 +0800 Subject: [PATCH 0831/2366] LoongArch: Avoid preempt count underflow without probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LoongArch uses break 11 for the breakpoint placed after an instruction that Kprobes executes out of line. Since userspace can issue the same break instruction, do_bp() can reach kprobe_singlestep_handler() when there is no current probe. The handler actually returns false in this case, but it first calls preempt_enable_no_resched(). The corresponding preempt_disable() is done by kprobe_breakpoint_handler() on a real Kprobe hit, so it has not run here. As a result, an ordinary userspace breakpoint (code 11) underflows the current task's preempt count. This also makes in_interrupt() return true until the task schedules. One visible consequence is the socket cgroup attribution: cgroup_sk_alloc() treats the allocation as interrupt context and assigns the socket to the root cgroup. A socket opened from the SIGTRAP handler can then avoid a BPF_CGROUP_INET_SOCK_CREATE policy attached to the task's own cgroup. Return as soon as kprobe_running() reports no active probe. The same check has appeared in [PATCH v10 2/4] of the original LoongArch Kprobes series, but was dropped before the feature reached mainline. Cc: stable@vger.kernel.org Fixes: 6d4cc40fb5f5 ("LoongArch: Add kprobes support") Link: https://lore.kernel.org/loongarch/1670575981-14389-3-git-send-email-yangtiezhu@loongson.cn/ Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Signed-off-by: Huacai Chen --- arch/loongarch/kernel/kprobes.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/loongarch/kernel/kprobes.c b/arch/loongarch/kernel/kprobes.c index 1985ed30dd16..ddfefea17472 100644 --- a/arch/loongarch/kernel/kprobes.c +++ b/arch/loongarch/kernel/kprobes.c @@ -275,6 +275,9 @@ bool kprobe_singlestep_handler(struct pt_regs *regs) struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); unsigned long addr = instruction_pointer(regs); + if (!cur) + return false; + if (cur && (kcb->kprobe_status & (KPROBE_HIT_SS | KPROBE_REENTER)) && ((unsigned long)&cur->ainsn.insn[1] == addr)) { restore_local_irqflag(kcb, regs); From 30419a0aa128135a81be917eaa3bd2f1a10c9ca3 Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Fri, 4 Sep 2026 21:44:43 +0800 Subject: [PATCH 0832/2366] LoongArch: BPF: Fix off-by-one error for insn_is_cast_user() In the LoongArch BPF JIT code, the branch offset represents the number of instructions. An offset of 1 means the target of the "beq" is the current PC plus 1 instruction (PC + 4 bytes). This matches the exact same path as the sequential non-branch execution, the "or" instruction is always executed for the cast_user JIT arm in build_insn(). If the pointer is not NULL, there is no side effect. But if the pointer is NULL, it is incorrectly combined with the base address and turns into a non-zero address, meaning a zero arena offset no longer casts to NULL. Fix this by changing the branch offset from 1 to 2, which properly skips the "or" instruction and jumps directly to the "move_reg" instruction if the pointer is NULL, ensuring the destination register is safely cleared to 0. Cc: stable@vger.kernel.org Fixes: 4fdb5dd8aeba ("LoongArch: BPF: Implement bpf_addr_space_cast instruction") Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/net/bpf_jit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index 1eb588e443c9..4da278900938 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -717,7 +717,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext move_reg(ctx, t1, src); emit_zext_32(ctx, t1, true); move_imm(ctx, dst, (ctx->user_vm_start >> 32) << 32, false); - emit_insn(ctx, beq, t1, LOONGARCH_GPR_ZERO, 1); + emit_insn(ctx, beq, t1, LOONGARCH_GPR_ZERO, 2); emit_insn(ctx, or, t1, dst, t1); move_reg(ctx, dst, t1); break; From f7a1064cce3b100b54780c68529176232d8eb01e Mon Sep 17 00:00:00 2001 From: Chaithanya Lagisetty Date: Fri, 4 Sep 2026 21:44:53 +0800 Subject: [PATCH 0833/2366] LoongArch: KVM: Free init resources if kvm_init() fails kvm_loongarch_init() calls kvm_loongarch_env_init() to allocate the per-CPU kvm_context (vmcs) and kvm_loongarch_ops and to register the perf callbacks, and then calls kvm_init(). If kvm_init() fails its result is returned directly, but since module_init() does not run the module_exit() stuff on failure, so kvm_loongarch_env_exit() is never called and those resources are leaked. So call kvm_loongarch_env_exit() when kvm_init() fails, matching the teardown-on-failure pattern used by riscv_kvm_init(). Cc: stable@vger.kernel.org Fixes: 2bd6ac687261 ("LoongArch: KVM: Implement kvm module related interface") Reviewed-by: Bibo Mao Signed-off-by: Chaithanya Lagisetty Signed-off-by: Huacai Chen --- arch/loongarch/kvm/main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/loongarch/kvm/main.c b/arch/loongarch/kvm/main.c index 3e1005526f4b..b6ddf5827c03 100644 --- a/arch/loongarch/kvm/main.c +++ b/arch/loongarch/kvm/main.c @@ -428,7 +428,11 @@ static int kvm_loongarch_init(void) if (r) return r; - return kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE); + r = kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE); + if (r) + kvm_loongarch_env_exit(); + + return r; } static void kvm_loongarch_exit(void) From 4af22177032ab2357bf551fbfcdebc8fd9f2502d Mon Sep 17 00:00:00 2001 From: Chaithanya Lagisetty Date: Fri, 4 Sep 2026 21:44:53 +0800 Subject: [PATCH 0834/2366] LoongArch: KVM: Add unregister helpers for the KVM interrupt devices The IPI/EIOINTC/PCH-PIC/DMSINTC KVM devices each have a helper that registers their kvm_device_ops, but there is no counterpart to remove them, so a caller that needs to undo a registration has to open-code kvm_unregister_device_ops() with the matching device type. Add kvm_loongarch_unregister_{ipi,eiointc,pch_pic,dmsintc}_device() next to the existing register helpers. kvm_unregister_device_ops() is a no-op when the corresponding device type is not currently registered. No functional change, as there are no callers yet. Cc: stable@vger.kernel.org Suggested-by: Bibo Mao Reviewed-by: Bibo Mao Signed-off-by: Chaithanya Lagisetty Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/kvm_dmsintc.h | 1 + arch/loongarch/include/asm/kvm_eiointc.h | 1 + arch/loongarch/include/asm/kvm_ipi.h | 1 + arch/loongarch/include/asm/kvm_pch_pic.h | 1 + arch/loongarch/kvm/intc/dmsintc.c | 5 +++++ arch/loongarch/kvm/intc/eiointc.c | 5 +++++ arch/loongarch/kvm/intc/ipi.c | 5 +++++ arch/loongarch/kvm/intc/pch_pic.c | 5 +++++ 8 files changed, 24 insertions(+) diff --git a/arch/loongarch/include/asm/kvm_dmsintc.h b/arch/loongarch/include/asm/kvm_dmsintc.h index 5a71b9ccbe78..7c0158764d84 100644 --- a/arch/loongarch/include/asm/kvm_dmsintc.h +++ b/arch/loongarch/include/asm/kvm_dmsintc.h @@ -20,6 +20,7 @@ struct dmsintc_state { }; int kvm_loongarch_register_dmsintc_device(void); +void kvm_loongarch_unregister_dmsintc_device(void); void dmsintc_inject_irq(struct kvm_vcpu *vcpu); int dmsintc_set_irq(struct kvm *kvm, u64 addr, int data, int level); int dmsintc_deliver_msi_to_vcpu(struct kvm *kvm, struct kvm_vcpu *vcpu, u32 vector, int level); diff --git a/arch/loongarch/include/asm/kvm_eiointc.h b/arch/loongarch/include/asm/kvm_eiointc.h index 8b7a2fa3f7f8..9633fbfc066d 100644 --- a/arch/loongarch/include/asm/kvm_eiointc.h +++ b/arch/loongarch/include/asm/kvm_eiointc.h @@ -79,6 +79,7 @@ struct loongarch_eiointc { }; int kvm_loongarch_register_eiointc_device(void); +void kvm_loongarch_unregister_eiointc_device(void); void eiointc_set_irq(struct loongarch_eiointc *s, int irq, int level); #endif /* __ASM_KVM_EIOINTC_H */ diff --git a/arch/loongarch/include/asm/kvm_ipi.h b/arch/loongarch/include/asm/kvm_ipi.h index 060163dfb4a3..d1d72d4bb8d1 100644 --- a/arch/loongarch/include/asm/kvm_ipi.h +++ b/arch/loongarch/include/asm/kvm_ipi.h @@ -41,5 +41,6 @@ struct ipi_state { #define IOCSR_ANY_SEND 0x158 int kvm_loongarch_register_ipi_device(void); +void kvm_loongarch_unregister_ipi_device(void); #endif diff --git a/arch/loongarch/include/asm/kvm_pch_pic.h b/arch/loongarch/include/asm/kvm_pch_pic.h index e74b3b742634..887b0431fd20 100644 --- a/arch/loongarch/include/asm/kvm_pch_pic.h +++ b/arch/loongarch/include/asm/kvm_pch_pic.h @@ -70,6 +70,7 @@ struct loongarch_pch_pic { struct kvm_kernel_irq_routing_entry; int kvm_loongarch_register_pch_pic_device(void); +void kvm_loongarch_unregister_pch_pic_device(void); void pch_pic_set_irq(struct loongarch_pch_pic *s, int irq, int level); int pch_msi_set_irq(struct kvm *kvm, struct kvm_kernel_irq_routing_entry *e, int level); diff --git a/arch/loongarch/kvm/intc/dmsintc.c b/arch/loongarch/kvm/intc/dmsintc.c index bb7285c49df3..c7d8841df96f 100644 --- a/arch/loongarch/kvm/intc/dmsintc.c +++ b/arch/loongarch/kvm/intc/dmsintc.c @@ -180,3 +180,8 @@ int kvm_loongarch_register_dmsintc_device(void) { return kvm_register_device_ops(&kvm_dmsintc_dev_ops, KVM_DEV_TYPE_LOONGARCH_DMSINTC); } + +void kvm_loongarch_unregister_dmsintc_device(void) +{ + kvm_unregister_device_ops(KVM_DEV_TYPE_LOONGARCH_DMSINTC); +} diff --git a/arch/loongarch/kvm/intc/eiointc.c b/arch/loongarch/kvm/intc/eiointc.c index 84d84bd432d7..80f78e07c74a 100644 --- a/arch/loongarch/kvm/intc/eiointc.c +++ b/arch/loongarch/kvm/intc/eiointc.c @@ -695,3 +695,8 @@ int kvm_loongarch_register_eiointc_device(void) { return kvm_register_device_ops(&kvm_eiointc_dev_ops, KVM_DEV_TYPE_LOONGARCH_EIOINTC); } + +void kvm_loongarch_unregister_eiointc_device(void) +{ + kvm_unregister_device_ops(KVM_DEV_TYPE_LOONGARCH_EIOINTC); +} diff --git a/arch/loongarch/kvm/intc/ipi.c b/arch/loongarch/kvm/intc/ipi.c index fcfaf1a66790..7b333a4a0430 100644 --- a/arch/loongarch/kvm/intc/ipi.c +++ b/arch/loongarch/kvm/intc/ipi.c @@ -463,3 +463,8 @@ int kvm_loongarch_register_ipi_device(void) { return kvm_register_device_ops(&kvm_ipi_dev_ops, KVM_DEV_TYPE_LOONGARCH_IPI); } + +void kvm_loongarch_unregister_ipi_device(void) +{ + kvm_unregister_device_ops(KVM_DEV_TYPE_LOONGARCH_IPI); +} diff --git a/arch/loongarch/kvm/intc/pch_pic.c b/arch/loongarch/kvm/intc/pch_pic.c index e7b77705c516..83fa2386cf81 100644 --- a/arch/loongarch/kvm/intc/pch_pic.c +++ b/arch/loongarch/kvm/intc/pch_pic.c @@ -500,3 +500,8 @@ int kvm_loongarch_register_pch_pic_device(void) { return kvm_register_device_ops(&kvm_pch_pic_dev_ops, KVM_DEV_TYPE_LOONGARCH_PCHPIC); } + +void kvm_loongarch_unregister_pch_pic_device(void) +{ + kvm_unregister_device_ops(KVM_DEV_TYPE_LOONGARCH_PCHPIC); +} From 910132bc7d72f26a8b288c2a38c32445a48d5be0 Mon Sep 17 00:00:00 2001 From: Chaithanya Lagisetty Date: Fri, 4 Sep 2026 21:44:54 +0800 Subject: [PATCH 0835/2366] LoongArch: KVM: Fix resource leak in kvm_loongarch_env_init() error path kvm_loongarch_env_init() allocates the per-CPU kvm_context (vmcs) and kvm_loongarch_ops, registers the perf callbacks, and then registers the IPI/EIOINTC/PCH-PIC/DMSINTC KVM devices. If any of those device registrations fails, the function returned the error directly, leaving everything acquired so far in place: vmcs and kvm_loongarch_ops are never freed, the perf callbacks stay registered, and all previously registered KVM device operations remain registered. kvm_loongarch_init() propagates the errors without calling kvm_loongarch_env_exit(), so nothing else cleans up either. Unwind the error path in reverse order of registration, so that each failure only undoes what had actually been set up. Use the same helpers in kvm_loongarch_env_exit() to remove the device registrations during normal teardown as well. Cc: stable@vger.kernel.org Fixes: c532de5a67a7 ("LoongArch: KVM: Add IPI device support") Reviewed-by: Bibo Mao Signed-off-by: Chaithanya Lagisetty Signed-off-by: Huacai Chen --- arch/loongarch/kvm/main.c | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/arch/loongarch/kvm/main.c b/arch/loongarch/kvm/main.c index b6ddf5827c03..236523d2449d 100644 --- a/arch/loongarch/kvm/main.c +++ b/arch/loongarch/kvm/main.c @@ -385,27 +385,52 @@ static int kvm_loongarch_env_init(void) /* Register LoongArch IPI interrupt controller interface. */ ret = kvm_loongarch_register_ipi_device(); if (ret) - return ret; + goto err_env; /* Register LoongArch EIOINTC interrupt controller interface. */ ret = kvm_loongarch_register_eiointc_device(); if (ret) - return ret; + goto err_ipi; /* Register LoongArch PCH-PIC interrupt controller interface. */ ret = kvm_loongarch_register_pch_pic_device(); if (ret) - return ret; + goto err_eiointc; /* Register LoongArch DMSINTC interrupt contrroller interface */ - if (cpu_has_msgint) + if (cpu_has_msgint) { ret = kvm_loongarch_register_dmsintc_device(); + if (ret) + goto err_pch_pic; + } + + return 0; + +err_pch_pic: + kvm_loongarch_unregister_pch_pic_device(); +err_eiointc: + kvm_loongarch_unregister_eiointc_device(); +err_ipi: + kvm_loongarch_unregister_ipi_device(); +err_env: + kvm_unregister_perf_callbacks(); + kfree(kvm_loongarch_ops); + kvm_loongarch_ops = NULL; + free_percpu(vmcs); + vmcs = NULL; return ret; } static void kvm_loongarch_env_exit(void) { + if (cpu_has_msgint) + kvm_loongarch_unregister_dmsintc_device(); + + kvm_loongarch_unregister_pch_pic_device(); + kvm_loongarch_unregister_eiointc_device(); + kvm_loongarch_unregister_ipi_device(); + if (vmcs) free_percpu(vmcs); From 40bdbb4bfa730400e8b383d6f5931f90aebb5f54 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Fri, 4 Sep 2026 21:44:54 +0800 Subject: [PATCH 0836/2366] LoongArch: KVM: Remove unused function kvm_arch_flush_remote_tlbs_memslot() Function kvm_arch_flush_remote_tlbs_memslot() is not called any more, so remove this API. Reviewed-by: Tao Cui Signed-off-by: Bibo Mao Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/kvm_host.h | 1 - arch/loongarch/kvm/mmu.c | 6 ------ 2 files changed, 7 deletions(-) diff --git a/arch/loongarch/include/asm/kvm_host.h b/arch/loongarch/include/asm/kvm_host.h index 23cfbecebbd7..5682b8c847d1 100644 --- a/arch/loongarch/include/asm/kvm_host.h +++ b/arch/loongarch/include/asm/kvm_host.h @@ -350,7 +350,6 @@ static inline void kvm_arch_vcpu_block_finish(struct kvm_vcpu *vcpu) {} static inline void kvm_arch_free_memslot(struct kvm *kvm, struct kvm_memory_slot *slot) {} void kvm_check_vpid(struct kvm_vcpu *vcpu); enum hrtimer_restart kvm_swtimer_wakeup(struct hrtimer *timer); -void kvm_arch_flush_remote_tlbs_memslot(struct kvm *kvm, const struct kvm_memory_slot *memslot); void kvm_init_vmcs(struct kvm *kvm); void kvm_exc_entry(void); int kvm_enter_guest(struct kvm_run *run, struct kvm_vcpu *vcpu); diff --git a/arch/loongarch/kvm/mmu.c b/arch/loongarch/kvm/mmu.c index e104897aa532..2c08402bfd3e 100644 --- a/arch/loongarch/kvm/mmu.c +++ b/arch/loongarch/kvm/mmu.c @@ -939,9 +939,3 @@ int kvm_handle_mm_fault(struct kvm_vcpu *vcpu, unsigned long gpa, bool write, in void kvm_arch_sync_dirty_log(struct kvm *kvm, struct kvm_memory_slot *memslot) { } - -void kvm_arch_flush_remote_tlbs_memslot(struct kvm *kvm, - const struct kvm_memory_slot *memslot) -{ - kvm_flush_remote_tlbs(kvm); -} From 27a9bfee3bbcb3cabb77797354f07e0e44e49831 Mon Sep 17 00:00:00 2001 From: Zeng Chi Date: Fri, 4 Sep 2026 21:45:13 +0800 Subject: [PATCH 0837/2366] LoongArch: KVM: Preserve memslot arch flags on KVM_MR_FLAGS_ONLY kvm_arch_prepare_memory_region() computes new->arch.flags, i.e. whether a memslot is KVM_MEM_HUGEPAGE_CAPABLE or KVM_MEM_HUGEPAGE_INCAPABLE, only for KVM_MR_CREATE and KVM_MR_MOVE, and returns early for every other change. But the generic code allocates a zeroed memslot for every change and never copies old->arch, so after a KVM_MR_FLAGS_ONLY update, e.g. toggling KVM_MEM_LOG_DIRTY_PAGES for live migration, the active memslot has arch.flags == 0. With both flags clear, fault_supports_huge_mapping() falls through to the alignment check on the HVA range alone, which no longer verifies that the GPA and HVA have the same offset within a PMD. A memslot that was marked KVM_MEM_HUGEPAGE_INCAPABLE because of a GPA/HVA offset mismatch can then be mapped with PMD entries on read faults, and since kvm_map_page() aligns the gfn and the pfn independently, the guest ends up accessing the wrong host pages, exactly the "d -> f, e -> g" case described in the comment above the check. Carry the arch flags over from the old memslot for KVM_MR_FLAGS_ONLY, as the GPA, HVA and size are guaranteed to be unchanged for that case. Cc: stable@vger.kernel.org Fixes: 7ab6fb505b2a ("LoongArch: KVM: Optimization for memslot hugepage checking") Tested-by: Tao Cui Reviewed-by: Tao Cui Reviewed-by: Bibo Mao Signed-off-by: Zeng Chi Signed-off-by: Huacai Chen --- arch/loongarch/kvm/mmu.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/loongarch/kvm/mmu.c b/arch/loongarch/kvm/mmu.c index 2c08402bfd3e..3e9a0b285fd2 100644 --- a/arch/loongarch/kvm/mmu.c +++ b/arch/loongarch/kvm/mmu.c @@ -383,6 +383,16 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm, const struct kvm_memory_slot hva_t hva_start; size_t size, gpa_offset, hva_offset; + /* + * The generic code allocates a fresh, zeroed memslot for every change, + * so the arch flags computed below must be carried over when only the + * userspace flags change, e.g. when dirty logging is toggled. + */ + if (change == KVM_MR_FLAGS_ONLY) { + new->arch = old->arch; + return 0; + } + if ((change != KVM_MR_MOVE) && (change != KVM_MR_CREATE)) return 0; /* From 501514d6ebd2111c353a1296f25dbe22fbd64657 Mon Sep 17 00:00:00 2001 From: Zeng Chi Date: Fri, 4 Sep 2026 21:45:13 +0800 Subject: [PATCH 0838/2366] LoongArch: KVM: Validate MSI data before routing it to EIOINTC pch_msi_set_irq() passes e->msi.data straight into eiointc_set_irq() as the irq number. The MSI data comes from userspace, that either via a KVM_IRQ_ROUTING_MSI entry set with KVM_SET_GSI_ROUTING (used by irqfd and KVM_IRQ_LINE) or directly via KVM_SIGNAL_MSI, and is never checked against EIOINTC_IRQS. eiointc_set_irq() uses the value with __set_bit()/__clear_bit() on the 256-bit isr bitmap, eiointc_update_irq() then indexes sw_coremap[] and the per-cpu coreisr/sw_coreisr bitmaps with it. Therefore a data value >= 256 reads and writes memory past the end of those arrays, i.e. any process holding a VM fd can corrupt kernel memory beyond the allocation of loongarch_eiointc. Reject MSI data that doesn't fit in the EIOINTC irq space. The DMSINTC path is unaffected as it decodes the vector from the address and masks it. Cc: stable@vger.kernel.org Fixes: 1928254c5ccb ("LoongArch: KVM: Add irqfd support") Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260531140921.1B1181F00893@smtp.kernel.org/ Reviewed-by: Tao Cui Reviewed-by: Bibo Mao Signed-off-by: Zeng Chi Signed-off-by: Huacai Chen --- arch/loongarch/kvm/intc/pch_pic.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/loongarch/kvm/intc/pch_pic.c b/arch/loongarch/kvm/intc/pch_pic.c index 83fa2386cf81..2b63b0c2c7ce 100644 --- a/arch/loongarch/kvm/intc/pch_pic.c +++ b/arch/loongarch/kvm/intc/pch_pic.c @@ -78,6 +78,9 @@ int pch_msi_set_irq(struct kvm *kvm, struct kvm_kernel_irq_routing_entry *e, int return dmsintc_set_irq(kvm, msg_addr, e->msi.data, level); } + if (e->msi.data >= EIOINTC_IRQS) + return -EINVAL; + eiointc_set_irq(kvm->arch.eiointc, e->msi.data, level); return 0; From 9296375902579f9b0e456bbb76e5cf179e5a4e0b Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Fri, 4 Sep 2026 21:45:13 +0800 Subject: [PATCH 0839/2366] LoongArch: KVM: Fix TOCTOU race on pv_features In kvm_loongarch_cpucfg_set_attr() the check-then-set on kvm->arch.pv_features is lockless, so two vCPUs can race past the validation and set different values. Add a spinlock to protect it. Cc: stable@vger.kernel.org Reviewed-by: Bibo Mao Signed-off-by: Tao Cui Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/kvm_host.h | 1 + arch/loongarch/kvm/vcpu.c | 6 +++++- arch/loongarch/kvm/vm.c | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/arch/loongarch/include/asm/kvm_host.h b/arch/loongarch/include/asm/kvm_host.h index 5682b8c847d1..65d91c3ce313 100644 --- a/arch/loongarch/include/asm/kvm_host.h +++ b/arch/loongarch/include/asm/kvm_host.h @@ -125,6 +125,7 @@ struct kvm_arch { unsigned int pte_shifts[MAX_PGTABLE_LEVELS]; unsigned int root_level; spinlock_t phyid_map_lock; + spinlock_t pv_setting_lock; struct kvm_phyid_map *phyid_map; /* Enabled PV features */ unsigned long pv_features; diff --git a/arch/loongarch/kvm/vcpu.c b/arch/loongarch/kvm/vcpu.c index ed9e092c97ba..8e028be3f0a9 100644 --- a/arch/loongarch/kvm/vcpu.c +++ b/arch/loongarch/kvm/vcpu.c @@ -1165,10 +1165,14 @@ static int kvm_loongarch_cpucfg_set_attr(struct kvm_vcpu *vcpu, return -EINVAL; /* All vCPUs need set the same PV features */ + spin_lock(&kvm->arch.pv_setting_lock); if ((kvm->arch.pv_features & LOONGARCH_PV_FEAT_UPDATED) - && ((kvm->arch.pv_features & valid) != val)) + && ((kvm->arch.pv_features & valid) != val)) { + spin_unlock(&kvm->arch.pv_setting_lock); return -EINVAL; + } kvm->arch.pv_features = val | LOONGARCH_PV_FEAT_UPDATED; + spin_unlock(&kvm->arch.pv_setting_lock); return 0; default: return -ENXIO; diff --git a/arch/loongarch/kvm/vm.c b/arch/loongarch/kvm/vm.c index 0a51931d6f6e..6dabb227a732 100644 --- a/arch/loongarch/kvm/vm.c +++ b/arch/loongarch/kvm/vm.c @@ -76,6 +76,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) return -ENOMEM; } spin_lock_init(&kvm->arch.phyid_map_lock); + spin_lock_init(&kvm->arch.pv_setting_lock); kvm_init_vmcs(kvm); kvm_vm_init_features(kvm); From a2628ce4ddb6873e35380a42396d17a66e704a1a Mon Sep 17 00:00:00 2001 From: Haiyong Sun Date: Fri, 4 Sep 2026 21:45:27 +0800 Subject: [PATCH 0840/2366] perf build: Add clang and rust target flags for LoongArch Add missing CLANG_TARGET_FLAGS_loongarch and RUST_TARGET_FLAGS_loongarch so that perf can be built with clang and enable rust cross compilation. Cc: stable@vger.kernel.org Acked-by: Miguel Ojeda Acked-by: Dmitrii Dolgov <9erthalion6@gmail.com> Signed-off-by: Haiyong Sun Signed-off-by: WANG Rui Signed-off-by: Huacai Chen --- tools/perf/Makefile.config | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config index 0ba307e78fe1..4d5993da9f94 100644 --- a/tools/perf/Makefile.config +++ b/tools/perf/Makefile.config @@ -27,6 +27,7 @@ CFLAGS += -fno-strict-aliasing ifeq ($(CC_NO_CLANG), 0) CLANG_TARGET_FLAGS_arm := arm-linux-gnueabi CLANG_TARGET_FLAGS_arm64 := aarch64-linux-gnu + CLANG_TARGET_FLAGS_loongarch := loongarch64-linux-gnu CLANG_TARGET_FLAGS_m68k := m68k-linux-gnu CLANG_TARGET_FLAGS_mips := mipsel-linux-gnu CLANG_TARGET_FLAGS_powerpc := powerpc64le-linux-gnu @@ -1142,6 +1143,7 @@ ifndef NO_RUST ifneq ($(CROSS_COMPILE),) RUST_TARGET_FLAGS_arm := arm-unknown-linux-gnueabi RUST_TARGET_FLAGS_arm64 := aarch64-unknown-linux-gnu + RUST_TARGET_FLAGS_loongarch := loongarch64-unknown-linux-gnu RUST_TARGET_FLAGS_m68k := m68k-unknown-linux-gnu RUST_TARGET_FLAGS_mips := mipsel-unknown-linux-gnu RUST_TARGET_FLAGS_powerpc := powerpc64le-unknown-linux-gnu From 5ad0af4f4367202b1bc71813052fe39b5116cdb9 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 17 Aug 2026 12:33:24 +0200 Subject: [PATCH 0841/2366] thermal: sysfs: switch to use scnprintf() to suppress truncation warning Switch the sysfs code to use scnprintf() to avoid warnings about potential truncation of the names of the sysfs attributes. We can't increase the buffer size because the size is the part of an ABI for some reason. Note, with the current size of buffer the affected attributes have a room for up to 1000 names, which ought to be enough for all cases. There is no functional change, as the same limitation was implied before. Fixes: c56f5c0342df ("Thermal: Make Thermal trip points writeable") Signed-off-by: Andy Shevchenko Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20260817103324.1020212-1-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_sysfs.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c index adbcb2c011e8..96fe5d591a0a 100644 --- a/drivers/thermal/thermal_sysfs.c +++ b/drivers/thermal/thermal_sysfs.c @@ -400,8 +400,8 @@ static int create_trip_attrs(struct thermal_zone_device *tz) struct thermal_trip_attrs *trip_attrs = &td->trip_attrs; /* create trip type attribute */ - snprintf(trip_attrs->type.name, THERMAL_NAME_LENGTH, - "trip_point_%d_type", i); + scnprintf(trip_attrs->type.name, sizeof(trip_attrs->type.name), + "trip_point_%d_type", i); sysfs_attr_init(&trip_attrs->type.attr.attr); trip_attrs->type.attr.attr.name = trip_attrs->type.name; @@ -410,8 +410,8 @@ static int create_trip_attrs(struct thermal_zone_device *tz) attrs[i] = &trip_attrs->type.attr.attr; /* create trip temp attribute */ - snprintf(trip_attrs->temp.name, THERMAL_NAME_LENGTH, - "trip_point_%d_temp", i); + scnprintf(trip_attrs->temp.name, sizeof(trip_attrs->temp.name), + "trip_point_%d_temp", i); sysfs_attr_init(&trip_attrs->temp.attr.attr); trip_attrs->temp.attr.attr.name = trip_attrs->temp.name; @@ -423,8 +423,8 @@ static int create_trip_attrs(struct thermal_zone_device *tz) } attrs[i + tz->num_trips] = &trip_attrs->temp.attr.attr; - snprintf(trip_attrs->hyst.name, THERMAL_NAME_LENGTH, - "trip_point_%d_hyst", i); + scnprintf(trip_attrs->hyst.name, sizeof(trip_attrs->hyst.name), + "trip_point_%d_hyst", i); sysfs_attr_init(&trip_attrs->hyst.attr.attr); trip_attrs->hyst.attr.attr.name = trip_attrs->hyst.name; From e67091609cf85962f64391c1b0f93d4cbfcd4e22 Mon Sep 17 00:00:00 2001 From: caina Date: Fri, 21 Aug 2026 17:17:20 +0800 Subject: [PATCH 0842/2366] Revert "irqchip/mbigen: Fix mbigen node address layout" This reverts commit 6be6cba9c4371d27f78d900ccfe34bb880d9ee20. Commit 6be6cba9c437 ("irqchip/mbigen: Fix mbigen node address layout") appears to cause a regression on Hi1616. On-board hns NIC has two ports, enahisic2i0 and enahisic2i1, both behind mbigen-v2. Port 0 works; port 1 cannot pass any traffic. Their interrupt pins fall on different mbigen nodes: enahisic2i0: pins 1152-1198 -> all in node 9 enahisic2i1: pins 1200-1246 -> node 9 (1200-1215) + node 10 (1216-1246) (nid = (hwirq - 64) / 128 + 1; pin 1215 = node 9, pin 1216 = node 10) /proc/interrupts shows the break happens exactly at the node boundary: enahisic2i1-rx0 pin 1200 count 102 <- node 9 enahisic2i1-rx5 pin 1215 count 1 <- node 9, last pin enahisic2i1-tx5 pin 1216 count 0 <- node 10, first pin enahisic2i1-rx6 pin 1218 count 0 <- node 10 ...all node 10 pins stay at zero. Port 0 (entirely node 9) is unaffected. Reverting the commit restores normal operation. The commit assumes CLEAR occupies a full 4 KB page at [0xa000, 0xb000) and collides with node 10, so node 10+ gets shifted by 0x1000. But get_mbigen_clear_reg() uses flat, chip-wide addressing -- it never multiplies by the node ID: *addr = (hwirq / 32) * 4 + REG_MBIGEN_CLEAR_OFFSET; /* 0xa000 */ Over the valid hwirq range [64, 1407], CLEAR only spans 0xa008-0xa0af (168 bytes). Node 10's registers are: TYPE: 0xa000-0xa00f (16 B) overlaps CLEAR by 8 B (0xa008-0xa00f) VEC: 0xa200-0xa3ff (512 B) no overlap with CLEAR Shifting the whole page moves VEC from 0xa200 to 0xb200. The hardware reads the event ID from the fixed silicon address 0xa200 on interrupt firing, but software wrote it to 0xb200 -- so the hardware gets an uninitialised value and the interrupt is lost. The only real overlap is 8 bytes of TYPE. It can only trigger when a single mbigen instance has devices on both node 1 (CLEAR 0xa008) and node 10 (TYPE 0xa008). On Hi1616 those nodes are on separate mbigen instances, so it never triggers. Fixes: 6be6cba9c4371d27f78d900ccfe34bb880d9ee20 ("irqchip/mbigen: Fix mbigen node address layout") Suggested-by: Marc Zyngier Signed-off-by: caina Signed-off-by: Thomas Gleixner Acked-by: Yipeng Zou Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260821091720.16665-1-caina@uniontech.com --- drivers/irqchip/irq-mbigen.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c index 6f69f4e5dbac..12919836dadb 100644 --- a/drivers/irqchip/irq-mbigen.c +++ b/drivers/irqchip/irq-mbigen.c @@ -64,20 +64,6 @@ struct mbigen_device { void __iomem *base; }; -static inline unsigned int get_mbigen_node_offset(unsigned int nid) -{ - unsigned int offset = nid * MBIGEN_NODE_OFFSET; - - /* - * To avoid touched clear register in unexpected way, we need to directly - * skip clear register when access to more than 10 mbigen nodes. - */ - if (nid >= (REG_MBIGEN_CLEAR_OFFSET / MBIGEN_NODE_OFFSET)) - offset += MBIGEN_NODE_OFFSET; - - return offset; -} - static inline unsigned int get_mbigen_vec_reg(irq_hw_number_t hwirq) { unsigned int nid, pin; @@ -86,7 +72,8 @@ static inline unsigned int get_mbigen_vec_reg(irq_hw_number_t hwirq) nid = hwirq / IRQS_PER_MBIGEN_NODE + 1; pin = hwirq % IRQS_PER_MBIGEN_NODE; - return pin * 4 + get_mbigen_node_offset(nid) + REG_MBIGEN_VEC_OFFSET; + return pin * 4 + nid * MBIGEN_NODE_OFFSET + + REG_MBIGEN_VEC_OFFSET; } static inline void get_mbigen_type_reg(irq_hw_number_t hwirq, @@ -101,7 +88,8 @@ static inline void get_mbigen_type_reg(irq_hw_number_t hwirq, *mask = 1 << (irq_ofst % 32); ofst = irq_ofst / 32 * 4; - *addr = ofst + get_mbigen_node_offset(nid) + REG_MBIGEN_TYPE_OFFSET; + *addr = ofst + nid * MBIGEN_NODE_OFFSET + + REG_MBIGEN_TYPE_OFFSET; } static inline void get_mbigen_clear_reg(irq_hw_number_t hwirq, From d31fbbade43f880b7e59e2b3a72722fe2725d93f Mon Sep 17 00:00:00 2001 From: Ju Nan Date: Fri, 21 Aug 2026 10:47:57 +0800 Subject: [PATCH 0843/2366] irqchip/stm32mp-exti: Fix the unit of the hwspinlock timeout HWSPNLCK_TIMEOUT is passed to hwspin_lock_timeout_in_atomic(), whose timeout argument is in milliseconds, not microseconds: atomic_delay += HWSPINLOCK_RETRY_DELAY_US; if (atomic_delay > to * 1000) return -ETIMEDOUT; So stm32mp_exti_set_type() asks for a 1 second timeout where the comment next to the macro says it wants 1 millisecond. The semaphore is polled with udelay() from a section that holds chip_data->rlock, a raw_spinlock_t, so preemption stays disabled for the whole wait on every configuration, PREEMPT_RT included. The hwspinlock core documents this explicitly: If the mode is HWLOCK_IN_ATOMIC (called from an atomic context) the timeout is handled with busy-waiting delays, hence shall not exceed few msecs. Fixes: 5257169ade8c ("irqchip/stm32-exti: Use the hwspin_lock_timeout_in_atomic() API") Signed-off-by: Ju Nan Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Reviewed-by: Antonio Borneo Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260821024756.24927-2-junan76@163.com --- drivers/irqchip/irq-stm32mp-exti.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-stm32mp-exti.c b/drivers/irqchip/irq-stm32mp-exti.c index bf3a2def69ca..a19e91fbd010 100644 --- a/drivers/irqchip/irq-stm32mp-exti.c +++ b/drivers/irqchip/irq-stm32mp-exti.c @@ -22,7 +22,7 @@ #define IRQS_PER_BANK 32 -#define HWSPNLCK_TIMEOUT 1000 /* usec */ +#define HWSPNLCK_TIMEOUT_MS 1 #define EXTI_EnCIDCFGR(n) (0x180 + (n) * 4) #define EXTI_HWCFGR1 0x3f0 @@ -376,7 +376,7 @@ static int stm32mp_exti_set_type(struct irq_data *d, unsigned int type) raw_spin_lock(&chip_data->rlock); if (hwlock) { - err = hwspin_lock_timeout_in_atomic(hwlock, HWSPNLCK_TIMEOUT); + err = hwspin_lock_timeout_in_atomic(hwlock, HWSPNLCK_TIMEOUT_MS); if (err) { pr_err("%s can't get hwspinlock (%d)\n", __func__, err); goto unlock; From 048029ba1c793f8cabc4ad5eea765da01903f8f1 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 4 Sep 2026 10:43:14 +0200 Subject: [PATCH 0844/2366] bpf: Require MEM_PERCPU for percpu kptr stores map_kptr_match_type() treats perm_flags as the set of register type flags that a kptr field permits. Adding MEM_PERCPU to that set for BPF_KPTR_PERCPU does not require the source register to carry it, however. The subset test consequently accepts both a plain bpf_obj_new() allocation and a referenced kernel pointer into a __percpu_kptr map field. Loads from the field are always marked MEM_PERCPU. Consumers then treat the stored value as the cookie returned by bpf_percpu_obj_new(): per-CPU pointer helpers relocate it, and map teardown selects the per-CPU free path. A plain allocation can therefore provide an arbitrary kernel read/write, while a kernel pointer can be relocated into an invalid address or sent through a missing destructor. Require the source MEM_PERCPU flag to match the destination field kind. This preserves valid bpf_percpu_obj_new() stores and rejects both the program-BTF and kernel-BTF variants. Fixes: 36d8bdf75a93 ("bpf: Add alloc/xchg/direct_access support for local percpu kptr") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904084325.52250-2-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index fde5d046b6e3..19c932e8c533 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -4495,6 +4495,13 @@ static int map_kptr_match_type(struct bpf_verifier_env *env, if (type_flag(reg->type) & ~perm_flags) goto bad_type; + /* + * A BPF_KPTR_PERCPU field is read back as MEM_PERCPU, so the value + * stored in it must carry the same flag. + */ + if ((kptr_field->type == BPF_KPTR_PERCPU) != !!(reg->type & MEM_PERCPU)) + goto bad_type; + /* We need to verify reg->type and reg->btf, before accessing reg->btf */ reg_name = btf_type_name(reg->btf, reg->btf_id); From 17487b31f479c85eda3685e8e44242358bd68f23 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 4 Sep 2026 10:43:15 +0200 Subject: [PATCH 0845/2366] selftests/bpf: Reject non-percpu values in percpu kptr fields Add verifier coverage for the two ways a non-percpu pointer can be stored in a __percpu_kptr field: a program-BTF local allocation returned by bpf_obj_new(), and a referenced kernel-BTF task_struct pointer. Without the verifier fix, both programs are unexpectedly accepted and the negative tests fail. Requiring MEM_PERCPU makes both programs fail verification with the expected invalid-kptr diagnostic. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904084325.52250-3-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/percpu_alloc_fail.c | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c b/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c index 08379c3b6a03..3701f4ea58c7 100644 --- a/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c +++ b/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c @@ -33,6 +33,20 @@ struct { __type(value, struct elem); } array SEC(".maps"); +struct kernel_percpu_elem { + struct task_struct __percpu_kptr *task; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, int); + __type(value, struct kernel_percpu_elem); +} kernel_percpu_array SEC(".maps"); + +struct task_struct *bpf_task_from_pid(s32 pid) __ksym; +void bpf_task_release(struct task_struct *p) __ksym; + long ret; SEC("?fentry/bpf_fentry_test1") @@ -137,6 +151,51 @@ int BPF_PROG(test_array_map_5) return 0; } +SEC("?syscall") +__failure __msg("invalid kptr access, R2 type=trusted_ptr_ expected=ptr_task_struct") +int reject_kernel_ptr_into_percpu_kptr(void *ctx) +{ + struct kernel_percpu_elem *e; + struct task_struct *p, *old; + int index = 0; + + e = bpf_map_lookup_elem(&kernel_percpu_array, &index); + if (!e) + return 0; + + p = bpf_task_from_pid(1); + if (!p) + return 0; + + old = bpf_kptr_xchg(&e->task, p); + if (old) + bpf_task_release(old); + return 0; +} + +SEC("?fentry.s/bpf_fentry_test1") +__failure __msg("invalid kptr access, R2 type=ptr_ expected=ptr_val_t") +int BPF_PROG(reject_plain_alloc_into_percpu_kptr) +{ + struct val_t __percpu_kptr *old; + struct val_t *p; + struct elem *e; + int index = 0; + + e = bpf_map_lookup_elem(&array, &index); + if (!e) + return 0; + + p = bpf_obj_new(struct val_t); + if (!p) + return 0; + + old = bpf_kptr_xchg(&e->pc, p); + if (old) + bpf_percpu_obj_drop(old); + return 0; +} + SEC("?fentry.s/bpf_fentry_test1") __failure __msg("bpf_percpu_obj_new type ID argument must be of a struct of scalars") int BPF_PROG(test_array_map_6) From dc36739e5cc9f60485418a910b42bc95339218d2 Mon Sep 17 00:00:00 2001 From: Ning Ding Date: Fri, 4 Sep 2026 10:43:16 +0200 Subject: [PATCH 0846/2366] bpf: Keep refcount_acquire nullable for borrowed RCU kptrs bpf_refcount_acquire() is fallible for a borrowed reference because the object may have reached a zero refcount. The verifier therefore keeps KF_RET_NULL on the return value unless the argument is an owning reference. An RCU-protected load of a local kptr is marked MEM_ALLOC, but it only receives NON_OWN_REF when the pointee contains a graph node. A refcounted object without a graph node consequently looks like an owning reference even though the loaded register has no acquired reference state. If the program drops the last real reference while remaining in the RCU critical section, refcount_inc_not_zero() returns NULL while the verifier treats the result as non-NULL. Only classify the argument as owning when it is backed by a verifier-tracked reference. This retains the non-NULL return for pointers from bpf_obj_new(), bpf_kptr_xchg(), or an earlier successful acquisition, while requiring a NULL check for borrowed RCU kptrs. Fixes: 1b12171533a9 ("bpf: Mark direct ld of stashed bpf_{rb,list}_node as non-owning ref") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Ning Ding [ kkd: Rewrote commit log ] Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904084325.52250-4-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 19c932e8c533..3af8bd838b82 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -13185,7 +13185,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me bpf_diag_reg_type_plain(env, reg->type)); return -EINVAL; } - if (!type_is_non_owning_ref(reg->type)) + if (!type_is_non_owning_ref(reg->type) && reg_is_referenced(env, reg)) meta->arg_owning_ref = true; rec = reg_btf_record(reg); From 2edd8339468e4bf0feecb3398aaad25fd7b84286 Mon Sep 17 00:00:00 2001 From: Ning Ding Date: Fri, 4 Sep 2026 10:43:17 +0200 Subject: [PATCH 0847/2366] selftests/bpf: Test borrowed refcount acquisition nullability Add verifier coverage for the distinction between owning and borrowed arguments to bpf_refcount_acquire(). An owning pointer returned by bpf_obj_new() must continue producing a non-NULL result without an extra check. An RCU-loaded local kptr is only borrowed, so a checked result must load successfully while passing an unchecked result to bpf_obj_drop() must be rejected as possibly NULL. Use a sleepable syscall program for the borrowed cases so the explicit RCU critical section is what permits the local kptr load. Without the verifier fix, the unchecked case is incorrectly accepted. With it, the verifier rejects the possibly NULL argument. Signed-off-by: Ning Ding [ kkd: Rewrote commit log ] Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904084325.52250-5-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/refcounted_kptr.c | 61 +++++++++++++++++++ .../bpf/progs/refcounted_kptr_fail.c | 48 +++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr.c b/tools/testing/selftests/bpf/progs/refcounted_kptr.c index 61906f48025c..cae00f7b0a24 100644 --- a/tools/testing/selftests/bpf/progs/refcounted_kptr.c +++ b/tools/testing/selftests/bpf/progs/refcounted_kptr.c @@ -23,6 +23,15 @@ struct map_value { struct node_data __kptr *node; }; +struct node_refcount_only { + long key; + struct bpf_refcount refcount; +}; + +struct map_value_refcount_only { + struct node_refcount_only __kptr *node; +}; + struct { __uint(type, BPF_MAP_TYPE_ARRAY); __type(key, int); @@ -30,6 +39,13 @@ struct { __uint(max_entries, 2); } stashed_nodes SEC(".maps"); +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, int); + __type(value, struct map_value_refcount_only); + __uint(max_entries, 1); +} stashed_refcount_only SEC(".maps"); + struct node_acquire { long key; long data; @@ -832,6 +848,51 @@ long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx) return 0; } +SEC("tc") +__success +long refcount_acquire_owning_input_no_null_check(void *ctx) +{ + struct node_refcount_only *n, *m; + + n = bpf_obj_new(typeof(*n)); + if (!n) + return 1; + + m = bpf_refcount_acquire(n); + bpf_obj_drop(m); + bpf_obj_drop(n); + + return 0; +} + +SEC("?syscall") +__success +long refcount_acquire_rcu_map_kptr_null_checked(void *ctx) +{ + struct map_value_refcount_only *mapval; + struct node_refcount_only *n, *m; + int idx = 0; + + mapval = bpf_map_lookup_elem(&stashed_refcount_only, &idx); + if (!mapval) + return 1; + + bpf_rcu_read_lock(); + n = mapval->node; + if (!n) { + bpf_rcu_read_unlock(); + return 2; + } + m = bpf_refcount_acquire(n); + bpf_rcu_read_unlock(); + + if (!m) + return 3; + bpf_obj_drop(m); + + return 0; +} + static long __stash_map_empty_xchg(struct node_data *n, int idx) { struct map_value *mapval = bpf_map_lookup_elem(&stashed_nodes, &idx); diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c index eaaed0859f94..7d2f8897e5ad 100644 --- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c +++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c @@ -19,6 +19,15 @@ struct node_refcounted { struct bpf_refcount refcount; }; +struct node_refcount_only { + long key; + struct bpf_refcount refcount; +}; + +struct map_value_refcount_only { + struct node_refcount_only __kptr *node; +}; + extern void bpf_rcu_read_lock(void) __ksym; extern void bpf_rcu_read_unlock(void) __ksym; @@ -28,6 +37,13 @@ private(A) struct bpf_rb_root groot __contains(node_acquire, node); private(B) struct bpf_spin_lock lock; private(B) struct bpf_list_head head __contains(node_refcounted, list); +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, int); + __type(value, struct map_value_refcount_only); + __uint(max_entries, 1); +} stashed_refcount_only SEC(".maps"); + static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b) { struct node_acquire *node_a; @@ -89,6 +105,38 @@ long refcount_acquire_non_object(void *ctx) return bpf_refcount_acquire(ctx) != NULL; } +SEC("?syscall") +__failure __msg("Possibly NULL pointer passed to trusted R1") +long refcount_acquire_rcu_map_kptr_unchecked_drop(void *ctx) +{ + struct map_value_refcount_only *mapval; + struct node_refcount_only *tmp, *n, *m; + int idx = 0; + + /* Force Clang to emit complete BTF for struct node_refcount_only. */ + tmp = bpf_obj_new(typeof(*tmp)); + if (!tmp) + return 3; + bpf_obj_drop(tmp); + + mapval = bpf_map_lookup_elem(&stashed_refcount_only, &idx); + if (!mapval) + return 1; + + bpf_rcu_read_lock(); + n = mapval->node; + if (!n) { + bpf_rcu_read_unlock(); + return 2; + } + m = bpf_refcount_acquire(n); + bpf_rcu_read_unlock(); + + bpf_obj_drop(m); + + return 0; +} + SEC("?tc") __failure __msg("Unreleased reference id=3 alloc_insn={{[0-9]+}}") long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx) From cd6f72d7f38e10aa82fcbc745a6a9e58e0d8e366 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 4 Sep 2026 10:43:18 +0200 Subject: [PATCH 0848/2366] bpf: Clear NON_OWN_REF after RCU protection ends A local kptr load of an object containing a graph node is marked MEM_RCU and NON_OWN_REF while protected by RCU. When the last RCU read-side critical section ends, invalidate_rcu_protected_refs() removes MEM_RCU and marks the pointer PTR_UNTRUSTED, but leaves NON_OWN_REF set. The stale flag lets graph kfunc argument checks continue treating the pointer as a live borrowed reference. In particular, bpf_rbtree_remove() can accept a pointer after its protection ended and return it as a new owning reference, even though the object may already have been freed. Clear NON_OWN_REF when an RCU-protected pointer is demoted. A spin lock also provides implicit RCU protection, so invalidate non-owning references before demoting RCU-protected pointers when releasing the lock. Otherwise the demotion would clear the flag before invalidate_non_owning_refs() can find and invalidate those aliases. The demoted pointer remains available for fault-protected reads. Exempt such reads from the allocated-object reference-state assertion; writes through a fault-prone pointer are already rejected, and bpf_may_fault_on_deref() makes the surviving loads use BPF_PROBE_MEM. Fixes: 1b12171533a9 ("bpf: Mark direct ld of stashed bpf_{rb,list}_node as non-owning ref") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904084325.52250-6-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 3af8bd838b82..9c6ad157a61e 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -6045,7 +6045,13 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env, return -EACCES; } - if (type_is_alloc(reg->type) && !type_is_non_owning_ref(reg->type) && + /* + * A fault-prone allocated object may still be read through a + * BPF_PROBE_MEM load after its lifetime protection ends. Writes + * through such pointers were rejected above. + */ + if (type_is_alloc(reg->type) && !bpf_may_fault_on_deref(reg->type) && + !type_is_non_owning_ref(reg->type) && !(reg->type & MEM_RCU) && !reg_is_referenced(env, reg)) { verifier_bug(env, "allocated object must have a referenced id"); return -EFAULT; @@ -7423,10 +7429,14 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state lock); return -EINVAL; } + /* + * Invalidate non-owning refs before RCU demotion clears their + * NON_OWN_REF flag. + */ + invalidate_non_owning_refs(env); + if (!in_rcu_cs(env)) invalidate_rcu_protected_refs(env); - - invalidate_non_owning_refs(env); } return 0; } @@ -9526,7 +9536,7 @@ static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env) bpf_for_each_reg_in_vstate_mask(env->cur_state, state, reg, stack, clear_mask, ({ if (reg->type & MEM_RCU) { bpf_diag_mod_begin(env, reg, NULL, BPF_DIAG_MOD_WRITE); - reg->type &= ~(MEM_RCU | PTR_MAYBE_NULL); + reg->type &= ~(MEM_RCU | PTR_MAYBE_NULL | NON_OWN_REF); reg->type |= PTR_UNTRUSTED; bpf_diag_mod_end(env); } From 6668ed271eaefaa63e686bdfbedaeb7b8e492722 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 4 Sep 2026 10:43:19 +0200 Subject: [PATCH 0849/2366] selftests/bpf: Reject graph kptr use after RCU unlock Add a sleepable verifier test that loads a graph-node local kptr in an explicit RCU read-side critical section, then passes its node to bpf_rbtree_remove() after the section ends. Before the verifier fix, the stale NON_OWN_REF flag makes the node look like a live borrowed reference and the program is accepted. After the fix, the pointer is demoted without NON_OWN_REF and the graph kfunc argument is rejected. Also exercise a graph kptr loaded while a spin lock provides implicit RCU protection. The pointer must be invalidated when the lock is released, which guards the required ordering between non-owning-reference invalidation and RCU demotion. Update the existing fault-protected load test state description. The post-unlock pointer no longer carries NON_OWN_REF, but remains readable because the load is rewritten to use BPF_PROBE_MEM. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904084325.52250-7-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/rcu_read_lock.c | 6 +- .../bpf/progs/refcounted_kptr_fail.c | 75 +++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/bpf/progs/rcu_read_lock.c b/tools/testing/selftests/bpf/progs/rcu_read_lock.c index 31d4081c3a9f..cdb255addbc3 100644 --- a/tools/testing/selftests/bpf/progs/rcu_read_lock.c +++ b/tools/testing/selftests/bpf/progs/rcu_read_lock.c @@ -592,9 +592,9 @@ int non_own_ref_untrusted_ld(void *ctx) } bpf_rcu_read_unlock(); /* - * The unlock leaves node as PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED - * | NON_OWN_REF, and the load below has to get the BPF_PROBE_MEM - * rewrite for it, otherwise a bad address panics the kernel. + * The unlock leaves node as PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED, + * and the load below has to get the BPF_PROBE_MEM rewrite for it, + * otherwise a bad address panics the kernel. */ non_own_ref_key = node->key; return 0; diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c index 7d2f8897e5ad..f787ecf189d8 100644 --- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c +++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c @@ -28,6 +28,17 @@ struct map_value_refcount_only { struct node_refcount_only __kptr *node; }; +struct rcu_graph_node { + struct bpf_rb_node node; + long data; +}; + +struct rcu_graph_node *just_here_because_btf_bug; + +struct map_value_rcu_graph { + struct rcu_graph_node __kptr *node; +}; + extern void bpf_rcu_read_lock(void) __ksym; extern void bpf_rcu_read_unlock(void) __ksym; @@ -36,6 +47,8 @@ private(A) struct bpf_spin_lock glock; private(A) struct bpf_rb_root groot __contains(node_acquire, node); private(B) struct bpf_spin_lock lock; private(B) struct bpf_list_head head __contains(node_refcounted, list); +private(C) struct bpf_spin_lock graph_lock; +private(C) struct bpf_rb_root graph_root __contains(rcu_graph_node, node); struct { __uint(type, BPF_MAP_TYPE_ARRAY); @@ -44,6 +57,13 @@ struct { __uint(max_entries, 1); } stashed_refcount_only SEC(".maps"); +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, int); + __type(value, struct map_value_rcu_graph); + __uint(max_entries, 1); +} stashed_rcu_graph SEC(".maps"); + static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b) { struct node_acquire *node_a; @@ -137,6 +157,61 @@ long refcount_acquire_rcu_map_kptr_unchecked_drop(void *ctx) return 0; } +SEC("?syscall") +__failure +__msg("bpf_rbtree_remove can only take non-owning or refcounted " + "bpf_rb_node pointer") +long rbtree_remove_after_rcu_unlock(void *ctx) +{ + struct map_value_rcu_graph *mapval; + struct bpf_rb_node *rb_node; + struct rcu_graph_node *node; + int idx = 0; + + mapval = bpf_map_lookup_elem(&stashed_rcu_graph, &idx); + if (!mapval) + return 0; + + bpf_rcu_read_lock(); + node = mapval->node; + if (!node) { + bpf_rcu_read_unlock(); + return 0; + } + bpf_rcu_read_unlock(); + + bpf_spin_lock(&graph_lock); + rb_node = bpf_rbtree_remove(&graph_root, &node->node); + bpf_spin_unlock(&graph_lock); + if (rb_node) + bpf_obj_drop(container_of(rb_node, struct rcu_graph_node, node)); + + return 0; +} + +SEC("?syscall") +__failure __msg("invalid mem access 'scalar'") +long graph_kptr_after_spin_unlock(void *ctx) +{ + struct map_value_rcu_graph *mapval; + struct rcu_graph_node *node; + int idx = 0; + + mapval = bpf_map_lookup_elem(&stashed_rcu_graph, &idx); + if (!mapval) + return 0; + + bpf_spin_lock(&graph_lock); + node = mapval->node; + if (!node) { + bpf_spin_unlock(&graph_lock); + return 0; + } + bpf_spin_unlock(&graph_lock); + + return node->data; +} + SEC("?tc") __failure __msg("Unreleased reference id=3 alloc_insn={{[0-9]+}}") long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx) From 7441ee8276641bddaf1cba7bb75ef9c1458ceb3b Mon Sep 17 00:00:00 2001 From: Ning Ding Date: Fri, 4 Sep 2026 10:43:20 +0200 Subject: [PATCH 0850/2366] bpf: Reject untrusted allocated-object pointers When the final RCU read-side critical section ends, a local kptr is demoted to PTR_UNTRUSTED but retains MEM_ALLOC. The pointer may be NULL or may refer to an object whose lifetime is no longer protected. type_is_ptr_alloc_obj() nevertheless recognizes any PTR_TO_BTF_ID with MEM_ALLOC as a live allocated object. In particular, a refcount-only local kptr never carries NON_OWN_REF, so it still passes the bpf_refcount_acquire() argument check after RCU protection ends. The kfunc can then dereference NULL or stale memory. Make type_is_ptr_alloc_obj() reject PTR_UNTRUSTED pointers. Since type_is_non_owning_ref() is based on the same predicate, graph kfunc arguments obey the same live-object requirement. Fault-protected reads of the demoted pointer remain valid: writes are already rejected, and read fixups use bpf_may_fault_on_deref() rather than this predicate. Fixes: 1b12171533a9 ("bpf: Mark direct ld of stashed bpf_{rb,list}_node as non-owning ref") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Ning Ding [ kkd: Rewrote commit log ] Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904084325.52250-8-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- include/linux/bpf_verifier.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 1339c2f028db..36b65797877d 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -1381,7 +1381,9 @@ static inline bool bpf_type_has_unsafe_modifiers(u32 type) static inline bool type_is_ptr_alloc_obj(u32 type) { - return base_type(type) == PTR_TO_BTF_ID && type_flag(type) & MEM_ALLOC; + return base_type(type) == PTR_TO_BTF_ID && + type_flag(type) & MEM_ALLOC && + !(type_flag(type) & PTR_UNTRUSTED); } static inline bool type_is_non_owning_ref(u32 type) From 9492baf8532ca285c58b82a269acd7a57e205ae9 Mon Sep 17 00:00:00 2001 From: Ning Ding Date: Fri, 4 Sep 2026 10:43:21 +0200 Subject: [PATCH 0851/2366] selftests/bpf: Reject refcount acquisition after RCU unlock Add a sleepable verifier test that loads a refcount-only local kptr in an explicit RCU read-side critical section, ends the section, and passes the pointer to bpf_refcount_acquire(). The loaded pointer never carries NON_OWN_REF. After RCU unlock it retains MEM_ALLOC while becoming PTR_UNTRUSTED, which previously made the kfunc argument check accept it as a live allocated object. Expect verification to reject the untrusted argument instead. Signed-off-by: Ning Ding [ kkd: Rewrote commit log ] Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904084325.52250-9-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../bpf/progs/refcounted_kptr_fail.c | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c index f787ecf189d8..338e43822ffe 100644 --- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c +++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c @@ -189,6 +189,33 @@ long rbtree_remove_after_rcu_unlock(void *ctx) return 0; } +SEC("?syscall") +__failure __msg("R1 is neither owning or non-owning ref") +long refcount_acquire_after_rcu_unlock(void *ctx) +{ + struct map_value_refcount_only *mapval; + struct node_refcount_only *node, *ref; + int idx = 0; + + mapval = bpf_map_lookup_elem(&stashed_refcount_only, &idx); + if (!mapval) + return 0; + + bpf_rcu_read_lock(); + node = mapval->node; + if (!node) { + bpf_rcu_read_unlock(); + return 0; + } + bpf_rcu_read_unlock(); + + ref = bpf_refcount_acquire(node); + if (ref) + bpf_obj_drop(ref); + + return 0; +} + SEC("?syscall") __failure __msg("invalid mem access 'scalar'") long graph_kptr_after_spin_unlock(void *ctx) From 3e5d1bf4bd687beb2cb4e32a07af695455925588 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 2 Sep 2026 12:19:15 +0800 Subject: [PATCH 0852/2366] cpufreq: initialize policy rwsem before sysfs publication cpufreq_policy_alloc() initializes policy->rwsem after kobject_init_and_add() has created the policy sysfs directory and its default attributes. A sysfs access can therefore reach a policy callback before the semaphore has been initialized. Initialize policy->rwsem before publishing the policy kobject so sysfs callbacks always see an initialized semaphore. Fixes: 2fc3384dc75b ("cpufreq: Initialize policy->kobj while allocating policy") Cc: All Applicable Link: https://lore.kernel.org/all/20260830155301.2713780-1-runyu.xiao@seu.edu.cn/ Reviewed-by: Zhongqiu Han Signed-off-by: Runyu Xiao Acked-by: Viresh Kumar Link: https://patch.msgid.link/20260902041915.3453421-1-runyu.xiao@seu.edu.cn Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/cpufreq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 0d0df986fa3d..9efbf5b1781a 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -1258,6 +1258,8 @@ static struct cpufreq_policy *cpufreq_policy_alloc(unsigned int cpu) if (!zalloc_cpumask_var(&policy->real_cpus, GFP_KERNEL)) goto err_free_rcpumask; + init_rwsem(&policy->rwsem); + init_completion(&policy->kobj_unregister); ret = kobject_init_and_add(&policy->kobj, &ktype_cpufreq, cpufreq_global_kobject, "policy%u", cpu); @@ -1272,8 +1274,6 @@ static struct cpufreq_policy *cpufreq_policy_alloc(unsigned int cpu) goto err_free_real_cpus; } - init_rwsem(&policy->rwsem); - freq_constraints_init(&policy->constraints); policy->nb_min.notifier_call = cpufreq_notifier_min; From d7dbdd2ee01e12211046d4a535623ac732b749fb Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Fri, 4 Sep 2026 08:25:04 +0900 Subject: [PATCH 0853/2366] tracing: Fix to avoid creating trace instances with duplicate names Since commit e645535a954a ("tracing: Add option to use memmapped memory for trace boot instance") changed trace_array_get_by_name() to trace_array_create_systems(), enable_instances() does not reuse the same name instance. Therefore, if an administrator mistakenly specifies multiple `trace_instance=` options with duplicate names, all are created but only the first is accessible via tracefs. Check whether an instance with the same name already exists before creating a new one, and reject duplicates with a warning. Link: https://patch.msgid.link/178847790399.283263.5313150997200138426.stgit@devnote2 Fixes: e645535a954a ("tracing: Add option to use memmapped memory for trace boot instance") Signed-off-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 722d0ba2d233..138e983c3c2f 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -9710,6 +9710,11 @@ __init static void enable_instances(void) if (flag_delim) *flag_delim++ = '\0'; + if (trace_array_find(name)) { + pr_warn("Tracing: Instance %s already exists\n", name); + continue; + } + if (backup) { if (backup_instance_area(backup, &addr, &size) < 0) continue; From 54d37bcf2f497140b9207968557ddb484058e749 Mon Sep 17 00:00:00 2001 From: Zhongqiu Han Date: Tue, 1 Sep 2026 22:36:35 +0800 Subject: [PATCH 0854/2366] cpufreq: zero-initialize policy cpumask before sysfs publication cpufreq_policy_alloc() allocates policy->cpus with alloc_cpumask_var(), i.e. without __GFP_ZERO, unlike the sibling related_cpus and real_cpus masks. With CONFIG_CPUMASK_OFFSTACK=y the mask is a separate kmalloc_node() allocation, so its bitmap holds whatever the slab allocator left behind: cpufreq_online() cpufreq_policy_alloc() alloc_cpumask_var(&policy->cpus) /* bitmap is uninitialized */ kobject_init_and_add() /* policy%u/ appears in sysfs */ cpufreq_policy_online() cpumask_copy(policy->cpus, cpumask_of(cpu)) /* first valid value */ This leaves a window in which the sysfs attributes are already reachable while policy->cpus is still garbage. show()/store() gate on policy_is_inactive(), i.e. cpumask_empty(policy->cpus), so a non-zero bitmap makes them run the attribute callbacks on a policy that is not initialized yet. Fix this by using zalloc_cpumask_var() for policy->cpus. Fixes: 2fc3384dc75b ("cpufreq: Initialize policy->kobj while allocating policy") Cc: All applicable Signed-off-by: Zhongqiu Han Acked-by: Viresh Kumar Link: https://patch.msgid.link/20260901143635.4106960-1-zhongqiu.han@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/cpufreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 9efbf5b1781a..96515880b4ac 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -1249,7 +1249,7 @@ static struct cpufreq_policy *cpufreq_policy_alloc(unsigned int cpu) if (!policy) return NULL; - if (!alloc_cpumask_var(&policy->cpus, GFP_KERNEL)) + if (!zalloc_cpumask_var(&policy->cpus, GFP_KERNEL)) goto err_free_policy; if (!zalloc_cpumask_var(&policy->related_cpus, GFP_KERNEL)) From 4e3bd70facac5b7c412271d0e9cd6c18a3cef6c6 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 4 Sep 2026 15:57:37 +0800 Subject: [PATCH 0855/2366] ASoC: rt766: fix uninitialized stream_config->type The stream_config variable was not initialized before being passed to sdw_stream_add_slave(). This may cause unexpected behavior when configuring the SoundWire stream. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260904075737.86525-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt766-sdca.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c index 64d763b96a06..5a1c1e10e6d7 100644 --- a/sound/soc/codecs/rt766-sdca.c +++ b/sound/soc/codecs/rt766-sdca.c @@ -936,9 +936,8 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; - enum sdw_data_direction direction; struct sdw_stream_runtime *sdw_stream; unsigned int sampling_rate; int retval, port; @@ -957,7 +956,6 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, /* SoundWire specific configuration */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - direction = SDW_DATA_DIR_RX; if (dai->id == RT766_AIF1) port = 3; else if (dai->id == RT766_AIF2) @@ -965,7 +963,6 @@ static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, else return -EINVAL; } else { - direction = SDW_DATA_DIR_TX; if (dai->id == RT766_AIF1) port = 12; else if (dai->id == RT766_AIF3) From 0a9bce8a4ff8357f3b0022e9aeee5db3e9326de5 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 4 Sep 2026 15:57:57 +0800 Subject: [PATCH 0856/2366] ASoC: rt712: fix uninitialized stream_config->type The stream_config variable was not initialized before being passed to sdw_stream_add_slave(). This may cause unexpected behavior when configuring the SoundWire stream. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260904075757.108427-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c index 13574513b181..eda87eb9ab66 100644 --- a/sound/soc/codecs/rt712-sdca.c +++ b/sound/soc/codecs/rt712-sdca.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "rt712-sdca.h" @@ -1449,11 +1450,10 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; - enum sdw_data_direction direction; struct sdw_stream_runtime *sdw_stream; - int retval, port, num_channels; + int retval, port; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); @@ -1471,7 +1471,6 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, /* SoundWire specific configuration */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - direction = SDW_DATA_DIR_RX; if (dai->id == RT712_AIF1) port = 1; else if (dai->id == RT712_AIF2) @@ -1479,7 +1478,6 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, else return -EINVAL; } else { - direction = SDW_DATA_DIR_TX; if (dai->id == RT712_AIF1) port = 4; else if (dai->id == RT712_AIF3) @@ -1488,13 +1486,8 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = params_channels(params); - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = direction; - - num_channels = params_channels(params); - port_config.ch_mask = GENMASK(num_channels - 1, 0); + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = port; retval = sdw_stream_add_slave(rt712->slave, &stream_config, From 2c68c4a0e57063a30a8139fa8bcb689323a45f16 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 4 Sep 2026 15:58:10 +0800 Subject: [PATCH 0857/2366] ASoC: rt1320: fix uninitialized stream_config->type The stream_config variable was not initialized before being passed to sdw_stream_add_slave(). This may cause unexpected behavior when configuring the SoundWire stream. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260904075810.121467-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1320-sdw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index 90333779971d..72af413306fa 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -3246,7 +3246,7 @@ static int rt1320_sdw_hw_params(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct rt1320_sdw_priv *rt1320 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; struct sdw_port_config dmic_port_config[2]; struct sdw_stream_runtime *sdw_stream; From c966210ed5f218d0297e94fb1002fba59e831c64 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 4 Sep 2026 15:58:18 +0800 Subject: [PATCH 0858/2366] ASoC: rt1318: fix uninitialized stream_config->type The stream_config variable was not initialized before being passed to sdw_stream_add_slave(). This may cause unexpected behavior when configuring the SoundWire stream. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260904075818.130215-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt1318-sdw.c | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/sound/soc/codecs/rt1318-sdw.c b/sound/soc/codecs/rt1318-sdw.c index efadb6b1b82d..f9ba16269817 100644 --- a/sound/soc/codecs/rt1318-sdw.c +++ b/sound/soc/codecs/rt1318-sdw.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "rt1318-sdw.h" @@ -564,11 +565,10 @@ static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct rt1318_sdw_priv *rt1318 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; - enum sdw_data_direction direction; struct sdw_stream_runtime *sdw_stream; - int retval, port, num_channels, ch_mask; + int retval, port; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s", __func__, dai->name); @@ -582,23 +582,13 @@ static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream, /* SoundWire specific configuration */ /* port 1 for playback */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - direction = SDW_DATA_DIR_RX; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) port = 1; - } else { - direction = SDW_DATA_DIR_TX; + else port = 2; - } - num_channels = params_channels(params); - ch_mask = (1 << num_channels) - 1; - - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = num_channels; - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = direction; - - port_config.ch_mask = ch_mask; + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = port; retval = sdw_stream_add_slave(rt1318->sdw_slave, &stream_config, From 0e17e50b993043cf3e21512a43ca63d176b46459 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 4 Sep 2026 15:58:26 +0800 Subject: [PATCH 0859/2366] ASoC: rt721: fix uninitialized stream_config->type The stream_config variable was not initialized before being passed to sdw_stream_add_slave(). This may cause unexpected behavior when configuring the SoundWire stream. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260904075826.130751-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index b1c3dc060c24..a9479d0e4941 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "rt721-sdca.h" @@ -1269,11 +1270,10 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt721_sdca_priv *rt721 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; - enum sdw_data_direction direction; struct sdw_stream_runtime *sdw_stream; - int retval, port, num_channels; + int retval, port; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s", __func__, dai->name); @@ -1292,7 +1292,6 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, * RT721_AIF3 with port = 6 for digital-mic capture */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - direction = SDW_DATA_DIR_RX; if (dai->id == RT721_AIF1) port = 1; else if (dai->id == RT721_AIF2) @@ -1300,7 +1299,6 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, else return -EINVAL; } else { - direction = SDW_DATA_DIR_TX; if (dai->id == RT721_AIF1) port = 2; else if (dai->id == RT721_AIF3) @@ -1308,13 +1306,9 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, else return -EINVAL; } - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = params_channels(params); - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = direction; - num_channels = params_channels(params); - port_config.ch_mask = GENMASK(num_channels - 1, 0); + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = port; retval = sdw_stream_add_slave(rt721->slave, &stream_config, From cff381508c1826ed93f7291372c81a50474d5565 Mon Sep 17 00:00:00 2001 From: Shuming Fan Date: Fri, 4 Sep 2026 15:58:34 +0800 Subject: [PATCH 0860/2366] ASoC: rt722: fix uninitialized stream_config->type The stream_config variable was not initialized before being passed to sdw_stream_add_slave(). This may cause unexpected behavior when configuring the SoundWire stream. Signed-off-by: Shuming Fan Link: https://patch.msgid.link/20260904075835.130778-1-shumingf@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt722-sdca.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c index 4cbe9e909585..149cb6617126 100644 --- a/sound/soc/codecs/rt722-sdca.c +++ b/sound/soc/codecs/rt722-sdca.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "rt722-sdca.h" @@ -1442,11 +1443,10 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; - enum sdw_data_direction direction; struct sdw_stream_runtime *sdw_stream; - int retval, port, num_channels; + int retval, port; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s", __func__, dai->name); @@ -1465,7 +1465,6 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, * RT722_AIF3 with port = 6 for digital-mic capture */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - direction = SDW_DATA_DIR_RX; if (dai->id == RT722_AIF1) port = 1; else if (dai->id == RT722_AIF2) @@ -1473,7 +1472,6 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, else return -EINVAL; } else { - direction = SDW_DATA_DIR_TX; if (dai->id == RT722_AIF1) port = 2; else if (dai->id == RT722_AIF3) @@ -1481,13 +1479,9 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, else return -EINVAL; } - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = params_channels(params); - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = direction; - num_channels = params_channels(params); - port_config.ch_mask = GENMASK(num_channels - 1, 0); + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = port; retval = sdw_stream_add_slave(rt722->slave, &stream_config, From 90feea391c64fc43bf44184fcf2b243ab991ce47 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 3 Sep 2026 11:05:58 -0700 Subject: [PATCH 0861/2366] drm/amd/display: Fix harmless type mismatch in allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While converting to kmalloc_obj() API, a type assignment mismatch was found between the desired struct dcn42_resource_pool and the allocated struct dcn401_resource_pool. Fix the type (it is harmless: the objects have the same contents and size). Signed-off-by: Kees Cook --- Cc: Harry Wentland Cc: Leo Li Cc: Rodrigo Siqueira Cc: Alex Deucher Cc: "Christian König" Cc: David Airlie Cc: Simona Vetter Cc: Dan Wheeler Cc: Roman Li Cc: Ovidiu Bunea Cc: Charlene Liu Cc: Leo Chen Cc: Ivan Lipski Cc: Gaghik Khachatrian Cc: Cc: --- drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c index 6370d3903eb5..b93d608b64a9 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c @@ -2441,7 +2441,7 @@ struct resource_pool *dcn42_create_resource_pool( struct dc *dc) { struct dcn42_resource_pool *pool = - kzalloc(sizeof(struct dcn401_resource_pool), GFP_KERNEL); + kzalloc(sizeof(struct dcn42_resource_pool), GFP_KERNEL); if (!pool) return NULL; From c1e01ea35093777d59c12128d549ff5016c38cec Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Fri, 4 Sep 2026 18:41:41 +0800 Subject: [PATCH 0862/2366] ASoC: codecs: ES8389: Prevent enable_count underflow in clk_core_disable To prevent enable_count underflow in clk_core_disable, I replaced the direct call to `es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY);` in the code with a helper function. Signed-off-by: Zhang Yi Link: https://patch.msgid.link/20260904104141.3885-1-zhangyi@everest-semi.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8389.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c index 80efce3e0a22..2ea0c4052ce1 100644 --- a/sound/soc/codecs/es8389.c +++ b/sound/soc/codecs/es8389.c @@ -812,6 +812,23 @@ static int es8389_pcm_hw_free(struct snd_pcm_substream *substream, return 0; } +static void es8389_standby(struct snd_soc_component *component) +{ + struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); + + regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x04); + regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x04); + regmap_write(es8389->regmap, ES8389_CSM_JUMP, 0xD4); + usleep_range(70000, 72000); + regmap_write(es8389->regmap, ES8389_ANA_CTL1, 0x59); + regmap_write(es8389->regmap, ES8389_ADC_EN, 0x00); + regmap_write(es8389->regmap, ES8389_CLK_OFF1, 0x00); + regmap_write(es8389->regmap, ES8389_RESET, 0x3E); + regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x80); + usleep_range(8000, 8500); + regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x00); +} + static int es8389_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { @@ -834,18 +851,7 @@ static int es8389_set_bias_level(struct snd_soc_component *component, case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: - regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x04); - regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x04); - regmap_write(es8389->regmap, ES8389_CSM_JUMP, 0xD4); - usleep_range(70000, 72000); - regmap_write(es8389->regmap, ES8389_ANA_CTL1, 0x59); - regmap_write(es8389->regmap, ES8389_ADC_EN, 0x00); - regmap_write(es8389->regmap, ES8389_CLK_OFF1, 0x00); - regmap_write(es8389->regmap, ES8389_RESET, 0x3E); - regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x80); - usleep_range(8000, 8500); - regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x00); - + es8389_standby(component); clk_disable_unprepare(es8389->mclk); break; case SND_SOC_BIAS_OFF: @@ -1015,7 +1021,7 @@ static int es8389_suspend(struct snd_soc_component *component) { struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); - es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); + es8389_standby(component); regcache_cache_only(es8389->regmap, true); regcache_mark_dirty(es8389->regmap); @@ -1084,7 +1090,7 @@ static int es8389_probe(struct snd_soc_component *component) es8389->hpf_freq = ES8389_HPF_DEFAULT; es8389_init(component); - es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); + es8389_standby(component); return 0; } From 5e8c349bc8d790fe031a4332e502f5d4f9878644 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Fri, 4 Sep 2026 15:37:39 +0800 Subject: [PATCH 0863/2366] selftests/bpf: Fix flaky bpf_nf test when random NAT port is 0 The bpf_nf test allocs a ct, sets snat and dnat with random addr and port via bpf_ct_set_nat_info(), then looks the ct up and checks the reply tuple against what was set. The port comes from bpf_get_prandom_u32() and can be 0. For bpf_ct_set_nat_info(), port 0 means "port not specified", so only the addr is mapped and the kernel keeps the original port. The check then compares that port with 0 and fails, which shows up as a flaky "Test for source natting" failure in CI [1][2]. Keep the random port in 1..65535 so it is always specified. [1] https://github.com/kernel-patches/bpf/actions/runs/33830002889/job/100893868791 [2] https://github.com/kernel-patches/bpf/actions/runs/33829976794/job/100893220999 Fixes: b06b45e82b59 ("selftests/bpf: add tests for bpf_ct_set_nat_info kfunc") Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260904073745.363314-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- tools/testing/selftests/bpf/progs/test_bpf_nf.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/bpf/progs/test_bpf_nf.c b/tools/testing/selftests/bpf/progs/test_bpf_nf.c index df43649ecb78..eda9b7bbab75 100644 --- a/tools/testing/selftests/bpf/progs/test_bpf_nf.c +++ b/tools/testing/selftests/bpf/progs/test_bpf_nf.c @@ -190,8 +190,8 @@ nf_ct_test(struct nf_conn *(*lookup_fn)(void *, struct bpf_sock_tuple *, u32, ct = alloc_fn(ctx, &bpf_tuple, sizeof(bpf_tuple.ipv4), &opts_def, sizeof(opts_def)); if (ct) { - __u16 sport = bpf_get_prandom_u32(); - __u16 dport = bpf_get_prandom_u32(); + __u16 sport = bpf_get_prandom_u32() % 65535 + 1; + __u16 dport = bpf_get_prandom_u32() % 65535 + 1; union nf_inet_addr saddr = {}; union nf_inet_addr daddr = {}; struct nf_conn *ct_ins; @@ -293,8 +293,8 @@ nf_ct_opts_new_test(struct nf_conn *(*lookup_fn)(void *, struct bpf_sock_tuple * ct = alloc_fn(ctx, &bpf_tuple, sizeof(bpf_tuple.ipv4), &opts_def, sizeof(opts_def)); if (ct) { - __u16 sport = bpf_get_prandom_u32(); - __u16 dport = bpf_get_prandom_u32(); + __u16 sport = bpf_get_prandom_u32() % 65535 + 1; + __u16 dport = bpf_get_prandom_u32() % 65535 + 1; union nf_inet_addr saddr = {}; union nf_inet_addr daddr = {}; struct nf_conn *ct_ins; From f76017a7663c4ce5e379f8a8d39f032bdb1fd865 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 14 Apr 2026 07:18:05 -0700 Subject: [PATCH 0864/2366] nfsd: fix handling of NFSEXP_PNFS in the netlink codepath The rework of how block layouts were checked moved the check for NFSEXP_PNFS out of nfsd4_setup_layout_type() and into the callers. That patch didn't account for the new call in nfsd4_setup_layout_type(). Cc: Christoph Hellwig Fixes: da9baa5470dc ("exportfs,nfsd: rework checking for layout-based block device access support") Cc: stable@vger.kernel.org Reported-by: Olga Kornievskaia Signed-off-by: Jeff Layton Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260414-pnfs-exp-fix-v1-1-9face14c16c2@kernel.org Signed-off-by: Chuck Lever --- fs/nfsd/export.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index a47c90f40422..1f678583f612 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -1006,7 +1006,8 @@ static int nfsd_nl_parse_one_export(struct cache_detail *cd, goto out_uuid; err = 0; - nfsd4_setup_layout_type(&exp); + if (exp.ex_flags & NFSEXP_PNFS) + nfsd4_setup_layout_type(&exp); } expp = svc_export_lookup(&exp); From 6903878d4654bdef4e08e38cdf1ae306ce7de5f9 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Fri, 4 Sep 2026 12:09:11 +0100 Subject: [PATCH 0865/2366] ima: allow users to specify the pcr index with IMA_MEASURE_PCR_IDX The IMA_MEASURE_PCR_IDX option is currently not visible in the kconfig frontend, so it always uses its default, 10. This means that the 'range 8 14' is dead code, and users are unable to specify the pcr index value. In a previous discussion, Mimi explained that users should be able to use this config option to specify the pcr index. [1] Let's add a prompt for users to specify the pcr index, when EXPERT is enabled. This dead range was found by kconfirm, a static analysis tool for Kconfig. Link: https://lore.kernel.org/all/1feff118-4afa-4b9c-86f1-271a7a88208f@gmail.com/T/#mc4efa2491b4937eb7c9e532c29ffba516a70e662 [1] Signed-off-by: Julian Braha Signed-off-by: Mimi Zohar --- security/integrity/ima/Kconfig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index b3a9f86809b0..72654cf797cd 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -46,12 +46,16 @@ config IMA_KEXEC config IMA_MEASURE_PCR_IDX int + prompt "PCR Index for Aggregate" if EXPERT range 8 14 default 10 help IMA_MEASURE_PCR_IDX determines the TPM PCR register index that IMA uses to maintain the integrity aggregate of the - measurement list. If unsure, use the default 10. + measurement list. Most attestation tooling expects PCR 10. + + The default is almost always what you want. Only change this + if you know what you are doing. config IMA_LSM_RULES bool From 5df46ddcb7b36878c1b691e9057a0509042a2567 Mon Sep 17 00:00:00 2001 From: Yuan Chen Date: Fri, 4 Sep 2026 12:41:52 +0200 Subject: [PATCH 0866/2366] bpf: Preserve special fields in recycled rhtab elements rhtab_map_update_elem() initializes special fields after obtaining an element from bpf_mem_cache_alloc(). The allocator can return a fresh, zeroed unit, or recycle one from its RCU-pending lists before the registered destructor has run. A BPF program can retain a map-value pointer after deleting its element and initialize and arm a timer through that pointer. If the deleted unit is recycled, check_and_init_map_value() clears the only pointer to the timer. Neither a later deletion nor rhtab_mem_dtor() can then cancel it, and the callback can run with its key and value pointing into freed memory. Do not reinitialize special fields on insertion. Fresh allocator units are already zeroed. For recycled units, the special fields are ownership state that must remain visible to the eventual destructor. copy_map_value() already skips those fields, matching the non-preallocated hash-map path and the lifecycle established by commit 275c30bcee66 ("bpf: Don't reinit map value in prealloc_lru_pop"). Fixes: 6905f8601298 ("bpf: Allow special fields in resizable hashtab") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Yuan Chen [ kkd: Split out the fix and rewrote the commit log ] Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904104203.345917-2-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/hashtab.c | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index e89fde188389..527cc5716ee8 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -3070,7 +3070,6 @@ static long rhtab_map_update_elem(struct bpf_map *map, void *key, void *value, u memcpy(elem->data, key, map->key_size); copy_map_value(map, rhtab_elem_value(elem, map->key_size), value); - check_and_init_map_value(map, rhtab_elem_value(elem, map->key_size)); /* Prevent deadlock for NMI programs attempting to take bucket lock */ bpf_disable_instrumentation(); From dbf6806dc81553edbab72fcec9a6d637dedff2f4 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 4 Sep 2026 12:41:53 +0200 Subject: [PATCH 0867/2366] selftests/bpf: Test timer field on recycled rhtab element Exercise the rhtab special-field lifecycle with the sequence from the original report. A bpf_for_each_map_elem() callback deletes the sole element, then initializes and arms a timer through the callback value pointer while it remains valid. Use a one-element map and pin userspace and BPF execution to one CPU. Repeated delete-and-replace cycles drain the per-CPU allocator cache, and periodic RCU synchronization makes the deleted units available for recycling. After each replacement, a second BPF program calls bpf_timer_cancel() on its value. A successful cancellation proves both that a timer-bearing unit was recycled and that insertion preserved the timer field. Without the fix, insertion clears that field and cancellation keeps returning -EINVAL. A long expiration keeps the timer callback out of the test, so the regression is detected without accessing freed memory. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904104203.345917-3-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/rhash_timer.c | 141 ++++++++++++++++++ .../testing/selftests/bpf/progs/rhash_timer.c | 98 ++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/rhash_timer.c create mode 100644 tools/testing/selftests/bpf/progs/rhash_timer.c diff --git a/tools/testing/selftests/bpf/prog_tests/rhash_timer.c b/tools/testing/selftests/bpf/prog_tests/rhash_timer.c new file mode 100644 index 000000000000..3aad9fc02e06 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/rhash_timer.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include + +#include +#include "rhash_timer.skel.h" + +#define MAX_ATTEMPTS 256 +#define RCU_SYNC_INTERVAL 64 + +static int pin_to_first_cpu(cpu_set_t *old_mask) +{ + cpu_set_t new_mask; + int cpu; + + if (sched_getaffinity(0, sizeof(*old_mask), old_mask)) + return -errno; + + for (cpu = 0; cpu < CPU_SETSIZE; cpu++) + if (CPU_ISSET(cpu, old_mask)) + break; + if (cpu == CPU_SETSIZE) + return -EINVAL; + + CPU_ZERO(&new_mask); + CPU_SET(cpu, &new_mask); + if (sched_setaffinity(0, sizeof(new_mask), &new_mask)) + return -errno; + return 0; +} + +static int update_timer_map(int map_fd, __u64 key) +{ + __u64 value[3] = {}; + + return bpf_map_update_elem(map_fd, &key, value, BPF_NOEXIST); +} + +static int run_prog(int prog_fd, struct bpf_test_run_opts *opts) +{ + int err; + + err = bpf_prog_test_run_opts(prog_fd, opts); + if (err) + return err; + return opts->retval; +} + +void test_rhash_timer(void) +{ + LIBBPF_OPTS(bpf_test_run_opts, opts); + struct rhash_timer *skel = NULL; + cpu_set_t old_mask; + int map_fd = -1, arm_fd, cancel_fd; + bool affinity_set = false; + __u64 key = 1; + int attempt, err; + + err = pin_to_first_cpu(&old_mask); + if (!ASSERT_OK(err, "pin_to_first_cpu")) + return; + affinity_set = true; + + skel = rhash_timer__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + goto out; + + map_fd = bpf_map__fd(skel->maps.timer_map); + if (!ASSERT_GE(map_fd, 0, "timer_map fd")) + goto out; + arm_fd = bpf_program__fd(skel->progs.arm_deleted_timer); + if (!ASSERT_GE(arm_fd, 0, "arm_deleted_timer fd")) + goto out; + cancel_fd = bpf_program__fd(skel->progs.cancel_recycled_timer); + if (!ASSERT_GE(cancel_fd, 0, "cancel_recycled_timer fd")) + goto out; + + err = update_timer_map(map_fd, key); + if (!ASSERT_OK(err, "seed_timer_map")) + goto out; + + for (attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + err = run_prog(arm_fd, &opts); + if (err) { + ASSERT_OK(err, "arm_deleted_timer"); + goto out; + } + if (skel->bss->armed != attempt + 1) { + ASSERT_EQ(skel->bss->armed, attempt + 1, "armed"); + goto out; + } + if (skel->bss->timer_init_err) { + ASSERT_OK(skel->bss->timer_init_err, "timer_init_err"); + goto out; + } + if (skel->bss->timer_set_callback_err) { + ASSERT_OK(skel->bss->timer_set_callback_err, + "timer_set_callback_err"); + goto out; + } + if (skel->bss->timer_start_err) { + ASSERT_OK(skel->bss->timer_start_err, "timer_start_err"); + goto out; + } + + if ((attempt + 1) % RCU_SYNC_INTERVAL == 0) { + err = kern_sync_rcu(); + if (err) { + ASSERT_OK(err, "kern_sync_rcu"); + goto out; + } + } + + err = update_timer_map(map_fd, ++key); + if (err) { + ASSERT_OK(err, "replace_timer_map"); + goto out; + } + + err = run_prog(cancel_fd, &opts); + if (err) { + ASSERT_OK(err, "cancel_recycled_timer"); + goto out; + } + if (skel->bss->timer_cancel_err) { + ASSERT_OK(skel->bss->timer_cancel_err, "timer_cancel_err"); + goto out; + } + if (skel->bss->cancelled) + break; + } + + ASSERT_GT(skel->bss->cancelled, 0, "preserved timer"); +out: + if (map_fd >= 0) + bpf_map_delete_elem(map_fd, &key); + rhash_timer__destroy(skel); + if (affinity_set) + sched_setaffinity(0, sizeof(old_mask), &old_mask); +} diff --git a/tools/testing/selftests/bpf/progs/rhash_timer.c b/tools/testing/selftests/bpf/progs/rhash_timer.c new file mode 100644 index 000000000000..2e06a463c605 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/rhash_timer.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include + +#define CLOCK_MONOTONIC 1 +#define TIMER_NSEC (60ULL * 1000 * 1000 * 1000) + +struct timer_value { + struct bpf_timer timer; + u64 data; +}; + +struct { + __uint(type, BPF_MAP_TYPE_RHASH); + __uint(map_flags, BPF_F_NO_PREALLOC); + __uint(max_entries, 1); + __type(key, u64); + __type(value, struct timer_value); +} timer_map SEC(".maps"); + +u64 armed; +u64 cancelled; +long timer_init_err; +long timer_set_callback_err; +long timer_start_err; +long timer_cancel_err; + +static int timer_cb(void *map, u64 *key, struct timer_value *value) +{ + return 0; +} + +static long arm_timer_cb(struct bpf_map *map, u64 *key, + struct timer_value *value, void *ctx) +{ + u64 key_copy = *key; + long err; + + err = bpf_map_delete_elem(map, &key_copy); + if (err) + return 1; + + err = bpf_timer_init(&value->timer, map, CLOCK_MONOTONIC); + if (err) { + timer_init_err = err; + return 1; + } + + err = bpf_timer_set_callback(&value->timer, timer_cb); + if (err) { + timer_set_callback_err = err; + return 1; + } + + err = bpf_timer_start(&value->timer, TIMER_NSEC, BPF_F_TIMER_CPU_PIN); + if (err) { + timer_start_err = err; + return 1; + } + + __sync_fetch_and_add(&armed, 1); + return 1; +} + +static long cancel_timer_cb(struct bpf_map *map, u64 *key, + struct timer_value *value, void *ctx) +{ + long err; + + err = bpf_timer_cancel(&value->timer); + if (err == -EINVAL) + return 1; + if (err < 0) { + timer_cancel_err = err; + return 1; + } + + __sync_fetch_and_add(&cancelled, 1); + return 1; +} + +SEC("syscall") +int arm_deleted_timer(void *ctx) +{ + bpf_for_each_map_elem(&timer_map, arm_timer_cb, NULL, 0); + return 0; +} + +SEC("syscall") +int cancel_recycled_timer(void *ctx) +{ + bpf_for_each_map_elem(&timer_map, cancel_timer_cb, NULL, 0); + return 0; +} + +char _license[] SEC("license") = "GPL"; From 65cc95eba9e8b46312cac38c227473605a4b996a Mon Sep 17 00:00:00 2001 From: Nuoqi Gui Date: Fri, 4 Sep 2026 12:41:54 +0200 Subject: [PATCH 0868/2366] bpf: Cancel special fields when recycling rhtab elements rhtab_map_update_existing() and rhtab_delete_elem() call bpf_obj_free_fields() when replacing or deleting a value. These map operations can run from BPF programs in NMI context, where releasing a referenced kptr or another complex field is not generally safe. Array and hash maps avoid that problem by cancelling only the asynchronous fields which can be stopped safely in the caller context. Other ownership state remains attached to the allocation until its memory allocator destructor performs the final cleanup. Use bpf_obj_cancel_fields() for the corresponding rhtab paths as well. This cancels timers, workqueues, and task work while allowing rhtab_mem_dtor() to release referenced kptrs when the allocation is eventually destroyed. Fixes: 6905f8601298 ("bpf: Allow special fields in resizable hashtab") Signed-off-by: Nuoqi Gui Acked-by: Mykyta Yatsenko [ kkd: Rebased, used direct helper calls, and rewrote the commit log ] Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904104203.345917-4-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/hashtab.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index 527cc5716ee8..cc60e99ffbe9 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -2868,16 +2868,6 @@ static int rhtab_map_alloc_check(union bpf_attr *attr) return htab_map_alloc_check(attr); } -static void rhtab_check_and_free_fields(struct bpf_rhtab *rhtab, - struct rhtab_elem *elem) -{ - if (IS_ERR_OR_NULL(rhtab->map.record)) - return; - - bpf_obj_free_fields(rhtab->map.record, - rhtab_elem_value(elem, rhtab->map.key_size)); -} - static void rhtab_mem_dtor(void *obj, void *ctx) { struct htab_btf_record *hrec = ctx; @@ -2967,8 +2957,8 @@ static int rhtab_delete_elem(struct bpf_rhtab *rhtab, struct rhtab_elem *elem, v rhtab_read_elem_value(&rhtab->map, copy, elem, flags); check_and_init_map_value(&rhtab->map, copy); } - /* Release internal structs: kptr, bpf_timer, task_work, wq */ - rhtab_check_and_free_fields(rhtab, elem); + bpf_obj_cancel_fields(&rhtab->map, + rhtab_elem_value(elem, rhtab->map.key_size)); bpf_mem_cache_free_rcu(&rhtab->ma, elem); return 0; } @@ -3009,7 +2999,6 @@ static int rhtab_map_lookup_and_delete_elem(struct bpf_map *map, void *key, void static long rhtab_map_update_existing(struct bpf_map *map, struct rhtab_elem *elem, void *value, u64 map_flags) { - struct bpf_rhtab *rhtab = container_of(map, struct bpf_rhtab, map); void *old_val = rhtab_elem_value(elem, map->key_size); if (map_flags & BPF_NOEXIST) @@ -3029,7 +3018,7 @@ static long rhtab_map_update_existing(struct bpf_map *map, struct rhtab_elem *el * kptrs/etc. still sit in the slot. Cancel them after the copy * to match arraymap's update semantics. */ - rhtab_check_and_free_fields(rhtab, elem); + bpf_obj_cancel_fields(map, old_val); return 0; } From 2b97956af60810cd382b86b9ce9aea421b889861 Mon Sep 17 00:00:00 2001 From: Nuoqi Gui Date: Fri, 4 Sep 2026 12:41:55 +0200 Subject: [PATCH 0869/2366] selftests/bpf: Test rhtab kptr cancellation semantics Resizable hash-map updates and deletions must not perform full special-field destruction in their caller context. In particular, a referenced kptr must remain attached to the allocation until the memory allocator destructor can release it safely. Add separate coverage for both affected paths. The update test stores a task kptr, replaces the ordinary value bytes with BPF_EXIST, and verifies that the kptr survived. The delete test removes an element and exchanges its kptr through the still-valid map-value pointer before the allocation is reclaimed. Both cases observe a NULL kptr when rhtab uses bpf_obj_free_fields(). They recover and release the reference after rhtab switches to cancellation semantics. Signed-off-by: Nuoqi Gui [ kkd: Split update and delete coverage and rewrote the commit log ] Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904104203.345917-5-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../testing/selftests/bpf/prog_tests/rhash.c | 6 + tools/testing/selftests/bpf/progs/rhash.c | 112 ++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/rhash.c b/tools/testing/selftests/bpf/prog_tests/rhash.c index 98bb66907b7f..0641bd5b0a9e 100644 --- a/tools/testing/selftests/bpf/prog_tests/rhash.c +++ b/tools/testing/selftests/bpf/prog_tests/rhash.c @@ -172,6 +172,12 @@ void test_rhash(void) if (test__start_subtest("test_rhash_delete_nonexistent")) rhash_run("test_rhash_delete_nonexistent"); + if (test__start_subtest("test_rhash_kptr_update")) + rhash_run("test_rhash_kptr_update"); + + if (test__start_subtest("test_rhash_kptr_delete")) + rhash_run("test_rhash_kptr_delete"); + if (test__start_subtest("test_rhash_map_extra_presize")) rhash_map_extra_presize(); diff --git a/tools/testing/selftests/bpf/progs/rhash.c b/tools/testing/selftests/bpf/progs/rhash.c index fc2dac3a719e..aea4de8dc781 100644 --- a/tools/testing/selftests/bpf/progs/rhash.c +++ b/tools/testing/selftests/bpf/progs/rhash.c @@ -19,6 +19,11 @@ struct elem { int val; }; +struct special_elem { + struct task_struct __kptr *task; + int val; +}; + struct { __uint(type, BPF_MAP_TYPE_RHASH); __uint(map_flags, BPF_F_NO_PREALLOC); @@ -27,6 +32,17 @@ struct { __type(value, struct elem); } rhmap SEC(".maps"); +struct { + __uint(type, BPF_MAP_TYPE_RHASH); + __uint(map_flags, BPF_F_NO_PREALLOC); + __uint(max_entries, 1); + __type(key, int); + __type(value, struct special_elem); +} special_fields SEC(".maps"); + +extern struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym; +extern void bpf_task_release(struct task_struct *p) __ksym; + SEC("syscall") int test_rhash_lookup_update(void *ctx) { @@ -246,3 +262,99 @@ int test_rhash_delete_nonexistent(void *ctx) err = 0; return 0; } + +SEC("syscall") +int test_rhash_kptr_update(void *ctx) +{ + struct special_elem val1 = { .val = 1 }; + struct special_elem val2 = { .val = 2 }; + struct task_struct *task, *old; + struct special_elem *elem; + int key = 0; + + err = 1; + if (bpf_map_update_elem(&special_fields, &key, &val1, BPF_NOEXIST)) + return 1; + + err = 2; + elem = bpf_map_lookup_elem(&special_fields, &key); + if (!elem) + return 2; + + err = 3; + task = bpf_task_acquire(bpf_get_current_task_btf()); + if (!task) + return 3; + + err = 4; + old = bpf_kptr_xchg(&elem->task, task); + if (old) { + bpf_task_release(old); + return 4; + } + + err = 5; + if (bpf_map_update_elem(&special_fields, &key, &val2, BPF_EXIST)) + return 5; + + err = 6; + elem = bpf_map_lookup_elem(&special_fields, &key); + if (!elem || elem->val != 2) + return 6; + + err = 7; + old = bpf_kptr_xchg(&elem->task, NULL); + if (!old) + return 7; + bpf_task_release(old); + + err = 8; + if (bpf_map_delete_elem(&special_fields, &key)) + return 8; + + err = 0; + return 0; +} + +SEC("syscall") +int test_rhash_kptr_delete(void *ctx) +{ + struct special_elem val = {}; + struct task_struct *task, *old; + struct special_elem *elem; + int key = 0; + + err = 1; + if (bpf_map_update_elem(&special_fields, &key, &val, BPF_NOEXIST)) + return 1; + + err = 2; + elem = bpf_map_lookup_elem(&special_fields, &key); + if (!elem) + return 2; + + err = 3; + task = bpf_task_acquire(bpf_get_current_task_btf()); + if (!task) + return 3; + + err = 4; + old = bpf_kptr_xchg(&elem->task, task); + if (old) { + bpf_task_release(old); + return 4; + } + + err = 5; + if (bpf_map_delete_elem(&special_fields, &key)) + return 5; + + err = 6; + old = bpf_kptr_xchg(&elem->task, NULL); + if (!old) + return 6; + bpf_task_release(old); + + err = 0; + return 0; +} From ecdc5043794c9184aa8e6c814603899479c46b35 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 4 Sep 2026 12:41:56 +0200 Subject: [PATCH 0870/2366] bpf: Mark NULL kptr stores precise check_map_kptr_access() permits a scalar store into an untrusted kptr field only when the register is known to contain zero. Unlike other verifier checks whose outcome depends on a scalar value, it does not mark that register precise. A state checkpoint reached with an imprecise zero can therefore prune a second path that reaches the store with an arbitrary nonzero scalar. The program can write attacker-controlled bits into the kptr field and load them back as a PTR_TO_BTF_ID. Call mark_chain_precision() before accepting a known-zero register. This forces state equivalence to compare its scalar range and makes the verifier visit and reject a path carrying a nonzero value. Fixes: 61df10c7799e ("bpf: Allow storing unreferenced kptr in map") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260904104203.345917-6-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 9c6ad157a61e..b71c5274b3dc 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -4708,8 +4708,15 @@ static int check_map_kptr_access(struct bpf_verifier_env *env, return ret; } else if (class == BPF_STX) { val_reg = reg_state(env, value_regno); - if (!bpf_register_is_null(val_reg) && - map_kptr_match_type(env, kptr_field, val_reg, value_regno)) + if (bpf_register_is_null(val_reg)) { + /* + * This store is valid only because the scalar is known to be + * zero. Mark it precise so another scalar cannot be pruned + * against this state. + */ + return mark_chain_precision(env, value_regno); + } + if (map_kptr_match_type(env, kptr_field, val_reg, value_regno)) return -EACCES; } else if (class == BPF_ST) { if (insn->imm) { From 9dcddf30ac1a14f18c3221db9292bcaa0735ee2f Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 4 Sep 2026 12:41:57 +0200 Subject: [PATCH 0871/2366] selftests/bpf: Test imprecise scalar kptr stores Add a verifier regression where an imprecise zero scalar reaches a kptr store first and a nonzero scalar reaches the same instruction on a second path. Without the corresponding verifier fix, the second path is pruned and the program is unexpectedly accepted. With the fix, the scalar range is compared and the invalid store is rejected. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904104203.345917-7-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/map_kptr_fail.c | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/map_kptr_fail.c b/tools/testing/selftests/bpf/progs/map_kptr_fail.c index 5e25ca806060..eee35d203b66 100644 --- a/tools/testing/selftests/bpf/progs/map_kptr_fail.c +++ b/tools/testing/selftests/bpf/progs/map_kptr_fail.c @@ -409,4 +409,41 @@ int reject_scalar_store_to_kptr(struct __sk_buff *ctx) return 0; } +SEC("?tc") +__description("reject imprecise scalar store to kptr after state pruning") +__failure __msg("invalid kptr access, R7 type=scalar") +__naked void reject_imprecise_scalar_store_to_kptr(void) +{ + asm volatile ( + "r0 = 0;" + "*(u32 *)(r10 - 4) = r0;" + "r2 = r10;" + "r2 += -4;" + "r1 = %[array_map] ll;" + "call %[bpf_map_lookup_elem];" + "if r0 == 0 goto l2_%=;" + "r6 = r0;" + "r9 = *(u64 *)(r6 + 0);" + "if r9 != 0 goto l0_%=;" + "r7 = 0;" + ".rept 10;" + "r5 = 1;" + ".endr;" + "goto l1_%=;" + "l0_%=:" + "r7 = 0x4141414141414141 ll;" + ".rept 10;" + "r5 = 1;" + ".endr;" + "l1_%=:" + "*(u64 *)(r6 + 8) = r7;" + "l2_%=:" + "r0 = 0;" + "exit;" + : + : __imm(bpf_map_lookup_elem), + __imm_addr(array_map) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; From b90c5d770dad910fb89e6c1b15052a8a1e8db752 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 4 Sep 2026 12:41:58 +0200 Subject: [PATCH 0872/2366] bpf: Preserve inner map identity in callback frames Callback frame constructors initialize map-typed argument registers with __mark_reg_known_zero() and then restore map_ptr. This clears map_uid, which is the only field distinguishing inner maps that share an inner_map_meta template. When a timer callback invokes bpf_for_each_map_elem() on a second inner map, both the saved first map and the second map value can reach the nested callback as the same template with map_uid zero. bpf_timer_init() then accepts pairing the timer from the second map with the first map. The runtime records the first map in the timer without taking a reference. Freeing that map does not find the timer stored in the second map, so a later timer callback dereferences the freed map. Copy map_uid from the same caller register as map_ptr when constructing for-each, timer/workqueue, and task-work callback arguments. The existing identity check can then reject mismatched inner maps while allowing a callback value to be paired with its actual map. Fixes: 3e8ce29850f1 ("bpf: Prevent pointer mismatch in bpf_timer_init.") Fixes: 69c087ba6225 ("bpf: Add bpf_for_each_map_elem() helper") Fixes: 5c8fd7e2b5b0 ("bpf: bpf task work plumbing") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904104203.345917-8-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index b71c5274b3dc..c8699a8831df 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -10018,10 +10018,12 @@ int map_set_for_each_callback_args(struct bpf_verifier_env *env, callee->regs[BPF_REG_2].type = PTR_TO_MAP_KEY; __mark_reg_known_zero(&callee->regs[BPF_REG_2]); callee->regs[BPF_REG_2].map_ptr = caller->regs[BPF_REG_1].map_ptr; + callee->regs[BPF_REG_2].map_uid = caller->regs[BPF_REG_1].map_uid; callee->regs[BPF_REG_3].type = PTR_TO_MAP_VALUE; __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = caller->regs[BPF_REG_1].map_ptr; + callee->regs[BPF_REG_3].map_uid = caller->regs[BPF_REG_1].map_uid; /* pointer to stack or null */ callee->regs[BPF_REG_4] = caller->regs[BPF_REG_3]; @@ -10099,6 +10101,7 @@ static int set_timer_callback_state(struct bpf_verifier_env *env, int insn_idx) { struct bpf_map *map_ptr = caller->regs[BPF_REG_1].map_ptr; + u32 map_uid = caller->regs[BPF_REG_1].map_uid; /* bpf_timer_set_callback(struct bpf_timer *timer, void *callback_fn); * callback_fn(struct bpf_map *map, void *key, void *value); @@ -10106,14 +10109,17 @@ static int set_timer_callback_state(struct bpf_verifier_env *env, callee->regs[BPF_REG_1].type = CONST_PTR_TO_MAP; __mark_reg_known_zero(&callee->regs[BPF_REG_1]); callee->regs[BPF_REG_1].map_ptr = map_ptr; + callee->regs[BPF_REG_1].map_uid = map_uid; callee->regs[BPF_REG_2].type = PTR_TO_MAP_KEY; __mark_reg_known_zero(&callee->regs[BPF_REG_2]); callee->regs[BPF_REG_2].map_ptr = map_ptr; + callee->regs[BPF_REG_2].map_uid = map_uid; callee->regs[BPF_REG_3].type = PTR_TO_MAP_VALUE; __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = map_ptr; + callee->regs[BPF_REG_3].map_uid = map_uid; /* unused */ bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]); @@ -10213,6 +10219,7 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env, int insn_idx) { struct bpf_map *map_ptr = caller->regs[BPF_REG_3].map_ptr; + u32 map_uid = caller->regs[BPF_REG_3].map_uid; /* * callback_fn(struct bpf_map *map, void *key, void *value); @@ -10220,14 +10227,17 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env, callee->regs[BPF_REG_1].type = CONST_PTR_TO_MAP; __mark_reg_known_zero(&callee->regs[BPF_REG_1]); callee->regs[BPF_REG_1].map_ptr = map_ptr; + callee->regs[BPF_REG_1].map_uid = map_uid; callee->regs[BPF_REG_2].type = PTR_TO_MAP_KEY; __mark_reg_known_zero(&callee->regs[BPF_REG_2]); callee->regs[BPF_REG_2].map_ptr = map_ptr; + callee->regs[BPF_REG_2].map_uid = map_uid; callee->regs[BPF_REG_3].type = PTR_TO_MAP_VALUE; __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = map_ptr; + callee->regs[BPF_REG_3].map_uid = map_uid; /* unused */ bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]); From e615b9fd4d9df602030d9b57a5eca206abbb0aff Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 4 Sep 2026 12:41:59 +0200 Subject: [PATCH 0873/2366] selftests/bpf: Test inner map identities in callbacks Add load-only timer_mim coverage for inner map identities propagated through nested timer and bpf_for_each_map_elem() callbacks. The negative case initializes a timer in the second inner map with the map saved from the first inner map timer callback. The positive case pairs the timer value with the map supplied to the same for-each callback. Without the verifier fix, the mismatched-map program is accepted while the same-map control is rejected. Preserving map_uid reverses both verdicts. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/r/20260904104203.345917-9-memxor@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/timer_mim.c | 29 ++++++- .../selftests/bpf/progs/timer_mim_reject.c | 84 ++++++++++++++++++- 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/timer_mim.c b/tools/testing/selftests/bpf/prog_tests/timer_mim.c index c930c7d7105b..fa7bb769ca31 100644 --- a/tools/testing/selftests/bpf/prog_tests/timer_mim.c +++ b/tools/testing/selftests/bpf/prog_tests/timer_mim.c @@ -59,10 +59,32 @@ void serial_test_timer_mim(void) int err; old_print_fn = libbpf_set_print(NULL); - timer_reject_skel = timer_mim_reject__open_and_load(); - libbpf_set_print(old_print_fn); - if (!ASSERT_ERR_PTR(timer_reject_skel, "timer_reject_skel_load")) + timer_reject_skel = timer_mim_reject__open(); + if (!ASSERT_OK_PTR(timer_reject_skel, "timer_reject_skel_open")) goto cleanup; + bpf_program__set_autoload(timer_reject_skel->progs.test1, true); + err = timer_mim_reject__load(timer_reject_skel); + ASSERT_ERR(err, "timer_reject_skel_load"); + timer_mim_reject__destroy(timer_reject_skel); + + timer_reject_skel = timer_mim_reject__open(); + if (!ASSERT_OK_PTR(timer_reject_skel, "callback_reject_skel_open")) + goto cleanup; + bpf_program__set_autoload(timer_reject_skel->progs.callback_map_uid_mismatch, true); + err = timer_mim_reject__load(timer_reject_skel); + ASSERT_ERR(err, "callback_reject_skel_load"); + timer_mim_reject__destroy(timer_reject_skel); + + timer_reject_skel = timer_mim_reject__open(); + if (!ASSERT_OK_PTR(timer_reject_skel, "callback_accept_skel_open")) + goto cleanup; + bpf_program__set_autoload(timer_reject_skel->progs.callback_map_uid_match, true); + err = timer_mim_reject__load(timer_reject_skel); + if (!ASSERT_OK(err, "callback_accept_skel_load")) + goto cleanup; + timer_mim_reject__destroy(timer_reject_skel); + timer_reject_skel = NULL; + libbpf_set_print(old_print_fn); timer_skel = timer_mim__open_and_load(); if (!timer_skel && errno == EOPNOTSUPP) { @@ -75,6 +97,7 @@ void serial_test_timer_mim(void) err = timer_mim(timer_skel); ASSERT_OK(err, "timer_mim"); cleanup: + libbpf_set_print(old_print_fn); timer_mim__destroy(timer_skel); timer_mim_reject__destroy(timer_reject_skel); } diff --git a/tools/testing/selftests/bpf/progs/timer_mim_reject.c b/tools/testing/selftests/bpf/progs/timer_mim_reject.c index dd3f1ed6d6e6..83f31138336b 100644 --- a/tools/testing/selftests/bpf/progs/timer_mim_reject.c +++ b/tools/testing/selftests/bpf/progs/timer_mim_reject.c @@ -43,7 +43,7 @@ static int timer_cb(void *map, int *key, struct hmap_elem *val) return 0; } -SEC("fentry/bpf_fentry_test1") +SEC("?fentry/bpf_fentry_test1") int BPF_PROG(test1, int a) { struct hmap_elem init = {}; @@ -72,3 +72,85 @@ int BPF_PROG(test1, int a) err |= 8; return 0; } + +struct callback_ctx { + void *map; +}; + +static int mismatch_iter_cb(void *map, int *key, struct hmap_elem *val, struct callback_ctx *ctx) +{ + bpf_timer_init(&val->timer, ctx->map, CLOCK_MONOTONIC); + return 0; +} + +static int timer_mismatch_cb(void *map, int *key, struct hmap_elem *val) +{ + struct callback_ctx ctx = { .map = map }; + struct bpf_map *inner_map2; + int array_key2 = ARRAY_KEY2; + + inner_map2 = bpf_map_lookup_elem(&outer_arr, &array_key2); + if (!inner_map2) + return 0; + bpf_for_each_map_elem(inner_map2, mismatch_iter_cb, &ctx, 0); + return 0; +} + +static int match_iter_cb(void *map, int *key, struct hmap_elem *val, struct callback_ctx *ctx) +{ + bpf_timer_init(&val->timer, map, CLOCK_MONOTONIC); + return 0; +} + +static int timer_match_cb(void *map, int *key, struct hmap_elem *val) +{ + struct callback_ctx ctx = {}; + struct bpf_map *inner_map2; + int array_key2 = ARRAY_KEY2; + + inner_map2 = bpf_map_lookup_elem(&outer_arr, &array_key2); + if (!inner_map2) + return 0; + bpf_for_each_map_elem(inner_map2, match_iter_cb, &ctx, 0); + return 0; +} + +SEC("?fentry/bpf_fentry_test1") +int BPF_PROG(callback_map_uid_mismatch, int a) +{ + struct hmap_elem *val; + struct bpf_map *inner_map; + int array_key = ARRAY_KEY; + int hash_key = HASH_KEY; + + inner_map = bpf_map_lookup_elem(&outer_arr, &array_key); + if (!inner_map) + return 0; + val = bpf_map_lookup_elem(inner_map, &hash_key); + if (!val) + return 0; + + bpf_timer_init(&val->timer, inner_map, CLOCK_MONOTONIC); + bpf_timer_set_callback(&val->timer, timer_mismatch_cb); + return 0; +} + +SEC("?fentry/bpf_fentry_test1") +int BPF_PROG(callback_map_uid_match, int a) +{ + struct hmap_elem *val; + struct bpf_map *inner_map; + int array_key = ARRAY_KEY; + int hash_key = HASH_KEY; + + inner_map = bpf_map_lookup_elem(&outer_arr, &array_key); + if (!inner_map) + return 0; + val = bpf_map_lookup_elem(inner_map, &hash_key); + if (!val) + return 0; + + bpf_timer_init(&val->timer, inner_map, CLOCK_MONOTONIC); + bpf_timer_set_callback(&val->timer, timer_match_cb); + return 0; +} From dae8dda341d2d9034a90d59e8a7d502e1263813f Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Fri, 4 Sep 2026 17:44:48 +0100 Subject: [PATCH 0874/2366] tracing: Fix subbuf resize races with trace_pipe_raw readers Concurrent subbuffer resizes may crash trace_pipe_raw readers or leak uninitialized memory to userspace due to stale size values. Modify ring_buffer_alloc_read_page() to handle the resizing of an existing buffer_data_read_page if necessary and add a new ring_buffer_read_page_size(). This new function enables ring-buffer buffer_data_read_page users to not call the racy ring_buffer_subbuf_size_get(). This makes the spare_size member of ftrace_buffer_info redundant. Finally, handle buffer_data_read_page/reader_page order discrepancy in ring_buffer_read_page(). On a mismatch simply copy manually the data to the buffer_data_read_page. Link: https://lore.kernel.org/all/20260817140812.2C7D41F00A3A@smtp.kernel.org/ Link: https://patch.msgid.link/20260904164450.1345852-3-vdonnefort@google.com Fixes: bce761d75745 ("ring-buffer: Read and write to ring buffers with custom sub buffer size") Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- include/linux/ring_buffer.h | 5 +- kernel/trace/ring_buffer.c | 135 ++++++++++++++++++--------- kernel/trace/ring_buffer_benchmark.c | 6 +- kernel/trace/trace.c | 99 +++++++++----------- kernel/trace/trace.h | 9 +- 5 files changed, 145 insertions(+), 109 deletions(-) diff --git a/include/linux/ring_buffer.h b/include/linux/ring_buffer.h index 0670742b2d60..afc7daa6ee7d 100644 --- a/include/linux/ring_buffer.h +++ b/include/linux/ring_buffer.h @@ -218,14 +218,15 @@ bool ring_buffer_time_stamp_abs(struct trace_buffer *buffer); size_t ring_buffer_nr_dirty_pages(struct trace_buffer *buffer, int cpu); struct buffer_data_read_page; -struct buffer_data_read_page * -ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu); +int ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu, + struct buffer_data_read_page **rpage); void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu, struct buffer_data_read_page *page); int ring_buffer_read_page(struct trace_buffer *buffer, struct buffer_data_read_page *data_page, size_t len, int cpu, int full); void *ring_buffer_read_page_data(struct buffer_data_read_page *page); +unsigned int ring_buffer_read_page_size(struct buffer_data_read_page *rpage); struct trace_seq; diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index ff0a44aa578d..077d6940af0c 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -330,6 +330,11 @@ struct buffer_data_read_page { struct buffer_data_page *data; /* actual data, stored in this page */ }; +static __always_inline unsigned int rb_read_page_capacity(struct buffer_data_read_page *rpage) +{ + return (PAGE_SIZE << rpage->order) - BUF_PAGE_HDR_SIZE; +} + /* * Note, the buffer_page list must be first. The buffer pages * are allocated in cache lines, which means that each buffer @@ -6998,56 +7003,78 @@ EXPORT_SYMBOL_GPL(ring_buffer_swap_cpu); * ring_buffer_alloc_read_page - allocate a page to read from buffer * @buffer: the buffer to allocate for. * @cpu: the cpu buffer to allocate. + * @rpage: pointer to pass in an already allocated page (can be NULL) + * and returns the allocated page. * - * This function is used in conjunction with ring_buffer_read_page. + * This function is used in conjunction with ring_buffer_read_page(). * When reading a full page from the ring buffer, these functions * can be used to speed up the process. The calling function should * allocate a few pages first with this function. Then when it * needs to get pages from the ring buffer, it passes the result - * of this function into ring_buffer_read_page, which will swap + * of this function into ring_buffer_read_page(), which will swap * the page that was allocated, with the read page of the buffer. * + * If @rpage is provided, and it has a different order than the current + * subbuffer order, its payload will be freed and re-allocated. If it + * already matches the order, it is simply returned. + * * Returns: - * The page allocated, or ERR_PTR + * 0 on success, < 0 on error */ -struct buffer_data_read_page * -ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu) +int ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu, + struct buffer_data_read_page **rpage) { struct ring_buffer_per_cpu *cpu_buffer; - struct buffer_data_read_page *bpage = NULL; unsigned long flags; + unsigned int order; if (!cpumask_test_cpu(cpu, buffer->cpumask)) - return ERR_PTR(-ENODEV); + return -ENODEV; - bpage = kzalloc_obj(*bpage); - if (!bpage) - return ERR_PTR(-ENOMEM); + if (!rpage) + return -EINVAL; - bpage->order = buffer->subbuf_order; + order = READ_ONCE(buffer->subbuf_order); + + if (*rpage) { + if ((*rpage)->order == order) + return 0; + + /* We can reuse rpage, but we discard the payload */ + free_pages((unsigned long)(*rpage)->data, (*rpage)->order); + (*rpage)->data = NULL; + } else { + *rpage = kzalloc_obj(**rpage); + if (!*rpage) + return -ENOMEM; + } + + (*rpage)->order = order; cpu_buffer = buffer->buffers[cpu]; + local_irq_save(flags); arch_spin_lock(&cpu_buffer->lock); if (cpu_buffer->free_page.data) { - *bpage = cpu_buffer->free_page; + **rpage = cpu_buffer->free_page; cpu_buffer->free_page.data = NULL; } arch_spin_unlock(&cpu_buffer->lock); local_irq_restore(flags); - if (bpage->data) { - rb_init_data_page(bpage->data); + if ((*rpage)->data) { + rb_init_data_page((*rpage)->data); } else { - bpage->data = alloc_cpu_data(cpu, bpage->order); - if (!bpage->data) { - kfree(bpage); - return ERR_PTR(-ENOMEM); + (*rpage)->data = alloc_cpu_data(cpu, (*rpage)->order); + if (!(*rpage)->data) { + kfree(*rpage); + *rpage = NULL; + return -ENOMEM; } } - return bpage; + return 0; } EXPORT_SYMBOL_GPL(ring_buffer_alloc_read_page); @@ -7055,21 +7082,30 @@ EXPORT_SYMBOL_GPL(ring_buffer_alloc_read_page); * ring_buffer_free_read_page - free an allocated read page * @buffer: the buffer the page was allocate for * @cpu: the cpu buffer the page came from - * @data_page: the page to free + * @rpage: the buffer_data_read_page to free * * Free a page allocated from ring_buffer_alloc_read_page. */ void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu, - struct buffer_data_read_page *data_page) + struct buffer_data_read_page *rpage) { struct ring_buffer_per_cpu *cpu_buffer; - struct buffer_data_page *dpage = data_page->data; - struct page *page = virt_to_page(dpage); + struct buffer_data_page *dpage; unsigned long flags; + struct page *page; if (!buffer || !buffer->buffers || !buffer->buffers[cpu]) return; + if (!rpage) + return; + + dpage = rpage->data; + if (!dpage) + goto out; + + page = virt_to_page(dpage); + cpu_buffer = buffer->buffers[cpu]; /* @@ -7077,14 +7113,14 @@ void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu, * is different from the subbuffer order of the buffer - * we can't reuse it */ - if (page_ref_count(page) > 1 || data_page->order != buffer->subbuf_order) + if (page_ref_count(page) > 1 || rpage->order != READ_ONCE(buffer->subbuf_order)) goto out; local_irq_save(flags); arch_spin_lock(&cpu_buffer->lock); if (!cpu_buffer->free_page.data) { - cpu_buffer->free_page = *data_page; + cpu_buffer->free_page = *rpage; dpage = NULL; } @@ -7092,8 +7128,8 @@ void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu, local_irq_restore(flags); out: - free_pages((unsigned long)dpage, data_page->order); - kfree(data_page); + free_pages((unsigned long)dpage, rpage->order); + kfree(rpage); } EXPORT_SYMBOL_GPL(ring_buffer_free_read_page); @@ -7164,10 +7200,9 @@ int ring_buffer_read_page(struct trace_buffer *buffer, if (!dpage) return -1; - guard(raw_spinlock_irqsave)(&cpu_buffer->reader_lock); + len = min_t(size_t, len, rb_read_page_capacity(data_page)); - if (data_page->order != cpu_buffer->reader_page->order) - return -1; + guard(raw_spinlock_irqsave)(&cpu_buffer->reader_lock); reader = rb_get_reader_page(cpu_buffer); if (!reader) @@ -7182,16 +7217,18 @@ int ring_buffer_read_page(struct trace_buffer *buffer, /* Check if any events were dropped */ missed_events = cpu_buffer->lost_events; - /* - * If this page has been partially read or - * if len is not big enough to read the rest of the page or - * a writer is still on the page, then - * we must copy the data from the page to the buffer. - * Otherwise, we can simply swap the page with the one passed in. - */ + /* + * It is not possible to swap the reader page if: + * - It has been partially read + * - len is not big enough to read it entirely + * - A writer is still on it + * - The ring buffer is static + * - The order doesn't match + */ if (read || (len < (size - read)) || cpu_buffer->reader_page == cpu_buffer->commit_page || - rb_is_static(cpu_buffer)) { + rb_is_static(cpu_buffer) || + data_page->order != reader->order) { struct buffer_data_page *rpage = cpu_buffer->reader_page->page; unsigned int rpos = read; unsigned int pos = 0; @@ -7285,7 +7322,7 @@ int ring_buffer_read_page(struct trace_buffer *buffer, * missed events, then record it there. */ if (missed_events > 0 && - rb_page_capacity(reader) - size >= sizeof(missed_events)) { + rb_read_page_capacity(data_page) - size >= sizeof(missed_events)) { memcpy(&dpage->data[size], &missed_events, sizeof(missed_events)); local_add(RB_MISSED_STORED, &dpage->commit); @@ -7305,8 +7342,8 @@ int ring_buffer_read_page(struct trace_buffer *buffer, /* * This page may be off to user land. Zero it out here. */ - if (size < rb_page_capacity(reader)) - memset(&dpage->data[size], 0, rb_page_capacity(reader) - size); + if (size < rb_read_page_capacity(data_page)) + memset(&dpage->data[size], 0, rb_read_page_capacity(data_page) - size); return read; } @@ -7324,6 +7361,18 @@ void *ring_buffer_read_page_data(struct buffer_data_read_page *page) } EXPORT_SYMBOL_GPL(ring_buffer_read_page_data); +/** + * ring_buffer_read_page_size - get size of the read page. + * @page: the page to get the size from + * + * Returns size of the page in bytes. + */ +unsigned int ring_buffer_read_page_size(struct buffer_data_read_page *rpage) +{ + return rpage ? PAGE_SIZE << rpage->order : 0; +} +EXPORT_SYMBOL_GPL(ring_buffer_read_page_size); + /** * ring_buffer_subbuf_size_get - get size of the sub buffer. * @buffer: the buffer to get the sub buffer size from @@ -7409,7 +7458,7 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order) /* Make sure all commits have finished */ synchronize_rcu(); - buffer->subbuf_order = order; + WRITE_ONCE(buffer->subbuf_order, order); /* Make sure all new buffers are allocated, before deleting the old ones */ for_each_buffer_cpu(buffer, cpu) { @@ -7513,7 +7562,7 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order) return 0; error: - buffer->subbuf_order = old_order; + WRITE_ONCE(buffer->subbuf_order, old_order); atomic_dec(&buffer->record_disabled); diff --git a/kernel/trace/ring_buffer_benchmark.c b/kernel/trace/ring_buffer_benchmark.c index 593e3b59e42e..c3d34c0e64e2 100644 --- a/kernel/trace/ring_buffer_benchmark.c +++ b/kernel/trace/ring_buffer_benchmark.c @@ -104,7 +104,7 @@ static enum event_status read_event(int cpu) static enum event_status read_page(int cpu) { - struct buffer_data_read_page *bpage; + struct buffer_data_read_page *bpage = NULL; struct ring_buffer_event *event; struct rb_page *rpage; unsigned long commit; @@ -114,8 +114,8 @@ static enum event_status read_page(int cpu) int inc; int i; - bpage = ring_buffer_alloc_read_page(buffer, cpu); - if (IS_ERR(bpage)) + ret = ring_buffer_alloc_read_page(buffer, cpu, &bpage); + if (ret < 0) return EVENT_DROPPED; page_size = ring_buffer_subbuf_size_get(buffer); diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 138e983c3c2f..b26c4c277ce5 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -7082,8 +7082,8 @@ ssize_t tracing_buffers_read(struct file *filp, char __user *ubuf, { struct ftrace_buffer_info *info = filp->private_data; struct trace_iterator *iter = &info->iter; + unsigned int spare_size; void *trace_data; - int page_size; ssize_t ret = 0; ssize_t size; @@ -7093,36 +7093,22 @@ ssize_t tracing_buffers_read(struct file *filp, char __user *ubuf, if (iter->snapshot && tracer_uses_snapshot(iter->tr->current_trace)) return -EBUSY; - page_size = ring_buffer_subbuf_size_get(iter->array_buffer->buffer); - - /* Make sure the spare matches the current sub buffer size */ - if (info->spare) { - if (page_size != info->spare_size) { - ring_buffer_free_read_page(iter->array_buffer->buffer, - info->spare_cpu, info->spare); - info->spare = NULL; - } - } - - if (!info->spare) { - info->spare = ring_buffer_alloc_read_page(iter->array_buffer->buffer, - iter->cpu_file); - if (IS_ERR(info->spare)) { - ret = PTR_ERR(info->spare); - info->spare = NULL; - } else { - info->spare_cpu = iter->cpu_file; - info->spare_size = page_size; - } - } - if (!info->spare) - return ret; + spare_size = ring_buffer_read_page_size(info->spare); +again: /* Do we have previous read data to read? */ - if (info->read < page_size) + if (info->read < spare_size) goto read; - again: + ret = ring_buffer_alloc_read_page(iter->array_buffer->buffer, iter->cpu_file, + &info->spare); + if (ret) + return ret; + + spare_size = ring_buffer_read_page_size(info->spare); + info->read = spare_size; + info->spare_cpu = iter->cpu_file; + trace_access_lock(iter->cpu_file); ret = ring_buffer_read_page(iter->array_buffer->buffer, info->spare, @@ -7148,8 +7134,9 @@ ssize_t tracing_buffers_read(struct file *filp, char __user *ubuf, } info->read = 0; + read: - size = page_size - info->read; + size = spare_size - info->read; if (size > count) size = count; trace_data = ring_buffer_read_page_data(info->spare); @@ -7190,26 +7177,24 @@ int tracing_buffers_release(struct inode *inode, struct file *file) __trace_array_put(iter->tr); - if (info->spare) - ring_buffer_free_read_page(iter->array_buffer->buffer, - info->spare_cpu, info->spare); + ring_buffer_free_read_page(iter->array_buffer->buffer, info->spare_cpu, info->spare); kvfree(info); return 0; } struct buffer_ref { - struct trace_buffer *buffer; - void *page; - int cpu; - refcount_t refcount; + struct trace_buffer *buffer; + struct buffer_data_read_page *rpage; + int cpu; + refcount_t refcount; }; static void buffer_ref_release(struct buffer_ref *ref) { if (!refcount_dec_and_test(&ref->refcount)) return; - ring_buffer_free_read_page(ref->buffer, ref->cpu, ref->page); + ring_buffer_free_read_page(ref->buffer, ref->cpu, ref->rpage); kfree(ref); } @@ -7268,25 +7253,15 @@ ssize_t tracing_buffers_splice_read(struct file *file, loff_t *ppos, .ops = &buffer_pipe_buf_ops, .spd_release = buffer_spd_release, }; + unsigned int page_size = 0; struct buffer_ref *ref; bool woken = false; - int page_size; int entries, i; ssize_t ret = 0; if (iter->snapshot && tracer_uses_snapshot(iter->tr->current_trace)) return -EBUSY; - page_size = ring_buffer_subbuf_size_get(iter->array_buffer->buffer); - if (*ppos & (page_size - 1)) - return -EINVAL; - - if (len & (page_size - 1)) { - if (len < page_size) - return -EINVAL; - len &= (~(page_size - 1)); - } - if (splice_grow_spd(pipe, &spd)) return -ENOMEM; @@ -7306,25 +7281,37 @@ ssize_t tracing_buffers_splice_read(struct file *file, loff_t *ppos, refcount_set(&ref->refcount, 1); ref->buffer = iter->array_buffer->buffer; - ref->page = ring_buffer_alloc_read_page(ref->buffer, iter->cpu_file); - if (IS_ERR(ref->page)) { - ret = PTR_ERR(ref->page); - ref->page = NULL; + + ret = ring_buffer_alloc_read_page(ref->buffer, iter->cpu_file, &ref->rpage); + if (ret) { kfree(ref); break; } ref->cpu = iter->cpu_file; - r = ring_buffer_read_page(ref->buffer, ref->page, - len, iter->cpu_file, 1); + page_size = ring_buffer_read_page_size(ref->rpage); + + r = -EINVAL; + if (IS_ALIGNED(*ppos, page_size) && len >= page_size) { + r = ring_buffer_read_page(ref->buffer, ref->rpage, len, iter->cpu_file, 1); + } else if (!i) { + /* + * We failed to read because the length is too small + * or unaligned. If this is the first iteration, it's + * an invalid userspace input. Otherwise, this is due + * to a subbuf order change. Do not report an error + * and just finish the read. + */ + ret = -EINVAL; + } + if (r < 0) { - ring_buffer_free_read_page(ref->buffer, ref->cpu, - ref->page); + ring_buffer_free_read_page(ref->buffer, ref->cpu, ref->rpage); kfree(ref); break; } - page = virt_to_page(ring_buffer_read_page_data(ref->page)); + page = virt_to_page(ring_buffer_read_page_data(ref->rpage)); spd.pages[i] = page; spd.partial[i].len = page_size; diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 3c111ca88e32..5e76f94e7a80 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -745,11 +745,10 @@ static inline int tracing_get_cpu(struct inode *inode) void tracing_reset_cpu(struct array_buffer *buf, int cpu); struct ftrace_buffer_info { - struct trace_iterator iter; - void *spare; - unsigned int spare_cpu; - unsigned int spare_size; - unsigned int read; + struct trace_iterator iter; + struct buffer_data_read_page *spare; + unsigned int spare_cpu; + unsigned int read; }; /** From f2b2b645595c82b4e824880f6cb987e077a8da19 Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Fri, 4 Sep 2026 17:44:49 +0100 Subject: [PATCH 0875/2366] ring-buffer: Cap static ring buffer nr_pages Static ring buffers (i.e. persistent, user-mapped and remote) rely on the bpage::id field. The number of pages for those ring buffers must fit into that variable. Enforce this limit on ring buffer creation or user-mapping. While at it, prevent nr_pages underflow when allocating a persistent buffer. Link: https://patch.msgid.link/20260904164450.1345852-4-vdonnefort@google.com Fixes: be68d63a139b ("ring-buffer: Add ring_buffer_alloc_range()") Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 077d6940af0c..76fed01f1c49 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -657,6 +657,15 @@ static bool rb_is_static(struct ring_buffer_per_cpu *cpu_buffer) return cpu_buffer->user_mapped || cpu_buffer->remote || cpu_buffer->ring_meta; } +static unsigned long rb_static_max_pages(void) +{ + /* + * Static ring buffers are using bpage::id and must account for the + * reader page. + */ + return (1UL << 30) - 1; +} + struct ring_buffer_iter { struct ring_buffer_per_cpu *cpu_buffer; unsigned long head; @@ -2838,6 +2847,8 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags, size = end - buffers_start; size = size / nr_cpu_ids; + if (size < sizeof(struct ring_buffer_cpu_meta)) + goto fail_free_buffers; /* * The number of sub-buffers (nr_pages) is determined by the * total size allocated minus the meta data size. @@ -2847,6 +2858,10 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags, */ nr_pages = (size - sizeof(struct ring_buffer_cpu_meta)) / (subbuf_size + sizeof(int)); + + if (nr_pages > rb_static_max_pages()) + goto fail_free_buffers; + /* Need at least two pages plus the reader page */ if (nr_pages < 3) goto fail_free_buffers; @@ -2879,6 +2894,10 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags, /* The writer is remote. This ring-buffer is read-only */ atomic_inc(&buffer->record_disabled); nr_pages = desc->nr_page_va - 1; + + if (nr_pages > rb_static_max_pages()) + goto fail_free_buffers; + if (nr_pages < 2) goto fail_free_buffers; } else { @@ -7841,6 +7860,9 @@ int ring_buffer_map(struct trace_buffer *buffer, int cpu, /* prevent another thread from changing buffer/sub-buffer sizes */ guard(mutex)(&buffer->mutex); + if (cpu_buffer->nr_pages > rb_static_max_pages()) + return -E2BIG; + err = rb_alloc_meta_page(cpu_buffer); if (err) return err; From c843fd3c73c94cb90b01c6bfe8d83796e652864d Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Fri, 4 Sep 2026 17:44:50 +0100 Subject: [PATCH 0876/2366] ring-buffer: Prevent truncation of nr_pages / nr_subbufs Although ring_buffer_per_cpu::nr_pages is defined as unsigned long, it is capped to 32-bits in a few places, limiting the operations possible on a very large buffer. Use `unsigned long` where appropriate and prevent truncation of values using nr_pages (or nr_subbufs). While at it, subbuf_size must be at least `unsigned int`. Note that persistent, remote and user-mapped ring buffers are capping the number of pages to 30 bits already, making "int" safe in many places. Link: https://patch.msgid.link/20260904164450.1345852-5-vdonnefort@google.com Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 61 +++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 76fed01f1c49..220b8405adfc 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -1683,7 +1683,7 @@ static void rb_check_pages(struct ring_buffer_per_cpu *cpu_buffer) * This is used to help find the next per cpu subbuffer within a mapped range. */ static unsigned long -rb_range_align_subbuf(unsigned long addr, int subbuf_size, int nr_subbufs) +rb_range_align_subbuf(unsigned long addr, unsigned int subbuf_size, unsigned long nr_subbufs) { addr += sizeof(struct ring_buffer_cpu_meta) + sizeof(int) * nr_subbufs; @@ -1693,13 +1693,12 @@ rb_range_align_subbuf(unsigned long addr, int subbuf_size, int nr_subbufs) /* * Return the ring_buffer_meta for a given @cpu. */ -static void *rb_range_meta(struct trace_buffer *buffer, int nr_pages, int cpu) +static void *rb_range_meta(struct trace_buffer *buffer, unsigned long nr_pages, int cpu) { - int subbuf_size = rb_subbuf_size(buffer); + unsigned int subbuf_size = rb_subbuf_size(buffer); struct ring_buffer_cpu_meta *meta; struct ring_buffer_meta *bmeta; - unsigned long ptr; - int nr_subbufs; + unsigned long ptr, nr_subbufs; bmeta = buffer->meta; if (!bmeta) @@ -1745,7 +1744,7 @@ static void *rb_range_meta(struct trace_buffer *buffer, int nr_pages, int cpu) /* Return the start of subbufs given the meta pointer */ static void *rb_subbufs_from_meta(struct ring_buffer_cpu_meta *meta) { - int subbuf_size = meta->subbuf_size; + unsigned int subbuf_size = meta->subbuf_size; unsigned long ptr; ptr = (unsigned long)meta; @@ -1757,11 +1756,11 @@ static void *rb_subbufs_from_meta(struct ring_buffer_cpu_meta *meta) /* * Return a specific sub-buffer for a given @cpu defined by @idx. */ -static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, int idx) +static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, unsigned long idx) { struct ring_buffer_cpu_meta *meta; + unsigned int subbuf_size; unsigned long ptr; - int subbuf_size; meta = rb_range_meta(cpu_buffer->buffer, 0, cpu_buffer->cpu); if (!meta) @@ -1777,7 +1776,7 @@ static void *rb_range_buffer(struct ring_buffer_per_cpu *cpu_buffer, int idx) ptr = (unsigned long)rb_subbufs_from_meta(meta); - ptr += subbuf_size * idx; + ptr += (unsigned long)subbuf_size * idx; if (ptr + subbuf_size > cpu_buffer->buffer->range_addr_end) return NULL; @@ -1854,13 +1853,12 @@ static bool rb_meta_init(struct trace_buffer *buffer, int scratch_size) * must be the same. */ static bool rb_cpu_meta_valid(struct ring_buffer_cpu_meta *meta, int cpu, - struct trace_buffer *buffer, int nr_pages, + struct trace_buffer *buffer, unsigned long nr_pages, unsigned long *subbuf_mask) { - int subbuf_size = PAGE_SIZE; unsigned long buffers_start; unsigned long buffers_end; - int i; + unsigned long i; if (!subbuf_mask) return false; @@ -1876,7 +1874,7 @@ static bool rb_cpu_meta_valid(struct ring_buffer_cpu_meta *meta, int cpu, } buffers_start = meta->first_buffer; - buffers_end = meta->first_buffer + (subbuf_size * meta->nr_subbufs); + buffers_end = meta->first_buffer + (meta->nr_subbufs * PAGE_SIZE); /* Is the head and commit buffers within the range of buffers? */ if (meta->head_buffer < buffers_start || @@ -2114,8 +2112,8 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer) struct buffer_page *head_page, *orig_head, *orig_reader; struct rb_validation_state state = { 0 }; bool skip = false; + unsigned long i; int ret; - int i; if (!meta || !meta->head_buffer) return; @@ -2166,7 +2164,7 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer) rb_validate_buffer(head_page, cpu_buffer, meta, &state, 0, state.ts); } if (i) - pr_info("Ring buffer [%d] rewound %d pages\n", cpu_buffer->cpu, i); + pr_info("Ring buffer [%d] rewound %lu pages\n", cpu_buffer->cpu, i); /* The last rewound page must be skipped. */ if (head_page != orig_head) @@ -2250,7 +2248,8 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer) } } -static void rb_range_meta_init(struct trace_buffer *buffer, int nr_pages, int scratch_size) +static void rb_range_meta_init(struct trace_buffer *buffer, unsigned long nr_pages, + int scratch_size) { struct ring_buffer_cpu_meta *meta; unsigned long *subbuf_mask; @@ -2350,8 +2349,8 @@ static int rbm_show(struct seq_file *m, void *v) rb_meta_subbuf_idx(meta, (void *)meta->head_buffer)); seq_printf(m, "commit_buffer: %d\n", rb_meta_subbuf_idx(meta, (void *)meta->commit_buffer)); - seq_printf(m, "subbuf_size: %d\n", meta->subbuf_size); - seq_printf(m, "nr_subbufs: %d\n", meta->nr_subbufs); + seq_printf(m, "subbuf_size: %u\n", meta->subbuf_size); + seq_printf(m, "nr_subbufs: %u\n", meta->nr_subbufs); return 0; } @@ -2436,7 +2435,7 @@ static void *ring_buffer_desc_page(struct ring_buffer_desc *desc, unsigned int p } static int __rb_allocate_pages(struct ring_buffer_per_cpu *cpu_buffer, - long nr_pages, struct list_head *pages) + unsigned long nr_pages, struct list_head *pages) { struct trace_buffer *buffer = cpu_buffer->buffer; struct ring_buffer_cpu_meta *meta = NULL; @@ -2564,7 +2563,7 @@ static int rb_allocate_pages(struct ring_buffer_per_cpu *cpu_buffer, } static struct ring_buffer_per_cpu * -rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu) +rb_allocate_cpu_buffer(struct trace_buffer *buffer, unsigned long nr_pages, int cpu) { struct ring_buffer_per_cpu *cpu_buffer __free(kfree) = alloc_cpu_buffer(cpu); @@ -2721,8 +2720,8 @@ static void rb_test_inject_invalid_pages(struct trace_buffer *buffer) struct ring_buffer_cpu_meta *meta; struct buffer_data_page *dpage; unsigned long entry_bytes = 0; + unsigned int subbuf_size; unsigned long ptr; - int subbuf_size; int invalid = 0; int cpu; int i; @@ -2792,8 +2791,8 @@ static struct trace_buffer *alloc_buffer(unsigned long size, unsigned flags, struct ring_buffer_remote *remote) { struct trace_buffer *buffer __free(kfree) = NULL; - long nr_pages; - int subbuf_size; + unsigned int subbuf_size; + unsigned long nr_pages; int bsize; int cpu; int ret; @@ -5882,12 +5881,12 @@ __rb_get_reader_page_from_remote(struct ring_buffer_per_cpu *cpu_buffer) static struct buffer_page * __rb_get_reader_page(struct ring_buffer_per_cpu *cpu_buffer) { - int max_loops = cpu_buffer->ring_meta ? cpu_buffer->nr_pages : 3; + unsigned long max_loops = cpu_buffer->ring_meta ? cpu_buffer->nr_pages : 3; struct buffer_page *reader = NULL; + unsigned long nr_loops = 0; unsigned long overwrite; unsigned long flags; int missed_events = 0; - int nr_loops = 0; bool ret; local_irq_save(flags); @@ -6205,8 +6204,8 @@ rb_iter_peek(struct ring_buffer_iter *iter, u64 *ts) struct trace_buffer *buffer; struct ring_buffer_per_cpu *cpu_buffer; struct ring_buffer_event *event; - int nr_loops = 0; - int max_loops; + unsigned long nr_loops = 0; + unsigned long max_loops; if (ts) *ts = 0; @@ -7446,8 +7445,8 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order) struct ring_buffer_per_cpu *cpu_buffer; struct buffer_page *bpage, *tmp; unsigned int old_capacity; + unsigned long nr_pages; int old_order; - int nr_pages; int psize; int err; int cpu; @@ -7629,10 +7628,10 @@ static void rb_setup_ids_meta_page(struct ring_buffer_per_cpu *cpu_buffer, struct buffer_page **subbuf_ids) { struct trace_buffer_meta *meta = cpu_buffer->meta_page; - unsigned int nr_subbufs = cpu_buffer->nr_pages + 1; + unsigned long nr_subbufs = cpu_buffer->nr_pages + 1; struct buffer_page *first_subbuf, *subbuf; - int cnt = 0; - int id = 0; + unsigned int cnt = 0; + unsigned int id = 0; id = rb_page_id(cpu_buffer, cpu_buffer->reader_page, id); subbuf_ids[id++] = cpu_buffer->reader_page; From 5cbea500775dd1944995f23320af030b9b24b24b Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Fri, 4 Sep 2026 14:49:02 -0400 Subject: [PATCH 0877/2366] tracing: Fix comment in tracing_buffers_splice_read() The comment about returning an error if the read fails on the first iteration is slightly incorrect. It makes it sound like the only reason it could fail on a later iteration is if the subbuf order changed. That is incorrect, it could also fail if the length passed in was not a multiple of the subbuf size. Fix the comment. Link: https://lore.kernel.org/all/20260904143527.40e73d36@gandalf.local.home/ Link: https://patch.msgid.link/20260904144902.506862a1@gandalf.local.home Fixes: dae8dda341d2 ("tracing: Fix subbuf resize races with trace_pipe_raw readers") Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index b26c4c277ce5..8658cad53cb5 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -7296,11 +7296,13 @@ ssize_t tracing_buffers_splice_read(struct file *file, loff_t *ppos, r = ring_buffer_read_page(ref->buffer, ref->rpage, len, iter->cpu_file, 1); } else if (!i) { /* - * We failed to read because the length is too small - * or unaligned. If this is the first iteration, it's - * an invalid userspace input. Otherwise, this is due - * to a subbuf order change. Do not report an error - * and just finish the read. + * If this fails to read on the first iteration, it + * means the length was too small and an error should + * be returned to user space. Otherwise, at least + * one sub-buffer was successfully read but this failed + * due to either the length was unaligned or the + * subbuf order changed. Either case, do not report + * an error. */ ret = -EINVAL; } From d80e12156f1fd490adf29a8d28489725a3ac817a Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Fri, 4 Sep 2026 15:16:41 -0400 Subject: [PATCH 0878/2366] ring-buffer: Use a macro for static buffer bits Instead of hard coding 30 for the number of bits used for the static buffer ids in two places, create a macro. This way if it changes in the future, it will change in all the locations that use it. Link: https://patch.msgid.link/20260904151641.17eae0aa@gandalf.local.home Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 220b8405adfc..b88c75b52e8f 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -335,6 +335,9 @@ static __always_inline unsigned int rb_read_page_capacity(struct buffer_data_rea return (PAGE_SIZE << rpage->order) - BUF_PAGE_HDR_SIZE; } +/* The number of bits for static buffer ids */ +#define RB_STATIC_BITS 30 + /* * Note, the buffer_page list must be first. The buffer pages * are allocated in cache lines, which means that each buffer @@ -350,7 +353,7 @@ struct buffer_page { local_t entries; /* entries on this page */ unsigned long real_end; /* real end of data */ unsigned order; /* order of the page */ - u32 id:30; /* ID for external mapping */ + u32 id:RB_STATIC_BITS; /* ID for external mapping */ u32 range:1; /* Mapped via a range */ struct buffer_data_page *page; /* Actual data page */ }; @@ -663,7 +666,7 @@ static unsigned long rb_static_max_pages(void) * Static ring buffers are using bpage::id and must account for the * reader page. */ - return (1UL << 30) - 1; + return (1UL << RB_STATIC_BITS) - 1; } struct ring_buffer_iter { From 5bd9e4e7cdaa03879e9b73b12ab52cceb1edd55b Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Thu, 3 Sep 2026 11:02:59 +0300 Subject: [PATCH 0879/2366] nexthop: Initialize extack in remove_nh_grp_entry() remove_nh_grp_entry() prints the extack message when a listener fails to replace the reduced nexthop group. However, extack is not initialized and listeners are not required to set a message when returning an error. Neither netdevsim nor mlxsw do so when an allocation fails, resulting in the dereference of an uninitialized stack pointer. Fix by zero-initializing extack, as was done in commit 6347c5314cee ("nexthop: initialize extack in nh_res_bucket_migrate()"). Fixes: 833a1065eeb1 ("nexthop: Emit a notification when a nexthop group is reduced") Signed-off-by: Ido Schimmel Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260903080259.10378-1-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- net/ipv4/nexthop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c index a7c2b8dced4e..42e55b5a755e 100644 --- a/net/ipv4/nexthop.c +++ b/net/ipv4/nexthop.c @@ -2036,7 +2036,7 @@ remove_nh_grp_entry(struct net *net, struct nh_grp_entry *nhge, { struct nh_grp_entry *nhges, *new_nhges; struct nexthop *nhp = nhge->nh_parent; - struct netlink_ext_ack extack; + struct netlink_ext_ack extack = {}; struct nexthop *nh = nhge->nh; struct nh_group *nhg, *newg; int i, j, err; From 1746ef2e2df2ad71c66eca56364d56bde284523b Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 3 Sep 2026 14:39:40 +0000 Subject: [PATCH 0880/2366] bonding: use skb_cow_head() in bond_do_alb_xmit() and rlb_arp_xmit() In bond_do_alb_xmit() and rlb_arp_xmit(), make sure to unclone skb head via skb_cow_head() before modifying the source MAC address (Ethernet header and ARP payload) to avoid silent corruption if the skb is shared or cloned. Avoid caching the header pointers across skb_cow_head(). In rlb_arp_xmit(), only modify arp->mac_src if it differs from tx_slave->dev->dev_addr to avoid an unnecessary copy and head reallocation. Also, we should not assume mac header is set in output path. Use skb_eth_hdr() instead of eth_hdr() to fix the issue, and remove now redundant skb_reset_mac_header() calls. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet Reviewed-by: Hangbin Liu Cc: Jay Vosburgh Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260903143940.1180513-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_alb.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/net/bonding/bond_alb.c b/drivers/net/bonding/bond_alb.c index 654f051d0023..43ac8e28e418 100644 --- a/drivers/net/bonding/bond_alb.c +++ b/drivers/net/bonding/bond_alb.c @@ -678,9 +678,15 @@ static struct slave *rlb_arp_xmit(struct sk_buff *skb, struct bonding *bond) if (arp->op_code == htons(ARPOP_REPLY)) { /* the arp must be sent on the selected rx channel */ tx_slave = rlb_choose_channel(skb, bond, arp); - if (tx_slave) + if (tx_slave && + !ether_addr_equal_64bits(arp->mac_src, + tx_slave->dev->dev_addr)) { + if (unlikely(skb_cow_head(skb, 0))) + return NULL; + arp = (struct arp_pkt *)skb_network_header(skb); bond_hw_addr_copy(arp->mac_src, tx_slave->dev->dev_addr, tx_slave->dev->addr_len); + } netdev_dbg(bond->dev, "(slave %s): Server sent ARP Reply packet\n", tx_slave ? tx_slave->dev->name : "NULL"); } else if (arp->op_code == htons(ARPOP_REQUEST)) { @@ -1340,7 +1346,6 @@ static netdev_tx_t bond_do_alb_xmit(struct sk_buff *skb, struct bonding *bond, struct slave *tx_slave) { struct alb_bond_info *bond_info = &(BOND_ALB_INFO(bond)); - struct ethhdr *eth_data = eth_hdr(skb); if (!tx_slave) { /* unbalanced or unassigned, send through primary */ @@ -1351,7 +1356,9 @@ static netdev_tx_t bond_do_alb_xmit(struct sk_buff *skb, struct bonding *bond, if (tx_slave && bond_slave_can_tx(tx_slave)) { if (tx_slave != rcu_access_pointer(bond->curr_active_slave)) { - ether_addr_copy(eth_data->h_source, + if (unlikely(skb_cow_head(skb, 0))) + return bond_tx_drop(bond->dev, skb); + ether_addr_copy(skb_eth_hdr(skb)->h_source, tx_slave->dev->dev_addr); } @@ -1375,8 +1382,7 @@ struct slave *bond_xmit_tlb_slave_get(struct bonding *bond, struct ethhdr *eth_data; u32 hash_index; - skb_reset_mac_header(skb); - eth_data = eth_hdr(skb); + eth_data = skb_eth_hdr(skb); /* Do not TX balance any multicast or broadcast */ if (!is_multicast_ether_addr(eth_data->h_dest)) { @@ -1428,8 +1434,7 @@ struct slave *bond_xmit_alb_slave_get(struct bonding *bond, u32 hash_index = 0; int hash_size = 0; - skb_reset_mac_header(skb); - eth_data = eth_hdr(skb); + eth_data = skb_eth_hdr(skb); switch (ntohs(skb->protocol)) { case ETH_P_IP: { From cdb719f4b8596d9ccee2d56d204c2c4dce982f46 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:07 -0700 Subject: [PATCH 0881/2366] net: dsa: bcm_sf2: bound the CFP rule dump by the caller's buffer size bcm_sf2_cfp_rule_get_all() walks the whole cfp.unique bitmap into rule_locs[] without consulting nfc->rule_cnt, which is how many entries the caller had room for. ETHTOOL_GRXCLSRLALL requires no CAP_NET_ADMIN and the ioctl sizes the buffer from the rule_cnt userspace passes in, so once an admin has installed CFP rules any user can ask for fewer slots than there are rules and run off the end of the allocation. A rule_cnt of 0 leaves the buffer pointer NULL and the walk dereferences it. Fixes: 7318166cacad ("net: dsa: bcm_sf2: Add support for ethtool::rxnfc") Reviewed-by: Jonas Gorski Reviewed-by: Florian Fainelli Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/bcm_sf2_cfp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/dsa/bcm_sf2_cfp.c b/drivers/net/dsa/bcm_sf2_cfp.c index 50d3a818eb1b..84a086c3e99b 100644 --- a/drivers/net/dsa/bcm_sf2_cfp.c +++ b/drivers/net/dsa/bcm_sf2_cfp.c @@ -1088,6 +1088,8 @@ static int bcm_sf2_cfp_rule_get_all(struct bcm_sf2_priv *priv, unsigned int index = 1, rules_cnt = 0; for_each_set_bit_from(index, priv->cfp.unique, priv->num_cfp_rules) { + if (rules_cnt == nfc->rule_cnt) + return -EMSGSIZE; rule_locs[rules_cnt] = index; rules_cnt++; } From f1986bf87b0709c95126fe196cf39e5b8c8453a1 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:08 -0700 Subject: [PATCH 0882/2366] eth: nfp: bound the ntuple rule dump by the caller's buffer size nfp_net_get_fs_loc() dumps every entry of nn->fs.list into rule_locs[] without consulting cmd->rule_cnt, which is how many entries the caller had room for. ETHTOOL_GRXCLSRLALL requires no CAP_NET_ADMIN and the ioctl sizes the buffer from the rule_cnt userspace passes in, so once an admin has installed flow steering rules any user can ask for fewer slots than there are rules and run off the end of the allocation. A rule_cnt of 0 leaves the buffer pointer NULL and the walk dereferences it. Bail out with -EMSGSIZE when the buffer fills up, the way the other ntuple capable drivers do, and report how many locations were filled so a shrinking rule list does not leave the caller reading stale slots. Reported-by: VEGA Fixes: 9eb03bb1c035 ("nfp: add ethtool flow steering callbacks") Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c b/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c index a2a89d48e3ca..9419e1ed8466 100644 --- a/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c +++ b/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c @@ -1421,7 +1421,8 @@ static int nfp_net_get_fs_rule(struct nfp_net *nn, struct ethtool_rxnfc *cmd) return -ENOENT; } -static int nfp_net_get_fs_loc(struct nfp_net *nn, u32 *rule_locs) +static int nfp_net_get_fs_loc(struct nfp_net *nn, struct ethtool_rxnfc *cmd, + u32 *rule_locs) { struct nfp_fs_entry *entry; u32 count = 0; @@ -1429,8 +1430,12 @@ static int nfp_net_get_fs_loc(struct nfp_net *nn, u32 *rule_locs) if (!(nn->cap_w1 & NFP_NET_CFG_CTRL_FLOW_STEER)) return -EOPNOTSUPP; - list_for_each_entry(entry, &nn->fs.list, node) + list_for_each_entry(entry, &nn->fs.list, node) { + if (count == cmd->rule_cnt) + return -EMSGSIZE; rule_locs[count++] = entry->loc; + } + cmd->rule_cnt = count; return 0; } @@ -1455,7 +1460,7 @@ static int nfp_net_get_rxnfc(struct net_device *netdev, return nfp_net_get_fs_rule(nn, cmd); case ETHTOOL_GRXCLSRLALL: cmd->data = NFP_FS_MAX_ENTRY; - return nfp_net_get_fs_loc(nn, rule_locs); + return nfp_net_get_fs_loc(nn, cmd, rule_locs); default: return -EOPNOTSUPP; } From 108bb2142e3a12c9ad625ad662973127a113ddc6 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:09 -0700 Subject: [PATCH 0883/2366] eth: nfp: drop the replaced rule from the list when reprogramming fails nfp_net_fs_add() replaces an existing rule by deleting it from the hardware, decrementing nn->fs.count and programming the new one. If nfp_net_fs_add_hw() fails the old entry stays on nn->fs.list - only the success path reaches list_replace() - so the list is one longer than nn->fs.count, and it advertises a rule whose hardware entry has already been torn down. nn->fs.count is what ETHTOOL_GRXCLSRLCNT reports, so userspace then sizes its buffer one entry short of what the GRXCLSRLALL walk wants to write. That used to overwrite one u32 past the allocation; since the walk is bounded it is a permanent -EMSGSIZE instead, as nothing ever resyncs the counter. Fixes: 9eb03bb1c035 ("nfp: add ethtool flow steering callbacks") Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-4-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c b/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c index 9419e1ed8466..4e83637715e0 100644 --- a/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c +++ b/drivers/net/ethernet/netronome/nfp/nfp_net_ethtool.c @@ -1703,8 +1703,14 @@ static int nfp_net_fs_add(struct nfp_net *nn, struct ethtool_rxnfc *cmd) nn->fs.count--; err = nfp_net_fs_add_hw(nn, new); - if (err) + if (err) { + /* mbox broken, adding the old rule back will + * likely also fail. + */ + list_del(&entry->node); + kfree(entry); goto err; + } nn->fs.count++; list_replace(&entry->node, &new->node); From b1fffc273112e7284c5b705e186b43b5770cd3d5 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:10 -0700 Subject: [PATCH 0884/2366] net: dsa: mv88e6xxx: bound the policy rule dump by the caller's buffer size mv88e6xxx_get_rxnfc() uses rxnfc->rule_cnt as the write index while dumping the policy IDR, clobbering the input value before it has been looked at. That input is the number of entries the caller had room for. ETHTOOL_GRXCLSRLALL requires no CAP_NET_ADMIN and the ioctl sizes the buffer from the rule_cnt userspace passes in, so once an admin has installed policy rules any user can ask for fewer slots than there are rules and run off the end of the allocation. A rule_cnt of 0 leaves the buffer pointer NULL and the walk dereferences it. Count into a local so the caller's limit survives the walk, and stop with -EMSGSIZE once it is reached. Fixes: da7dc8755304 ("net: dsa: mv88e6xxx: add RXNFC support") Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-5-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mv88e6xxx/chip.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index 80b877c74513..7f68a0c55802 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -2438,6 +2438,7 @@ static int mv88e6xxx_get_rxnfc(struct dsa_switch *ds, int port, struct ethtool_rx_flow_spec *fs = &rxnfc->fs; struct mv88e6xxx_chip *chip = ds->priv; struct mv88e6xxx_policy *policy; + u32 cnt = 0; int err; int id; @@ -2463,11 +2464,18 @@ static int mv88e6xxx_get_rxnfc(struct dsa_switch *ds, int port, break; case ETHTOOL_GRXCLSRLALL: rxnfc->data = 0; - rxnfc->rule_cnt = 0; - idr_for_each_entry(&chip->policies, policy, id) - if (policy->port == port) - rule_locs[rxnfc->rule_cnt++] = id; err = 0; + idr_for_each_entry(&chip->policies, policy, id) { + if (policy->port != port) + continue; + if (cnt == rxnfc->rule_cnt) { + err = -EMSGSIZE; + break; + } + rule_locs[cnt++] = id; + } + if (!err) + rxnfc->rule_cnt = cnt; break; default: err = -EOPNOTSUPP; From 47a582b2b0e7bb5753e4803988e150a405b57f51 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 2 Sep 2026 20:26:11 -0700 Subject: [PATCH 0885/2366] ethtool: document that GRXCLSRLALL rule_cnt is a caller-provided limit Three drivers have shipped a get_rxnfc() which dumps its entire rule table into rule_locs, reading rule_cnt as "how many rules do I have" rather than "how many entries did the caller allocate". Nothing in the callback's documentation contradicted that reading. The distinction only matters because the ioctl lets an unprivileged caller pick rule_cnt directly, so getting it wrong is a heap overflow rather than a truncated dump. Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260903032611.3000029-6-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/ethtool.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 12683b5d125e..253600c0eccd 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -1057,6 +1057,12 @@ struct kernel_ethtool_ts_info { * @get_sset_count: Get number of strings that @get_strings will write. * @get_rxnfc: Get RX flow classification rules. Returns a negative * error code or zero. + * Note that for %ETHTOOL_GRXCLSRLALL rule_cnt and size of the arrays + * is user-provided, and not guaranteed to match what driver would + * have reported via %ETHTOOL_GRXCLSRLCNT. Drivers must return -%EMSGSIZE + * when rule_cnt is too small. rule_locs is %NULL when rule_cnt is zero. + * On success drivers must set rule_cnt to the number of locations they + * filled in, the core copies out exactly that many. * @set_rxnfc: Set RX flow classification rules. Returns a negative * error code or zero. * @flash_device: Write a firmware image to device's flash memory. From 4b772869a1e5f9da5cef5b9c722ec0aa424ee0a0 Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Thu, 3 Sep 2026 12:38:51 +0300 Subject: [PATCH 0886/2366] net: bridge: mcast: properly convert mglist to rcu Sashiko reported a bug [1] that br_multicast_del_port_group unlists the port group not using proper rcu helper that preserves the next pointer and after that immediately frees the port group without waiting for rcu grace period. The only rcu walker of mglist is br_multicast_list_adjacent() and it turns out that function has always been buggy because mglist was never properly converted to RCU. Fix it by converting it to rcu and moving its initialization after eth_addr's. Initializing p->next can use RCU_INIT_POINTER because we have a barrier from the hlist_add_head_rcu call later, besides we're initializing an unpublished structure anyway. [1] https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260826014200.362304-1-littleddfu%40gmail.com Fixes: 07f8ac4a1e26 ("bridge: add export of multicast database adjacent to net_dev") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260903093851.1494297-1-razor@blackwall.org Signed-off-by: Jakub Kicinski --- net/bridge/br_multicast.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 3e9b10f8abf1..2f9bb30e1a1f 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -1441,16 +1441,17 @@ struct net_bridge_port_group *br_multicast_new_port_group( goto free_out; } - rcu_assign_pointer(p->next, next); timer_setup(&p->timer, br_multicast_port_group_expired, 0); timer_setup(&p->rexmit_timer, br_multicast_port_group_rexmit, 0); - hlist_add_head(&p->mglist, &port->mglist); if (src) memcpy(p->eth_addr, src, ETH_ALEN); else eth_broadcast_addr(p->eth_addr); + RCU_INIT_POINTER(p->next, next); + hlist_add_head_rcu(&p->mglist, &port->mglist); + return p; free_out: @@ -1465,11 +1466,11 @@ void br_multicast_del_port_group(struct net_bridge_port_group *p) struct net_bridge_port *port = p->key.port; __u16 vid = p->key.addr.vid; - hlist_del_init(&p->mglist); + hlist_del_init_rcu(&p->mglist); if (!br_multicast_is_star_g(&p->key.addr)) rhashtable_remove_fast(&port->br->sg_port_tbl, &p->rhnode, br_sg_port_rht_params); - kfree(p); + kfree_rcu(p, rcu); br_multicast_port_ngroups_dec(port, vid); } From 1f29543126dde307e8b5fb6a740c54e59deaa2ff Mon Sep 17 00:00:00 2001 From: Viswajith Murali Date: Tue, 1 Sep 2026 15:13:17 +0530 Subject: [PATCH 0887/2366] octeontx2-af: mcs: Clear stale X2P calibration state before calibration Some firmware versions leave MCSX_MIL_GLOBAL bit 5 set on boot. If the bit is already set when the driver attempts X2P calibration, the hardware sees no rising edge and calibration never triggers. Clear the bit and wait briefly before starting calibration to ensure a clean rising edge. Fixes: ca7f49ff8846 ("octeontx2-af: cn10k: Introduce driver for macsec block.") Signed-off-by: Nitin Shetty J Signed-off-by: Viswajith Murali Link: https://patch.msgid.link/20260901094318.1395356-1-nshettyj@marvell.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/af/mcs.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mcs.c b/drivers/net/ethernet/marvell/octeontx2/af/mcs.c index a07e0b3d8d00..211c10aa5880 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mcs.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/mcs.c @@ -1417,6 +1417,16 @@ static int mcs_x2p_calibration(struct mcs *mcs) int i, err = 0; u64 val; + /* Clear any stale calibration state left by firmware/bootloader. + * Some firmware versions may leave MCSX_MIL_GLOBAL bit 5 set, + * preventing the hardware from detecting the rising edge needed to + * trigger X2P calibration. + */ + val = mcs_reg_read(mcs, MCSX_MIL_GLOBAL); + val &= ~BIT_ULL(5); + mcs_reg_write(mcs, MCSX_MIL_GLOBAL, val); + usleep_range(100, 200); + /* set X2P calibration */ val = mcs_reg_read(mcs, MCSX_MIL_GLOBAL); val |= BIT_ULL(5); From c91b4d6e5cc30ceea3f23ebe29aec012709a065f Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Tue, 1 Sep 2026 05:56:27 +0000 Subject: [PATCH 0888/2366] ionic: use netif_txq_maybe_stop() in ionic_tx() Commit 061b9bedbef1 ("ionic: Rework Tx start/stop flow") replaced ionic_maybe_stop_tx() with netif_txq_maybe_stop() to get the memory barriers around the stop/start bits right, but did not cover the stop in ionic_tx() added by commit 138506ab249b ("ionic: Check stop no restart"). Convert the remaining site. netif_txq_maybe_stop() requires the ring indexes to be updated before it is invoked, so the post has to come first. But ring_dbell comes from __netdev_tx_sent_queue(), which runs after that and reads the stop bit, so it is not known in time to pass to ionic_txq_post(). Post without the doorbell and ring it separately. The stop condition is unchanged. The re-check only clears the stop bit when space has become available, so the doorbell starvation fixed by commit 138506ab249b ("ionic: Check stop no restart") cannot recur. Fixes: 138506ab249b ("ionic: Check stop no restart") Signed-off-by: Nikhil P. Rao Reviewed-by: Brett Creeley Link: https://patch.msgid.link/20260901055627.1373129-1-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/pensando/ionic/ionic_txrx.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c index e436e3231e86..2543a8ff8547 100644 --- a/drivers/net/ethernet/pensando/ionic/ionic_txrx.c +++ b/drivers/net/ethernet/pensando/ionic/ionic_txrx.c @@ -1672,15 +1672,22 @@ static int ionic_tx(struct net_device *netdev, struct ionic_queue *q, stats->pkts++; stats->bytes += skb->len; + ionic_txq_post(q, false); + if (likely(!ionic_txq_hwstamp_enabled(q))) { struct netdev_queue *ndq = q_to_ndq(netdev, q); - if (unlikely(!ionic_q_has_space(q, MAX_SKB_FRAGS + 1))) - netif_tx_stop_queue(ndq); + netif_txq_maybe_stop(ndq, ionic_q_space_avail(q), + MAX_SKB_FRAGS + 1, MAX_SKB_FRAGS + 1); ring_dbell = __netdev_tx_sent_queue(ndq, skb->len, netdev_xmit_more()); } - ionic_txq_post(q, ring_dbell); + + if (ring_dbell) { + ionic_dbell_ring(q->lif->kern_dbpage, q->hw_type, + q->dbval | q->head_idx); + q->dbell_jiffies = jiffies; + } return 0; } From 78a86d75a70e1e227711c72865c59b1422d0a5ae Mon Sep 17 00:00:00 2001 From: Fourie Zhang Date: Wed, 2 Sep 2026 17:27:12 +0800 Subject: [PATCH 0889/2366] net: mpls: clear inner_protocol when the last label is popped skb_mpls_push() records the pre-encapsulation network header once, gated on !skb->inner_protocol. skb_mpls_pop() never clears that record, so it outlives the encapsulation it describes. Open vSwitch can then re-push MPLS onto a packet whose inner_network_header still points at the older, deeper offset: push a label, pop every label, recirculate (ovs_flow_key_update() re-derives key->eth.type and resets network_header, but leaves inner_*), then push again. ovs_fragment() trusts the record: skb->network_header = skb->inner_network_header; so skb_network_offset() goes negative. The bound check is signed: if (skb_network_offset(skb) > MAX_L2_LEN) a negative offset passes it, and prepare_frag() widens the value: unsigned int hlen = skb_network_offset(skb); memcpy(&data->l2_data, skb->data, hlen); which is a ~4GiB memcpy out of a 30-byte per-CPU buffer. Reproduced on v7.3-rc1. RDX is the truncated length, (unsigned int)(-8): BUG: unable to handle page fault for address: ffffe8ffffc16000 #PF: supervisor write access in kernel mode Oops: 0002 [#1] SMP KASAN NOPTI RIP: 0010:memcpy+0x8/0x20 RDX: 00000000fffffff8 RSI: ffff888105d732db RDI: ffffe8ffffc16000 prepare_frag+0x3df/0x4e0 ovs_fragment+0x589/0x7e0 do_output+0x4ce/0x5e0 do_execute_actions+0x55d2/0x7b30 ovs_execute_actions+0xea/0x450 Same root-cause shape as commit 975b5b067f52 ("ipv6: sr: restore network header before routing and forwarding"): a stale network header offset reaching a consumer that widens it. Here it originates in the MPLS push/pop path. Clear inner_protocol once the packet is no longer MPLS, so a later push re-records the current header. net/sched/act_mpls.c is the only other skb_mpls_pop() caller and gets the same fix; sch_frag.c saves and restores inner_protocol around fragmentation in the same way OVS does. Fixes: 48d2ab609b6b ("net: mpls: Fixups for GSO") Cc: stable@vger.kernel.org Signed-off-by: Fourie Zhang Acked-by: Jiri Benc Link: https://patch.msgid.link/20260902092719.2874481-1-fouriezhang@tencent.com Signed-off-by: Jakub Kicinski --- net/core/skbuff.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 966af3beed94..cc3b4b70288b 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -6690,6 +6690,13 @@ int skb_mpls_pop(struct sk_buff *skb, __be16 next_proto, int mac_len, } skb->protocol = next_proto; + /* The last label is gone, so the inner header recorded by + * skb_mpls_push() no longer describes this packet. Drop it, or a + * later push keeps the stale offset. + */ + if (!eth_p_mpls(next_proto)) + skb->inner_protocol = 0; + return 0; } EXPORT_SYMBOL_GPL(skb_mpls_pop); From 5d50e90add8b4a978395e893e81954d19d58a7c5 Mon Sep 17 00:00:00 2001 From: Jason Winter Date: Wed, 2 Sep 2026 10:40:41 +0200 Subject: [PATCH 0890/2366] net: usb: cx82310_eth: drop URB after 0xffff reboot sentinel to prevent partial_data heap overflow The 0xffff length sentinel detects a router reboot and schedules re-enabling of ethernet mode, but then falls through to the rest of the loop body. The next check is } else if (len > CX82310_MTU) { which is the else of the just-matched if -- it never fires for len == 0xffff. The MTU bound that normally caps the incomplete-packet save path is silently bypassed. With 0xffff > skb->len always true (rx_urb_size is 4096), the incomplete-packet branch saves dev->partial_len = skb->len bytes into dev->partial_data. partial_data is kmalloc(hard_mtu) = kmalloc(CX82310_MTU + 2) = 1516 bytes, but skb->len after the 2-byte header pull can be up to 4094. A device that sends a 4096-byte URB starting with [0xff 0xff] therefore copies 4094 device-provided bytes into a buffer allocated for 1516 bytes, exceeding its requested size by 2578 bytes. The next URB then reads dev->partial_len (4094) back from the same 1516-byte buffer and dev->partial_rem (65535 - 4094 = 61441) from the new URB's ~4KB skb, both well past their allocations, and delivers the spliced result as a 64KB "frame" to the network stack. Bail out of rx_fixup after scheduling the re-enable work; the remainder of a reboot-marker URB is not meaningful packet data. This restores the invariant that partial_len < CX82310_MTU + 2 on the save path, since every other route there has already passed the MTU check. Fixes: ca139d76b0d9 ("cx82310_eth: re-enable ethernet mode after router reboot") Signed-off-by: Jason Winter Link: https://patch.msgid.link/BESP194MB283265DDDC63B6B78D8D34FBB8B72@BESP194MB2832.EURP194.PROD.OUTLOOK.COM Signed-off-by: Jakub Kicinski --- drivers/net/usb/cx82310_eth.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/usb/cx82310_eth.c b/drivers/net/usb/cx82310_eth.c index 068acb052adb..5df657acf3d5 100644 --- a/drivers/net/usb/cx82310_eth.c +++ b/drivers/net/usb/cx82310_eth.c @@ -282,6 +282,7 @@ static int cx82310_rx_fixup(struct usbnet *dev, struct sk_buff *skb) if (len == 0xffff) { netdev_info(dev->net, "router was rebooted, re-enabling ethernet mode"); schedule_work(&priv->reenable_work); + return 0; } else if (len > CX82310_MTU) { netdev_err(dev->net, "RX packet too long: %d B\n", len); return 0; From 1668a31e3b1ad358d981ddb6dbd3db1fe0533621 Mon Sep 17 00:00:00 2001 From: Alexandra Winter Date: Wed, 2 Sep 2026 16:34:38 +0200 Subject: [PATCH 0891/2366] dibs: Unregister dibs_class after error In case dibs_loopback_init() fails, e.g. because of -ENOMEM, dibs_init() must unregister dibs_class. Otherwise dibs_class and /sys/class/dibs exist even though the functionality is not available. A retry to load the module fails with -EEXIST. Unregister dibs_class in the error path of dibs_init. Note that before commit ad3dfa80be76 ("dibs: change dibs_class to a const struct") class_destroy(dibs_class) is required instead of class_unregister(&dibs_class). Fixes: 804737349813 ("dibs: Create class dibs") Signed-off-by: Alexandra Winter Link: https://patch.msgid.link/20260902143438.426664-1-wintera@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/dibs/dibs_main.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/dibs/dibs_main.c b/drivers/dibs/dibs_main.c index 2b53a9d277dc..20c50997a7cf 100644 --- a/drivers/dibs/dibs_main.c +++ b/drivers/dibs/dibs_main.c @@ -251,13 +251,19 @@ static int __init dibs_init(void) rc = class_register(&dibs_class); if (rc) - return rc; + goto err; rc = dibs_loopback_init(); if (rc) - pr_err("%s fails with %d\n", __func__, rc); + goto err_unregister; return rc; + +err_unregister: + class_unregister(&dibs_class); +err: + pr_err("%s fails with %d\n", __func__, rc); + return rc; } static void __exit dibs_exit(void) From 907a56ab3eb8a58500a58daa76087f17bb2b6826 Mon Sep 17 00:00:00 2001 From: Alexandra Winter Date: Wed, 2 Sep 2026 16:37:33 +0200 Subject: [PATCH 0892/2366] s390/ism: folio_put() after error dmb->cpu_addr was allocated via folio_alloc(). Use folio_put() instead of kfree() in the error exit of ism_alloc_dmb() to avoid slab allocator corruption. While at it, reset dmb->cpu_addr after folio_put to avoid unintentional UAF by future callers. Fixes: 83781384a96b ("s390/ism: Properly fix receive message buffer allocation") Signed-off-by: Alexandra Winter Reviewed-by: Gerd Bayer Link: https://patch.msgid.link/20260902143733.433574-1-wintera@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/s390/net/ism_drv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/s390/net/ism_drv.c b/drivers/s390/net/ism_drv.c index 242da20f27e0..035b233abb4e 100644 --- a/drivers/s390/net/ism_drv.c +++ b/drivers/s390/net/ism_drv.c @@ -231,6 +231,7 @@ static void ism_free_dmb(struct ism_dev *ism, struct dibs_dmb *dmb) dma_unmap_page(&ism->pdev->dev, dmb->dma_addr, dmb->dmb_len, DMA_FROM_DEVICE); folio_put(virt_to_folio(dmb->cpu_addr)); + dmb->cpu_addr = NULL; } static int ism_alloc_dmb(struct ism_dev *ism, struct dibs_dmb *dmb) @@ -274,7 +275,8 @@ static int ism_alloc_dmb(struct ism_dev *ism, struct dibs_dmb *dmb) return 0; out_free: - kfree(dmb->cpu_addr); + folio_put(folio); + dmb->cpu_addr = NULL; out_bit: clear_bit(dmb->idx, ism->sba_bitmap); return rc; From 98fc57d167446b95b4e719815fe79edef93f8e7a Mon Sep 17 00:00:00 2001 From: Seungwon Bae Date: Thu, 3 Sep 2026 00:59:56 +0900 Subject: [PATCH 0893/2366] vxlan: reject dynamic fdb entries that reference a nexthop id The commit cited in the Fixes tag allowed VXLAN FDB entries to point to FDB nexthops so that overlay traffic could be load balanced across multiple VTEPs. Such entries can only be configured from user space, cannot be learned and cannot roam. They only make sense with a user space control plane such as E-VPN where data plane learning is disabled. Despite that, the VXLAN driver does not currently prevent such entries from being configured with the "dynamic" flag. The per-nexthop FDB list is only protected by the per-device hash lock, which is not sufficient when two VXLAN devices point to the same FDB nexthop and therefore share the list. Aging runs in softirq context without RTNL, so an entry deleted by one device can race with an addition or deletion from the other, leading to list corruption: list_del corruption. next->prev should be ffff8881069d9548, but was dead000000000122. (next=ffff8881069d9448) WARNING: CPU: 0 PID: 90 at lib/list_debug.c:65 __list_del_entry_valid_or_report+0x1aa/0x210 ... vxlan_fdb_destroy+0x5b8/0xad0 vxlan_cleanup+0x328/0x450 call_timer_fn+0x2a/0x1c0 run_timer_softirq+0x18c/0x210 BUG: KASAN: slab-use-after-free in vxlan_fdb_destroy Fix this by rejecting the bogus configuration of dynamic FDB entries that point to FDB nexthops, both when created and when an existing entry is updated. As such, the per-nexthop FDB list is only ever mutated under the RTNL lock. Add test cases to make sure that this does not regress in the future. Fixes: 1274e1cc4226 ("vxlan: ecmp support for mac fdb entries") Suggested-by: Ido Schimmel Signed-off-by: Seungwon Bae Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260902155956.296699-1-qotmddnjs@ajou.ac.kr Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 11 ++++++++ tools/testing/selftests/net/fib_nexthops.sh | 28 +++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 459f19f7071e..be95af64a1f5 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -996,6 +996,12 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, return -EOPNOTSUPP; } + if (rcu_access_pointer(f->nh) && + !(state & (NUD_PERMANENT | NUD_NOARP))) { + NL_SET_ERR_MSG(extack, "Cannot make a nexthop fdb dynamic"); + return -EOPNOTSUPP; + } + /* Do not allow an externally learned entry to take over an entry added * by the user. */ @@ -1257,6 +1263,11 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], if (err) return err; + if (nhid && !(ndm->ndm_state & (NUD_PERMANENT | NUD_NOARP))) { + NL_SET_ERR_MSG(extack, "A nexthop fdb cannot be dynamic"); + return -EINVAL; + } + if (vxlan->default_dst.remote_ip.sa.sa_family != ip.sa.sa_family) return -EAFNOSUPPORT; diff --git a/tools/testing/selftests/net/fib_nexthops.sh b/tools/testing/selftests/net/fib_nexthops.sh index 3d347126730a..431d7bed7622 100755 --- a/tools/testing/selftests/net/fib_nexthops.sh +++ b/tools/testing/selftests/net/fib_nexthops.sh @@ -533,6 +533,20 @@ ipv6_fdb_grp_fcnal() run_cmd "$BRIDGE fdb add 02:02:00:00:00:14 dev vx10 nhid 61 self" log_test $? 255 "Fdb mac add with nexthop" + # fdb entries with a nexthop group cannot be aged out + run_cmd "$BRIDGE fdb add 02:02:00:00:00:15 dev vx10 nhid 102 self static" + log_test $? 0 "Fdb mac add with nexthop group and static state" + + run_cmd "$BRIDGE fdb add 02:02:00:00:00:16 dev vx10 nhid 102 self dynamic" + log_test $? 255 "Fdb mac add with nexthop group and dynamic state" + + run_cmd "$BRIDGE fdb add 02:02:00:00:00:17 dev vx10 nhid 102 self" + run_cmd "$BRIDGE fdb replace 02:02:00:00:00:17 dev vx10 dst 2001:db8:91::11 self dynamic" + log_test $? 255 "Fdb mac replace with nexthop group and dynamic state" + + run_cmd "$BRIDGE fdb append 02:02:00:00:00:17 dev vx10 dst 2001:db8:91::11 self dynamic" + log_test $? 255 "Fdb mac append with nexthop group and dynamic state" + run_cmd "$IP -6 ro add 2001:db8:101::1/128 nhid 66" log_test $? 2 "Route add with fdb nexthop" @@ -669,6 +683,20 @@ ipv4_fdb_grp_fcnal() run_cmd "$BRIDGE fdb add 02:02:00:00:00:14 dev vx10 nhid 12 self" log_test $? 255 "Fdb mac add with nexthop" + # fdb entries with a nexthop group cannot be aged out + run_cmd "$BRIDGE fdb add 02:02:00:00:00:15 dev vx10 nhid 102 self static" + log_test $? 0 "Fdb mac add with nexthop group and static state" + + run_cmd "$BRIDGE fdb add 02:02:00:00:00:16 dev vx10 nhid 102 self dynamic" + log_test $? 255 "Fdb mac add with nexthop group and dynamic state" + + run_cmd "$BRIDGE fdb add 02:02:00:00:00:17 dev vx10 nhid 102 self" + run_cmd "$BRIDGE fdb replace 02:02:00:00:00:17 dev vx10 dst 10.0.0.3 self dynamic" + log_test $? 255 "Fdb mac replace with nexthop group and dynamic state" + + run_cmd "$BRIDGE fdb append 02:02:00:00:00:17 dev vx10 dst 10.0.0.3 self dynamic" + log_test $? 255 "Fdb mac append with nexthop group and dynamic state" + run_cmd "$IP ro add 172.16.0.0/22 nhid 16" log_test $? 2 "Route add with fdb nexthop" From 66ab4c59b74db7ab53a1c9083feaaede393a96a0 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 2 Sep 2026 17:29:08 -0400 Subject: [PATCH 0894/2366] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations Several subsystems allocate ring buffers sized by dev->tx_queue_len with no upper bound. An unprivileged user (via unshare -Urn) can set a huge tx_queue_len and exhaust global memory with ring allocations: - pfifo_fast: pfifo_fast_init() and pfifo_fast_change_tx_queue_len() allocate 3 skb_array rings of tx_queue_len entries each. - tun: tun_queue_resize() and the queue-attach path resize ptr_rings to tx_queue_len on the NETDEV_CHANGE_TX_QUEUE_LEN notifier. - tap (macvtap/ipvtap): tap_queue_resize() and tap_init() resize/init ptr_rings to tx_queue_len on the same notifier. netif_change_tx_queue_len() is the single entry point for IFLA_TXQLEN, sysfs, and the SIOCSIFTXQLEN ioctl. Cap new_len at S16_MAX (32767) there so the oversized value is rejected at set time. This takes effect whether the device is up or down, before dev->tx_queue_len is written, before any notifier fires, and before any ring is allocated. The "> S16_MAX" check also subsumes the previous unsigned-long truncation test, and a negative ifr_qlen from the ioctl lands far above the cap after conversion, so both old failure modes are covered by the one comparison. tx_queue_len is ambigious: both a per-ring sizing multiplier and a default queue-length/limit knob for consumers that allocate nothing at set time (pfifo/bfifo/gred/plug/sfb limits, htb direct_qlen, qfq max_classes, teql). 32767 is chosen as the largest value NLA_POLICY_FULL_RANGE can express for the u32 IFLA_TXQLEN policy in patch 2/3 while staying a legitimate queue length on high-BDP paths; the ring-memory trade-off of a shared knob is disclosed below. Conditions to recreate the bug: - CONFIG_NET_SCHED=y, CONFIG_VETH=y, CONFIG_USER_NS=y, CONFIG_NET_NS=y. - Unprivileged user in a fresh user+net namespace (unshare -Urn). - pfifo_fast: create veth pairs, set tx_queue_len to 500000, attach mq+pfifo_fast. ~28 iterations OOMs a 2GB guest. - tun: create 50 tun devices with IFF_MULTI_QUEUE, set tx_queue_len to 500000, open 8 queues each. ~1.6GB of ptr_ring allocations OOMs a 512MB guest. - tap: same as tun with IFF_TAP. ~960MB OOMs a 512MB guest. - On the fixed kernel the oversized tx_queue_len is rejected with -ERANGE at set time (all four paths: RTM_SETLINK, RTM_NEWLINK create, sysfs, ioctl - the latter two via this check, the former two via this check and the 2/3 parse policy respectively). Fixes: 6a643ddb5624 ("net: introduce helper dev_change_tx_queue_len()") Reported-by: Vega Closes: https://lore.kernel.org/netdev/20260828121902.66837-1-jhs@mojatatu.com/ Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-2899.v2.20260901233641@mojatatu.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index 290e0f099e6b..ecfbd72d5d1a 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -9982,7 +9982,7 @@ int netif_change_tx_queue_len(struct net_device *dev, unsigned long new_len) unsigned int orig_len = dev->tx_queue_len; int res; - if (new_len != (unsigned int)new_len) + if (new_len > S16_MAX) return -ERANGE; if (new_len != orig_len) { From 1aa9e143bf51405665a793d4cc925e1c4f0c5922 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 2 Sep 2026 17:29:09 -0400 Subject: [PATCH 0895/2366] net: reject oversized tx_queue_len at netlink parse time rtnl_create_link() assigns IFLA_TXQLEN directly to dev->tx_queue_len without going through netif_change_tx_queue_len(), so a device created with "ip link add ... txqueuelen 500000" bypasses the S16_MAX cap and still triggers the oversized ring allocations in pfifo_fast, tun and tap. The veth peer nest (rtnl_nla_parse_ifinfomsg()) and the RTM_NEWLINK-on-existing-device path reach the same sinks. Enforce the cap in ifla_policy instead: IFLA_TXQLEN becomes NLA_POLICY_FULL_RANGE(NLA_U32, &txqlen_range) with txqlen_range = { .min = 0, .max = S16_MAX }. All netlink consumers parse against this policy - rtnl_setlink(), rtnl_newlink() (create and change), and the veth peer nest - so every netlink path is capped at parse time and rejects the attribute with -ERANGE plus a proper "integer out of range" extack message before any device state is modified (the RTM_SETLINK half-application wart is gone with it). Document the bound in the rt-link.yaml netlink spec. Conditions to recreate the bug: - CONFIG_NET_SCHED=y, CONFIG_VETH=y, CONFIG_USER_NS=y, CONFIG_NET_NS=y. - Unprivileged user in a fresh user+net namespace (unshare -Urn): ip link add v0 txqueuelen 500000 type veth peer name v1 -> on the fixed kernel this is rejected with -ERANGE ("integer out of range" extack) instead of installing an oversized tx_queue_len that later inflates pfifo_fast/tun/tap ring allocations. - ip link set v0 txqueuelen 500000 is likewise rejected at parse time. Fixes: 38f7b870d4a6 ("[RTNETLINK]: Link creation API") Reported-by: Vega Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-2899.v2.20260901233641@mojatatu.com.2 Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/rt-link.yaml | 2 ++ net/core/rtnetlink.c | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml index b80c2ac3ac31..99f6fba456cc 100644 --- a/Documentation/netlink/specs/rt-link.yaml +++ b/Documentation/netlink/specs/rt-link.yaml @@ -898,6 +898,8 @@ attribute-sets: - name: txqlen type: u32 + checks: + max: 32767 - name: map type: binary diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 81c5a6104dea..be9d1625bac3 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -2287,6 +2287,11 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, return -EMSGSIZE; } +static const struct netlink_range_validation txqlen_range = { + .min = 0, + .max = S16_MAX, +}; + static const struct nla_policy ifla_policy[IFLA_MAX+1] = { [IFLA_UNSPEC] = { .strict_start_type = IFLA_DPLL_PIN }, [IFLA_IFNAME] = { .type = NLA_STRING, .len = IFNAMSIZ-1 }, @@ -2297,7 +2302,7 @@ static const struct nla_policy ifla_policy[IFLA_MAX+1] = { [IFLA_LINK] = { .type = NLA_U32 }, [IFLA_MASTER] = { .type = NLA_U32 }, [IFLA_CARRIER] = { .type = NLA_U8 }, - [IFLA_TXQLEN] = { .type = NLA_U32 }, + [IFLA_TXQLEN] = NLA_POLICY_FULL_RANGE(NLA_U32, &txqlen_range), [IFLA_WEIGHT] = { .type = NLA_U32 }, [IFLA_OPERSTATE] = { .type = NLA_U8 }, [IFLA_LINKMODE] = { .type = NLA_U8 }, From 0a7252d7f85478080385de4c1072085e30849fe3 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 2 Sep 2026 17:29:10 -0400 Subject: [PATCH 0896/2366] selftests: tc-testing: add tx_queue_len cap regression tests Add nine test cases for the S16_MAX tx_queue_len cap to the pfifo_fast suite. Netlink cases exercise the ifla_policy bound (2/3); the two new sysfs cases exercise the netif_change_tx_queue_len() choke point that 1/3 owns (SIOCSIFTXQLEN shares it; the ioctl is not portably reachable from tdc): - dbe3: set txqueuelen 32767 (S16_MAX) - accepted, pins the exact boundary value. - b50e: set txqueuelen 32768 - rejected with -ERANGE. - 40f8: write 32768 to /sys/class/net/*/tx_queue_len - rejected (covers patch 1/3 directly; netlink cannot reach this path). - 4b6e: write 32767 via sysfs - accepted, boundary positive control for the patch-1 path. - b90d: create a dummy with txqueuelen 32767 - accepted. - 57ab: create a dummy with txqueuelen 32768 - rejected at netlink parse time. - e777: create a dummy with txqueuelen 500000 - rejected (the v1 bypass path flagged by review). - 31ac: create a veth with an oversized txqueuelen on the peer nest - rejected (the peer nest is parsed against ifla_policy too). - b567: create a veth with txqueuelen on both ends within the cap - accepted (positive control for the peer nest). The three negative-creation verifies assert device absence ("ip -o link show" must not contain the device), not merely absence of a qlen pattern - the device does not exist when creation fails, so the exit code carries the signal and the verify adds content. The v1 04b5 "resize rollback" case is dropped: with the cap checked first, netif_change_tx_queue_len() returns -ERANGE before the write, the notifier or any qdisc resize, so the case exercised no resize and no rollback. It was also nondeterministic: pre-patch, the resize issues three ~11 MB kvmallocs for qlen 500000 which normally succeed, so the case passed on an unfixed kernel only under memory pressure - its outcome depended on the test host's free memory. Test commands run inside the netns, but nsPlugin creates the veth peer in the root namespace, so the teardown deletes the in-ns end only; deleting the peer via the pair is implicit. Note: iproute2 treats "txqueuelen" appearing after "type X" as a link-type attribute and silently drops it, so the creation cases place it before "type" to actually reach the kernel. Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-2899.v2.20260901233641@mojatatu.com.3 Signed-off-by: Jakub Kicinski --- .../tc-tests/qdiscs/pfifo_fast.json | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json index 30da27fe8806..a6e25e76ecb1 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json @@ -105,5 +105,209 @@ "teardown": [ "$TC qdisc del dev $DUMMY handle 1: root" ] + }, + { + "id": "dbe3", + "name": "Set tx_queue_len to S16_MAX boundary (32767 accepted)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$IP link set dev $DUMMY txqueuelen 32767", + "expExitCode": "0", + "verifyCmd": "$IP link show dev $DUMMY", + "matchPattern": "qlen 32767$", + "matchCount": "1", + "teardown": [] + }, + { + "id": "b50e", + "name": "Reject tx_queue_len above S16_MAX at set time (32768)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$IP link set dev $DUMMY txqueuelen 32768", + "expExitCode": "2", + "verifyCmd": "$IP link show dev $DUMMY", + "matchPattern": "qlen 1000$", + "matchCount": "1", + "teardown": [] + }, + { + "id": "40f8", + "name": "Reject tx_queue_len above S16_MAX via sysfs (32768)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "sh -c 'echo 32768 > /sys/class/net/$DUMMY/tx_queue_len'", + "expExitCode": "1", + "verifyCmd": "$IP link show dev $DUMMY", + "matchPattern": "qlen 1000$", + "matchCount": "1", + "teardown": [] + }, + { + "id": "4b6e", + "name": "Set tx_queue_len to S16_MAX via sysfs (32767 accepted)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "sh -c 'echo 32767 > /sys/class/net/$DUMMY/tx_queue_len'", + "expExitCode": "0", + "verifyCmd": "$IP link show dev $DUMMY", + "matchPattern": "qlen 32767$", + "matchCount": "1", + "teardown": [] + }, + { + "id": "b90d", + "name": "Create device with tx_queue_len at S16_MAX boundary (32767 accepted)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$IP link del dev $DUMMY", + 0, + 1 + ] + ], + "cmdUnderTest": "$IP link add dev $DUMMY txqueuelen 32767 type dummy", + "expExitCode": "0", + "verifyCmd": "$IP link show dev $DUMMY", + "matchPattern": "qlen 32767$", + "matchCount": "1", + "teardown": [ + [ + "$IP link del dev $DUMMY", + 0, + 1 + ] + ] + }, + { + "id": "57ab", + "name": "Reject creating device with tx_queue_len above S16_MAX (32768)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$IP link del dev $DUMMY", + 0, + 1 + ] + ], + "cmdUnderTest": "$IP link add dev $DUMMY txqueuelen 32768 type dummy", + "expExitCode": "2", + "verifyCmd": "$IP -o link show", + "matchPattern": "^[0-9]+: $DUMMY", + "matchCount": "0", + "teardown": [] + }, + { + "id": "e777", + "name": "Reject creating device with oversized tx_queue_len (500000)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$IP link del dev $DUMMY", + 0, + 1 + ] + ], + "cmdUnderTest": "$IP link add dev $DUMMY txqueuelen 500000 type dummy", + "expExitCode": "2", + "verifyCmd": "$IP -o link show", + "matchPattern": "^[0-9]+: $DUMMY", + "matchCount": "0", + "teardown": [] + }, + { + "id": "31ac", + "name": "Reject veth peer nest tx_queue_len above S16_MAX at create", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$IP link del dev $DEV1", + 0, + 1 + ] + ], + "cmdUnderTest": "$IP link add dev $DEV1 type veth peer name $DEV0 txqueuelen 500000", + "expExitCode": "2", + "verifyCmd": "$IP -o link show", + "matchPattern": "^[0-9]+: $DEV1", + "matchCount": "0", + "teardown": [] + }, + { + "id": "b567", + "name": "Accept veth peer nest tx_queue_len within S16_MAX", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$IP link del dev $DEV1", + 0, + 1 + ] + ], + "cmdUnderTest": "$IP link add dev $DEV1 txqueuelen 100 type veth peer name $DEV0 txqueuelen 200", + "expExitCode": "0", + "verifyCmd": "$IP link show", + "matchPattern": "qlen (100|200)$", + "matchCount": "2", + "teardown": [ + [ + "$IP link del dev $DEV0", + 0, + 1 + ] + ] } ] From 7980325b2f71e3f65c1323c39792e2455da6fab6 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Tue, 1 Sep 2026 04:42:17 +0000 Subject: [PATCH 0897/2366] pds_core: fix cmd_regs access racing BAR unmap on reset pdsc_reset_prepare() and pdsc_reset_done()'s pdsc_map_bars() error path clear/iounmap cmd_regs without devcmd_lock, and pdsc_legacy_firmware_update()'s download loop derefs cmd_regs after dropping and retaking the lock without re-checking. An FLR concurrent with a devlink flash can unmap cmd_regs under an in-flight devcmd, causing a NULL deref or a write to unmapped MMIO. Take devcmd_lock across the BAR unmap/remap, and re-check cmd_regs in the download loop. Only the PF maps cmd_regs and runs devcmd, so skip the unmap on a VF, as pdsc_remove() and pdsc_reset_done() already do. A reset that completes entirely within the unlocked window is not a correctness problem for the image: the device clears its update session, so a resumed download is rejected, and it verifies the staged image before writing a flash slot, reporting PDS_RC_BAD_FW rather than activating it. pdsc_unmap_bars() also clears info_regs, intr_status and intr_ctrl. The interrupt and start/stop readers of those are quiesced before the unmap by pdsc_fw_down(), which frees the interrupts and tears down the queues. The debugfs readers are not, since those files outlive a reset; that is pre-existing and out of scope here. Fixes: e96094c1d11c ("pds_core: Clear BARs on reset") Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260708212222.296202-1-nikhil.rao%40amd.com?part=3 Signed-off-by: Nikhil P. Rao Link: https://patch.msgid.link/20260901044219.1361466-2-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/fw.c | 10 +++++++++- drivers/net/ethernet/amd/pds_core/main.c | 8 +++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/amd/pds_core/fw.c b/drivers/net/ethernet/amd/pds_core/fw.c index 5ccf017f6af4..19899bf38d40 100644 --- a/drivers/net/ethernet/amd/pds_core/fw.c +++ b/drivers/net/ethernet/amd/pds_core/fw.c @@ -171,8 +171,10 @@ pdsc_legacy_firmware_update(struct pdsc *pdsc, dev_info(pdsc->dev, "Installing firmware\n"); - if (!pdsc->cmd_regs) + if (!pdsc->cmd_regs) { + NL_SET_ERR_MSG_MOD(extack, "BARs not mapped"); return -ENXIO; + } dl = priv_to_devlink(pdsc); devlink_flash_update_status_notify(dl, "Preparing to flash", @@ -198,6 +200,12 @@ pdsc_legacy_firmware_update(struct pdsc *pdsc, copy_sz = min_t(unsigned int, buf_sz, fw->size - offset); mutex_lock(&pdsc->devcmd_lock); + if (!pdsc->cmd_regs) { + mutex_unlock(&pdsc->devcmd_lock); + err = -ENXIO; + NL_SET_ERR_MSG_MOD(extack, "Device reset during flash"); + goto err_out; + } memcpy_toio(&pdsc->cmd_regs->data, fw->data + offset, copy_sz); err = pdsc_devcmd_fw_download_locked(pdsc, data_addr, offset, copy_sz); diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c index bb79e7476370..6e1079f9ba0f 100644 --- a/drivers/net/ethernet/amd/pds_core/main.c +++ b/drivers/net/ethernet/amd/pds_core/main.c @@ -513,7 +513,11 @@ static void pdsc_reset_prepare(struct pci_dev *pdev) pdsc_auxbus_dev_del(pdsc, pdsc, &pdsc->padev); } - pdsc_unmap_bars(pdsc); + if (!pdev->is_virtfn) { + mutex_lock(&pdsc->devcmd_lock); + pdsc_unmap_bars(pdsc); + mutex_unlock(&pdsc->devcmd_lock); + } pci_release_regions(pdev); if (pci_is_enabled(pdev)) pci_disable_device(pdev); @@ -543,7 +547,9 @@ static void pdsc_reset_done(struct pci_dev *pdev) return; } + mutex_lock(&pdsc->devcmd_lock); err = pdsc_map_bars(pdsc); + mutex_unlock(&pdsc->devcmd_lock); if (err) return; } From 73608de7e59246b4b533c1ffaee158a7048e186e Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Tue, 1 Sep 2026 04:42:18 +0000 Subject: [PATCH 0898/2366] pds_core: don't release PCI regions for VFs on reset pdsc_reset_prepare() called pci_release_regions() unconditionally, but only PFs call pci_request_regions() (pdsc_init_pf). On a VF FLR this makes the kernel warn "Trying to free nonexistent resource". Fixes: ffa55858330f ("pds_core: implement pci reset handlers") Reported-by: sashiko-bot Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260804235946.177762-1-nikhil.rao%40amd.com Signed-off-by: Nikhil P. Rao Link: https://patch.msgid.link/20260901044219.1361466-3-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c index 6e1079f9ba0f..a971c66d36f9 100644 --- a/drivers/net/ethernet/amd/pds_core/main.c +++ b/drivers/net/ethernet/amd/pds_core/main.c @@ -517,8 +517,8 @@ static void pdsc_reset_prepare(struct pci_dev *pdev) mutex_lock(&pdsc->devcmd_lock); pdsc_unmap_bars(pdsc); mutex_unlock(&pdsc->devcmd_lock); + pci_release_regions(pdev); } - pci_release_regions(pdev); if (pci_is_enabled(pdev)) pci_disable_device(pdev); pdsc_deferred_dma_free(pdsc); From 1a3a10b030c96ea88868ccc060a16827c01eaa5a Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 17:05:52 -0700 Subject: [PATCH 0899/2366] bpf: mark a NULL call argument precise check_func_arg() allows bpf_register_is_null() for nullable arguments w/o marking the underlying scalar register precise. Hence a checkpoint created on such a path would prune against arbitrary scalar value. check_helper_call() enforces second parameter of the bpf_get_local_storage() to be zero, w/o marking the underlying scalar register precise. Hence a checkpoint created on such a path would prune against arbitrary scalar value. Grouping these two into one patch, as they share the same fixes tag. Fixes: b5dc0163d8fd ("bpf: precise scalar_value tracking") Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260904-register-is-null-precise-fixes-v1-1-0f5a360ff15d@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 9 ++++++++- .../selftests/bpf/progs/verifier_subprog_precision.c | 12 ++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index c8699a8831df..b107f551a62d 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -8759,11 +8759,15 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, return err; } - if (bpf_register_is_null(reg) && type_may_be_null(arg_type)) + if (bpf_register_is_null(reg) && type_may_be_null(arg_type)) { /* A NULL register has a SCALAR_VALUE type, so skip * type checking. */ + err = mark_chain_precision(env, regno); + if (err) + return err; goto skip_type_check; + } /* arg_btf_id and arg_size are in a union. */ if (base_type(arg_type) == ARG_PTR_TO_BTF_ID || @@ -10923,6 +10927,9 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn verbose(env, "get_local_storage() doesn't support non-zero flags\n"); return -EINVAL; } + err = mark_chain_precision(env, BPF_REG_2); + if (err) + return err; break; case BPF_FUNC_for_each_map_elem: err = push_callback_call(env, insn, insn_idx, meta.subprogno, diff --git a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c index e174a905c562..dc0c7034c04f 100644 --- a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c +++ b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c @@ -287,9 +287,9 @@ __msg("17: (b7) r0 = 0") __msg("18: (95) exit") __msg("returning from callee:") __msg("to caller at 9:") -__msg("frame 0: propagating r1,r4") +__msg("frame 0: propagating r1,r3,r4") __msg("mark_precise: frame0: last_idx 9 first_idx 9 subseq_idx -1") -__msg("mark_precise: frame0: regs=r1,r4 stack= before 18: (95) exit") +__msg("mark_precise: frame0: regs=r1,r3,r4 stack= before 18: (95) exit") __msg("from 18 to 9: safe") __naked int callback_result_precise(void) { @@ -419,9 +419,9 @@ __msg("to caller at 9:") /* r1, r4 are always precise for bpf_loop(), * r6 was marked before backtracking to callback body. */ -__msg("frame 0: propagating r1,r4,r6") +__msg("frame 0: propagating r1,r3,r4,r6") __msg("mark_precise: frame0: last_idx 9 first_idx 9 subseq_idx -1") -__msg("mark_precise: frame0: regs=r1,r4,r6 stack= before 16: (95) exit") +__msg("mark_precise: frame0: regs=r1,r3,r4,r6 stack= before 16: (95) exit") __msg("mark_precise: frame1: regs= stack= before 15: (b7) r0 = 0") __msg("mark_precise: frame1: regs= stack= before 9: (85) call bpf_loop") __msg("mark_precise: frame0: parent state regs= stack=:") @@ -575,9 +575,9 @@ __msg("to caller at 10:") /* r1, r4 are always precise for bpf_loop(), * fp-8 was marked before backtracking to callback body. */ -__msg("frame 0: propagating r1,r4,fp-8") +__msg("frame 0: propagating r1,r3,r4,fp-8") __msg("mark_precise: frame0: last_idx 10 first_idx 10 subseq_idx -1") -__msg("mark_precise: frame0: regs=r1,r4 stack=-8 before 18: (95) exit") +__msg("mark_precise: frame0: regs=r1,r3,r4 stack=-8 before 18: (95) exit") __msg("mark_precise: frame1: regs= stack= before 17: (b7) r0 = 0") __msg("mark_precise: frame1: regs= stack= before 10: (85) call bpf_loop#181") __msg("mark_precise: frame0: parent state regs= stack=:") From 593c8eb0fb91a24c39244a7f9e7d04412d750544 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 17:05:53 -0700 Subject: [PATCH 0900/2366] selftests/bpf: precision of a NULL helper argument Check that mark_chain_precision() is called for a NULL nullable memory argument and for the zero flags argument of bpf_get_local_storage(). Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260904-register-is-null-precise-fixes-v1-2-0f5a360ff15d@gmail.com Signed-off-by: Alexei Starovoitov --- .../bpf/progs/verifier_cgroup_storage.c | 29 ++++++++++++++++ .../selftests/bpf/progs/verifier_precision.c | 34 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_cgroup_storage.c b/tools/testing/selftests/bpf/progs/verifier_cgroup_storage.c index 9a13f5c11ac7..884080a5bffc 100644 --- a/tools/testing/selftests/bpf/progs/verifier_cgroup_storage.c +++ b/tools/testing/selftests/bpf/progs/verifier_cgroup_storage.c @@ -305,4 +305,33 @@ __naked void cpu_cgroup_storage_access_6(void) : __clobber_all); } +/* + * Verification takes two paths: with r2 being scalar zero on path (1) + * and with r2 being some other scalar on path (2). + * Check that the verifier does not use checkpoints created + * on path (1) to prune path (2). + */ +SEC("cgroup/skb") +__failure +__flag(BPF_F_TEST_STATE_FREQ) +__msg("get_local_storage() doesn't support non-zero flags") +__naked void non_zero_flags_on_a_pruned_path(void) +{ + asm volatile (" \ + call %[bpf_get_prandom_u32]; \ + /* r2 is 0 on the path explored first, 1 on the other */\ + r2 = 1; \ + if r0 == 0 goto 1f; \ + r2 = 0; \ +1: r1 = %[cgroup_storage] ll; \ + call %[bpf_get_local_storage]; \ + r0 = 0; \ + exit; \ +" : + : __imm(bpf_get_prandom_u32), + __imm(bpf_get_local_storage), + __imm_addr(cgroup_storage) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_precision.c b/tools/testing/selftests/bpf/progs/verifier_precision.c index 6f325876efdd..3e290b07f672 100644 --- a/tools/testing/selftests/bpf/progs/verifier_precision.c +++ b/tools/testing/selftests/bpf/progs/verifier_precision.c @@ -642,4 +642,38 @@ __naked int bpf_atomic_cmpxchg_32bit_precision(void) : __clobber_all); } +/* + * Verification takes two paths: with r1 being scalar zero on path (1) + * and with r1 being some other scalar on path (2). + * Check that the verifier does not use checkpoints created + * on path (1) to prune path (2). + */ +SEC("?tc") +__flag(BPF_F_TEST_STATE_FREQ) +__failure __msg("R1 type=scalar expected=fp") +__naked int null_mem_arg_zero_size(void) +{ + asm volatile ( + "call %[bpf_get_prandom_u32];" + "r1 = 42;" + "if r0 > 42 goto 1f;" + "r1 = 0;" + "1:" + "r2 = 0;" + "r3 = 0;" + "r4 = 0;" + "r5 = 0;" + /* + * ARG_PTR_TO_MEM | PTR_MAYBE_NULL parameter can be NULL, + * but can't be some other scalar value. + */ + "call %[bpf_csum_diff];" + "r0 = 0;" + "exit;" + : + : __imm(bpf_get_prandom_u32), + __imm(bpf_csum_diff) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; From f1e418129f2ebb5376df2f1cd19720fa80f8adb4 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 17:05:54 -0700 Subject: [PATCH 0901/2366] bpf: mark a NULL memory argument of a call precise check_mem_reg() allows bpf_register_is_null() for nullable arguments w/o marking the underlying scalar register precise. Hence a checkpoint created on such a path would prune against arbitrary scalar value. The argument may live on the stack rather than in a register when a call has more than MAX_BPF_FUNC_REG_ARGS arguments, hence the new mark_arg_precision() helper. Fixes: e5069b9c23b3 ("bpf: Support pointers in global func args") Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260904-register-is-null-precise-fixes-v1-3-0f5a360ff15d@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index b107f551a62d..7926e131b1cb 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -4246,6 +4246,15 @@ static int mark_stack_arg_precision(struct bpf_verifier_env *env, int arg_idx) return mark_chain_precision_batch(env, env->cur_state); } +static int mark_arg_precision(struct bpf_verifier_env *env, argno_t argno) +{ + int regno = reg_from_argno(argno); + + if (regno >= 0) + return mark_chain_precision(env, regno); + return mark_stack_arg_precision(env, arg_idx_from_argno(argno)); +} + static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_func_state *caller, int nargs, const char *callee_name, const struct btf *btf, const struct btf_param *args) @@ -7175,7 +7184,7 @@ static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg int size, err = 0; if (bpf_register_is_null(reg)) - return 0; + return mark_arg_precision(env, argno); if (known_memory) *known_memory = true; From 100f4cc0d59be88d2b4d6eb42e51910ea2548d04 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 17:05:55 -0700 Subject: [PATCH 0902/2366] selftests/bpf: precision of a NULL global subprogram memory argument Check that mark_chain_precision() is called for a NULL pointer passed as a nullable pointer argument of a global subprogram. (Pointer arguments of the global subprograms are nullable by default). Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260904-register-is-null-precise-fixes-v1-4-0f5a360ff15d@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/verifier_precision.c | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_precision.c b/tools/testing/selftests/bpf/progs/verifier_precision.c index 3e290b07f672..fb7dfa1246ef 100644 --- a/tools/testing/selftests/bpf/progs/verifier_precision.c +++ b/tools/testing/selftests/bpf/progs/verifier_precision.c @@ -676,4 +676,36 @@ __naked int null_mem_arg_zero_size(void) : __clobber_all); } +__weak int subprog_mem_arg(int *p) +{ + if (p) + return *p; + return 0; +} + +/* + * Verification takes two paths: with r1 being scalar zero on path (1) + * and with r1 being some other scalar on path (2). + * Check that the verifier does not use checkpoints created + * on path (1) to prune path (2). + */ +SEC("?raw_tp") +__flag(BPF_F_TEST_STATE_FREQ) +__failure __msg("R1 type=scalar expected=fp") +__naked int null_mem_arg_global_subprog(void) +{ + asm volatile ( + "call %[bpf_get_prandom_u32];" + "r1 = 42;" + "if r0 > 42 goto 1f;" + "r1 = 0;" + "1:" + "call subprog_mem_arg;" + "r0 = 0;" + "exit;" + : + : __imm(bpf_get_prandom_u32) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; From 506ada89629ec7059b96ecfb0dc7d33ece0103ca Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 17:05:56 -0700 Subject: [PATCH 0903/2366] bpf: mark a NULL kfunc argument precise check_kfunc_arg() allows bpf_register_is_null() for nullable arguments w/o marking the underlying scalar register precise. Hence a checkpoint created on such a path would prune against arbitrary scalar value. Fixes: 3bda08b63670 ("bpf: Allow NULL buffers in bpf_dynptr_slice(_rw)") Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260904-register-is-null-precise-fixes-v1-5-0f5a360ff15d@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 7926e131b1cb..2117c39ac332 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -12733,8 +12733,12 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me if (reg_is_referenced(env, reg)) update_ref_obj(&meta->ref_obj, reg); - if (bpf_register_is_null(reg) && type_may_be_null(kf_arg_type)) + if (bpf_register_is_null(reg) && type_may_be_null(kf_arg_type)) { + ret = mark_arg_precision(env, argno); + if (ret) + return ret; continue; + } if (is_kfunc_arg_map(btf, &args[i])) { ref_id = *reg2btf_ids[CONST_PTR_TO_MAP]; From 562d266d3fae571617e72417753df648db261c56 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 17:05:57 -0700 Subject: [PATCH 0904/2366] selftests/bpf: precision of a NULL kfunc argument Check that mark_chain_precision() is called for a NULL pointer passed as a __nullable kfunc memory argument. Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260904-register-is-null-precise-fixes-v1-6-0f5a360ff15d@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/verifier_precision.c | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_precision.c b/tools/testing/selftests/bpf/progs/verifier_precision.c index fb7dfa1246ef..f4459561bf39 100644 --- a/tools/testing/selftests/bpf/progs/verifier_precision.c +++ b/tools/testing/selftests/bpf/progs/verifier_precision.c @@ -2,8 +2,10 @@ /* Copyright (C) 2023 SUSE LLC */ #include #include +#include #include "../../../include/linux/filter.h" #include "bpf_misc.h" +#include "bpf_kfuncs.h" struct { __uint(type, BPF_MAP_TYPE_ARRAY); @@ -708,4 +710,36 @@ __naked int null_mem_arg_global_subprog(void) : __clobber_all); } +/* Same as above, check that path with r3 == 0 does not prune the path with r3 != 0 */ +SEC("?tc") +__flag(BPF_F_TEST_STATE_FREQ) +__failure __msg("R3 type=scalar expected=fp") +int null_kfunc_arg_dynptr_slice(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr; + + bpf_dynptr_from_skb(skb, 0, &ptr); + asm volatile ( + "call %[bpf_get_prandom_u32];" + "r3 = 42;" + "if r0 > 42 goto 1f;" + "r3 = 0;" + "1:" + "r1 = %[ptr];" + "r2 = 0;" + "r4 = 8;" + "call %[bpf_dynptr_slice];" + : + : __imm_ptr(ptr), + __imm(bpf_get_prandom_u32), + __imm(bpf_dynptr_slice) + : __clobber_common); + return 0; +} + +void __kfunc_btf_root(void) +{ + bpf_dynptr_slice(0, 0, 0, 0); +} + char _license[] SEC("license") = "GPL"; From e726fc6b9afe6b3a446a31d0f0767b7b9cb5085e Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 17:05:58 -0700 Subject: [PATCH 0905/2366] bpf: mark a NULL BTF_ID argument of a global subprogram precise btf_check_func_arg_match() accepts a NULL register for an ARG_PTR_TO_BTF_ID argument tagged __arg_nullable and skips check_reg_type() and check_func_arg_reg_off() without marking the register precise. Hence a checkpoint created on such a path would prune against arbitrary scalar value. Fixes: e2b3c4ff5d18 ("bpf: add __arg_trusted global func arg tag") Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260904-register-is-null-precise-fixes-v1-7-0f5a360ff15d@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 2117c39ac332..1b9fcbe4621a 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9774,8 +9774,12 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog, struct bpf_call_arg_meta meta; int err; - if (bpf_register_is_null(reg) && type_may_be_null(arg->arg_type)) + if (bpf_register_is_null(reg) && type_may_be_null(arg->arg_type)) { + err = mark_arg_precision(env, argno); + if (err) + return err; continue; + } memset(&meta, 0, sizeof(meta)); /* leave func_id as zero */ err = check_reg_type(env, reg, argno, arg->arg_type, &arg->btf_id, &meta, From 91957791663f49561848c40e982061799b8f86b0 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 17:05:59 -0700 Subject: [PATCH 0906/2366] selftests/bpf: precision of a NULL global subprogram BTF_ID argument Check that mark_chain_precision() is called for a NULL pointer passed as an __arg_trusted __arg_nullable argument of a global subprogram. Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260904-register-is-null-precise-fixes-v1-8-0f5a360ff15d@gmail.com Signed-off-by: Alexei Starovoitov --- .../bpf/progs/verifier_global_ptr_args.c | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c index 0bdeb7bc4687..a3d2af8dc839 100644 --- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c +++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c @@ -56,6 +56,30 @@ int trusted_task_arg_nullable(void *ctx) return res; } +/* + * Check that the verifier does not use checkpoints created + * on path with r1 == 0 to prune path with r1 != 0. + */ +SEC("?tp_btf/task_newtask") +__failure +__flag(BPF_F_TEST_STATE_FREQ) +__msg("R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_") +__naked int null_btf_id_arg_global_subprog(void) +{ + asm volatile ( + "call %[bpf_get_prandom_u32];" + "r1 = 42;" + "if r0 > 42 goto 1f;" + "r1 = 0;" + "1:" + "call subprog_trusted_task_nullable;" + "r0 = 0;" + "exit;" + : + : __imm(bpf_get_prandom_u32) + : __clobber_all); +} + __weak int subprog_trusted_task_nonnull(struct task_struct *task __arg_trusted) { return task->pid + task->tgid; From 1d7f8f191c06f967a85922c4652dc33c132b585d Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 17:06:00 -0700 Subject: [PATCH 0907/2366] bpf: propagate mark_chain_precision() errors out of loop_flag_is_zero() Stop verification if mark_chain_precision() fails when called from loop_flag_is_zero(). No functional change intended for the paths where backtracking succeeds. Fixes: 1ade23711971 ("bpf: Inline calls to bpf_loop when callback is known") Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260904-register-is-null-precise-fixes-v1-9-0f5a360ff15d@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 1b9fcbe4621a..e8af1d3dbdeb 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -10702,33 +10702,45 @@ static struct bpf_insn_aux_data *cur_aux(const struct bpf_verifier_env *env) return &env->insn_aux_data[env->insn_idx]; } -static bool loop_flag_is_zero(struct bpf_verifier_env *env) +/* Returns 1 if R4 is a known zero, 0 if it is not, a negative errno on error. */ +static int loop_flag_is_zero(struct bpf_verifier_env *env) { struct bpf_reg_state *reg = reg_state(env, BPF_REG_4); - bool reg_is_null = bpf_register_is_null(reg); + int err; - if (reg_is_null) - mark_chain_precision(env, BPF_REG_4); + if (!bpf_register_is_null(reg)) + return 0; - return reg_is_null; + err = mark_chain_precision(env, BPF_REG_4); + if (err) + return err; + return 1; } -static void update_loop_inline_state(struct bpf_verifier_env *env, u32 subprogno) +static int update_loop_inline_state(struct bpf_verifier_env *env, u32 subprogno) { struct bpf_loop_inline_state *state = &cur_aux(env)->loop_inline_state; + int flag_is_zero; if (!state->initialized) { + flag_is_zero = loop_flag_is_zero(env); + if (flag_is_zero < 0) + return flag_is_zero; state->initialized = 1; - state->fit_for_inline = loop_flag_is_zero(env); + state->fit_for_inline = flag_is_zero; state->callback_subprogno = subprogno; - return; + return 0; } if (!state->fit_for_inline) - return; + return 0; - state->fit_for_inline = (loop_flag_is_zero(env) && + flag_is_zero = loop_flag_is_zero(env); + if (flag_is_zero < 0) + return flag_is_zero; + state->fit_for_inline = (flag_is_zero && state->callback_subprogno == subprogno); + return 0; } /* Returns whether or not the given map can potentially elide @@ -10960,7 +10972,9 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn err = check_bpf_snprintf_call(env, regs); break; case BPF_FUNC_loop: - update_loop_inline_state(env, meta.subprogno); + err = update_loop_inline_state(env, meta.subprogno); + if (err) + return err; /* Verifier relies on R1 value to determine if bpf_loop() iteration * is finished, thus mark it precise. */ From cf2475616b11c0efefdd969d42913f56ca39f918 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 17:06:01 -0700 Subject: [PATCH 0908/2366] bpf: use mark_arg_precision() in check_mem_size_reg() Use newly added mark_arg_precision() helper in check_mem_size_reg(). Signed-off-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260904-register-is-null-precise-fixes-v1-10-0f5a360ff15d@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index e8af1d3dbdeb..1c3039f3fc32 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -7160,14 +7160,8 @@ static int check_mem_size_reg(struct bpf_verifier_env *env, if (err && failure) *failure = BPF_MEM_SIZE_FAIL_MEMORY; - if (!err) { - int regno = reg_from_argno(size_argno); - - if (regno >= 0) - err = mark_chain_precision(env, regno); - else - err = mark_stack_arg_precision(env, arg_idx_from_argno(size_argno)); - } + if (!err) + err = mark_arg_precision(env, size_argno); return err; From 80dd7e754b3aa9637a0758ad93fa209f9650ec48 Mon Sep 17 00:00:00 2001 From: Sahil Chandna Date: Tue, 1 Sep 2026 07:17:58 -0500 Subject: [PATCH 0909/2366] net: mana: Reserve extra CQ slot for the fence completion CQE The RX completion queue is sized to hold exactly one CQE per posted RX WQE. MANA_FENCE_RQ makes hardware post an additional CQE_RX_OBJECT_FENCE after the packet CQEs. The current sizing reserves no extra slot for it and in rare cases, CQ has no guaranteed slot for the fence CQE when it is full of packet CQEs. This can lead to dropping the fence completion while the driver waits holding RTNL lock throughout the timeout duration. Reserve one extra CQE slot for CQE_RX_OBJECT_FENCE. mana_gd_alloc_memory() requires queue_size to be a power-of-two and at least MANA_PAGE_SIZE; the reservation pushes cq_size past a power-of-two, so round up the CQ size in mana_create_rxq(). Cc: stable@vger.kernel.org Fixes: 6cc74443a773 ("net: mana: Add RX fencing") Signed-off-by: Sahil Chandna Reviewed-by: Haiyang Zhang Link: https://patch.msgid.link/20260901121837.3503240-1-sahilchandna@linux.microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/mana_en.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 7a1ac853e3ab..45a7520491a6 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -2986,6 +2986,10 @@ static int mana_alloc_rx_wqe(struct mana_port_context *apc, *cq_size += COMP_ENTRY_SIZE; } + /* Reserve an extra slot for Fence completion + * event (CQE_RX_OBJECT_FENCE) in case RX CQ is full. + */ + *cq_size += COMP_ENTRY_SIZE; return 0; } @@ -3080,7 +3084,7 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc, goto out; rq_size = MANA_PAGE_ALIGN(rq_size); - cq_size = MANA_PAGE_ALIGN(cq_size); + cq_size = MANA_PAGE_ALIGN(roundup_pow_of_two(cq_size)); /* Create RQ */ memset(&spec, 0, sizeof(spec)); From 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 2 Sep 2026 15:31:14 -0700 Subject: [PATCH 0910/2366] treewide: refresh kmalloc_obj() conversions This is another run of the Coccinelle script for converting kmalloc() family of allocations to kmalloc_obj() via the existing rules in scripts/coccinelle/api/kmalloc_objs.cocci This catches both the set of kmalloc() uses added since the first kmalloc_obj() conversions in v7.0 and adds a large group missed in the first pass due to Coccinelle not interacting well with the cleanup.h scoped_...() family of macros[1]. I worked around this with spatch's "--macro-file" argument to a file with all the scoped_...() macros mapped to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control flow indicator I could find. Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc, riscv, and s390 with no new warnings. Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1] Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2] Signed-off-by: Kees Cook --- arch/arm64/crypto/aes-neonbs-glue.c | 4 +- arch/arm64/kvm/nested.c | 2 +- arch/loongarch/kvm/intc/dmsintc.c | 2 +- arch/mips/bcm47xx/buttons.c | 8 +-- arch/powerpc/sysdev/xive/common.c | 2 +- arch/riscv/kvm/vcpu_pmu.c | 4 +- arch/s390/kvm/s390/s390.c | 4 +- drivers/accel/rocket/rocket_job.c | 2 +- drivers/acpi/irq.c | 8 +-- drivers/base/property.c | 2 +- drivers/block/drbd/drbd_nl_gen.c | 40 +++++++------- drivers/block/ublk_drv.c | 4 +- drivers/block/zram/backend_lz4.c | 2 +- drivers/clk/ti/composite.c | 2 +- drivers/clk/ti/mux.c | 2 +- drivers/cpufreq/amd-pstate.c | 2 +- .../crypto/inside-secure/eip93/eip93-common.c | 5 +- .../intel/qat/qat_common/qat_comp_algs.c | 3 +- drivers/crypto/ti/dthev2-aes.c | 8 +-- drivers/devfreq/hisi_uncore_freq.c | 2 +- drivers/dma-buf/st-dma-fence.c | 2 +- drivers/dma-buf/udmabuf.c | 4 +- drivers/dma/switchtec_dma.c | 7 ++- drivers/edac/versalnet_edac.c | 2 +- drivers/firmware/arm_scmi/driver.c | 2 +- drivers/firmware/qcom/qcom_tzmem.c | 8 ++- drivers/firmware/ti_sci.c | 2 +- drivers/fpga/dfl-afu-dma-region.c | 2 +- drivers/gpio/gpio-aggregator.c | 4 +- drivers/gpio/gpio-mpsse.c | 2 +- drivers/gpio/gpio-sim.c | 3 +- drivers/gpio/gpio-virtuser.c | 3 +- drivers/gpio/gpiolib-cdev.c | 2 +- drivers/gpio/gpiolib.c | 4 +- drivers/gpu/buddy.c | 13 ++--- .../gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c | 5 +- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 4 +- drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c | 2 +- .../gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 5 +- drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c | 4 +- .../gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 6 +-- drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c | 6 +-- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 12 ++--- .../tests/amdgpu_dm_connector_test.c | 2 +- .../amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 8 +-- .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 18 +++---- .../amdgpu_dm/tests/amdgpu_dm_plane_test.c | 4 +- .../display/amdgpu_dm/tests/amdgpu_dm_test.c | 2 +- .../gpu/drm/amd/display/dc/clk_mgr/clk_mgr.c | 2 +- .../display/dc/clk_mgr/dcn60/dcn60_clk_mgr.c | 4 +- drivers/gpu/drm/amd/display/dc/core/dc.c | 3 +- .../gpu/drm/amd/display/dc/core/dc_surface.c | 2 +- .../amd/display/dc/dccg/dcn42/dcn42_dccg.c | 2 +- .../amd/display/dc/dccg/dcn60/dcn60_dccg.c | 2 +- drivers/gpu/drm/amd/display/dc/gpio/hw_ddc.c | 2 +- .../display/dc/irq/dcn42/irq_service_dcn42.c | 2 +- .../display/dc/irq/dcn60/irq_service_dcn60.c | 3 +- .../amd/display/dc/pg/dcn42/dcn42_pg_cntl.c | 2 +- .../dc/resource/dcn30/dcn30_resource.c | 4 +- .../dc/resource/dcn302/dcn302_resource.c | 4 +- .../dc/resource/dcn303/dcn303_resource.c | 4 +- .../dc/resource/dcn31/dcn31_resource.c | 4 +- .../dc/resource/dcn314/dcn314_resource.c | 4 +- .../dc/resource/dcn315/dcn315_resource.c | 4 +- .../dc/resource/dcn316/dcn316_resource.c | 4 +- .../dc/resource/dcn32/dcn32_resource.c | 4 +- .../dc/resource/dcn321/dcn321_resource.c | 4 +- .../dc/resource/dcn35/dcn35_resource.c | 4 +- .../dc/resource/dcn351/dcn351_resource.c | 4 +- .../dc/resource/dcn36/dcn36_resource.c | 4 +- .../dc/resource/dcn401/dcn401_resource.c | 4 +- .../dc/resource/dcn42/dcn42_resource.c | 50 ++++++++---------- .../dc/resource/dcn42b/dcn42b_resource.c | 46 ++++++++-------- .../dc/resource/dcn60/dcn60_resource.c | 42 +++++++-------- .../gpu/drm/amd/display/modules/power/power.c | 5 +- .../drm/amd/display/modules/power/power_abm.c | 3 +- .../drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c | 2 +- .../drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c | 4 +- drivers/gpu/drm/amd/ras/core/cmd.c | 4 +- drivers/gpu/drm/drm_exec.c | 2 +- drivers/gpu/drm/drm_syncobj.c | 2 +- drivers/gpu/drm/qxl/qxl_display.c | 2 +- drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c | 2 +- drivers/gpu/drm/verisilicon/vs_plane.c | 4 +- drivers/gpu/drm/virtio/virtgpu_prime.c | 4 +- drivers/gpu/drm/vkms/vkms_configfs.c | 8 +-- drivers/gpu/drm/xe/xe_amc.c | 2 +- drivers/gpu/drm/xe/xe_vm.c | 2 +- drivers/hid/hid-asus.c | 6 +-- drivers/hid/hid-steam.c | 4 +- drivers/hid/hid-steelseries-arctis.c | 2 +- drivers/hv/channel.c | 5 +- drivers/hv/hv_balloon.c | 2 +- drivers/hwmon/applesmc.c | 17 +++--- drivers/hwtracing/coresight/coresight-core.c | 2 +- drivers/i2c/busses/i2c-gpio.c | 2 +- drivers/i3c/master/amd-i3c-master.c | 4 +- drivers/iio/adc/ad7280a.c | 4 +- .../buffer/industrialio-buffer-dmaengine.c | 2 +- drivers/iio/inkern.c | 2 +- drivers/infiniband/core/nldev.c | 2 +- drivers/infiniband/hw/hns/hns_roce_debugfs.c | 3 +- drivers/input/input.c | 2 +- drivers/input/keyboard/adp5585-keys.c | 4 +- drivers/input/keyboard/atkbd.c | 2 +- drivers/input/misc/ims-pcu.c | 2 +- drivers/input/mouse/psmouse-smbus.c | 2 +- drivers/input/serio/serio_raw.c | 2 +- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 2 +- drivers/iommu/iommufd/device.c | 2 +- drivers/iommu/iommufd/driver.c | 2 +- drivers/iommu/iommufd/hwpt_noiommu.c | 2 +- drivers/iommu/vsi-iommu.c | 2 +- drivers/irqchip/irq-gic-v5-irs.c | 2 +- drivers/irqchip/irq-loongarch-ir.c | 2 +- drivers/irqchip/irq-realtek-rtl.c | 2 +- drivers/mailbox/riscv-sbi-mpxy-mbox.c | 2 +- drivers/md/dm-inlinecrypt.c | 2 +- drivers/md/md-llbitmap.c | 4 +- .../media/platform/allegro-dvt/allegro-core.c | 2 +- .../media/platform/amd/isp4/isp4_interface.c | 6 +-- .../media/platform/renesas/rcar-isp/core.c | 2 +- drivers/media/rc/igorplugusb.c | 2 +- drivers/mfd/mfd-core.c | 4 +- drivers/mfd/ucb1x00-assabet.c | 2 +- drivers/mtd/mtd_virt_concat.c | 4 +- drivers/mtd/mtdconcat.c | 2 +- drivers/net/dsa/mv88e6xxx/tcflower.c | 2 +- drivers/net/ethernet/alibaba/eea/eea_adminq.c | 7 ++- drivers/net/ethernet/alibaba/eea/eea_net.c | 8 +-- drivers/net/ethernet/alibaba/eea/eea_pci.c | 2 +- drivers/net/ethernet/alibaba/eea/eea_ring.c | 2 +- drivers/net/ethernet/alibaba/eea/eea_rx.c | 5 +- drivers/net/ethernet/alibaba/eea/eea_tx.c | 3 +- drivers/net/ethernet/amd/pds_core/core.c | 3 +- drivers/net/ethernet/amd/pds_core/fw.c | 6 +-- drivers/net/ethernet/cadence/macb_main.c | 2 +- drivers/net/ethernet/cisco/enic/enic_admin.c | 2 +- drivers/net/ethernet/cisco/enic/enic_main.c | 6 +-- .../ethernet/freescale/dpaa2/dpaa2-switch.c | 5 +- drivers/net/ethernet/intel/libie/controlq.c | 3 +- .../ethernet/marvell/octeontx2/af/cn20k/npc.c | 13 ++--- .../net/ethernet/mellanox/mlx5/core/eswitch.c | 3 +- .../mellanox/mlx5/core/eswitch_offloads.c | 2 +- .../ethernet/mellanox/mlx5/core/sf/hw_table.c | 2 +- .../mlx5/core/steering/sws/dr_icm_pool.c | 2 +- drivers/net/ethernet/meta/fbnic/fbnic_irq.c | 2 +- .../net/ethernet/microsoft/mana/gdma_main.c | 8 +-- drivers/net/ntb_netdev.c | 3 +- drivers/net/wireless/ath/ath12k/ahb.c | 2 +- .../wireless/intel/iwlwifi/mld/regulatory.c | 3 +- .../net/wireless/mediatek/mt76/mt7921/regd.c | 2 +- .../net/wireless/mediatek/mt76/mt7925/main.c | 2 +- .../net/wireless/mediatek/mt76/mt7925/regd.c | 2 +- drivers/net/wireless/morsemicro/mm81x/mac.c | 9 ++-- drivers/net/wireless/morsemicro/mm81x/yaps.c | 10 ++-- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 14 ++--- drivers/net/wireless/nxp/nxpwifi/cmdevt.c | 2 +- drivers/net/wireless/nxp/nxpwifi/ie.c | 14 ++--- drivers/net/wireless/nxp/nxpwifi/init.c | 2 +- drivers/net/wireless/nxp/nxpwifi/main.c | 8 +-- drivers/net/wireless/nxp/nxpwifi/scan.c | 13 +++-- drivers/net/wireless/nxp/nxpwifi/sta_cfg.c | 2 +- drivers/net/wireless/nxp/nxpwifi/sta_cmd.c | 2 +- drivers/net/wireless/nxp/nxpwifi/uap_event.c | 2 +- drivers/nvdimm/region_devs.c | 3 +- drivers/nvme/host/core.c | 3 +- drivers/nvme/host/pci.c | 2 +- drivers/opp/core.c | 4 +- drivers/pci/endpoint/pci-ep-msi.c | 4 +- drivers/platform/x86/amd/hsmp/acpi.c | 5 +- .../x86/hp/hp-bioscfg/enum-attributes.c | 4 +- .../platform/x86/intel/pmc/pwrm_telemetry.c | 3 +- drivers/pmdomain/core.c | 2 +- drivers/power/reset/reboot-mode.c | 6 +-- drivers/power/sequencing/core.c | 2 +- drivers/power/sequencing/pwrseq-pcie-m2.c | 2 +- drivers/power/supply/power_supply_core.c | 6 +-- drivers/ptp/ptp_chardev.c | 2 +- drivers/s390/block/dasd.c | 2 +- drivers/scsi/fnic/fnic_debugfs.c | 2 +- drivers/scsi/leapraid/leapraid_func.c | 52 ++++++++----------- drivers/scsi/scsi_scan.c | 2 +- drivers/soc/bcm/brcmstb/common.c | 2 +- drivers/spi/spi-offload.c | 2 +- drivers/staging/greybus/raw.c | 2 +- drivers/staging/media/atomisp/pci/sh_css.c | 30 ++++------- .../media/atomisp/pci/sh_css_firmware.c | 4 +- drivers/tee/qcomtee/user_obj.c | 3 +- drivers/thunderbolt/stream.c | 12 ++--- drivers/tty/moxa.c | 3 +- drivers/tty/vt/consolemap.c | 3 +- drivers/ufs/core/ufs-txeq.c | 2 +- drivers/ufs/host/ufs-qcom.c | 2 +- drivers/usb/gadget/function/f_ncm.c | 2 +- drivers/usb/usbip/usbip_common.c | 2 +- drivers/xen/grant-table.c | 2 +- fs/9p/vfs_dentry.c | 3 +- fs/afs/dir.c | 2 +- fs/afs/symlink.c | 3 +- fs/binfmt_misc.c | 7 ++- fs/ceph/addr.c | 2 +- fs/ceph/mds_client.c | 2 +- fs/ceph/subvolume_metrics.c | 2 +- fs/coredump.c | 2 +- fs/ext4/fast_commit.c | 4 +- fs/fuse/file.c | 3 +- fs/fuse/readdir.c | 2 +- fs/hfs/bnode.c | 2 +- fs/namespace.c | 2 +- fs/nfsd/export.c | 7 ++- fs/nfsd/nfs4callback.c | 4 +- fs/nfsd/nfs4state.c | 7 +-- fs/nfsd/nfsctl.c | 2 +- fs/ntfs/bitmap.c | 2 +- fs/ntfs/compress.c | 8 +-- fs/ntfs/dir.c | 16 +++--- fs/ntfs/ea.c | 2 +- fs/ntfs/index.c | 2 +- fs/ntfs/logfile.c | 2 +- fs/ntfs/mft.c | 10 ++-- fs/ntfs/runlist.c | 14 ++--- fs/ntfs/super.c | 2 +- fs/overlayfs/readdir.c | 2 +- fs/smb/client/cifs_swn.c | 2 +- fs/smb/client/dfs_cache.c | 2 +- fs/smb/client/smb2inode.c | 2 +- fs/smb/server/ksmbd_work.c | 2 +- fs/smb/server/smb2pdu.c | 2 +- fs/xfs/libxfs/xfs_da_btree.c | 4 +- init/initramfs_test.c | 2 +- io_uring/napi.c | 2 +- io_uring/zcrx.c | 4 +- kernel/bpf/diagnostics.c | 2 +- kernel/bpf/hashtab.c | 2 +- kernel/bpf/liveness.c | 2 +- kernel/bpf/log.c | 2 +- kernel/bpf/verifier.c | 5 +- kernel/dma/map_benchmark.c | 3 +- kernel/events/core.c | 5 +- kernel/futex/core.c | 6 +-- kernel/irq/manage.c | 2 +- kernel/jump_label.c | 5 +- kernel/kthread.c | 2 +- kernel/sched/ext/cid.c | 16 +++--- kernel/sched/ext/ext.c | 2 +- kernel/sched/ext/sub.c | 2 +- kernel/trace/fprobe.c | 2 +- kernel/trace/ring_buffer.c | 4 +- kernel/trace/trace_eprobe.c | 2 +- kernel/trace/trace_remote.c | 10 ++-- lib/test_rhashtable.c | 2 +- lib/test_workqueue.c | 4 +- lib/tests/kunit_iov_iter.c | 2 +- mm/damon/tests/vaddr-kunit.h | 2 +- net/batman-adv/hard-interface.c | 2 +- net/bluetooth/hci_sync.c | 3 +- net/devlink/netlink.c | 2 +- net/devlink/param.c | 7 ++- net/ipv4/tcp_ipv4.c | 4 +- net/mac80211/nan.c | 3 +- net/mctp/test/route-test.c | 2 +- net/mctp/test/utils.c | 2 +- net/netfilter/nf_tables_api.c | 2 +- net/netfilter/nfnetlink_cttimeout.c | 2 +- net/rds/info.c | 2 +- net/rxrpc/key.c | 2 +- net/sched/act_gate.c | 4 +- net/sched/act_tunnel_key.c | 2 +- net/sunrpc/auth_gss/gss_krb5_crypto.c | 5 +- net/sunrpc/svcauth_unix.c | 4 +- net/sunrpc/xdr.c | 3 +- net/unix/af_unix.c | 4 +- net/wireless/core.c | 5 +- net/wireless/nl80211.c | 9 ++-- security/integrity/ima/ima_queue.c | 3 +- sound/core/compress_offload.c | 4 +- sound/core/control.c | 6 +-- sound/core/control_led.c | 2 +- sound/core/init.c | 2 +- sound/core/misc.c | 2 +- sound/core/oss/mixer_oss.c | 2 +- sound/core/pcm_native.c | 2 +- sound/core/seq/oss/seq_oss_synth.c | 2 +- sound/core/seq/seq_clientmgr.c | 2 +- sound/core/seq/seq_virmidi.c | 2 +- sound/core/timer.c | 2 +- sound/drivers/aloop.c | 4 +- sound/isa/gus/gus_dma.c | 2 +- sound/pci/cs46xx/cs46xx_lib.c | 2 +- sound/pci/ctxfi/ctamixer.c | 4 +- sound/pci/ctxfi/ctdaio.c | 4 +- sound/pci/ctxfi/ctsrc.c | 4 +- sound/pci/ctxfi/cttimer.c | 2 +- sound/pci/emu10k1/emufx.c | 2 +- sound/soc/codecs/simple-amplifier.c | 2 +- sound/soc/generic/simple-card-utils.c | 2 +- sound/soc/meson/gx-formatter.c | 2 +- sound/soc/qcom/qdsp6/q6afe.c | 2 +- sound/soc/sdca/sdca_functions.c | 19 ++++--- sound/soc/sof/sof-client-probes-ipc4.c | 2 +- sound/soc/sof/sof-client.c | 2 +- sound/sparc/amd7930.c | 2 +- 304 files changed, 637 insertions(+), 726 deletions(-) diff --git a/arch/arm64/crypto/aes-neonbs-glue.c b/arch/arm64/crypto/aes-neonbs-glue.c index 5bcbac979893..7cb1aede9a48 100644 --- a/arch/arm64/crypto/aes-neonbs-glue.c +++ b/arch/arm64/crypto/aes-neonbs-glue.c @@ -66,7 +66,7 @@ static int aesbs_setkey(struct crypto_skcipher *tfm, const u8 *in_key, struct crypto_aes_ctx *rk; int err; - rk = kmalloc(sizeof(*rk), GFP_KERNEL); + rk = kmalloc_obj(*rk); if (!rk) return -ENOMEM; @@ -128,7 +128,7 @@ static int aesbs_cbc_ctr_setkey(struct crypto_skcipher *tfm, const u8 *in_key, struct crypto_aes_ctx *rk; int err; - rk = kmalloc(sizeof(*rk), GFP_KERNEL); + rk = kmalloc_obj(*rk); if (!rk) return -ENOMEM; diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index 17123f0b6dab..3c4fc566eafc 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -93,7 +93,7 @@ int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu) num_mmus = atomic_read(&kvm->online_vcpus) * S2_MMU_PER_VCPU; if (num_mmus > kvm->arch.nested_mmus_size) { - tmp = kvcalloc(num_mmus, sizeof(*tmp), GFP_KERNEL_ACCOUNT); + tmp = kvzalloc_objs(*tmp, num_mmus, GFP_KERNEL_ACCOUNT); if (!tmp) return -ENOMEM; diff --git a/arch/loongarch/kvm/intc/dmsintc.c b/arch/loongarch/kvm/intc/dmsintc.c index bb7285c49df3..5518cafada55 100644 --- a/arch/loongarch/kvm/intc/dmsintc.c +++ b/arch/loongarch/kvm/intc/dmsintc.c @@ -149,7 +149,7 @@ static int kvm_dmsintc_create(struct kvm_device *dev, u32 type) return -EINVAL; } - s = kzalloc(sizeof(struct loongarch_dmsintc), GFP_KERNEL); + s = kzalloc_obj(struct loongarch_dmsintc); if (!s) return -ENOMEM; diff --git a/arch/mips/bcm47xx/buttons.c b/arch/mips/bcm47xx/buttons.c index 151a4ee2803f..7bb338da8e42 100644 --- a/arch/mips/bcm47xx/buttons.c +++ b/arch/mips/bcm47xx/buttons.c @@ -523,24 +523,24 @@ bcm47xx_buttons_add(const struct bcm47xx_gpio_key *buttons, int nbuttons) /* 1 node for gpio-keys device, 1 node for each button, 1 terminator */ const struct software_node **node_group __free(kfree) = - kcalloc(1 + nbuttons + 1, sizeof(*node_group), GFP_KERNEL); + kzalloc_objs(*node_group, 1 + nbuttons + 1); if (!node_group) return -ENOMEM; /* 1 code property, 1 gpio property, 1 terminator */ struct property_entry *props __free(kfree) = - kcalloc(nbuttons * 3, sizeof(*props), GFP_KERNEL); + kzalloc_objs(*props, nbuttons * 3); if (!props) return -ENOMEM; /* 1 node for gpio-keys device, 1 node for each button */ struct software_node *nodes __free(kfree) = - kcalloc(1 + nbuttons, sizeof(*nodes), GFP_KERNEL); + kzalloc_objs(*nodes, 1 + nbuttons); if (!nodes) return -ENOMEM; struct software_node_ref_args *ref_args __free(kfree) = - kcalloc(nbuttons, sizeof(*ref_args), GFP_KERNEL); + kzalloc_objs(*ref_args, nbuttons); if (!ref_args) return -ENOMEM; diff --git a/arch/powerpc/sysdev/xive/common.c b/arch/powerpc/sysdev/xive/common.c index 8ae088632337..ed3d3e26c136 100644 --- a/arch/powerpc/sysdev/xive/common.c +++ b/arch/powerpc/sysdev/xive/common.c @@ -1134,7 +1134,7 @@ static int __init xive_init_ipis(void) if (!ipi_domain) goto out_free_fwnode; - xive_ipis = kzalloc_objs(*xive_ipis, nr_node_ids, GFP_KERNEL); + xive_ipis = kzalloc_objs(*xive_ipis, nr_node_ids); if (!xive_ipis) goto out_free_domain; diff --git a/arch/riscv/kvm/vcpu_pmu.c b/arch/riscv/kvm/vcpu_pmu.c index 752f1014d633..6ff741ee7803 100644 --- a/arch/riscv/kvm/vcpu_pmu.c +++ b/arch/riscv/kvm/vcpu_pmu.c @@ -503,8 +503,8 @@ int kvm_riscv_vcpu_pmu_event_info(struct kvm_vcpu *vcpu, unsigned long saddr_low } } - einfo = kvcalloc(num_events, sizeof(*einfo), - GFP_KERNEL_ACCOUNT | __GFP_NOWARN); + einfo = kvzalloc_objs(*einfo, num_events, + GFP_KERNEL_ACCOUNT | __GFP_NOWARN); if (!einfo) { ret = SBI_ERR_FAILURE; goto out; diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c index b0839e887221..5c73f43782a7 100644 --- a/arch/s390/kvm/s390/s390.c +++ b/arch/s390/kvm/s390/s390.c @@ -2168,7 +2168,7 @@ static int kvm_s390_get_skeys(struct kvm *kvm, struct kvm_s390_skeys *args) if (args->count < 1 || args->count > KVM_S390_SKEYS_MAX) return -EINVAL; - keys = kvmalloc_array(args->count, sizeof(*keys), GFP_KERNEL_ACCOUNT); + keys = kvmalloc_objs(*keys, args->count, GFP_KERNEL_ACCOUNT); if (!keys) return -ENOMEM; @@ -2205,7 +2205,7 @@ static int kvm_s390_set_skeys(struct kvm *kvm, struct kvm_s390_skeys *args) if (args->count < 1 || args->count > KVM_S390_SKEYS_MAX) return -EINVAL; - keys = kvmalloc_array(args->count, sizeof(*keys), GFP_KERNEL_ACCOUNT); + keys = kvmalloc_objs(*keys, args->count, GFP_KERNEL_ACCOUNT); if (!keys) return -ENOMEM; diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c index 3141f210fcd1..f40435505818 100644 --- a/drivers/accel/rocket/rocket_job.c +++ b/drivers/accel/rocket/rocket_job.c @@ -196,7 +196,7 @@ static int rocket_job_push(struct rocket_job *job) if (check_add_overflow(job->in_bo_count, job->out_bo_count, &bo_count)) return -EINVAL; - bos = kvmalloc_array(bo_count, sizeof(*bos), GFP_KERNEL); + bos = kvmalloc_objs(*bos, bo_count); if (!bos) return -ENOMEM; memcpy(bos, job->in_bos, job->in_bo_count * sizeof(void *)); diff --git a/drivers/acpi/irq.c b/drivers/acpi/irq.c index e4293458bf61..a670722ddd5a 100644 --- a/drivers/acpi/irq.c +++ b/drivers/acpi/irq.c @@ -486,7 +486,8 @@ static u32 acpi_add_prt_dep(acpi_handle handle) if (ACPI_FAILURE(status)) continue; dep_devices.count = 1; - dep_devices.handles = kcalloc(1, sizeof(*dep_devices.handles), GFP_KERNEL); + dep_devices.handles = kzalloc_objs(*dep_devices.handles, + 1); if (!dep_devices.handles) { acpi_handle_err(handle, "failed to allocate memory\n"); continue; @@ -499,7 +500,8 @@ static u32 acpi_add_prt_dep(acpi_handle handle) if (!gsi_handle) continue; dep_devices.count = 1; - dep_devices.handles = kcalloc(1, sizeof(*dep_devices.handles), GFP_KERNEL); + dep_devices.handles = kzalloc_objs(*dep_devices.handles, + 1); if (!dep_devices.handles) { acpi_handle_err(handle, "failed to allocate memory\n"); continue; @@ -526,7 +528,7 @@ static u32 acpi_add_irq_dep(acpi_handle handle) continue; dep_devices.count = 1; - dep_devices.handles = kcalloc(1, sizeof(*dep_devices.handles), GFP_KERNEL); + dep_devices.handles = kzalloc_objs(*dep_devices.handles, 1); if (!dep_devices.handles) { acpi_handle_err(handle, "failed to allocate memory\n"); continue; diff --git a/drivers/base/property.c b/drivers/base/property.c index b136c339ddae..cceaa3240ca4 100644 --- a/drivers/base/property.c +++ b/drivers/base/property.c @@ -526,7 +526,7 @@ int fwnode_property_match_string(const struct fwnode_handle *fwnode, if (nval == 0) return -ENODATA; - const char **values __free(kfree) = kcalloc(nval, sizeof(*values), GFP_KERNEL); + const char **values __free(kfree) = kzalloc_objs(*values, nval); if (!values) return -ENOMEM; diff --git a/drivers/block/drbd/drbd_nl_gen.c b/drivers/block/drbd/drbd_nl_gen.c index fb44b948cec8..9753dc789bde 100644 --- a/drivers/block/drbd/drbd_nl_gen.c +++ b/drivers/block/drbd/drbd_nl_gen.c @@ -656,7 +656,7 @@ static int __drbd_cfg_context_from_attrs(struct drbd_cfg_context *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_DRBD_CFG_CONTEXT_CTX_PEER_ADDR + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_DRBD_CFG_CONTEXT_CTX_PEER_ADDR + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_drbd_cfg_context_nl_policy, NULL); @@ -714,7 +714,7 @@ static int __disk_conf_from_attrs(struct disk_conf *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_DISK_CONF_DISABLE_WRITE_SAME + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_DISK_CONF_DISABLE_WRITE_SAME + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_disk_conf_nl_policy, NULL); @@ -871,7 +871,7 @@ static int __res_opts_from_attrs(struct res_opts *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_RES_OPTS_ON_NO_DATA + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_RES_OPTS_ON_NO_DATA + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_res_opts_nl_policy, NULL); @@ -921,7 +921,7 @@ static int __net_conf_from_attrs(struct net_conf *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_NET_CONF_SOCK_CHECK_TIMEO + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_NET_CONF_SOCK_CHECK_TIMEO + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_net_conf_nl_policy, NULL); @@ -1087,7 +1087,7 @@ static int __set_role_parms_from_attrs(struct set_role_parms *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_SET_ROLE_PARMS_ASSUME_UPTODATE + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_SET_ROLE_PARMS_ASSUME_UPTODATE + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_set_role_parms_nl_policy, NULL); @@ -1133,7 +1133,7 @@ static int __resize_parms_from_attrs(struct resize_parms *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_RESIZE_PARMS_AL_STRIPE_SIZE + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_RESIZE_PARMS_AL_STRIPE_SIZE + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_resize_parms_nl_policy, NULL); @@ -1195,7 +1195,7 @@ static int __start_ov_parms_from_attrs(struct start_ov_parms *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_START_OV_PARMS_OV_STOP_SECTOR + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_START_OV_PARMS_OV_STOP_SECTOR + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_start_ov_parms_nl_policy, NULL); @@ -1245,7 +1245,7 @@ static int __new_c_uuid_parms_from_attrs(struct new_c_uuid_parms *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_NEW_C_UUID_PARMS_CLEAR_BM + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_NEW_C_UUID_PARMS_CLEAR_BM + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_new_c_uuid_parms_nl_policy, NULL); @@ -1291,7 +1291,7 @@ static int __disconnect_parms_from_attrs(struct disconnect_parms *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_DISCONNECT_PARMS_FORCE_DISCONNECT + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_DISCONNECT_PARMS_FORCE_DISCONNECT + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_disconnect_parms_nl_policy, NULL); @@ -1337,7 +1337,7 @@ static int __detach_parms_from_attrs(struct detach_parms *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_DETACH_PARMS_FORCE_DETACH + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_DETACH_PARMS_FORCE_DETACH + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_detach_parms_nl_policy, NULL); @@ -1383,7 +1383,7 @@ static int __resource_info_from_attrs(struct resource_info *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_RESOURCE_INFO_RES_SUSP_FEN + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_RESOURCE_INFO_RES_SUSP_FEN + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_resource_info_nl_policy, NULL); @@ -1441,7 +1441,7 @@ static int __device_info_from_attrs(struct device_info *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_DEVICE_INFO_DEV_DISK_STATE + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_DEVICE_INFO_DEV_DISK_STATE + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_device_info_nl_policy, NULL); @@ -1487,7 +1487,7 @@ static int __connection_info_from_attrs(struct connection_info *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_CONNECTION_INFO_CONN_ROLE + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_CONNECTION_INFO_CONN_ROLE + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_connection_info_nl_policy, NULL); @@ -1537,7 +1537,8 @@ static int __peer_device_info_from_attrs(struct peer_device_info *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_DEPENDENCY + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, + DRBD_A_PEER_DEVICE_INFO_PEER_RESYNC_SUSP_DEPENDENCY + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_peer_device_info_nl_policy, NULL); @@ -1599,7 +1600,8 @@ static int __resource_statistics_from_attrs(struct resource_statistics *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_RESOURCE_STATISTICS_RES_STAT_WRITE_ORDERING + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, + DRBD_A_RESOURCE_STATISTICS_RES_STAT_WRITE_ORDERING + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_resource_statistics_nl_policy, NULL); @@ -1645,7 +1647,7 @@ static int __device_statistics_from_attrs(struct device_statistics *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_DEVICE_STATISTICS_HISTORY_UUIDS + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, DRBD_A_DEVICE_STATISTICS_HISTORY_UUIDS + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_device_statistics_nl_policy, NULL); @@ -1743,7 +1745,8 @@ static int __connection_statistics_from_attrs(struct connection_statistics *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_CONNECTION_STATISTICS_CONN_CONGESTED + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, + DRBD_A_CONNECTION_STATISTICS_CONN_CONGESTED + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_connection_statistics_nl_policy, NULL); @@ -1789,7 +1792,8 @@ static int __peer_device_statistics_from_attrs(struct peer_device_statistics *s, *ret_nested_attribute_table = NULL; if (!tla) return -ENOMSG; - ntb = kcalloc(DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_FLAGS + 1, sizeof(*ntb), GFP_KERNEL); + ntb = kzalloc_objs(*ntb, + DRBD_A_PEER_DEVICE_STATISTICS_PEER_DEV_FLAGS + 1); if (!ntb) return -ENOMEM; err = nla_parse_nested_deprecated(ntb, maxtype, tla, drbd_peer_device_statistics_nl_policy, NULL); diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 6c5bec7da97c..47574a98fc86 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -5390,7 +5390,7 @@ static int __ublk_ctrl_reg_buf(struct ublk_device *ub, page_to_pfn(pages[i + 1]) == pfn + (i - start) + 1) i++; - range = kzalloc(sizeof(*range), GFP_KERNEL); + range = kzalloc_obj(*range); if (!range) { ret = -ENOMEM; goto unwind; @@ -5453,7 +5453,7 @@ static int ublk_ctrl_reg_buf(struct ublk_device *ub, nr_pages = buf_reg.len >> PAGE_SHIFT; /* Pin pages before any locks (may sleep) */ - pages = kvmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL); + pages = kvmalloc_objs(*pages, nr_pages); if (!pages) return -ENOMEM; diff --git a/drivers/block/zram/backend_lz4.c b/drivers/block/zram/backend_lz4.c index 1e28104ad964..1e4ad31d39a6 100644 --- a/drivers/block/zram/backend_lz4.c +++ b/drivers/block/zram/backend_lz4.c @@ -42,7 +42,7 @@ static int lz4_setup_params(struct zcomp_params *params) if (!params->dict || !params->dict_sz) return 0; - dict_stream = kzalloc_obj(*dict_stream, GFP_KERNEL); + dict_stream = kzalloc_obj(*dict_stream); if (!dict_stream) return -ENOMEM; diff --git a/drivers/clk/ti/composite.c b/drivers/clk/ti/composite.c index 01eae8995254..83c3592cd179 100644 --- a/drivers/clk/ti/composite.c +++ b/drivers/clk/ti/composite.c @@ -248,7 +248,7 @@ int __init ti_clk_add_component(struct device_node *node, struct clk_hw *hw, return -EINVAL; } - parent_data = kcalloc(num_parents, sizeof(*parent_data), GFP_KERNEL); + parent_data = kzalloc_objs(*parent_data, num_parents); if (!parent_data) return -ENOMEM; diff --git a/drivers/clk/ti/mux.c b/drivers/clk/ti/mux.c index 0fef60e82107..baf90a3fb49e 100644 --- a/drivers/clk/ti/mux.c +++ b/drivers/clk/ti/mux.c @@ -181,7 +181,7 @@ static void of_mux_clk_setup(struct device_node *node) pr_err("mux-clock %pOFn must have parents\n", node); return; } - parent_data = kcalloc(num_parents, sizeof(*parent_data), GFP_KERNEL); + parent_data = kzalloc_objs(*parent_data, num_parents); if (!parent_data) return; diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index d4ff8b228f86..8bfd46d60843 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1624,7 +1624,7 @@ static struct freq_attr **get_freq_attrs(void) /* amd_pstate_{max_freq, lowest_nonlinear_freq, highest_perf} should always be visible */ BUG_ON(!count); - attrs = kcalloc(count + 1, sizeof(struct freq_attr *), GFP_KERNEL); + attrs = kzalloc_objs(struct freq_attr *, count + 1); if (!attrs) return ERR_PTR(-ENOMEM); diff --git a/drivers/crypto/inside-secure/eip93/eip93-common.c b/drivers/crypto/inside-secure/eip93/eip93-common.c index 4c163d7281b3..dacf586b2641 100644 --- a/drivers/crypto/inside-secure/eip93/eip93-common.c +++ b/drivers/crypto/inside-secure/eip93/eip93-common.c @@ -533,7 +533,7 @@ int eip93_send_req(struct crypto_async_request *async, memcpy(iv, reqiv, rctx->ivsize); - rctx->sa_state = kzalloc(sizeof(*rctx->sa_state), GFP_KERNEL); + rctx->sa_state = kzalloc_obj(*rctx->sa_state); if (!rctx->sa_state) return -ENOMEM; @@ -561,8 +561,7 @@ int eip93_send_req(struct crypto_async_request *async, iv[3] = 0xffffffff; crypto_inc((u8 *)iv, AES_BLOCK_SIZE); - rctx->sa_state_ctr = kzalloc(sizeof(*rctx->sa_state_ctr), - GFP_KERNEL); + rctx->sa_state_ctr = kzalloc_obj(*rctx->sa_state_ctr); if (!rctx->sa_state_ctr) { err = -ENOMEM; goto free_sa_state; diff --git a/drivers/crypto/intel/qat/qat_common/qat_comp_algs.c b/drivers/crypto/intel/qat/qat_common/qat_comp_algs.c index e0d003b50358..50e444da97e6 100644 --- a/drivers/crypto/intel/qat/qat_common/qat_comp_algs.c +++ b/drivers/crypto/intel/qat/qat_common/qat_comp_algs.c @@ -59,8 +59,7 @@ static void *qat_zstd_alloc_scratch(void) if (!scratch->literals) goto error; - scratch->out_seqs = kvcalloc(QAT_MAX_SEQUENCES, sizeof(ZSTD_Sequence), - GFP_KERNEL); + scratch->out_seqs = kvzalloc_objs(ZSTD_Sequence, QAT_MAX_SEQUENCES); if (!scratch->out_seqs) goto error; diff --git a/drivers/crypto/ti/dthev2-aes.c b/drivers/crypto/ti/dthev2-aes.c index eb5cd902dfb5..a3c069ca2baf 100644 --- a/drivers/crypto/ti/dthev2-aes.c +++ b/drivers/crypto/ti/dthev2-aes.c @@ -387,7 +387,7 @@ static int dthe_aes_run(struct crypto_engine *engine, void *areq) src_nents++; dst_nents++; - src = kmalloc_array(src_nents, sizeof(*src), GFP_ATOMIC); + src = kmalloc_objs(*src, src_nents, GFP_ATOMIC); if (!src) { ret = -ENOMEM; goto aes_ctr_src_alloc_err; @@ -399,7 +399,7 @@ static int dthe_aes_run(struct crypto_engine *engine, void *areq) sg_set_buf(sg, pad_buf, pad_size); if (diff_dst) { - dst = kmalloc_array(dst_nents, sizeof(*dst), GFP_ATOMIC); + dst = kmalloc_objs(*dst, dst_nents, GFP_ATOMIC); if (!dst) { ret = -ENOMEM; goto aes_ctr_dst_alloc_err; @@ -624,7 +624,7 @@ static struct scatterlist *dthe_aead_prep_aad(struct scatterlist *sg, if (assoclen % AES_BLOCK_SIZE) aad_nents++; - aad_sg = kmalloc_array(aad_nents, sizeof(struct scatterlist), GFP_ATOMIC); + aad_sg = kmalloc_objs(struct scatterlist, aad_nents, GFP_ATOMIC); if (!aad_sg) return ERR_PTR(-ENOMEM); @@ -680,7 +680,7 @@ static struct scatterlist *dthe_aead_prep_crypt(struct scatterlist *sg, if (cryptlen % AES_BLOCK_SIZE) crypt_nents++; - crypt_sg = kmalloc_array(crypt_nents, sizeof(struct scatterlist), GFP_ATOMIC); + crypt_sg = kmalloc_objs(struct scatterlist, crypt_nents, GFP_ATOMIC); if (!crypt_sg) { err = -ENOMEM; goto dthe_aead_prep_crypt_mem_err; diff --git a/drivers/devfreq/hisi_uncore_freq.c b/drivers/devfreq/hisi_uncore_freq.c index e1f64b723082..ac55641b0259 100644 --- a/drivers/devfreq/hisi_uncore_freq.c +++ b/drivers/devfreq/hisi_uncore_freq.c @@ -474,7 +474,7 @@ static int hisi_uncore_mark_related_cpus(struct hisi_uncore_freq *uncore, return -EINVAL; len = rc; - u32 *num __free(kfree) = kcalloc(len, sizeof(*num), GFP_KERNEL); + u32 *num __free(kfree) = kzalloc_objs(*num, len); if (!num) return -ENOMEM; diff --git a/drivers/dma-buf/st-dma-fence.c b/drivers/dma-buf/st-dma-fence.c index 856d0d302a5d..cb62e606d0a2 100644 --- a/drivers/dma-buf/st-dma-fence.c +++ b/drivers/dma-buf/st-dma-fence.c @@ -27,7 +27,7 @@ static struct dma_fence *mock_fence(void) { struct dma_fence *f; - f = kmalloc(sizeof(*f), GFP_KERNEL); + f = kmalloc_obj(*f); if (!f) return NULL; diff --git a/drivers/dma-buf/udmabuf.c b/drivers/dma-buf/udmabuf.c index 4a9ab5822ffc..df6dd0046242 100644 --- a/drivers/dma-buf/udmabuf.c +++ b/drivers/dma-buf/udmabuf.c @@ -359,7 +359,7 @@ static long udmabuf_create(struct miscdevice *device, long ret = -EINVAL; u32 i, flags; - ubuf = kzalloc(sizeof(*ubuf), GFP_KERNEL); + ubuf = kzalloc_obj(*ubuf); if (!ubuf) return -ENOMEM; @@ -387,7 +387,7 @@ static long udmabuf_create(struct miscdevice *device, if (ret) goto err; - folios = kvmalloc_array(max_nr_folios, sizeof(*folios), GFP_KERNEL); + folios = kvmalloc_objs(*folios, max_nr_folios); if (!folios) { ret = -ENOMEM; goto err; diff --git a/drivers/dma/switchtec_dma.c b/drivers/dma/switchtec_dma.c index c133535d3765..c8e2169877e9 100644 --- a/drivers/dma/switchtec_dma.c +++ b/drivers/dma/switchtec_dma.c @@ -1056,7 +1056,7 @@ static int switchtec_dma_chan_init(struct switchtec_dma_dev *swdma_dev, int se_buf_len, irq, rc; struct dma_chan *chan; - swdma_chan = kzalloc_obj(*swdma_chan, GFP_KERNEL); + swdma_chan = kzalloc_obj(*swdma_chan); if (!swdma_chan) return -ENOMEM; @@ -1162,8 +1162,7 @@ static int switchtec_dma_chans_enumerate(struct switchtec_dma_dev *swdma_dev, struct dma_device *dma = &swdma_dev->dma_dev; int base, cnt, rc, i; - swdma_dev->swdma_chans = kcalloc(chan_cnt, sizeof(*swdma_dev->swdma_chans), - GFP_KERNEL); + swdma_dev->swdma_chans = kzalloc_objs(*swdma_dev->swdma_chans, chan_cnt); if (!swdma_dev->swdma_chans) return -ENOMEM; @@ -1222,7 +1221,7 @@ static int switchtec_dma_create(struct pci_dev *pdev) /* * Create the switchtec dma device */ - swdma_dev = kzalloc_obj(*swdma_dev, GFP_KERNEL); + swdma_dev = kzalloc_obj(*swdma_dev); if (!swdma_dev) return -ENOMEM; diff --git a/drivers/edac/versalnet_edac.c b/drivers/edac/versalnet_edac.c index 97ec05d68bbb..9e65c4b1d99d 100644 --- a/drivers/edac/versalnet_edac.c +++ b/drivers/edac/versalnet_edac.c @@ -813,7 +813,7 @@ static int init_one_mc(struct mc_priv *priv, struct platform_device *pdev, int i layers[1].is_virt_csrow = false; rc = -ENOMEM; - dev = kzalloc(sizeof(*dev), GFP_KERNEL); + dev = kzalloc_obj(*dev); if (!dev) return rc; diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index ef29fd223287..922777e86d58 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -1792,7 +1792,7 @@ static void *scmi_iterator_init(const struct scmi_protocol_handle *ph, { int ret; - struct scmi_iterator *i __free(kfree) = kzalloc(sizeof(*i), GFP_KERNEL); + struct scmi_iterator *i __free(kfree) = kzalloc_obj(*i); if (!i) return ERR_PTR(-ENOMEM); diff --git a/drivers/firmware/qcom/qcom_tzmem.c b/drivers/firmware/qcom/qcom_tzmem.c index 0fd9581275f1..f926c1f64822 100644 --- a/drivers/firmware/qcom/qcom_tzmem.c +++ b/drivers/firmware/qcom/qcom_tzmem.c @@ -169,7 +169,7 @@ static int qcom_tzmem_init_area(struct qcom_tzmem_area *area) { int ret; - u64 *handle __free(kfree) = kzalloc(sizeof(*handle), GFP_KERNEL); + u64 *handle __free(kfree) = kzalloc_obj(*handle); if (!handle) return -ENOMEM; @@ -197,8 +197,7 @@ static int qcom_tzmem_pool_add_memory(struct qcom_tzmem_pool *pool, { int ret; - struct qcom_tzmem_area *area __free(kfree) = kzalloc(sizeof(*area), - gfp); + struct qcom_tzmem_area *area __free(kfree) = kzalloc_obj(*area, gfp); if (!area) return -ENOMEM; @@ -415,8 +414,7 @@ void *qcom_tzmem_alloc(struct qcom_tzmem_pool *pool, size_t size, gfp_t gfp) size = PAGE_ALIGN(size); - struct qcom_tzmem_chunk *chunk __free(kfree) = kzalloc(sizeof(*chunk), - gfp); + struct qcom_tzmem_chunk *chunk __free(kfree) = kzalloc_obj(*chunk, gfp); if (!chunk) return NULL; diff --git a/drivers/firmware/ti_sci.c b/drivers/firmware/ti_sci.c index cc747ab0237f..33d7c4a4181e 100644 --- a/drivers/firmware/ti_sci.c +++ b/drivers/firmware/ti_sci.c @@ -2386,7 +2386,7 @@ static int ti_sci_set_irq(const struct ti_sci_handle *handle, u32 valid_params, if (ret || !(info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST)) goto end; - irq = kzalloc_obj(*irq, GFP_KERNEL); + irq = kzalloc_obj(*irq); if (!irq) { ti_sci_manage_irq(handle, valid_params, src_id, src_index, dst_id, dst_host_irq, ia_id, vint, diff --git a/drivers/fpga/dfl-afu-dma-region.c b/drivers/fpga/dfl-afu-dma-region.c index 87652d58d03f..1b3a1af8658d 100644 --- a/drivers/fpga/dfl-afu-dma-region.c +++ b/drivers/fpga/dfl-afu-dma-region.c @@ -316,7 +316,7 @@ int afu_dma_map_region(struct dfl_feature_dev_data *fdata, if (user_addr + length < user_addr) return -EINVAL; - region = kzalloc(sizeof(*region), GFP_KERNEL); + region = kzalloc_obj(*region); if (!region) return -ENOMEM; diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index 5ce89f52b4b5..b0b65416d74d 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -886,8 +886,8 @@ gpio_aggregator_make_device_sw_node(struct gpio_aggregator *aggr) if (num_lines == 0) return NULL; - const char **line_names __free(kfree) = kcalloc( - num_lines, sizeof(*line_names), GFP_KERNEL); + const char **line_names __free(kfree) = kzalloc_objs(*line_names, + num_lines); if (!line_names) return ERR_PTR(-ENOMEM); diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c index a859deab2bca..30e8009b5fcc 100644 --- a/drivers/gpio/gpio-mpsse.c +++ b/drivers/gpio/gpio-mpsse.c @@ -518,7 +518,7 @@ static void gpio_mpsse_irq_enable(struct irq_data *irqd) * Can't be devm because it uses a non-raw spinlock (illegal in * this context, where a raw spinlock is held by our caller) */ - worker = kzalloc(sizeof(*worker), GFP_NOWAIT); + worker = kzalloc_obj(*worker, GFP_NOWAIT); if (!worker) return; diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c index ef1b779e8ea6..0c73bec04267 100644 --- a/drivers/gpio/gpio-sim.c +++ b/drivers/gpio/gpio-sim.c @@ -790,8 +790,7 @@ gpio_sim_make_bank_swnode(struct gpio_sim_bank *bank, line_names_size = gpio_sim_get_line_names_size(bank); if (line_names_size) { - line_names = kcalloc(line_names_size, sizeof(*line_names), - GFP_KERNEL); + line_names = kzalloc_objs(*line_names, line_names_size); if (!line_names) return ERR_PTR(-ENOMEM); diff --git a/drivers/gpio/gpio-virtuser.c b/drivers/gpio/gpio-virtuser.c index 7d0d366be37a..449fb1aed24b 100644 --- a/drivers/gpio/gpio-virtuser.c +++ b/drivers/gpio/gpio-virtuser.c @@ -1429,8 +1429,7 @@ gpio_virtuser_make_device_swnode(struct gpio_virtuser_device *dev) memset(properties, 0, sizeof(properties)); num_ids = list_count_nodes(&dev->lookup_list); - char **ids __free(kfree) = kcalloc(num_ids + 1, sizeof(*ids), - GFP_KERNEL); + char **ids __free(kfree) = kzalloc_objs(*ids, num_ids + 1); if (!ids) return ERR_PTR(-ENOMEM); diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 9f3b628d5793..d1105b7ae437 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2653,7 +2653,7 @@ static int gpio_chrdev_open(struct inode *inode, struct file *file) struct gpio_chardev_data *cdev; int ret = -ENOMEM; - cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + cdev = kzalloc_obj(*cdev); if (!cdev) return -ENOMEM; diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index ef8ccaf17c9c..66d2325bfae8 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1178,7 +1178,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, int base = 0; int ret; - gdev = kzalloc(sizeof(*gdev), GFP_KERNEL); + gdev = kzalloc_obj(*gdev); if (!gdev) return -ENOMEM; gc->gpiodev = gdev; @@ -1218,7 +1218,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data, goto err_put_device; gdev->ngpio = gc->ngpio; - gdev->descs = kcalloc(gc->ngpio, sizeof(*gdev->descs), GFP_KERNEL); + gdev->descs = kzalloc_objs(*gdev->descs, gc->ngpio); if (!gdev->descs) { ret = -ENOMEM; goto err_put_device; diff --git a/drivers/gpu/buddy.c b/drivers/gpu/buddy.c index a5553fcec28c..26e7a48b55f3 100644 --- a/drivers/gpu/buddy.c +++ b/drivers/gpu/buddy.c @@ -411,16 +411,13 @@ int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size) if (!mm->used_scoreboard) goto out_free_free_scoreboard; - mm->free_trees = kmalloc_array(GPU_BUDDY_MAX_FREE_TREES, - sizeof(*mm->free_trees), - GFP_KERNEL); + mm->free_trees = kmalloc_objs(*mm->free_trees, GPU_BUDDY_MAX_FREE_TREES); if (!mm->free_trees) goto out_free_used_scoreboard; for_each_free_tree(i) { - mm->free_trees[i] = kmalloc_array(mm->max_order + 1, - sizeof(struct rb_root), - GFP_KERNEL); + mm->free_trees[i] = kmalloc_objs(struct rb_root, + mm->max_order + 1); if (!mm->free_trees[i]) goto out_free_tree; @@ -430,9 +427,7 @@ int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size) mm->n_roots = hweight64(size); - mm->roots = kmalloc_array(mm->n_roots, - sizeof(struct gpu_buddy_block *), - GFP_KERNEL); + mm->roots = kmalloc_objs(struct gpu_buddy_block *, mm->n_roots); if (!mm->roots) goto out_free_tree; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c index 87e15e39eb30..6ff9ffe47f35 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dev_coredump.c @@ -603,9 +603,8 @@ void amdgpu_coredump(struct amdgpu_device *adev, bool skip_vram_check, ring_count++; } if (ring_count) - coredump->rings = kvcalloc(ring_count, - sizeof(struct amdgpu_coredump_ring), - GFP_NOWAIT); + coredump->rings = kvzalloc_objs(struct amdgpu_coredump_ring, + ring_count, GFP_NOWAIT); if (coredump->rings) { for (i = 0, idx = 0; i < adev->num_rings && idx < ring_count; i++) { struct amdgpu_coredump_ring *cdump_ring; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index 164e85b66e2d..62f5c5cbd7f9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -1608,11 +1608,11 @@ int amdgpu_discovery_sysfs_early_init(struct amdgpu_device *adev, struct pci_dev discovery_bin = adev->discovery.bin; - early_entry = kzalloc(sizeof(*early_entry), GFP_KERNEL); + early_entry = kzalloc_obj(*early_entry); if (!early_entry) return -ENOMEM; - ip_top = kzalloc(sizeof(*ip_top), GFP_KERNEL); + ip_top = kzalloc_obj(*ip_top); if (!ip_top) { kfree(early_entry); return -ENOMEM; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c index d5787d848d04..4a7f63fb2fad 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_lockdep.c @@ -98,7 +98,7 @@ int amdgpu_lockdep_init(void) struct amdgpu_lockdep_dummy_locks *locks; unsigned long flags; - locks = kzalloc(sizeof(*locks), GFP_KERNEL); + locks = kzalloc_obj(*locks); if (!locks) return -ENOMEM; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c index 95468b9463fb..78adfc839ef8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras_eeprom.c @@ -1293,7 +1293,7 @@ amdgpu_ras_debugfs_table_read_uniras(struct amdgpu_device *adev, return -ENOMEM; if (num_recs) { - records = kvcalloc(num_recs, sizeof(*records), GFP_KERNEL); + records = kvzalloc_objs(*records, num_recs); if (!records) { res = -ENOMEM; goto out; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index d5a419776e93..05abf4c31dce 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2361,9 +2361,8 @@ void amdgpu_ttm_enable_buffer_funcs(struct amdgpu_device *adev) num_clear_entities = MIN(adev->mman.num_buffer_funcs_scheds, TTM_NUM_MOVE_FENCES); num_move_entities = MIN(adev->mman.num_buffer_funcs_scheds, TTM_NUM_MOVE_FENCES); - adev->mman.clear_entities = kcalloc(num_clear_entities, - sizeof(struct amdgpu_ttm_buffer_entity), - GFP_KERNEL); + adev->mman.clear_entities = kzalloc_objs(struct amdgpu_ttm_buffer_entity, + num_clear_entities); atomic_set(&adev->mman.next_clear_entity, 0); if (!adev->mman.clear_entities) goto error_free_default_entity; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c index a98a6cfd4fba..71d34fd09385 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c @@ -159,8 +159,8 @@ void amdgpu_umc_handle_bad_pages(struct amdgpu_device *adev, adev->umc.ras->ecc_info_query_ras_error_address && adev->umc.max_ras_err_cnt_per_query) { err_data->err_addr = - kcalloc(adev->umc.max_ras_err_cnt_per_query, - sizeof(struct eeprom_table_record), GFP_KERNEL); + kzalloc_objs(struct eeprom_table_record, + adev->umc.max_ras_err_cnt_per_query); /* still call query_ras_error_address to clear error status * even NOMEM error is encountered diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index 4b023e024d9f..743b41db5b7c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -227,7 +227,7 @@ static int amdgpu_userq_fence_alloc(struct amdgpu_usermode_queue *userq, struct amdgpu_userq_fence *userq_fence; void *entry; - userq_fence = kmalloc(sizeof(*userq_fence), GFP_KERNEL); + userq_fence = kmalloc_obj(*userq_fence); if (!userq_fence) return -ENOMEM; @@ -244,9 +244,7 @@ static int amdgpu_userq_fence_alloc(struct amdgpu_usermode_queue *userq, } while (xas_retry(&xas, entry)); rcu_read_unlock(); - userq_fence->fence_drv_array = kvmalloc_array(xas.xa_index, - sizeof(fence_drv), - GFP_KERNEL); + userq_fence->fence_drv_array = kvmalloc_objs(fence_drv, xas.xa_index); if (!userq_fence->fence_drv_array) { mutex_unlock(&userq->fence_drv_lock); kfree(userq_fence); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c index b43fc643668d..7a007f4916bc 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_virt.c @@ -316,8 +316,8 @@ static int amdgpu_virt_ras_realloc_eh_data_space(struct amdgpu_device *adev, if (align_space > AMDGPU_VIRT_RAS_BAD_PAGE_TABLE_MAX_CAPACITY) return -ENOMEM; - new_bps = kmalloc_array(align_space, sizeof(*data->bps), GFP_KERNEL); - new_bo = kcalloc(align_space, sizeof(*data->bps_bo), GFP_KERNEL); + new_bps = kmalloc_objs(*data->bps, align_space); + new_bo = kzalloc_objs(*data->bps_bo, align_space); if (!new_bps || !new_bo) { kfree(new_bps); kfree(new_bo); @@ -355,7 +355,7 @@ static int amdgpu_virt_init_ras_err_handler_data(struct amdgpu_device *adev) if (!bps) goto bps_failure; - bps_bo = kcalloc(align_space, sizeof(*(*data)->bps_bo), GFP_KERNEL); + bps_bo = kzalloc_objs(*(*data)->bps_bo, align_space); if (!bps_bo) goto bps_bo_failure; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 7fcfc150a7fc..504a286368eb 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -1921,13 +1921,13 @@ static int criu_checkpoint_devices(struct kfd_process *p, struct kfd_criu_device_bucket *device_buckets = NULL; int ret = 0, i; - device_buckets = kvcalloc(num_devices, sizeof(*device_buckets), GFP_KERNEL); + device_buckets = kvzalloc_objs(*device_buckets, num_devices); if (!device_buckets) { ret = -ENOMEM; goto exit; } - device_priv = kvcalloc(num_devices, sizeof(*device_priv), GFP_KERNEL); + device_priv = kvzalloc_objs(*device_priv, num_devices); if (!device_priv) { ret = -ENOMEM; goto exit; @@ -2047,17 +2047,17 @@ static int criu_checkpoint_bos(struct kfd_process *p, int ret = 0, pdd_index, bo_index = 0, id; void *mem; - bo_buckets = kvcalloc(num_bos, sizeof(*bo_buckets), GFP_KERNEL); + bo_buckets = kvzalloc_objs(*bo_buckets, num_bos); if (!bo_buckets) return -ENOMEM; - bo_privs = kvcalloc(num_bos, sizeof(*bo_privs), GFP_KERNEL); + bo_privs = kvzalloc_objs(*bo_privs, num_bos); if (!bo_privs) { ret = -ENOMEM; goto exit; } - files = kvcalloc(num_bos, sizeof(struct file *), GFP_KERNEL); + files = kvzalloc_objs(struct file *, num_bos); if (!files) { ret = -ENOMEM; goto exit; @@ -2588,7 +2588,7 @@ static int criu_restore_bos(struct kfd_process *p, if (!bo_buckets) return -ENOMEM; - files = kvcalloc(args->num_bos, sizeof(struct file *), GFP_KERNEL); + files = kvzalloc_objs(struct file *, args->num_bos); if (!files) { ret = -ENOMEM; goto exit; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index a54fd9529dc9..0d2f9dbce0a9 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -4180,7 +4180,7 @@ dm_test_destroy_connector(struct kunit *test, struct drm_device *drm) { struct amdgpu_dm_connector *aconnector; - aconnector = kzalloc(sizeof(*aconnector), GFP_KERNEL); + aconnector = kzalloc_obj(*aconnector); KUNIT_ASSERT_NOT_NULL(test, aconnector); KUNIT_ASSERT_EQ(test, diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c index 4dacddd23878..0d998f204250 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c @@ -1445,7 +1445,7 @@ static void dm_test_crtc_destroy_state_no_stream(struct kunit *test) struct dm_crtc_state *dm_state; /* destroy_state kfree()s the state, so use a plain (unmanaged) alloc. */ - dm_state = kzalloc_obj(*dm_state, GFP_KERNEL); + dm_state = kzalloc_obj(*dm_state); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state); amdgpu_dm_crtc_destroy_state(NULL, &dm_state->base); @@ -1475,7 +1475,7 @@ static void dm_test_crtc_destroy_state_releases_stream(struct kunit *test) kref_get(&stream->refcount); /* destroy_state kfree()s the state, so use a plain (unmanaged) alloc. */ - dm_state = kzalloc_obj(*dm_state, GFP_KERNEL); + dm_state = kzalloc_obj(*dm_state); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dm_state); dm_state->stream = stream; @@ -1569,7 +1569,7 @@ static void dm_test_crtc_handle_vblank_completes_cursor_only(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, acrtc); /* drm_crtc_send_vblank_event() consumes (kfree()s) the event. */ - event = kzalloc_obj(*event, GFP_KERNEL); + event = kzalloc_obj(*event); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, event); acrtc->base.dev = &adev->ddev; @@ -1628,7 +1628,7 @@ dm_test_vblank_control_worker_setup(struct kunit *test, bool enable, kref_get(&stream->refcount); /* Worker kfree()s the work item, so it must be a plain allocation. */ - work = kzalloc_obj(*work, GFP_KERNEL); + work = kzalloc_obj(*work); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, work); work->dm = &adev->dm; work->acrtc = acrtc; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index 28c36217f6a2..861ee9eaa032 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -103,7 +103,7 @@ static enum dc_status dm_test_dp_read_hpd_rx_irq_data_ok(struct dc_link *link, */ static struct dc_sink *dm_test_sink_create(struct dc_link *link) { - struct dc_sink *sink = kzalloc(sizeof(*sink), GFP_KERNEL); + struct dc_sink *sink = kzalloc_obj(*sink); if (!sink) return NULL; @@ -2131,7 +2131,7 @@ static void dm_test_hpd_rx_offload_work_no_connector(struct kunit *test) offload_wq = kunit_kzalloc(test, sizeof(*offload_wq), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_wq); - offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + offload_work = kzalloc_obj(*offload_work); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); offload_work->offload_wq = offload_wq; offload_work->adev = adev; @@ -2177,7 +2177,7 @@ static void dm_test_hpd_rx_offload_work_no_connection(struct kunit *test) link->dc = dc; aconn->dc_link = link; - offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + offload_work = kzalloc_obj(*offload_work); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); offload_work->offload_wq = offload_wq; offload_work->adev = adev; @@ -2244,7 +2244,7 @@ static void dm_test_hpd_rx_offload_work_automated_test(struct kunit *test) link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; aconn->dc_link = link; - offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + offload_work = kzalloc_obj(*offload_work); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); offload_work->offload_wq = offload_wq; offload_work->adev = adev; @@ -2315,7 +2315,7 @@ static void dm_test_hpd_rx_offload_work_link_loss(struct kunit *test) link->connector_signal = SIGNAL_TYPE_DISPLAY_PORT; aconn->dc_link = link; - offload_work = kzalloc(sizeof(*offload_work), GFP_KERNEL); + offload_work = kzalloc_obj(*offload_work); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, offload_work); offload_work->offload_wq = offload_wq; offload_work->adev = adev; @@ -3452,9 +3452,9 @@ static void dm_test_handle_hpd_work_out_of_range(struct kunit *test) struct amdgpu_device *adev; adev = dm_kunit_alloc_adev(test); - hpd_work = kzalloc(sizeof(*hpd_work), GFP_KERNEL); + hpd_work = kzalloc_obj(*hpd_work); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hpd_work); - hpd_work->dmub_notify = kzalloc(sizeof(*hpd_work->dmub_notify), GFP_KERNEL); + hpd_work->dmub_notify = kzalloc_obj(*hpd_work->dmub_notify); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hpd_work->dmub_notify); hpd_work->dmub_notify->type = (enum dmub_notification_type)ARRAY_SIZE(adev->dm.dmub_callback); @@ -3899,9 +3899,9 @@ static void dm_test_handle_vmin_vmax_update(struct kunit *test) kref_get(&stream->refcount); /* The worker kfree()s both, so they must come from the slab. */ - work = kzalloc(sizeof(*work), GFP_KERNEL); + work = kzalloc_obj(*work); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, work); - adjust = kzalloc(sizeof(*adjust), GFP_KERNEL); + adjust = kzalloc_obj(*adjust); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adjust); work->adev = adev; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c index ba97092c7bb8..23ac8ae41c68 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_plane_test.c @@ -2945,7 +2945,7 @@ static void dm_test_plane_reset_initializes_state(struct kunit *test) * destroy-existing-state path. The destroy hook frees this state, so it * must be a plain (non-KUnit-managed) allocation. */ - old_state = kzalloc(sizeof(*old_state), GFP_KERNEL); + old_state = kzalloc_obj(*old_state); KUNIT_ASSERT_NOT_NULL(test, old_state); plane->funcs = &dm_test_plane_reset_funcs; plane->state = &old_state->base; @@ -3018,7 +3018,7 @@ static void dm_test_plane_destroy_state_minimal(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, plane); /* destroy_state frees the state itself, so use a plain allocation. */ - dm_plane_state = kzalloc(sizeof(*dm_plane_state), GFP_KERNEL); + dm_plane_state = kzalloc_obj(*dm_plane_state); KUNIT_ASSERT_NOT_NULL(test, dm_plane_state); amdgpu_dm_plane_drm_plane_destroy_state(plane, &dm_plane_state->base); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c index 3c10eec9b1e0..5645866610fc 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_test.c @@ -239,7 +239,7 @@ static void dm_test_atomic_destroy_state_no_context(struct kunit *test) * Use kzalloc(), not kunit_kzalloc(): dm_atomic_destroy_state() frees * the state itself, so KUnit-managed memory would be double-freed. */ - dm_state = kzalloc(sizeof(*dm_state), GFP_KERNEL); + dm_state = kzalloc_obj(*dm_state); KUNIT_ASSERT_NOT_NULL(test, dm_state); /* context == NULL: dc_state_release() is skipped and the state is freed. */ diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/clk_mgr.c index e2148cb3b8ab..bf80eaf23e9a 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/clk_mgr.c @@ -377,7 +377,7 @@ struct clk_mgr *dc_clk_mgr_create(struct dc_context *ctx, struct pp_smu_funcs *p } break; case AMDGPU_FAMILY_GC_11_5_4: { - struct clk_mgr_dcn42 *clk_mgr = kzalloc(sizeof(*clk_mgr), GFP_KERNEL); + struct clk_mgr_dcn42 *clk_mgr = kzalloc_obj(*clk_mgr); if (clk_mgr == NULL) { BREAK_TO_DEBUGGER(); diff --git a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn60/dcn60_clk_mgr.c b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn60/dcn60_clk_mgr.c index d71b0aed90c9..7dd88d90eba2 100644 --- a/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn60/dcn60_clk_mgr.c +++ b/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn60/dcn60_clk_mgr.c @@ -1595,7 +1595,7 @@ struct clk_mgr_internal *dcn60_clk_mgr_construct( struct dccg *dccg) { struct clk_log_info log_info = {0}; - struct dcn60_clk_mgr *clk_mgr60 = kzalloc(sizeof(struct dcn60_clk_mgr), GFP_KERNEL); + struct dcn60_clk_mgr *clk_mgr60 = kzalloc_obj(struct dcn60_clk_mgr); struct clk_mgr_internal *clk_mgr; if (!clk_mgr60) @@ -1644,7 +1644,7 @@ struct clk_mgr_internal *dcn60_clk_mgr_construct( clk_mgr->smu_present = false; - clk_mgr->base.bw_params = kzalloc(sizeof(*clk_mgr->base.bw_params), GFP_KERNEL); + clk_mgr->base.bw_params = kzalloc_obj(*clk_mgr->base.bw_params); if (!clk_mgr->base.bw_params) goto fail; diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 68f228014305..a98ed4617a03 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -914,8 +914,7 @@ static bool dc_construct_update_scratch_pool(struct dc *dc) unsigned int i; for (i = 0; i < ARRAY_SIZE(dc->update_scratch_pool); i++) { - dc->update_scratch_pool[i] = kzalloc( - sizeof(struct dc_update_scratch_space), GFP_KERNEL); + dc->update_scratch_pool[i] = kzalloc_obj(struct dc_update_scratch_space); if (!dc->update_scratch_pool[i]) return false; dc->update_scratch_in_use[i] = false; diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_surface.c b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c index 88e825a6582c..0b135a9e6c4f 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_surface.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_surface.c @@ -290,7 +290,7 @@ static void dc_plane_cm_free(struct kref *kref) struct dc_plane_cm *dc_plane_cm_create(void) { - struct dc_plane_cm *cm = kvzalloc(sizeof(*cm), GFP_KERNEL); + struct dc_plane_cm *cm = kvzalloc_obj(*cm); if (cm == NULL) goto alloc_fail; diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c index 3b03b152da22..c2fa7fd56acf 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn42/dcn42_dccg.c @@ -358,7 +358,7 @@ struct dccg *dccg42_create( const struct dccg_shift *dccg_shift, const struct dccg_mask *dccg_mask) { - struct dcn_dccg *dccg_dcn = kzalloc(sizeof(*dccg_dcn), GFP_KERNEL); + struct dcn_dccg *dccg_dcn = kzalloc_obj(*dccg_dcn); struct dccg *base; if (dccg_dcn == NULL) { diff --git a/drivers/gpu/drm/amd/display/dc/dccg/dcn60/dcn60_dccg.c b/drivers/gpu/drm/amd/display/dc/dccg/dcn60/dcn60_dccg.c index 8e2f88913e4c..1d6d193b23c7 100644 --- a/drivers/gpu/drm/amd/display/dc/dccg/dcn60/dcn60_dccg.c +++ b/drivers/gpu/drm/amd/display/dc/dccg/dcn60/dcn60_dccg.c @@ -167,7 +167,7 @@ struct dccg *dccg60_create( const struct dccg_shift *dccg_shift, const struct dccg_mask *dccg_mask) { - struct dcn_dccg *dccg_dcn = kzalloc(sizeof(*dccg_dcn), GFP_KERNEL); + struct dcn_dccg *dccg_dcn = kzalloc_obj(*dccg_dcn); struct dccg *base; if (dccg_dcn == NULL) { diff --git a/drivers/gpu/drm/amd/display/dc/gpio/hw_ddc.c b/drivers/gpu/drm/amd/display/dc/gpio/hw_ddc.c index b75bfea635fd..cf9c7a2089a1 100644 --- a/drivers/gpu/drm/amd/display/dc/gpio/hw_ddc.c +++ b/drivers/gpu/drm/amd/display/dc/gpio/hw_ddc.c @@ -542,7 +542,7 @@ void dal_hw_ddc_init_i3cpad( *hw_ddc = NULL; } - *hw_ddc = kzalloc(sizeof(struct hw_ddc), GFP_KERNEL); + *hw_ddc = kzalloc_obj(struct hw_ddc); if (!*hw_ddc) { ASSERT_CRITICAL(false); return; diff --git a/drivers/gpu/drm/amd/display/dc/irq/dcn42/irq_service_dcn42.c b/drivers/gpu/drm/amd/display/dc/irq/dcn42/irq_service_dcn42.c index f4d1ce9079de..3a87c1a26488 100644 --- a/drivers/gpu/drm/amd/display/dc/irq/dcn42/irq_service_dcn42.c +++ b/drivers/gpu/drm/amd/display/dc/irq/dcn42/irq_service_dcn42.c @@ -405,7 +405,7 @@ static void dcn42_irq_construct(struct irq_service *irq_service, struct irq_service *dal_irq_service_dcn42_create(struct irq_service_init_data *init_data) { - struct irq_service *irq_service = kzalloc(sizeof(*irq_service), GFP_KERNEL); + struct irq_service *irq_service = kzalloc_obj(*irq_service); if (!irq_service) return NULL; diff --git a/drivers/gpu/drm/amd/display/dc/irq/dcn60/irq_service_dcn60.c b/drivers/gpu/drm/amd/display/dc/irq/dcn60/irq_service_dcn60.c index 99163346e7d9..651bae6b5ede 100644 --- a/drivers/gpu/drm/amd/display/dc/irq/dcn60/irq_service_dcn60.c +++ b/drivers/gpu/drm/amd/display/dc/irq/dcn60/irq_service_dcn60.c @@ -406,8 +406,7 @@ static void dcn60_irq_construct( struct irq_service *dal_irq_service_dcn60_create( struct irq_service_init_data *init_data) { - struct irq_service *irq_service = kzalloc(sizeof(*irq_service), - GFP_KERNEL); + struct irq_service *irq_service = kzalloc_obj(*irq_service); if (!irq_service) return NULL; diff --git a/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c b/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c index 78b33b2dbae8..2c87e33e909e 100644 --- a/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c +++ b/drivers/gpu/drm/amd/display/dc/pg/dcn42/dcn42_pg_cntl.c @@ -615,7 +615,7 @@ struct pg_cntl *pg_cntl42_create( const struct pg_cntl_shift *pg_cntl_shift, const struct pg_cntl_mask *pg_cntl_mask) { - struct dcn_pg_cntl *pg_cntl_dcn = kzalloc(sizeof(*pg_cntl_dcn), GFP_KERNEL); + struct dcn_pg_cntl *pg_cntl_dcn = kzalloc_obj(*pg_cntl_dcn); struct pg_cntl *base; if (pg_cntl_dcn == NULL) { diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c index dbb8bb7fc20d..aecbcd28c2d9 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn30/dcn30_resource.c @@ -1135,7 +1135,7 @@ static struct hpo_frl_stream_encoder *dcn30_hpo_frl_stream_encoder_create(enum e } /* allocate HPO stream encoder and create VPG sub-block */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_stream_encoder); vpg = dcn30_vpg_create(ctx, vpg_inst); afmt = dcn30_afmt_create(ctx, afmt_inst); @@ -1166,7 +1166,7 @@ static struct hpo_frl_link_encoder *dcn30_hpo_frl_link_encoder_create(enum engin ASSERT((eng_id == ENGINE_ID_HPO_0) || (eng_id == ENGINE_ID_HPO_1)); /* allocate HPO link encoder */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_enc3) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c index 88dcf8166378..f67b3e00bd86 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn302/dcn302_resource.c @@ -492,7 +492,7 @@ static struct hpo_frl_stream_encoder *dcn302_hpo_frl_stream_encoder_create(enum return NULL; /* allocate HPO stream encoder and create VPG sub-block */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_stream_encoder); vpg = dcn302_vpg_create(ctx, vpg_inst); afmt = dcn302_afmt_create(ctx, afmt_inst); @@ -531,7 +531,7 @@ static struct hpo_frl_link_encoder *dcn302_hpo_frl_link_encoder_create(enum engi ASSERT((eng_id == ENGINE_ID_HPO_0) || (eng_id == ENGINE_ID_HPO_1)); /* allocate HPO link encoder */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_enc3) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c index 50b7c7b85fac..9e1bf34cca2a 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn303/dcn303_resource.c @@ -479,7 +479,7 @@ static struct hpo_frl_stream_encoder *dcn303_hpo_frl_stream_encoder_create(enum return NULL; /* allocate HPO stream encoder and create VPG sub-block */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_stream_encoder); vpg = dcn303_vpg_create(ctx, vpg_inst); afmt = dcn303_afmt_create(ctx, afmt_inst); @@ -518,7 +518,7 @@ static struct hpo_frl_link_encoder *dcn303_hpo_frl_link_encoder_create(enum engi ASSERT((eng_id == ENGINE_ID_HPO_0) || (eng_id == ENGINE_ID_HPO_1)); /* allocate HPO link encoder */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_enc3) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c index db56e30cf259..6359fbb574df 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn31/dcn31_resource.c @@ -1346,7 +1346,7 @@ static struct hpo_frl_stream_encoder *dcn31_hpo_frl_stream_encoder_create(enum e } /* allocate HPO stream encoder and create VPG, AFMT sub-blocks */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_stream_encoder); vpg = dcn31_vpg_create(ctx, vpg_inst); afmt = dcn31_afmt_create(ctx, afmt_inst); @@ -1377,7 +1377,7 @@ static struct hpo_frl_link_encoder *dcn31_hpo_frl_link_encoder_create(enum engin ASSERT((eng_id == ENGINE_ID_HPO_0) || (eng_id == ENGINE_ID_HPO_1)); /* allocate HPO link encoder */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_enc3) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c index 63f92e9da6d8..0ddfceca69dd 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn314/dcn314_resource.c @@ -1404,7 +1404,7 @@ static struct hpo_frl_stream_encoder *dcn31_hpo_frl_stream_encoder_create(enum e } /* allocate HPO stream encoder and create VPG, AFMT sub-blocks */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_stream_encoder); vpg = dcn31_vpg_create(ctx, vpg_inst); afmt = dcn31_afmt_create(ctx, afmt_inst); @@ -1436,7 +1436,7 @@ static struct hpo_frl_link_encoder *dcn31_hpo_frl_link_encoder_create(enum engin ASSERT((eng_id == ENGINE_ID_HPO_0) || (eng_id == ENGINE_ID_HPO_1)); /* allocate HPO link encoder */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_enc3) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c index 15c1615c5f45..a1894be8cbb2 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn315/dcn315_resource.c @@ -1347,7 +1347,7 @@ static struct hpo_frl_stream_encoder *dcn31_hpo_frl_stream_encoder_create(enum e } /* allocate HPO stream encoder and create VPG, AFMT sub-blocks */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_stream_encoder); vpg = dcn31_vpg_create(ctx, vpg_inst); afmt = dcn31_afmt_create(ctx, afmt_inst); @@ -1379,7 +1379,7 @@ static struct hpo_frl_link_encoder *dcn31_hpo_frl_link_encoder_create(enum engin ASSERT((eng_id == ENGINE_ID_HPO_0) || (eng_id == ENGINE_ID_HPO_1)); /* allocate HPO link encoder */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_enc3) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c index a5ea35e45791..4a2d56a196b4 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn316/dcn316_resource.c @@ -1340,7 +1340,7 @@ static struct hpo_frl_stream_encoder *dcn31_hpo_frl_stream_encoder_create(enum e } /* allocate HPO stream encoder and create VPG, AFMT sub-blocks */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_stream_encoder); vpg = dcn31_vpg_create(ctx, vpg_inst); afmt = dcn31_afmt_create(ctx, afmt_inst); @@ -1372,7 +1372,7 @@ static struct hpo_frl_link_encoder *dcn31_hpo_frl_link_encoder_create(enum engin ASSERT((eng_id == ENGINE_ID_HPO_0) || (eng_id == ENGINE_ID_HPO_1)); /* allocate HPO link encoder */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_enc3) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c index 7c2a79015f4e..a2e66761b8ec 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c @@ -1339,7 +1339,7 @@ static struct hpo_frl_stream_encoder *dcn32_hpo_frl_stream_encoder_create(enum e } /* allocate HPO stream encoder and create VPG, AFMT sub-blocks */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_stream_encoder); vpg = dcn32_vpg_create(ctx, vpg_inst); afmt = dcn32_afmt_create(ctx, afmt_inst); @@ -1375,7 +1375,7 @@ static struct hpo_frl_link_encoder *dcn32_hpo_frl_link_encoder_create(enum engin hpo_frl_link_encoder_reg_list(0); /* allocate HPO link encoder */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_enc3) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c index 5ee9a5a8ec3c..4097093d9012 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c @@ -1314,7 +1314,7 @@ static struct hpo_frl_stream_encoder *dcn321_hpo_frl_stream_encoder_create(enum } /* allocate HPO stream encoder and create VPG, AFMT sub-blocks */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_stream_encoder); vpg = dcn321_vpg_create(ctx, vpg_inst); afmt = dcn321_afmt_create(ctx, afmt_inst); @@ -1350,7 +1350,7 @@ static struct hpo_frl_link_encoder *dcn321_hpo_frl_link_encoder_create(enum engi hpo_frl_link_encoder_reg_list(0); /* allocate HPO link encoder */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_enc3) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c index 52826e96c184..fd7a22fcca59 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn35/dcn35_resource.c @@ -1398,7 +1398,7 @@ static struct hpo_frl_stream_encoder *dcn31_hpo_frl_stream_encoder_create( return NULL; /* allocate HPO stream encoder and create VPG, AFMT sub-blocks */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_stream_encoder); vpg = dcn31_vpg_create(ctx, vpg_inst); afmt = dcn31_afmt_create(ctx, afmt_inst); @@ -1430,7 +1430,7 @@ static struct hpo_frl_link_encoder *dcn31_hpo_frl_link_encoder_create( hpo_frl_link_encoder_reg_list(0); /* allocate HPO link encoder */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_enc3) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c index e3fc71307c91..83248ee01d11 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn351/dcn351_resource.c @@ -1378,7 +1378,7 @@ static struct hpo_frl_stream_encoder *dcn31_hpo_frl_stream_encoder_create( return NULL; /* allocate HPO stream encoder and create VPG, AFMT sub-blocks */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_stream_encoder); vpg = dcn31_vpg_create(ctx, vpg_inst); afmt = dcn31_afmt_create(ctx, afmt_inst); @@ -1410,7 +1410,7 @@ static struct hpo_frl_link_encoder *dcn31_hpo_frl_link_encoder_create( hpo_frl_link_encoder_reg_list(0); /* allocate HPO link encoder */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_enc3) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c index c019a657005d..8d1baa76c347 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn36/dcn36_resource.c @@ -1385,7 +1385,7 @@ static struct hpo_frl_stream_encoder *dcn31_hpo_frl_stream_encoder_create( return NULL; /* allocate HPO stream encoder and create VPG, AFMT sub-blocks */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_stream_encoder); vpg = dcn31_vpg_create(ctx, vpg_inst); afmt = dcn31_afmt_create(ctx, afmt_inst); @@ -1417,7 +1417,7 @@ static struct hpo_frl_link_encoder *dcn31_hpo_frl_link_encoder_create( hpo_frl_link_encoder_reg_list(0); /* allocate HPO link encoder */ - hpo_enc3 = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_enc3 = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_enc3) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c index aced8ff657bf..0cfdca82bef0 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c @@ -1322,7 +1322,7 @@ static struct hpo_frl_stream_encoder *dcn401_hpo_frl_stream_encoder_create( return NULL; /* allocate HPO stream encoder and create VPG, AFMT sub-blocks */ - hpo_enc401 = kzalloc(sizeof(struct dcn401_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc401 = kzalloc_obj(struct dcn401_hpo_frl_stream_encoder); vpg = dcn401_vpg_create(ctx, vpg_inst); afmt = dcn401_afmt_create(ctx, afmt_inst); @@ -1353,7 +1353,7 @@ static struct hpo_frl_link_encoder *dcn401_hpo_frl_link_encoder_create( hpo_frl_link_encoder_reg_list(0); /* allocate HPO link encoder */ - hpo_link_enc = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_link_enc = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_link_enc) return NULL; /* out of memory */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c index b93d608b64a9..28192d6dda7a 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42/dcn42_resource.c @@ -818,7 +818,7 @@ static struct dce_aux *dcn42_aux_engine_create( uint32_t inst) { struct aux_engine_dce110 *aux_engine = - kzalloc(sizeof(struct aux_engine_dce110), GFP_KERNEL); + kzalloc_obj(struct aux_engine_dce110); if (!aux_engine) return NULL; @@ -884,7 +884,7 @@ static struct dce_i2c_hw *dcn42_i2c_hw_create( uint32_t inst) { struct dce_i2c_hw *dce_i2c_hw = - kzalloc(sizeof(struct dce_i2c_hw), GFP_KERNEL); + kzalloc_obj(struct dce_i2c_hw); if (!dce_i2c_hw) return NULL; @@ -910,7 +910,7 @@ static struct clock_source *dcn42_clock_source_create( bool dp_clk_src) { struct dce110_clk_src *clk_src = - kzalloc(sizeof(struct dce110_clk_src), GFP_KERNEL); + kzalloc_obj(struct dce110_clk_src); if (!clk_src) return NULL; @@ -929,8 +929,7 @@ static struct hubbub *dcn42_hubbub_create(struct dc_context *ctx) { int i; - struct dcn20_hubbub *hubbub3 = kzalloc(sizeof(struct dcn20_hubbub), - GFP_KERNEL); + struct dcn20_hubbub *hubbub3 = kzalloc_obj(struct dcn20_hubbub); if (!hubbub3) return NULL; @@ -983,7 +982,7 @@ static struct hubp *dcn42_hubp_create( uint32_t inst) { struct dcn20_hubp *hubp2 = - kzalloc(sizeof(struct dcn20_hubp), GFP_KERNEL); + kzalloc_obj(struct dcn20_hubp); if (!hubp2) return NULL; @@ -1025,7 +1024,7 @@ static struct dpp *dcn42_dpp_create( uint32_t inst) { struct dcn42_dpp *dpp42 = - kzalloc(sizeof(struct dcn42_dpp), GFP_KERNEL); + kzalloc_obj(struct dcn42_dpp); if (!dpp42) return NULL; @@ -1051,8 +1050,7 @@ static struct mpc *dcn42_mpc_create( int num_mpcc, int num_rmu) { - struct dcn42_mpc *mpc401 = kzalloc(sizeof(struct dcn42_mpc), - GFP_KERNEL); + struct dcn42_mpc *mpc401 = kzalloc_obj(struct dcn42_mpc); if (!mpc401) return NULL; @@ -1075,7 +1073,7 @@ static struct output_pixel_processor *dcn42_opp_create( struct dc_context *ctx, uint32_t inst) { struct dcn20_opp *opp4 = - kzalloc(sizeof(struct dcn20_opp), GFP_KERNEL); + kzalloc_obj(struct dcn20_opp); if (!opp4) { BREAK_TO_DEBUGGER(); @@ -1098,7 +1096,7 @@ static struct timing_generator *dcn42_timing_generator_create( uint32_t instance) { struct optc *tgn10 = - kzalloc(sizeof(struct optc), GFP_KERNEL); + kzalloc_obj(struct optc); if (!tgn10) return NULL; @@ -1136,7 +1134,7 @@ static struct link_encoder *dcn42_link_encoder_create( const struct encoder_init_data *enc_init_data) { struct dcn20_link_encoder *enc20 = - kzalloc(sizeof(struct dcn20_link_encoder), GFP_KERNEL); + kzalloc_obj(struct dcn20_link_encoder); if (!enc20 || enc_init_data->hpd_source >= ARRAY_SIZE(link_enc_hpd_regs)) return NULL; @@ -1202,7 +1200,7 @@ static struct vpg *dcn42_vpg_create( struct dc_context *ctx, uint32_t inst) { - struct dcn31_vpg *vpg4 = kzalloc(sizeof(struct dcn31_vpg), GFP_KERNEL); + struct dcn31_vpg *vpg4 = kzalloc_obj(struct dcn31_vpg); if (!vpg4) return NULL; @@ -1231,7 +1229,7 @@ static struct apg *dcn42_apg_create( struct dc_context *ctx, uint32_t inst) { - struct dcn31_apg *apg31 = kzalloc(sizeof(struct dcn31_apg), GFP_KERNEL); + struct dcn31_apg *apg31 = kzalloc_obj(struct dcn31_apg); if (!apg31) return NULL; @@ -1275,7 +1273,7 @@ static struct stream_encoder *dcn42_stream_encoder_create( } else return NULL; - enc1 = kzalloc(sizeof(struct dcn10_stream_encoder), GFP_KERNEL); + enc1 = kzalloc_obj(struct dcn10_stream_encoder); vpg = dcn42_vpg_create(ctx, vpg_inst); apg = dcn42_apg_create(ctx, apg_inst); @@ -1325,7 +1323,7 @@ static struct hpo_frl_stream_encoder *dcn42_hpo_frl_stream_encoder_create( return NULL; /* allocate HPO stream encoder and create VPG sub-block */ - hpo_enc42 = kzalloc(sizeof(struct dcn42_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc42 = kzalloc_obj(struct dcn42_hpo_frl_stream_encoder); vpg = dcn42_vpg_create(ctx, vpg_inst); apg = dcn42_apg_create(ctx, apg_inst); @@ -1357,7 +1355,7 @@ static struct hpo_frl_link_encoder *dcn42_hpo_frl_link_encoder_create( hpo_frl_link_encoder_reg_list(0); /* allocate HPO link encoder */ - hpo_link_enc = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_link_enc = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_link_enc) return NULL; /* out of memory */ @@ -1399,7 +1397,7 @@ static struct hpo_dp_stream_encoder *dcn42_hpo_dp_stream_encoder_create( apg_inst = hpo_dp_inst + 5; /* allocate HPO stream encoder and create VPG sub-block */ - hpo_dp_enc31 = kzalloc(sizeof(struct dcn31_hpo_dp_stream_encoder), GFP_KERNEL); + hpo_dp_enc31 = kzalloc_obj(struct dcn31_hpo_dp_stream_encoder); vpg = dcn42_vpg_create(ctx, vpg_inst); apg = dcn42_apg_create(ctx, apg_inst); @@ -1432,7 +1430,7 @@ static struct hpo_dp_link_encoder *dcn42_hpo_dp_link_encoder_create( struct dcn31_hpo_dp_link_encoder *hpo_dp_enc31; /* allocate HPO link encoder */ - hpo_dp_enc31 = kzalloc(sizeof(struct dcn31_hpo_dp_link_encoder), GFP_KERNEL); + hpo_dp_enc31 = kzalloc_obj(struct dcn31_hpo_dp_link_encoder); if (!hpo_dp_enc31) return NULL; /* out of memory */ @@ -1453,7 +1451,7 @@ static struct hpo_dp_link_encoder *dcn42_hpo_dp_link_encoder_create( static struct dce_hwseq *dcn42_hwseq_create( struct dc_context *ctx) { - struct dce_hwseq *hws = kzalloc(sizeof(struct dce_hwseq), GFP_KERNEL); + struct dce_hwseq *hws = kzalloc_obj(struct dce_hwseq); #undef REG_STRUCT #define REG_STRUCT hwseq_reg @@ -1727,8 +1725,7 @@ static bool dcn42_dwbc_create(struct dc_context *ctx, struct resource_pool *pool uint32_t dwb_count = pool->res_cap->num_dwb; for (i = 0; i < dwb_count; i++) { - struct dcn30_dwbc *dwbc42 = kzalloc(sizeof(struct dcn30_dwbc), - GFP_KERNEL); + struct dcn30_dwbc *dwbc42 = kzalloc_obj(struct dcn30_dwbc); if (!dwbc42) { dm_error("DC: failed to create dwbc42!\n"); @@ -1764,8 +1761,7 @@ static bool dcn42_mmhubbub_create(struct dc_context *ctx, struct resource_pool * uint32_t pipe_count = pool->res_cap->num_dwb; for (i = 0; i < pipe_count; i++) { - struct dcn30_mmhubbub *mcif_wb30 = kzalloc(sizeof(struct dcn30_mmhubbub), - GFP_KERNEL); + struct dcn30_mmhubbub *mcif_wb30 = kzalloc_obj(struct dcn30_mmhubbub); if (!mcif_wb30) { dm_error("DC: failed to create mcif_wb30!\n"); @@ -1793,7 +1789,7 @@ static struct display_stream_compressor *dcn42_dsc_create( struct dc_context *ctx, uint32_t inst) { struct dcn401_dsc *dsc = - kzalloc(sizeof(struct dcn401_dsc), GFP_KERNEL); + kzalloc_obj(struct dcn401_dsc); if (!dsc) { BREAK_TO_DEBUGGER(); @@ -1890,7 +1886,7 @@ static struct link_encoder *dcn42_link_enc_create_minimal( if ((unsigned int)(eng_id - ENGINE_ID_DIGA) >= ctx->dc->res_pool->res_cap->num_dig_link_enc) return NULL; - enc20 = kzalloc(sizeof(struct dcn20_link_encoder), GFP_KERNEL); + enc20 = kzalloc_obj(struct dcn20_link_encoder); if (!enc20) return NULL; @@ -2441,7 +2437,7 @@ struct resource_pool *dcn42_create_resource_pool( struct dc *dc) { struct dcn42_resource_pool *pool = - kzalloc(sizeof(struct dcn42_resource_pool), GFP_KERNEL); + kzalloc_obj(struct dcn42_resource_pool); if (!pool) return NULL; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c index 41f0c67f57ff..f36d0f828166 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn42b/dcn42b_resource.c @@ -870,7 +870,7 @@ static struct dce_aux *dcn42b_aux_engine_create( uint32_t inst) { struct aux_engine_dce110 *aux_engine = - kzalloc(sizeof(struct aux_engine_dce110), GFP_KERNEL); + kzalloc_obj(struct aux_engine_dce110); if (!aux_engine) return NULL; @@ -940,7 +940,7 @@ static struct dce_i2c_hw *dcn42b_i2c_hw_create( uint32_t inst) { struct dce_i2c_hw *dce_i2c_hw = - kzalloc(sizeof(struct dce_i2c_hw), GFP_KERNEL); + kzalloc_obj(struct dce_i2c_hw); if (!dce_i2c_hw) return NULL; @@ -968,7 +968,7 @@ static struct clock_source *dcn42b_clock_source_create( bool dp_clk_src) { struct dce110_clk_src *clk_src = - kzalloc(sizeof(struct dce110_clk_src), GFP_KERNEL); + kzalloc_obj(struct dce110_clk_src); if (!clk_src) return NULL; @@ -988,8 +988,7 @@ static struct hubbub *dcn42b_hubbub_create(struct dc_context *ctx) { int i; - struct dcn20_hubbub *hubbub3 = kzalloc(sizeof(struct dcn20_hubbub), - GFP_KERNEL); + struct dcn20_hubbub *hubbub3 = kzalloc_obj(struct dcn20_hubbub); if (!hubbub3) return NULL; @@ -1042,7 +1041,7 @@ static struct hubp *dcn42b_hubp_create( uint32_t inst) { struct dcn20_hubp *hubp2 = - kzalloc(sizeof(struct dcn20_hubp), GFP_KERNEL); + kzalloc_obj(struct dcn20_hubp); if (!hubp2) return NULL; @@ -1084,7 +1083,7 @@ static struct dpp *dcn42b_dpp_create( uint32_t inst) { struct dcn42_dpp *dpp42b = - kzalloc(sizeof(struct dcn42_dpp), GFP_KERNEL); + kzalloc_obj(struct dcn42_dpp); if (!dpp42b) return NULL; @@ -1110,8 +1109,7 @@ static struct mpc *dcn42b_mpc_create( int num_mpcc, int num_rmu) { - struct dcn42_mpc *mpc42b = kzalloc(sizeof(struct dcn42_mpc), - GFP_KERNEL); + struct dcn42_mpc *mpc42b = kzalloc_obj(struct dcn42_mpc); if (!mpc42b) return NULL; @@ -1134,7 +1132,7 @@ static struct output_pixel_processor *dcn42b_opp_create( struct dc_context *ctx, uint32_t inst) { struct dcn20_opp *opp4 = - kzalloc(sizeof(struct dcn20_opp), GFP_KERNEL); + kzalloc_obj(struct dcn20_opp); if (!opp4) { BREAK_TO_DEBUGGER(); @@ -1158,7 +1156,7 @@ static struct timing_generator *dcn42b_timing_generator_create( uint32_t instance) { struct optc *tgn10 = - kzalloc(sizeof(struct optc), GFP_KERNEL); + kzalloc_obj(struct optc); if (!tgn10) return NULL; @@ -1196,7 +1194,7 @@ static struct link_encoder *dcn42b_link_encoder_create( const struct encoder_init_data *enc_init_data) { struct dcn20_link_encoder *enc20 = - kzalloc(sizeof(struct dcn20_link_encoder), GFP_KERNEL); + kzalloc_obj(struct dcn20_link_encoder); if (!enc20 || enc_init_data->hpd_source >= ARRAY_SIZE(link_enc_hpd_regs)) return NULL; @@ -1271,7 +1269,7 @@ static struct vpg *dcn42b_vpg_create( struct dc_context *ctx, uint32_t inst) { - struct dcn31_vpg *vpg4 = kzalloc(sizeof(struct dcn31_vpg), GFP_KERNEL); + struct dcn31_vpg *vpg4 = kzalloc_obj(struct dcn31_vpg); if (!vpg4) return NULL; @@ -1300,7 +1298,7 @@ static struct apg *dcn42b_apg_create( struct dc_context *ctx, uint32_t inst) { - struct dcn31_apg *apg31 = kzalloc(sizeof(struct dcn31_apg), GFP_KERNEL); + struct dcn31_apg *apg31 = kzalloc_obj(struct dcn31_apg); if (!apg31) return NULL; @@ -1344,7 +1342,7 @@ static struct stream_encoder *dcn42b_stream_encoder_create( } else return NULL; - enc1 = kzalloc(sizeof(struct dcn10_stream_encoder), GFP_KERNEL); + enc1 = kzalloc_obj(struct dcn10_stream_encoder); vpg = dcn42b_vpg_create(ctx, vpg_inst); apg = dcn42b_apg_create(ctx, apg_inst); @@ -1404,7 +1402,7 @@ static struct hpo_dp_stream_encoder *dcn42b_hpo_dp_stream_encoder_create( apg_inst = hpo_dp_inst + 6; /* allocate HPO stream encoder and create VPG sub-block */ - hpo_dp_enc31 = kzalloc(sizeof(struct dcn31_hpo_dp_stream_encoder), GFP_KERNEL); + hpo_dp_enc31 = kzalloc_obj(struct dcn31_hpo_dp_stream_encoder); vpg = dcn42b_vpg_create(ctx, vpg_inst); apg = dcn42b_apg_create(ctx, apg_inst); @@ -1437,7 +1435,7 @@ static struct hpo_dp_link_encoder *dcn42b_hpo_dp_link_encoder_create( struct dcn31_hpo_dp_link_encoder *hpo_dp_enc31; /* allocate HPO link encoder */ - hpo_dp_enc31 = kzalloc(sizeof(struct dcn31_hpo_dp_link_encoder), GFP_KERNEL); + hpo_dp_enc31 = kzalloc_obj(struct dcn31_hpo_dp_link_encoder); if (!hpo_dp_enc31) return NULL; /* out of memory */ @@ -1457,7 +1455,7 @@ static struct hpo_dp_link_encoder *dcn42b_hpo_dp_link_encoder_create( static struct dce_hwseq *dcn42b_hwseq_create( struct dc_context *ctx) { - struct dce_hwseq *hws = kzalloc(sizeof(struct dce_hwseq), GFP_KERNEL); + struct dce_hwseq *hws = kzalloc_obj(struct dce_hwseq); #undef REG_STRUCT #define REG_STRUCT hwseq_reg @@ -1716,8 +1714,7 @@ static bool dcn42b_dwbc_create(struct dc_context *ctx, struct resource_pool *poo uint32_t dwb_count = pool->res_cap->num_dwb; for (i = 0; i < dwb_count; i++) { - struct dcn30_dwbc *dwbc42 = kzalloc(sizeof(struct dcn30_dwbc), - GFP_KERNEL); + struct dcn30_dwbc *dwbc42 = kzalloc_obj(struct dcn30_dwbc); if (!dwbc42) { dm_error("DC: failed to create dwbc42!\n"); @@ -1753,8 +1750,7 @@ static bool dcn42b_mmhubbub_create(struct dc_context *ctx, struct resource_pool uint32_t pipe_count = pool->res_cap->num_dwb; for (i = 0; i < pipe_count; i++) { - struct dcn30_mmhubbub *mcif_wb30 = kzalloc(sizeof(struct dcn30_mmhubbub), - GFP_KERNEL); + struct dcn30_mmhubbub *mcif_wb30 = kzalloc_obj(struct dcn30_mmhubbub); if (!mcif_wb30) { dm_error("DC: failed to create mcif_wb30!\n"); @@ -1782,7 +1778,7 @@ static struct display_stream_compressor *dcn42b_dsc_create( struct dc_context *ctx, uint32_t inst) { struct dcn401_dsc *dsc = - kzalloc(sizeof(struct dcn401_dsc), GFP_KERNEL); + kzalloc_obj(struct dcn401_dsc); if (!dsc) { BREAK_TO_DEBUGGER(); @@ -1875,7 +1871,7 @@ static struct link_encoder *dcn42b_link_enc_create_minimal( if ((unsigned int)(eng_id - ENGINE_ID_DIGA) >= ctx->dc->res_pool->res_cap->num_dig_link_enc) return NULL; - enc20 = kzalloc(sizeof(struct dcn20_link_encoder), GFP_KERNEL); + enc20 = kzalloc_obj(struct dcn20_link_encoder); if (!enc20) return NULL; @@ -2439,7 +2435,7 @@ struct resource_pool *dcn42b_create_resource_pool( struct dc *dc) { struct dcn42b_resource_pool *pool = - kzalloc(sizeof(struct dcn42b_resource_pool), GFP_KERNEL); + kzalloc_obj(struct dcn42b_resource_pool); if (!pool) return NULL; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn60/dcn60_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn60/dcn60_resource.c index 4b7668abf4dc..d091ea55cb5d 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn60/dcn60_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn60/dcn60_resource.c @@ -998,7 +998,7 @@ static struct dce_aux *dcn60_aux_engine_create( uint32_t inst) { struct aux_engine_dce110 *aux_engine = - kzalloc(sizeof(struct aux_engine_dce110), GFP_KERNEL); + kzalloc_obj(struct aux_engine_dce110); if (!aux_engine) return NULL; @@ -1037,7 +1037,7 @@ static struct dce_i2c_hw *dcn60_i2c_hw_create( uint32_t inst) { struct dce_i2c_hw *dce_i2c_hw = - kzalloc(sizeof(struct dce_i2c_hw), GFP_KERNEL); + kzalloc_obj(struct dce_i2c_hw); if (!dce_i2c_hw) return NULL; @@ -1061,7 +1061,7 @@ static struct clock_source *dcn60_clock_source_create( bool dp_clk_src) { struct dce110_clk_src *clk_src = - kzalloc(sizeof(struct dce110_clk_src), GFP_KERNEL); + kzalloc_obj(struct dce110_clk_src); if (!clk_src) return NULL; @@ -1081,8 +1081,7 @@ static struct hubbub *dcn60_hubbub_create(struct dc_context *ctx) { int i; - struct dcn20_hubbub *hubbub2 = kzalloc(sizeof(struct dcn20_hubbub), - GFP_KERNEL); + struct dcn20_hubbub *hubbub2 = kzalloc_obj(struct dcn20_hubbub); if (!hubbub2) return NULL; @@ -1136,7 +1135,7 @@ static struct hubp *dcn60_hubp_create( uint32_t inst) { struct dcn20_hubp *hubp2 = - kzalloc(sizeof(struct dcn20_hubp), GFP_KERNEL); + kzalloc_obj(struct dcn20_hubp); if (!hubp2) return NULL; @@ -1168,7 +1167,7 @@ static struct dpp *dcn60_dpp_create( uint32_t inst) { struct dcn60_dpp *dpp60 = - kzalloc(sizeof(struct dcn60_dpp), GFP_KERNEL); + kzalloc_obj(struct dcn60_dpp); if (!dpp60) return NULL; @@ -1194,8 +1193,7 @@ static struct mpc *dcn60_mpc_create( int num_mpcc, int num_rmu) { - struct dcn60_mpc *mpc60 = kzalloc(sizeof(struct dcn60_mpc), - GFP_KERNEL); + struct dcn60_mpc *mpc60 = kzalloc_obj(struct dcn60_mpc); if (!mpc60) return NULL; @@ -1218,7 +1216,7 @@ static struct output_pixel_processor *dcn60_opp_create( struct dc_context *ctx, uint32_t inst) { struct dcn20_opp *opp2 = - kzalloc(sizeof(struct dcn20_opp), GFP_KERNEL); + kzalloc_obj(struct dcn20_opp); if (!opp2) { BREAK_TO_DEBUGGER(); @@ -1242,7 +1240,7 @@ static struct timing_generator *dcn60_timing_generator_create( uint32_t instance) { struct optc *tgn10 = - kzalloc(sizeof(struct optc), GFP_KERNEL); + kzalloc_obj(struct optc); if (!tgn10) return NULL; @@ -1282,7 +1280,7 @@ static struct link_encoder *dcn60_link_encoder_create( const struct encoder_init_data *enc_init_data) { struct dcn20_link_encoder *enc20 = - kzalloc(sizeof(struct dcn20_link_encoder), GFP_KERNEL); + kzalloc_obj(struct dcn20_link_encoder); if (!enc20 || enc_init_data->hpd_source >= ARRAY_SIZE(link_enc_hpd_regs)) { kfree(enc20); @@ -1349,7 +1347,7 @@ static struct vpg *dcn60_vpg_create( struct dc_context *ctx, uint32_t inst) { - struct dcn31_vpg *vpg6 = kzalloc(sizeof(struct dcn31_vpg), GFP_KERNEL); + struct dcn31_vpg *vpg6 = kzalloc_obj(struct dcn31_vpg); if (!vpg6) return NULL; @@ -1378,7 +1376,7 @@ static struct apg *dcn60_apg_create( struct dc_context *ctx, uint32_t inst) { - struct dcn31_apg *apg60 = kzalloc(sizeof(struct dcn31_apg), GFP_KERNEL); + struct dcn31_apg *apg60 = kzalloc_obj(struct dcn31_apg); if (!apg60) return NULL; @@ -1415,7 +1413,7 @@ static struct stream_encoder *dcn60_stream_encoder_create( } else return NULL; - enc1 = kzalloc(sizeof(struct dcn10_stream_encoder), GFP_KERNEL); + enc1 = kzalloc_obj(struct dcn10_stream_encoder); vpg = dcn60_vpg_create(ctx, vpg_inst); apg = dcn60_apg_create(ctx, apg_inst); @@ -1462,7 +1460,7 @@ static struct hpo_frl_stream_encoder *dcn60_hpo_frl_stream_encoder_create( return NULL; /* allocate HPO stream encoder and create VPG, APG sub-blocks */ - hpo_enc60 = kzalloc(sizeof(struct dcn401_hpo_frl_stream_encoder), GFP_KERNEL); + hpo_enc60 = kzalloc_obj(struct dcn401_hpo_frl_stream_encoder); vpg = dcn60_vpg_create(ctx, vpg_inst); apg = dcn60_apg_create(ctx, apg_inst); @@ -1493,7 +1491,7 @@ static struct hpo_frl_link_encoder *dcn60_hpo_frl_link_encoder_create( hpo_frl_link_encoder_reg_list(0); /* allocate HPO link encoder */ - hpo_link_enc = kzalloc(sizeof(struct dcn30_hpo_frl_link_encoder), GFP_KERNEL); + hpo_link_enc = kzalloc_obj(struct dcn30_hpo_frl_link_encoder); if (!hpo_link_enc) return NULL; /* out of memory */ @@ -1535,7 +1533,7 @@ static struct hpo_dp_stream_encoder *dcn60_hpo_dp_stream_encoder_create( apg_inst = hpo_dp_inst; /* allocate HPO stream encoder and create VPG sub-block */ - hpo_dp_enc60 = kzalloc(sizeof(struct dcn31_hpo_dp_stream_encoder), GFP_KERNEL); + hpo_dp_enc60 = kzalloc_obj(struct dcn31_hpo_dp_stream_encoder); vpg = dcn60_vpg_create(ctx, vpg_inst); apg = dcn60_apg_create(ctx, apg_inst); @@ -1568,7 +1566,7 @@ static struct hpo_dp_link_encoder *dcn60_hpo_dp_link_encoder_create( struct dcn31_hpo_dp_link_encoder *hpo_dp_enc60; /* allocate HPO link encoder */ - hpo_dp_enc60 = kzalloc(sizeof(struct dcn31_hpo_dp_link_encoder), GFP_KERNEL); + hpo_dp_enc60 = kzalloc_obj(struct dcn31_hpo_dp_link_encoder); if (!hpo_dp_enc60) return NULL; /* out of memory */ @@ -1589,7 +1587,7 @@ static struct hpo_dp_link_encoder *dcn60_hpo_dp_link_encoder_create( static struct dce_hwseq *dcn60_hwseq_create( struct dc_context *ctx) { - struct dce_hwseq *hws = kzalloc(sizeof(struct dce_hwseq), GFP_KERNEL); + struct dce_hwseq *hws = kzalloc_obj(struct dce_hwseq); #undef REG_STRUCT #define REG_STRUCT hwseq_reg @@ -1784,7 +1782,7 @@ static struct display_stream_compressor *dcn60_dsc_create( struct dc_context *ctx, uint32_t inst) { struct dcn60_dsc *dsc = - kzalloc(sizeof(struct dcn60_dsc), GFP_KERNEL); + kzalloc_obj(struct dcn60_dsc); if (!dsc) { BREAK_TO_DEBUGGER(); @@ -2363,7 +2361,7 @@ struct resource_pool *dcn60_create_resource_pool( struct dc *dc) { struct dcn60_resource_pool *pool = - kzalloc(sizeof(struct dcn60_resource_pool), GFP_KERNEL); + kzalloc_obj(struct dcn60_resource_pool); if (!pool) return NULL; diff --git a/drivers/gpu/drm/amd/display/modules/power/power.c b/drivers/gpu/drm/amd/display/modules/power/power.c index ee15c14a899e..2f9690e65ca9 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power.c +++ b/drivers/gpu/drm/amd/display/modules/power/power.c @@ -111,7 +111,7 @@ struct mod_power *mod_power_create(struct dc *dc, if (dc == NULL) goto fail_dc_null; - core_power = kzalloc(sizeof(struct core_power), GFP_KERNEL); + core_power = kzalloc_obj(struct core_power); if (core_power == NULL) goto fail_alloc_context; @@ -129,8 +129,7 @@ struct mod_power *mod_power_create(struct dc *dc, for (i = 0; i < MOD_POWER_MAX_CONCURRENT_STREAMS; i++) { core_power->map[i].psr_context = - kzalloc(sizeof(struct mod_power_psr_context), - GFP_KERNEL); + kzalloc_obj(struct mod_power_psr_context); if (core_power->map[i].psr_context == NULL) goto fail_construct; } diff --git a/drivers/gpu/drm/amd/display/modules/power/power_abm.c b/drivers/gpu/drm/amd/display/modules/power/power_abm.c index 5e86889eaa84..5f27dea4aa0a 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power_abm.c +++ b/drivers/gpu/drm/amd/display/modules/power/power_abm.c @@ -705,8 +705,7 @@ void initialize_backlight_caps(struct core_power *core_power, unsigned int inst) * (do not want to use 256 bytes on the stack) */ ext_backlight_caps = (struct dm_acpi_atif_backlight_caps *) - (kzalloc(sizeof(struct dm_acpi_atif_backlight_caps), - GFP_KERNEL)); + (kzalloc_obj(struct dm_acpi_atif_backlight_caps)); if (ext_backlight_caps == NULL) return; diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c index 2393fa8d4e4b..bdf1e489369e 100644 --- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c +++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/smu7_hwmgr.c @@ -2988,7 +2988,7 @@ static int smu7_init_voltage_dependency_on_display_clock_table(struct pp_hwmgr * if (!amdgpu_device_ip_get_ip_block(hwmgr->adev, AMD_IP_BLOCK_TYPE_DCE)) return 0; - table = kzalloc(struct_size(table, entries, 4), GFP_KERNEL); + table = kzalloc_flex(*table, entries, 4); if (!table) return -ENOMEM; diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c index aa4daf8f7d6f..4f820db5f8dd 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0_8_ppt.c @@ -222,7 +222,7 @@ static int smu_v15_0_8_tables_init(struct smu_context *smu) smu_table->metrics_time = 0; - driver_pptable = kzalloc(sizeof(PPTable_t), GFP_KERNEL); + driver_pptable = kzalloc_obj(PPTable_t); if (!driver_pptable) return -ENOMEM; @@ -1026,7 +1026,7 @@ static int smu_v15_0_8_fru_get_product_info(struct smu_context *smu, struct amdgpu_device *adev = smu->adev; if (!adev->fru_info) { - adev->fru_info = kzalloc(sizeof(*adev->fru_info), GFP_KERNEL); + adev->fru_info = kzalloc_obj(*adev->fru_info); if (!adev->fru_info) return -ENOMEM; } diff --git a/drivers/gpu/drm/amd/ras/core/cmd.c b/drivers/gpu/drm/amd/ras/core/cmd.c index 6c37dc519eed..446b935c1fc9 100644 --- a/drivers/gpu/drm/amd/ras/core/cmd.c +++ b/drivers/gpu/drm/amd/ras/core/cmd.c @@ -222,7 +222,7 @@ static int ras_cmd_get_cper_records(struct ras_core_context *ras_core, if (!buffer) return RAS_CMD__ERROR_GENERIC; - trace = kcalloc(trace_count, sizeof(*trace), GFP_KERNEL); + trace = kzalloc_objs(*trace, trace_count); if (!trace) { ret = RAS_CMD__ERROR_GENERIC; goto out; @@ -316,7 +316,7 @@ static int ras_cmd_get_batch_trace_records(struct ras_core_context *ras_core, (input_data->start_batch_id >= overview.last_batch_id)) return RAS_CMD__ERROR_INVALID_INPUT_SIZE; - trace_arry = kcalloc(trace_count, sizeof(*trace_arry), GFP_KERNEL); + trace_arry = kzalloc_objs(*trace_arry, trace_count); if (!trace_arry) return RAS_CMD__ERROR_GENERIC; diff --git a/drivers/gpu/drm/drm_exec.c b/drivers/gpu/drm/drm_exec.c index fa923852fae4..41034a5996ff 100644 --- a/drivers/gpu/drm/drm_exec.c +++ b/drivers/gpu/drm/drm_exec.c @@ -79,7 +79,7 @@ void drm_exec_init(struct drm_exec *exec, u32 flags, unsigned nr) nr = PAGE_SIZE / sizeof(void *); exec->flags = flags; - exec->objects = kvmalloc_objs(*exec->objects, nr, GFP_KERNEL); + exec->objects = kvmalloc_objs(*exec->objects, nr); /* If allocation here fails, just delay that till the first use */ exec->max_objects = exec->objects ? nr : 0; diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index 2fa170a29a62..c23a5de27eff 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -1619,7 +1619,7 @@ drm_syncobj_timeline_signal_ioctl(struct drm_device *dev, void *data, goto err_points; } - chains = kmalloc_objs(*chains, args->count_handles, GFP_KERNEL); + chains = kmalloc_objs(*chains, args->count_handles); if (!chains) { ret = -ENOMEM; goto err_points; diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c index 7f4178800afd..2fc41fb90aaa 100644 --- a/drivers/gpu/drm/qxl/qxl_display.c +++ b/drivers/gpu/drm/qxl/qxl_display.c @@ -981,7 +981,7 @@ static struct drm_plane *qxl_create_plane(struct qxl_device *qdev, return ERR_PTR(-EINVAL); } - plane = kzalloc(sizeof(*plane), GFP_KERNEL); + plane = kzalloc_obj(*plane); if (!plane) return ERR_PTR(-ENOMEM); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c b/drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c index 2e7b3e87fea1..7583b6ebba2a 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c @@ -22,7 +22,7 @@ tilcdc_panel_update_prop(struct of_changeset *ocs, struct device_node *node, { struct property *prop; - prop = kzalloc(sizeof(*prop), GFP_KERNEL); + prop = kzalloc_obj(*prop); if (!prop) return -ENOMEM; diff --git a/drivers/gpu/drm/verisilicon/vs_plane.c b/drivers/gpu/drm/verisilicon/vs_plane.c index d81f7b8f4c65..7ddb9d2dcb83 100644 --- a/drivers/gpu/drm/verisilicon/vs_plane.c +++ b/drivers/gpu/drm/verisilicon/vs_plane.c @@ -136,7 +136,7 @@ struct drm_plane_state *vs_plane_duplicate_state(struct drm_plane *plane) vs_state_old = to_vs_plane_state(plane->state); - vs_state = kzalloc_obj(*vs_state, GFP_KERNEL); + vs_state = kzalloc_obj(*vs_state); if (!vs_state) return NULL; @@ -166,7 +166,7 @@ void vs_plane_reset(struct drm_plane *plane) plane->state = NULL; } - vs_state = kzalloc_obj(*vs_state, GFP_KERNEL); + vs_state = kzalloc_obj(*vs_state); if (!vs_state) return; diff --git a/drivers/gpu/drm/virtio/virtgpu_prime.c b/drivers/gpu/drm/virtio/virtgpu_prime.c index 216c77cd0d21..149e6bcb5878 100644 --- a/drivers/gpu/drm/virtio/virtgpu_prime.c +++ b/drivers/gpu/drm/virtio/virtgpu_prime.c @@ -293,9 +293,7 @@ int virtgpu_dma_buf_obj_resubmit(struct virtio_gpu_device *vgdev, return -ENOMEM; } - ents = kvmalloc_array(bo->sgt->nents, - sizeof(struct virtio_gpu_mem_entry), - GFP_KERNEL); + ents = kvmalloc_objs(struct virtio_gpu_mem_entry, bo->sgt->nents); if (!ents) { DRM_ERROR("failed to allocate ent list\n"); return -ENOMEM; diff --git a/drivers/gpu/drm/vkms/vkms_configfs.c b/drivers/gpu/drm/vkms/vkms_configfs.c index 7551b8c7766d..601e3f128c9f 100644 --- a/drivers/gpu/drm/vkms/vkms_configfs.c +++ b/drivers/gpu/drm/vkms/vkms_configfs.c @@ -212,7 +212,7 @@ static struct config_group *make_crtc_group(struct config_group *group, if (dev->enabled) return ERR_PTR(-EBUSY); - crtc = kzalloc(sizeof(*crtc), GFP_KERNEL); + crtc = kzalloc_obj(*crtc); if (!crtc) return ERR_PTR(-ENOMEM); @@ -368,7 +368,7 @@ static struct config_group *make_plane_group(struct config_group *group, if (dev->enabled) return ERR_PTR(-EBUSY); - plane = kzalloc(sizeof(*plane), GFP_KERNEL); + plane = kzalloc_obj(*plane); if (!plane) return ERR_PTR(-ENOMEM); @@ -484,7 +484,7 @@ static struct config_group *make_encoder_group(struct config_group *group, if (dev->enabled) return ERR_PTR(-EBUSY); - encoder = kzalloc(sizeof(*encoder), GFP_KERNEL); + encoder = kzalloc_obj(*encoder); if (!encoder) return ERR_PTR(-ENOMEM); @@ -651,7 +651,7 @@ static struct config_group *make_connector_group(struct config_group *group, if (dev->enabled) return ERR_PTR(-EBUSY); - connector = kzalloc(sizeof(*connector), GFP_KERNEL); + connector = kzalloc_obj(*connector); if (!connector) return ERR_PTR(-ENOMEM); diff --git a/drivers/gpu/drm/xe/xe_amc.c b/drivers/gpu/drm/xe/xe_amc.c index 8ecadee6eea3..edd50bf8261e 100644 --- a/drivers/gpu/drm/xe/xe_amc.c +++ b/drivers/gpu/drm/xe/xe_amc.c @@ -177,7 +177,7 @@ int xe_amc_init(struct xe_i2c *i2c) { struct xe_amc *amc; - amc = kzalloc(sizeof(*amc), GFP_KERNEL); + amc = kzalloc_obj(*amc); if (!amc) return -ENOMEM; diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index 9e0176861cb6..23952ad8951e 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -4148,7 +4148,7 @@ static int fill_faults(struct xe_vm *vm, entry_size = sizeof(struct xe_vm_fault); count = args->size / entry_size; - fault_list = kcalloc(count, sizeof(struct xe_vm_fault), GFP_KERNEL); + fault_list = kzalloc_objs(struct xe_vm_fault, count); if (!fault_list) return -ENOMEM; diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index ec966fc0a411..c38a2b3b33d9 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -406,7 +406,7 @@ static int asus_kbd_fn_lock_set(struct asus_drvdata *drvdata, bool enabled) struct asus_work_action *action; unsigned long flags; - action = kzalloc(sizeof(struct asus_work_action), GFP_ATOMIC); + action = kzalloc_obj(struct asus_work_action, GFP_ATOMIC); if (!action) return -ENOMEM; @@ -433,7 +433,7 @@ static int asus_kbd_wmi_fan_send(struct asus_drvdata *drvdata, u8 *report_data, return -EINVAL; } - action = kzalloc(sizeof(struct asus_work_action), GFP_NOWAIT); + action = kzalloc_obj(struct asus_work_action, GFP_NOWAIT); if (!action) return -ENOMEM; @@ -746,7 +746,7 @@ static void asus_kbd_backlight_set(struct asus_hid_listener *listener, int brigh drvdata->kbd_backlight_brightness = brightness; - action = kzalloc(sizeof(struct asus_work_action), GFP_NOWAIT); + action = kzalloc_obj(struct asus_work_action, GFP_NOWAIT); if (!action) return; diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c index ac08cb2d0368..0f364d43aa4b 100644 --- a/drivers/hid/hid-steam.c +++ b/drivers/hid/hid-steam.c @@ -759,7 +759,7 @@ static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad, if (steam->quirks & STEAM_QUIRK_IBEX) { struct steam_ibex_output_report *report = - kzalloc(sizeof(struct steam_ibex_output_report), GFP_KERNEL); + kzalloc_obj(struct steam_ibex_output_report); if (!report) return -ENOMEM; @@ -798,7 +798,7 @@ static inline int steam_haptic_rumble(struct steam_device *steam, if (steam->quirks & STEAM_QUIRK_IBEX) { struct steam_ibex_output_report *report = - kzalloc(sizeof(struct steam_ibex_output_report), GFP_KERNEL); + kzalloc_obj(struct steam_ibex_output_report); if (!report) return -ENOMEM; diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c index 23fb0cebd72a..7a855b66730a 100644 --- a/drivers/hid/hid-steelseries-arctis.c +++ b/drivers/hid/hid-steelseries-arctis.c @@ -469,7 +469,7 @@ static int steelseries_arctis_probe(struct hid_device *hdev, return hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (interface_num == info->sync_interface) { - sd = kzalloc_obj(*sd, GFP_KERNEL); + sd = kzalloc_obj(*sd); if (!sd) return -ENOMEM; diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index f4370617deac..7e4cc6f55237 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -694,12 +694,11 @@ void *vmbus_alloc_buffer(struct vmbus_channel *channel, return vzalloc(nr_pages << PAGE_SHIFT); /* Worst case: every chunk is a single page. */ - chunks = kvmalloc_array(nr_pages, sizeof(*chunks), - GFP_KERNEL | __GFP_ZERO); + chunks = kvmalloc_objs(*chunks, nr_pages, GFP_KERNEL | __GFP_ZERO); if (!chunks) goto err; - pages = kvmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL); + pages = kvmalloc_objs(*pages, nr_pages); if (!pages) goto err; diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 9cba97e81111..39c1b793a893 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -939,7 +939,7 @@ static unsigned long process_hot_add(unsigned long pg_start, */ if (rg_size != 0) { - ha_region = kzalloc(sizeof(struct hv_hotadd_state), GFP_KERNEL); + ha_region = kzalloc_obj(struct hv_hotadd_state); if (!ha_region) return 0; diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index ca56bd8b170e..00e603b5e401 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -1476,9 +1476,9 @@ static int __init applesmc_init(void) applesmc_fan_config[smcreg.fan_count] = 0; applesmc_pwm_config[smcreg.fan_count] = 0; - applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL); - applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL); - applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL); + applesmc_info_temp = kzalloc_obj(*applesmc_info_temp); + applesmc_info_fan = kzalloc_obj(*applesmc_info_fan); + applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm); if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) { ret = -ENOMEM; goto out_info; @@ -1493,7 +1493,7 @@ static int __init applesmc_init(void) applesmc_info_pwm->type = hwmon_pwm; applesmc_info_pwm->config = applesmc_pwm_config; - applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); + applesmc_info_arr = kzalloc_objs(*applesmc_info_arr, 4); if (!applesmc_info_arr) { ret = -ENOMEM; goto out_info; @@ -1504,7 +1504,7 @@ static int __init applesmc_init(void) applesmc_info_arr[2] = applesmc_info_pwm; applesmc_info_arr[3] = NULL; - applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); + applesmc_chip = kzalloc_obj(*applesmc_chip); if (!applesmc_chip) { ret = -ENOMEM; goto out_info; @@ -1514,10 +1514,9 @@ static int __init applesmc_init(void) applesmc_chip->info = applesmc_info_arr; /* Create non-standard fanX_safe attributes group */ - fan_safe_attrs = kcalloc(smcreg.fan_count, - sizeof(*fan_safe_attrs), GFP_KERNEL); - fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, - sizeof(*fan_safe_attr_list), GFP_KERNEL); + fan_safe_attrs = kzalloc_objs(*fan_safe_attrs, smcreg.fan_count); + fan_safe_attr_list = kzalloc_objs(*fan_safe_attr_list, + smcreg.fan_count + 1); if (!fan_safe_attrs || !fan_safe_attr_list) { ret = -ENOMEM; goto out_info; diff --git a/drivers/hwtracing/coresight/coresight-core.c b/drivers/hwtracing/coresight/coresight-core.c index 6d65c43d574f..928488a216d4 100644 --- a/drivers/hwtracing/coresight/coresight-core.c +++ b/drivers/hwtracing/coresight/coresight-core.c @@ -1679,7 +1679,7 @@ coresight_allocate_device_list(const char *prefix) return list; } - list = kzalloc(sizeof(*list), GFP_KERNEL); + list = kzalloc_obj(*list); if (!list) return NULL; diff --git a/drivers/i2c/busses/i2c-gpio.c b/drivers/i2c/busses/i2c-gpio.c index b7521b7ece37..6294b0b0cc8f 100644 --- a/drivers/i2c/busses/i2c-gpio.c +++ b/drivers/i2c/busses/i2c-gpio.c @@ -418,7 +418,7 @@ static struct i2c_gpio_scl_data *i2c_gpio_create_scl(struct device *dev) } } - scl = kzalloc(sizeof(*scl), GFP_KERNEL); + scl = kzalloc_obj(*scl); if (!scl) { if (sharable) fwnode_handle_put(args.fwnode); diff --git a/drivers/i3c/master/amd-i3c-master.c b/drivers/i3c/master/amd-i3c-master.c index ef5ad5abb788..e4da9c38eccb 100644 --- a/drivers/i3c/master/amd-i3c-master.c +++ b/drivers/i3c/master/amd-i3c-master.c @@ -376,7 +376,7 @@ static struct xi3c_xfer *xi3c_master_alloc_xfer(unsigned int ncmds) { struct xi3c_xfer *xfer; - xfer = kzalloc_flex(*xfer, cmds, ncmds, GFP_KERNEL); + xfer = kzalloc_flex(*xfer, cmds, ncmds); if (!xfer) return NULL; @@ -735,7 +735,7 @@ static int xi3c_master_send_bdcast_ccc_cmd(struct xi3c_master *master, if (!xfer) return -ENOMEM; - buf = kmalloc_objs(*buf, xfer_len, GFP_KERNEL); + buf = kmalloc_objs(*buf, xfer_len); if (!buf) return -ENOMEM; diff --git a/drivers/iio/adc/ad7280a.c b/drivers/iio/adc/ad7280a.c index 505fa68ee539..5ae0d694d20c 100644 --- a/drivers/iio/adc/ad7280a.c +++ b/drivers/iio/adc/ad7280a.c @@ -796,8 +796,8 @@ static irqreturn_t ad7280_event_handler(int irq, void *private) struct ad7280_state *st = iio_priv(indio_dev); int i, ret; - unsigned int *channels __free(kfree) = kcalloc(st->scan_cnt, sizeof(*channels), - GFP_KERNEL); + unsigned int *channels __free(kfree) = kzalloc_objs(*channels, + st->scan_cnt); if (!channels) return IRQ_HANDLED; diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index ecc02a427b92..1bd82d3db5ea 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -109,7 +109,7 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, if (nents < 0) return nents; - vecs = kmalloc_array(nents, sizeof(*vecs), GFP_ATOMIC); + vecs = kmalloc_objs(*vecs, nents, GFP_ATOMIC); if (!vecs) return -ENOMEM; diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c index fb08e4f02520..1f22275de55c 100644 --- a/drivers/iio/inkern.c +++ b/drivers/iio/inkern.c @@ -345,7 +345,7 @@ static struct iio_channel *iio_channel_get_sys(const char *name, return ERR_PTR(-ENODEV); struct iio_channel *channel __free(kfree) = - kzalloc(sizeof(*channel), GFP_KERNEL); + kzalloc_obj(*channel); if (!channel) { err = -ENOMEM; goto error_no_mem; diff --git a/drivers/infiniband/core/nldev.c b/drivers/infiniband/core/nldev.c index a4014a230639..4e8fbee34745 100644 --- a/drivers/infiniband/core/nldev.c +++ b/drivers/infiniband/core/nldev.c @@ -2943,7 +2943,7 @@ static int nldev_frmr_pools_set_doit(struct sk_buff *skb, struct nlmsghdr *nlh, u32 aging_period; int err; - tb = kzalloc_objs(*tb, RDMA_NLDEV_ATTR_MAX, GFP_KERNEL); + tb = kzalloc_objs(*tb, RDMA_NLDEV_ATTR_MAX); if (!tb) return -ENOMEM; diff --git a/drivers/infiniband/hw/hns/hns_roce_debugfs.c b/drivers/infiniband/hw/hns/hns_roce_debugfs.c index 05630f7c9155..103b8c9ca969 100644 --- a/drivers/infiniband/hw/hns/hns_roce_debugfs.c +++ b/drivers/infiniband/hw/hns/hns_roce_debugfs.c @@ -267,8 +267,7 @@ static int hns_roce_alloc_scc_param(struct hns_roce_dev *hr_dev) struct hns_roce_scc_param *scc_param; int i; - scc_param = kvcalloc(HNS_ROCE_SCC_ALGO_TOTAL, sizeof(*scc_param), - GFP_KERNEL); + scc_param = kvzalloc_objs(*scc_param, HNS_ROCE_SCC_ALGO_TOTAL); if (!scc_param) return -ENOMEM; diff --git a/drivers/input/input.c b/drivers/input/input.c index 78c10eea7328..01c91fec9b7e 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -2344,7 +2344,7 @@ static int input_device_tune_vals(struct input_dev *dev) if (dev->max_vals >= max_vals) return 0; - vals = kcalloc(max_vals, sizeof(*vals), GFP_KERNEL); + vals = kzalloc_objs(*vals, max_vals); if (!vals) return -ENOMEM; diff --git a/drivers/input/keyboard/adp5585-keys.c b/drivers/input/keyboard/adp5585-keys.c index 017c95029180..f2c1ba017d20 100644 --- a/drivers/input/keyboard/adp5585-keys.c +++ b/drivers/input/keyboard/adp5585-keys.c @@ -115,8 +115,8 @@ static int adp5585_keys_parse_fw(const struct adp5585_dev *adp5585, "Too many keypad pins (%d) defined (max=%d)\n", n_pins, adp5585->n_pins); - unsigned int *keypad_pins __free(kfree) = kcalloc(n_pins, sizeof(*keypad_pins), - GFP_KERNEL); + unsigned int *keypad_pins __free(kfree) = kzalloc_objs(*keypad_pins, + n_pins); if (!keypad_pins) return -ENOMEM; diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index 5736f4bc5a50..b9ad2381f885 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -1070,7 +1070,7 @@ static int atkbd_get_keymap_from_fwnode(struct atkbd *atkbd) if (n <= 0 || n > ATKBD_KEYMAP_SIZE) return -ENXIO; - u32 *ptr __free(kfree) = kcalloc(n, sizeof(*ptr), GFP_KERNEL); + u32 *ptr __free(kfree) = kzalloc_objs(*ptr, n); if (!ptr) return -ENOMEM; diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c index b1a0edcc49b4..c3244c1a3751 100644 --- a/drivers/input/misc/ims-pcu.c +++ b/drivers/input/misc/ims-pcu.c @@ -1844,7 +1844,7 @@ static int ims_pcu_line_setup(struct ims_pcu *pcu) { struct usb_host_interface *interface = pcu->ctrl_intf->cur_altsetting; struct usb_cdc_line_coding *line __free(kfree) = - kmalloc(sizeof(*line), GFP_KERNEL); + kmalloc_obj(*line); int error; if (!line) diff --git a/drivers/input/mouse/psmouse-smbus.c b/drivers/input/mouse/psmouse-smbus.c index 7fb4cbb2aca2..98d9c860d2db 100644 --- a/drivers/input/mouse/psmouse-smbus.c +++ b/drivers/input/mouse/psmouse-smbus.c @@ -232,7 +232,7 @@ int psmouse_smbus_init(struct psmouse *psmouse, struct psmouse_smbus_dev *smbdev; int error; - smbdev = kzalloc(sizeof(*smbdev), GFP_KERNEL); + smbdev = kzalloc_obj(*smbdev); if (!smbdev) return -ENOMEM; diff --git a/drivers/input/serio/serio_raw.c b/drivers/input/serio/serio_raw.c index a7ccedfa459c..868dee9a2ed8 100644 --- a/drivers/input/serio/serio_raw.c +++ b/drivers/input/serio/serio_raw.c @@ -84,7 +84,7 @@ static int serio_raw_open(struct inode *inode, struct file *file) if (serio_raw->dead) return -ENODEV; - client = kzalloc(sizeof(*client), GFP_KERNEL); + client = kzalloc_obj(*client); if (!client) return -ENOMEM; diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 50f8321e979c..dd2fee2f560e 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -796,7 +796,7 @@ static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs) { struct arm_smmu_invs *new_invs; - new_invs = kzalloc(struct_size(new_invs, inv, num_invs), GFP_KERNEL); + new_invs = kzalloc_flex(*new_invs, inv, num_invs); if (!new_invs) return NULL; new_invs->max_invs = num_invs; diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c index 5c4b06eda546..a664c70a6fe7 100644 --- a/drivers/iommu/iommufd/device.c +++ b/drivers/iommu/iommufd/device.c @@ -76,7 +76,7 @@ static struct iommufd_group *iommufd_alloc_group(struct iommufd_ctx *ictx, { struct iommufd_group *new_igroup; - new_igroup = kzalloc_obj(*new_igroup, GFP_KERNEL); + new_igroup = kzalloc_obj(*new_igroup); if (!new_igroup) return ERR_PTR(-ENOMEM); diff --git a/drivers/iommu/iommufd/driver.c b/drivers/iommu/iommufd/driver.c index 3b8067976eac..e4d17a748178 100644 --- a/drivers/iommu/iommufd/driver.c +++ b/drivers/iommu/iommufd/driver.c @@ -49,7 +49,7 @@ int _iommufd_alloc_mmap(struct iommufd_ctx *ictx, struct iommufd_object *owner, if (!length || !PAGE_ALIGNED(length)) return -EINVAL; - immap = kzalloc(sizeof(*immap), GFP_KERNEL); + immap = kzalloc_obj(*immap); if (!immap) return -ENOMEM; immap->owner = owner; diff --git a/drivers/iommu/iommufd/hwpt_noiommu.c b/drivers/iommu/iommufd/hwpt_noiommu.c index 9b8b5eb71491..a80209a7429a 100644 --- a/drivers/iommu/iommufd/hwpt_noiommu.c +++ b/drivers/iommu/iommufd/hwpt_noiommu.c @@ -54,7 +54,7 @@ noiommu_alloc_paging_flags(struct device *dev, u32 flags, (BIT(PT_FEAT_DYNAMIC_TOP) | BIT(PT_FEAT_AMDV1_ENCRYPT_TABLES) | BIT(PT_FEAT_AMDV1_FORCE_COHERENCE)); - dom = kzalloc(sizeof(*dom), GFP_KERNEL); + dom = kzalloc_obj(*dom); if (!dom) return ERR_PTR(-ENOMEM); diff --git a/drivers/iommu/vsi-iommu.c b/drivers/iommu/vsi-iommu.c index 42c424496d07..5dac20b143a5 100644 --- a/drivers/iommu/vsi-iommu.c +++ b/drivers/iommu/vsi-iommu.c @@ -231,7 +231,7 @@ static struct iommu_domain *vsi_iommu_domain_alloc_paging(struct device *dev) struct vsi_iommu *iommu = dev_iommu_priv_get(dev); struct vsi_iommu_domain *vsi_domain; - vsi_domain = kzalloc(sizeof(*vsi_domain), GFP_KERNEL); + vsi_domain = kzalloc_obj(*vsi_domain); if (!vsi_domain) return NULL; diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c index b3feb6340b59..4486645a4b6f 100644 --- a/drivers/irqchip/irq-gic-v5-irs.c +++ b/drivers/irqchip/irq-gic-v5-irs.c @@ -618,7 +618,7 @@ static int __init gicv5_irs_of_init_affinity(struct device_node *node, if (niaffids != ncpus) return -EINVAL; - u16 *iaffids __free(kfree) = kcalloc(niaffids, sizeof(*iaffids), GFP_KERNEL); + u16 *iaffids __free(kfree) = kzalloc_objs(*iaffids, niaffids); if (!iaffids) return -ENOMEM; diff --git a/drivers/irqchip/irq-loongarch-ir.c b/drivers/irqchip/irq-loongarch-ir.c index 21c649a89a70..cb4ad34da058 100644 --- a/drivers/irqchip/irq-loongarch-ir.c +++ b/drivers/irqchip/irq-loongarch-ir.c @@ -331,7 +331,7 @@ static int redirect_domain_alloc(struct irq_domain *domain, unsigned int virq, struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq + i); struct redirect_item *item; - item = kzalloc(sizeof(*item), GFP_KERNEL); + item = kzalloc_obj(*item); if (!item) { pr_err("Alloc redirect descriptor failed\n"); goto out_free_resources; diff --git a/drivers/irqchip/irq-realtek-rtl.c b/drivers/irqchip/irq-realtek-rtl.c index 26e52c3f8c68..f1ab5a77dcef 100644 --- a/drivers/irqchip/irq-realtek-rtl.c +++ b/drivers/irqchip/irq-realtek-rtl.c @@ -188,7 +188,7 @@ static int __init realtek_setup_parents(struct device_node *node) struct irq_domain *domain; cnt = max(1, num_parents); - output = kcalloc(cnt, sizeof(*output), GFP_KERNEL); + output = kzalloc_objs(*output, cnt); if (!output) return -ENOMEM; diff --git a/drivers/mailbox/riscv-sbi-mpxy-mbox.c b/drivers/mailbox/riscv-sbi-mpxy-mbox.c index 714f7fb97a2f..ea69c6b6b4f9 100644 --- a/drivers/mailbox/riscv-sbi-mpxy-mbox.c +++ b/drivers/mailbox/riscv-sbi-mpxy-mbox.c @@ -783,7 +783,7 @@ static int mpxy_mbox_populate_channels(struct mpxy_mbox *mbox) return dev_err_probe(mbox->dev, -ENODEV, "no MPXY channels available\n"); /* Allocate and fetch all channel IDs */ - channel_ids = kcalloc(mbox->channel_count, sizeof(*channel_ids), GFP_KERNEL); + channel_ids = kzalloc_objs(*channel_ids, mbox->channel_count); if (!channel_ids) return -ENOMEM; rc = mpxy_get_channel_ids(mbox->channel_count, channel_ids); diff --git a/drivers/md/dm-inlinecrypt.c b/drivers/md/dm-inlinecrypt.c index 66fa2f9d1fdc..3479dfe5f708 100644 --- a/drivers/md/dm-inlinecrypt.c +++ b/drivers/md/dm-inlinecrypt.c @@ -322,7 +322,7 @@ static int inlinecrypt_ctr(struct dm_target *ti, unsigned int argc, char **argv) return -EINVAL; } - ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + ctx = kzalloc_obj(*ctx); if (!ctx) { ti->error = "Out of memory"; return -ENOMEM; diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c index e1a783ee2032..1c9fcc746ec6 100644 --- a/drivers/md/md-llbitmap.c +++ b/drivers/md/md-llbitmap.c @@ -810,7 +810,7 @@ static int llbitmap_expand_pages(struct llbitmap *llbitmap, if (nr_pages <= old_nr_pages) return 0; - pctl = kcalloc(nr_pages, sizeof(*pctl), GFP_NOIO); + pctl = kzalloc_objs(*pctl, nr_pages, GFP_NOIO); if (!pctl) return -ENOMEM; @@ -846,7 +846,7 @@ static int llbitmap_alloc_pages(struct llbitmap *llbitmap) unsigned int nr_pages = max(used_pages, llbitmap_reserved_pages(llbitmap)); int i; - llbitmap->pctl = kcalloc(nr_pages, sizeof(*llbitmap->pctl), GFP_NOIO); + llbitmap->pctl = kzalloc_objs(*llbitmap->pctl, nr_pages, GFP_NOIO); if (!llbitmap->pctl) return -ENOMEM; diff --git a/drivers/media/platform/allegro-dvt/allegro-core.c b/drivers/media/platform/allegro-dvt/allegro-core.c index eac3bc9af990..4f3299a66b37 100644 --- a/drivers/media/platform/allegro-dvt/allegro-core.c +++ b/drivers/media/platform/allegro-dvt/allegro-core.c @@ -3105,7 +3105,7 @@ static int allegro_open(struct file *file) unsigned int cpb_size_max; unsigned int cpb_size_def; - channel = kzalloc(sizeof(*channel), GFP_KERNEL); + channel = kzalloc_obj(*channel); if (!channel) return -ENOMEM; diff --git a/drivers/media/platform/amd/isp4/isp4_interface.c b/drivers/media/platform/amd/isp4/isp4_interface.c index 4801617f9559..218aad5d7dfb 100644 --- a/drivers/media/platform/amd/isp4/isp4_interface.c +++ b/drivers/media/platform/amd/isp4/isp4_interface.c @@ -127,7 +127,7 @@ isp4if_gpu_mem_alloc(struct isp4_interface *ispif, u32 mem_size) struct device *dev = ispif->dev; int ret; - mem_info = kmalloc_obj(*mem_info, GFP_KERNEL); + mem_info = kmalloc_obj(*mem_info); if (!mem_info) return NULL; @@ -368,7 +368,7 @@ static int isp4if_send_fw_cmd(struct isp4_interface *ispif, u32 cmd_id, /* Allocate the sync command object early and outside of the lock */ if (sync) { - ele = kmalloc_obj(*ele, GFP_KERNEL); + ele = kmalloc_obj(*ele); if (!ele) return -ENOMEM; @@ -738,7 +738,7 @@ isp4if_alloc_buffer_node(struct isp4if_img_buf_info *buf_info) { struct isp4if_img_buf_node *node; - node = kmalloc_obj(*node, GFP_KERNEL); + node = kmalloc_obj(*node); if (node) node->buf_info = *buf_info; diff --git a/drivers/media/platform/renesas/rcar-isp/core.c b/drivers/media/platform/renesas/rcar-isp/core.c index b5861d0cd0e8..586a5adbedfa 100644 --- a/drivers/media/platform/renesas/rcar-isp/core.c +++ b/drivers/media/platform/renesas/rcar-isp/core.c @@ -232,7 +232,7 @@ int risp_core_job_prepare(struct rcar_isp_core *core) } /* Memory is released when the job is consumed. */ - job = kzalloc(sizeof(*job), GFP_KERNEL); + job = kzalloc_obj(*job); if (!job) return -ENOMEM; diff --git a/drivers/media/rc/igorplugusb.c b/drivers/media/rc/igorplugusb.c index b5117ee9f5fa..7758487da3d3 100644 --- a/drivers/media/rc/igorplugusb.c +++ b/drivers/media/rc/igorplugusb.c @@ -164,7 +164,7 @@ static int igorplugusb_probe(struct usb_interface *intf, if (!ir) return -ENOMEM; - ir->request = kzalloc_obj(*ir->request, GFP_KERNEL); + ir->request = kzalloc_obj(*ir->request); if (!ir->request) goto fail; diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 7aa32b90cf1e..16ad4fd26357 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -135,7 +135,7 @@ static int mfd_match_of_node_to_dev(struct platform_device *pdev, return -EAGAIN; allocate_of_node: - of_entry = kzalloc(sizeof(*of_entry), GFP_KERNEL); + of_entry = kzalloc_obj(*of_entry); if (!of_entry) return -ENOMEM; @@ -174,7 +174,7 @@ static int mfd_add_device(struct device *parent, int id, if (!pdev->mfd_cell) goto fail_device; - res = kcalloc(cell->num_resources, sizeof(*res), GFP_KERNEL); + res = kzalloc_objs(*res, cell->num_resources); if (!res) goto fail_device; diff --git a/drivers/mfd/ucb1x00-assabet.c b/drivers/mfd/ucb1x00-assabet.c index ee49ac779d1a..698f60aa9756 100644 --- a/drivers/mfd/ucb1x00-assabet.c +++ b/drivers/mfd/ucb1x00-assabet.c @@ -96,7 +96,7 @@ static int ucb1x00_assabet_add(struct ucb1x00_dev *dev) struct ucb1x00_assabet_priv *priv; - priv = kzalloc_obj(*priv, GFP_KERNEL); + priv = kzalloc_obj(*priv); if (!priv) return -ENOMEM; diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c index da4277ced4d6..25cf33fe1ec1 100644 --- a/drivers/mtd/mtd_virt_concat.c +++ b/drivers/mtd/mtd_virt_concat.c @@ -166,7 +166,7 @@ static int mtd_virt_concat_create_item(struct device_node *parts, return 0; } - item = kzalloc_flex(*item, nodes, count, GFP_KERNEL); + item = kzalloc_flex(*item, nodes, count); if (!item) return -ENOMEM; @@ -182,7 +182,7 @@ static int mtd_virt_concat_create_item(struct device_node *parts, for (i = 1; i < count; i++) item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, (i - 1)); - concat = kzalloc_flex(*concat, subdev, count, GFP_KERNEL); + concat = kzalloc_flex(*concat, subdev, count); if (!concat) { kfree(item); return -ENOMEM; diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index c97167d51fe2..d54d309c30a0 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -639,7 +639,7 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c printk(KERN_NOTICE "into device \"%s\"\n", name); /* allocate the device structure */ - concat = kzalloc_flex(*concat, subdev, num_devs, GFP_KERNEL); + concat = kzalloc_flex(*concat, subdev, num_devs); if (!concat) { printk ("memory allocation error while creating concatenated device \"%s\"\n", diff --git a/drivers/net/dsa/mv88e6xxx/tcflower.c b/drivers/net/dsa/mv88e6xxx/tcflower.c index d67604a55b9f..2ddfeaaa0690 100644 --- a/drivers/net/dsa/mv88e6xxx/tcflower.c +++ b/drivers/net/dsa/mv88e6xxx/tcflower.c @@ -97,7 +97,7 @@ int mv88e6xxx_cls_flower_add(struct dsa_switch *ds, int port, goto err_unlock; } - entry = kzalloc(sizeof(*entry), GFP_KERNEL); + entry = kzalloc_obj(*entry); if (!entry) { err = -ENOMEM; goto err_unlock; diff --git a/drivers/net/ethernet/alibaba/eea/eea_adminq.c b/drivers/net/ethernet/alibaba/eea/eea_adminq.c index dfad1bdbc44d..73d0caf25700 100644 --- a/drivers/net/ethernet/alibaba/eea/eea_adminq.c +++ b/drivers/net/ethernet/alibaba/eea/eea_adminq.c @@ -439,8 +439,7 @@ int eea_adminq_dev_status(struct eea_net *enet, q_num = enet->cfg.rx_ring_num + enet->cfg.tx_ring_num + 1; io_num = enet->cfg.rx_ring_num + enet->cfg.tx_ring_num; - req = kcalloc(q_num, sizeof(struct eea_aq_queue_drv_status), - GFP_KERNEL); + req = kzalloc_objs(struct eea_aq_queue_drv_status, q_num); if (!req) return -ENOMEM; @@ -486,11 +485,11 @@ void eea_adminq_config_host_info(struct eea_net *enet) struct eea_aq_host_info_rep *rep; int rc = -ENOMEM; - cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + cfg = kzalloc_obj(*cfg); if (!cfg) return; - rep = kzalloc(sizeof(*rep), GFP_KERNEL); + rep = kzalloc_obj(*rep); if (!rep) goto err_free_cfg; diff --git a/drivers/net/ethernet/alibaba/eea/eea_net.c b/drivers/net/ethernet/alibaba/eea/eea_net.c index 63e68580de94..0af6c95b5e63 100644 --- a/drivers/net/ethernet/alibaba/eea/eea_net.c +++ b/drivers/net/ethernet/alibaba/eea/eea_net.c @@ -62,7 +62,7 @@ static int eea_alloc_irq_blks(struct eea_net *enet) num = enet->edev->rx_num; - irq_blks = kvcalloc(num, sizeof(*blk), GFP_KERNEL); + irq_blks = kvzalloc_objs(*blk, num); if (!irq_blks) return -ENOMEM; @@ -194,11 +194,11 @@ static int eea_alloc_rxtx_q_mem(struct eea_net_init_ctx *ctx) struct eea_net_tx *tx; int err, i; - ctx->tx = kvcalloc(ctx->cfg.tx_ring_num, sizeof(*ctx->tx), GFP_KERNEL); + ctx->tx = kvzalloc_objs(*ctx->tx, ctx->cfg.tx_ring_num); if (!ctx->tx) return -ENOMEM; - ctx->rx = kvcalloc(ctx->cfg.rx_ring_num, sizeof(*ctx->rx), GFP_KERNEL); + ctx->rx = kvzalloc_objs(*ctx->rx, ctx->cfg.rx_ring_num); if (!ctx->rx) goto err_free_tx; @@ -601,7 +601,7 @@ static int eea_netdev_init_features(struct net_device *netdev, int err; u32 mtu; - cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + cfg = kzalloc_obj(*cfg); if (!cfg) return -ENOMEM; diff --git a/drivers/net/ethernet/alibaba/eea/eea_pci.c b/drivers/net/ethernet/alibaba/eea/eea_pci.c index 9872e360dd5d..c625b58b26f0 100644 --- a/drivers/net/ethernet/alibaba/eea/eea_pci.c +++ b/drivers/net/ethernet/alibaba/eea/eea_pci.c @@ -651,7 +651,7 @@ static int eea_pci_probe(struct pci_dev *pci_dev, struct eea_device *edev; int err; - ep_dev = kzalloc(sizeof(*ep_dev), GFP_KERNEL); + ep_dev = kzalloc_obj(*ep_dev); if (!ep_dev) return -ENOMEM; diff --git a/drivers/net/ethernet/alibaba/eea/eea_ring.c b/drivers/net/ethernet/alibaba/eea/eea_ring.c index 99dcabd094b8..fac7d4647ecb 100644 --- a/drivers/net/ethernet/alibaba/eea/eea_ring.c +++ b/drivers/net/ethernet/alibaba/eea/eea_ring.c @@ -217,7 +217,7 @@ struct eea_ring *eea_ering_alloc(u32 index, u32 num, struct eea_device *edev, if (!cq_desc_size || !is_power_of_2(cq_desc_size)) return NULL; - ering = kzalloc(sizeof(*ering), GFP_KERNEL); + ering = kzalloc_obj(*ering); if (!ering) return NULL; diff --git a/drivers/net/ethernet/alibaba/eea/eea_rx.c b/drivers/net/ethernet/alibaba/eea/eea_rx.c index a3f0d2a79ad8..5dfb7cb2ba6d 100644 --- a/drivers/net/ethernet/alibaba/eea/eea_rx.c +++ b/drivers/net/ethernet/alibaba/eea/eea_rx.c @@ -764,7 +764,7 @@ struct eea_net_rx *eea_alloc_rx(struct eea_net_init_ctx *ctx, u32 idx) struct eea_net_rx *rx; int err; - rx = kzalloc(sizeof(*rx), GFP_KERNEL); + rx = kzalloc_obj(*rx); if (!rx) return rx; @@ -786,8 +786,7 @@ struct eea_net_rx *eea_alloc_rx(struct eea_net_init_ctx *ctx, u32 idx) rx->dma_dev = ctx->edev->dma_dev; /* meta */ - rx->meta = kvcalloc(ctx->cfg.rx_ring_depth, - sizeof(*rx->meta), GFP_KERNEL); + rx->meta = kvzalloc_objs(*rx->meta, ctx->cfg.rx_ring_depth); if (!rx->meta) goto err_free_rx; diff --git a/drivers/net/ethernet/alibaba/eea/eea_tx.c b/drivers/net/ethernet/alibaba/eea/eea_tx.c index 85fb0e9ca5ba..c9292bca1a8b 100644 --- a/drivers/net/ethernet/alibaba/eea/eea_tx.c +++ b/drivers/net/ethernet/alibaba/eea/eea_tx.c @@ -480,8 +480,7 @@ int eea_alloc_tx(struct eea_net_init_ctx *ctx, struct eea_net_tx *tx, u32 idx) tx->dma_dev = ctx->edev->dma_dev; /* meta */ - tx->meta = kvcalloc(ctx->cfg.tx_ring_depth, - sizeof(*tx->meta), GFP_KERNEL); + tx->meta = kvzalloc_objs(*tx->meta, ctx->cfg.tx_ring_depth); if (!tx->meta) goto err_free_tx; diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c index 922e3ec8af1b..419e4b6ac983 100644 --- a/drivers/net/ethernet/amd/pds_core/core.c +++ b/drivers/net/ethernet/amd/pds_core/core.c @@ -810,8 +810,7 @@ void pdsc_host_mem_add(struct pdsc *pdsc) if (count == 0) return; - pdsc->host_mem_reqs = kzalloc_objs(*pdsc->host_mem_reqs, count, - GFP_KERNEL); + pdsc->host_mem_reqs = kzalloc_objs(*pdsc->host_mem_reqs, count); if (!pdsc->host_mem_reqs) { dev_err(pdsc->dev, "failed to alloc host_mem_reqs array\n"); return; diff --git a/drivers/net/ethernet/amd/pds_core/fw.c b/drivers/net/ethernet/amd/pds_core/fw.c index 5ccf017f6af4..8551ce3cd86a 100644 --- a/drivers/net/ethernet/amd/pds_core/fw.c +++ b/drivers/net/ethernet/amd/pds_core/fw.c @@ -382,7 +382,7 @@ static int pdsc_send_package_data(struct pldmfw *context, const u8 *data, if (!length) return 0; - deferred = kmalloc_obj(*deferred, GFP_KERNEL); + deferred = kmalloc_obj(*deferred); if (!deferred) return -ENOMEM; @@ -505,7 +505,7 @@ static int pdsc_send_component_table(struct pldmfw *context, component->version_string, component->index, component->component_size, transfer_flag); - component_priv = kzalloc_obj(*component_priv, GFP_KERNEL); + component_priv = kzalloc_obj(*component_priv); if (!component_priv) return -ENOMEM; @@ -710,7 +710,7 @@ static int pdsc_flash_component_chunk(struct pdsc *pdsc, struct device *dev, u8 *component_data; int err; - deferred = kmalloc_obj(*deferred, GFP_KERNEL); + deferred = kmalloc_obj(*deferred); if (!deferred) return -ENOMEM; diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index b1939da4c95a..daa8a4a149e9 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -4334,7 +4334,7 @@ static int macb_taprio_setup_replace(struct net_device *netdev, return -EINVAL; } - enst_queue = kcalloc(conf->num_entries, sizeof(*enst_queue), GFP_KERNEL); + enst_queue = kzalloc_objs(*enst_queue, conf->num_entries); if (unlikely(!enst_queue)) return -ENOMEM; diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c index 7188f1b81c04..61c82b48044d 100644 --- a/drivers/net/ethernet/cisco/enic/enic_admin.c +++ b/drivers/net/ethernet/cisco/enic/enic_admin.c @@ -137,7 +137,7 @@ static void enic_admin_msg_enqueue(struct enic *enic, void *buf, { struct enic_admin_msg *msg; - msg = kmalloc(struct_size(msg, data, len), GFP_KERNEL); + msg = kmalloc_flex(*msg, data, len); if (!msg) return; diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c index 0baef7a120ec..65830c1d3ff6 100644 --- a/drivers/net/ethernet/cisco/enic/enic_main.c +++ b/drivers/net/ethernet/cisco/enic/enic_main.c @@ -2858,7 +2858,7 @@ enic_sriov_v2_enable(struct enic *enic, int num_vfs) return -EOPNOTSUPP; } - enic->vf_state = kcalloc(num_vfs, sizeof(*enic->vf_state), GFP_KERNEL); + enic->vf_state = kzalloc_objs(*enic->vf_state, num_vfs); if (!enic->vf_state) return -ENOMEM; @@ -2946,7 +2946,7 @@ enic_sriov_configure(struct pci_dev *pdev, int num_vfs) if (enic->vf_type == ENIC_VF_TYPE_V2) return enic_sriov_v2_enable(enic, num_vfs); - pp = kcalloc(num_vfs, sizeof(*pp), GFP_KERNEL); + pp = kzalloc_objs(*pp, num_vfs); if (!pp) return -ENOMEM; @@ -2971,7 +2971,7 @@ enic_sriov_configure(struct pci_dev *pdev, int num_vfs) return 0; } - pp = kzalloc_obj(*enic->pp, GFP_KERNEL); + pp = kzalloc_obj(*enic->pp); if (!pp) return -ENOMEM; diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c index 79d4a77f72bd..8cabce0eb2ab 100644 --- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c @@ -616,7 +616,7 @@ static int dpaa2_switch_lag_fdb_add(struct dpaa2_switch_lag *lag, goto out; } - a = kzalloc(sizeof(*a), GFP_KERNEL); + a = kzalloc_obj(*a); if (!a) { err = -ENOMEM; goto out; @@ -4131,8 +4131,7 @@ static int dpaa2_switch_probe(struct fsl_mc_device *sw_dev) goto err_free_fdbs; } - ethsw->lags = kcalloc(ethsw->sw_attr.num_ifs, sizeof(*ethsw->lags), - GFP_KERNEL); + ethsw->lags = kzalloc_objs(*ethsw->lags, ethsw->sw_attr.num_ifs); if (!ethsw->lags) { err = -ENOMEM; goto err_free_filter; diff --git a/drivers/net/ethernet/intel/libie/controlq.c b/drivers/net/ethernet/intel/libie/controlq.c index 45a49eba6a82..6214fc036ce5 100644 --- a/drivers/net/ethernet/intel/libie/controlq.c +++ b/drivers/net/ethernet/intel/libie/controlq.c @@ -157,8 +157,7 @@ static void libie_ctlq_free_tx_msgs(struct libie_ctlq_info *ctlq, */ static int libie_ctlq_alloc_tx_msgs(struct libie_ctlq_info *ctlq) { - ctlq->tx_msg = kvzalloc_objs(*ctlq->tx_msg, ctlq->ring_len, - GFP_KERNEL); + ctlq->tx_msg = kvzalloc_objs(*ctlq->tx_msg, ctlq->ring_len); if (!ctlq->tx_msg) return -ENOMEM; diff --git a/drivers/net/ethernet/marvell/octeontx2/af/cn20k/npc.c b/drivers/net/ethernet/marvell/octeontx2/af/cn20k/npc.c index 3070700b952b..2b67671a2bd8 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/cn20k/npc.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/cn20k/npc.c @@ -3647,7 +3647,7 @@ static int npc_defrag_add_2_show_list(struct rvu *rvu, u16 old_midx, { struct npc_defrag_show_node *node; - node = kcalloc(1, sizeof(*node), GFP_KERNEL); + node = kzalloc_objs(*node, 1); if (!node) return -ENOMEM; @@ -4082,7 +4082,7 @@ int npc_cn20k_defrag(struct rvu *rvu) INIT_LIST_HEAD(&x4lh); INIT_LIST_HEAD(&x2lh); - node = kcalloc(npc_priv->num_subbanks, sizeof(*node), GFP_KERNEL); + node = kzalloc_objs(*node, npc_priv->num_subbanks); if (!node) return -ENOMEM; @@ -4711,7 +4711,7 @@ static int npc_priv_init(struct rvu *rvu) return -EINVAL; } - npc_priv = kcalloc(1, sizeof(*npc_priv), GFP_KERNEL); + npc_priv = kzalloc_objs(*npc_priv, 1); if (!npc_priv) return -ENOMEM; @@ -4729,8 +4729,7 @@ static int npc_priv_init(struct rvu *rvu) num_banks, bank_depth, num_subbanks, subbank_depth, npc_kw_name[npc_priv->kw]); - npc_priv->sb = kcalloc(num_subbanks, sizeof(struct npc_subbank), - GFP_KERNEL); + npc_priv->sb = kzalloc_objs(struct npc_subbank, num_subbanks); if (!npc_priv->sb) goto fail1; @@ -4757,9 +4756,7 @@ static int npc_priv_init(struct rvu *rvu) /* Get number of pcifuncs in the system */ npc_priv->pf_cnt = npc_pcifunc_map_create(rvu); - npc_priv->xa_pf2idx_map = kcalloc(npc_priv->pf_cnt, - sizeof(struct xarray), - GFP_KERNEL); + npc_priv->xa_pf2idx_map = kzalloc_objs(struct xarray, npc_priv->pf_cnt); if (!npc_priv->xa_pf2idx_map) { ret = -ENOMEM; goto fail3; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index b6e2c153b4f7..60d477c4eb25 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -2288,8 +2288,7 @@ static int mlx5_esw_spfs_init(struct mlx5_eswitch *esw) if (!num_entries) goto out_free; - esw_funcs->spfs = kcalloc(num_entries, sizeof(*esw_funcs->spfs), - GFP_KERNEL); + esw_funcs->spfs = kzalloc_objs(*esw_funcs->spfs, num_entries); if (!esw_funcs->spfs) { err = -ENOMEM; goto out_free; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c index d603e294ee0e..eb74b6260168 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c @@ -1524,7 +1524,7 @@ struct mlx5_flow_handle * mlx5_esw_lag_demux_rule_create(struct mlx5_eswitch *esw, u16 vport_num, struct mlx5_flow_table *lag_ft) { - struct mlx5_flow_spec *spec = kvzalloc(sizeof(*spec), GFP_KERNEL); + struct mlx5_flow_spec *spec = kvzalloc_obj(*spec); struct mlx5_flow_destination dest = {}; struct mlx5_flow_act flow_act = {}; struct mlx5_flow_handle *ret; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/sf/hw_table.c b/drivers/net/ethernet/mellanox/mlx5/core/sf/hw_table.c index 95a8b1e64ba4..1724bf7e0e7a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/sf/hw_table.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/sf/hw_table.c @@ -317,7 +317,7 @@ int mlx5_sf_hw_table_init(struct mlx5_core_dev *dev) num_spfs = mlx5_esw_get_num_spfs(dev); num_hwc = MLX5_SF_HWC_FIRST_SPF + num_spfs; - table->hwc = kcalloc(num_hwc, sizeof(*table->hwc), GFP_KERNEL); + table->hwc = kzalloc_objs(*table->hwc, num_hwc); if (!table->hwc) { err = -ENOMEM; goto hwc_alloc_err; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/sws/dr_icm_pool.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/sws/dr_icm_pool.c index fa4d24b3dfaa..1b928b40686e 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/sws/dr_icm_pool.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/sws/dr_icm_pool.c @@ -239,7 +239,7 @@ static int dr_icm_buddy_init_ste_cache(struct mlx5dr_icm_buddy_mem *buddy) if (!buddy->hw_ste_arr) goto free_ste_arr; - buddy->miss_list = kvmalloc_array(num_of_entries, sizeof(struct list_head), GFP_KERNEL); + buddy->miss_list = kvmalloc_objs(struct list_head, num_of_entries); if (!buddy->miss_list) goto free_hw_ste_arr; diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_irq.c b/drivers/net/ethernet/meta/fbnic/fbnic_irq.c index 5e383d40abc7..ec3b628ecb2d 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_irq.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_irq.c @@ -282,7 +282,7 @@ enum fbnic_msix_self_test_codes fbnic_msix_test(struct fbnic_dev *fbd) int i; /* Allocate bitmap and IRQ vector table */ - test_data = kzalloc_obj(*test_data, GFP_KERNEL); + test_data = kzalloc_obj(*test_data); /* memory allocation failure */ if (!test_data) diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index f92b2d0bf926..8e9bfc1d6a2a 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -429,11 +429,11 @@ int mana_gd_alloc_memory(struct gdma_context *gc, unsigned int length, /* length is a power of 2 above PAGE_SIZE, so this divides exactly. */ npages = length / PAGE_SIZE; - gmi->pages_va = kvcalloc(npages, sizeof(*gmi->pages_va), GFP_KERNEL); + gmi->pages_va = kvzalloc_objs(*gmi->pages_va, npages); if (!gmi->pages_va) return -ENOMEM; - gmi->pages_dma = kvcalloc(npages, sizeof(*gmi->pages_dma), GFP_KERNEL); + gmi->pages_dma = kvzalloc_objs(*gmi->pages_dma, npages); if (!gmi->pages_dma) goto free_va; @@ -825,7 +825,7 @@ int mana_schedule_serv_work(struct gdma_context *gc, enum gdma_eqe_type type) return -ENODEV; } - mns_wk = kzalloc(sizeof(*mns_wk), GFP_ATOMIC); + mns_wk = kzalloc_obj(*mns_wk, GFP_ATOMIC); if (!mns_wk) { module_put(THIS_MODULE); clear_bit(GC_IN_SERVICE, &gc->flags); @@ -1991,7 +1991,7 @@ struct gdma_irq_context *mana_gd_get_gic(struct gdma_context *gc, *msi_requested = msi; } - gic = kzalloc(sizeof(*gic), GFP_KERNEL); + gic = kzalloc_obj(*gic); if (!gic) { gic = ERR_PTR(-ENOMEM); if (irq_map.virq) diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c index 2c04be6d61a8..7a0d5e892a1a 100644 --- a/drivers/net/ntb_netdev.c +++ b/drivers/net/ntb_netdev.c @@ -659,8 +659,7 @@ static int ntb_netdev_probe(struct device *client_dev) dev->client_dev = client_dev; dev->num_queues = 0; - dev->queues = kzalloc_objs(*dev->queues, NTB_NETDEV_MAX_QUEUES, - GFP_KERNEL); + dev->queues = kzalloc_objs(*dev->queues, NTB_NETDEV_MAX_QUEUES); if (!dev->queues) { rc = -ENOMEM; goto err_free_netdev; diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 0fc55c9169e1..d89a49c6ebb7 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -870,7 +870,7 @@ static struct ath12k_ahb_rproc_info *ath12k_ahb_rproc_info_alloc(struct ath12k_b lockdep_assert_held(&ath12k_rproc_info_lock); - rproc_info = kzalloc_obj(*rproc_info, GFP_KERNEL); + rproc_info = kzalloc_obj(*rproc_info); if (!rproc_info) return NULL; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c index 533870cf443f..6db84c2117f2 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/regulatory.c @@ -514,8 +514,7 @@ void iwl_mld_init_ap_type_tables(struct iwl_mld *mld) return; if (iwl_fw_lookup_cmd_ver(mld->fw, cmd.id, 1) == 1) { - struct iwl_mcc_allowed_ap_type_cmd_v1 *cmd_v1 = - kzalloc(sizeof(*cmd_v1), GFP_KERNEL); + struct iwl_mcc_allowed_ap_type_cmd_v1 *cmd_v1 = kzalloc_obj(*cmd_v1); if (!cmd_v1) return; diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c index 4a8ea4624fee..fcdeb65cd8ef 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c @@ -289,7 +289,7 @@ int mt7921_regd_update(struct mt792x_phy *phy, char *alpha2) goto err; } - regd = kzalloc(struct_size(regd, reg_rules, num_of_rules), GFP_KERNEL); + regd = kzalloc_flex(*regd, reg_rules, num_of_rules); if (!regd) { ret = -ENOMEM; goto err; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 84b55f008b3d..5993b31e1aae 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1105,7 +1105,7 @@ mt7925_mac_sta_add_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, mlink = &msta->deflink; is_deflink = true; } else { - mlink = kzalloc(sizeof(*mlink), GFP_KERNEL); + mlink = kzalloc_obj(*mlink); if (!mlink) { err = -ENOMEM; break; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c index f4beb7f52043..57f1736b54a3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c @@ -336,7 +336,7 @@ int mt7925_regd_update(struct mt792x_phy *phy, char *alpha2) goto err; } - regd = kzalloc(struct_size(regd, reg_rules, num_of_rules), GFP_KERNEL); + regd = kzalloc_flex(*regd, reg_rules, num_of_rules); if (!regd) { ret = -ENOMEM; goto err; diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.c b/drivers/net/wireless/morsemicro/mm81x/mac.c index 08ca116a68b4..0fa80b1488aa 100644 --- a/drivers/net/wireless/morsemicro/mm81x/mac.c +++ b/drivers/net/wireless/morsemicro/mm81x/mac.c @@ -706,8 +706,7 @@ static int mm81x_hw_scan_h_init_chan_list(struct mm81x_hw_scan_params *params, params->num_chans = 0; params->allocated_chans = 0; - params->channels = kcalloc(chans_to_allocate, sizeof(*params->channels), - GFP_KERNEL); + params->channels = kzalloc_objs(*params->channels, chans_to_allocate); if (!params->channels) return -ENOMEM; @@ -728,8 +727,8 @@ static int mm81x_hw_scan_h_init_chan_list(struct mm81x_hw_scan_params *params, } } - params->powers_qdbm = kmalloc_array( - num_pwrs_coarse, sizeof(*params->powers_qdbm), GFP_KERNEL); + params->powers_qdbm = kmalloc_objs(*params->powers_qdbm, + num_pwrs_coarse); if (!params->powers_qdbm) return -ENOMEM; @@ -822,7 +821,7 @@ __mm81x_hw_scan_h_init_params(struct mm81x *mors) struct mm81x_hw_scan_params *params = mors->hw_scan.params; if (!params) { - params = kzalloc_obj(*params, GFP_KERNEL); + params = kzalloc_obj(*params); if (params) mors->hw_scan.params = params; } else { diff --git a/drivers/net/wireless/morsemicro/mm81x/yaps.c b/drivers/net/wireless/morsemicro/mm81x/yaps.c index e98a2a58726f..2d1728c1a6de 100644 --- a/drivers/net/wireless/morsemicro/mm81x/yaps.c +++ b/drivers/net/wireless/morsemicro/mm81x/yaps.c @@ -22,15 +22,13 @@ static int mm81x_yaps_alloc_pkt_buffers(struct mm81x_yaps *yaps) { - yaps->hw.to_chip_pkts = kcalloc(MAX_PKTS_PER_TX_TXN, - sizeof(*yaps->hw.to_chip_pkts), - GFP_KERNEL); + yaps->hw.to_chip_pkts = kzalloc_objs(*yaps->hw.to_chip_pkts, + MAX_PKTS_PER_TX_TXN); if (!yaps->hw.to_chip_pkts) return -ENOMEM; - yaps->hw.from_chip_pkts = kcalloc(MAX_PKTS_PER_RX_TXN, - sizeof(*yaps->hw.from_chip_pkts), - GFP_KERNEL); + yaps->hw.from_chip_pkts = kzalloc_objs(*yaps->hw.from_chip_pkts, + MAX_PKTS_PER_RX_TXN); if (!yaps->hw.from_chip_pkts) { kfree(yaps->hw.to_chip_pkts); yaps->hw.to_chip_pkts = NULL; diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 5cc8cdf594d3..86304ea3331c 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -641,7 +641,7 @@ nxpwifi_cfg80211_set_wiphy_params(struct wiphy *wiphy, int radio_idx, u32 change switch (priv->bss_role) { case NXPWIFI_BSS_ROLE_UAP: - bss_cfg = kzalloc_obj(*bss_cfg, GFP_KERNEL); + bss_cfg = kzalloc_obj(*bss_cfg); if (!bss_cfg) { ret = -ENOMEM; break; @@ -1713,7 +1713,7 @@ static int nxpwifi_cfg80211_start_ap(struct wiphy *wiphy, if (!nxpwifi_is_channel_setting_allowable(priv, params->chandef.chan)) return -EOPNOTSUPP; - bss_cfg = kzalloc_obj(*bss_cfg, GFP_KERNEL); + bss_cfg = kzalloc_obj(*bss_cfg); if (!bss_cfg) return -ENOMEM; @@ -1866,7 +1866,7 @@ nxpwifi_cfg80211_scan(struct wiphy *wiphy, if (!nxpwifi_stop_bg_scan(priv)) cfg80211_sched_scan_stopped_locked(priv->wdev.wiphy, 0); - user_scan_cfg = kzalloc_obj(*user_scan_cfg, GFP_KERNEL); + user_scan_cfg = kzalloc_obj(*user_scan_cfg); if (!user_scan_cfg) return -ENOMEM; @@ -1973,7 +1973,7 @@ nxpwifi_cfg80211_sched_scan_start(struct wiphy *wiphy, request->n_channels, request->scan_plans->interval, (int)request->ie_len); - bgscan_cfg = kzalloc_obj(*bgscan_cfg, GFP_KERNEL); + bgscan_cfg = kzalloc_obj(*bgscan_cfg); if (!bgscan_cfg) return -ENOMEM; @@ -2304,7 +2304,7 @@ nxpwifi_setup_he_caps(struct nxpwifi_private *priv, if (!hw_he_cap_len) return; - iftype_data = kmalloc_obj(*iftype_data, GFP_KERNEL); + iftype_data = kmalloc_obj(*iftype_data); if (!iftype_data) return; memset(iftype_data, 0, sizeof(*iftype_data)); @@ -2759,7 +2759,7 @@ static int nxpwifi_set_mef_filter(struct nxpwifi_private *priv, if (wowlan->n_patterns || wowlan->magic_pkt) num_entries++; - mef_entry = kzalloc_objs(*mef_entry, num_entries, GFP_KERNEL); + mef_entry = kzalloc_objs(*mef_entry, num_entries); if (!mef_entry) return -ENOMEM; @@ -3227,7 +3227,7 @@ static int nxpwifi_tm_cmd(struct wiphy *wiphy, struct wireless_dev *wdev, if (!tb[NXPWIFI_TM_ATTR_DATA]) return -EINVAL; - hostcmd = kzalloc_obj(*hostcmd, GFP_KERNEL); + hostcmd = kzalloc_obj(*hostcmd); if (!hostcmd) return -ENOMEM; diff --git a/drivers/net/wireless/nxp/nxpwifi/cmdevt.c b/drivers/net/wireless/nxp/nxpwifi/cmdevt.c index 4eb17ada5db8..fc1950a125b6 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cmdevt.c +++ b/drivers/net/wireless/nxp/nxpwifi/cmdevt.c @@ -302,7 +302,7 @@ int nxpwifi_alloc_cmd_buffer(struct nxpwifi_adapter *adapter) /* Allocate and initialize struct cmd_ctrl_node */ cmd_array = kzalloc_objs(struct cmd_ctrl_node, - NXPWIFI_NUM_OF_CMD_BUFFER, GFP_KERNEL); + NXPWIFI_NUM_OF_CMD_BUFFER); if (!cmd_array) return -ENOMEM; diff --git a/drivers/net/wireless/nxp/nxpwifi/ie.c b/drivers/net/wireless/nxp/nxpwifi/ie.c index 158755c0c905..86b18d50feaf 100644 --- a/drivers/net/wireless/nxp/nxpwifi/ie.c +++ b/drivers/net/wireless/nxp/nxpwifi/ie.c @@ -143,7 +143,7 @@ nxpwifi_update_uap_custom_ie(struct nxpwifi_private *priv, u16 len; int ret; - ap_custom_ie = kzalloc_obj(*ap_custom_ie, GFP_KERNEL); + ap_custom_ie = kzalloc_obj(*ap_custom_ie); if (!ap_custom_ie) return -ENOMEM; @@ -209,7 +209,7 @@ static int nxpwifi_update_vs_ie(const u8 *ies, int ies_len, vendor_ie = cfg80211_find_vendor_ie(oui, oui_type, ies, ies_len); if (vendor_ie) { if (!*ie_ptr) { - *ie_ptr = kzalloc_obj(struct nxpwifi_ie, GFP_KERNEL); + *ie_ptr = kzalloc_obj(struct nxpwifi_ie); if (!*ie_ptr) return -ENOMEM; ie = *ie_ptr; @@ -309,7 +309,7 @@ static int nxpwifi_uap_parse_tail_ies(struct nxpwifi_private *priv, if (!info->tail || !info->tail_len) return 0; - gen_ie = kzalloc_obj(*gen_ie, GFP_KERNEL); + gen_ie = kzalloc_obj(*gen_ie); if (!gen_ie) return -ENOMEM; @@ -417,7 +417,7 @@ int nxpwifi_del_mgmt_ies(struct nxpwifi_private *priv) int ret = 0; if (priv->gen_idx != NXPWIFI_AUTO_IDX_MASK) { - gen_ie = kmalloc_obj(*gen_ie, GFP_KERNEL); + gen_ie = kmalloc_obj(*gen_ie); if (!gen_ie) return -ENOMEM; @@ -434,7 +434,7 @@ int nxpwifi_del_mgmt_ies(struct nxpwifi_private *priv) } if (priv->beacon_idx != NXPWIFI_AUTO_IDX_MASK) { - beacon_ie = kmalloc_obj(*beacon_ie, GFP_KERNEL); + beacon_ie = kmalloc_obj(*beacon_ie); if (!beacon_ie) { ret = -ENOMEM; goto done; @@ -444,7 +444,7 @@ int nxpwifi_del_mgmt_ies(struct nxpwifi_private *priv) beacon_ie->ie_length = 0; } if (priv->proberesp_idx != NXPWIFI_AUTO_IDX_MASK) { - pr_ie = kmalloc_obj(*pr_ie, GFP_KERNEL); + pr_ie = kmalloc_obj(*pr_ie); if (!pr_ie) { ret = -ENOMEM; goto done; @@ -454,7 +454,7 @@ int nxpwifi_del_mgmt_ies(struct nxpwifi_private *priv) pr_ie->ie_length = 0; } if (priv->assocresp_idx != NXPWIFI_AUTO_IDX_MASK) { - ar_ie = kmalloc_obj(*ar_ie, GFP_KERNEL); + ar_ie = kmalloc_obj(*ar_ie); if (!ar_ie) { ret = -ENOMEM; goto done; diff --git a/drivers/net/wireless/nxp/nxpwifi/init.c b/drivers/net/wireless/nxp/nxpwifi/init.c index b128fc9fe31a..7c75dca5d0a8 100644 --- a/drivers/net/wireless/nxp/nxpwifi/init.c +++ b/drivers/net/wireless/nxp/nxpwifi/init.c @@ -20,7 +20,7 @@ static int nxpwifi_add_bss_prio_tbl(struct nxpwifi_private *priv) struct nxpwifi_bss_prio_node *bss_prio; struct nxpwifi_bss_prio_tbl *tbl = adapter->bss_prio_tbl; - bss_prio = kzalloc_obj(*bss_prio, GFP_KERNEL); + bss_prio = kzalloc_obj(*bss_prio); if (!bss_prio) return -ENOMEM; diff --git a/drivers/net/wireless/nxp/nxpwifi/main.c b/drivers/net/wireless/nxp/nxpwifi/main.c index b4c63829024a..55b962430f37 100644 --- a/drivers/net/wireless/nxp/nxpwifi/main.c +++ b/drivers/net/wireless/nxp/nxpwifi/main.c @@ -32,7 +32,7 @@ static struct nxpwifi_adapter *nxpwifi_register(void *card, struct device *dev, int ret = 0; int i; - adapter = kzalloc_obj(*adapter, GFP_KERNEL); + adapter = kzalloc_obj(*adapter); if (!adapter) return ERR_PTR(-ENOMEM); @@ -55,7 +55,7 @@ static struct nxpwifi_adapter *nxpwifi_register(void *card, struct device *dev, for (i = 0; i < NXPWIFI_MAX_BSS_NUM; i++) { /* Allocate memory for private structure */ adapter->priv[i] = - kzalloc_obj(struct nxpwifi_private, GFP_KERNEL); + kzalloc_obj(struct nxpwifi_private); if (!adapter->priv[i]) { ret = -ENOMEM; goto error; @@ -1106,7 +1106,7 @@ void nxpwifi_drv_info_dump(struct nxpwifi_adapter *adapter) p += adapter->if_ops.reg_dump(adapter, p); } p += sprintf(p, "\n=== more debug information\n"); - debug_info = kzalloc_obj(*debug_info, GFP_KERNEL); + debug_info = kzalloc_obj(*debug_info); if (debug_info) { for (i = 0; i < adapter->priv_num; i++) { if (!adapter->priv[i]->netdev) @@ -1242,7 +1242,7 @@ void nxpwifi_init_priv_params(struct nxpwifi_private *priv, if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA || GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) { - priv->hist_data = kmalloc_obj(*priv->hist_data, GFP_KERNEL); + priv->hist_data = kmalloc_obj(*priv->hist_data); if (priv->hist_data) nxpwifi_hist_data_reset(priv); } diff --git a/drivers/net/wireless/nxp/nxpwifi/scan.c b/drivers/net/wireless/nxp/nxpwifi/scan.c index b77056983e83..67d7ff2f10c5 100644 --- a/drivers/net/wireless/nxp/nxpwifi/scan.c +++ b/drivers/net/wireless/nxp/nxpwifi/scan.c @@ -1341,15 +1341,14 @@ int nxpwifi_scan_networks(struct nxpwifi_private *priv, adapter->scan_processing = true; spin_unlock_bh(&adapter->nxpwifi_cmd_lock); - scan_cfg_out = kzalloc_obj(union nxpwifi_scan_cmd_config_tlv, - GFP_KERNEL); + scan_cfg_out = kzalloc_obj(union nxpwifi_scan_cmd_config_tlv); if (!scan_cfg_out) { ret = -ENOMEM; goto done; } scan_chan_list = kzalloc_objs(struct nxpwifi_chan_scan_param_set, - NXPWIFI_USER_SCAN_CHAN_MAX, GFP_KERNEL); + NXPWIFI_USER_SCAN_CHAN_MAX); if (!scan_chan_list) { kfree(scan_cfg_out); ret = -ENOMEM; @@ -1471,7 +1470,7 @@ static int nxpwifi_save_hidden_ssid_channels(struct nxpwifi_private *priv, int chid; /* Allocate and fill new bss descriptor */ - bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL); + bss_desc = kzalloc_obj(*bss_desc); if (!bss_desc) return -ENOMEM; @@ -1512,7 +1511,7 @@ static int nxpwifi_update_curr_bss_params(struct nxpwifi_private *priv, int ret; /* Allocate and fill new bss descriptor */ - bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL); + bss_desc = kzalloc_obj(*bss_desc); if (!bss_desc) return -ENOMEM; @@ -1751,7 +1750,7 @@ nxpwifi_active_scan_req_for_passive_chan(struct nxpwifi_private *priv) nxpwifi_dbg(adapter, INFO, "No BSS with hidden SSID found on DFS channels\n"); return 0; } - user_scan_cfg = kzalloc_obj(*user_scan_cfg, GFP_KERNEL); + user_scan_cfg = kzalloc_obj(*user_scan_cfg); if (!user_scan_cfg) return -ENOMEM; @@ -2258,7 +2257,7 @@ int nxpwifi_stop_bg_scan(struct nxpwifi_private *priv) return 0; } - bgscan_cfg = kzalloc_obj(*bgscan_cfg, GFP_KERNEL); + bgscan_cfg = kzalloc_obj(*bgscan_cfg); if (!bgscan_cfg) return -ENOMEM; diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c b/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c index 702fa1531da1..56cf63fbf7fe 100644 --- a/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c +++ b/drivers/net/wireless/nxp/nxpwifi/sta_cfg.c @@ -245,7 +245,7 @@ int nxpwifi_bss_start(struct nxpwifi_private *priv, struct cfg80211_bss *bss, return -EINVAL; /* Allocate and fill new bss descriptor */ - bss_desc = kzalloc_obj(*bss_desc, GFP_KERNEL); + bss_desc = kzalloc_obj(*bss_desc); if (!bss_desc) return -ENOMEM; diff --git a/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c b/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c index 5e8ffd306b31..9bfa4aebc3e5 100644 --- a/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c +++ b/drivers/net/wireless/nxp/nxpwifi/sta_cmd.c @@ -2822,7 +2822,7 @@ nxpwifi_create_custom_regdomain(struct nxpwifi_private *priv, if (WARN_ON_ONCE(num_chan > NL80211_MAX_SUPP_REG_RULES)) return ERR_PTR(-EINVAL); - regd = kzalloc_flex(*regd, reg_rules, num_chan, GFP_KERNEL); + regd = kzalloc_flex(*regd, reg_rules, num_chan); if (!regd) return ERR_PTR(-ENOMEM); diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_event.c b/drivers/net/wireless/nxp/nxpwifi/uap_event.c index 9f717a3d7ec5..ab5c15686f54 100644 --- a/drivers/net/wireless/nxp/nxpwifi/uap_event.c +++ b/drivers/net/wireless/nxp/nxpwifi/uap_event.c @@ -88,7 +88,7 @@ nxpwifi_uap_event_sta_assoc(struct nxpwifi_private *priv) struct nxpwifi_sta_node *node; int len, i; - sinfo = kzalloc_obj(*sinfo, GFP_KERNEL); + sinfo = kzalloc_obj(*sinfo); if (!sinfo) return -ENOMEM; diff --git a/drivers/nvdimm/region_devs.c b/drivers/nvdimm/region_devs.c index 24f42b4650ba..09842ca68110 100644 --- a/drivers/nvdimm/region_devs.c +++ b/drivers/nvdimm/region_devs.c @@ -1002,8 +1002,7 @@ static struct nd_region *nd_region_create(struct nvdimm_bus *nvdimm_bus, nd_region->num_lanes = ndr_desc->num_lanes; if (!nd_region->num_lanes) goto err_percpu; - nd_region->lane = kcalloc(nd_region->num_lanes, - sizeof(*nd_region->lane), GFP_KERNEL); + nd_region->lane = kzalloc_objs(*nd_region->lane, nd_region->num_lanes); if (!nd_region->lane) goto err_percpu; diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 1322c678f4eb..b16cec1ff51f 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2382,8 +2382,7 @@ static int nvme_query_fdp_info(struct nvme_ns *ns, struct nvme_ns_info *info) if (!head->nr_plids) goto free; - head->plids = kcalloc(head->nr_plids, sizeof(*head->plids), - GFP_KERNEL); + head->plids = kzalloc_objs(*head->plids, head->nr_plids); if (!head->plids) { dev_warn(ctrl->device, "failed to allocate %u FDP placement IDs\n", diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index da93b505d239..5440cf18b55b 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -201,7 +201,7 @@ static int quirks_param_set(const char *value, const struct kernel_param *kp) count++; } - qlist = kcalloc(count, sizeof(*qlist), GFP_KERNEL); + qlist = kzalloc_objs(*qlist, count); if (!qlist) { err = -ENOMEM; goto out_free_val; diff --git a/drivers/opp/core.c b/drivers/opp/core.c index cd0e82dae776..2fafd983de8f 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -348,7 +348,7 @@ unsigned long dev_pm_opp_get_max_volt_latency(struct device *dev) count = opp_table->regulator_count; - uV = kmalloc_array(count, sizeof(*uV), GFP_KERNEL); + uV = kmalloc_objs(*uV, count); if (!uV) return 0; @@ -1505,7 +1505,7 @@ struct opp_device *_add_opp_dev(const struct device *dev, { struct opp_device *opp_dev; - opp_dev = kzalloc(sizeof(*opp_dev), GFP_KERNEL); + opp_dev = kzalloc_obj(*opp_dev); if (!opp_dev) return NULL; diff --git a/drivers/pci/endpoint/pci-ep-msi.c b/drivers/pci/endpoint/pci-ep-msi.c index 0855c7930abb..9c13f193537d 100644 --- a/drivers/pci/endpoint/pci-ep-msi.c +++ b/drivers/pci/endpoint/pci-ep-msi.c @@ -57,7 +57,7 @@ static int pci_epf_alloc_doorbell_embedded(struct pci_epf *epf, u16 num_db) return -ENODEV; struct pci_epc_aux_resource *res __free(kfree) = - kcalloc(count, sizeof(*res), GFP_KERNEL); + kzalloc_objs(*res, count); if (!res) return -ENOMEM; @@ -98,7 +98,7 @@ static int pci_epf_alloc_doorbell_embedded(struct pci_epf *epf, u16 num_db) addr = iova_base + off; } - msg = kcalloc(num_db, sizeof(*msg), GFP_KERNEL); + msg = kzalloc_objs(*msg, num_db); if (!msg) { ret = -ENOMEM; goto err_unmap; diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 8257cd1da48e..ddd7a04ee753 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -720,9 +720,8 @@ static int hsmp_acpi_probe(struct platform_device *pdev) return -ENODEV; } - hsmp_pdev->sock = kcalloc(hsmp_pdev->num_sockets, - sizeof(*hsmp_pdev->sock), - GFP_KERNEL); + hsmp_pdev->sock = kzalloc_objs(*hsmp_pdev->sock, + hsmp_pdev->num_sockets); if (!hsmp_pdev->sock) return -ENOMEM; diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c index 446dd18d2cee..72a4c2107d7e 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c @@ -96,8 +96,8 @@ int hp_alloc_enumeration_data(void) if (!bioscfg_drv.enumeration_instances_count) return -EINVAL; - bioscfg_drv.enumeration_data = kvcalloc(bioscfg_drv.enumeration_instances_count, - sizeof(*bioscfg_drv.enumeration_data), GFP_KERNEL); + bioscfg_drv.enumeration_data = kvzalloc_objs(*bioscfg_drv.enumeration_data, + bioscfg_drv.enumeration_instances_count); if (!bioscfg_drv.enumeration_data) { bioscfg_drv.enumeration_instances_count = 0; diff --git a/drivers/platform/x86/intel/pmc/pwrm_telemetry.c b/drivers/platform/x86/intel/pmc/pwrm_telemetry.c index 4cde241e01d6..013f779f20a4 100644 --- a/drivers/platform/x86/intel/pmc/pwrm_telemetry.c +++ b/drivers/platform/x86/intel/pmc/pwrm_telemetry.c @@ -93,8 +93,7 @@ acpi_disc_t pmc_parse_telem_dsd(union acpi_object *obj, if (header->num_entries != num_regions) return ERR_PTR(-EINVAL); - acpi_disc_t disc __free(kfree) = kmalloc_array(num_regions, sizeof(*disc), - GFP_KERNEL); + acpi_disc_t disc __free(kfree) = kmalloc_objs(*disc, num_regions); if (!disc) return ERR_PTR(-ENOMEM); diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index 842c4169e290..052ec478dfcc 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -2962,7 +2962,7 @@ int of_genpd_add_child_ids(struct device_node *np, return -EINVAL; /* Allocate tracking array for error unwind (parent/child pairs) */ - pairs = kmalloc_array(count * 2, sizeof(*pairs), GFP_KERNEL); + pairs = kmalloc_objs(*pairs, count * 2); if (!pairs) return -ENOMEM; diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c index af00c00eceee..3611bed341e1 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -122,14 +122,14 @@ static int reboot_mode_create_device(struct reboot_mode_driver *reboot) struct mode_info *info; int ret; - priv = kzalloc_obj(*priv, GFP_KERNEL); + priv = kzalloc_obj(*priv); if (!priv) return -ENOMEM; INIT_LIST_HEAD(&priv->head); list_for_each_entry(info, &reboot->head, list) { - sysfs_info = kzalloc_obj(*sysfs_info, GFP_KERNEL); + sysfs_info = kzalloc_obj(*sysfs_info); if (!sysfs_info) { ret = -ENOMEM; goto error; @@ -188,7 +188,7 @@ int reboot_mode_register(struct reboot_mode_driver *reboot) continue; } - info = kzalloc_obj(*info, GFP_KERNEL); + info = kzalloc_obj(*info); if (!info) { ret = -ENOMEM; goto error; diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 721e888b658d..0cb71efbb268 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -480,7 +480,7 @@ pwrseq_device_register(const struct pwrseq_config *config) !config->targets[0]) return ERR_PTR(-EINVAL); - pwrseq = kzalloc(sizeof(*pwrseq), GFP_KERNEL); + pwrseq = kzalloc_obj(*pwrseq); if (!pwrseq) return ERR_PTR(-ENOMEM); diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index de9848a9a9f1..471ffe914a4e 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -291,7 +291,7 @@ static int pwrseq_pcie_m2_create_serdev_one(struct pwrseq_pcie_m2_ctx *ctx, } } - pci_dev = kzalloc(sizeof(*pci_dev), GFP_KERNEL); + pci_dev = kzalloc_obj(*pci_dev); if (!pci_dev) { ret = -ENOMEM; goto err_put_ctrl; diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 00d8bc98d588..47e307709e5e 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -904,7 +904,7 @@ int power_supply_get_battery_info(struct power_supply *psy, goto out_put_node; } - u32 *propdata __free(kfree) = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL); + u32 *propdata __free(kfree) = kzalloc_objs(*propdata, proplen); if (!propdata) { power_supply_put_battery_info(psy, info); err = -EINVAL; @@ -944,7 +944,7 @@ int power_supply_get_battery_info(struct power_supply *psy, goto out_put_node; } - propdata = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL); + propdata = kzalloc_objs(*propdata, proplen); if (!propdata) { power_supply_put_battery_info(psy, info); err = -ENOMEM; @@ -1726,7 +1726,7 @@ __power_supply_register(struct device *parent, pr_warn("%s: Expected proper parent device for '%s'\n", __func__, desc->name); - psy = kzalloc(sizeof(*psy), GFP_KERNEL); + psy = kzalloc_obj(*psy); if (!psy) return ERR_PTR(-ENOMEM); diff --git a/drivers/ptp/ptp_chardev.c b/drivers/ptp/ptp_chardev.c index dc23cd708cfe..f63b45f5ee6a 100644 --- a/drivers/ptp/ptp_chardev.c +++ b/drivers/ptp/ptp_chardev.c @@ -136,7 +136,7 @@ int ptp_open(struct posix_clock_context *pccontext, fmode_t fmode) struct timestamp_event_queue *queue; char debugfsname[32]; - queue = kzalloc(sizeof(*queue), GFP_KERNEL); + queue = kzalloc_obj(*queue); if (!queue) return -EINVAL; queue->mask = bitmap_alloc(PTP_MAX_CHANNELS, GFP_KERNEL); diff --git a/drivers/s390/block/dasd.c b/drivers/s390/block/dasd.c index 5979758311c8..2ffe404b890f 100644 --- a/drivers/s390/block/dasd.c +++ b/drivers/s390/block/dasd.c @@ -87,7 +87,7 @@ struct dasd_device *dasd_alloc_device(void) { struct dasd_device *device; - device = kzalloc_obj(struct dasd_device, GFP_KERNEL); + device = kzalloc_obj(struct dasd_device); if (!device) return ERR_PTR(-ENOMEM); diff --git a/drivers/scsi/fnic/fnic_debugfs.c b/drivers/scsi/fnic/fnic_debugfs.c index 61f167e20574..21b7e9666b74 100644 --- a/drivers/scsi/fnic/fnic_debugfs.c +++ b/drivers/scsi/fnic/fnic_debugfs.c @@ -767,7 +767,7 @@ static int fnic_nvmef_debugfs_open(struct inode *inode, struct file *file) struct fnic_nvmef_info *info; int buf_size = 2 * PAGE_SIZE; - info = kzalloc_obj(struct fnic_nvmef_info, GFP_KERNEL); + info = kzalloc_obj(struct fnic_nvmef_info); if (!info) return -ENOMEM; diff --git a/drivers/scsi/leapraid/leapraid_func.c b/drivers/scsi/leapraid/leapraid_func.c index 089d0810bd13..66b43c1c72e1 100644 --- a/drivers/scsi/leapraid/leapraid_func.c +++ b/drivers/scsi/leapraid/leapraid_func.c @@ -2984,8 +2984,7 @@ static void leapraid_fw_evt_put(struct leapraid_fw_evt_work *fw_work) static struct leapraid_fw_evt_work *leapraid_alloc_fw_evt_work(void) { - struct leapraid_fw_evt_work *fw_evt = - kzalloc(sizeof(*fw_evt), GFP_ATOMIC); + struct leapraid_fw_evt_work *fw_evt = kzalloc_obj(*fw_evt, GFP_ATOMIC); if (fw_evt) kref_init(&fw_evt->refcnt); @@ -3644,8 +3643,7 @@ static void leapraid_sas_host_add(struct leapraid_adapter *adapter, return; adapter->dev_topo.card.card_phy = - kcalloc(phys_num, - sizeof(struct leapraid_card_phy), GFP_KERNEL); + kzalloc_objs(struct leapraid_card_phy, phys_num); if (!adapter->dev_topo.card.card_phy) return; @@ -3763,8 +3761,7 @@ static int leapraid_internal_exp_add(struct leapraid_adapter *adapter, } topo_node_exp->card_phy = - kcalloc(topo_node_exp->phys_num, - sizeof(struct leapraid_card_phy), GFP_KERNEL); + kzalloc_objs(struct leapraid_card_phy, topo_node_exp->phys_num); if (!topo_node_exp->card_phy) { dev_err(&adapter->pdev->dev, "%s: Failed to alloc expander phy array, count=%u\n", @@ -4352,7 +4349,7 @@ static void leapraid_sas_volume_add( return; } - raid_volume = kzalloc(sizeof(*raid_volume), GFP_KERNEL); + raid_volume = kzalloc_obj(*raid_volume); if (!raid_volume) return; @@ -6090,17 +6087,15 @@ static void leapraid_update_card_port_after_reset( if (!adapter->dev_topo.card.card_phy) { adapter->dev_topo.card.card_phy = - kcalloc(nr_phys, sizeof(struct leapraid_card_phy), - GFP_KERNEL); + kzalloc_objs(struct leapraid_card_phy, nr_phys); if (!adapter->dev_topo.card.card_phy) return; } adapter->dev_topo.card.phys_num = nr_phys; - new_card_port_table = kcalloc(adapter->dev_topo.card.phys_num, - sizeof(struct leapraid_card_port), - GFP_KERNEL); + new_card_port_table = kzalloc_objs(struct leapraid_card_port, + adapter->dev_topo.card.phys_num); if (!new_card_port_table) return; @@ -7224,8 +7219,8 @@ static int leapraid_set_legacy_int(struct leapraid_adapter *adapter) adapter->notification_desc.iopoll_qdex, adapter->notification_desc.iopoll_qcnt); adapter->notification_desc.int_rqs = - kcalloc(adapter->notification_desc.iopoll_qdex, - sizeof(struct leapraid_int_rq), GFP_KERNEL); + kzalloc_objs(struct leapraid_int_rq, + adapter->notification_desc.iopoll_qdex); if (!adapter->notification_desc.int_rqs) return -ENOMEM; @@ -7268,9 +7263,8 @@ static int leapraid_set_msix(struct leapraid_adapter *adapter) } if (iopoll_qcnt) { adapter->notification_desc.blk_mq_poll_rqs = - kcalloc(iopoll_qcnt, - sizeof(struct leapraid_blk_mq_poll_rq), - GFP_KERNEL); + kzalloc_objs(struct leapraid_blk_mq_poll_rq, + iopoll_qcnt); if (!adapter->notification_desc.blk_mq_poll_rqs) return -ENOMEM; adapter->adapter_attr.rq_cnt = @@ -7289,8 +7283,8 @@ static int leapraid_set_msix(struct leapraid_adapter *adapter) adapter->notification_desc.iopoll_qcnt); adapter->notification_desc.int_rqs = - kcalloc(adapter->notification_desc.iopoll_qdex, - sizeof(struct leapraid_int_rq), GFP_KERNEL); + kzalloc_objs(struct leapraid_int_rq, + adapter->notification_desc.iopoll_qdex); if (!adapter->notification_desc.int_rqs) return -ENOMEM; @@ -7365,9 +7359,8 @@ static int leapraid_set_msi(struct leapraid_adapter *adapter) if (iopoll_qcnt) { adapter->notification_desc.blk_mq_poll_rqs = - kcalloc(iopoll_qcnt, - sizeof(struct leapraid_blk_mq_poll_rq), - GFP_KERNEL); + kzalloc_objs(struct leapraid_blk_mq_poll_rq, + iopoll_qcnt); if (!adapter->notification_desc.blk_mq_poll_rqs) return -ENOMEM; @@ -7404,9 +7397,8 @@ static int leapraid_set_msi(struct leapraid_adapter *adapter) adapter->notification_desc.iopoll_qcnt); adapter->notification_desc.int_rqs = - kcalloc(adapter->notification_desc.iopoll_qdex, - sizeof(struct leapraid_int_rq), - GFP_KERNEL); + kzalloc_objs(struct leapraid_int_rq, + adapter->notification_desc.iopoll_qdex); if (!adapter->notification_desc.int_rqs) return -ENOMEM; @@ -7958,18 +7950,16 @@ static int leapraid_request_host_memory(struct leapraid_adapter *adapter) DIV_ROUND_UP(adapter->adapter_attr.rq_cnt, LEAPRAID_REP_DESC_CHUNK_SIZE); adapter->mem_desc.rep_desc_seg_maint = - kcalloc(adapter->adapter_attr.rep_desc_q_seg_cnt, - sizeof(struct leapraid_rep_desc_seg_maint), - GFP_KERNEL); + kzalloc_objs(struct leapraid_rep_desc_seg_maint, + adapter->adapter_attr.rep_desc_q_seg_cnt); if (!adapter->mem_desc.rep_desc_seg_maint) return -ENOMEM; rep_desc_q_cnt_allocated = 0; for (i = 0; i < adapter->adapter_attr.rep_desc_q_seg_cnt; i++) { adapter->mem_desc.rep_desc_seg_maint[i].rep_desc_maint = - kcalloc(LEAPRAID_REP_DESC_CHUNK_SIZE, - sizeof(struct leapraid_rep_desc_maint), - GFP_KERNEL); + kzalloc_objs(struct leapraid_rep_desc_maint, + LEAPRAID_REP_DESC_CHUNK_SIZE); if (!adapter->mem_desc.rep_desc_seg_maint[i].rep_desc_maint) return -ENOMEM; diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c index 3b82e80e807a..0f0f243c2561 100644 --- a/drivers/scsi/scsi_scan.c +++ b/drivers/scsi/scsi_scan.c @@ -158,7 +158,7 @@ int scsi_complete_async_scans(void) * sleep a little. Even if we never get memory, the async * scans will finish eventually. */ - data = kmalloc(sizeof(*data), GFP_KERNEL); + data = kmalloc_obj(*data); if (!data) msleep(1); } while (!data); diff --git a/drivers/soc/bcm/brcmstb/common.c b/drivers/soc/bcm/brcmstb/common.c index 7be0374f5943..a903fa44e4e5 100644 --- a/drivers/soc/bcm/brcmstb/common.c +++ b/drivers/soc/bcm/brcmstb/common.c @@ -65,7 +65,7 @@ static int __init brcmstb_soc_device_init(void) goto out_put_node; } - soc_info = kzalloc(sizeof(*soc_info), GFP_KERNEL); + soc_info = kzalloc_obj(*soc_info); if (!soc_info) { ret = -ENOMEM; goto out_unmap; diff --git a/drivers/spi/spi-offload.c b/drivers/spi/spi-offload.c index a579ef33b2d2..a446927a51d3 100644 --- a/drivers/spi/spi-offload.c +++ b/drivers/spi/spi-offload.c @@ -434,7 +434,7 @@ int devm_spi_offload_trigger_register(struct device *dev, if (!info->fwnode || !info->ops || !info->ops->match) return -EINVAL; - trigger = kzalloc(sizeof(*trigger), GFP_KERNEL); + trigger = kzalloc_obj(*trigger); if (!trigger) return -ENOMEM; diff --git a/drivers/staging/greybus/raw.c b/drivers/staging/greybus/raw.c index 459aed0f1240..4f1b3f4db404 100644 --- a/drivers/staging/greybus/raw.c +++ b/drivers/staging/greybus/raw.c @@ -178,7 +178,7 @@ static int gb_raw_probe(struct gb_bundle *bundle, if (minor < 0) return minor; - raw = kzalloc_obj(*raw, GFP_KERNEL); + raw = kzalloc_obj(*raw); if (!raw) { ida_free(&minors, minor); return -ENOMEM; diff --git a/drivers/staging/media/atomisp/pci/sh_css.c b/drivers/staging/media/atomisp/pci/sh_css.c index 00082276f1db..4a182dc384ca 100644 --- a/drivers/staging/media/atomisp/pci/sh_css.c +++ b/drivers/staging/media/atomisp/pci/sh_css.c @@ -5820,36 +5820,31 @@ static int ia_css_pipe_create_cas_scaler_desc_single_output( } descr->in_info = kmalloc_objs(*descr->in_info, - descr->num_stage, - GFP_KERNEL); + descr->num_stage); if (!descr->in_info) { err = -ENOMEM; goto ERR; } descr->internal_out_info = kmalloc_objs(*descr->internal_out_info, - descr->num_stage, - GFP_KERNEL); + descr->num_stage); if (!descr->internal_out_info) { err = -ENOMEM; goto ERR; } descr->out_info = kmalloc_objs(*descr->out_info, - descr->num_stage, - GFP_KERNEL); + descr->num_stage); if (!descr->out_info) { err = -ENOMEM; goto ERR; } descr->vf_info = kmalloc_objs(*descr->vf_info, - descr->num_stage, - GFP_KERNEL); + descr->num_stage); if (!descr->vf_info) { err = -ENOMEM; goto ERR; } descr->is_output_stage = kmalloc_objs(*descr->is_output_stage, - descr->num_stage, - GFP_KERNEL); + descr->num_stage); if (!descr->is_output_stage) { err = -ENOMEM; goto ERR; @@ -5970,36 +5965,31 @@ ia_css_pipe_create_cas_scaler_desc(struct ia_css_pipe *pipe, descr->num_stage = num_stages; descr->in_info = kmalloc_objs(*descr->in_info, - descr->num_stage, - GFP_KERNEL); + descr->num_stage); if (!descr->in_info) { err = -ENOMEM; goto ERR; } descr->internal_out_info = kmalloc_objs(*descr->internal_out_info, - descr->num_stage, - GFP_KERNEL); + descr->num_stage); if (!descr->internal_out_info) { err = -ENOMEM; goto ERR; } descr->out_info = kmalloc_objs(*descr->out_info, - descr->num_stage, - GFP_KERNEL); + descr->num_stage); if (!descr->out_info) { err = -ENOMEM; goto ERR; } descr->vf_info = kmalloc_objs(*descr->vf_info, - descr->num_stage, - GFP_KERNEL); + descr->num_stage); if (!descr->vf_info) { err = -ENOMEM; goto ERR; } descr->is_output_stage = kmalloc_objs(*descr->is_output_stage, - descr->num_stage, - GFP_KERNEL); + descr->num_stage); if (!descr->is_output_stage) { err = -ENOMEM; goto ERR; diff --git a/drivers/staging/media/atomisp/pci/sh_css_firmware.c b/drivers/staging/media/atomisp/pci/sh_css_firmware.c index af12df2f9b09..b895dee77568 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_firmware.c +++ b/drivers/staging/media/atomisp/pci/sh_css_firmware.c @@ -254,8 +254,8 @@ sh_css_load_firmware(struct device *dev, const char *fw_data, /* Only allocate memory for ISP blob info */ if (sh_css_num_binaries > NUM_OF_SPS) { sh_css_blob_info = - kmalloc_array(sh_css_num_binaries - NUM_OF_SPS, - sizeof(*sh_css_blob_info), GFP_KERNEL); + kmalloc_objs(*sh_css_blob_info, + sh_css_num_binaries - NUM_OF_SPS); if (!sh_css_blob_info) return -ENOMEM; } else { diff --git a/drivers/tee/qcomtee/user_obj.c b/drivers/tee/qcomtee/user_obj.c index 10452fcc7ccb..a06eb94e0bae 100644 --- a/drivers/tee/qcomtee/user_obj.c +++ b/drivers/tee/qcomtee/user_obj.c @@ -230,8 +230,7 @@ static int qcomtee_user_object_dispatch(struct qcomtee_object_invoke_ctx *oic, struct qcomtee_context_data *ctxdata = uo->ctx->data; int errno; - struct qcomtee_ureq *ureq __free(kfree) = kzalloc(sizeof(*ureq), - GFP_KERNEL); + struct qcomtee_ureq *ureq __free(kfree) = kzalloc_obj(*ureq); if (!ureq) return -ENOMEM; diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index c737dd0ca6e7..25c259dd0760 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -408,8 +408,7 @@ static int tbstream_dev_alloc_rx_buffers(struct tbstream_dev *sdev) size_t ring_size = tb_ring_size(sdev->rx_ring.ring); int i; - sdev->rx_ring.frames = kcalloc(ring_size, sizeof(struct tbstream_frame), - GFP_KERNEL); + sdev->rx_ring.frames = kzalloc_objs(struct tbstream_frame, ring_size); if (!sdev->rx_ring.frames) return -ENOMEM; @@ -463,8 +462,7 @@ static int tbstream_dev_alloc_tx_buffers(struct tbstream_dev *sdev) size_t ring_size = tb_ring_size(sdev->tx_ring.ring); int i; - sdev->tx_ring.frames = kcalloc(ring_size, sizeof(struct tbstream_frame), - GFP_KERNEL); + sdev->tx_ring.frames = kzalloc_objs(struct tbstream_frame, ring_size); if (!sdev->tx_ring.frames) return -ENOMEM; @@ -1498,7 +1496,7 @@ tbstream_dev_make_group(struct config_group *group, const char *name) if (strlen(name) > TB_PROPERTY_KEY_SIZE) return ERR_PTR(-ENAMETOOLONG); - sdev = kzalloc_obj(*sdev, GFP_KERNEL); + sdev = kzalloc_obj(*sdev); if (!sdev) return ERR_PTR(-ENOMEM); @@ -1592,7 +1590,7 @@ tbstream_make_group(struct config_group *group, const char *name) if (sscanf(name, "%u-%llx.%u", &domain, &route, &index) != 3) return ERR_PTR(-EINVAL); - sg = kzalloc_obj(*sg, GFP_KERNEL); + sg = kzalloc_obj(*sg); if (!sg) return ERR_PTR(-ENOMEM); @@ -1698,7 +1696,7 @@ static int tbstream_probe(struct tb_service *svc) { struct tbstream *stream; - stream = kzalloc_obj(*stream, GFP_KERNEL); + stream = kzalloc_obj(*stream); if (!stream) return -ENOMEM; diff --git a/drivers/tty/moxa.c b/drivers/tty/moxa.c index 1bb2376af85c..40a1c614e23b 100644 --- a/drivers/tty/moxa.c +++ b/drivers/tty/moxa.c @@ -954,8 +954,7 @@ static int moxa_init_board(struct moxa_board_conf *brd, struct device *dev) unsigned int i, first_idx; int ret; - brd->ports = kcalloc(MAX_PORTS_PER_BOARD, sizeof(*brd->ports), - GFP_KERNEL); + brd->ports = kzalloc_objs(*brd->ports, MAX_PORTS_PER_BOARD); if (brd->ports == NULL) { printk(KERN_ERR "cannot allocate memory for ports\n"); ret = -ENOMEM; diff --git a/drivers/tty/vt/consolemap.c b/drivers/tty/vt/consolemap.c index 3fa89a2dbeba..7d564341a7eb 100644 --- a/drivers/tty/vt/consolemap.c +++ b/drivers/tty/vt/consolemap.c @@ -776,7 +776,8 @@ int con_get_unimap(struct vc_data *vc, ushort ct, ushort __user *uct, struct uni_pagedict *dict; unsigned int d, r, g; - struct unipair *unilist __free(kvfree) = kvmalloc_array(ct, sizeof(*unilist), GFP_KERNEL); + struct unipair *unilist __free(kvfree) = kvmalloc_objs(*unilist, ct, + GFP_KERNEL); if (!unilist) return -ENOMEM; diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c index fa5f539632eb..6192512e29de 100644 --- a/drivers/ufs/core/ufs-txeq.c +++ b/drivers/ufs/core/ufs-txeq.c @@ -1073,7 +1073,7 @@ static int __ufshcd_tx_eqtr(struct ufs_hba *hba, struct ufs_pa_layer_attr *pwr_mode) { struct ufshcd_tx_eqtr_data *eqtr_data __free(kfree) = - kzalloc(sizeof(*eqtr_data), GFP_KERNEL); + kzalloc_obj(*eqtr_data); struct tx_eqtr_iter h_iter = {}; struct tx_eqtr_iter d_iter = {}; u32 gear = pwr_mode->gear_tx; diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c index 62396212a0a7..65c0816bc675 100644 --- a/drivers/ufs/host/ufs-qcom.c +++ b/drivers/ufs/host/ufs-qcom.c @@ -2761,7 +2761,7 @@ static int ufs_qcom_get_rx_fom(struct ufs_hba *hba, struct tx_eqtr_iter *d_iter) { struct ufshcd_tx_eq_params *params __free(kfree) = - kzalloc(sizeof(*params), GFP_KERNEL); + kzalloc_obj(*params); struct ufs_qcom_host *host = ufshcd_get_variant(hba); struct ufs_pa_layer_attr old_pwr_info; u32 fom[PA_MAXDATALANES] = { 0 }; diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index bf02545b37a2..500c9c19c78b 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -1766,7 +1766,7 @@ static struct usb_function *ncm_alloc(struct usb_function_instance *fi) int status; /* allocate and initialize one new instance */ - ncm = kzalloc(sizeof(*ncm), GFP_KERNEL); + ncm = kzalloc_obj(*ncm); if (!ncm) return ERR_PTR(-ENOMEM); diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c index a5837c0feb05..100f8920624b 100644 --- a/drivers/usb/usbip/usbip_common.c +++ b/drivers/usb/usbip/usbip_common.c @@ -696,7 +696,7 @@ int usbip_recv_iso(struct usbip_device *ud, struct urb *urb) size = np * sizeof(*iso); - buff = kcalloc(np, sizeof(*iso), GFP_KERNEL); + buff = kzalloc_objs(*iso, np); if (!buff) return -ENOMEM; diff --git a/drivers/xen/grant-table.c b/drivers/xen/grant-table.c index 69922be28b54..076c1b0ab87f 100644 --- a/drivers/xen/grant-table.c +++ b/drivers/xen/grant-table.c @@ -1478,7 +1478,7 @@ static int gnttab_map(unsigned int start_idx, unsigned int end_idx) /* No need for kzalloc as it is initialized in following hypercall * GNTTABOP_setup_table. */ - frames = kmalloc_array(nr_gframes, sizeof(*frames), GFP_ATOMIC); + frames = kmalloc_objs(*frames, nr_gframes, GFP_ATOMIC); if (!frames) return -ENOMEM; diff --git a/fs/9p/vfs_dentry.c b/fs/9p/vfs_dentry.c index e549e222602e..fa6b7143db98 100644 --- a/fs/9p/vfs_dentry.c +++ b/fs/9p/vfs_dentry.c @@ -113,8 +113,7 @@ void v9fs_dentry_fid_remove(struct dentry *dentry) */ static int v9fs_dentry_init(struct dentry *dentry) { - struct v9fs_dentry *v9fs_dentry = kzalloc(sizeof(*v9fs_dentry), - GFP_KERNEL); + struct v9fs_dentry *v9fs_dentry = kzalloc_obj(*v9fs_dentry); if (!v9fs_dentry) return -ENOMEM; diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 81565366d937..2db534a2c7cc 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -1801,7 +1801,7 @@ static int afs_symlink(struct mnt_idmap *idmap, struct inode *dir, goto error; ret = -ENOMEM; - symlink = kmalloc_flex(struct afs_symlink, content, clen + 1, GFP_KERNEL); + symlink = kmalloc_flex(struct afs_symlink, content, clen + 1); if (!symlink) goto error; refcount_set(&symlink->ref, 1); diff --git a/fs/afs/symlink.c b/fs/afs/symlink.c index 16b4823cb7b7..6b8c122877ca 100644 --- a/fs/afs/symlink.c +++ b/fs/afs/symlink.c @@ -119,8 +119,7 @@ static ssize_t afs_do_read_symlink(struct afs_vnode *vnode) vnode->directory_size = i_size; /* Copy the symlink. */ - symlink = kmalloc_flex(struct afs_symlink, content, i_size + 1, - GFP_KERNEL); + symlink = kmalloc_flex(struct afs_symlink, content, i_size + 1); if (!symlink) return -ENOMEM; diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index ddfd3aa57ac8..620da85948b4 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -331,8 +331,8 @@ static int entry_attach_interpreter(struct binfmt_misc_entry *e, return -ENOSPC; /* One allocation, both strings in it, like the entry's own buffer. */ - interp = kmalloc(struct_size(interp, name, nlen + plen + 2), - GFP_KERNEL_ACCOUNT); + interp = kmalloc_flex(*interp, name, nlen + plen + 2, + GFP_KERNEL_ACCOUNT); if (!interp) { dec_ucount(ucounts, UCOUNT_BINFMT_MISC_INTERPRETERS); return -ENOMEM; @@ -858,8 +858,7 @@ static struct binfmt_misc_entry *create_entry(const char __user *buffer, if ((count < 11) || (count > MAX_REGISTER_LENGTH)) return ERR_PTR(-EINVAL); - e = kmalloc(struct_size(e, buf, count + MISC_DELIM_PAD), - GFP_KERNEL_ACCOUNT); + e = kmalloc_flex(*e, buf, count + MISC_DELIM_PAD, GFP_KERNEL_ACCOUNT); if (!e) return ERR_PTR(-ENOMEM); diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index 657c2cb0f881..e598b2d424ec 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -2546,7 +2546,7 @@ static int __ceph_pool_perm_get(struct ceph_inode_info *ci, } pool_ns_len = pool_ns ? pool_ns->len : 0; - perm = kmalloc_flex(*perm, pool_ns, pool_ns_len + 1, GFP_KERNEL); + perm = kmalloc_flex(*perm, pool_ns, pool_ns_len + 1); if (!perm) { err = -ENOMEM; goto out_unlock; diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index a091f77cedaf..cc5fea0a9012 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -5492,7 +5492,7 @@ static void ceph_mdsc_reset_workfn(struct work_struct *work) goto out_complete; } - sessions = kcalloc(max_sessions, sizeof(*sessions), GFP_KERNEL); + sessions = kzalloc_objs(*sessions, max_sessions); if (!sessions) { mutex_unlock(&mdsc->mutex); ret = -ENOMEM; diff --git a/fs/ceph/subvolume_metrics.c b/fs/ceph/subvolume_metrics.c index 03fda1f9257b..01419c9482f1 100644 --- a/fs/ceph/subvolume_metrics.c +++ b/fs/ceph/subvolume_metrics.c @@ -245,7 +245,7 @@ int ceph_subvolume_metrics_snapshot(struct ceph_subvolume_metrics_tracker *track return 0; } - snap = kcalloc(count, sizeof(*snap), GFP_NOFS); + snap = kzalloc_objs(*snap, count, GFP_NOFS); if (!snap) { atomic64_inc(&tracker->snapshot_failures); return -ENOMEM; diff --git a/fs/coredump.c b/fs/coredump.c index ac3cd74808c6..6114839f5178 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -1000,7 +1000,7 @@ static bool coredump_pipe(struct core_name *cn, struct coredump_params *cprm, return false; } - helper_argv = kmalloc_array(argc + 1, sizeof(*helper_argv), GFP_KERNEL); + helper_argv = kmalloc_objs(*helper_argv, argc + 1); if (!helper_argv) { coredump_report_failure("%s failed to allocate memory", __func__); return false; diff --git a/fs/ext4/fast_commit.c b/fs/ext4/fast_commit.c index 062103e42cd8..0cac890cf370 100644 --- a/fs/ext4/fast_commit.c +++ b/fs/ext4/fast_commit.c @@ -1116,7 +1116,7 @@ static int ext4_fc_snapshot_inode(struct inode *inode, else if (EXT4_INODE_SIZE(inode->i_sb) > EXT4_GOOD_OLD_INODE_SIZE) inode_len += ei->i_extra_isize; - snap = kmalloc(struct_size(snap, inode_buf, inode_len), GFP_NOFS); + snap = kmalloc_flex(*snap, inode_buf, inode_len, GFP_NOFS); if (!snap) { atomic64_inc(&stats->snap_fail_nomem); ext4_fc_set_snap_err(snap_err, EXT4_FC_SNAP_ERR_NOMEM); @@ -1522,7 +1522,7 @@ static int ext4_fc_alloc_snapshot_inodes(struct super_block *sb, if (nr_inodes > EXT4_FC_SNAPSHOT_MAX_INODES) return -E2BIG; - inodes = kvcalloc(nr_inodes, sizeof(*inodes), GFP_NOFS); + inodes = kvzalloc_objs(*inodes, nr_inodes, GFP_NOFS); if (!inodes) return -ENOMEM; diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 8d6135a6108a..9a36d0329e22 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1597,8 +1597,7 @@ static int fuse_get_user_pages(struct fuse_args_pages *ap, struct iov_iter *ii, * manually extract pages using iov_iter_extract_pages() and then * copy that to a folios array. */ - struct page **pages = kcalloc(max_pages, sizeof(struct page *), - GFP_KERNEL); + struct page **pages = kzalloc_objs(struct page *, max_pages); if (!pages) { ret = -ENOMEM; goto out; diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c index 5ca87151d70d..d2599043f7ec 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -336,7 +336,7 @@ static int parse_dirplusfile(char *buf, size_t nbytes, struct file *file, static struct page **fuse_readdir_alloc_buf(struct fuse_args_pages *ap, size_t *bufsize) { unsigned int i, nr_alloc, nr_pages = DIV_ROUND_UP(*bufsize, PAGE_SIZE); - struct page **pages = kcalloc(nr_pages, sizeof(*pages), GFP_KERNEL); + struct page **pages = kzalloc_objs(*pages, nr_pages); if (!pages) return NULL; diff --git a/fs/hfs/bnode.c b/fs/hfs/bnode.c index 1b331108d9c0..fcb5b9cd17f6 100644 --- a/fs/hfs/bnode.c +++ b/fs/hfs/bnode.c @@ -312,7 +312,7 @@ static struct hfs_bnode *__hfs_bnode_create(struct hfs_btree *tree, u32 cnid) return NULL; } - node = kzalloc_flex(*node, page, tree->pages_per_bnode, GFP_KERNEL); + node = kzalloc_flex(*node, page, tree->pages_per_bnode); if (!node) return NULL; node->tree = tree; diff --git a/fs/namespace.c b/fs/namespace.c index 1ecd96c918b3..ae5dc64f8b45 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -5999,7 +5999,7 @@ SYSCALL_DEFINE4(statmount, const struct mnt_id_req __user *, req, return -EPERM; } - ks = kmalloc(sizeof(*ks), GFP_KERNEL_ACCOUNT); + ks = kmalloc_obj(*ks, GFP_KERNEL_ACCOUNT); if (!ks) return -ENOMEM; diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index a47c90f40422..a7ebce53faec 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -358,7 +358,7 @@ int nfsd_nl_expkey_get_reqs_dumpit(struct sk_buff *skb, goto out_unlock; } - items = kcalloc(cnt, sizeof(*items), GFP_KERNEL); + items = kzalloc_objs(*items, cnt); seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL); if (!items || !seqnos) { ret = -ENOMEM; @@ -685,7 +685,7 @@ int nfsd_nl_svc_export_get_reqs_dumpit(struct sk_buff *skb, goto out_unlock; } - items = kcalloc(cnt, sizeof(*items), GFP_KERNEL); + items = kzalloc_objs(*items, cnt); seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL); pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); if (!items || !seqnos || !pathbuf) { @@ -786,8 +786,7 @@ static int nfsd_nl_parse_fslocations(struct nlattr *attr, if (!count) return 0; - fsloc->locations = kcalloc(count, sizeof(struct nfsd4_fs_location), - GFP_KERNEL); + fsloc->locations = kzalloc_objs(struct nfsd4_fs_location, count); if (!fsloc->locations) return -ENOMEM; diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c index a901bbe67e03..19dc337502ca 100644 --- a/fs/nfsd/nfs4callback.c +++ b/fs/nfsd/nfs4callback.c @@ -1981,12 +1981,12 @@ int nfsd_net_cb_init(struct nfsd_net *nn) { struct nfsd_net_cb *cb; - cb = kzalloc(sizeof(*cb), GFP_KERNEL); + cb = kzalloc_obj(*cb); if (!cb) return -ENOMEM; cb->version4.counts = kzalloc_objs(unsigned int, - ARRAY_SIZE(nfs4_cb_procedures), GFP_KERNEL); + ARRAY_SIZE(nfs4_cb_procedures)); if (!cb->version4.counts) { kfree(cb); return -ENOMEM; diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 18e17232cf94..9c4adf3110ae 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1341,7 +1341,7 @@ alloc_init_dir_deleg(struct nfs4_client *clp, struct nfs4_file *fp) return NULL; } - ncn->ncn_nf = kcalloc(NOTIFY4_EVENT_QUEUE_SIZE, sizeof(*ncn->ncn_nf), GFP_KERNEL); + ncn->ncn_nf = kzalloc_objs(*ncn->ncn_nf, NOTIFY4_EVENT_QUEUE_SIZE); if (!ncn->ncn_nf) { nfs4_put_stid(&dp->dl_stid); return NULL; @@ -10419,8 +10419,9 @@ alloc_nfsd_notify_event(u32 mask, const struct qstr *q, struct dentry *dentry, newnamelen = newname.name.len; } - ne = kmalloc(struct_size(ne, ne_name, q->len + 1 + - (newnamelen ? newnamelen + 1 : 0)), GFP_NOFS); + ne = kmalloc_flex(*ne, ne_name, + q->len + 1 + (newnamelen ? newnamelen + 1 : 0), + GFP_NOFS); if (!ne) goto out; diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index adb032b7311a..5abb2d4274c9 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -1647,7 +1647,7 @@ static int nfsd_nl_fh_key_set(const struct nlattr *attr, struct nfsd_net *nn) k1 = get_unaligned_le64(nla_data(attr) + 8); if (!fh_key) { - fh_key = kmalloc(sizeof(siphash_key_t), GFP_KERNEL); + fh_key = kmalloc_obj(siphash_key_t); if (!fh_key) { trace_nfsd_ctl_fh_key_set(false, -ENOMEM); return -ENOMEM; diff --git a/fs/ntfs/bitmap.c b/fs/ntfs/bitmap.c index 1840b7d84c62..5a4457551306 100644 --- a/fs/ntfs/bitmap.c +++ b/fs/ntfs/bitmap.c @@ -40,7 +40,7 @@ int ntfs_trim_fs(struct ntfs_volume *vol, struct fstrim_range *range) end_cluster = vol->nr_clusters; } - ra = kzalloc(sizeof(*ra), GFP_NOFS); + ra = kzalloc_obj(*ra, GFP_NOFS); if (!ra) return -ENOMEM; diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c index 197d8607fc63..99a3ea2b5c55 100644 --- a/fs/ntfs/compress.c +++ b/fs/ntfs/compress.c @@ -514,8 +514,8 @@ int ntfs_read_compressed_block(struct folio *folio) return -EIO; } - pages = kmalloc_array(nr_pages, sizeof(struct page *), GFP_NOFS); - completed_pages = kmalloc_array(nr_pages + 1, sizeof(int), GFP_NOFS); + pages = kmalloc_objs(struct page *, nr_pages, GFP_NOFS); + completed_pages = kmalloc_objs(int, nr_pages + 1, GFP_NOFS); if (unlikely(!pages || !completed_pages)) { kfree(pages); @@ -1262,7 +1262,7 @@ static int ntfs_compress_workspace_init(struct ntfs_inode *ni, size = ni->itype.compressed.block_size + 2 * (ni->itype.compressed.block_size / NTFS_SB_SIZE) + 2; ws->nr_pages = DIV_ROUND_UP(size, PAGE_SIZE); - ws->pages = kcalloc(ws->nr_pages, sizeof(*ws->pages), GFP_NOFS); + ws->pages = kzalloc_objs(*ws->pages, ws->nr_pages, GFP_NOFS); if (!ws->pages) return -ENOMEM; @@ -1483,7 +1483,7 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count, pages_per_cb = DIV_ROUND_UP(offset_in_page(pos & ~(cb_size - 1)) + cb_size, PAGE_SIZE); - pages = kmalloc_array(pages_per_cb, sizeof(struct page *), GFP_NOFS); + pages = kmalloc_objs(struct page *, pages_per_cb, GFP_NOFS); if (!pages) return -ENOMEM; ctx = kvzalloc_obj(*ctx, GFP_NOFS); diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c index 2d594cbb4ebe..df60138f9b2d 100644 --- a/fs/ntfs/dir.c +++ b/fs/ntfs/dir.c @@ -166,8 +166,8 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname, */ if (ie->key.file_name.file_name_type == FILE_NAME_DOS) { if (!name) { - name = kmalloc(sizeof(struct ntfs_name), - GFP_NOFS); + name = kmalloc_obj(struct ntfs_name, + GFP_NOFS); if (!name) { err = -ENOMEM; goto err_out; @@ -401,8 +401,8 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname, */ if (ie->key.file_name.file_name_type == FILE_NAME_DOS) { if (!name) { - name = kmalloc(sizeof(struct ntfs_name), - GFP_NOFS); + name = kmalloc_obj(struct ntfs_name, + GFP_NOFS); if (!name) { err = -ENOMEM; goto unm_err_out; @@ -700,7 +700,7 @@ static int ntfs_ia_blocks_readahead(struct ntfs_inode *ia_ni, loff_t pos) if (dir_start_index >= dir_end_index) return 0; - dir_ra = kzalloc(sizeof(*dir_ra), GFP_NOFS); + dir_ra = kzalloc_obj(*dir_ra, GFP_NOFS); if (!dir_ra) return -ENOMEM; @@ -777,7 +777,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor) return -ENOMEM; } - ra = kzalloc(sizeof(struct file_ra_state), GFP_NOFS); + ra = kzalloc_obj(struct file_ra_state, GFP_NOFS); if (!ra) { kfree(name); ntfs_index_ctx_put(ictx); @@ -813,7 +813,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor) goto out; } } else if (!private) { - private = kzalloc(sizeof(struct ntfs_file_private), GFP_KERNEL); + private = kzalloc_obj(struct ntfs_file_private); if (!private) { err = -ENOMEM; goto out; @@ -949,7 +949,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor) } if (!nir) { - nir = kzalloc(sizeof(struct ntfs_index_ra), GFP_KERNEL); + nir = kzalloc_obj(struct ntfs_index_ra); if (nir) { nir->start_index = index; nir->count = 1; diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 3f4ba7667522..b4fcfbe2da4c 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -235,7 +235,7 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len, ea_info_qsize = le32_to_cpu(p_ea_info->ea_query_length); } else { create_ea_info: - p_ea_info = kzalloc(sizeof(struct ea_information), GFP_NOFS); + p_ea_info = kzalloc_obj(struct ea_information, GFP_NOFS); if (!p_ea_info) return -ENOMEM; diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c index 46a8b19c0723..580998990bc9 100644 --- a/fs/ntfs/index.c +++ b/fs/ntfs/index.c @@ -1660,7 +1660,7 @@ static int ntfs_ib_split(struct ntfs_index_context *icx, struct index_block *ib) goto out; } } else { - si = kzalloc(sizeof(struct split_info), GFP_NOFS); + si = kzalloc_obj(struct split_info, GFP_NOFS); if (!si) { ntfs_ibm_clear(icx, new_vcn); ret = -ENOMEM; diff --git a/fs/ntfs/logfile.c b/fs/ntfs/logfile.c index 024ddee42dc8..1404664dacc0 100644 --- a/fs/ntfs/logfile.c +++ b/fs/ntfs/logfile.c @@ -691,7 +691,7 @@ bool ntfs_empty_logfile(struct inode *log_vi) memset(empty_buf, 0xff, vol->cluster_size); - ra = kzalloc(sizeof(*ra), GFP_NOFS); + ra = kzalloc_obj(*ra, GFP_NOFS); if (!ra) goto err; diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 7e58c99f1728..98ab686a5ea2 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -2635,11 +2635,13 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w struct ntfs_inode *ni = NTFS_I(vi); struct ntfs_volume *vol = ni->vol; u8 *kaddr; - struct ntfs_inode **locked_nis __free(kfree) = kmalloc_array(PAGE_SIZE / NTFS_BLOCK_SIZE, - sizeof(struct ntfs_inode *), GFP_NOFS); + struct ntfs_inode **locked_nis __free(kfree) = kmalloc_objs(struct ntfs_inode *, + PAGE_SIZE / NTFS_BLOCK_SIZE, + GFP_NOFS); int nr_locked_nis = 0, err = 0, mft_ofs, prev_mft_ofs; - struct inode **ref_inos __free(kfree) = kmalloc_array(PAGE_SIZE / NTFS_BLOCK_SIZE, - sizeof(struct inode *), GFP_NOFS); + struct inode **ref_inos __free(kfree) = kmalloc_objs(struct inode *, + PAGE_SIZE / NTFS_BLOCK_SIZE, + GFP_NOFS); int nr_ref_inos = 0; struct bio *bio = NULL; u64 mft_no; diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c index 00373e450ea7..3a61f19bcbee 100644 --- a/fs/ntfs/runlist.c +++ b/fs/ntfs/runlist.c @@ -1804,7 +1804,7 @@ struct runlist_element *ntfs_rl_insert_range(struct runlist_element *dst_rl, int new_2nd_cnt = src_cnt; new_cnt = new_1st_cnt + new_2nd_cnt + new_3rd_cnt; new_cnt += dst_rl_split.lcn >= LCN_HOLE ? 1 : 0; - new_rl = kvcalloc(new_cnt, sizeof(*new_rl), GFP_NOFS); + new_rl = kvzalloc_objs(*new_rl, new_cnt, GFP_NOFS); if (!new_rl) return ERR_PTR(-ENOMEM); @@ -1888,13 +1888,13 @@ struct runlist_element *ntfs_rl_punch_hole(struct runlist_element *dst_rl, int d punch_cnt = (int)(e_rl - s_rl) + 1; - *punch_rl = kvcalloc(punch_cnt + 1, sizeof(struct runlist_element), - GFP_NOFS); + *punch_rl = kvzalloc_objs(struct runlist_element, punch_cnt + 1, + GFP_NOFS); if (!*punch_rl) return ERR_PTR(-ENOMEM); new_cnt = dst_cnt - (int)(e_rl - s_rl + 1) + 3; - new_rl = kvcalloc(new_cnt, sizeof(struct runlist_element), GFP_NOFS); + new_rl = kvzalloc_objs(struct runlist_element, new_cnt, GFP_NOFS); if (!new_rl) { kvfree(*punch_rl); *punch_rl = NULL; @@ -2038,13 +2038,13 @@ struct runlist_element *ntfs_rl_collapse_range(struct runlist_element *dst_rl, i one_split_3 = e_rl == s_rl && begin_split && end_split; punch_cnt = (int)(e_rl - s_rl) + 1; - *punch_rl = kvcalloc(punch_cnt + 1, sizeof(struct runlist_element), - GFP_NOFS); + *punch_rl = kvzalloc_objs(struct runlist_element, punch_cnt + 1, + GFP_NOFS); if (!*punch_rl) return ERR_PTR(-ENOMEM); new_cnt = dst_cnt - (int)(e_rl - s_rl + 1) + 3; - new_rl = kvcalloc(new_cnt, sizeof(struct runlist_element), GFP_NOFS); + new_rl = kvzalloc_objs(struct runlist_element, new_cnt, GFP_NOFS); if (!new_rl) { kvfree(*punch_rl); *punch_rl = NULL; diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 60d43339c590..5aad2d2a36bb 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -2539,7 +2539,7 @@ static int ntfs_init_fs_context(struct fs_context *fc) struct ntfs_volume *vol; /* Allocate a new struct ntfs_volume and place it in sb->s_fs_info. */ - vol = kmalloc(sizeof(struct ntfs_volume), GFP_NOFS); + vol = kmalloc_obj(struct ntfs_volume, GFP_NOFS); if (!vol) return -ENOMEM; diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index e7fe29cb6028..7d6f7f6022eb 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -1044,7 +1044,7 @@ static int ovl_dir_open(struct inode *inode, struct file *file) struct ovl_dir_file *od; enum ovl_path_type type; - od = kzalloc(sizeof(struct ovl_dir_file), GFP_KERNEL); + od = kzalloc_obj(struct ovl_dir_file); if (!od) return -ENOMEM; diff --git a/fs/smb/client/cifs_swn.c b/fs/smb/client/cifs_swn.c index fe10719e627e..c49ecddf4a33 100644 --- a/fs/smb/client/cifs_swn.c +++ b/fs/smb/client/cifs_swn.c @@ -443,7 +443,7 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon) goto unlock; } - reg = kmalloc_obj(struct cifs_swn_reg, GFP_KERNEL); + reg = kmalloc_obj(struct cifs_swn_reg); if (reg == NULL) { ret = -ENOMEM; goto fail_unlock; diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c index 86dba25b7a5a..f6c4259479c5 100644 --- a/fs/smb/client/dfs_cache.c +++ b/fs/smb/client/dfs_cache.c @@ -365,7 +365,7 @@ static struct cache_dfs_tgt *alloc_target(const char *name, int path_consumed) { struct cache_dfs_tgt *t; - t = kmalloc_obj(*t, GFP_KERNEL); + t = kmalloc_obj(*t); if (!t) return ERR_PTR(-ENOMEM); t->name = kstrdup(name, GFP_KERNEL); diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 98ea5c6c34af..96063e355186 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -237,7 +237,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, num_rqst = 0; server = cifs_pick_channel(ses); - vars = kzalloc_obj(*vars, GFP_KERNEL); + vars = kzalloc_obj(*vars); if (vars == NULL) { rc = -ENOMEM; goto out; diff --git a/fs/smb/server/ksmbd_work.c b/fs/smb/server/ksmbd_work.c index f35335307670..d307aefe0aec 100644 --- a/fs/smb/server/ksmbd_work.c +++ b/fs/smb/server/ksmbd_work.c @@ -30,7 +30,7 @@ static int ksmbd_reserve_iov(struct ksmbd_work *work, int need_iov_cnt) } while (new_alloc_cnt < work->iov_cnt + need_iov_cnt); if (work->iov == work->iov_inline) { - new = kcalloc(new_alloc_cnt, sizeof(*new), KSMBD_DEFAULT_GFP); + new = kzalloc_objs(*new, new_alloc_cnt, KSMBD_DEFAULT_GFP); if (!new) return -ENOMEM; diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 0ecc52fde69c..b7ce67094626 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -11820,7 +11820,7 @@ static void smb2_notify_cancel_fn(void **argv) return; conn = in_work->conn; - ctx = kmalloc(sizeof(*ctx), GFP_ATOMIC); + ctx = kmalloc_obj(*ctx, GFP_ATOMIC); if (!ctx) { /* Can't defer the response -- free without sending one. */ list_del_init(&in_work->async_request_entry); diff --git a/fs/xfs/libxfs/xfs_da_btree.c b/fs/xfs/libxfs/xfs_da_btree.c index f190c088591b..7938d2324e87 100644 --- a/fs/xfs/libxfs/xfs_da_btree.c +++ b/fs/xfs/libxfs/xfs_da_btree.c @@ -2746,8 +2746,8 @@ xfs_dabuf_map( * larger one that needs to be free by the caller. */ if (nirecs > 1) { - map = kcalloc(nirecs, sizeof(struct xfs_buf_map), - GFP_KERNEL | __GFP_NOLOCKDEP | __GFP_NOFAIL); + map = kzalloc_objs(struct xfs_buf_map, nirecs, + GFP_KERNEL | __GFP_NOLOCKDEP | __GFP_NOFAIL); *mapp = map; } diff --git a/init/initramfs_test.c b/init/initramfs_test.c index 9cf316c13ffa..1154547721ea 100644 --- a/init/initramfs_test.c +++ b/init/initramfs_test.c @@ -514,7 +514,7 @@ static void __init initramfs_test_hdr_hex(struct kunit *test) char fdata[] = "this file data will not be unpacked"; struct initramfs_test_bufs { char cpio_src[(CPIO_HDRLEN + PATH_MAX + 3 + sizeof(fdata)) * 2]; - } *tbufs = kzalloc(sizeof(struct initramfs_test_bufs), GFP_KERNEL); + } *tbufs = kzalloc_obj(struct initramfs_test_bufs); struct initramfs_test_cpio c[] = { { .magic = "070701", .ino = 1, diff --git a/io_uring/napi.c b/io_uring/napi.c index bfc771445912..ca1c814fa73a 100644 --- a/io_uring/napi.c +++ b/io_uring/napi.c @@ -58,7 +58,7 @@ int __io_napi_add_id(struct io_ring_ctx *ctx, unsigned int napi_id, } } - e = kmalloc(sizeof(*e), GFP_NOWAIT); + e = kmalloc_obj(*e, GFP_NOWAIT); if (!e) return -ENOMEM; diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 1b3b11405dac..86d580d4410d 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -483,8 +483,8 @@ static int io_zcrx_append_area(struct io_zcrx_ifq *ifq, old_areas = ifq->areas; old_nr = ifq->nr_areas; - areas = kmalloc_array(old_nr + 1, sizeof(areas[0]), - GFP_KERNEL_ACCOUNT | __GFP_ZERO); + areas = kmalloc_objs(areas[0], old_nr + 1, + GFP_KERNEL_ACCOUNT | __GFP_ZERO); if (!areas) return -ENOMEM; if (old_areas) diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c index b682fd2be443..0abbbe177e31 100644 --- a/kernel/bpf/diagnostics.c +++ b/kernel/bpf/diagnostics.c @@ -266,7 +266,7 @@ static char *diag_fmt_alloc(struct bpf_verifier_env *env, size_t size) } capacity = max_t(size_t, BPF_DIAG_FMT_CHUNK_SIZE, size); - chunk = kmalloc(struct_size(chunk, data, capacity), GFP_KERNEL_ACCOUNT); + chunk = kmalloc_flex(*chunk, data, capacity, GFP_KERNEL_ACCOUNT); if (!chunk) return NULL; diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index d40cb5dd446c..c2796e8d29ea 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -511,7 +511,7 @@ static int bpf_ma_set_dtor(struct bpf_map *map, struct bpf_mem_alloc *ma, if (IS_ERR_OR_NULL(map->record)) return 0; - hrec = kzalloc(sizeof(*hrec), GFP_KERNEL); + hrec = kzalloc_obj(*hrec); if (!hrec) return -ENOMEM; hrec->key_size = map->key_size; diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c index 74fc4b3f80d6..301fc60bddc4 100644 --- a/kernel/bpf/liveness.c +++ b/kernel/bpf/liveness.c @@ -85,7 +85,7 @@ static struct func_instance *call_instance(struct bpf_verifier_env *env, if (f) return f; - f = kvzalloc(sizeof(*f), GFP_KERNEL_ACCOUNT); + f = kvzalloc_obj(*f, GFP_KERNEL_ACCOUNT); if (!f) return ERR_PTR(-ENOMEM); f->callsite = lookup_key; diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c index 589770ca3d3a..fb032dfdc0de 100644 --- a/kernel/bpf/log.c +++ b/kernel/bpf/log.c @@ -862,7 +862,7 @@ struct bpf_verifier_log *bpf_log_attr_create_vlog(struct bpf_log_attr *attr_log, if (!size) return NULL; - log = kzalloc_obj(*log, GFP_KERNEL); + log = kzalloc_obj(*log); if (!log) return ERR_PTR(-ENOMEM); diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index e421ea2b80c3..a68e5435ca5f 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -5483,7 +5483,7 @@ static int check_max_stack_depth(struct bpf_verifier_env *env) bool priv_stack_supported; int ret; - dinfo = kvcalloc(env->subprog_cnt, sizeof(*dinfo), GFP_KERNEL_ACCOUNT); + dinfo = kvzalloc_objs(*dinfo, env->subprog_cnt, GFP_KERNEL_ACCOUNT); if (!dinfo) return -ENOMEM; @@ -20536,8 +20536,7 @@ static int process_fd_array_continuous(struct bpf_verifier_env *env, return -E2BIG; } - env->fd_array = kvcalloc(cnt, sizeof(*env->fd_array), - GFP_KERNEL_ACCOUNT); + env->fd_array = kvzalloc_objs(*env->fd_array, cnt, GFP_KERNEL_ACCOUNT); if (!env->fd_array) return -ENOMEM; env->fd_array_cnt = cnt; diff --git a/kernel/dma/map_benchmark.c b/kernel/dma/map_benchmark.c index fdc070f419f6..957707158ff6 100644 --- a/kernel/dma/map_benchmark.c +++ b/kernel/dma/map_benchmark.c @@ -51,8 +51,7 @@ struct dma_single_map_param { static void *dma_single_map_benchmark_prepare(struct map_benchmark_data *map) { - struct dma_single_map_param *params __free(kfree) = kzalloc(sizeof(*params), - GFP_KERNEL); + struct dma_single_map_param *params __free(kfree) = kzalloc_obj(*params); if (!params) return NULL; diff --git a/kernel/events/core.c b/kernel/events/core.c index a6c8e38a3110..a7adc8e34089 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -13558,9 +13558,8 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu, return ERR_PTR(err); if (has_addr_filter(event)) { - event->addr_filter_ranges = kcalloc(pmu->nr_addr_filters, - sizeof(struct perf_addr_filter_range), - GFP_KERNEL); + event->addr_filter_ranges = kzalloc_objs(struct perf_addr_filter_range, + pmu->nr_addr_filters); if (!event->addr_filter_ranges) return ERR_PTR(-ENOMEM); diff --git a/kernel/futex/core.c b/kernel/futex/core.c index 51ba5e1257c0..a061f54b606d 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -1874,8 +1874,8 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags) free_percpu(ref); } - fph = kvzalloc(struct_size(fph, queues, hash_slots), - GFP_KERNEL_ACCOUNT | __GFP_NOWARN); + fph = kvzalloc_flex(*fph, queues, hash_slots, + GFP_KERNEL_ACCOUNT | __GFP_NOWARN); if (!fph) return -ENOMEM; @@ -2103,7 +2103,7 @@ static int __init futex_init(void) size = sizeof(struct futex_hash_bucket) * hashsize; order = get_order(size); - __futex_queues = kcalloc(nr_node_ids, sizeof(*__futex_queues), GFP_KERNEL); + __futex_queues = kzalloc_objs(*__futex_queues, nr_node_ids); kmemleak_not_leak(__futex_queues); runtime_const_init(shift, __futex_shift); diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 2fbff2618a1e..57eff26fa646 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -2306,7 +2306,7 @@ int request_nmi(unsigned int irq, irq_handler_t handler, !irq_supports_nmi(desc)) return -EINVAL; - action = kzalloc(sizeof(struct irqaction), GFP_KERNEL); + action = kzalloc_obj(struct irqaction); if (!action) return -ENOMEM; diff --git a/kernel/jump_label.c b/kernel/jump_label.c index e851e4b37d0e..ab6b774bcfd7 100644 --- a/kernel/jump_label.c +++ b/kernel/jump_label.c @@ -726,12 +726,11 @@ static int jump_label_add_module(struct module *mod) if (static_key_sealed(key)) goto do_poke; - jlm = kzalloc(sizeof(struct static_key_mod), GFP_KERNEL); + jlm = kzalloc_obj(struct static_key_mod); if (!jlm) return -ENOMEM; if (!static_key_linked(key)) { - jlm2 = kzalloc(sizeof(struct static_key_mod), - GFP_KERNEL); + jlm2 = kzalloc_obj(struct static_key_mod); if (!jlm2) { kfree(jlm); return -ENOMEM; diff --git a/kernel/kthread.c b/kernel/kthread.c index 63beb59b7a3d..a3f95c90456b 100644 --- a/kernel/kthread.c +++ b/kernel/kthread.c @@ -852,7 +852,7 @@ int kthread_affine_preferred(struct task_struct *p, const struct cpumask *mask) if (!zalloc_cpumask_var(&affinity, GFP_KERNEL)) return -ENOMEM; - kthread->preferred_affinity = kzalloc(sizeof(struct cpumask), GFP_KERNEL); + kthread->preferred_affinity = kzalloc_obj(struct cpumask); if (!kthread->preferred_affinity) { ret = -ENOMEM; goto out; diff --git a/kernel/sched/ext/cid.c b/kernel/sched/ext/cid.c index 39f88deb94bc..bc4eee5bb4cb 100644 --- a/kernel/sched/ext/cid.c +++ b/kernel/sched/ext/cid.c @@ -98,16 +98,16 @@ static struct scx_cid_tables *scx_cid_alloc_tables(void) u32 npossible = num_possible_cpus(); struct scx_cid_tables *tbls; - tbls = kzalloc_obj(*tbls, GFP_KERNEL); + tbls = kzalloc_obj(*tbls); if (!tbls) return NULL; - tbls->cid_to_cpu = kvcalloc(npossible, sizeof(*tbls->cid_to_cpu), GFP_KERNEL); - tbls->cpu_to_cid = kvcalloc(nr_cpu_ids, sizeof(*tbls->cpu_to_cid), GFP_KERNEL); - tbls->cid_to_shard = kvcalloc(npossible, sizeof(*tbls->cid_to_shard), GFP_KERNEL); - tbls->shard_node = kvcalloc(npossible, sizeof(*tbls->shard_node), GFP_KERNEL); - tbls->shard_ranges = kvcalloc(npossible, sizeof(*tbls->shard_ranges), GFP_KERNEL); - tbls->topo = kvcalloc(npossible, sizeof(*tbls->topo), GFP_KERNEL); + tbls->cid_to_cpu = kvzalloc_objs(*tbls->cid_to_cpu, npossible); + tbls->cpu_to_cid = kvzalloc_objs(*tbls->cpu_to_cid, nr_cpu_ids); + tbls->cid_to_shard = kvzalloc_objs(*tbls->cid_to_shard, npossible); + tbls->shard_node = kvzalloc_objs(*tbls->shard_node, npossible); + tbls->shard_ranges = kvzalloc_objs(*tbls->shard_ranges, npossible); + tbls->topo = kvzalloc_objs(*tbls->topo, npossible); if (!tbls->cid_to_cpu || !tbls->cpu_to_cid || !tbls->cid_to_shard || !tbls->shard_node || !tbls->shard_ranges || !tbls->topo) { @@ -490,7 +490,7 @@ __bpf_kfunc void scx_bpf_cid_override(const s32 *cpu_to_cid__arena, u32 cpu_to_c * region that arena fault recovery covers. */ alloced = zalloc_cpumask_var(&seen, GFP_KERNEL); - node_counts = kcalloc(nr_node_ids, sizeof(*node_counts), GFP_KERNEL); + node_counts = kzalloc_objs(*node_counts, nr_node_ids); if (cpu_to_cid_cnt == nr_cpu_ids) cpu_to_cid = kmemdup(cpu_to_cid__arena, cpu_to_cid_cnt * sizeof(s32), GFP_KERNEL); diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 713aa26b2828..51de1d8b72a1 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -5449,7 +5449,7 @@ static ssize_t scx_attr_caps_show(struct kobject *kobj, struct scx_sched *sch = container_of(kobj, struct scx_sched, kobj); u32 npossible = num_possible_cpus(); struct scx_cmask *agg __free(kfree) = - kzalloc(struct_size(agg, bits, SCX_CMASK_NR_WORDS(npossible)), GFP_KERNEL); + kzalloc_flex(*agg, bits, SCX_CMASK_NR_WORDS(npossible)); unsigned long *agg_bm __free(bitmap) = bitmap_zalloc(npossible, GFP_KERNEL); ssize_t count = 0; s32 cap, si; diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index 0554448835bd..9e7040482bde 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -194,7 +194,7 @@ s32 scx_alloc_pshards(struct scx_sched *sch) shard_node = rcu_dereference_protected(scx_shard_node, lockdep_is_held(&scx_enable_mutex)); - pshard = kzalloc_objs(pshard[0], scx_nr_cid_shards, GFP_KERNEL); + pshard = kzalloc_objs(pshard[0], scx_nr_cid_shards); if (!pshard) return -ENOMEM; diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c index ddb0b12a5c4a..1e9b00997ff2 100644 --- a/kernel/trace/fprobe.c +++ b/kernel/trace/fprobe.c @@ -945,7 +945,7 @@ int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter if (num < 0) return num; - addrs = kcalloc(num, sizeof(*addrs), GFP_KERNEL); + addrs = kzalloc_objs(*addrs, num); if (!addrs) return -ENOMEM; diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index b0963ac6fd16..9726413a6385 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -2600,8 +2600,8 @@ rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu) cpu_buffer->remote = buffer->remote; cpu_buffer->meta_page = (struct trace_buffer_meta *)(void *)desc->meta_va; cpu_buffer->nr_pages = nr_pages; - cpu_buffer->subbuf_ids = kcalloc(cpu_buffer->nr_pages + 1, - sizeof(*cpu_buffer->subbuf_ids), GFP_KERNEL); + cpu_buffer->subbuf_ids = kzalloc_objs(*cpu_buffer->subbuf_ids, + cpu_buffer->nr_pages + 1); if (!cpu_buffer->subbuf_ids) goto fail_free_reader; diff --git a/kernel/trace/trace_eprobe.c b/kernel/trace/trace_eprobe.c index 78fa1cbda9ac..998e6390937a 100644 --- a/kernel/trace/trace_eprobe.c +++ b/kernel/trace/trace_eprobe.c @@ -930,7 +930,7 @@ static int __trace_eprobe_create(int argc, const char *argv[]) } else ep->filter_str = NULL; - ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + ctx = kzalloc_obj(*ctx); if (!ctx) return -ENOMEM; ctx->event = ep->event; diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c index e6724f947170..75fa1ffc4c96 100644 --- a/kernel/trace/trace_remote.c +++ b/kernel/trace/trace_remote.c @@ -251,8 +251,8 @@ static int trace_remote_get(struct trace_remote *remote, int cpu) if (cpu != RING_BUFFER_ALL_CPUS && !remote->pcpu_reader_locks) { int lock_cpu; - remote->pcpu_reader_locks = kcalloc(nr_cpu_ids, sizeof(*remote->pcpu_reader_locks), - GFP_KERNEL); + remote->pcpu_reader_locks = kzalloc_objs(*remote->pcpu_reader_locks, + nr_cpu_ids); if (!remote->pcpu_reader_locks) { trace_remote_try_unload(remote); return -ENOMEM; @@ -324,7 +324,7 @@ static int __alloc_ring_buffer_iter(struct trace_remote_iterator *iter, int cpu) return iter->rb_iter ? 0 : -ENOMEM; } - iter->rb_iters = kcalloc(nr_cpu_ids, sizeof(*iter->rb_iters), GFP_KERNEL); + iter->rb_iters = kzalloc_objs(*iter->rb_iters, nr_cpu_ids); if (!iter->rb_iters) return -ENOMEM; @@ -1204,7 +1204,7 @@ remote_events_dir_header_page_read(struct file *filp, char __user *ubuf, size_t struct trace_seq *s; int ret; - s = kmalloc(sizeof(*s), GFP_KERNEL); + s = kmalloc_obj(*s); if (!s) return -ENOMEM; @@ -1227,7 +1227,7 @@ remote_events_dir_header_event_read(struct file *filp, char __user *ubuf, size_t struct trace_seq *s; int ret; - s = kmalloc(sizeof(*s), GFP_KERNEL); + s = kmalloc_obj(*s); if (!s) return -ENOMEM; diff --git a/lib/test_rhashtable.c b/lib/test_rhashtable.c index b767a38a74f9..2f922b63d545 100644 --- a/lib/test_rhashtable.c +++ b/lib/test_rhashtable.c @@ -696,7 +696,7 @@ static int __init test_rhashtable_next_key(void) if (err) return err; - objs = kcalloc(n, sizeof(*objs), GFP_KERNEL); + objs = kzalloc_objs(*objs, n); if (!objs) { rhashtable_destroy(&ht); return -ENOMEM; diff --git a/lib/test_workqueue.c b/lib/test_workqueue.c index 99e160bd5ad1..2bdfcbbcabb4 100644 --- a/lib/test_workqueue.c +++ b/lib/test_workqueue.c @@ -149,11 +149,11 @@ static int __init run_bench(int n_threads, const char *scope, const char *label) if (ret) return ret; - ctxs = kcalloc(n_threads, sizeof(*ctxs), GFP_KERNEL); + ctxs = kzalloc_objs(*ctxs, n_threads); if (!ctxs) return -ENOMEM; - tasks = kcalloc(n_threads, sizeof(*tasks), GFP_KERNEL); + tasks = kzalloc_objs(*tasks, n_threads); if (!tasks) { kfree(ctxs); return -ENOMEM; diff --git a/lib/tests/kunit_iov_iter.c b/lib/tests/kunit_iov_iter.c index d9690ba1db88..32e42d8c7ca1 100644 --- a/lib/tests/kunit_iov_iter.c +++ b/lib/tests/kunit_iov_iter.c @@ -57,7 +57,7 @@ static void *__init iov_kunit_create_buffer(struct kunit *test, void *buffer; unsigned int i; - pages = kzalloc_objs(struct page *, npages, GFP_KERNEL); + pages = kzalloc_objs(struct page *, npages); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pages); *ppages = pages; diff --git a/mm/damon/tests/vaddr-kunit.h b/mm/damon/tests/vaddr-kunit.h index 6a95441d193a..d61b503e319a 100644 --- a/mm/damon/tests/vaddr-kunit.h +++ b/mm/damon/tests/vaddr-kunit.h @@ -136,7 +136,7 @@ static void damon_do_test_apply_three_regions(struct kunit *test, if (!t) kunit_skip(test, "target alloc fail"); - ranges = kmalloc_array(nr_regions / 2, sizeof(*ranges), GFP_KERNEL); + ranges = kmalloc_objs(*ranges, nr_regions / 2); if (!ranges) { damon_destroy_target(t, NULL); kunit_skip(test, "ranges alloc fail"); diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index e7ad295504e4..c5bc60d16e40 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -981,7 +981,7 @@ batadv_wifi_net_device_insert(struct net_device *net_dev, u32 wifi_flags) ASSERT_RTNL(); - device_state = kzalloc_obj(*device_state, GFP_KERNEL); + device_state = kzalloc_obj(*device_state); if (!device_state) return -ENOMEM; diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index ffd7b37e7401..3f121099eb22 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -6423,8 +6423,7 @@ static int hci_update_event_filter_sync(struct hci_dev *hdev) goto update_scan; } - accept_list = kmalloc_array(num_entries, sizeof(*accept_list), - GFP_KERNEL); + accept_list = kmalloc_objs(*accept_list, num_entries); if (!accept_list) { hci_dev_unlock(hdev); return -ENOMEM; diff --git a/net/devlink/netlink.c b/net/devlink/netlink.c index 300580c1a217..a8eda727bff1 100644 --- a/net/devlink/netlink.c +++ b/net/devlink/netlink.c @@ -251,7 +251,7 @@ devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs) if (!attrs[DEVLINK_ATTR_PARENT_DEV]) return ERR_PTR(-EINVAL); - tb = kcalloc(maxtype + 1, sizeof(*tb), GFP_KERNEL); + tb = kzalloc_objs(*tb, maxtype + 1); if (!tb) return ERR_PTR(-ENOMEM); diff --git a/net/devlink/param.c b/net/devlink/param.c index 8ca0f3ed646c..95ac9db8c993 100644 --- a/net/devlink/param.c +++ b/net/devlink/param.c @@ -330,13 +330,12 @@ static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink, int err; int i; - default_value = kcalloc(DEVLINK_PARAM_CMODE_MAX + 1, - sizeof(*default_value), GFP_KERNEL); + default_value = kzalloc_objs(*default_value, + DEVLINK_PARAM_CMODE_MAX + 1); if (!default_value) return -ENOMEM; - param_value = kcalloc(DEVLINK_PARAM_CMODE_MAX + 1, - sizeof(*param_value), GFP_KERNEL); + param_value = kzalloc_objs(*param_value, DEVLINK_PARAM_CMODE_MAX + 1); if (!param_value) { kfree(default_value); return -ENOMEM; diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 9f053eb8b46e..04dbb2babbcd 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -2931,8 +2931,8 @@ static int bpf_iter_tcp_realloc_batch(struct bpf_tcp_iter_state *iter, { union bpf_tcp_iter_batch_item *new_batch; - new_batch = kvmalloc_array(new_batch_sz, sizeof(*new_batch), - flags | __GFP_NOWARN); + new_batch = kvmalloc_objs(*new_batch, new_batch_sz, + flags | __GFP_NOWARN); if (!new_batch) return -ENOMEM; diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c index 19e08661be43..c7769da9617b 100644 --- a/net/mac80211/nan.c +++ b/net/mac80211/nan.c @@ -659,8 +659,7 @@ int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata, if (!sta) return -ENOENT; - new_sched = kzalloc(struct_size(new_sched, channels, sched->n_channels), - GFP_KERNEL); + new_sched = kzalloc_flex(*new_sched, channels, sched->n_channels); if (!new_sched) return -ENOMEM; diff --git a/net/mctp/test/route-test.c b/net/mctp/test/route-test.c index c92e3abb40d7..f05f75bf93e0 100644 --- a/net/mctp/test/route-test.c +++ b/net/mctp/test/route-test.c @@ -193,7 +193,7 @@ static void __mctp_route_test_init(struct kunit *test, if (netid != MCTP_NET_ANY) WRITE_ONCE(dev->mdev->net, netid); - dev->mdev->addrs = kmalloc_objs(u8, 1, GFP_KERNEL); + dev->mdev->addrs = kmalloc_objs(u8, 1); dev->mdev->num_addrs = 1; dev->mdev->addrs[0] = 8; diff --git a/net/mctp/test/utils.c b/net/mctp/test/utils.c index 6eef8d485c25..6b131084a249 100644 --- a/net/mctp/test/utils.c +++ b/net/mctp/test/utils.c @@ -88,7 +88,7 @@ struct mctp_test_dev *mctp_test_create_dev_with_addr(mctp_eid_t addr) if (!dev) return NULL; - dev->mdev->addrs = kmalloc_objs(u8, 1, GFP_KERNEL); + dev->mdev->addrs = kmalloc_objs(u8, 1); if (!dev->mdev->addrs) { mctp_test_destroy_dev(dev); return NULL; diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 765a92fa90d6..31fbd5a28937 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -3215,7 +3215,7 @@ static int nft_trans_delhook(struct nft_hook *hook, { struct nft_trans_hook *trans_hook; - trans_hook = kmalloc_obj(*trans_hook, GFP_KERNEL); + trans_hook = kmalloc_obj(*trans_hook); if (!trans_hook) return -ENOMEM; diff --git a/net/netfilter/nfnetlink_cttimeout.c b/net/netfilter/nfnetlink_cttimeout.c index 66c2016f6049..f43bf768b31c 100644 --- a/net/netfilter/nfnetlink_cttimeout.c +++ b/net/netfilter/nfnetlink_cttimeout.c @@ -150,7 +150,7 @@ static int cttimeout_new_timeout(struct sk_buff *skb, goto err_proto_put; } - timeout = kzalloc(sizeof(*timeout), GFP_KERNEL); + timeout = kzalloc_obj(*timeout); if (timeout == NULL) { ret = -ENOMEM; goto err_proto_put; diff --git a/net/rds/info.c b/net/rds/info.c index 31e7ad108459..a57f81a05c87 100644 --- a/net/rds/info.c +++ b/net/rds/info.c @@ -205,7 +205,7 @@ int rds_info_getsockopt(struct socket *sock, int optname, sockopt_t *opt) * iterator code to allocate and hand it back. */ npages = iov_iter_npages(&opt->iter_out, INT_MAX); - pages = kvmalloc_array(npages, sizeof(*pages), GFP_KERNEL); + pages = kvmalloc_objs(*pages, npages); if (!pages) { ret = -ENOMEM; goto out; diff --git a/net/rxrpc/key.c b/net/rxrpc/key.c index a0aa78d89289..cbd26da44951 100644 --- a/net/rxrpc/key.c +++ b/net/rxrpc/key.c @@ -210,7 +210,7 @@ static int rxrpc_preparse_xdr_yfs_rxgk(struct key_preparsed_payload *prep, if (!token) goto nomem; - token->rxgk = kzalloc(struct_size_t(struct rxgk_key, _key, raw_keylen), GFP_KERNEL); + token->rxgk = kzalloc_flex(struct rxgk_key, _key, raw_keylen); if (!token->rxgk) goto nomem_token; diff --git a/net/sched/act_gate.c b/net/sched/act_gate.c index fdbfcaa3e2ab..5d228a402204 100644 --- a/net/sched/act_gate.c +++ b/net/sched/act_gate.c @@ -240,7 +240,7 @@ static int tcf_gate_copy_entries(struct tcf_gate_params *dst, list_for_each_entry(entry, &src->entries, list) { struct tcfg_gate_entry *new; - new = kzalloc(sizeof(*new), GFP_ATOMIC); + new = kzalloc_obj(*new, GFP_ATOMIC); if (!new) { NL_SET_ERR_MSG(extack, "Not enough memory for entry"); return -ENOMEM; @@ -415,7 +415,7 @@ static int tcf_gate_init(struct net *net, struct nlattr *nla, if (err < 0) goto release_idr; - p = kzalloc(sizeof(*p), GFP_KERNEL); + p = kzalloc_obj(*p); if (!p) { err = -ENOMEM; goto chain_put; diff --git a/net/sched/act_tunnel_key.c b/net/sched/act_tunnel_key.c index ff401ace4f3d..503834853306 100644 --- a/net/sched/act_tunnel_key.c +++ b/net/sched/act_tunnel_key.c @@ -526,7 +526,7 @@ static int tunnel_key_init(struct net *net, struct nlattr *nla, } t = to_tunnel_key(*a); - params_new = kzalloc(sizeof(*params_new), GFP_KERNEL); + params_new = kzalloc_obj(*params_new); if (unlikely(!params_new)) { NL_SET_ERR_MSG(extack, "Cannot allocate tunnel key parameters"); ret = -ENOMEM; diff --git a/net/sunrpc/auth_gss/gss_krb5_crypto.c b/net/sunrpc/auth_gss/gss_krb5_crypto.c index cf461ebcdde5..09a7c97e87da 100644 --- a/net/sunrpc/auth_gss/gss_krb5_crypto.c +++ b/net/sunrpc/auth_gss/gss_krb5_crypto.c @@ -282,9 +282,8 @@ int gss_krb5_mic_build_sg(const struct xdr_buf *body, unsigned int overflow_nents = nsg - XDR_BUF_TO_SG_NENTS + 1; - *sg_overflow = kmalloc_array(overflow_nents, - sizeof(**sg_overflow), - GFP_NOFS); + *sg_overflow = kmalloc_objs(**sg_overflow, overflow_nents, + GFP_NOFS); if (!*sg_overflow) return -ENOMEM; diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c index aebd97e7f66c..31a1bc60a5f6 100644 --- a/net/sunrpc/svcauth_unix.c +++ b/net/sunrpc/svcauth_unix.c @@ -625,7 +625,7 @@ int sunrpc_nl_unix_gid_get_reqs_dumpit(struct sk_buff *skb, if (!cnt) return 0; - items = kcalloc(cnt, sizeof(*items), GFP_KERNEL); + items = kzalloc_objs(*items, cnt); seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL); if (!items || !seqnos) { ret = -ENOMEM; @@ -1326,7 +1326,7 @@ int sunrpc_nl_ip_map_get_reqs_dumpit(struct sk_buff *skb, if (!cnt) return 0; - items = kcalloc(cnt, sizeof(*items), GFP_KERNEL); + items = kzalloc_objs(*items, cnt); seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL); if (!items || !seqnos) { ret = -ENOMEM; diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c index cb2ef428651f..c42fd338c607 100644 --- a/net/sunrpc/xdr.c +++ b/net/sunrpc/xdr.c @@ -371,8 +371,7 @@ int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset, unsigned int overflow_nents = nsg - sg_head_nents + 1; struct scatterlist *overflow; - overflow = kmalloc_array(overflow_nents, sizeof(*overflow), - gfp); + overflow = kmalloc_objs(*overflow, overflow_nents, gfp); if (!overflow) return -ENOMEM; diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 13f9926bf205..79c3921c583c 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -3667,8 +3667,8 @@ static int bpf_iter_unix_realloc_batch(struct bpf_unix_iter_state *iter, { struct sock **new_batch; - new_batch = kvmalloc_array(new_batch_sz, sizeof(*new_batch), - GFP_USER | __GFP_NOWARN); + new_batch = kvmalloc_objs(*new_batch, new_batch_sz, + GFP_USER | __GFP_NOWARN); if (!new_batch) return -ENOMEM; diff --git a/net/wireless/core.c b/net/wireless/core.c index d13310fef691..3032993ba5dc 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -311,9 +311,8 @@ int cfg80211_nan_set_local_schedule(struct cfg80211_registered_device *rdev, if (!sched->n_channels) return 0; - wdev->u.nan.chandefs = kcalloc(sched->n_channels, - sizeof(*wdev->u.nan.chandefs), - GFP_KERNEL); + wdev->u.nan.chandefs = kzalloc_objs(*wdev->u.nan.chandefs, + sched->n_channels); if (!wdev->u.nan.chandefs) return -ENOMEM; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 44f2bad08670..899b6374c550 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -17330,8 +17330,7 @@ static int nl80211_parse_nan_channel(struct cfg80211_registered_device *rdev, u8 n_rx_nss; int ret; - channel_parsed = kcalloc(NL80211_ATTR_MAX + 1, sizeof(*channel_parsed), - GFP_KERNEL); + channel_parsed = kzalloc_objs(*channel_parsed, NL80211_ATTR_MAX + 1); if (!channel_parsed) return -ENOMEM; @@ -17554,8 +17553,7 @@ static int nl80211_nan_set_peer_sched(struct sk_buff *skb, } if (n_channels) { - nan_channels = kcalloc(n_channels, sizeof(*nan_channels), - GFP_KERNEL); + nan_channels = kzalloc_objs(*nan_channels, n_channels); if (!nan_channels) return -ENOMEM; } @@ -17693,8 +17691,7 @@ static int nl80211_nan_set_local_sched(struct sk_buff *skb, info->nlhdr, GENL_HDRLEN, rem) n_channels++; - sched = kzalloc(struct_size(sched, nan_channels, n_channels), - GFP_KERNEL); + sched = kzalloc_flex(*sched, nan_channels, n_channels); if (!sched) return -ENOMEM; diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index f89f0ca3d4ed..0f1b7e4113c4 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -75,8 +75,7 @@ static struct hlist_head *ima_alloc_replace_htable(void) struct hlist_head *old_htable, *new_htable; /* Initializing to zeros is equivalent to call HLIST_HEAD_INIT. */ - new_htable = kcalloc(IMA_MEASURE_HTABLE_SIZE, sizeof(struct hlist_head), - GFP_KERNEL); + new_htable = kzalloc_objs(struct hlist_head, IMA_MEASURE_HTABLE_SIZE); if (!new_htable) return ERR_PTR(-ENOMEM); diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 23d62fede06e..7c397b1c9231 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -107,7 +107,7 @@ static int snd_compr_open(struct inode *inode, struct file *f) return -EINVAL; } - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = kzalloc_obj(*data); if (!data) { snd_card_unref(compr->card); return -ENOMEM; @@ -119,7 +119,7 @@ static int snd_compr_open(struct inode *inode, struct file *f) data->stream.direction = dirn; data->stream.private_data = compr->private_data; data->stream.device = compr; - runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + runtime = kzalloc_obj(*runtime); if (!runtime) { kfree(data); snd_card_unref(compr->card); diff --git a/sound/core/control.c b/sound/core/control.c index 78ce7bc936d2..4199342d4ffe 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -69,7 +69,7 @@ static int snd_ctl_open(struct inode *inode, struct file *file) err = -ENODEV; goto __error2; } - ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); + ctl = kzalloc_obj(*ctl); if (ctl == NULL) { err = -ENOMEM; goto __error; @@ -174,7 +174,7 @@ void snd_ctl_notify(struct snd_card *card, unsigned int mask, goto _found; } } - ev = kzalloc(sizeof(*ev), GFP_ATOMIC); + ev = kzalloc_obj(*ev, GFP_ATOMIC); if (ev) { ev->id = *id; ev->mask = mask; @@ -871,7 +871,7 @@ static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl, unsigned int cmd, void __user *arg) { struct snd_ctl_card_info *info __free(kfree) = - kzalloc(sizeof(*info), GFP_KERNEL); + kzalloc_obj(*info); ssize_t n; if (! info) diff --git a/sound/core/control_led.c b/sound/core/control_led.c index 3d13bbec1c54..ec0e6c7ad657 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -158,7 +158,7 @@ static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access, UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); } if (!found && kctl && card) { - lctl = kzalloc(sizeof(*lctl), GFP_KERNEL); + lctl = kzalloc_obj(*lctl); if (lctl) { lctl->card = card; lctl->access = access; diff --git a/sound/core/init.c b/sound/core/init.c index 2f7f83a7611b..9693e646b3bb 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -364,7 +364,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, sound_debugfs_root); #endif #ifdef CONFIG_SND_CTL_DEBUG - card->value_buf = kmalloc(sizeof(*card->value_buf), GFP_KERNEL); + card->value_buf = kmalloc_obj(*card->value_buf); if (!card->value_buf) return -ENOMEM; #endif diff --git a/sound/core/misc.c b/sound/core/misc.c index 4772b2a3b808..066fb9ecdcdc 100644 --- a/sound/core/misc.c +++ b/sound/core/misc.c @@ -125,7 +125,7 @@ int snd_fasync_helper(int fd, struct file *file, int on, struct snd_fasync *fasync = NULL; if (on) { - fasync = kzalloc(sizeof(*fasync), GFP_KERNEL); + fasync = kzalloc_obj(*fasync); if (!fasync) return -ENOMEM; INIT_LIST_HEAD(&fasync->list); diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c index ff9d7fd60a7e..c533d767c29a 100644 --- a/sound/core/oss/mixer_oss.c +++ b/sound/core/oss/mixer_oss.c @@ -890,7 +890,7 @@ static int snd_mixer_oss_build_test(struct snd_mixer_oss *mixer, struct slot *sl int err; struct snd_ctl_elem_info *info __free(kfree) = - kmalloc(sizeof(*info), GFP_KERNEL); + kmalloc_obj(*info); if (!info) return -ENOMEM; scoped_guard(rwsem_read, &card->controls_rwsem) { diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 62324282fcae..6d32c12fb79b 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2330,7 +2330,7 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd) return -EINVAL; struct snd_pcm_group *group __free(kfree) = - kzalloc(sizeof(*group), GFP_KERNEL); + kzalloc_obj(*group); if (!group) return -ENOMEM; snd_pcm_group_init(group); diff --git a/sound/core/seq/oss/seq_oss_synth.c b/sound/core/seq/oss/seq_oss_synth.c index c4b82e29ab05..21a0a98c8e92 100644 --- a/sound/core/seq/oss/seq_oss_synth.c +++ b/sound/core/seq/oss/seq_oss_synth.c @@ -86,7 +86,7 @@ snd_seq_oss_synth_probe(struct snd_seq_device *dev) struct seq_oss_synth *rec; struct snd_seq_oss_reg *reg = SNDRV_SEQ_DEVICE_ARGPTR(dev); - rec = kzalloc(sizeof(*rec), GFP_KERNEL); + rec = kzalloc_obj(*rec); if (!rec) return -ENOMEM; rec->seq_device = -1; diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 5b86e75c2658..239809ce48d7 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -210,7 +210,7 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) struct snd_seq_client *client; /* init client data */ - client = kzalloc(sizeof(*client), GFP_KERNEL); + client = kzalloc_obj(*client); if (client == NULL) return NULL; client->pool = snd_seq_pool_new(poolsize); diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c index 6208bf7f57bf..a01785a6de9b 100644 --- a/sound/core/seq/seq_virmidi.c +++ b/sound/core/seq/seq_virmidi.c @@ -188,7 +188,7 @@ static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream) struct snd_rawmidi_runtime *runtime = substream->runtime; struct snd_virmidi *vmidi; - vmidi = kzalloc(sizeof(*vmidi), GFP_KERNEL); + vmidi = kzalloc_obj(*vmidi); if (vmidi == NULL) return -ENOMEM; vmidi->substream = substream; diff --git a/sound/core/timer.c b/sound/core/timer.c index f666f05e9d45..679b26435670 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -1855,7 +1855,7 @@ static int snd_timer_user_info(struct file *file, return -EBADFD; struct snd_timer_info *info __free(kfree) = - kzalloc(sizeof(*info), GFP_KERNEL); + kzalloc_obj(*info); if (! info) return -ENOMEM; info->card = t->card ? t->card->number : -1; diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index 4e3ea23ca913..81cb1f59f703 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -1365,7 +1365,7 @@ static int loopback_open(struct snd_pcm_substream *substream) int dev = get_cable_index(substream); guard(mutex)(&loopback->cable_lock); - dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + dpcm = kzalloc_obj(*dpcm); if (!dpcm) return -ENOMEM; dpcm->loopback = loopback; @@ -1373,7 +1373,7 @@ static int loopback_open(struct snd_pcm_substream *substream) cable = loopback->cables[substream->number][dev]; if (!cable) { - cable = kzalloc(sizeof(*cable), GFP_KERNEL); + cable = kzalloc_obj(*cable); if (!cable) { err = -ENOMEM; goto unlock; diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c index 30bd76eee96e..7be6ff201ecb 100644 --- a/sound/isa/gus/gus_dma.c +++ b/sound/isa/gus/gus_dma.c @@ -214,7 +214,7 @@ int snd_gf1_dma_transfer_block(struct snd_gus_card * gus, struct snd_gf1_dma_block *block; struct snd_gf1_dma_block *free_block = NULL; - block = kmalloc(sizeof(*block), atomic ? GFP_ATOMIC : GFP_KERNEL); + block = kmalloc_obj(*block, atomic ? GFP_ATOMIC : GFP_KERNEL); if (!block) return -ENOMEM; diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c index 19a6927c079d..f6db08b75649 100644 --- a/sound/pci/cs46xx/cs46xx_lib.c +++ b/sound/pci/cs46xx/cs46xx_lib.c @@ -1444,7 +1444,7 @@ static int _cs46xx_playback_open_channel (struct snd_pcm_substream *substream,in struct snd_cs46xx_pcm * cpcm; struct snd_pcm_runtime *runtime = substream->runtime; - cpcm = kzalloc(sizeof(*cpcm), GFP_KERNEL); + cpcm = kzalloc_obj(*cpcm); if (cpcm == NULL) return -ENOMEM; if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &chip->pci->dev, diff --git a/sound/pci/ctxfi/ctamixer.c b/sound/pci/ctxfi/ctamixer.c index 5fc1c922620a..f356917cb6ac 100644 --- a/sound/pci/ctxfi/ctamixer.c +++ b/sound/pci/ctxfi/ctamixer.c @@ -236,7 +236,7 @@ static int get_amixer_rsc(struct amixer_mgr *mgr, *ramixer = NULL; /* Allocate mem for amixer resource */ - amixer = kzalloc(sizeof(*amixer), GFP_KERNEL); + amixer = kzalloc_obj(*amixer); if (!amixer) return -ENOMEM; @@ -390,7 +390,7 @@ static int get_sum_rsc(struct sum_mgr *mgr, *rsum = NULL; /* Allocate mem for sum resource */ - sum = kzalloc(sizeof(*sum), GFP_KERNEL); + sum = kzalloc_obj(*sum); if (!sum) return -ENOMEM; diff --git a/sound/pci/ctxfi/ctdaio.c b/sound/pci/ctxfi/ctdaio.c index 69aacd06716c..9be70c6862ab 100644 --- a/sound/pci/ctxfi/ctdaio.c +++ b/sound/pci/ctxfi/ctdaio.c @@ -540,7 +540,7 @@ static int get_daio_rsc(struct daio_mgr *mgr, err = -ENOMEM; /* Allocate mem for daio resource */ if (desc->output) { - struct dao *dao = kzalloc(sizeof(*dao), GFP_KERNEL); + struct dao *dao = kzalloc_obj(*dao); if (!dao) goto error; @@ -552,7 +552,7 @@ static int get_daio_rsc(struct daio_mgr *mgr, *rdaio = &dao->daio; } else { - struct dai *dai = kzalloc(sizeof(*dai), GFP_KERNEL); + struct dai *dai = kzalloc_obj(*dai); if (!dai) goto error; diff --git a/sound/pci/ctxfi/ctsrc.c b/sound/pci/ctxfi/ctsrc.c index 46dc1f509234..1fadaf22309f 100644 --- a/sound/pci/ctxfi/ctsrc.c +++ b/sound/pci/ctxfi/ctsrc.c @@ -432,9 +432,9 @@ get_src_rsc(struct src_mgr *mgr, const struct src_desc *desc, struct src **rsrc) /* Allocate mem for master src resource */ if (MEMRD == desc->mode) - src = kcalloc(desc->multi, sizeof(*src), GFP_KERNEL); + src = kzalloc_objs(*src, desc->multi); else - src = kzalloc(sizeof(*src), GFP_KERNEL); + src = kzalloc_obj(*src); if (!src) { err = -ENOMEM; diff --git a/sound/pci/ctxfi/cttimer.c b/sound/pci/ctxfi/cttimer.c index cc379d880cad..9d6f5df2bc7e 100644 --- a/sound/pci/ctxfi/cttimer.c +++ b/sound/pci/ctxfi/cttimer.c @@ -318,7 +318,7 @@ ct_timer_instance_new(struct ct_timer *atimer, struct ct_atc_pcm *apcm) { struct ct_timer_instance *ti; - ti = kzalloc(sizeof(*ti), GFP_KERNEL); + ti = kzalloc_obj(*ti); if (!ti) return NULL; spin_lock_init(&ti->lock); diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 49cabb2eb2b7..a33817aba17f 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -2470,7 +2470,7 @@ static int snd_emu10k1_fx8010_ioctl(struct snd_hwdep * hw, struct file *file, un emu->support_tlv = 1; return put_user(SNDRV_EMU10K1_VERSION, (int __user *)argp); case SNDRV_EMU10K1_IOCTL_INFO: - info = kzalloc(sizeof(*info), GFP_KERNEL); + info = kzalloc_obj(*info, GFP_KERNEL); if (!info) return -ENOMEM; snd_emu10k1_fx8010_info(emu, info); diff --git a/sound/soc/codecs/simple-amplifier.c b/sound/soc/codecs/simple-amplifier.c index ca53b08c0b33..07c040355c37 100644 --- a/sound/soc/codecs/simple-amplifier.c +++ b/sound/soc/codecs/simple-amplifier.c @@ -371,7 +371,7 @@ static unsigned int *simple_amp_alloc_tlv_ranges(const struct simple_amp_ranges unsigned int *t; unsigned int i; - tlv = kzalloc_objs(*tlv, 2 + ranges->nb_ranges * 6, GFP_KERNEL); + tlv = kzalloc_objs(*tlv, 2 + ranges->nb_ranges * 6); if (!tlv) return NULL; diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 42019daa5e04..5f3423129b13 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -168,7 +168,7 @@ int simple_util_parse_tdm_width_map(struct simple_util_priv *priv, struct device if (!dai->tdm_width_map) return simple_ret(priv, ret); /* see NOTE */ - u32 *array_values __free(kfree) = kcalloc(n, sizeof(*array_values), GFP_KERNEL); + u32 *array_values __free(kfree) = kzalloc_objs(*array_values, n); if (!array_values) goto end; diff --git a/sound/soc/meson/gx-formatter.c b/sound/soc/meson/gx-formatter.c index 311e63affb23..2d3218cce426 100644 --- a/sound/soc/meson/gx-formatter.c +++ b/sound/soc/meson/gx-formatter.c @@ -253,7 +253,7 @@ struct gx_stream *gx_stream_alloc(struct gx_iface *iface) { struct gx_stream *ts; - ts = kzalloc(sizeof(*ts), GFP_KERNEL); + ts = kzalloc_obj(*ts); if (ts) { INIT_LIST_HEAD(&ts->formatter_list); mutex_init(&ts->lock); diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c index 1d68a80e8e0c..f0ff1350e9dd 100644 --- a/sound/soc/qcom/qdsp6/q6afe.c +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -1857,7 +1857,7 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) return ERR_PTR(-EINVAL); } - port = kzalloc(sizeof(*port), GFP_KERNEL); + port = kzalloc_obj(*port, GFP_KERNEL); if (!port) return ERR_PTR(-ENOMEM); diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index e01d91eb3cc8..32d9b7f30a4f 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -1249,8 +1249,7 @@ static int find_sdca_entity_pde(struct device *dev, return -EINVAL; } - u32 *delay_list __free(kfree) = kcalloc(num_delays, sizeof(*delay_list), - GFP_KERNEL); + u32 *delay_list __free(kfree) = kzalloc_objs(*delay_list, num_delays); if (!delay_list) return -ENOMEM; @@ -1313,8 +1312,8 @@ static int find_sdca_entity_ge(struct device *dev, return -EINVAL; } - u8 *affected_list __free(kfree) = kcalloc(num_affected, sizeof(*affected_list), - GFP_KERNEL); + u8 *affected_list __free(kfree) = kzalloc_objs(*affected_list, + num_affected); if (!affected_list) return -ENOMEM; @@ -1552,8 +1551,8 @@ static int find_sdca_entities(struct device *dev, struct fwnode_handle *function if (!entities) return -ENOMEM; - u32 *entity_list __free(kfree) = kcalloc(num_entities, sizeof(*entity_list), - GFP_KERNEL); + u32 *entity_list __free(kfree) = kzalloc_objs(*entity_list, + num_entities); if (!entity_list) return -ENOMEM; @@ -1715,8 +1714,8 @@ static int find_sdca_entity_connection_pde(struct device *dev, if (!managed) return -ENOMEM; - u32 *managed_list __free(kfree) = kcalloc(num_managed, sizeof(*managed_list), - GFP_KERNEL); + u32 *managed_list __free(kfree) = kzalloc_objs(*managed_list, + num_managed); if (!managed_list) return -ENOMEM; @@ -2033,8 +2032,8 @@ static int find_sdca_clusters(struct device *dev, if (!clusters) return -ENOMEM; - u32 *cluster_list __free(kfree) = kcalloc(num_clusters, sizeof(*cluster_list), - GFP_KERNEL); + u32 *cluster_list __free(kfree) = kzalloc_objs(*cluster_list, + num_clusters); if (!cluster_list) return -ENOMEM; diff --git a/sound/soc/sof/sof-client-probes-ipc4.c b/sound/soc/sof/sof-client-probes-ipc4.c index 2eef32b55395..c547ea61fb3d 100644 --- a/sound/soc/sof/sof-client-probes-ipc4.c +++ b/sound/soc/sof/sof-client-probes-ipc4.c @@ -260,7 +260,7 @@ static int ipc4_probes_points_info(struct sof_client_dev *cdev, *num_desc = info->num_elems; dev_dbg(dev, "%s: got %zu probe points", __func__, *num_desc); - *desc = kcalloc(*num_desc, sizeof(**desc), GFP_KERNEL); + *desc = kzalloc_objs(**desc, *num_desc); if (!*desc) { kfree(msg.data_ptr); return -ENOMEM; diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c index c7bbf09e547f..64da8df15bf2 100644 --- a/sound/soc/sof/sof-client.c +++ b/sound/soc/sof/sof-client.c @@ -230,7 +230,7 @@ int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id, struct sof_client_dev *cdev; int ret; - centry = kzalloc(sizeof(*centry), GFP_KERNEL); + centry = kzalloc_obj(*centry); if (!centry) return -ENOMEM; diff --git a/sound/sparc/amd7930.c b/sound/sparc/amd7930.c index da04ed5cbac4..d746b2586d88 100644 --- a/sound/sparc/amd7930.c +++ b/sound/sparc/amd7930.c @@ -903,7 +903,7 @@ static int snd_amd7930_create(struct snd_card *card, int err; *ramd = NULL; - amd = kzalloc(sizeof(*amd), GFP_KERNEL); + amd = kzalloc_obj(*amd); if (amd == NULL) return -ENOMEM; From d96171d911e3b89ca2957c04264019cf2f96287b Mon Sep 17 00:00:00 2001 From: Shivaprasad G Bhat Date: Wed, 19 Aug 2026 17:58:22 +0000 Subject: [PATCH 0911/2366] powerpc: pci-ioda: Fix the stale irq chip reference The commit f0ac60e6e311 ("powerpc/powernv/pci: Switch to use msi_create_parent_irq_domain()") removed the legacy MSI irq chip pnv_pci_msi_irq_chip but left behind the static definition of it and its reference in is_pnv_opal_msi(). The KVM IRQ bypass for vfio devices is broken because the comparision in is_pnv_opal_msi() fails on the comparision with stale unused variable showing the below errors in dmesg. kvmppc_set_passthru_irq_hv: Could not assign IRQ map for (X,Y) kvmppc_set_passthru_irq (irq X, gsi Y) fails: -2 vfio-pci A:B:C.D irq bypass producer (eventfd Z) registration fails: -2 The patch removes the stale variable definition and fixes the is_pnv_opal_msi() by comparing against the chip name prefix. Fixes: f0ac60e6e311 ("powerpc/powernv/pci: Switch to use msi_create_parent_irq_domain()") Cc: stable@kernel.org Signed-off-by: Shivaprasad G Bhat Tested-by: Gautam Menghani Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/178716225364.1437.6201568081502251835.stgit@linux.ibm.com --- arch/powerpc/platforms/powernv/pci-ioda.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/arch/powerpc/platforms/powernv/pci-ioda.c b/arch/powerpc/platforms/powernv/pci-ioda.c index 32ecbc46e74b..728a5610d167 100644 --- a/arch/powerpc/platforms/powernv/pci-ioda.c +++ b/arch/powerpc/platforms/powernv/pci-ioda.c @@ -1623,15 +1623,13 @@ int64_t pnv_opal_pci_msi_eoi(struct irq_data *d) return opal_pci_msi_eoi(phb->opal_id, d->parent_data->hwirq); } -static struct irq_chip pnv_pci_msi_irq_chip; - /* * Returns true iff chip is something that we could call * pnv_opal_pci_msi_eoi for. */ bool is_pnv_opal_msi(struct irq_chip *chip) { - return chip == &pnv_pci_msi_irq_chip; + return chip && chip->name && str_has_prefix(chip->name, "PNV-"); } EXPORT_SYMBOL_GPL(is_pnv_opal_msi); @@ -1728,7 +1726,7 @@ static const struct msi_parent_ops pnv_msi_parent_ops = { .chip_flags = MSI_CHIP_FLAG_SET_EOI, .bus_select_token = DOMAIN_BUS_NEXUS, .bus_select_mask = MATCH_PCI_MSI, - .prefix = "PNV-", + .prefix = "PNV-", /* Note: is_pnv_opal_msi() uses this */ .init_dev_msi_info = pnv_init_dev_msi_info, }; From c5e68706527968282e49de205cc2b935823cb88a Mon Sep 17 00:00:00 2001 From: Shivaprasad G Bhat Date: Tue, 14 Jul 2026 17:16:23 +0000 Subject: [PATCH 0912/2366] powerpc/eeh: Fix recursive locking on devices without EEH sensitive driver The commit 1010b4c012b0 ("powerpc/eeh: Make EEH driver device hotplug safe") refactored the EEH code such that the pci_rescan_remove_lock is held at the beginning of eeh_handle_normal_event() and the eeh_reset_device() is called with that lock being held. Looks like the commit missed to remove the existing lock/unlock inside eeh_rmv_device() which is no longer necessary. This is causing the eehd to hang on the lock which it actually holds when that code path is taken. [<0>] 0xc00000011c78f870 [<0>] __switch_to+0xfc/0x1a0 [<0>] pci_lock_rescan_remove+0x30/0x44 [<0>] eeh_rmv_device+0x290/0x2e0 [<0>] eeh_pe_dev_traverse+0x80/0x130 [<0>] eeh_reset_device+0xcc/0x23c [<0>] eeh_handle_normal_event+0x830/0xa80 [<0>] eeh_event_handler+0xf8/0x190 [<0>] kthread+0x194/0x1b0 [<0>] start_kernel_thread+0x14/0x18 The issue is seen for cases where the errors are detected on the PHB directly AND|OR for devices where the driver error_detected() returns PCI_ERS_RESULT_NEED_RESET, and driver being not EEH sensitive(i.e no error handlers like slot_reset(), resume() etc defined). Fixes: 1010b4c012b0 ("powerpc/eeh: Make EEH driver device hotplug safe") Cc: stable Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Shivaprasad G Bhat Reviewed-by: Amit Machhiwal Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/178404937381.913.2759874335293830160.stgit@linux.ibm.com --- arch/powerpc/kernel/eeh_driver.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/powerpc/kernel/eeh_driver.c b/arch/powerpc/kernel/eeh_driver.c index 028f69158532..d64cce17a4e0 100644 --- a/arch/powerpc/kernel/eeh_driver.c +++ b/arch/powerpc/kernel/eeh_driver.c @@ -533,9 +533,7 @@ static void eeh_rmv_device(struct eeh_dev *edev, void *userdata) if (rmv_data) list_add(&edev->rmv_entry, &rmv_data->removed_vf_list); } else { - pci_lock_rescan_remove(); pci_stop_and_remove_bus_device(dev); - pci_unlock_rescan_remove(); } } From 8a4978c17a144a6583478cce933bcb2dbb25298d Mon Sep 17 00:00:00 2001 From: Shivaprasad G Bhat Date: Fri, 26 Jun 2026 09:13:03 +0000 Subject: [PATCH 0913/2366] powerpc/rtas_pci: No hotplug on permanently removed device on pSeries The eeh_driver disables and offlines the PE permanently when it exceeds the freeze count beyond eeh_max_freeze within the last hour. The PE is only offline, so the device tree entries, eeh device references are all intact till the real unplug of the device from the guest/host takes place. On pSeries, with a new hotplug of any PCI device, the drmgr initiates a system-wide PCI rescan, which finds devices offlined by the eeh_driver and there will be attempts to bring them online. This leads to recurring EEHs either at the config read time itself or a bit later depending on the type of the problem. For PowerNV, the commit d2b0f6f77ee5 ("powerpc/eeh: No hotplug on permanently removed dev") introduced the EEH_DEV_REMOVED flag to prevent such inadvertent rescans on hierarchical toplogies relavent in Baremetal setups. For pSeries, such topologies don't really make sense as the devices are either part of the same PE OR exposed as independent devices on multiple virtual PHBs. However, the inadvertent rescans are still a possibility with either hotplug of a new device or otherwise with manual system-wide pci bus rescan attempts. So the patch checks for EEH_DEV_REMOVED before allowing config space access just like PowerNV, making the PCI core omit the PE, and thus preventing subsequent EEH recurances. The patch is tested on PowerVM and KVM machines with single and multi-function devices, and on the devices behind a switch. The unplug of the affected devices post EEH removal is also working fine as expected. Signed-off-by: Shivaprasad G Bhat Reported-by: Tasmiya Nalatwad Tested-by: Tasmiya Nalatwad Reviewed-by: Harsh Prateek Bora References: d2b0f6f77ee5 ("powerpc/eeh: No hotplug on permanently removed dev") Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/178246517230.1267.12206176311111155505.stgit@linux.ibm.com --- arch/powerpc/kernel/rtas_pci.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/powerpc/kernel/rtas_pci.c b/arch/powerpc/kernel/rtas_pci.c index fccf96e897f6..206c825225c2 100644 --- a/arch/powerpc/kernel/rtas_pci.c +++ b/arch/powerpc/kernel/rtas_pci.c @@ -54,6 +54,10 @@ int rtas_pci_dn_read_config(struct pci_dn *pdn, int where, int size, u32 *val) if (!config_access_valid(pdn, where)) return PCIBIOS_BAD_REGISTER_NUMBER; #ifdef CONFIG_EEH + if (pdn->edev && + (pdn->edev->mode & EEH_DEV_REMOVED)) + return PCIBIOS_DEVICE_NOT_FOUND; + if (pdn->edev && pdn->edev->pe && (pdn->edev->pe->state & EEH_PE_CFG_BLOCKED)) return PCIBIOS_SET_FAILED; @@ -105,6 +109,10 @@ int rtas_pci_dn_write_config(struct pci_dn *pdn, int where, int size, u32 val) if (!config_access_valid(pdn, where)) return PCIBIOS_BAD_REGISTER_NUMBER; #ifdef CONFIG_EEH + if (pdn->edev && + (pdn->edev->mode & EEH_DEV_REMOVED)) + return PCIBIOS_DEVICE_NOT_FOUND; + if (pdn->edev && pdn->edev->pe && (pdn->edev->pe->state & EEH_PE_CFG_BLOCKED)) return PCIBIOS_SET_FAILED; From c6755be4838d6ccd641effbcdc3d917b82631ff9 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 9 Aug 2026 18:24:01 +0200 Subject: [PATCH 0914/2366] powerpc/kexec_file: Use inclusive range checks in add_usable_mem() add_usable_mem() adds usable memory ranges for the kdump kernel. The ranges are inclusive, but the partial overlap check uses exclusive comparisons. This skips ranges with base == loc_end or end == loc_base. Use inclusive comparisons instead. Fixes: 7c64e21a1c5a ("powerpc/kexec_file: Restrict memory usage of kdump kernel") Signed-off-by: Thorsten Blum Reviewed-by: Sourabh Jain Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260809162403.18142-2-thorsten.blum@linux.dev --- arch/powerpc/kexec/file_load_64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/kexec/file_load_64.c b/arch/powerpc/kexec/file_load_64.c index 6075b1c88511..c2ed0d1c92e7 100644 --- a/arch/powerpc/kexec/file_load_64.c +++ b/arch/powerpc/kexec/file_load_64.c @@ -113,7 +113,7 @@ static int add_usable_mem(struct umem_info *um_info, u64 base, u64 end) loc_end = um_info->ranges[i].end; if (loc_base >= base && loc_end <= end) add = true; - else if (base < loc_end && end > loc_base) { + else if (base <= loc_end && end >= loc_base) { if (loc_base < base) loc_base = base; if (loc_end > end) From 68832eb08751b4ce23e90bd90e9414a465a99da3 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 30 Jul 2026 15:19:40 +0200 Subject: [PATCH 0915/2366] powerpc/kexec: Simplify kdump_extra_elfcorehdr_size() Return the size directly and drop the extra_sz variable to simplify kdump_extra_elfcorehdr_size(). The two warning paths now fall through to the existing return 0 at the end of the function. Signed-off-by: Thorsten Blum Reviewed-by: Christophe Leroy (CS GROUP) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260730131940.597739-2-thorsten.blum@linux.dev --- arch/powerpc/kexec/file_load_64.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/arch/powerpc/kexec/file_load_64.c b/arch/powerpc/kexec/file_load_64.c index c2ed0d1c92e7..4a499a69e2d8 100644 --- a/arch/powerpc/kexec/file_load_64.c +++ b/arch/powerpc/kexec/file_load_64.c @@ -377,16 +377,12 @@ static int load_backup_segment(struct kimage *image, struct kexec_buf *kbuf) static unsigned int kdump_extra_elfcorehdr_size(struct crash_mem *cmem) { #if defined(CONFIG_CRASH_HOTPLUG) && defined(CONFIG_MEMORY_HOTPLUG) - unsigned int extra_sz = 0; - if (CONFIG_CRASH_MAX_MEMORY_RANGES > (unsigned int)PN_XNUM) pr_warn("Number of Phdrs %u exceeds max\n", CONFIG_CRASH_MAX_MEMORY_RANGES); else if (cmem->nr_ranges >= CONFIG_CRASH_MAX_MEMORY_RANGES) pr_warn("Configured crash mem ranges may not be enough\n"); else - extra_sz = (CONFIG_CRASH_MAX_MEMORY_RANGES - cmem->nr_ranges) * sizeof(Elf64_Phdr); - - return extra_sz; + return (CONFIG_CRASH_MAX_MEMORY_RANGES - cmem->nr_ranges) * sizeof(Elf64_Phdr); #endif return 0; } From 449f60f99f8f3cbe80a9bd2242945e827c5ed003 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 10 Aug 2026 16:58:27 +0200 Subject: [PATCH 0916/2366] powerpc/kexec_file: Use inclusive range checks for excluded memory arch_check_excluded_range() checks if a kexec segment overlaps an excluded memory range. Both ranges use inclusive end addresses, but the overlap check uses exclusive comparisons. This skips ranges with start == ->ranges[i].end or end == ->ranges[i].start. Use inclusive comparisons instead. Fixes: 6e5250eaa665 ("powerpc/crash: use generic APIs to locate memory hole for kdump") Signed-off-by: Thorsten Blum Reviewed-by: Sourabh Jain Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260810145827.157972-3-thorsten.blum@linux.dev --- arch/powerpc/kexec/file_load_64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/kexec/file_load_64.c b/arch/powerpc/kexec/file_load_64.c index 4a499a69e2d8..bd80c5fb1b1f 100644 --- a/arch/powerpc/kexec/file_load_64.c +++ b/arch/powerpc/kexec/file_load_64.c @@ -57,7 +57,7 @@ int arch_check_excluded_range(struct kimage *image, unsigned long start, emem = image->arch.exclude_ranges; for (i = 0; i < emem->nr_ranges; i++) - if (start < emem->ranges[i].end && end > emem->ranges[i].start) + if (start <= emem->ranges[i].end && end >= emem->ranges[i].start) return 1; return 0; From b1824233b19c1dffdb5e81283805a9e52e763caa Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Thu, 13 Aug 2026 14:37:31 +0800 Subject: [PATCH 0917/2366] powerpc/pseries/pci: Fix misleading VF limit error message When the number of requested VFs exceeds MAX_VFS_FOR_MAP_PE, the message prints that limit but labels it "Configurable VFs". Report the configurable VF limit and the PE mapping limit with separate error messages. Suggested-by: Christophe Leroy Signed-off-by: Jiangshan Yi Reviewed-by: Christophe Leroy [Maddy: Fixed Christophe's reviewed by tag] Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260813063731.399598-1-yijiangshan@kylinos.cn --- arch/powerpc/platforms/pseries/pci.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/arch/powerpc/platforms/pseries/pci.c b/arch/powerpc/platforms/pseries/pci.c index d11a64a086c1..6fc13f4a79a3 100644 --- a/arch/powerpc/platforms/pseries/pci.c +++ b/arch/powerpc/platforms/pseries/pci.c @@ -132,11 +132,14 @@ static int pseries_pci_sriov_enable(struct pci_dev *pdev, u16 num_vfs) /* First integer stores max config */ max_config_vfs = of_read_number(&max_vfs[0], 1); - if (max_config_vfs < num_vfs || num_vfs > MAX_VFS_FOR_MAP_PE) { - dev_err(&pdev->dev, - "Num VFs %x > %x Configurable VFs\n", - num_vfs, (num_vfs > MAX_VFS_FOR_MAP_PE) ? - MAX_VFS_FOR_MAP_PE : max_config_vfs); + if (max_config_vfs < num_vfs) { + dev_err(&pdev->dev, "Num VFs %x > %x Configurable VFs\n", + num_vfs, max_config_vfs); + return -EINVAL; + } + if (num_vfs > MAX_VFS_FOR_MAP_PE) { + dev_err(&pdev->dev, "Num VFs %x > %x PE mapping limit\n", + num_vfs, MAX_VFS_FOR_MAP_PE); return -EINVAL; } From 2b4707a149a55e8fa75c9ef32b359d60f470a566 Mon Sep 17 00:00:00 2001 From: XingWang Xiang Date: Wed, 2 Sep 2026 15:01:18 +0900 Subject: [PATCH 0918/2366] net: mctp: i3c: serialize probe with bus removal mctp_i3c_probe() drops busdevs_lock after finding the matching bus. A concurrent I3C_NOTIFY_BUS_REMOVE can then unregister and free the bus netdev before probe passes its private data to mctp_i3c_add_device(). The latter consequently adds a list node through a freed mbus pointer. Keep busdevs_lock held until the device has been added. This also satisfies the __must_hold annotation on mctp_i3c_add_device(). Fixes: c8755b29b58e ("mctp i3c: MCTP I3C driver") Signed-off-by: XingWang Xiang Acked-by: Matt Johnston Signed-off-by: David S. Miller --- drivers/net/mctp/mctp-i3c.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/mctp/mctp-i3c.c b/drivers/net/mctp/mctp-i3c.c index 88d9e36cd4a2..4e857dd5df64 100644 --- a/drivers/net/mctp/mctp-i3c.c +++ b/drivers/net/mctp/mctp-i3c.c @@ -288,6 +288,7 @@ __must_hold(&busdevs_lock) static int mctp_i3c_probe(struct i3c_device *i3c) { struct mctp_i3c_bus *b = NULL, *mbus = NULL; + int rc; /* Look for a known bus */ mutex_lock(&busdevs_lock); @@ -296,14 +297,16 @@ static int mctp_i3c_probe(struct i3c_device *i3c) mbus = b; break; } - mutex_unlock(&busdevs_lock); if (!mbus) { /* probably no "mctp-controller" property on the i3c bus */ - return -ENODEV; + rc = -ENODEV; + } else { + rc = mctp_i3c_add_device(mbus, i3c); } + mutex_unlock(&busdevs_lock); - return mctp_i3c_add_device(mbus, i3c); + return rc; } static void mctp_i3c_remove_device(struct mctp_i3c_device *mi) From 63a7531ca31f9f097d9cc1cc3fe86ae683cdabdd Mon Sep 17 00:00:00 2001 From: "Mukesh Kumar Chaurasiya (IBM)" Date: Fri, 4 Sep 2026 14:38:58 +0530 Subject: [PATCH 0919/2366] powerpc/entry: Fix irq_soft_mask corruption on replayed interrupt exit When __replay_soft_interrupts() replays a pending interrupt (e.g. PACA_IRQ_DEC -> timer_interrupt), it calls the handler directly with a synthetic pt_regs. The DEFINE_INTERRUPT_HANDLER_ASYNC wrapper around each handler calls arch_interrupt_async_exit_prepare() on the way out, which calls arch_interrupt_exit_prepare() -> local_irq_disable() -> arch_local_irq_disable(), which does: irq_soft_mask_set(IRQS_DISABLED) /* 0x1 */ This unconditionally overwrites irq_soft_mask with IRQS_DISABLED (0x1), stripping the IRQS_PMI_DISABLED (0x2) bit. The result is that irq_soft_mask is 0x1 instead of IRQS_ALL_DISABLED (0x3) when the handler returns to __replay_soft_interrupts(). For a normally-taken interrupt this is harmless: the next interrupt always enters through arch_interrupt_enter_prepare() which unconditionally sets irq_soft_mask to IRQS_ALL_DISABLED. But during replay, next_interrupt() is called directly between replayed handlers without going back through arch_interrupt_enter_prepare(), so the stripped bit is never restored. next_interrupt() then fires a WARNING: WARNING: arch/powerpc/kernel/irq_64.c:75 WARN_ON(irq_soft_mask_return() != IRQS_ALL_DISABLED) The warning was observed early in boot on a POWER10 pseries guest during kmem_cache_init_late(), where a spinlock release triggers interrupt replay that processes a pending timer interrupt. Debugger state confirming the bug: Before timer_interrupt(®s): irq_soft_mask = 0x3 (IRQS_ALL_DISABLED) correct irq_happened = 0x41 (HARD_DIS|REPLAYING) correct After timer_interrupt(®s) returns: irq_soft_mask = 0x1 (IRQS_DISABLED) WRONG - PMI bit stripped irq_happened = 0x41 unchanged The fix is to replace local_irq_disable() with hard_irq_disable(). hard_irq_disable() is the right primitive here for two reasons: 1. On PPC64 (hw_irq.h:301) it calls irq_soft_mask_set_return(IRQS_ALL_DISABLED), setting the soft mask to 0x3 (both IRQS_DISABLED and IRQS_PMI_DISABLED), which preserves the PMI bit and fixes the WARNING. The additional work it does (__hard_irq_disable(), PACA_IRQ_HARD_DIS |=) is redundant but safe since both are already set at this point in the exit path; the trace_hardirqs_off() inside is guarded by if (!arch_irqs_disabled_flags(flags)) so it will not double-fire. 2. On PPC32 (hw_irq.h:467) hard_irq_disable() maps to arch_local_irq_disable() -> __hard_irq_disable(), which clears MSR[EE] in hardware. This is exactly correct: PPC32 has no soft-mask PACA mechanism, so the hardware disable is the right way to satisfy irqentry_exit()'s requirement. This also fixes a build error on PPC32 where irq_soft_mask_set() is only defined under CONFIG_PPC64: arch/powerpc/include/asm/entry-common.h:273: error: implicit declaration of function 'irq_soft_mask_set' Using hard_irq_disable() requires no #ifdef and is consistent with how the rest of the entry code (e.g. entry-common.h:463) handles the same PPC32/PPC64 split. Fixes: 334f3f6d7a16 ("powerpc/entry: Disable interrupts before irqentry_exit") Reported-by: Venkat Rao Bagalkote Closes: https://lore.kernel.org/all/6f9bfb0f-b14c-468e-bb9f-c157d120d0dc@linux.ibm.com/ Tested-by: Venkat Rao Bagalkote Reviewed-by: Shrikanth Hegde Signed-off-by: Mukesh Kumar Chaurasiya (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260904090858.128563-1-mkchauras@gmail.com --- arch/powerpc/include/asm/entry-common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/include/asm/entry-common.h b/arch/powerpc/include/asm/entry-common.h index 94083516df57..80b07750b531 100644 --- a/arch/powerpc/include/asm/entry-common.h +++ b/arch/powerpc/include/asm/entry-common.h @@ -270,7 +270,7 @@ static inline void arch_interrupt_exit_prepare(struct pt_regs *regs) } /* irqentry_exit expects to be called with interrupts disabled */ - local_irq_disable(); + hard_irq_disable(); } static inline void arch_interrupt_async_enter_prepare(struct pt_regs *regs) From 9f2e63f1b2d5fc5b5423424902c091123e220e7e Mon Sep 17 00:00:00 2001 From: Bjoern Doebel Date: Thu, 3 Sep 2026 21:28:58 +0000 Subject: [PATCH 0920/2366] smb: client: avoid leaking refcount in cifs_queue_oplock_break() cifs_queue_oplock_break() unconditionally takes a reference on the target file before queueing cifs_oplock_break(). Only that work item decreases the reference counter again. If another oplock break arrives while that work is still queued, queue_work() will return false and not queue this second work item. As a result, we will never reach the point to drop the file reference again and are leaking this reference. This can be triggered when interacting with a slow-responding server. As a result, later unmount operations for this file system will fail with BUG: Dentry ... still in use (1) [unmount of cifs cifs] VFS: Busy inodes after unmount of cifs (cifs) kernel BUG at fs/super.c:777! Fix this by only incrementing the reference count if the work has been queued successfully. Taking it after queue_work() is safe because all three callers hold tcon->open_file_lock across the call and _cifsFileInfo_put() decrements under that same lock, so a worker that starts the handler in the window cannot drop the reference before it has been taken. Fixes: b98749cac4a69 ("CIFS: keep FileInfo handle live during oplock break") Cc: stable@vger.kernel.org Assisted-by: Kiro:claude-opus-5 Signed-off-by: Bjoern Doebel Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/misc.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index d4db3f91a91f..945194fe7a97 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -378,10 +378,11 @@ void cifs_queue_oplock_break(struct cifsFileInfo *cfile) * open_file_lock to enforce the validity of it for the oplock * break handler. The matching put is done at the end of the * handler. + * + * Only take a reference if the work is actually queued. */ - cifsFileInfo_get(cfile); - - queue_work(cifsoplockd_wq, &cfile->oplock_break); + if (queue_work(cifsoplockd_wq, &cfile->oplock_break)) + cifsFileInfo_get(cfile); } void cifs_done_oplock_break(struct cifsInodeInfo *cinode) From 23b26f4408ac3f35a482d2e5cf6fc865d4201b71 Mon Sep 17 00:00:00 2001 From: Bjoern Doebel Date: Fri, 4 Sep 2026 10:42:36 +0000 Subject: [PATCH 0921/2366] smb: client: avoid leaking refcount when cifs_sb_tlink() fails cifs_oplock_break() takes over the reference that cifs_queue_oplock_break() acquired when it queued the work, and drops it with _cifsFileInfo_put() once the break has been processed. Only in setups with "-o multiuser", cifs_sb_tlink() may fail, at which point cifs_oplock_break() returns without putting the file reference, mirroring the reference leak we already fixed in the companion patch to cifs_queue_oplock_break(). This would trigger a crash due to busy inodes on the next unmount: BUG: Dentry ... still in use (1) [unmount of cifs cifs] VFS: Busy inodes after unmount of cifs (cifs) Drop the reference on that path as well. Doing so before the out label mirrors the normal path, which also puts the reference before cifs_done_oplock_break(). Found by Sashiko code review. The failure path was not exercised at runtime. Fixes: e8f5f849ffce2 ("cifs: fix potential oops in cifs_oplock_break") Cc: stable@vger.kernel.org Assisted-by: Kiro:claude-opus-5 Signed-off-by: Bjoern Doebel Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/file.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index d7b0a9512dfa..27b58d907203 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -3348,8 +3348,11 @@ void cifs_oplock_break(struct work_struct *work) TASK_UNINTERRUPTIBLE); tlink = cifs_sb_tlink(cifs_sb); - if (IS_ERR(tlink)) + if (IS_ERR(tlink)) { + /* drop the reference taken when the break was queued */ + _cifsFileInfo_put(cfile, false /* do not wait for ourself */, false); goto out; + } tcon = tlink_tcon(tlink); server = tcon->ses->server; From 5520e89a5a4f834bced64cf2ac927001cc513a40 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Fri, 4 Sep 2026 13:48:11 +0000 Subject: [PATCH 0922/2366] smb: client: fix cifsFileInfo reference leak in deferred close When cifs_close() defers a close, it hands the cifsFileInfo reference of the closing struct file to the queued work. Each execution of smb2_deferred_work_close() drops one such reference. deferred_close_scheduled can be false while the work is pending: the workqueue clears PENDING when the callback starts to run, before the callback clears the flag under deferred_lock. A close in that interval requeues the running work, and the callback then clears the flag, leaving the requeued work pending with the flag down. A later cifs_open() can reuse the handle and its cifs_close() reaches the same branch: queue_delayed_work() fails because the work is still pending, but cifs_close() returns without dropping the closing file's reference. The cifsFileInfo count stays pinned and its tlink, dentry and server handle are leaked. Check the return value and hand off the reference only when work was actually queued. Otherwise, use the shared _cifsFileInfo_put(), like the mod_delayed_work() branch above: the pending execution already owns its reference. This issue was found by an in-house static analysis tool. Fixes: c3f207ab29f7 ("cifs: Deferred close for files") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Signed-off-by: Paulo Alcantara --- fs/smb/client/file.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 27b58d907203..1aa4844f8b8a 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -1515,11 +1515,18 @@ int cifs_close(struct inode *inode, struct file *file) trace_smb3_close_cached(tcon->tid, tcon->ses->Suid, cfile->fid.persistent_fid, cifs_sb->ctx->closetimeo); - queue_delayed_work(deferredclose_wq, - &cfile->deferred, cifs_sb->ctx->closetimeo); - cfile->deferred_close_scheduled = true; - spin_unlock(&cinode->deferred_lock); - return 0; + /* + * Each queued execution owns one reference. + * If nothing was queued, the reference of + * the closing file is dropped below. + */ + if (queue_delayed_work(deferredclose_wq, + &cfile->deferred, + cifs_sb->ctx->closetimeo)) { + cfile->deferred_close_scheduled = true; + spin_unlock(&cinode->deferred_lock); + return 0; + } } spin_unlock(&cinode->deferred_lock); _cifsFileInfo_put(cfile, true, false); From b144dc5a24149ba9a0cb2197001973a74b8c93b2 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 10 Aug 2026 09:40:00 -0700 Subject: [PATCH 0923/2366] virtio_console: allocate the port_buffer with the caller's gfp put_chars() runs from the hvc console write path with preemption disabled, so it asks alloc_buf() for GFP_ATOMIC. Only the data buffer gets it: the struct port_buffer itself keeps the GFP_KERNEL default, so the allocation can enter direct reclaim and sleep. A write to /dev/kmsg on a CONFIG_DEBUG_ATOMIC_SLEEP kernel splats: BUG: sleeping function called from invalid context at ./include/linux/sched/mm.h:320 in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 1, name: virtme-ng-init preempt_count: 1, expected: 0 Preemption disabled at: [] vprintk_emit+0x17d/0x510 Call Trace: dump_stack_lvl+0x69/0xa0 __might_resched+0x37a/0x4d0 __kmalloc_cache_noprof+0x94/0x5f0 put_chars+0x209/0x3e0 hvc_console_print+0x234/0x640 console_flush_all+0x4fc/0x950 console_unlock+0xbf/0x1b0 vprintk_emit+0x312/0x510 devkmsg_emit+0xba/0x110 devkmsg_write+0x21b/0x2e0 vfs_write+0x4dc/0x9d0 ksys_write+0x108/0x1e0 do_syscall_64+0xfa/0x460 Pass gfp on to that allocation too. Fixes: fc220d6be3c7 ("virtio_console: refactor __send_to_port() buffer ownership") Signed-off-by: Breno Leitao Acked-by: Sungho Bae Tested-by: Florian Westphal Link: https://patch.msgid.link/20260810-serial-v1-1-abbe51602c13@debian.org Signed-off-by: Greg Kroah-Hartman --- drivers/char/virtio_console.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 62eecfa61646..7f6cbe851d1e 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -426,7 +426,7 @@ static struct port_buffer *alloc_buf(struct virtio_device *vdev, size_t buf_size * Allocate buffer and the sg list. The sg list array is allocated * directly after the port_buffer struct. */ - buf = kmalloc_flex(*buf, sg, pages); + buf = kmalloc_flex(*buf, sg, pages, gfp); if (!buf) goto fail; From e384abeb559d10d6505aec053ede9368d81d4c71 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 27 Aug 2026 20:55:57 +0100 Subject: [PATCH 0924/2366] mm/huge_memory: bypass THP tuneables for huge pfnmap mappings The sysfs THP tuneables at /sys/kernel/mm/transparent_huge_pages/ rather confusingly only control the behaviour of THP in some instances. They are not applicable to MADV_COLLAPSE operations, nor to DAX mappings. Long-term, THP is predicated upon compaction being able to obtain large folios to populate THP ranges. However, vm_normal_folio() returns NULL for PFN map mappings, thus their reference count is maintained by the driver, not core mm. As a consequence, the folios are not subject to reclaim nor compaction, so are not truly part of the THP mechanism at all. However, since commit 5dd40721f147 ("mm: allow THP orders for PFNMAPs") introduced the ability to establish huge PFN maps, they have been subject to THP tuneables. This is incorrect - if a huge PFN map is available (defined by vma->vm_ops->huge_fault being non-NULL for a VMA_PFNMAP_BIT VMA), then it should be mapped huge upon fault-in. Correct this by explicitly checking for this while ensuring that smaps continues to accurately report THPeligible statistics. While here, abstract the entire file-backed THP check in vma_can_map_huge_file(), with sensible separation of logic into helper functions. Note that drm_gem_shmem_mmap() and panthor_gem_mmap() establish huge PFN maps of shmem folios, however they are marked unevictable in drm_gem_get_pages(), and in any case would fail the reference check in __remove_mapping() even if they weren't. Failing to map huge PFN maps has resulted in significant real-world performance degradation, see links for details. [ziy@nvidia.com: rename some functions] Link: https://lore.kernel.org/DL1HIHWYJ7TB.1CY76SJS0V03L@nvidia.com Link: https://lore.kernel.org/20260827-hugepfn-allowable-orders-v1-1-94819c8807c8@kernel.org Fixes: 5dd40721f147 ("mm: allow THP orders for PFNMAPs") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Zi Yan Reported-by: Cedric Le Goater Closes: https://lore.kernel.org/linux-mm/20260805055544.1568534-1-clg@redhat.com/ Reported-by: Saravanan D Closes: https://lore.kernel.org/linux-mm/20260821070520.25759-1-saravanand@crusoe.ai/ Reviewed-by: Zi Yan Tested-by: Saravanan D Tested-by: Lance Yang Reviewed-by: SJ Park Reviewed-by: Baolin Wang Cc: Barry Song Cc: David Hildenbrand Cc: Dev Jain Cc: Jason Gunthorpe Cc: Liam R. Howlett Cc: Peter Xu Cc: Ryan Roberts Cc: Signed-off-by: Andrew Morton --- mm/huge_memory.c | 86 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/mm/huge_memory.c b/mm/huge_memory.c index afbb5974bd22..1e5d68acf62a 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -92,7 +92,7 @@ unsigned long huge_anon_orders_madvise __read_mostly; unsigned long huge_anon_orders_inherit __read_mostly; static bool anon_orders_configured __initdata; -static inline bool file_thp_enabled(struct vm_area_struct *vma) +static inline bool file_thp_enabled(const struct vm_area_struct *vma) { struct inode *inode; @@ -118,6 +118,67 @@ static bool vma_is_special_huge(const struct vm_area_struct *vma) return vma_test_any(vma, VMA_PFNMAP_BIT, VMA_MIXEDMAP_BIT); } +static bool vma_file_bypass_thp_tuneables(const struct vm_area_struct *vma, + enum tva_type type) +{ + const bool has_huge_fault = vma->vm_ops->huge_fault; + + /* MADV_COLLAPSE ignores tuneables. */ + if (type == TVA_FORCED_COLLAPSE) + return true; + /* Huge PFN mappings are uncompactable so the policy doesn't apply. */ + if (vma_test(vma, VMA_PFNMAP_BIT) && has_huge_fault) + return true; + return false; +} + +static bool vma_file_allow_thp_tuneables(vm_flags_t vm_flags) +{ + /* THP=always? */ + if (hugepage_global_always()) + return true; + /* THP=madvise and marked MADV_HUGEPAGE? */ + if (hugepage_global_enabled() && (vm_flags & VM_HUGEPAGE)) + return true; + return false; +} + +static bool vma_file_check_thp_tuneables(const struct vm_area_struct *vma, + vm_flags_t vm_flags, enum tva_type type) +{ + return vma_file_bypass_thp_tuneables(vma, type) || + vma_file_allow_thp_tuneables(vm_flags); +} + +static bool vma_can_map_huge_file(const struct vm_area_struct *vma, + vm_flags_t vm_flags, enum tva_type type) +{ + const bool has_huge_fault = vma->vm_ops->huge_fault; + + /* + * Enforce THP collapse requirements as necessary. Anonymous vmas + * were already handled in thp_vma_allowable_orders(). + */ + if (!vma_file_check_thp_tuneables(vma, vm_flags, type)) + return false; + + switch (type) { + case TVA_PAGEFAULT: + /* + * Trust that ->huge_fault() handlers know what they are doing + * in fault path. + */ + return has_huge_fault; + case TVA_SMAPS: + if (has_huge_fault) + return true; + fallthrough; + default: + /* Only regular file is valid in collapse path. */ + return file_thp_enabled(vma); + } +} + unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma, vm_flags_t vm_flags, enum tva_type type, @@ -190,27 +251,8 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma, vma, vma_start_pgoff(vma), 0, forced_collapse); - if (!vma_is_anonymous(vma)) { - /* - * Enforce THP collapse requirements as necessary. Anonymous vmas - * were already handled in thp_vma_allowable_orders(). - */ - if (!forced_collapse && - (!hugepage_global_enabled() || (!(vm_flags & VM_HUGEPAGE) && - !hugepage_global_always()))) - return 0; - - /* - * Trust that ->huge_fault() handlers know what they are doing - * in fault path. - */ - if (((in_pf || smaps)) && vma->vm_ops->huge_fault) - return orders; - /* Only regular file is valid in collapse path */ - if (((!in_pf || smaps)) && file_thp_enabled(vma)) - return orders; - return 0; - } + if (!vma_is_anonymous(vma)) + return vma_can_map_huge_file(vma, vm_flags, type) ? orders : 0; if (vma_is_temporary_stack(vma)) return 0; From 397432cab17bccb600fd6c16ed593f1149042268 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Fri, 28 Aug 2026 12:20:37 +0100 Subject: [PATCH 0925/2366] mm/mremap: account mm->locked_vm correctly for MREMAP_DONTUNMAP When a VMA is mremap()'d with MREMAP_DONTUNMAP set, that results in the VMA being copied, but the source VMA not being unmapped. If the VMA is mlock()'d this is a legal operation, though the source VMA has its VMA_LOCKED_BIT cleared. However this is done in dontunmap_complete(), after mm->locked_vm was incremented via vrm_stat_account(), resulting in double-counting. Worse, this is not even corrected when source VMA is unmapped, due to the VMA_LOCKED_BIT flag having been cleared. This all works fine in the usual mremap() case (without MREMAP_DONTUNMAP), as the source VMA is unmapped with VMA_LOCKED_BIT intact, at which time mm->locked_vm is decremented accordingly. Resolve the issue by invoking vrm_stat_account() only after dontunmap_complete() has run. Note that MREMAP_DONTUNMAP requires old_len == new_len, so no need to account for a delta in size in this case. The bug was introduced by commit b714ccb02a76 ("mm/mremap: complete refactor of move_vma()") which incorrectly reordered the accounting and the clearing of the VMA_LOCKED_BIT flag. Link: https://lore.kernel.org/20260828-mremap-fix-locked-vm-v1-1-c80be7505d1e@kernel.org Fixes: b714ccb02a76 ("mm/mremap: complete refactor of move_vma()") Signed-off-by: Lorenzo Stoakes (ARM) Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260825-fix-mremap-dontunmap-pgoff-v1-1-39a40b2c98b3@kernel.org Reported-by: Kunwu Chan Closes: https://lore.kernel.org/all/20260828094823.594279-1-kunwu.chan@linux.dev/ Acked-by: Vlastimil Babka (SUSE) Tested-by: Kunwu Chan Reviewed-by: Kunwu Chan Cc: Jann Horn Cc: Liam R. Howlett Cc: Pedro Falcato Cc: Signed-off-by: Andrew Morton --- mm/mremap.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mm/mremap.c b/mm/mremap.c index 2b4b523a86b8..7c368440fafe 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -1355,12 +1355,11 @@ static void dontunmap_complete(struct vma_remap_struct *vrm, if (vma_is_anonymous(vma) && !vma->vm_file) vma_set_pgoff(vma, pgoff_unfaulted); } - - /* Because we won't unmap we don't need to touch locked_vm. */ } static unsigned long move_vma(struct vma_remap_struct *vrm) { + const bool is_dontunmap = vrm->flags & MREMAP_DONTUNMAP; struct mm_struct *mm = current->mm; struct vm_area_struct *new_vma; unsigned long hiwater_vm; @@ -1401,10 +1400,10 @@ static unsigned long move_vma(struct vma_remap_struct *vrm) */ hiwater_vm = mm->hiwater_vm; - vrm_stat_account(vrm, vrm->new_len); - if (unlikely(!err && (vrm->flags & MREMAP_DONTUNMAP))) + if (unlikely(is_dontunmap && !err)) dontunmap_complete(vrm, new_vma); - else + vrm_stat_account(vrm, vrm->new_len); + if (!is_dontunmap || err) unmap_source_vma(vrm); mm->hiwater_vm = hiwater_vm; From e1d56f046507befa20a5e0837d8075abdf5848fd Mon Sep 17 00:00:00 2001 From: Coiby Xu Date: Fri, 28 Aug 2026 16:41:06 +0800 Subject: [PATCH 0926/2366] mailmap: map Coiby Xu's address Point to my gmail address as I've left Red Hat. Link: https://lore.kernel.org/20260828084106.1494733-1-coiby.xu@gmail.com Signed-off-by: Coiby Xu Signed-off-by: Andrew Morton --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 37aad399e4e0..b355f1c85958 100644 --- a/.mailmap +++ b/.mailmap @@ -222,6 +222,7 @@ Chuck Lever Chuck Lever Chuck Lever Claudiu Beznea +Coiby Xu Colin Ian King Corey Minyard Damian Hobson-Garcia From 12e9ac7bc5b254048f886bf421e3a15491106c1f Mon Sep 17 00:00:00 2001 From: Nhat Pham Date: Fri, 28 Aug 2026 12:14:33 -0700 Subject: [PATCH 0927/2366] mm, swap: fix SWAP_USAGE_OFFLIST_BIT collision with real usage count SWAP_USAGE_OFFLIST_BIT is embedded in the si->inuse_pages usage counter, and is meant to sit above any value that counter can reach. However, it is defined from BITS_PER_TYPE(atomic_t), so it is bit 30. On a system with 4 KiB pages the flag collides with the usage count once that count reaches 4 TiB. swap_usage_in_pages() masks bit 30 out, so whenever the real count has that bit set, every caller of it reads 4 TiB low: * /proc/swaps understates Used by 4 TiB. * A raw count of exactly 2^30 masks to zero, so try_to_unuse() takes its "if (!swap_usage_in_pages(si)) goto success;" early exit and swapoff tears the device down while pages are still swapped out. Nothing in the rest of swapoff aborts the teardown, so those pages are lost. Independently of swapoff, the collision also corrupts the counter and the plist. On a device in normal use, a free that leaves bit 30 set in the count makes swap_usage_sub() see the flag where there is only count, and call add_to_avail_list(). It clears the bit with fetch_and(~SWAP_USAGE_OFFLIST_BIT), leaving the stored count 4 TiB below the real one, and calls plist_add() on a device that is already listed, tripping the WARN_ON(!plist_node_empty(node)) in plist_add() and linking the node a second time. Change the definition of SWAP_USAGE_OFFLIST_BIT to be based on atomic_long_t instead. Note that the usage counter field itself is of this same type, so it is still a valid bit. Link: https://lore.kernel.org/20260828191433.3304458-1-nphamcs@gmail.com Fixes: b228386cf237 ("mm, swap: clean up plist removal and adding") Signed-off-by: Nhat Pham Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260825153238.2695446-1-nphamcs%40gmail.com Suggested-by: Andrew Morton Reviewed-by: Andrew Morton Acked-by: Kairui Song Cc: Baoquan He Cc: Barry Song Cc: Chris Li Cc: Gregory Price Cc: Johannes Weiner Cc: Joshua Hahn Cc: Kemeng Shi Cc: Shakeel Butt Cc: Youngjun Park Cc: Signed-off-by: Andrew Morton --- mm/swapfile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/swapfile.c b/mm/swapfile.c index 53bf01d5f7f1..601979b97f95 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -156,7 +156,7 @@ static struct swap_info_struct *swap_entry_to_info(swp_entry_t entry) * This bit will be set if the device is not on the plist and not * usable, will be cleared if the device is on the plist. */ -#define SWAP_USAGE_OFFLIST_BIT (1UL << (BITS_PER_TYPE(atomic_t) - 2)) +#define SWAP_USAGE_OFFLIST_BIT (1UL << (BITS_PER_TYPE(atomic_long_t) - 2)) #define SWAP_USAGE_COUNTER_MASK (~SWAP_USAGE_OFFLIST_BIT) static long swap_usage_in_pages(struct swap_info_struct *si) { From 6e673d0879ef78c395cfe0d3ba316690a60055d8 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Fri, 28 Aug 2026 19:32:51 -0700 Subject: [PATCH 0928/2366] memcg: avoid charging the root memcg from obj_cgroup_charge_pages() obj_cgroup_charge_pages() resolves the objcg to its memcg and calls try_charge_memcg(), which does not short circuit the root memcg. That memcg can be the root memcg: obj_cgroup_is_root() reflects the memcg the objcg was created for and is never updated, while memcg_reparent_objcgs() does redirect objcg->memcg to the parent on rmdir. An objcg of a dying child of root therefore passes every obj_cgroup_is_root() filter but resolves to the root memcg. Folios keep the objcg they were charged with, so this is easy to reach through zswap: allocate anon memory in a cgroup, move the task out, remove the cgroup, then write to the root cgroup's memory.reclaim. The reclaimed folios are charged through the reparented objcg and end up in refill_stock() with the root memcg: WARNING: mm/memcontrol.c:2198 at refill_stock+0x644/0x940 refill_stock+0x644/0x940 try_charge_memcg+0x12d6/0x1570 __obj_cgroup_charge+0x35/0xf0 obj_cgroup_charge+0x1de/0x210 obj_cgroup_charge_zswap+0x83/0x270 zswap_store+0x1620/0x2000 swap_writeout+0x94c/0x14c0 shrink_folio_list+0x3388/0x52b0 [...] try_to_free_mem_cgroup_pages+0x30d/0x830 user_proactive_reclaim+0x504/0x840 memory_reclaim+0x1f/0x30 Beyond the warning, the charge is asymmetric: obj_cgroup_uncharge_pages() skips refill_stock() for the root memcg, so the root's page counter grows and is never uncharged. It is not user visible, since memory.current is not exposed on the root, but it is a leak. Use try_charge(), which returns early for the root memcg, restoring the symmetry with obj_cgroup_uncharge_pages(). The above sequence was scripted into a standalone reproducer (zswap on, swap on a virtio disk, 512MB of anon memory faulted in inside a child of the root cgroup, the task then migrated to the root cgroup, the child removed, followed by "echo 600M swappiness=max > memory.reclaim" on the root) and run in a CONFIG_DEBUG_VM=y VM. It reproduces the splat on the first zswap store of a reparented folio, with the same call chain as the report. With this patch applied the splat is gone while the zswap store count over the run is unchanged, so the same path is still exercised. cgroup selftests test_zswap, test_kmem and test_memcontrol show no new failures. Link: https://lore.kernel.org/20260829023251.474083-1-shakeel.butt@linux.dev Fixes: 20d6c1725228 ("memcg: avoid refill_stock for root memcg") Signed-off-by: Shakeel Butt Reported-by: Farhad Alemi Closes: https://lore.kernel.org/all/CA+0ovCgWzUMK+nNbbtH7eV65Ca=fDN4Ozu7iASgryjvv8Tk8zQ@mail.gmail.com/ Reviewed-by: Muchun Song Reviewed-by: Johannes Weiner Cc: Michal Hocko Cc: Roman Gushchin Cc: Signed-off-by: Andrew Morton --- mm/memcontrol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 1271d390b617..856a7d07586c 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -3158,7 +3158,7 @@ static int obj_cgroup_charge_pages(struct obj_cgroup *objcg, gfp_t gfp, memcg = get_mem_cgroup_from_objcg(objcg); - ret = try_charge_memcg(memcg, gfp, nr_pages); + ret = try_charge(memcg, gfp, nr_pages); if (ret) goto out; From e1d469a8d6c63032a5aed3679a13a086b61bd5cc Mon Sep 17 00:00:00 2001 From: Christopher Obbard Date: Sat, 29 Aug 2026 12:28:23 +0100 Subject: [PATCH 0929/2366] mailmap: update entry for Christopher Obbard I have changed employer; update my mailmap entry to point at my new email address. Link: https://lore.kernel.org/20260829-update-mail-oss-qualcomm-v2-1-1670c515f225@oss.qualcomm.com Signed-off-by: Christopher Obbard Signed-off-by: Andrew Morton --- .mailmap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index b355f1c85958..90ff4831f592 100644 --- a/.mailmap +++ b/.mailmap @@ -211,7 +211,8 @@ Christophe Leroy Christophe Leroy Christophe Leroy Christophe Ricard -Christopher Obbard +Christopher Obbard +Christopher Obbard Christoph Hellwig Christoph Manszewski Christoph Paasch From 848d2ce2fce15fbdc083fbf9691bfa72911033c4 Mon Sep 17 00:00:00 2001 From: Wenjie Qi Date: Sun, 30 Aug 2026 01:36:12 +0800 Subject: [PATCH 0930/2366] mm: filemap: retain mapped dropbehind folios Fault-around can map ready dropbehind folios without going through the normal page-cache lookup that clears dropbehind. A mapping represents a competing cached user, so retain the folio instead of forcibly unmapping it when writeback completes. For a mapped folio, folio_unmap_invalidate() can call unmap_mapping_folio(), which takes i_mmap_rwsem and may sleep. Retaining mapped folios avoids this path when folio_end_dropbehind() runs in non-preemptible task context. Tal was able to trigger a sleeping-in-atomic warning due to this [1]. Unmapped dropbehind folios continue through the existing invalidation path. Link: https://lore.kernel.org/4aba05e1a2c3b61cb337d373eb9b7a8db4ddd822.1788024049.git.qiwenjie@xiaomi.com Link: https://lore.kernel.org/076bb01b-6fcf-4691-be8c-0e8507c9fe64@columbia.edu [1] Fixes: fb7d3bc41493 ("mm/filemap: drop streaming/uncached pages when writeback completes") Signed-off-by: Wenjie Qi Reviewed-by: Matthew Wilcox (Oracle) Reviewed-by: Tal Zussman Tested-by: Tal Zussman Cc: Barry Song Cc: Jan Kara Cc: Jens Axboe Cc: Trond Myklebust Cc: Signed-off-by: Andrew Morton --- mm/filemap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/filemap.c b/mm/filemap.c index 6afec636881f..00fd89cf6f55 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -1616,7 +1616,7 @@ static void filemap_end_dropbehind(struct folio *folio) return; if (!folio_test_clear_dropbehind(folio)) return; - if (mapping) + if (mapping && !folio_mapped(folio)) folio_unmap_invalidate(mapping, folio, 0); } From 641aade99f06df0037e52b5c81645461b7132947 Mon Sep 17 00:00:00 2001 From: Qi Zheng Date: Mon, 17 Aug 2026 17:03:25 +0800 Subject: [PATCH 0931/2366] fs: fix missed removal of super_fs_objects_eligible() Commit 0ef8faff490be ("fs: push nr_cached_objects memcg gating into individual filesystems") was meant to drop the blanket memcg gate in fs/super.c and let each ->nr_cached_objects() implementation decide for itself whether it is meaningful in per-memcg reclaim. However, when that patch was applied the removal of super_fs_objects_eligible() and its two call sites in super_cache_scan() / super_cache_count() was lost, so the helper is still gating every ->nr_cached_objects() hook and 0ef8faff490be is effectively a no-op. Consequences of the leftover gate: - XFS's inode-reclaim hook, which is intentionally driven from per-memcg contexts to free memcg-charged slab, is still short-circuited in fs/super.c exactly the regression from commit 0baad6f9b997 ("fs/super: skip non-memcg-aware nr_cached_objects in memcg slab shrink") that 0ef8faff490be was written to undo. Memcg-charged XFS inode slab therefore keeps piling up under per-memcg pressure until global reclaim kicks in. - Any future ->nr_cached_objects()/->free_cached_objects() that grows memcg awareness is likewise blocked before it can run, so filesystems cannot opt in to per-memcg reclaim on their own defeating the whole point of pushing the gating decision down into the callbacks. Drop the leftover helper and its call sites so the intent of 0ef8faff490be actually takes effect. Link: https://lore.kernel.org/cover.1786955972.git.zhengqi.arch@bytedance.com Link: https://lore.kernel.org/3b038d373c70ebac7cdabfb0035bb91d1d6e6cfe.1786955972.git.zhengqi.arch@bytedance.com Link: https://lore.kernel.org/all/20260715103516.2410175-1-usama.arif@linux.dev/ [0] Fixes: 0ef8faff490b ("fs: push nr_cached_objects memcg gating into individual filesystems") Signed-off-by: Qi Zheng Acked-by: Usama Arif Cc: Baolin Wang Cc: Christian Brauner Cc: David Hildenbrand Cc: Hugh Dickins Cc: Johannes Weiner Cc: Michal Hocko Cc: Muchun Song Cc: Roman Gushchin Cc: Shakeel Butt Cc: Signed-off-by: Andrew Morton --- fs/super.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/fs/super.c b/fs/super.c index 05e443173038..3ecce24328f6 100644 --- a/fs/super.c +++ b/fs/super.c @@ -171,19 +171,6 @@ static void super_wake(struct super_block *sb, unsigned int flag) wake_up_var(&sb->s_flags); } -/* - * The s_op->nr_cached_objects hooks (used for example by btrfs and xfs) - * operate on filesystem-global state and ignore sc->memcg. Driving them - * from per-memcg shrink_slab_memcg() invocations only burns CPU walking - * per-cpu counters and queueing duplicate work: the actual reclaim happens on - * the global path (kswapd or root direct reclaim) regardless. Restrict them - * to that path. - */ -static inline bool super_fs_objects_eligible(struct shrink_control *sc) -{ - return !sc->memcg || mem_cgroup_is_root(sc->memcg); -} - /* * One thing we have to be careful of with a per-sb shrinker is that we don't * drop the last active reference to the superblock from within the shrinker. @@ -213,7 +200,7 @@ static unsigned long super_cache_scan(struct shrinker *shrink, if (!super_trylock_shared(sb)) return SHRINK_STOP; - if (sb->s_op->nr_cached_objects && super_fs_objects_eligible(sc)) + if (sb->s_op->nr_cached_objects) fs_objects = sb->s_op->nr_cached_objects(sb, sc); inodes = list_lru_shrink_count(&sb->s_inode_lru, sc); @@ -274,8 +261,7 @@ static unsigned long super_cache_count(struct shrinker *shrink, return 0; smp_rmb(); - if (sb->s_op && sb->s_op->nr_cached_objects && - super_fs_objects_eligible(sc)) + if (sb->s_op && sb->s_op->nr_cached_objects) total_objects = sb->s_op->nr_cached_objects(sb, sc); total_objects += list_lru_shrink_count(&sb->s_dentry_lru, sc); From 8e2b8614039853e68d5338e37821e8bcee9fc05f Mon Sep 17 00:00:00 2001 From: Seunguk Shin Date: Mon, 3 Aug 2026 13:34:55 +0100 Subject: [PATCH 0932/2366] fs/dax: check zero or empty entry before converting xarray entry Calling dax_to_folio() with empty entry causes kernel panic below when booting a VM with DAX enabled storage. This patch checks empty entry before calling dax_to_folio() on dax_associate_entry(), dax_disassociate_entry(), and dax_busy_page(). Commit 98c183a4fccf ("fs/dax: don't disassociate zero page entries") added guards in the associate and disassociate paths, but the guards still come after dax_to_folio(), and dax_busy_page() still has the same problem. [ 0.737679] EXT4-fs (pmem0p1): mounted filesystem 79676804-7c8b-491a-b2a6-9bae3c72af70 ro with ordered data mode. Quota mode: disabled. [ 0.737891] VFS: Mounted root (ext4 filesystem) readonly on device 259:1. [ 0.739119] devtmpfs: mounted [ 0.739476] Freeing unused kernel memory: 1920K [ 0.740156] Run /sbin/init as init process [ 0.740229] with arguments: [ 0.740286] /sbin/init [ 0.740321] with environment: [ 0.740369] HOME=/ [ 0.740400] TERM=linux [ 0.743162] Unable to handle kernel paging request at virtual address fffffdffbf000008 [ 0.743285] Mem abort info: [ 0.743316] ESR = 0x0000000096000006 [ 0.743371] EC = 0x25: DABT (current EL), IL = 32 bits [ 0.743444] SET = 0, FnV = 0 [ 0.743489] EA = 0, S1PTW = 0 [ 0.743545] FSC = 0x06: level 2 translation fault [ 0.743610] Data abort info: [ 0.743656] ISV = 0, ISS = 0x00000006, ISS2 = 0x00000000 [ 0.743720] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 0.743785] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 0.743848] swapper pgtable: 4k pages, 48-bit VAs, pgdp=00000000b9d17000 [ 0.743931] [fffffdffbf000008] pgd=10000000bfa3d403, p4d=10000000bfa3d403, pud=1000000040bfe403, pmd=0000000000000000 [ 0.744070] Internal error: Oops: 0000000096000006 [#1] SMP [ 0.748888] CPU: 0 UID: 0 PID: 1 Comm: init Not tainted 6.18.4 #1 NONE [ 0.749421] pstate: 004000c5 (nzcv daIF +PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 0.749969] pc : dax_disassociate_entry.constprop.0+0x20/0x50 [ 0.750444] lr : dax_insert_entry+0xcc/0x408 [ 0.750802] sp : ffff80008000b9e0 [ 0.751083] x29: ffff80008000b9e0 x28: 0000000000000000 x27: 0000000000000000 [ 0.751682] x26: 0000000001963d01 x25: ffff0000004f7d90 x24: 0000000000000000 [ 0.752264] x23: 0000000000000000 x22: ffff80008000bcc8 x21: 0000000000000011 [ 0.752836] x20: ffff80008000ba90 x19: 0000000001963d01 x18: 0000000000000000 [ 0.753407] x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000000 [ 0.753970] x14: ffffbf3154b9ae70 x13: 0000000000000000 x12: ffffbf3154b9ae70 [ 0.754548] x11: ffffffffffffffff x10: 0000000000000000 x9 : 0000000000000000 [ 0.755122] x8 : 000000000000000d x7 : 000000000000001f x6 : 0000000000000000 [ 0.755707] x5 : 0000000000000000 x4 : 0000000000000000 x3 : fffffdffc0000000 [ 0.756287] x2 : 0000000000000008 x1 : 0000000040000000 x0 : fffffdffbf000000 [ 0.756871] Call trace: [ 0.757107] dax_disassociate_entry.constprop.0+0x20/0x50 (P) [ 0.757592] dax_iomap_pte_fault+0x4fc/0x808 [ 0.757951] dax_iomap_fault+0x28/0x30 [ 0.758258] ext4_dax_huge_fault+0x80/0x2dc [ 0.758594] ext4_dax_fault+0x10/0x3c [ 0.758892] __do_fault+0x38/0x12c [ 0.759175] __handle_mm_fault+0x530/0xcf0 [ 0.759518] handle_mm_fault+0xe4/0x230 [ 0.759833] do_page_fault+0x17c/0x4dc [ 0.760144] do_translation_fault+0x30/0x38 [ 0.760483] do_mem_abort+0x40/0x8c [ 0.760771] el0_ia+0x4c/0x170 [ 0.761032] el0t_64_sync_handler+0xd8/0xdc [ 0.761371] el0t_64_sync+0x168/0x16c [ 0.761677] Code: f9453021 f2dfbfe3 cb813080 8b001860 (f9400401) [ 0.762168] ---[ end trace 0000000000000000 ]--- [ 0.762550] note: init[1] exited with irqs disabled [ 0.762631] Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b Link: https://lore.kernel.org/m2y0enxtzk.fsf@arm.com Fixes: 38607c62b34b ("fs/dax: properly refcount fs dax pages") Signed-off-by: Seunguk Shin Reviewed-by: Jan Kara Reviewed-by: Alistair Popple Reported-by: Kiara Grouwstra Cc: Al Viro Cc: Christian Brauner Cc: Matthew Wilcox (Oracle) Cc: Signed-off-by: Andrew Morton --- fs/dax.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/dax.c b/fs/dax.c index 6ba50142eeb2..1fbba0d21c13 100644 --- a/fs/dax.c +++ b/fs/dax.c @@ -480,11 +480,12 @@ static void dax_associate_entry(void *entry, struct address_space *mapping, unsigned long address, bool shared) { unsigned long size = dax_entry_size(entry), index; - struct folio *folio = dax_to_folio(entry); + struct folio *folio; if (dax_is_zero_entry(entry) || dax_is_empty_entry(entry)) return; + folio = dax_to_folio(entry); index = linear_page_index(vma, address & ~(size - 1)); if (shared && (folio->mapping || dax_folio_is_shared(folio))) { if (folio->mapping) @@ -505,21 +506,23 @@ static void dax_associate_entry(void *entry, struct address_space *mapping, static void dax_disassociate_entry(void *entry, struct address_space *mapping, bool trunc) { - struct folio *folio = dax_to_folio(entry); + struct folio *folio; if (dax_is_zero_entry(entry) || dax_is_empty_entry(entry)) return; + folio = dax_to_folio(entry); dax_folio_put(folio); } static struct page *dax_busy_page(void *entry) { - struct folio *folio = dax_to_folio(entry); + struct folio *folio; if (dax_is_zero_entry(entry) || dax_is_empty_entry(entry)) return NULL; + folio = dax_to_folio(entry); if (folio_ref_count(folio) - folio_mapcount(folio)) return &folio->page; else From 0791a234b35d4b72187c497e05817f2c9019c3ab Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Tue, 1 Sep 2026 13:09:29 -0700 Subject: [PATCH 0933/2366] remove old lib/alloc_tag.c This was moved into mm/, but the original lib/ file somehow remained. Remove it. Reported-by: Suren Baghdasaryan Cc: Lorenzo Stoakes Signed-off-by: Andrew Morton --- lib/alloc_tag.c | 1029 ----------------------------------------------- 1 file changed, 1029 deletions(-) delete mode 100644 lib/alloc_tag.c diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c deleted file mode 100644 index e5b218176c5a..000000000000 --- a/lib/alloc_tag.c +++ /dev/null @@ -1,1029 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ALLOCINFO_FILE_NAME "allocinfo" -#define MODULE_ALLOC_TAG_VMAP_SIZE (100000UL * sizeof(struct alloc_tag)) -#define SECTION_START(NAME) (CODETAG_SECTION_START_PREFIX NAME) -#define SECTION_STOP(NAME) (CODETAG_SECTION_STOP_PREFIX NAME) - -#ifdef CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT -static bool mem_profiling_support = true; -#else -static bool mem_profiling_support; -#endif - -/* - * Memory allocation profiling is permanently disabled and cannot be enabled. - * Must be called after setup_early_mem_profiling(). - */ -bool mem_alloc_profiling_permanently_disabled(void) -{ - return !mem_profiling_support; -} - -static struct codetag_type *alloc_tag_cttype; - -#ifdef CONFIG_ARCH_MODULE_NEEDS_WEAK_PER_CPU -DEFINE_PER_CPU(struct alloc_tag_counters, _shared_alloc_tag); -EXPORT_SYMBOL(_shared_alloc_tag); -#endif - -DEFINE_STATIC_KEY_MAYBE(CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT, - mem_alloc_profiling_key); -EXPORT_SYMBOL(mem_alloc_profiling_key); - -DEFINE_STATIC_KEY_FALSE(mem_profiling_compressed); - -struct alloc_tag_kernel_section kernel_tags = { NULL, 0 }; -unsigned long alloc_tag_ref_mask; -int alloc_tag_ref_offs; - -struct allocinfo_private { - struct codetag_iterator iter; - struct codetag_iterator reported_iter; - bool print_header; -}; - -static void *allocinfo_start(struct seq_file *m, loff_t *pos) -{ - struct allocinfo_private *priv; - loff_t node = *pos; - - priv = (struct allocinfo_private *)m->private; - codetag_lock_module_list(alloc_tag_cttype); - if (node == 0) { - priv->print_header = true; - priv->iter = codetag_get_ct_iter(alloc_tag_cttype); - } else { - priv->iter = priv->reported_iter; - } - codetag_next_ct(&priv->iter); - return priv->iter.ct ? priv : NULL; -} - -static void *allocinfo_next(struct seq_file *m, void *arg, loff_t *pos) -{ - struct allocinfo_private *priv = (struct allocinfo_private *)arg; - struct codetag *ct; - - priv->reported_iter = priv->iter; - ct = codetag_next_ct(&priv->iter); - (*pos)++; - if (!ct) - return NULL; - - return priv; -} - -static void allocinfo_stop(struct seq_file *m, void *arg) -{ - codetag_unlock_module_list(alloc_tag_cttype); -} - -static void print_allocinfo_header(struct seq_buf *buf) -{ - /* Output format version, so we can change it. */ - seq_buf_printf(buf, "allocinfo - version: 2.0\n"); - seq_buf_printf(buf, "# \n"); -} - -static void alloc_tag_to_text(struct seq_buf *out, struct codetag *ct) -{ - struct alloc_tag *tag = ct_to_alloc_tag(ct); - struct alloc_tag_counters counter = alloc_tag_read(tag); - s64 bytes = counter.bytes; - - seq_buf_printf(out, "%12lli %8llu ", bytes, counter.calls); - codetag_to_text(out, ct); - if (unlikely(alloc_tag_is_inaccurate(tag))) - seq_buf_printf(out, " accurate:no"); - seq_buf_putc(out, ' '); - seq_buf_putc(out, '\n'); -} - -static int allocinfo_show(struct seq_file *m, void *arg) -{ - struct allocinfo_private *priv = (struct allocinfo_private *)arg; - char *bufp; - size_t n = seq_get_buf(m, &bufp); - struct seq_buf buf; - - seq_buf_init(&buf, bufp, n); - if (priv->print_header) { - print_allocinfo_header(&buf); - priv->print_header = false; - } - alloc_tag_to_text(&buf, priv->iter.ct); - seq_commit(m, seq_buf_used(&buf)); - return 0; -} - -static const struct seq_operations allocinfo_seq_op = { - .start = allocinfo_start, - .next = allocinfo_next, - .stop = allocinfo_stop, - .show = allocinfo_show, -}; - -size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep) -{ - struct codetag_iterator iter; - struct codetag *ct; - struct codetag_bytes n; - unsigned int i, nr = 0; - - if (IS_ERR_OR_NULL(alloc_tag_cttype)) - return 0; - - if (can_sleep) - codetag_lock_module_list(alloc_tag_cttype); - else if (!codetag_trylock_module_list(alloc_tag_cttype)) - return 0; - - iter = codetag_get_ct_iter(alloc_tag_cttype); - while ((ct = codetag_next_ct(&iter))) { - struct alloc_tag_counters counter = alloc_tag_read(ct_to_alloc_tag(ct)); - - n.ct = ct; - n.bytes = counter.bytes; - - for (i = 0; i < nr; i++) - if (n.bytes > tags[i].bytes) - break; - - if (i < count) { - nr -= nr == count; - memmove(&tags[i + 1], - &tags[i], - sizeof(tags[0]) * (nr - i)); - nr++; - tags[i] = n; - } - } - - codetag_unlock_module_list(alloc_tag_cttype); - - return nr; -} - -void pgalloc_tag_split(struct folio *folio, int old_order, int new_order) -{ - int i; - struct alloc_tag *tag; - unsigned int nr_pages = 1 << new_order; - - if (!mem_alloc_profiling_enabled()) - return; - - tag = __pgalloc_tag_get(&folio->page); - if (!tag) - return; - - for (i = nr_pages; i < (1 << old_order); i += nr_pages) { - union pgtag_ref_handle handle; - union codetag_ref ref; - - if (get_page_tag_ref(folio_page(folio, i), &ref, &handle)) { - /* Set new reference to point to the original tag */ - alloc_tag_ref_set(&ref, tag); - update_page_tag_ref(handle, &ref); - put_page_tag_ref(handle); - } - } -} - -void pgalloc_tag_swap(struct folio *new, struct folio *old) -{ - union pgtag_ref_handle handle_old, handle_new; - union codetag_ref ref_old, ref_new; - struct alloc_tag *tag_old, *tag_new; - - if (!mem_alloc_profiling_enabled()) - return; - - tag_old = __pgalloc_tag_get(&old->page); - if (!tag_old) - return; - tag_new = __pgalloc_tag_get(&new->page); - if (!tag_new) - return; - - if (!get_page_tag_ref(&old->page, &ref_old, &handle_old)) - return; - if (!get_page_tag_ref(&new->page, &ref_new, &handle_new)) { - put_page_tag_ref(handle_old); - return; - } - - /* - * Clear tag references to avoid debug warning when using - * __alloc_tag_ref_set() with non-empty reference. - */ - set_codetag_empty(&ref_old); - set_codetag_empty(&ref_new); - - /* swap tags */ - __alloc_tag_ref_set(&ref_old, tag_new); - update_page_tag_ref(handle_old, &ref_old); - __alloc_tag_ref_set(&ref_new, tag_old); - update_page_tag_ref(handle_new, &ref_new); - - put_page_tag_ref(handle_old); - put_page_tag_ref(handle_new); -} - -static void shutdown_mem_profiling(bool remove_file) -{ - if (mem_alloc_profiling_enabled()) - static_branch_disable(&mem_alloc_profiling_key); - - if (!mem_profiling_support) - return; - - if (remove_file) - remove_proc_entry(ALLOCINFO_FILE_NAME, NULL); - mem_profiling_support = false; -} - -void __init alloc_tag_sec_init(void) -{ - struct alloc_tag *last_codetag; - - if (!mem_profiling_support) - return; - - if (!static_key_enabled(&mem_profiling_compressed)) - return; - - kernel_tags.first_tag = (struct alloc_tag *)kallsyms_lookup_name( - SECTION_START(ALLOC_TAG_SECTION_NAME)); - last_codetag = (struct alloc_tag *)kallsyms_lookup_name( - SECTION_STOP(ALLOC_TAG_SECTION_NAME)); - kernel_tags.count = last_codetag - kernel_tags.first_tag; - - /* Check if kernel tags fit into page flags */ - if (kernel_tags.count > (1UL << NR_UNUSED_PAGEFLAG_BITS)) { - shutdown_mem_profiling(false); /* allocinfo file does not exist yet */ - pr_err("%lu allocation tags cannot be references using %d available page flag bits. Memory allocation profiling is disabled!\n", - kernel_tags.count, NR_UNUSED_PAGEFLAG_BITS); - return; - } - - alloc_tag_ref_offs = (LRU_REFS_PGOFF - NR_UNUSED_PAGEFLAG_BITS); - alloc_tag_ref_mask = ((1UL << NR_UNUSED_PAGEFLAG_BITS) - 1); - pr_debug("Memory allocation profiling compression is using %d page flag bits!\n", - NR_UNUSED_PAGEFLAG_BITS); -} - -#ifdef CONFIG_MODULES - -static struct maple_tree mod_area_mt = MTREE_INIT(mod_area_mt, MT_FLAGS_ALLOC_RANGE); -static struct vm_struct *vm_module_tags; -/* A dummy object used to indicate an unloaded module */ -static struct module unloaded_mod; -/* A dummy object used to indicate a module prepended area */ -static struct module prepend_mod; - -struct alloc_tag_module_section module_tags; - -static inline unsigned long alloc_tag_align(unsigned long val) -{ - if (!static_key_enabled(&mem_profiling_compressed)) { - /* No alignment requirements when we are not indexing the tags */ - return val; - } - - if (val % sizeof(struct alloc_tag) == 0) - return val; - return ((val / sizeof(struct alloc_tag)) + 1) * sizeof(struct alloc_tag); -} - -static bool ensure_alignment(unsigned long align, unsigned int *prepend) -{ - if (!static_key_enabled(&mem_profiling_compressed)) { - /* No alignment requirements when we are not indexing the tags */ - return true; - } - - /* - * If alloc_tag size is not a multiple of required alignment, tag - * indexing does not work. - */ - if (!IS_ALIGNED(sizeof(struct alloc_tag), align)) - return false; - - /* Ensure prepend consumes multiple of alloc_tag-sized blocks */ - if (*prepend) - *prepend = alloc_tag_align(*prepend); - - return true; -} - -static inline bool tags_addressable(void) -{ - unsigned long tag_idx_count; - - if (!static_key_enabled(&mem_profiling_compressed)) - return true; /* with page_ext tags are always addressable */ - - tag_idx_count = CODETAG_ID_FIRST + kernel_tags.count + - module_tags.size / sizeof(struct alloc_tag); - - return tag_idx_count < (1UL << NR_UNUSED_PAGEFLAG_BITS); -} - -static bool needs_section_mem(struct module *mod, unsigned long size) -{ - if (!mem_profiling_support) - return false; - - return size >= sizeof(struct alloc_tag); -} - -static bool clean_unused_counters(struct alloc_tag *start_tag, - struct alloc_tag *end_tag) -{ - struct alloc_tag *tag; - bool ret = true; - - for (tag = start_tag; tag <= end_tag; tag++) { - struct alloc_tag_counters counter; - - if (!tag->counters) - continue; - - counter = alloc_tag_read(tag); - if (!counter.bytes) { - free_percpu(tag->counters); - tag->counters = NULL; - } else { - ret = false; - } - } - - return ret; -} - -/* Called with mod_area_mt locked */ -static void clean_unused_module_areas_locked(void) -{ - MA_STATE(mas, &mod_area_mt, 0, module_tags.size); - struct module *val; - - mas_for_each(&mas, val, module_tags.size) { - struct alloc_tag *start_tag; - struct alloc_tag *end_tag; - - if (val != &unloaded_mod) - continue; - - /* Release area if all tags are unused */ - start_tag = (struct alloc_tag *)(module_tags.start_addr + mas.index); - end_tag = (struct alloc_tag *)(module_tags.start_addr + mas.last); - if (clean_unused_counters(start_tag, end_tag)) - mas_erase(&mas); - } -} - -/* Called with mod_area_mt locked */ -static bool find_aligned_area(struct ma_state *mas, unsigned long section_size, - unsigned long size, unsigned int prepend, unsigned long align) -{ - bool cleanup_done = false; - -repeat: - /* Try finding exact size and hope the start is aligned */ - if (!mas_empty_area(mas, 0, section_size - 1, prepend + size)) { - if (IS_ALIGNED(mas->index + prepend, align)) - return true; - - /* Try finding larger area to align later */ - mas_reset(mas); - if (!mas_empty_area(mas, 0, section_size - 1, - size + prepend + align - 1)) - return true; - } - - /* No free area, try cleanup stale data and repeat the search once */ - if (!cleanup_done) { - clean_unused_module_areas_locked(); - cleanup_done = true; - mas_reset(mas); - goto repeat; - } - - return false; -} - -static int vm_module_tags_populate(void) -{ - unsigned long phys_end = ALIGN_DOWN(module_tags.start_addr, PAGE_SIZE) + - (vm_module_tags->nr_pages << PAGE_SHIFT); - unsigned long new_end = module_tags.start_addr + module_tags.size; - - if (phys_end < new_end) { - struct page **next_page = vm_module_tags->pages + vm_module_tags->nr_pages; - unsigned long old_shadow_end = ALIGN(phys_end, MODULE_ALIGN); - unsigned long new_shadow_end = ALIGN(new_end, MODULE_ALIGN); - unsigned long more_pages; - unsigned long nr = 0; - - more_pages = ALIGN(new_end - phys_end, PAGE_SIZE) >> PAGE_SHIFT; - while (nr < more_pages) { - unsigned long allocated; - - allocated = alloc_pages_bulk_node(GFP_KERNEL | __GFP_NOWARN, - NUMA_NO_NODE, more_pages - nr, next_page + nr); - - if (!allocated) - break; - nr += allocated; - } - - if (nr < more_pages || - vmap_pages_range(phys_end, phys_end + (nr << PAGE_SHIFT), PAGE_KERNEL, - next_page, PAGE_SHIFT) < 0) { - release_pages_arg arg = { .pages = next_page }; - - /* Clean up and error out */ - release_pages(arg, nr); - return -ENOMEM; - } - - vm_module_tags->nr_pages += nr; - - /* - * Kasan allocates 1 byte of shadow for every 8 bytes of data. - * When kasan_alloc_module_shadow allocates shadow memory, - * its unit of allocation is a page. - * Therefore, here we need to align to MODULE_ALIGN. - */ - if (old_shadow_end < new_shadow_end) - kasan_alloc_module_shadow((void *)old_shadow_end, - new_shadow_end - old_shadow_end, - GFP_KERNEL); - } - - /* - * Mark the pages as accessible, now that they are mapped. - * With hardware tag-based KASAN, marking is skipped for - * non-VM_ALLOC mappings, see __kasan_unpoison_vmalloc(). - */ - kasan_unpoison_vmalloc((void *)module_tags.start_addr, - new_end - module_tags.start_addr, - KASAN_VMALLOC_PROT_NORMAL); - - return 0; -} - -static void *reserve_module_tags(struct module *mod, unsigned long size, - unsigned int prepend, unsigned long align) -{ - unsigned long section_size = module_tags.end_addr - module_tags.start_addr; - MA_STATE(mas, &mod_area_mt, 0, section_size - 1); - unsigned long offset; - void *ret = NULL; - - /* If no tags return error */ - if (size < sizeof(struct alloc_tag)) - return ERR_PTR(-EINVAL); - - /* - * align is always power of 2, so we can use IS_ALIGNED and ALIGN. - * align 0 or 1 means no alignment, to simplify set to 1. - */ - if (!align) - align = 1; - - if (!ensure_alignment(align, &prepend)) { - shutdown_mem_profiling(true); - pr_err("%s: alignment %lu is incompatible with allocation tag indexing. Memory allocation profiling is disabled!\n", - mod->name, align); - return ERR_PTR(-EINVAL); - } - - mas_lock(&mas); - if (!find_aligned_area(&mas, section_size, size, prepend, align)) { - ret = ERR_PTR(-ENOMEM); - goto unlock; - } - - /* Mark found area as reserved */ - offset = mas.index; - offset += prepend; - offset = ALIGN(offset, align); - if (offset != mas.index) { - unsigned long pad_start = mas.index; - - mas.last = offset - 1; - mas_store(&mas, &prepend_mod); - if (mas_is_err(&mas)) { - ret = ERR_PTR(xa_err(mas.node)); - goto unlock; - } - mas.index = offset; - mas.last = offset + size - 1; - mas_store(&mas, mod); - if (mas_is_err(&mas)) { - mas.index = pad_start; - mas_erase(&mas); - ret = ERR_PTR(xa_err(mas.node)); - } - } else { - mas.last = offset + size - 1; - mas_store(&mas, mod); - if (mas_is_err(&mas)) - ret = ERR_PTR(xa_err(mas.node)); - } -unlock: - mas_unlock(&mas); - - if (IS_ERR(ret)) - return ret; - - if (module_tags.size < offset + size) { - int grow_res; - - module_tags.size = offset + size; - if (mem_alloc_profiling_enabled() && !tags_addressable()) { - shutdown_mem_profiling(true); - pr_warn("With module %s there are too many tags to fit in %d page flag bits. Memory allocation profiling is disabled!\n", - mod->name, NR_UNUSED_PAGEFLAG_BITS); - } - - grow_res = vm_module_tags_populate(); - if (grow_res) { - shutdown_mem_profiling(true); - pr_err("Failed to allocate memory for allocation tags in the module %s. Memory allocation profiling is disabled!\n", - mod->name); - return ERR_PTR(grow_res); - } - } - - return (struct alloc_tag *)(module_tags.start_addr + offset); -} - -static void release_module_tags(struct module *mod, bool used) -{ - MA_STATE(mas, &mod_area_mt, module_tags.size, module_tags.size); - struct alloc_tag *start_tag; - struct alloc_tag *end_tag; - struct module *val; - - mas_lock(&mas); - mas_for_each_rev(&mas, val, 0) - if (val == mod) - break; - - if (!val) /* module not found */ - goto out; - - if (!used) - goto release_area; - - start_tag = (struct alloc_tag *)(module_tags.start_addr + mas.index); - end_tag = (struct alloc_tag *)(module_tags.start_addr + mas.last); - if (!clean_unused_counters(start_tag, end_tag)) { - struct alloc_tag *tag; - - for (tag = start_tag; tag <= end_tag; tag++) { - struct alloc_tag_counters counter; - - if (!tag->counters) - continue; - - counter = alloc_tag_read(tag); - pr_info("%s:%u module %s func:%s has %llu allocated at module unload\n", - tag->ct.filename, tag->ct.lineno, tag->ct.modname, - tag->ct.function, counter.bytes); - } - } else { - used = false; - } -release_area: - mas_store(&mas, used ? &unloaded_mod : NULL); - val = mas_prev_range(&mas, 0); - if (val == &prepend_mod) - mas_store(&mas, NULL); -out: - mas_unlock(&mas); -} - -static int load_module(struct module *mod, struct codetag *start, struct codetag *stop) -{ - /* Allocate module alloc_tag percpu counters */ - struct alloc_tag *start_tag; - struct alloc_tag *stop_tag; - struct alloc_tag *tag; - - /* percpu counters for core allocations are already statically allocated */ - if (!mod) - return 0; - - start_tag = ct_to_alloc_tag(start); - stop_tag = ct_to_alloc_tag(stop); - for (tag = start_tag; tag < stop_tag; tag++) { - WARN_ON(tag->counters); - tag->counters = alloc_percpu(struct alloc_tag_counters); - if (!tag->counters) { - while (--tag >= start_tag) { - free_percpu(tag->counters); - tag->counters = NULL; - } - pr_err("Failed to allocate memory for allocation tag percpu counters in the module %s\n", - mod->name); - return -ENOMEM; - } - - /* - * Avoid a kmemleak false positive. The pointer to the counters is stored - * in the alloc_tag section of the module and cannot be directly accessed. - */ - kmemleak_ignore_percpu(tag->counters); - } - return 0; -} - -static void replace_module(struct module *mod, struct module *new_mod) -{ - MA_STATE(mas, &mod_area_mt, 0, module_tags.size); - struct module *val; - - mas_lock(&mas); - mas_for_each(&mas, val, module_tags.size) { - if (val != mod) - continue; - - mas_store_gfp(&mas, new_mod, GFP_KERNEL); - break; - } - mas_unlock(&mas); -} - -static int __init alloc_mod_tags_mem(void) -{ - /* Map space to copy allocation tags */ - vm_module_tags = execmem_vmap(MODULE_ALLOC_TAG_VMAP_SIZE); - if (!vm_module_tags) { - pr_err("Failed to map %lu bytes for module allocation tags\n", - MODULE_ALLOC_TAG_VMAP_SIZE); - module_tags.start_addr = 0; - return -ENOMEM; - } - - vm_module_tags->pages = kmalloc_objs(struct page *, - get_vm_area_size(vm_module_tags) >> PAGE_SHIFT, - GFP_KERNEL | __GFP_ZERO); - if (!vm_module_tags->pages) { - free_vm_area(vm_module_tags); - return -ENOMEM; - } - - module_tags.start_addr = (unsigned long)vm_module_tags->addr; - module_tags.end_addr = module_tags.start_addr + MODULE_ALLOC_TAG_VMAP_SIZE; - /* Ensure the base is alloc_tag aligned when required for indexing */ - module_tags.start_addr = alloc_tag_align(module_tags.start_addr); - - return 0; -} - -static void __init free_mod_tags_mem(void) -{ - release_pages_arg arg = { .pages = vm_module_tags->pages }; - - module_tags.start_addr = 0; - release_pages(arg, vm_module_tags->nr_pages); - kfree(vm_module_tags->pages); - free_vm_area(vm_module_tags); -} - -#else /* CONFIG_MODULES */ - -static inline int alloc_mod_tags_mem(void) { return 0; } -static inline void free_mod_tags_mem(void) {} - -#endif /* CONFIG_MODULES */ - -/* See: Documentation/mm/allocation-profiling.rst */ -static int __init setup_early_mem_profiling(char *str) -{ - bool compressed = false; - bool enable; - - if (!str || !str[0]) - return -EINVAL; - - if (!strncmp(str, "never", 5)) { - enable = false; - mem_profiling_support = false; - pr_info("Memory allocation profiling is disabled!\n"); - } else { - char *token = strsep(&str, ","); - - if (kstrtobool(token, &enable)) - return -EINVAL; - - if (str) { - - if (strcmp(str, "compressed")) - return -EINVAL; - - compressed = true; - } - mem_profiling_support = true; - pr_info("Memory allocation profiling is enabled %s compression and is turned %s!\n", - compressed ? "with" : "without", str_on_off(enable)); - } - - if (enable != mem_alloc_profiling_enabled()) { - if (enable) - static_branch_enable(&mem_alloc_profiling_key); - else - static_branch_disable(&mem_alloc_profiling_key); - } - if (compressed != static_key_enabled(&mem_profiling_compressed)) { - if (compressed) - static_branch_enable(&mem_profiling_compressed); - else - static_branch_disable(&mem_profiling_compressed); - } - - return 0; -} -early_param("sysctl.vm.mem_profiling", setup_early_mem_profiling); - -static __init bool need_page_alloc_tagging(void) -{ - if (static_key_enabled(&mem_profiling_compressed)) - return false; - - return mem_profiling_support; -} - -#ifdef CONFIG_MEM_ALLOC_PROFILING_DEBUG -/* - * Track page allocations before page_ext is initialized. - * Some pages are allocated before page_ext becomes available, leaving - * their codetag uninitialized. Track these early PFNs so we can clear - * their codetag refs later to avoid warnings when they are freed. - * - * Each page is cast to a pfn_pool: the first few bytes hold metadata - * (next pointer and slot count), the remainder stores PFNs. - */ -struct pfn_pool { - struct pfn_pool *next; - atomic_t count; - unsigned long pfns[]; -}; - -#define PFN_POOL_SIZE ((PAGE_SIZE - offsetof(struct pfn_pool, pfns)) / \ - sizeof(unsigned long)) - -/* - * Skip early PFN recording for a page allocation. Reuses the - * %__GFP_NO_OBJ_EXT bit. Used by __alloc_tag_add_early_pfn() to avoid - * recursion when allocating pages for the early PFN tracking list - * itself. - * - * Codetags of the pages allocated with __GFP_NO_CODETAG should be - * cleared (via clear_page_tag_ref()) before freeing the pages to prevent - * alloc_tag_sub_check() from triggering a warning. - */ -#define __GFP_NO_CODETAG __GFP_NO_OBJ_EXT - -static struct pfn_pool *current_pfn_pool __initdata; - -static void __init __alloc_tag_add_early_pfn(unsigned long pfn) -{ - struct pfn_pool *pool; - int idx; - - do { - pool = READ_ONCE(current_pfn_pool); - if (!pool || atomic_read(&pool->count) >= PFN_POOL_SIZE) { - struct page *new_page = alloc_page(__GFP_HIGH | __GFP_NO_CODETAG); - struct pfn_pool *new; - - if (!new_page) { - pr_warn_once("early PFN tracking page allocation failed\n"); - return; - } - new = page_address(new_page); - new->next = pool; - atomic_set(&new->count, 0); - if (cmpxchg(¤t_pfn_pool, pool, new) != pool) { - clear_page_tag_ref(new_page); - __free_page(new_page); - continue; - } - pool = new; - } - idx = atomic_read(&pool->count); - if (idx >= PFN_POOL_SIZE) - continue; - if (atomic_cmpxchg(&pool->count, idx, idx + 1) == idx) - break; - } while (1); - - pool->pfns[idx] = pfn; -} - -typedef void alloc_tag_add_func(unsigned long pfn); -static alloc_tag_add_func __rcu *alloc_tag_add_early_pfn_ptr __refdata = - RCU_INITIALIZER(__alloc_tag_add_early_pfn); - -void alloc_tag_add_early_pfn(unsigned long pfn, gfp_t gfp_flags) -{ - alloc_tag_add_func *alloc_tag_add; - - if (static_key_enabled(&mem_profiling_compressed)) - return; - - /* Skip allocations for the tracking list itself to avoid recursion. */ - if (gfp_flags & __GFP_NO_CODETAG) - return; - - rcu_read_lock(); - alloc_tag_add = rcu_dereference(alloc_tag_add_early_pfn_ptr); - if (alloc_tag_add) - alloc_tag_add(pfn); - rcu_read_unlock(); -} - -static void __init clear_early_alloc_pfn_tag_refs(void) -{ - struct pfn_pool *pool, *next; - struct page *page; - int i; - - if (static_key_enabled(&mem_profiling_compressed)) - return; - - rcu_assign_pointer(alloc_tag_add_early_pfn_ptr, NULL); - /* Make sure we are not racing with __alloc_tag_add_early_pfn() */ - synchronize_rcu(); - - for (pool = current_pfn_pool; pool; pool = next) { - int nr_pfns = atomic_read(&pool->count); - - for (i = 0; i < nr_pfns; i++) { - unsigned long pfn = pool->pfns[i]; - - if (pfn_valid(pfn)) { - union pgtag_ref_handle handle; - union codetag_ref ref; - - if (get_page_tag_ref(pfn_to_page(pfn), &ref, &handle)) { - /* - * An early-allocated page could be freed and reallocated - * after its page_ext is initialized but before we clear it. - * In that case, it already has a valid tag set. - * We should not overwrite that valid tag - * with CODETAG_EMPTY. - * - * Note: there is still a small race window between checking - * ref.ct and calling set_codetag_empty(). We accept this - * race as it's unlikely and the extra complexity of atomic - * cmpxchg is not worth it for this debug-only code path. - */ - if (ref.ct) { - put_page_tag_ref(handle); - continue; - } - - set_codetag_empty(&ref); - update_page_tag_ref(handle, &ref); - put_page_tag_ref(handle); - } - } - } - - next = pool->next; - page = virt_to_page(pool); - clear_page_tag_ref(page); - __free_page(page); - } -} -#else /* !CONFIG_MEM_ALLOC_PROFILING_DEBUG */ -static inline void __init clear_early_alloc_pfn_tag_refs(void) {} -#endif /* CONFIG_MEM_ALLOC_PROFILING_DEBUG */ - -static __init void init_page_alloc_tagging(void) -{ - clear_early_alloc_pfn_tag_refs(); -} - -struct page_ext_operations page_alloc_tagging_ops = { - .size = sizeof(union codetag_ref), - .need = need_page_alloc_tagging, - .init = init_page_alloc_tagging, -}; -EXPORT_SYMBOL(page_alloc_tagging_ops); - -#ifdef CONFIG_SYSCTL -/* - * Not using proc_do_static_key() directly to prevent enabling profiling - * after it was shut down. - */ -static int proc_mem_profiling_handler(const struct ctl_table *table, int write, - void *buffer, size_t *lenp, loff_t *ppos) -{ - if (write) { - /* - * Call from do_sysctl_args() which is a no-op since the same - * value was already set by setup_early_mem_profiling. - * Return success to avoid warnings from do_sysctl_args(). - */ - if (!current->mm) - return 0; - -#ifdef CONFIG_MEM_ALLOC_PROFILING_DEBUG - /* User can't toggle profiling while debugging */ - return -EACCES; -#endif - if (!mem_profiling_support) - return -EINVAL; - } - - return proc_do_static_key(table, write, buffer, lenp, ppos); -} - - -static const struct ctl_table memory_allocation_profiling_sysctls[] = { - { - .procname = "mem_profiling", - .data = &mem_alloc_profiling_key, - .mode = 0644, - .proc_handler = proc_mem_profiling_handler, - }, -}; - -static void __init sysctl_init(void) -{ - register_sysctl_init("vm", memory_allocation_profiling_sysctls); -} -#else /* CONFIG_SYSCTL */ -static inline void sysctl_init(void) {} -#endif /* CONFIG_SYSCTL */ - -static int __init alloc_tag_init(void) -{ - const struct codetag_type_desc desc = { - .section = ALLOC_TAG_SECTION_NAME, - .tag_size = sizeof(struct alloc_tag), -#ifdef CONFIG_MODULES - .needs_section_mem = needs_section_mem, - .alloc_section_mem = reserve_module_tags, - .free_section_mem = release_module_tags, - .module_load = load_module, - .module_replaced = replace_module, -#endif - }; - int res; - - sysctl_init(); - - if (!mem_profiling_support) { - pr_info("Memory allocation profiling is not supported!\n"); - return 0; - } - - if (!proc_create_seq_private(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_seq_op, - sizeof(struct allocinfo_private), NULL)) { - pr_err("Failed to create %s file\n", ALLOCINFO_FILE_NAME); - shutdown_mem_profiling(false); - return -ENOMEM; - } - - res = alloc_mod_tags_mem(); - if (res) { - pr_err("Failed to reserve address space for module tags, errno = %d\n", res); - shutdown_mem_profiling(true); - return res; - } - - alloc_tag_cttype = codetag_register_type(&desc); - if (IS_ERR(alloc_tag_cttype)) { - pr_err("Allocation tags registration failed, errno = %pe\n", alloc_tag_cttype); - free_mod_tags_mem(); - shutdown_mem_profiling(true); - return PTR_ERR(alloc_tag_cttype); - } - - return 0; -} -module_init(alloc_tag_init); From e14a3454806468b086fe2e4ca2e1bff95b528531 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Tue, 1 Sep 2026 11:01:09 -0700 Subject: [PATCH 0934/2366] mm/mlock: use the IRQ-safe accessor for NR_MLOCK in __munlock_folio() NR_MLOCK is updated from interrupt context. __free_pages_prepare() clears a stray PG_mlocked and adjusts NR_MLOCK, and a folio can reach it with the flag still set from a bio completion handler: __free_pages_ok+0x6af/0x7a0 __bio_release_pages+0xde/0x260 __iomap_dio_bio_end_io+0x16e/0x1a0 blk_update_request+0x14b/0x3d0 blk_mq_end_request+0x18/0x30 blk_done_softirq+0x49/0x60 The folio gets there like this. A MAP_SHARED file mapping is mlocked, so its page cache folios carry PG_mlocked, and an O_DIRECT write sourced from that mapping GUP-pins those same folios. munlock() then runs mlock_vma_pages_range(), which clears VM_LOCKED before walking the page tables to munlock each folio. A concurrent hole punch reaches the folio through the rmap (i_mmap_rwsem, not mmap_lock) and can land inside that window: __folio_remove_rmap() -> munlock_vma_folio() sees VM_LOCKED already clear, so it neither queues the folio on the mlock batch nor takes a reference, and the pte it clears makes the pending mlock_pte_range() walk skip the folio at its !pte_present() check. filemap_remove_folio() then drops the page cache reference, leaving the bio's pin as the last one, released from the completion handler above. So __zone_stat_mod_folio() here needs interrupts disabled, not merely preemption, and __munlock_folio() has a path where they are not: when the folio has already been taken off the LRU by somebody else the function jumps straight to the counter update without taking the lruvec lock. The read-modify-write of the per-CPU NR_MLOCK diff can then be interrupted by the softirq above, and one of the two decrements is lost, leaving Mlocked in /proc/meminfo permanently overstated. Use zone_stat_mod_folio(). mod_zone_state()'s this_cpu_try_cmpxchg() is atomic against a same-CPU interrupt and retries, and on the path where the lruvec lock is held its cost is negligible next to the lock itself. The UNEVICTABLE_PG* events are deliberately left on the __ accessors: they occupy different vm_event_states slots from the UNEVICTABLE_PGCLEARED that __free_pages_prepare() bumps, and nothing updates those two from interrupt context. Link: https://lore.kernel.org/20260901180109.3797944-1-shakeel.butt@linux.dev Fixes: 2fbb0c10d1e8 ("mm/munlock: mlock_page() munlock_page() batch by pagevec") Signed-off-by: Shakeel Butt Reported-by: syzbot+cd2073ee6d958a8d0fcd@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-mm/6a931c5a.08e933ee.dbf97.0093.GAE@google.com/ Acked-by: Hugh Dickins Cc: Jann Horn Cc: Liam R. Howlett Cc: Lorenzo Stoakes Cc: Matthew Wilcox (Oracle) Cc: Pedro Falcato Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- mm/mlock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/mlock.c b/mm/mlock.c index efa6716e4dfb..39215a3eab1f 100644 --- a/mm/mlock.c +++ b/mm/mlock.c @@ -141,7 +141,7 @@ static struct lruvec *__munlock_folio(struct folio *folio, struct lruvec *lruvec munlock: if (folio_test_clear_mlocked(folio)) { - __zone_stat_mod_folio(folio, NR_MLOCK, -nr_pages); + zone_stat_mod_folio(folio, NR_MLOCK, -nr_pages); if (isolated || !folio_test_unevictable(folio)) __count_vm_events(UNEVICTABLE_PGMUNLOCKED, nr_pages); else From e6662f2100f8d33b0f4d0047c219efd6bba186ea Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 2 Sep 2026 23:52:31 +0800 Subject: [PATCH 0935/2366] net/sched: defer qdisc freeing after failed creation An RTM_NEWQDISC request can make clsact bind a populated shared ingress block during ->init(), publishing an embedded mini_Qdisc to lockless readers. If the same request has an invalid TCA_RATE, estimator setup fails after ->init(); the unwind removes the pointer but synchronously frees its containing qdisc while tc_run() may still hold it. Retire failed qdiscs through the same RCU helper as normal destruction. Inline the synchronous free into the callback now that no direct callers remain. Fixes: 51ab2994c387 ("net: sched: allow ingress and clsact qdiscs to share filter blocks") Reported-by: Xiang Mei Link: https://lore.kernel.org/netdev/20260805102505.740806-1-david.lee@trailofbits.com/ Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20260902155231.2149915-2-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- include/net/sch_generic.h | 2 +- net/sched/sch_api.c | 2 +- net/sched/sch_generic.c | 24 ++++++++++++------------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index cbc248776511..f35bd06a6bad 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -793,7 +793,7 @@ void qdisc_offload_query_caps(struct net_device *dev, struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue, const struct Qdisc_ops *ops, struct netlink_ext_ack *extack); -void qdisc_free(struct Qdisc *qdisc); +void qdisc_free_rcu(struct Qdisc *qdisc); struct Qdisc *qdisc_create_dflt(struct netdev_queue *dev_queue, const struct Qdisc_ops *ops, u32 parentid, struct netlink_ext_ack *extack); diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index 90503e59e6e3..463ededcdcfe 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -1385,7 +1385,7 @@ static struct Qdisc *qdisc_create(struct net_device *dev, err_out3: qdisc_lock_uninit(sch, ops); netdev_put(dev, &sch->dev_tracker); - qdisc_free(sch); + qdisc_free_rcu(sch); err_out2: bpf_module_put(ops, ops->owner); err_out: diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index 4539dc2c6d38..6f6a6f0d5eb0 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -1086,21 +1086,21 @@ void qdisc_reset(struct Qdisc *qdisc) } EXPORT_SYMBOL(qdisc_reset); -void qdisc_free(struct Qdisc *qdisc) -{ - if (qdisc_is_percpu_stats(qdisc)) { - free_percpu(qdisc->cpu_bstats); - free_percpu(qdisc->cpu_qstats); - } - - kfree(qdisc); -} - static void qdisc_free_cb(struct rcu_head *head) { struct Qdisc *q = container_of(head, struct Qdisc, rcu); - qdisc_free(q); + if (qdisc_is_percpu_stats(q)) { + free_percpu(q->cpu_bstats); + free_percpu(q->cpu_qstats); + } + + kfree(q); +} + +void qdisc_free_rcu(struct Qdisc *qdisc) +{ + call_rcu(&qdisc->rcu, qdisc_free_cb); } static void __qdisc_destroy(struct Qdisc *qdisc) @@ -1127,7 +1127,7 @@ static void __qdisc_destroy(struct Qdisc *qdisc) trace_qdisc_destroy(qdisc); - call_rcu(&qdisc->rcu, qdisc_free_cb); + qdisc_free_rcu(qdisc); } void qdisc_destroy(struct Qdisc *qdisc) From 802eedcc0b25bb3e1b492f0600ab74325274d53b Mon Sep 17 00:00:00 2001 From: Shahar Shitrit Date: Wed, 2 Sep 2026 19:46:32 +0300 Subject: [PATCH 0936/2366] net/mlx5e: Fix missing FEC mode mapping for RS_544_514_INTERLEAVED_QUAD MLX5E_FEC_RS_544_514_INTERLEAVED_QUAD is missing from pplm_fec_2_ethtool_linkmodes[], leaving index 4 zero-initialized. As a result, when this FEC mode is active, find_first_bit() returns index 4, causing __set_bit() to set bit 0 (ETHTOOL_LINK_MODE_10baseT_Half_BIT) instead of ETHTOOL_LINK_MODE_FEC_RS_BIT. Consequently, ethtool reports: Advertised FEC modes: Not reported Add the missing mapping to ETHTOOL_LINK_MODE_FEC_RS_BIT. Fixes: 4e343c11efbb ("net/mlx5e: Support FEC settings for 200G per lane link modes") Signed-off-by: Shahar Shitrit Reviewed-by: Dragos Tatulea Reviewed-by: Yael Chemla Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902164634.3657606-2-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c index 112926d07634..f285ad88b6d5 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c @@ -1013,6 +1013,7 @@ static const u32 pplm_fec_2_ethtool_linkmodes[] = { [MLX5E_FEC_NOFEC] = ETHTOOL_LINK_MODE_FEC_NONE_BIT, [MLX5E_FEC_FIRECODE] = ETHTOOL_LINK_MODE_FEC_BASER_BIT, [MLX5E_FEC_RS_528_514] = ETHTOOL_LINK_MODE_FEC_RS_BIT, + [MLX5E_FEC_RS_544_514_INTERLEAVED_QUAD] = ETHTOOL_LINK_MODE_FEC_RS_BIT, [MLX5E_FEC_RS_544_514] = ETHTOOL_LINK_MODE_FEC_RS_BIT, [MLX5E_FEC_LLRS_272_257_1] = ETHTOOL_LINK_MODE_FEC_LLRS_BIT, }; From b9d755c5a37519fb1354034db1dfeb30e1ba6856 Mon Sep 17 00:00:00 2001 From: Shahar Shitrit Date: Wed, 2 Sep 2026 19:46:33 +0300 Subject: [PATCH 0937/2366] net/mlx5e: Fix setting RS FEC after remapping When a user sets a FEC mode via ethtool, the driver maps the ethtool FEC type to the lowest mlx5 bit of that type. For RS FEC, this is MLX5E_FEC_RS_528_514 (bit 2). The driver then checks whether this bit is supported by at least one link mode by inspecting the fec_override_cap fields via mlx5e_fec_in_caps(), and returns -EOPNOTSUPP if not. This check is incorrect. RS FEC has three supported hardware variants: RS_528_514 (bit 2), RS_544_514_INTERLEAVED_QUAD (bit 4), and RS_544_514 (bit 7). mlx5e_remap_fec_conf_mode() already remaps bit 2 to the appropriate RS variant per link mode when writing the admin fields, but the early capability check is done against the raw unmapped bit. As a result, a device that supports RS_544_514 or RS_544_514_INTERLEAVED_QUAD but not RS_528_514 will incorrectly reject the user's RS FEC request. Remove the early support check from mlx5e_set_fec_mode() and fold it into the existing write loop, checking caps against the remapped policy per link mode. Return -EOPNOTSUPP before the final register write if no link mode accepted the policy. Fixes: 2608a2f831c4 ("net/mlx5e: Fix return status when setting unsupported FEC mode") Signed-off-by: Shahar Shitrit Reviewed-by: Dragos Tatulea Reviewed-by: Yael Chemla Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902164634.3657606-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/port.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/port.c b/drivers/net/ethernet/mellanox/mlx5/core/en/port.c index 6049ccf475bc..a4c096a4fed2 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/port.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/port.c @@ -557,6 +557,7 @@ int mlx5e_set_fec_mode(struct mlx5_core_dev *dev, u16 fec_policy) u32 in[MLX5_ST_SZ_DW(pplm_reg)] = {}; int sz = MLX5_ST_SZ_BYTES(pplm_reg); u16 fec_policy_auto = 0; + bool fec_set = false; int err; int i; @@ -569,9 +570,6 @@ int mlx5e_set_fec_mode(struct mlx5_core_dev *dev, u16 fec_policy) if (fec_policy >= (1 << MLX5E_FEC_LLRS_272_257_1) && !fec_50g_per_lane) return -EOPNOTSUPP; - if (fec_policy && !mlx5e_fec_in_caps(dev, fec_policy)) - return -EOPNOTSUPP; - MLX5_SET(pplm_reg, in, local_port, 1); err = mlx5_core_access_reg(dev, in, sz, out, sz, MLX5_REG_PPLM, 0, 0); if (err) @@ -591,12 +589,17 @@ int mlx5e_set_fec_mode(struct mlx5_core_dev *dev, u16 fec_policy) mlx5e_get_fec_cap_field(out, &fec_caps, i); /* policy supported for link speed */ - if (fec_caps & conf_fec) + if (fec_caps & conf_fec) { mlx5e_fec_admin_field(out, &conf_fec, 1, i); - else - /* set FEC to auto*/ + fec_set = true; + } else { + /* set FEC to auto */ mlx5e_fec_admin_field(out, &fec_policy_auto, 1, i); + } } + if (fec_policy && !fec_set) + return -EOPNOTSUPP; + return mlx5_core_access_reg(dev, out, sz, out, sz, MLX5_REG_PPLM, 0, 1); } From c84ce45a7a3f3f024502c7f53308db9c76e4ae71 Mon Sep 17 00:00:00 2001 From: Shahar Shitrit Date: Wed, 2 Sep 2026 19:46:34 +0300 Subject: [PATCH 0938/2366] net/mlx5e: Fix reporting support for all RS FEC variants get_fec_supported_advertised() populates the FEC modes reported as supported to userspace. The MLX5E_ADVERTISE_SUPPORTED_FEC macro only checked MLX5E_FEC_RS_528_514, causing devices that support only the other RS variants (RS_544_514_INTERLEAVED_QUAD or RS_544_514) to not advertise RS as supported to ethtool at all. Introduce MLX5E_FEC_RS_MASK covering all three RS bit positions, update the macro to accept a bitmask directly rather than a single enum value, and pass MLX5E_FEC_RS_MASK for the RS entry. Fixes: b5ede32d3329 ("net/mlx5e: Add support for FEC modes based on 50G per lane links") Fixes: 4e343c11efbb ("net/mlx5e: Support FEC settings for 200G per lane link modes") Signed-off-by: Shahar Shitrit Reviewed-by: Dragos Tatulea Reviewed-by: Yael Chemla Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902164634.3657606-4-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/port.h | 4 ++++ drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/port.h b/drivers/net/ethernet/mellanox/mlx5/core/en/port.h index fa2283dd383b..53dbdf77bcce 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/port.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/port.h @@ -66,4 +66,8 @@ enum { MLX5E_FEC_LLRS_272_257_1 = 9, }; +#define MLX5E_FEC_RS_MASK (BIT(MLX5E_FEC_RS_528_514) | \ + BIT(MLX5E_FEC_RS_544_514_INTERLEAVED_QUAD) | \ + BIT(MLX5E_FEC_RS_544_514)) + #endif diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c index f285ad88b6d5..3ed59ced0407 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c @@ -1002,9 +1002,9 @@ static u32 pplm2ethtool_fec(u_long fec_mode, unsigned long size) return 0; } -#define MLX5E_ADVERTISE_SUPPORTED_FEC(mlx5_fec, ethtool_fec) \ +#define MLX5E_ADVERTISE_SUPPORTED_FEC(fec_mask, ethtool_fec) \ do { \ - if (mlx5e_fec_in_caps(dev, 1 << (mlx5_fec))) \ + if (mlx5e_fec_in_caps(dev, fec_mask)) \ __set_bit(ethtool_fec, \ link_ksettings->link_modes.supported);\ } while (0) @@ -1030,13 +1030,13 @@ static int get_fec_supported_advertised(struct mlx5_core_dev *dev, if (err) return (err == -EOPNOTSUPP) ? 0 : err; - MLX5E_ADVERTISE_SUPPORTED_FEC(MLX5E_FEC_NOFEC, + MLX5E_ADVERTISE_SUPPORTED_FEC(BIT(MLX5E_FEC_NOFEC), ETHTOOL_LINK_MODE_FEC_NONE_BIT); - MLX5E_ADVERTISE_SUPPORTED_FEC(MLX5E_FEC_FIRECODE, + MLX5E_ADVERTISE_SUPPORTED_FEC(BIT(MLX5E_FEC_FIRECODE), ETHTOOL_LINK_MODE_FEC_BASER_BIT); - MLX5E_ADVERTISE_SUPPORTED_FEC(MLX5E_FEC_RS_528_514, + MLX5E_ADVERTISE_SUPPORTED_FEC(MLX5E_FEC_RS_MASK, ETHTOOL_LINK_MODE_FEC_RS_BIT); - MLX5E_ADVERTISE_SUPPORTED_FEC(MLX5E_FEC_LLRS_272_257_1, + MLX5E_ADVERTISE_SUPPORTED_FEC(BIT(MLX5E_FEC_LLRS_272_257_1), ETHTOOL_LINK_MODE_FEC_LLRS_BIT); active_fec_long = active_fec; From b3c79dee5038c5e8460c59d7d01cb1450bdf5ecb Mon Sep 17 00:00:00 2001 From: Akiva Goldberger Date: Wed, 2 Sep 2026 22:27:40 +0300 Subject: [PATCH 0939/2366] net/mlx5: LAG, use local tracker to update active ports The CREATE_LAG command is handled asynchronously by queuing a work, which stores a local copy of ldev->tracker. When the work is processed, it is possible that the values of the local copy and ldev->tracker have diverged. A single CREATE_LAG command programs two related fields into the firmware: the v2p (virtual-to-physical) map, which selects the physical egress port for each hash bucket, and the active_port bitmask, which tells the firmware which physical ports are currently up so it can redirect QP/TIS away from inactive ports. For the firmware to steer traffic correctly, both must be derived from the same view of the ports' link state. The v2p map is computed by mlx5_infer_tx_affinity_mapping() from the local tracker snapshot, but lag_active_port_bits() called mlx5_infer_tx_enabled() on the live ldev->tracker instead. If ldev->tracker changed between the snapshot and command execution, the two fields reflect different port states: the v2p map may steer a bucket to a port that the active_port mask marks as inactive (or vice versa). The firmware then receives a self-contradictory configuration and can redirect or drop traffic on a port the mapping still points at, until a later event happens to reconcile the state. Update lag_active_port_bits so that it receives the local version of the tracker from when the work was queued, effectively closing the window for injecting an inconsistency. Fixes: c5c13b456cb8 ("net/mlx5: Lag, set active ports if support bypass port select flow table") Signed-off-by: Akiva Goldberger Reviewed-by: Shay Drori Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902192740.3665435-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/lag/lag.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index 2285c889c215..c655f6e32e9b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -63,14 +63,15 @@ static int get_port_sel_mode(enum mlx5_lag_mode mode, unsigned long flags) return MLX5_LAG_PORT_SELECT_MODE_QUEUE_AFFINITY; } -static u8 lag_active_port_bits(struct mlx5_lag *ldev) +static u8 lag_active_port_bits(struct mlx5_lag *ldev, + struct lag_tracker *tracker) { u8 enabled_ports[MLX5_MAX_PORTS] = {}; u8 active_port = 0; int num_enabled; int idx; - mlx5_infer_tx_enabled(&ldev->tracker, ldev, enabled_ports, + mlx5_infer_tx_enabled(tracker, ldev, enabled_ports, &num_enabled); for (idx = 0; idx < num_enabled; idx++) active_port |= BIT_MASK(enabled_ports[idx]); @@ -79,7 +80,8 @@ static u8 lag_active_port_bits(struct mlx5_lag *ldev) } static int mlx5_cmd_create_lag(struct mlx5_core_dev *dev, struct mlx5_lag *ldev, - int mode, unsigned long flags) + struct lag_tracker *tracker, int mode, + unsigned long flags) { bool fdb_sel_mode = test_bit(MLX5_LAG_MODE_FLAG_FDB_SEL_MODE_NATIVE, &flags); @@ -108,7 +110,7 @@ static int mlx5_cmd_create_lag(struct mlx5_core_dev *dev, struct mlx5_lag *ldev, break; MLX5_SET(lagc, lag_ctx, active_port, - lag_active_port_bits(mlx5_lag_dev(dev))); + lag_active_port_bits(ldev, tracker)); break; default: break; @@ -787,7 +789,8 @@ static int mlx5_cmd_modify_active_port(struct mlx5_core_dev *dev, u8 ports) return mlx5_cmd_exec_in(dev, modify_lag, in); } -static int _mlx5_modify_lag(struct mlx5_lag *ldev, u8 *ports) +static int _mlx5_modify_lag(struct mlx5_lag *ldev, + struct lag_tracker *tracker, u8 *ports) { int idx = mlx5_lag_get_dev_index_by_seq(ldev, MLX5_LAG_P1); struct mlx5_core_dev *dev0; @@ -804,7 +807,7 @@ static int _mlx5_modify_lag(struct mlx5_lag *ldev, u8 *ports) !MLX5_CAP_PORT_SELECTION(dev0, port_select_flow_table_bypass)) return ret; - active_ports = lag_active_port_bits(ldev); + active_ports = lag_active_port_bits(ldev, tracker); return mlx5_cmd_modify_active_port(dev0, active_ports); } @@ -868,7 +871,7 @@ void mlx5_modify_lag(struct mlx5_lag *ldev, idx = i * ldev->buckets + j; if (ports[idx] == ldev->v2p_map[idx]) continue; - err = _mlx5_modify_lag(ldev, ports); + err = _mlx5_modify_lag(ldev, tracker, ports); if (err) { mlx5_core_err(dev0, "Failed to modify LAG (%d)\n", @@ -976,7 +979,7 @@ static int mlx5_create_lag(struct mlx5_lag *ldev, mlx5_core_info(dev0, "shared_fdb:%d mode:%s\n", shared_fdb, mlx5_get_str_port_sel_mode(mode, flags)); - err = mlx5_cmd_create_lag(dev0, ldev, mode, flags); + err = mlx5_cmd_create_lag(dev0, ldev, tracker, mode, flags); if (err) { mlx5_core_err(dev0, "Failed to create LAG (%d)\n", From e7ee89740800a1cf253713e9249c3ee9203ebe91 Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Wed, 2 Sep 2026 22:32:24 +0300 Subject: [PATCH 0940/2366] net/mlx5e: Fix ETS zero BW reporting when one TC holds 100% When ETS TCs with zero bandwidth are configured, the driver programs the firmware using an alternate representation. On get, it needs to recognize that representation so those TCs can be translated back and reported as 0% bandwidth. The existing detection relied on the programmed bandwidth because it was enough to identify this representation. However, when a single ETS TC owns 100% of the bandwidth, its firmware representation becomes the same as a strict-priority TC, causing zero-bandwidth ETS TCs to be reported with non-zero bandwidth values. Use the cached TSA instead to distinguish the ETS and strict-priority cases. Fixes: be0f161ef141 ("net/mlx5e: DCBNL, Implement tc with ets type and zero bandwidth") Signed-off-by: Carolina Jubran Reviewed-by: Alex Lazar Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902193224.3668743-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c b/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c index 00e706e1ede1..741f75b5bfec 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_dcbnl.c @@ -148,7 +148,7 @@ static int mlx5e_dcbnl_ieee_getets(struct net_device *netdev, if (err) return err; - if (ets->tc_tx_bw[i] < MLX5E_MAX_BW_ALLOC && + if (priv->dcbx.tc_tsa[i] == IEEE_8021QAZ_TSA_ETS && tc_group[i] == (MLX5E_LOWEST_PRIO_GROUP + 1)) is_zero_bw_ets_tc = true; From af3aef0245abbab5e9f6302e7a7d6407187afb71 Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Wed, 2 Sep 2026 22:33:41 +0300 Subject: [PATCH 0941/2366] net/mlx5e: Fix use-after-free race in sample_restore_put() Concurrent teardown of TC sample rules sharing the same restore context may re-read restore->count after dropping restore_lock. At that point another thread may already have completed cleanup and freed the restore object. Use the result of the refcount decrement while holding restore_lock to determine whether cleanup is needed. Fixes: 36a3196256bf ("net/mlx5e: TC, Add sampler restore handle API") Signed-off-by: Carolina Jubran Reviewed-by: Shahar Shitrit Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902193341.3668809-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/tc/sample.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc/sample.c b/drivers/net/ethernet/mellanox/mlx5/core/en/tc/sample.c index 89490f687a9c..93c62d3f3e5b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/tc/sample.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc/sample.c @@ -311,12 +311,15 @@ sample_restore_get(struct mlx5e_tc_psample *tc_psample, u32 obj_id, static void sample_restore_put(struct mlx5e_tc_psample *tc_psample, struct mlx5e_sample_restore *restore) { + bool last; + mutex_lock(&tc_psample->restore_lock); - if (--restore->count == 0) + last = --restore->count == 0; + if (last) hash_del(&restore->hlist); mutex_unlock(&tc_psample->restore_lock); - if (!restore->count) { + if (last) { mlx5_del_flow_rules(restore->rule); mlx5_modify_header_dealloc(tc_psample->esw->dev, restore->modify_hdr); kfree(restore); From 7ee07f601f8f507c9faf25c68a49396ab8950596 Mon Sep 17 00:00:00 2001 From: Yael Chemla Date: Wed, 2 Sep 2026 22:35:14 +0300 Subject: [PATCH 0942/2366] net/mlx5: E-Switch: fix use-after-free in mlx5_eswitch_termtbl_put In mlx5_eswitch_termtbl_put(), the zero-ref cleanup check reads tt->ref_count after termtbl_mutex has been released. Two concurrent callers on the same mlx5_termtbl_handle race: one decrements ref_count to zero, removes the hash entry, and calls kfree(tt) while the other has already dropped the mutex and is about to evaluate if (!tt->ref_count), producing a use-after-free. Fix this by capturing the result of the decrement into a stack-local last variable before dropping the mutex. The cleanup decision is now made entirely under termtbl_mutex, and tt is not touched after kfree. Fixes: 10caabdaad5a ("net/mlx5e: Use termination table for VLAN push actions") Signed-off-by: Yael Chemla Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902193514.3668880-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../ethernet/mellanox/mlx5/core/eswitch_offloads_termtbl.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads_termtbl.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads_termtbl.c index 19f65d4c4def..d43f07360159 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads_termtbl.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads_termtbl.c @@ -163,12 +163,15 @@ void mlx5_eswitch_termtbl_put(struct mlx5_eswitch *esw, struct mlx5_termtbl_handle *tt) { + bool last; + mutex_lock(&esw->offloads.termtbl_mutex); - if (--tt->ref_count == 0) + last = (--tt->ref_count == 0); + if (last) hash_del(&tt->termtbl_hlist); mutex_unlock(&esw->offloads.termtbl_mutex); - if (!tt->ref_count) { + if (last) { mlx5_del_flow_rules(tt->rule); mlx5_destroy_flow_table(tt->termtbl); kfree(tt); From c0c6f4ba8a37688f7b4d4044898d88f0450d44c2 Mon Sep 17 00:00:00 2001 From: Lama Kayal Date: Wed, 2 Sep 2026 22:38:54 +0300 Subject: [PATCH 0943/2366] net/mlx5: E-Switch, prevent mc_list repopulation during vport disable In mlx5_esw_vport_disable(), move esw_apply_vport_rx_mode() ahead of esw_vport_change_handle_locked() so vport->allmulti_rule is NULL before the change handler observes it. During FW-fatal recovery the disable runs while dev->state == INTERNAL_ERROR. The promisc query inside esw_update_vport_rx_mode() fails and returns early, leaving vport->allmulti_rule intact, so esw_update_vport_mc_promisc() runs and adds MLX5_ACTION_ADD entries to vport->mc_list whose flow rules are then installed in the FDB by esw_add_mc_addr(). esw_destroy_legacy_table() tears down the FDB with those refs still held, corrupting the sub-tree and leaving dangling flow_rule pointers in vport->mc_list. Two-stage failure on `echo 1 > /sys/bus/pci/devices//reset`: refcount_t: underflow; use-after-free. tree_put_node+0xef/0x110 [mlx5_core] clean_tree+0x44/0xd0 [mlx5_core] (x5) mlx5_fs_core_cleanup+0x57/0x1c0 [mlx5_core] mlx5_unload+0x65/0xd0 [mlx5_core] ... mlx5_health_try_recover BUG: unable to handle page fault for address: 0000000003000055 down_write+0x1c/0x60 mlx5_del_flow_rules+0x33/0x1f0 [mlx5_core] esw_del_mc_addr+0x7b/0x170 [mlx5_core] esw_apply_vport_addr_list+0x56/0xf0 [mlx5_core] esw_vport_change_handle_locked+0x28b/0x310 [mlx5_core] mlx5_esw_vport_enable+0x270/0x4a0 [mlx5_core] ... mlx5_load ... mlx5_health_try_recover esw_apply_vport_rx_mode(false, false) clears vport->allmulti_rule via its local state machine even when the FW del fails. With the rule NULL the !IS_ERR_OR_NULL(allmulti_rule) gate in the change handler closes, no rules are installed during disable, and the reload starts with a clean mc_list. Fixes: 922f56e9a795 ("net/mlx5: Fix steering rules cleanup") Signed-off-by: Lama Kayal Reviewed-by: Cosmin Ratiu Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902193854.3669035-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/eswitch.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index b6e2c153b4f7..4c7fa4a52b0e 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -1040,13 +1040,19 @@ void mlx5_esw_vport_disable(struct mlx5_eswitch *esw, struct mlx5_vport *vport) (vport->info.ipsec_crypto_enabled || vport->info.ipsec_packet_enabled)) esw->enabled_ipsec_vf_count--; + /* Clear rx-mode before esw_vport_change_handle_locked(): on + * MLX5_VPORT_PROMISC_CHANGE it calls esw_update_vport_mc_promisc() + * when vport->allmulti_rule is set, repopulating mc_list with FDB + * rules that dangle once the FDB is destroyed. NULL allmulti_rule + * here skips that path. + */ + esw_apply_vport_rx_mode(esw, vport, false, false); /* We don't assume VFs will cleanup after themselves. * Calling vport change handler while vport is disabled will cleanup * the vport resources. */ esw_vport_change_handle_locked(vport); vport->enabled_events = 0; - esw_apply_vport_rx_mode(esw, vport, false, false); esw_vport_cleanup(esw, vport); esw->enabled_vports--; From df99553f840e4c529c1ba4c29bd39396466ca11a Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Wed, 2 Sep 2026 22:37:31 +0300 Subject: [PATCH 0944/2366] net/mlx5e: Keep HW timestamp stats monotonic across reconfiguration `mlx5e_stats_ts_get()` currently selects either DMA or port timestamp counters based on `tx_ptp_opened`. This flag is intentionally kept set once the PTP TX queues have been opened so their statistics remain available after queue teardown. As a result, DMA timestamps are no longer reported after switching from port timestamping back to DMA timestamping. The function also reads statistics only from the currently active channels and TCs. Reducing the number of channels or TCs can therefore drop previously accumulated timestamp counters from the reported value. Read the persistent channel statistics instead and always include DMA timestamp counters. Once the PTP TX queues have been opened, also include the port timestamp counters. This also drops state_lock. It previously protected live channel/PTP pointers, the new code only reads persistent channel_stats and ptp_stats via mlx5e_stats_nch_read(), which is already safe for lockless stats access. Fixes: 3579032c08c1 ("net/mlx5e: Implement ethtool hardware timestamping statistics") Signed-off-by: Carolina Jubran Reviewed-by: Shahar Shitrit Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260902193731.3668958-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../ethernet/mellanox/mlx5/core/en_stats.c | 51 ++++++++----------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c index e7e6db7f6bf1..cd94bb44f6ab 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c @@ -1199,50 +1199,39 @@ void mlx5e_stats_rmon_get(struct mlx5e_priv *priv, void mlx5e_stats_ts_get(struct mlx5e_priv *priv, struct ethtool_ts_stats *ts_stats) { - int i, j; + u16 nch = mlx5e_stats_nch_read(priv); + int i, tc; - mutex_lock(&priv->state_lock); + ts_stats->pkts = 0; + for (i = 0; i < nch; i++) { + struct mlx5e_channel_stats *channel_stats = + priv->channel_stats[i]; + + for (tc = 0; tc < priv->max_opened_tc; tc++) + ts_stats->pkts += channel_stats->sq[tc].timestamps; + } + + /* Accumulate DMA and port timestamp counters so values stay monotonic + * across channel teardown and mode switches. + */ if (priv->tx_ptp_opened) { - struct mlx5e_ptp *ptp = priv->channels.ptp; - - ts_stats->pkts = 0; + /* Err and Lost stats are only relevant for port timestamping, + * as the DMA layer will always successfully timestamp packets. + */ ts_stats->err = 0; ts_stats->lost = 0; - if (!ptp) - goto out; - - /* Aggregate stats across all TCs */ - for (i = 0; i < ptp->num_tc; i++) { + for (tc = 0; tc < priv->max_opened_tc; tc++) { struct mlx5e_ptp_cq_stats *stats = - ptp->ptpsq[i].cq_stats; + &priv->ptp_stats.cq[tc]; ts_stats->pkts += stats->cqe; ts_stats->err += stats->abort + stats->err_cqe + - stats->late_cqe; + stats->late_cqe; ts_stats->lost += stats->lost_cqe; } - } else { - /* DMA layer will always successfully timestamp packets. Other - * counters do not make sense for this layer. - */ - ts_stats->pkts = 0; - - /* Aggregate stats across all SQs */ - for (j = 0; j < priv->channels.num; j++) { - struct mlx5e_channel *c = priv->channels.c[j]; - - for (i = 0; i < c->num_tc; i++) { - struct mlx5e_sq_stats *stats = c->sq[i].stats; - - ts_stats->pkts += stats->timestamps; - } - } } - -out: - mutex_unlock(&priv->state_lock); } #define PPORT_PHY_LAYER_OFF(c) \ From 094cc07f98dfe70a34e2a1923af17fd29b8cf622 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:22 -0400 Subject: [PATCH 0945/2366] net/sched: fq: clamp quantum and initial_quantum in change path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fq change path accepts TCA_FQ_QUANTUM in [1, INT_MAX] and TCA_FQ_INITIAL_QUANTUM up to INT_MAX, while fq_init() already clamps to [1, 1<<20]. A user can override the init clamp via tc qdisc change, restoring the small-quantum deficit spin that the init clamp prevents. Narrow iq_range.max to 1<<20 so TCA_FQ_INITIAL_QUANTUM is rejected at parse time. Clamp TCA_FQ_QUANTUM to [256, 1<<20] in fq_change() and fq_init() quantum to [256, 1<<20] for tiny-MTU devices. Conditions to recreate the bug: CONFIG_NET_SCH_FQ=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root fq tc qdisc change dev dummy0 root fq quantum 1 stab data 32768 size_log 15 cell_log 0 Fixes: 709f34f7c28d ("net/sched: fq: add overflow bounds to quantum and initial quantum") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.2 Signed-off-by: Jakub Kicinski --- net/sched/sch_fq.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c index 6144b5686f13..35f940b2205d 100644 --- a/net/sched/sch_fq.c +++ b/net/sched/sch_fq.c @@ -980,7 +980,7 @@ static int fq_resize(struct Qdisc *sch, u32 log) } static const struct netlink_range_validation iq_range = { - .max = INT_MAX, + .max = 1 << 20, }; static const struct nla_policy fq_policy[TCA_FQ_MAX + 1] = { @@ -1106,14 +1106,10 @@ static int fq_change(struct Qdisc *sch, struct nlattr *opt, nla_get_u32(tb[TCA_FQ_FLOW_PLIMIT])); if (tb[TCA_FQ_QUANTUM]) { - u32 quantum = nla_get_u32(tb[TCA_FQ_QUANTUM]); + u32 quantum = clamp_t(u32, nla_get_u32(tb[TCA_FQ_QUANTUM]), + 256, 1 << 20); - if (quantum > 0 && quantum <= (1 << 20)) { - WRITE_ONCE(q->quantum, quantum); - } else { - NL_SET_ERR_MSG_MOD(extack, "invalid quantum"); - err = -EINVAL; - } + WRITE_ONCE(q->quantum, quantum); } if (tb[TCA_FQ_INITIAL_QUANTUM]) @@ -1232,7 +1228,7 @@ static int fq_init(struct Qdisc *sch, struct nlattr *opt, sch->limit = 10000; q->flow_plimit = 100; mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 1, 1 << 20); - q->quantum = min_t(u32, 2 * mtu, 1 << 20); + q->quantum = clamp_t(u32, 2 * mtu, 256, 1 << 20); q->initial_quantum = min_t(u32, 10 * mtu, 1 << 20); q->flow_refill_delay = msecs_to_jiffies(40); q->flow_max_rate = ~0UL; From 4864f58c53eb47257d55e01f47d4a9f355f7f970 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:23 -0400 Subject: [PATCH 0946/2366] net/sched: fq_pie: clamp quantum in change path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fq_pie_change() accepts any quantum value from userspace, including 1. With a crafted size table qdisc_pkt_len reaches ~2 GiB, so quantum=1 makes the deficit-refill loop spin ~2^31 times under the qdisc lock (a soft lockup / denial of service). Add max(256U, ...) matching fq_codel_change(). Conditions to recreate the bug: CONFIG_NET_SCH_FQ_PIE=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root fq_pie tc qdisc change dev dummy0 root fq_pie quantum 1 stab data 32768 size_log 15 cell_log 0 Fixes: ec97ecf1ebe4 ("net: sched: add Flow Queue PIE packet scheduler") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.3 Signed-off-by: Jakub Kicinski --- net/sched/sch_fq_pie.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/sched/sch_fq_pie.c b/net/sched/sch_fq_pie.c index b27d95418707..5982847df8f8 100644 --- a/net/sched/sch_fq_pie.c +++ b/net/sched/sch_fq_pie.c @@ -341,7 +341,8 @@ static int fq_pie_change(struct Qdisc *sch, struct nlattr *opt, nla_get_u32(tb[TCA_FQ_PIE_BETA])); if (tb[TCA_FQ_PIE_QUANTUM]) - WRITE_ONCE(q->quantum, nla_get_u32(tb[TCA_FQ_PIE_QUANTUM])); + WRITE_ONCE(q->quantum, + max(256U, nla_get_u32(tb[TCA_FQ_PIE_QUANTUM]))); if (tb[TCA_FQ_PIE_MEMORY_LIMIT]) WRITE_ONCE(q->memory_limit, From fb9f88a33c516ea5c0bcd9a22ca288b246b34567 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:24 -0400 Subject: [PATCH 0947/2366] net/sched: sfq: clamp quantum in change path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sfq_change() accepts any non-negative quantum (only rejects (int)ctl->quantum < 0). With a crafted size table qdisc_pkt_len reaches ~2 GiB, so quantum=1 makes the deficit-refill loop spin ~2^31 times under the qdisc lock (a soft lockup / denial of service). Add max(256U, ...) matching fq_codel_change(). Reject quantum > 1<<20 with -EINVAL, matching fq_codel_change() and the init clamp. Conditions to recreate the bug: CONFIG_NET_SCH_SFQ=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root sfq tc qdisc change dev dummy0 root sfq quantum 1 stab data 32768 size_log 15 cell_log 0 Fixes: e4650d7ae425 ("net_sched: sch_sfq: handle bigger packets") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.4 Signed-off-by: Jakub Kicinski --- net/sched/sch_sfq.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/sched/sch_sfq.c b/net/sched/sch_sfq.c index 187d3ed578f2..8bbcfc9e85d9 100644 --- a/net/sched/sch_sfq.c +++ b/net/sched/sch_sfq.c @@ -660,6 +660,11 @@ static int sfq_change(struct Qdisc *sch, struct nlattr *opt, return -EINVAL; } + if (ctl->quantum > 1 << 20) { + NL_SET_ERR_MSG_MOD(extack, "quantum too large"); + return -EINVAL; + } + if (ctl->perturb_period < 0 || ctl->perturb_period > INT_MAX / HZ) { NL_SET_ERR_MSG_MOD(extack, "invalid perturb period"); @@ -688,7 +693,7 @@ static int sfq_change(struct Qdisc *sch, struct nlattr *opt, /* update and validate configuration */ if (ctl->quantum) - quantum = ctl->quantum; + quantum = max(256U, ctl->quantum); if (ctl->flows) maxflows = min_t(u32, ctl->flows, SFQ_MAX_FLOWS); if (ctl->divisor) { From eb56a495f59baf6cad5ed80e3ffb9078098b1346 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:25 -0400 Subject: [PATCH 0948/2366] net/sched: hhf: clamp quantum in change and init paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hhf_change() accepts any quantum from userspace, including 1. With a crafted size table qdisc_pkt_len reaches ~2 GiB, so quantum=1 makes the deficit-refill loop spin ~2^31 times under the qdisc lock (a soft lockup / denial of service). Add max(256U, ...) in hhf_change() matching fq_codel_change(). Clamp hhf_init() to [256, 1<<20] matching the siblings, and remove the old fallback that only set quantum=256 on overflow. Conditions to recreate the bug: CONFIG_NET_SCH_HHF=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root hhf tc qdisc change dev dummy0 root hhf quantum 1 stab data 32768 size_log 15 cell_log 0 Fixes: 10239edf86f1 ("net-qdisc-hhf: Heavy-Hitter Filter (HHF) qdisc") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.5 Signed-off-by: Jakub Kicinski --- net/sched/sch_hhf.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/net/sched/sch_hhf.c b/net/sched/sch_hhf.c index 96acab6a8da0..fc72f825fbd9 100644 --- a/net/sched/sch_hhf.c +++ b/net/sched/sch_hhf.c @@ -551,7 +551,7 @@ static int hhf_change(struct Qdisc *sch, struct nlattr *opt, return err; if (tb[TCA_HHF_QUANTUM]) - new_quantum = nla_get_u32(tb[TCA_HHF_QUANTUM]); + new_quantum = max(256U, nla_get_u32(tb[TCA_HHF_QUANTUM])); if (tb[TCA_HHF_NON_HH_WEIGHT]) new_hhf_non_hh_weight = nla_get_u32(tb[TCA_HHF_NON_HH_WEIGHT]); @@ -613,7 +613,7 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt, int i; sch->limit = 1000; - q->quantum = psched_mtu(qdisc_dev(sch)); + q->quantum = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 256, 1 << 20); get_random_bytes(&q->perturbation, sizeof(q->perturbation)); INIT_LIST_HEAD(&q->new_buckets); INIT_LIST_HEAD(&q->old_buckets); @@ -624,10 +624,6 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt, q->hhf_evict_timeout = HZ; /* 1 sec */ q->hhf_non_hh_weight = 2; - if ((int)q->quantum <= 0 || - (u64)q->quantum * q->hhf_non_hh_weight > INT_MAX) - q->quantum = 256; - if (opt) { int err = hhf_change(sch, opt, extack); From 3c01f1ca5dfc6d6911b0e5b37f5062b1dc451b94 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:26 -0400 Subject: [PATCH 0949/2366] net/sched: dualpi2: clamp psched_mtu at all call sites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dualpi2_calculate_c_protection(), must_drop(), and get_memory_limit() call psched_mtu() with no clamp. A huge MTU makes (s32)psched_mtu() overflow in the signed multiply for c_protection_init, and 2 * psched_mtu() wraps in get_memory_limit(). With a crafted size table qdisc_pkt_len reaches ~2 GiB, causing a soft lockup / denial of service. Clamp psched_mtu() to [1, 1<<20] at all three call sites. Conditions to recreate the bug: CONFIG_NET_SCH_DUALPI2=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root dualpi2 tc qdisc change dev dummy0 root dualpi2 stab data 32768 size_log 15 cell_log 0 Fixes: 320d031ad6e4 ("sched: Struct definition and parsing of dualpi2 qdisc") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.6 Signed-off-by: Jakub Kicinski --- net/sched/sch_dualpi2.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/net/sched/sch_dualpi2.c b/net/sched/sch_dualpi2.c index 4f678d4ff10e..4947def7c49e 100644 --- a/net/sched/sch_dualpi2.c +++ b/net/sched/sch_dualpi2.c @@ -208,9 +208,11 @@ static void dualpi2_reset_c_protection(struct dualpi2_sched_data *q) static void dualpi2_calculate_c_protection(struct Qdisc *sch, struct dualpi2_sched_data *q, u32 wc) { + u32 mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 1, 1 << 20); + q->c_protection_wc = wc; q->c_protection_wl = MAX_WC - wc; - q->c_protection_init = (s32)psched_mtu(qdisc_dev(sch)) * + q->c_protection_init = (s32)mtu * ((int)q->c_protection_wc - (int)q->c_protection_wl); dualpi2_reset_c_protection(q); } @@ -285,8 +287,9 @@ static bool must_drop(struct Qdisc *sch, struct dualpi2_sched_data *q, u64 local_l_prob; bool overload; u32 prob; + u32 mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 1, 1 << 20); - if (sch->qstats.backlog < 2 * psched_mtu(qdisc_dev(sch))) + if (sch->qstats.backlog < 2 * mtu) return false; prob = READ_ONCE(q->pi2_prob); @@ -712,7 +715,8 @@ static u32 get_memory_limit(struct Qdisc *sch, u32 limit) /* Apply rule of thumb, i.e., doubling the packet length, * to further include per packet overhead in memory_limit. */ - u64 memlim = mul_u32_u32(limit, 2 * psched_mtu(qdisc_dev(sch))); + u64 memlim = mul_u32_u32(limit, 2 * clamp_t(u32, psched_mtu(qdisc_dev(sch)), + 1, 1 << 20)); if (upper_32_bits(memlim)) return U32_MAX; From 54370e44c002770ae61fc889f28f699e91616ffc Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:27 -0400 Subject: [PATCH 0950/2366] net/sched: pie: clamp psched_mtu in pie_drop_early MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pie_drop_early() calls psched_mtu() with no clamp. With mtu=0x80000000 the bytemode divide silently zeroes the drop probability, disabling AQM. Clamp to [1, 1<<20]. Conditions to recreate the bug: CONFIG_NET_SCH_PIE=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root pie tc qdisc change dev dummy0 root pie stab data 32768 size_log 15 cell_log 0 Fixes: d4b36210c2e6 ("net: pkt_sched: PIE AQM scheme") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.7 Signed-off-by: Jakub Kicinski --- net/sched/sch_pie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/sch_pie.c b/net/sched/sch_pie.c index b41f2def2e2c..3b7863ffd284 100644 --- a/net/sched/sch_pie.c +++ b/net/sched/sch_pie.c @@ -35,7 +35,7 @@ bool pie_drop_early(struct Qdisc *sch, struct pie_params *params, { u64 rnd; u64 local_prob = vars->prob; - u32 mtu = psched_mtu(qdisc_dev(sch)); + u32 mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 1, 1 << 20); /* If there is still burst allowance left skip random early drop */ if (vars->burst_time > 0) From 8382abec0f1568d0a5590d75a3df92f23fcf5196 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:28 -0400 Subject: [PATCH 0951/2366] net/sched: drr: clamp quantum in change class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drr_change_class() rejects explicit quantum==0 but falls back to psched_mtu() with no floor. With a crafted size table qdisc_pkt_len reaches ~2 GiB, so quantum=1 (or a zero psched_mtu on a headerless device) makes the deficit-refill loop spin under the qdisc lock. Add clamp_t(u32, quantum, 256, 1<<20) after the zero reject and on the fallback path. The explicit-zero reject is preserved. Conditions to recreate the bug: CONFIG_NET_SCH_DRR=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root drr tc class add dev dummy0 parent 1: classid 1:1 drr quantum 1 Fixes: 13d2a1d2b032 ("pkt_sched: add DRR scheduler") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.8 Signed-off-by: Jakub Kicinski --- net/sched/sch_drr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/sched/sch_drr.c b/net/sched/sch_drr.c index 91b1ef824afa..8621d057edd9 100644 --- a/net/sched/sch_drr.c +++ b/net/sched/sch_drr.c @@ -82,8 +82,9 @@ static int drr_change_class(struct Qdisc *sch, u32 classid, u32 parentid, NL_SET_ERR_MSG(extack, "Specified DRR quantum cannot be zero"); return -EINVAL; } + quantum = clamp_t(u32, quantum, 256, 1 << 20); } else - quantum = psched_mtu(qdisc_dev(sch)); + quantum = clamp_t(u32, (u32)psched_mtu(qdisc_dev(sch)), 256, 1 << 20); if (cl != NULL) { if (tca[TCA_RATE]) { From 1c38487f46b243bfeefec0c0c86023a3904f2214 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:29 -0400 Subject: [PATCH 0952/2366] net/sched: ets: clamp quantum in parse and fallback paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ets_qdisc_change() falls back to psched_mtu() with no floor for bands without an explicit quantum. With a crafted size table qdisc_pkt_len reaches ~2 GiB, so a zero psched_mtu on a headerless device makes the deficit-refill loop spin under the qdisc lock. Move the floor into ets_quantum_parse() so explicitly configured quanta are also clamped to [256, 1<<20], not just the fallback path. Conditions to recreate the bug: CONFIG_NET_SCH_ETS=y. Requires CAP_NET_ADMIN (namespace-local via unshare -Urn suffices). tc qdisc add dev dummy0 root ets bands 3 strict 2 quanta 1 1 Fixes: dcc68b4d8084 ("net: sch_ets: Add a new Qdisc") Reported-by: Vega Reviewed-by: Toke Høiland-Jørgensen Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.9 Signed-off-by: Jakub Kicinski --- net/sched/sch_ets.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/net/sched/sch_ets.c b/net/sched/sch_ets.c index 25fcf4079fec..6cc902a03838 100644 --- a/net/sched/sch_ets.c +++ b/net/sched/sch_ets.c @@ -83,11 +83,7 @@ static int ets_quantum_parse(struct Qdisc *sch, const struct nlattr *attr, unsigned int *quantum, struct netlink_ext_ack *extack) { - *quantum = nla_get_u32(attr); - if (!*quantum) { - NL_SET_ERR_MSG(extack, "ETS quantum cannot be zero"); - return -EINVAL; - } + *quantum = clamp_t(u32, nla_get_u32(attr), 256, 1 << 20); return 0; } @@ -632,11 +628,13 @@ static int ets_qdisc_change(struct Qdisc *sch, struct nlattr *opt, return err; } /* If there are more bands than strict + quanta provided, the remaining - * ones are ETS with quantum of MTU. Initialize the missing values here. + * ones are ETS with quantum of max(MTU, 256). Initialize the missing + * values here. */ for (i = nstrict; i < nbands; i++) { if (!quanta[i]) - quanta[i] = psched_mtu(qdisc_dev(sch)); + quanta[i] = clamp_t(u32, (u32)psched_mtu(qdisc_dev(sch)), + 256, 1 << 20); } /* Before commit, make sure we can allocate all new qdiscs */ From 8f0229bef3cba996bd40e40aafc512150016b696 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Tue, 1 Sep 2026 17:39:30 -0400 Subject: [PATCH 0953/2366] selftests: tc-testing: update ETS test 41f5 for clamped quanta Commit "net/sched: ets: clamp quantum in parse and fallback paths" moved the quantum floor into ets_quantum_parse(), so every explicitly configured quantum is now clamped to [256, 1 << 20], not just the psched_mtu() fallback. Test 41f5 passes "quanta 4294967294 1 1" and matches the values back verbatim, so all three bands now differ from what it expects: before: bands 3 quanta 4294967294 1 1 after: bands 3 quanta 1048576 256 256 Update the match pattern accordingly. Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-0CFC.v3.20260901204856@mojatatu.com.10 Signed-off-by: Jakub Kicinski --- tools/testing/selftests/tc-testing/tc-tests/qdiscs/ets.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/ets.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/ets.json index ee09e6d6fdf3..d2eab61c099a 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/ets.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/ets.json @@ -987,7 +987,7 @@ }, { "id": "41f5", - "name": "ETS offload where the sum of quanta wraps u32", + "name": "ETS offload with out-of-range quanta clamped", "category": [ "qdisc", "ets" @@ -1002,7 +1002,7 @@ "cmdUnderTest": "$TC qdisc add dev $ETH root ets quanta 4294967294 1 1", "expExitCode": "0", "verifyCmd": "$TC qdisc show dev $ETH", - "matchPattern": "qdisc ets .*bands 3 quanta 4294967294 1 1", + "matchPattern": "qdisc ets .*bands 3 quanta 1048576 256 256", "matchCount": "1", "teardown": [ "echo \"1\" > /sys/bus/netdevsim/del_device" From 38b6be101006d3e7af972999f45d4f1e8250587a Mon Sep 17 00:00:00 2001 From: Vineeth Karumanchi Date: Wed, 2 Sep 2026 15:58:36 +0530 Subject: [PATCH 0954/2366] net: macb: fix NULL pointer dereference on unbind with fixed-link When the device tree describes a fixed-link and has no "mdio" child node, macb_mii_init() returns early without allocating the MDIO bus, leaving bp->mii_bus as NULL. Two cleanup paths then dereference this NULL bus: 1. On driver unbind, macb_remove() unconditionally calls mdiobus_unregister(bp->mii_bus), which oopses: Unable to handle kernel NULL pointer dereference at virtual address 00000000000004a8 pc : mdiobus_unregister+0x14/0xa4 lr : macb_remove+0x38/0xa4 Call trace: mdiobus_unregister+0x14/0xa4 (P) macb_remove+0x38/0xa4 platform_remove+0x20/0x30 device_release_driver_internal+0x1c8/0x224 unbind_store+0xb4/0xbc 2. On the probe error path in macb_probe(), reached when macb_mii_init() has succeeded but a subsequent step fails, the err_out_unregister_mdio label runs the same unconditional cleanup. mdiobus_unregister() and mdiobus_free() do not guard against a NULL bus, so guard the calls in both macb_remove() and the probe error path. Fixes: d0c3601f2c4e ("net: macb: Avoid 20s boot delay by skipping MDIO bus registration for fixed-link PHY") Signed-off-by: Vineeth Karumanchi Reviewed-by: Xuanqiang Luo Reviewed-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260902102836.2019355-1-vineeth.karumanchi@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index b1939da4c95a..8469df0d89c3 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -5976,8 +5976,10 @@ static int macb_probe(struct platform_device *pdev) macb_free_tieoff(bp); err_out_unregister_mdio: - mdiobus_unregister(bp->mii_bus); - mdiobus_free(bp->mii_bus); + if (bp->mii_bus) { + mdiobus_unregister(bp->mii_bus); + mdiobus_free(bp->mii_bus); + } err_out_phy_exit: phy_exit(bp->phy); @@ -6006,8 +6008,10 @@ static void macb_remove(struct platform_device *pdev) unregister_netdev(netdev); macb_free_tieoff(bp); phy_exit(bp->phy); - mdiobus_unregister(bp->mii_bus); - mdiobus_free(bp->mii_bus); + if (bp->mii_bus) { + mdiobus_unregister(bp->mii_bus); + mdiobus_free(bp->mii_bus); + } device_set_wakeup_enable(&bp->pdev->dev, 0); cancel_delayed_work_sync(&bp->tx_lpi_work); From 3265ef0b670180b0b946d73ee9825d6d91e98a08 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 06:09:55 -1000 Subject: [PATCH 0955/2366] sched_ext: Rename sch to root_sch in dispatch_one() dispatch_one() uses the root scheduler for everything it does, including the two decisions to keep running @prev, which are wrong when @prev belongs to a sub-scheduler. The function has to deal with @prev's scheduler too. Rename the root's local from sch to root_sch for clarity and to make room for it. No functional change. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 7e414a7c53fc..120540cdda74 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2919,7 +2919,7 @@ static inline void maybe_queue_balance_callback(struct rq *rq) static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev) { - struct scx_sched *sch = scx_root_protected_live(); + struct scx_sched *root_sch = scx_root_protected_live(); enum scx_dsp_verdict verdict; s32 cpu = cpu_of(rq); @@ -2928,7 +2928,7 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev scx_process_sync_ecaps(rq, prev); - if ((sch->ops.flags & SCX_OPS_HAS_CPU_PREEMPT) && + if ((root_sch->ops.flags & SCX_OPS_HAS_CPU_PREEMPT) && unlikely(rq->scx.cpu_released)) { /* * If the previous sched_class for the current CPU was not SCX, @@ -2936,8 +2936,8 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev * core. This callback complements ->cpu_release(), which is * emitted in switch_class(). */ - if (sch->ops.cpu_acquire) - SCX_CALL_OP(sch, cpu_acquire, rq, cpu, NULL); + if (root_sch->ops.cpu_acquire) + SCX_CALL_OP(root_sch, cpu_acquire, rq, cpu, NULL); rq->scx.cpu_released = false; } @@ -2955,7 +2955,7 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev * test. */ if ((prev->scx.flags & SCX_TASK_QUEUED) && prev->scx.slice && - !scx_bypassing(sch, cpu)) { + !scx_bypassing(root_sch, cpu)) { verdict = SCX_DSP_PREV; goto has_tasks; } @@ -2967,7 +2967,7 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev goto has_tasks; } - verdict = scx_dispatch_sched(sch, rq, prev, false); + verdict = scx_dispatch_sched(root_sch, rq, prev, false); if (verdict != SCX_DSP_NONE) goto has_tasks; @@ -2976,9 +2976,9 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev * %SCX_OPS_ENQ_LAST is in effect. */ if ((prev->scx.flags & SCX_TASK_QUEUED) && - (!(sch->ops.flags & SCX_OPS_ENQ_LAST) || scx_bypassing(sch, cpu)) && + (!(root_sch->ops.flags & SCX_OPS_ENQ_LAST) || scx_bypassing(root_sch, cpu)) && scx_task_can_stay_on_cpu(rq, prev)) { - __scx_add_event(sch, SCX_EV_DISPATCH_KEEP_LAST, 1); + __scx_add_event(root_sch, SCX_EV_DISPATCH_KEEP_LAST, 1); verdict = SCX_DSP_PREV; goto has_tasks; } From 90f19b2816f5d243a8daf36ca83d9d9d04f00e4c Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 06:09:56 -1000 Subject: [PATCH 0956/2366] sched_ext: Use @prev's scheduler for the keep decisions in dispatch_one() dispatch_one() tests ops flags and bypass state against the root scheduler in both places where it decides to keep running @prev: the early keep of a @prev with slice left tests the root's bypass state, and the keep-last at the end tests the root's SCX_OPS_ENQ_LAST and bypass state. Both are properties of the scheduler @prev belongs to, and put_prev_task_scx(), which acts on the outcome, reads them from that scheduler. When @prev belongs to a sub-scheduler the two sides disagree. The keep-last case is visible. The root set SCX_OPS_ENQ_LAST, so a lone @prev of a sub-scheduler is not kept and is enqueued with SCX_ENQ_LAST to a sub-scheduler that never opted in. This trips the WARN_ON_ONCE in put_prev_task_scx() for the missing flag, and the sub-scheduler queues the task like any other and triggers no follow-up scheduling event, which can lead to stalls. Test SCX_OPS_ENQ_LAST and bypass state on @prev's sched in both places and charge SCX_EV_DISPATCH_KEEP_LAST to it. Read the sched at each decision, as the dispatch in between can drop the rq lock. Fixes: 88234b075c3f ("sched_ext: Introduce scx_task_sched[_rcu]()") Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 120540cdda74..adf5993fa597 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2955,7 +2955,7 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev * test. */ if ((prev->scx.flags & SCX_TASK_QUEUED) && prev->scx.slice && - !scx_bypassing(root_sch, cpu)) { + !scx_bypassing(scx_task_sched(prev), cpu)) { verdict = SCX_DSP_PREV; goto has_tasks; } @@ -2972,15 +2972,20 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev goto has_tasks; /* - * Didn't find another task to run. Keep running @prev unless - * %SCX_OPS_ENQ_LAST is in effect. + * Didn't find another task to run. Keep running @prev unless its own + * scheduler set %SCX_OPS_ENQ_LAST and takes the enqueue instead, see + * put_prev_task_scx(). Read the scheduler here as the dispatch above + * may have dropped the rq lock while @prev changed class or scheduler. */ - if ((prev->scx.flags & SCX_TASK_QUEUED) && - (!(root_sch->ops.flags & SCX_OPS_ENQ_LAST) || scx_bypassing(root_sch, cpu)) && - scx_task_can_stay_on_cpu(rq, prev)) { - __scx_add_event(root_sch, SCX_EV_DISPATCH_KEEP_LAST, 1); - verdict = SCX_DSP_PREV; - goto has_tasks; + if (prev->scx.flags & SCX_TASK_QUEUED) { + struct scx_sched *prev_sch = scx_task_sched(prev); + + if ((!(prev_sch->ops.flags & SCX_OPS_ENQ_LAST) || + scx_bypassing(prev_sch, cpu)) && scx_task_can_stay_on_cpu(rq, prev)) { + __scx_add_event(prev_sch, SCX_EV_DISPATCH_KEEP_LAST, 1); + verdict = SCX_DSP_PREV; + goto has_tasks; + } } rq->scx.flags &= ~SCX_RQ_IN_DISPATCH; return SCX_DSP_NONE; From a0d356696f87700c8c2934e3881277b0d37f0b71 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 06:09:57 -1000 Subject: [PATCH 0957/2366] sched_ext: scx_qmap: Do not add IMMED to rescue inserts qmap's stranded fallback forces a task that can run on none of its self cids onto its first allowed cid with SCX_ENQ_RESCUE, and adds SCX_ENQ_IMMED when that cid is a time-share it holds. On such a cid the insert stops being a rescue request: 1. A task is enqueued while none of its allowed cids is in self_cids. At attach self_cids is still empty. 2. qmap inserts it into cid 0's local DSQ with SCX_ENQ_RESCUE | SCX_ENQ_IMMED. 3. The kernel finds ENQ_IMMED held on cid 0, admits the insert and skips the rescue diversion. 4. cid 0's cpu is busy, so the IMMED task is bounced back to qmap with SCX_ENQ_REENQ. 5. qmap's enqueue sees the same inputs and repeats step 2. Nothing runs in between. 6. The reenqueue limit ejects qmap with SCX_EXIT_ERROR_REENQ. The caps granted during the parent's ops.sub_attach() are delivered after the sub already holds its tasks, while the per-cid effective caps that mark the time-shares are delivered from the first dispatch after bypass lifts, so every attach that receives a time-share on a task's first allowed cid starts the loop. Drop IMMED from the rescue inserts so that step 3 diverts to the rescue path. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- tools/sched_ext/scx_qmap.bpf.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index 9f6e61d7ca07..e4e51303bd29 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -358,8 +358,8 @@ s32 BPF_STRUCT_OPS(qmap_select_cid, struct task_struct *p, } /* - * A received time-shared cid is held ENQ_IMMED-only, so inserts must set - * SCX_ENQ_IMMED. + * A received time-shared cid is held ENQ_IMMED-only, so inserts meant to run + * there must set SCX_ENQ_IMMED. */ static u64 needs_immed(s32 cid) { @@ -444,9 +444,11 @@ void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags) * didn't grant them or we delegated them to children - would starve in * SHARED/FIFO since we only pull from those on self cids. * - * Force it onto its first allowed cid's local DSQ. If we hold that cid - * it runs. Otherwise the insert carries SCX_ENQ_RESCUE and the kernel - * diverts the task to its rescue path. + * Force it onto its first allowed cid's local DSQ with SCX_ENQ_RESCUE. + * If we hold ENQ on that cid it runs. Otherwise the kernel diverts the + * task to its rescue path. IMMED would turn the insert into a legal + * placement on a time-shared cid and the kernel would bounce it back + * here instead of rescuing it. */ if (!cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) { s32 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0); @@ -455,7 +457,7 @@ void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags) taskc->force_local = false; __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | c, slice_ns, - enq_flags | needs_immed(c) | SCX_ENQ_RESCUE); + enq_flags | SCX_ENQ_RESCUE); return; } } @@ -618,7 +620,7 @@ static bool scan_shared_dsq(bool from_timer) if (c >= 0 && c < scx_bpf_nr_cids()) { __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c, - needs_immed(c) | SCX_ENQ_RESCUE); + SCX_ENQ_RESCUE); } continue; } @@ -659,7 +661,7 @@ static bool scan_shared_dsq(bool from_timer) if (c >= 0 && c < nr_cids) { __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c, - needs_immed(c) | SCX_ENQ_RESCUE); + SCX_ENQ_RESCUE); } continue; } From 63b4ff622244483e7c530e97d787a3d6c2c38a33 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 06:09:58 -1000 Subject: [PATCH 0958/2366] sched_ext: scx_qmap: Place only on cids whose caps are in effect qmap decides placements from self_cids, which redistribute() derives from the caps view at ops.sub_caps_updated() time. That view runs ahead of the cpus: a granted cid can be in self_cids before its cpu has reported the caps in effect through ops.sub_ecaps_updated(). ops.update_idle() only comes once BASE is in effect, so the idle-gated placements reach such a cid only through an idle bit left over from an earlier hold. The highpri scan has no gate at all: parent cpu Y, qmap cpu X grants ENQ on X to qmap sub_caps_updated() adds X to self_cids highpri scan moves a task to X with PREEMPT caps not in effect, move denied, task bounced with REENQ_CAP reject drain, enqueue the scan moves it to X again denied again dispatch syncs ecaps, sub_ecaps_updated(X) Every highpri move to X in that window is denied and bounced. The two callbacks are meant to split the roles: ops.sub_caps_updated() tracks what the node holds and drives what it delegates to its children, while ops.sub_ecaps_updated() says whether a task can run on a cpu now. qmap used the first for both. Track the caps in effect from ops.sub_ecaps_updated() as avail_cids and place only on self_cids & avail_cids, so that self_cids stays the delegation split and avail_cids gates the placement. The stranded tests keep self_cids, as they ask whether the split gives the task anywhere at all. A highpri task whose self_cids lack caps in effect waits for them instead of being moved and bounced. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- tools/sched_ext/scx_qmap.bpf.c | 83 ++++++++++++++++++++++++++-------- tools/sched_ext/scx_qmap.h | 3 ++ 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index e4e51303bd29..062bb22ee65c 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -24,6 +24,9 @@ * time-share that stays self-local. * self - The excl cpus the node kept for itself, plus all of held_shared. * owner - Who holds a cid - a child slot, CID_SELF, or CID_NONE. + * avail - Cpus whose caps are in effect, per ops.sub_ecaps_updated(). + * usable - self AND avail. Placement decisions use this: self is the + * delegation split and can run ahead of what the cpus honor. * * The scheduler splits its held-excl cpus among self and the children in * proportion to each node's cpu.weight, handing each the floor of its share as @@ -208,8 +211,8 @@ static int qmap_spin_lock(struct bpf_res_spin_lock *lock) } /* - * Try prev_cid, then scan cpus_allowed AND idle_cids AND self_cids round-robin - * from prev_cid + 1. Atomic claim retries on race; bounded by + * Try prev_cid, then scan cpus_allowed AND idle_cids AND usable_cids + * round-robin from prev_cid + 1. Atomic claim retries on race; bounded by * IDLE_PICK_RETRIES to keep the verifier's insn budget in check. */ #define IDLE_PICK_RETRIES 16 @@ -221,7 +224,7 @@ static s32 pick_direct_dispatch_cid(struct task_struct *p, s32 prev_cid, s32 cid; u32 i; - if (cmask_test(prev_cid, &qa.self_cids.mask) && + if (cmask_test(prev_cid, &qa.usable_cids.mask) && cmask_test_and_clear(prev_cid, &qa.idle_cids.mask)) return prev_cid; @@ -229,7 +232,7 @@ static s32 pick_direct_dispatch_cid(struct task_struct *p, s32 prev_cid, bpf_for(i, 0, IDLE_PICK_RETRIES) { cid = cmask_next_and2_set_wrap(&taskc->cpus_allowed, &qa.idle_cids.mask, - &qa.self_cids.mask, cid + 1); + &qa.usable_cids.mask, cid + 1); barrier_var(cid); if (cid >= nr_cids) return -1; @@ -542,7 +545,7 @@ void BPF_STRUCT_OPS(qmap_enqueue, struct task_struct *p, u64 enq_flags) scx_bpf_dsq_insert(p, SHARED_DSQ, 0, enq_flags); cid = cmask_next_and2_set_wrap(&taskc->cpus_allowed, &qa.idle_cids.mask, - &qa.self_cids.mask, 0); + &qa.usable_cids.mask, 0); if (cid < scx_bpf_nr_cids()) scx_bpf_kick_cid(cid, SCX_KICK_IDLE); return; @@ -646,18 +649,23 @@ static bool scan_shared_dsq(bool from_timer) if (!(taskc = lookup_task_ctx(p))) return false; - /* only run highpri tasks on cids this node holds, not delegated ones */ + /* only run highpri tasks on cids this node can use right now */ if (cmask_test(this_cid, &taskc->cpus_allowed) && - cmask_test(this_cid, &qa.self_cids.mask)) + cmask_test(this_cid, &qa.usable_cids.mask)) cid = this_cid; else cid = cmask_next_and_set_wrap(&taskc->cpus_allowed, - &qa.self_cids.mask, + &qa.usable_cids.mask, this_cid + 1); if (cid >= nr_cids) { - /* stranded after the cull - rescue it from here */ - s32 c = cmask_next_set_wrap(&taskc->cpus_allowed, 0); + s32 c; + /* self cids lack caps in effect yet, leave it queued */ + if (cmask_intersects(&taskc->cpus_allowed, &qa.self_cids.mask)) + continue; + + /* stranded after the cull - rescue it from here */ + c = cmask_next_set_wrap(&taskc->cpus_allowed, 0); if (c >= 0 && c < nr_cids) { __sync_fetch_and_add(&qa.nr_rescue_dsp, 1); scx_bpf_dsq_move(BPF_FOR_EACH_ITER, p, SCX_DSQ_LOCAL_ON | c, @@ -1115,7 +1123,7 @@ void BPF_STRUCT_OPS(qmap_update_idle, s32 cid, bool idle) /* * The kernel delivers update_idle() for every cid this node holds * SCX_CAP_BASE on. Track every cid's idle state regardless of - * delegation: the direct-dispatch pick masks idle_cids with self_cids + * delegation: the direct-dispatch pick masks idle_cids with usable_cids * at selection, so a cid already idle when it returns to self needs no * reseed here. */ @@ -1539,6 +1547,19 @@ static __noinline void account_alloc(void) } } +/* + * usable_cids = self_cids & avail_cids. The inputs have separate writers, + * apply_partition() and qmap_sub_ecaps_updated(), so the result is rebuilt in + * full under the partition guard, in scratch first so that readers never see + * self_cids alone. + */ +static void refresh_usable(void) +{ + cmask_copy(&qa.usable_scratch.mask, &qa.self_cids.mask); + cmask_and(&qa.usable_scratch.mask, &qa.avail_cids.mask); + cmask_copy(&qa.usable_cids.mask, &qa.usable_scratch.mask); +} + /* * apply_partition - execute the plan compute_partition() built * @@ -1561,6 +1582,7 @@ __noinline void apply_partition(void) /* no excl cpu: run own tasks on the held shares, evict children */ if (!qa.part.nr_excl) { cmask_copy(&qa.self_cids.mask, &qa.held_shared.mask); + refresh_usable(); bpf_for(i, 0, MAX_SUB_SCHEDS) if (qa.sub_sched_ctxs[i].cgroup_id) scx_bpf_sub_kill(qa.sub_sched_ctxs[i].cgroup_id, @@ -1598,6 +1620,7 @@ __noinline void apply_partition(void) else if (o == CID_SELF) cmask_set(cid, &qa.self_cids.mask); } + refresh_usable(); /* * Apply each child's exclusive cids as a delta against its previous @@ -1839,8 +1862,11 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init) cmask_init(&qa.rr_cids.mask, 0, nr_cids); cmask_init(&qa.prev_rr_cids.mask, 0, nr_cids); cmask_init(&qa.self_cids.mask, 0, nr_cids); + cmask_init(&qa.avail_cids.mask, 0, nr_cids); + cmask_init(&qa.usable_cids.mask, 0, nr_cids); cmask_init(&qa.to_revoke_cids.mask, 0, nr_cids); cmask_init(&qa.to_grant_cids.mask, 0, nr_cids); + cmask_init(&qa.usable_scratch.mask, 0, nr_cids); cmask_init(&qa.held_excl.mask, 0, nr_cids); cmask_init(&qa.held_shared.mask, 0, nr_cids); @@ -1854,14 +1880,16 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init) } /* - * The root starts holding every cid. qmap_sub_ecaps_updated() maintains - * per-cid shared state as effective caps settle, and redistribute() - * rebuilds owner and self from held caps. A non-root node starts with - * nothing. + * The root starts holding every cid and gets no ecaps notifications, so + * its avail set is fixed here. qmap_sub_ecaps_updated() maintains the + * per-cid state as effective caps settle, and redistribute() rebuilds + * owner and self from held caps. A non-root node starts with nothing. */ bpf_for(i, 0, nr_cids) { if (!sub_cgroup_id) { cmask_set(i, &qa.self_cids.mask); + cmask_set(i, &qa.avail_cids.mask); + cmask_set(i, &qa.usable_cids.mask); qa.part.cid_owner[i] = CID_SELF; } else { qa.part.cid_owner[i] = CID_NONE; @@ -2002,12 +2030,31 @@ void BPF_STRUCT_OPS(qmap_sub_ecaps_updated, s32 cid, u64 before, u64 after) { /* * Effective caps updated. Track which cids hold shared caps so a self - * task placed there enqueues IMMED. + * task placed there enqueues IMMED, and which cids have ENQ_IMMED in + * effect at all (avail, see the header comment). */ - if (after & SCX_CAP_ENQ_IMMED) + if (after & SCX_CAP_ENQ_IMMED) { qa.cid_shared[cid] = (after & SCX_CAP_ENQ) ? 0 : 1; - else + cmask_set(cid, &qa.avail_cids.mask); + } else { qa.cid_shared[cid] = 0; + cmask_clear(cid, &qa.avail_cids.mask); + } + + /* + * When another runner holds the partition guard, set part_pending: + * redistribute() drains it before releasing and rr_advance() checks it + * after, so the deferred refresh lands by the next rr tick. A + * repartition that lost the guard to us runs here. + */ + if (part_try_start()) { + refresh_usable(); + part_end(); + if (__sync_fetch_and_or(&part_pending, 0)) + redistribute(); + } else { + __sync_fetch_and_or(&part_pending, 1); + } } SCX_OPS_CID_DEFINE(qmap_ops, diff --git a/tools/sched_ext/scx_qmap.h b/tools/sched_ext/scx_qmap.h index c78d61806b39..e95fffcf7b23 100644 --- a/tools/sched_ext/scx_qmap.h +++ b/tools/sched_ext/scx_qmap.h @@ -165,12 +165,15 @@ struct qmap_arena { /* bpf-internal cmasks (embedded, see struct qmap_cmask) */ struct qmap_cmask self_cids; /* cids this node runs its own tasks on */ + struct qmap_cmask avail_cids; /* cids with caps in effect on the cpu */ + struct qmap_cmask usable_cids; /* self_cids & avail_cids, placeable right now */ struct qmap_cmask idle_cids; /* idle state of all cids regardless of delegation */ struct qmap_cmask rr_cids; /* the shared pool, as a mask for grant/revoke */ /* scratch cmasks */ struct qmap_cmask to_revoke_cids; /* delta cids to revoke */ struct qmap_cmask to_grant_cids; /* delta cids to grant */ + struct qmap_cmask usable_scratch; /* refresh_usable() build area */ struct qmap_cmask prev_rr_cids; /* previous shared pool, to clear stale grants */ struct qmap_cmask held_excl; /* cids held excl (ENQ): delegatable */ struct qmap_cmask held_shared; /* cids held shared (ENQ_IMMED only): self-local */ From c6709d5e14072d0e3d02f291daee46a199e5dad3 Mon Sep 17 00:00:00 2001 From: Younes Akhouayri Date: Sat, 5 Sep 2026 17:16:51 +0200 Subject: [PATCH 0959/2366] rust: num: seal Integer Bounded relies on Integer implementations to describe primitive integer semantics correctly. In particular, it uses Integer::BITS and Signedness to justify unchecked operations. Integer is currently safe and externally implementable, so an implementation can violate those assumptions and make safe Bounded operations reach undefined behavior. For example, an Integer implementation for a u8 wrapper can report BITS = 16. Safe code can then cast a Bounded containing 256 to that wrapper. Its TryFrom implementation returns Err, and Bounded::cast() calls unwrap_unchecked() on it, causing undefined behavior. Seal Integer so only the primitive implementations provided by the kernel crate can satisfy it. Fixes: 01e345e82ec3 ("rust: num: add Bounded integer wrapping type") Reported-by: Miguel Ojeda Closes: https://lore.kernel.org/rust-for-linux/CANiq72mOfR33s4y+Ueivd5NrC5yre+Pcp57ZOBz0msw9A4AP1Q@mail.gmail.com/ Cc: stable@vger.kernel.org Suggested-by: Miguel Ojeda Signed-off-by: Younes Akhouayri Acked-by: Alexandre Courbot Link: https://patch.msgid.link/20260905-feature-rust-num-seal-integer-v2-1-f1311ffbe6e7@younes.io Signed-off-by: Miguel Ojeda --- rust/kernel/num.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rust/kernel/num.rs b/rust/kernel/num.rs index dbe848e30efe..de589792a77a 100644 --- a/rust/kernel/num.rs +++ b/rust/kernel/num.rs @@ -15,9 +15,14 @@ pub enum Unsigned {} /// Designates signed primitive types. pub enum Signed {} +mod private { + pub trait Sealed {} +} + /// Describes core properties of integer types. pub trait Integer: - Sized + private::Sealed + + Sized + Copy + Clone + PartialEq @@ -56,6 +61,8 @@ pub trait Integer: macro_rules! impl_integer { ($($type:ty: $signedness:ty), *) => { $( + impl private::Sealed for $type {} + impl Integer for $type { type Signedness = $signedness; From c3fd8e5fd100f122bad503bdc0e9277219533253 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Sat, 5 Sep 2026 03:47:33 +0200 Subject: [PATCH 0960/2366] bpf: Reject non-scalar bpf_loop iteration counts bpf_loop() declares its nr_loops argument as ARG_ANYTHING. Privileged programs may pass pointer values to such arguments, so check_func_arg() lets a pointer-valued R1 reach the helper-specific checks. Since commit bb124da69c47 ("bpf: keep track of max number of bpf_loop callback iterations"), the verifier marks R1 precise and reads its upper bound to limit callback simulation. Precision backtracking only accepts scalar registers, so passing a pointer instead triggers the "backtracking misuse" verifier warning. Kernels with panic_on_warn enabled subsequently panic. Introduce ARG_SCALAR for helper arguments that only accept scalar values and use it for bpf_loop() nr_loops. Generic helper argument validation then rejects pointers before loop inlining and precision processing. Fixes: bb124da69c47 ("bpf: keep track of max number of bpf_loop callback iterations") Reported-by: syzbot+7b47f87674e9a1569110@syzkaller.appspotmail.com Signed-off-by: Kumar Kartikeya Dwivedi Link: https://patch.msgid.link/20260905014735.1452988-2-memxor@gmail.com Closes: https://lore.kernel.org/bpf/6a9ad24c.b5d4176b.238c3e.0001.GAE@google.com/ Signed-off-by: Eduard Zingerman --- include/linux/bpf.h | 1 + kernel/bpf/bpf_iter.c | 2 +- kernel/bpf/verifier.c | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index b7dbf3d9b5c0..e57af902560c 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -894,6 +894,7 @@ enum bpf_arg_type { ARG_PTR_TO_CTX, /* pointer to context */ ARG_ANYTHING, /* any (initialized) argument is ok */ + ARG_SCALAR, /* scalar argument */ ARG_PTR_TO_SPIN_LOCK, /* pointer to bpf_spin_lock */ ARG_PTR_TO_SOCK_COMMON, /* pointer to sock_common */ ARG_PTR_TO_SOCKET, /* pointer to bpf_sock (fullsock) */ diff --git a/kernel/bpf/bpf_iter.c b/kernel/bpf/bpf_iter.c index 14a5fdfa0421..b40eb404adab 100644 --- a/kernel/bpf/bpf_iter.c +++ b/kernel/bpf/bpf_iter.c @@ -754,7 +754,7 @@ const struct bpf_func_proto bpf_loop_proto = { .func = bpf_loop, .gpl_only = false, .ret_type = RET_INTEGER, - .arg1_type = ARG_ANYTHING, + .arg1_type = ARG_SCALAR, .arg2_type = ARG_PTR_TO_FUNC, .arg3_type = ARG_PTR_TO_STACK_OR_NULL, .arg4_type = ARG_ANYTHING, diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 1c3039f3fc32..4638a2f85d0f 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -8211,6 +8211,7 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = { [ARG_MEM_SIZE] = &scalar_types, [ARG_MEM_SIZE_OR_ZERO] = &scalar_types, [ARG_CONST_ALLOC_SIZE_OR_ZERO] = &scalar_types, + [ARG_SCALAR] = &scalar_types, [ARG_CONST_MAP_PTR] = &const_map_ptr_types, [ARG_PTR_TO_CTX] = &context_types, [ARG_PTR_TO_SOCK_COMMON] = &sock_types, From bde8901ea14244e7195a2d6b6aa2023b28d4233c Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Sat, 5 Sep 2026 03:47:34 +0200 Subject: [PATCH 0961/2366] selftests/bpf: Test pointer bpf_loop iteration count rejection Add a verifier test that leaves the raw tracepoint context pointer in R1 when calling bpf_loop(). This is the smallest trigger for the incorrect precision backtracking: it reuses an existing callback and needs no maps or userspace setup. Expect an ordinary scalar-type rejection. Without the verifier fix, the test instead reaches precision backtracking and reports an internal "backtracking misuse" error. Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260905014735.1452988-3-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../bpf/progs/verifier_iterating_callbacks.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c b/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c index 75dd922e4e9f..1fbcc5228306 100644 --- a/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c +++ b/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c @@ -168,6 +168,23 @@ static int iter_limit_cb(__u32 idx, struct num_context *ctx) return 0; } +SEC("?raw_tp") +__failure __msg("R1 type=ctx expected=scalar") +__naked void bpf_loop_reject_pointer(void) +{ + asm volatile ( + "r2 = %[iter_limit_cb];" + "r3 = 0;" + "r4 = 0;" + "call %[bpf_loop];" + "exit;" + : + : __imm_ptr(iter_limit_cb), + __imm(bpf_loop) + : __clobber_common + ); +} + SEC("?raw_tp") __success int bpf_loop_iter_limit_ok(void *unused) From 662ade4de9ff5eceb0820a9f8e9fac70ba6a815b Mon Sep 17 00:00:00 2001 From: Quanye Yang Date: Mon, 31 Aug 2026 20:30:58 +0800 Subject: [PATCH 0962/2366] RDMA/ucma: Serialize join and leave on copy_to_user failure rdma_join_multicast() queues RoCE work that later reads the ucma_multicast through event->param.ud.private_data, then list_add()s the CMA multicast at the head of id_priv->mc_list. rdma_leave_multicast() matches only by sockaddr and destroys the first hit. ucma_process_join() used to drop ctx->mutex after a successful join and retake it only if copy_to_user() failed. Two concurrent JOIN_MCAST calls with the same address can therefore insert a second CMA entry before the first thread's leave. leave then cancels the newer work and the older worker still dereferences the ucma_multicast that the first thread frees. Keep ctx->mutex held from rdma_join_multicast() through copy_to_user() and, on -EFAULT, through rdma_leave_multicast() so leave cannot miss this join. Do not leave if join itself failed: that path never published this address on mc_list, and a leave-by-addr would destroy an earlier successful join. Reported-by: syzbot+a6ffe86390c8a6afc818@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a6ffe86390c8a6afc818 Fixes: fe454dc31e84 ("RDMA/ucma: Fix use-after-free bug in ucma_create_uevent") Cc: stable@vger.kernel.org Signed-off-by: Quanye Yang Link: https://patch.msgid.link/20260831-rdma-ucma-mc-uaf-v1-1-b8eeb7046aff@proton.me Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/ucma.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/infiniband/core/ucma.c b/drivers/infiniband/core/ucma.c index 4929636f7c53..a15182f7a7c5 100644 --- a/drivers/infiniband/core/ucma.c +++ b/drivers/infiniband/core/ucma.c @@ -1556,9 +1556,10 @@ static ssize_t ucma_process_join(struct ucma_file *file, mutex_lock(&ctx->mutex); ret = rdma_join_multicast(ctx->cm_id, (struct sockaddr *)&mc->addr, join_state, mc); - mutex_unlock(&ctx->mutex); - if (ret) + if (ret) { + mutex_unlock(&ctx->mutex); goto err_xa_erase; + } resp.id = mc->id; if (copy_to_user(u64_to_user_ptr(cmd->response), @@ -1566,6 +1567,7 @@ static ssize_t ucma_process_join(struct ucma_file *file, ret = -EFAULT; goto err_leave_multicast; } + mutex_unlock(&ctx->mutex); xa_store(&multicast_table, mc->id, mc, 0); @@ -1573,7 +1575,6 @@ static ssize_t ucma_process_join(struct ucma_file *file, return 0; err_leave_multicast: - mutex_lock(&ctx->mutex); rdma_leave_multicast(ctx->cm_id, (struct sockaddr *) &mc->addr); mutex_unlock(&ctx->mutex); ucma_cleanup_mc_events(mc); From 9a141d3dc869d18b2eab35e999f4790a9b84e40f Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Wed, 2 Sep 2026 17:06:32 +0300 Subject: [PATCH 0963/2366] IB/IPoIB: Avoid restoring OPER_UP after multicast flush ipoib_ib_dev_flush_light() temporarily clears IPOIB_FLAG_OPER_UP to prevent multicast joins while ipoib_mcast_dev_flush() is running, and restores the flag afterwards if it was previously set. This restore races with ipoib_ib_dev_down(). If the interface is brought down while the flush is in progress, ipoib_ib_dev_down() clears IPOIB_FLAG_OPER_UP, but the flush path may set it again after the device has already gone down. Since commit 894021a75291 ("IB/ipoib: Make the carrier_on_task race aware"), ipoib_mcast_carrier_on_task() relies on IPOIB_FLAG_OPER_UP being cleared to terminate its rtnl_trylock() retry loop. If the flag is left set after shutdown, the workqueue retries forever, causing teardown to deadlock when ipoib_ndo_uninit() waits in destroy_workqueue() while holding RTNL. Instead of overloading IPOIB_FLAG_OPER_UP to block multicast joins during a light flush, introduce a dedicated IPOIB_FLAG_MCAST_FLUSH flag. Use it together with IPOIB_FLAG_OPER_UP to determine whether multicast joins are allowed, avoiding the race with device shutdown. Fixes: 344bacca8cd8 ("IB/ipoib: Don't allow MC joins during light MC flush") Reported-by: Ben Davies Signed-off-by: Carolina Jubran Reviewed-by: Cosmin Ratiu Signed-off-by: Edward Srouji Link: https://patch.msgid.link/20260902-avoid-rest-oper-up-v1-1-04fcd4916cae@nvidia.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/ipoib/ipoib.h | 7 +++++++ drivers/infiniband/ulp/ipoib/ipoib_ib.c | 12 +++++++----- drivers/infiniband/ulp/ipoib/ipoib_multicast.c | 16 ++++++++-------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/drivers/infiniband/ulp/ipoib/ipoib.h b/drivers/infiniband/ulp/ipoib/ipoib.h index 91f866e3fb8b..143e03b64902 100644 --- a/drivers/infiniband/ulp/ipoib/ipoib.h +++ b/drivers/infiniband/ulp/ipoib/ipoib.h @@ -87,6 +87,7 @@ enum { IPOIB_FLAG_INITIALIZED = 1, IPOIB_FLAG_ADMIN_UP = 2, IPOIB_PKEY_ASSIGNED = 3, + IPOIB_FLAG_MCAST_FLUSH = 4, IPOIB_FLAG_SUBINTERFACE = 5, IPOIB_STOP_REAPER = 7, IPOIB_FLAG_ADMIN_CM = 9, @@ -414,6 +415,12 @@ struct ipoib_dev_priv { const struct net_device_ops *rn_ops; }; +static inline bool ipoib_mcast_allowed(struct ipoib_dev_priv *priv) +{ + return test_bit(IPOIB_FLAG_OPER_UP, &priv->flags) && + !test_bit(IPOIB_FLAG_MCAST_FLUSH, &priv->flags); +} + struct ipoib_ah { struct net_device *dev; struct ib_ah *ah; diff --git a/drivers/infiniband/ulp/ipoib/ipoib_ib.c b/drivers/infiniband/ulp/ipoib/ipoib_ib.c index 5061d52a7b12..81bbb3f7c113 100644 --- a/drivers/infiniband/ulp/ipoib/ipoib_ib.c +++ b/drivers/infiniband/ulp/ipoib/ipoib_ib.c @@ -1227,17 +1227,19 @@ static void __ipoib_ib_dev_flush(struct ipoib_dev_priv *priv, } if (level == IPOIB_FLUSH_LIGHT) { - int oper_up; ipoib_mark_paths_invalid(dev); - /* Set IPoIB operation as down to prevent races between: + /* Set MCAST_FLUSH to prevent races between: * the flush flow which leaves MCG and on the fly joins * which can happen during that time. mcast restart task * should deal with join requests we missed. + * + * Do not clear OPER_UP for this; restoring it races with + * ipoib_ib_dev_down() and can leave OPER_UP set after the + * device is down. */ - oper_up = test_and_clear_bit(IPOIB_FLAG_OPER_UP, &priv->flags); + set_bit(IPOIB_FLAG_MCAST_FLUSH, &priv->flags); ipoib_mcast_dev_flush(dev); - if (oper_up) - set_bit(IPOIB_FLAG_OPER_UP, &priv->flags); + clear_bit(IPOIB_FLAG_MCAST_FLUSH, &priv->flags); ipoib_reap_dead_ahs(priv); } diff --git a/drivers/infiniband/ulp/ipoib/ipoib_multicast.c b/drivers/infiniband/ulp/ipoib/ipoib_multicast.c index 6401af2fd548..379b78374e21 100644 --- a/drivers/infiniband/ulp/ipoib/ipoib_multicast.c +++ b/drivers/infiniband/ulp/ipoib/ipoib_multicast.c @@ -74,7 +74,7 @@ static void __ipoib_mcast_schedule_join_thread(struct ipoib_dev_priv *priv, struct ipoib_mcast *mcast, bool delay) { - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + if (!ipoib_mcast_allowed(priv)) return; /* @@ -469,7 +469,7 @@ static int ipoib_mcast_join(struct net_device *dev, struct ipoib_mcast *mcast) int ret = 0; if (!priv->broadcast || - !test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + !ipoib_mcast_allowed(priv)) return -EINVAL; init_completion(&mcast->done); @@ -555,7 +555,7 @@ void ipoib_mcast_join_task(struct work_struct *work) unsigned long delay_until = 0; struct ipoib_mcast *mcast = NULL; - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + if (!ipoib_mcast_allowed(priv)) return; if (ib_query_port(priv->ca, priv->port, &port_attr)) { @@ -577,7 +577,7 @@ void ipoib_mcast_join_task(struct work_struct *work) netif_addr_unlock_bh(dev); spin_lock_irq(&priv->lock); - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + if (!ipoib_mcast_allowed(priv)) goto out; if (!priv->broadcast) { @@ -749,7 +749,7 @@ void ipoib_mcast_send(struct net_device *dev, u8 *daddr, struct sk_buff *skb) spin_lock_irqsave(&priv->lock, flags); - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags) || + if (!ipoib_mcast_allowed(priv) || !priv->broadcast || !test_bit(IPOIB_MCAST_FLAG_ATTACHED, &priv->broadcast->flags)) { ++dev->stats.tx_dropped; @@ -871,7 +871,7 @@ void ipoib_mcast_restart_task(struct work_struct *work) LIST_HEAD(remove_list); struct ib_sa_mcmember_rec rec; - if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) + if (!ipoib_mcast_allowed(priv)) /* * shortcut...on shutdown flush is called next, just * let it do all the work @@ -965,9 +965,9 @@ void ipoib_mcast_restart_task(struct work_struct *work) ipoib_mcast_remove_list(&remove_list); /* - * Double check that we are still up + * Double check that we are still up and not flushing */ - if (test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) { + if (ipoib_mcast_allowed(priv)) { spin_lock_irq(&priv->lock); __ipoib_mcast_schedule_join_thread(priv, NULL, 0); spin_unlock_irq(&priv->lock); From 402a9d6aab7ac787ab075adeb562c3db8b8f564b Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Thu, 3 Sep 2026 21:05:21 +0800 Subject: [PATCH 0964/2366] ALSA: caiaq: Decoupling ep1_in_urb in caiaq dev The epq_in_urb object belonging to the caiaq device is coupled within the struct snd_usb_caiaqdev. After usb_submit_urb(epq_in_urb, GFP_KERNEL) executes successfully, epq_in_urb is successfully added to the urbp_list queue of the dummy HCD driver (userspace specifies dummy_hcd as the HCD layer driver for the caiaq USB device). When init_card() calls snd_usb_caiaq_send_command() which subsequently fails due to a timeout, and proceeds to call snd_card_free() to release the card, the embedded ep1_in_urb object is also freed. When the dummy HCD driver detects that the URB has been unlinked, it returns the URB (by usb_hcd_giveback_urb()), which triggers [1]. Decouple the ep1_in_urb object from the struct snd_usb_caiaqdev and switch to using a pointer instead. Separately allocate and manage the memory for ep1_in_urb to prevent the release of the snd_card memory object from interfering with it. midi_out_urb has the same issue as ep1_in_urb and is handled in the same way. [1] BUG: KASAN: slab-use-after-free in usb_free_urb+0x24/0x120 drivers/usb/core/urb.c:96 Write of size 4 at addr ffff88803cee1050 by task ktimers/1/29 Call Trace: usb_free_urb+0x24/0x120 drivers/usb/core/urb.c:96 dummy_timer+0xaac/0x4d50 drivers/usb/gadget/udc/dummy_hcd.c:2019 __run_hrtimer kernel/time/hrtimer.c:2067 [inline] __hrtimer_run_queues+0x3eb/0xaf0 kernel/time/hrtimer.c:2124 hrtimer_run_softirq+0x1e1/0x2e0 kernel/time/hrtimer.c:2141 Allocated by task 36: snd_card_new+0x7b/0x110 sound/core/init.c:184 create_card sound/usb/caiaq/device.c:429 [inline] snd_probe+0x236/0x1af0 sound/usb/caiaq/device.c:544 Freed by task 36: snd_card_free_when_closed sound/core/init.c:630 [inline] snd_card_free+0x138/0x1d0 sound/core/init.c:662 snd_probe+0x162b/0x1af0 sound/usb/caiaq/device.c:553 Fixes: 523f1dce3743 ("[ALSA] Add Native Instrument usb audio device support") Reported-by: syzbot+832ce9fa3face1b7d44d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=832ce9fa3face1b7d44d Tested-by: syzbot+832ce9fa3face1b7d44d@syzkaller.appspotmail.com Signed-off-by: Edward Adam Davis Link: https://patch.msgid.link/20260903130521.554840-1-eadavis@sina.com Signed-off-by: Takashi Iwai --- sound/usb/caiaq/device.c | 36 ++++++++++++++++++++++++------------ sound/usb/caiaq/device.h | 4 ++-- sound/usb/caiaq/midi.c | 6 +++--- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index a16e59248480..3d821fde4582 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -192,8 +192,8 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) break; } - cdev->ep1_in_urb.actual_length = 0; - ret = usb_submit_urb(&cdev->ep1_in_urb, GFP_ATOMIC); + cdev->ep1_in_urb->actual_length = 0; + ret = usb_submit_urb(cdev->ep1_in_urb, GFP_ATOMIC); if (ret < 0) dev_err(dev, "unable to submit urb. OOM!?\n"); } @@ -408,6 +408,10 @@ static void card_free(struct snd_card *card) #endif snd_usb_caiaq_audio_free(cdev); usb_put_dev(cdev->chip.dev); + usb_free_urb(cdev->ep1_in_urb); + cdev->ep1_in_urb = NULL; + usb_free_urb(cdev->midi_out_urb); + cdev->midi_out_urb = NULL; } static int create_card(struct usb_device *usb_dev, @@ -457,22 +461,30 @@ static int init_card(struct snd_usb_caiaqdev *cdev) return -EIO; } - usb_init_urb(&cdev->ep1_in_urb); - usb_init_urb(&cdev->midi_out_urb); + cdev->ep1_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cdev->ep1_in_urb) + return -ENOMEM; - usb_fill_bulk_urb(&cdev->ep1_in_urb, usb_dev, + cdev->midi_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cdev->midi_out_urb) { + usb_free_urb(cdev->ep1_in_urb); + cdev->ep1_in_urb = NULL; + return -ENOMEM; + } + + usb_fill_bulk_urb(cdev->ep1_in_urb, usb_dev, usb_rcvbulkpipe(usb_dev, 0x1), cdev->ep1_in_buf, EP1_BUFSIZE, usb_ep1_command_reply_dispatch, cdev); - usb_fill_bulk_urb(&cdev->midi_out_urb, usb_dev, + usb_fill_bulk_urb(cdev->midi_out_urb, usb_dev, usb_sndbulkpipe(usb_dev, 0x1), cdev->midi_out_buf, EP1_BUFSIZE, snd_usb_caiaq_midi_output_done, cdev); /* sanity checks of EPs before actually submitting */ - if (usb_urb_ep_type_check(&cdev->ep1_in_urb) || - usb_urb_ep_type_check(&cdev->midi_out_urb)) { + if (usb_urb_ep_type_check(cdev->ep1_in_urb) || + usb_urb_ep_type_check(cdev->midi_out_urb)) { dev_err(dev, "invalid EPs\n"); return -EINVAL; } @@ -480,7 +492,7 @@ static int init_card(struct snd_usb_caiaqdev *cdev) init_waitqueue_head(&cdev->ep1_wait_queue); init_waitqueue_head(&cdev->prepare_wait_queue); - if (usb_submit_urb(&cdev->ep1_in_urb, GFP_KERNEL) != 0) + if (usb_submit_urb(cdev->ep1_in_urb, GFP_KERNEL) != 0) return -EIO; err = snd_usb_caiaq_send_command(cdev, EP1_CMD_GET_DEVICE_INFO, NULL, 0); @@ -530,7 +542,7 @@ static int init_card(struct snd_usb_caiaqdev *cdev) return 0; err_kill_urb: - usb_kill_urb(&cdev->ep1_in_urb); + usb_kill_urb(cdev->ep1_in_urb); return err; } @@ -576,8 +588,8 @@ static void snd_disconnect(struct usb_interface *intf) #endif snd_usb_caiaq_audio_disconnect(cdev); - usb_kill_urb(&cdev->ep1_in_urb); - usb_kill_urb(&cdev->midi_out_urb); + usb_kill_urb(cdev->ep1_in_urb); + usb_kill_urb(cdev->midi_out_urb); snd_card_free_when_closed(card); } diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index 743eb0387b5f..1c6f34693fa8 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -60,8 +60,8 @@ struct snd_usb_caiaq_cb_info; struct snd_usb_caiaqdev { struct snd_usb_audio chip; - struct urb ep1_in_urb; - struct urb midi_out_urb; + struct urb *ep1_in_urb; + struct urb *midi_out_urb; struct urb **data_urbs_in; struct urb **data_urbs_out; struct snd_usb_caiaq_cb_info *data_cb_info; diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c index c656d0162432..18529484c8dc 100644 --- a/sound/usb/caiaq/midi.c +++ b/sound/usb/caiaq/midi.c @@ -43,7 +43,7 @@ static int snd_usb_caiaq_midi_output_close(struct snd_rawmidi_substream *substre { struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data; if (cdev->midi_out_active) { - usb_kill_urb(&cdev->midi_out_urb); + usb_kill_urb(cdev->midi_out_urb); cdev->midi_out_active = 0; } return 0; @@ -64,9 +64,9 @@ static void snd_usb_caiaq_midi_send(struct snd_usb_caiaqdev *cdev, return; cdev->midi_out_buf[2] = len; - cdev->midi_out_urb.transfer_buffer_length = len+3; + cdev->midi_out_urb->transfer_buffer_length = len+3; - ret = usb_submit_urb(&cdev->midi_out_urb, GFP_ATOMIC); + ret = usb_submit_urb(cdev->midi_out_urb, GFP_ATOMIC); if (ret < 0) dev_err(dev, "snd_usb_caiaq_midi_send(%p): usb_submit_urb() failed," From 07b01b0d8ac4b5f89cbe74e52376221f21db260d Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 Sep 2026 18:04:37 +0200 Subject: [PATCH 0965/2366] ALSA: usb: ua101: Avoid embedded URBs UA101 driver uses URBs embedded in struct ua101, and this is basically a buggy implementation nowadays; since a URB is managed with a refcount, this may lead to a UAF when the URB is released asynchronously. For addressing the problem, this patch converts the embedded URBs to ones that are properly allocated via usb_alloc_urb(). The iso_frame_desc[] is gone, as it's allocated together by usb_alloc_urb(). Along with the dynamic allocation of each URB, the ua101.urbs[] becomes a static array of struct ua101_urb, and struct ua101_urb contains the pointer to struct ua101. Those are needed to handle the ready_list linked list in the complete callback. No functional changes, only compile-tested. Link: https://lore.kernel.org/20260903130757.0668310a.michal.pecio@gmail.com Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260903160458.1938392-2-tiwai@suse.de --- sound/usb/misc/ua101.c | 102 ++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c index b9a62e94e06c..860a62a3d74b 100644 --- a/sound/usb/misc/ua101.c +++ b/sound/usb/misc/ua101.c @@ -109,10 +109,10 @@ struct ua101 { unsigned int buffer_pos; unsigned int queue_length; struct ua101_urb { - struct urb urb; - struct usb_iso_packet_descriptor iso_frame_desc[1]; + struct urb *urb; struct list_head ready_list; - } *urbs[MAX_QUEUE_LENGTH]; + struct ua101 *ua; + } urbs[MAX_QUEUE_LENGTH]; struct { unsigned int size; void *addr; @@ -167,15 +167,15 @@ static void abort_usb_playback(struct ua101 *ua) wake_up(&ua->alsa_playback_wait); } -static void playback_urb_complete(struct urb *usb_urb) +static void playback_urb_complete(struct urb *urb) { - struct ua101_urb *urb = (struct ua101_urb *)usb_urb; - struct ua101 *ua = urb->urb.context; + struct ua101_urb *ua_urb = urb->context; + struct ua101 *ua = ua_urb->ua; - if (unlikely(urb->urb.status == -ENOENT || /* unlinked */ - urb->urb.status == -ENODEV || /* device removed */ - urb->urb.status == -ECONNRESET || /* unlinked */ - urb->urb.status == -ESHUTDOWN)) { /* device disabled */ + if (unlikely(urb->status == -ENOENT || /* unlinked */ + urb->status == -ENODEV || /* device removed */ + urb->status == -ECONNRESET || /* unlinked */ + urb->status == -ESHUTDOWN)) { /* device disabled */ abort_usb_playback(ua); abort_alsa_playback(ua); return; @@ -184,18 +184,19 @@ static void playback_urb_complete(struct urb *usb_urb) if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) { /* append URB to FIFO */ guard(spinlock_irqsave)(&ua->lock); - list_add_tail(&urb->ready_list, &ua->ready_playback_urbs); + list_add_tail(&ua_urb->ready_list, &ua->ready_playback_urbs); if (ua->rate_feedback_count > 0) queue_work(system_highpri_wq, &ua->playback_work); ua->playback.substream->runtime->delay -= - urb->urb.iso_frame_desc[0].length / + urb->iso_frame_desc[0].length / ua->playback.frame_bytes; } } static void first_playback_urb_complete(struct urb *urb) { - struct ua101 *ua = urb->context; + struct ua101_urb *ua_urb = urb->context; + struct ua101 *ua = ua_urb->ua; urb->complete = playback_urb_complete; playback_urb_complete(urb); @@ -248,7 +249,8 @@ static void playback_work(struct work_struct *work) { struct ua101 *ua = container_of(work, struct ua101, playback_work); unsigned int frames; - struct ua101_urb *urb; + struct ua101_urb *ua_urb; + struct urb *urb; bool do_period_elapsed = false; int err; @@ -275,23 +277,24 @@ static void playback_work(struct work_struct *work) ua->rate_feedback_count--; /* take URB out of FIFO */ - urb = list_first_entry(&ua->ready_playback_urbs, - struct ua101_urb, ready_list); - list_del(&urb->ready_list); + ua_urb = list_first_entry(&ua->ready_playback_urbs, + struct ua101_urb, ready_list); + list_del(&ua_urb->ready_list); + urb = ua_urb->urb; /* fill packet with data or silence */ - urb->urb.iso_frame_desc[0].length = + urb->iso_frame_desc[0].length = frames * ua->playback.frame_bytes; if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) do_period_elapsed |= copy_playback_data(&ua->playback, - &urb->urb, + urb, frames); else - memset(urb->urb.transfer_buffer, 0, - urb->urb.iso_frame_desc[0].length); + memset(urb->transfer_buffer, 0, + urb->iso_frame_desc[0].length); /* and off you go ... */ - err = usb_submit_urb(&urb->urb, GFP_ATOMIC); + err = usb_submit_urb(urb, GFP_ATOMIC); if (unlikely(err < 0)) { abort_usb_playback(ua); abort_alsa_playback(ua); @@ -342,7 +345,8 @@ static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb, static void capture_urb_complete(struct urb *urb) { - struct ua101 *ua = urb->context; + struct ua101_urb *ua_urb = urb->context; + struct ua101 *ua = ua_urb->ua; struct ua101_stream *stream = &ua->capture; unsigned int frames, write_ptr; bool do_period_elapsed; @@ -413,7 +417,8 @@ static void capture_urb_complete(struct urb *urb) static void first_capture_urb_complete(struct urb *urb) { - struct ua101 *ua = urb->context; + struct ua101_urb *ua_urb = urb->context; + struct ua101 *ua = ua_urb->ua; urb->complete = capture_urb_complete; capture_urb_complete(urb); @@ -427,7 +432,7 @@ static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream) unsigned int i; for (i = 0; i < stream->queue_length; ++i) { - int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL); + int err = usb_submit_urb(stream->urbs[i].urb, GFP_KERNEL); if (err < 0) { dev_err(&ua->dev->dev, "USB request error %d: %s\n", err, usb_error_string(err)); @@ -442,8 +447,8 @@ static void kill_stream_urbs(struct ua101_stream *stream) unsigned int i; for (i = 0; i < stream->queue_length; ++i) - if (stream->urbs[i]) - usb_kill_urb(&stream->urbs[i]->urb); + if (stream->urbs[i].urb) + usb_kill_urb(stream->urbs[i].urb); } static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index) @@ -508,7 +513,7 @@ static int start_usb_capture(struct ua101 *ua) return err; clear_bit(CAPTURE_URB_COMPLETED, &ua->states); - ua->capture.urbs[0]->urb.complete = first_capture_urb_complete; + ua->capture.urbs[0].urb->complete = first_capture_urb_complete; ua->rate_feedback_start = 0; ua->rate_feedback_count = 0; @@ -550,7 +555,7 @@ static int start_usb_playback(struct ua101 *ua) return err; clear_bit(PLAYBACK_URB_COMPLETED, &ua->states); - ua->playback.urbs[0]->urb.complete = + ua->playback.urbs[0].urb->complete = first_playback_urb_complete; scoped_guard(spinlock_irq, &ua->lock) { INIT_LIST_HEAD(&ua->ready_playback_urbs); @@ -580,7 +585,7 @@ static int start_usb_playback(struct ua101 *ua) add_with_wraparound(ua, &ua->rate_feedback_start, 1); ua->rate_feedback_count--; } - urb = &ua->playback.urbs[i]->urb; + urb = ua->playback.urbs[i].urb; urb->iso_frame_desc[0].length = frames * ua->playback.frame_bytes; memset(urb->transfer_buffer, 0, @@ -1059,7 +1064,7 @@ static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, void (*urb_complete)(struct urb *)) { unsigned max_packet_size = stream->max_packet_bytes; - struct ua101_urb *urb; + struct urb *urb; unsigned int b, u = 0; for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) { @@ -1070,23 +1075,24 @@ static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, while (size >= max_packet_size) { if (u >= stream->queue_length) goto bufsize_error; - urb = kmalloc_obj(*urb); + urb = usb_alloc_urb(1, GFP_KERNEL); if (!urb) return -ENOMEM; - usb_init_urb(&urb->urb); - urb->urb.dev = ua->dev; - urb->urb.pipe = stream->usb_pipe; - urb->urb.transfer_flags = URB_NO_TRANSFER_DMA_MAP; - urb->urb.transfer_buffer = addr; - urb->urb.transfer_dma = dma; - urb->urb.transfer_buffer_length = max_packet_size; - urb->urb.number_of_packets = 1; - urb->urb.interval = 1; - urb->urb.context = ua; - urb->urb.complete = urb_complete; - urb->urb.iso_frame_desc[0].offset = 0; - urb->urb.iso_frame_desc[0].length = max_packet_size; - stream->urbs[u++] = urb; + urb->dev = ua->dev; + urb->pipe = stream->usb_pipe; + urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer = addr; + urb->transfer_dma = dma; + urb->transfer_buffer_length = max_packet_size; + urb->number_of_packets = 1; + urb->interval = 1; + urb->context = &stream->urbs[u]; + urb->complete = urb_complete; + urb->iso_frame_desc[0].offset = 0; + urb->iso_frame_desc[0].length = max_packet_size; + stream->urbs[u].ua = ua; + stream->urbs[u].urb = urb; + u++; size -= max_packet_size; addr += max_packet_size; dma += max_packet_size; @@ -1104,8 +1110,8 @@ static void free_stream_urbs(struct ua101_stream *stream) unsigned int i; for (i = 0; i < stream->queue_length; ++i) { - kfree(stream->urbs[i]); - stream->urbs[i] = NULL; + usb_free_urb(stream->urbs[i].urb); + stream->urbs[i].urb = NULL; } } From 7a8e247dab54a42aeec0240153c55da286ac5aef Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 Sep 2026 18:04:38 +0200 Subject: [PATCH 0966/2366] ALSA: usb: hiface: Avoid embedded URBs The hiface driver uses URBs embedded in struct pcm_urb, and this is basically a buggy implementation nowadays; since a URB is managed with a refcount, this may lead to a UAF when the URB is released asynchronously. For addressing the problem, this patch converts the embedded URBs to ones that are properly allocated via usb_alloc_urb(). The conversion is rather straightforward; pcm_urb.instance became a pointer, assigned/freed via usb_alloc_urb() and usb_free_urb(), and the call with this is corrected accordingly. Along with it, the resource release is done in the common destructor that is called from both at the error path and the disconnect. No functional changes, only compile-tested. Link: https://lore.kernel.org/20260903130757.0668310a.michal.pecio@gmail.com Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260903160458.1938392-3-tiwai@suse.de --- sound/usb/hiface/pcm.c | 44 ++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c index cd1a4c871c5d..3157952e4c1d 100644 --- a/sound/usb/hiface/pcm.c +++ b/sound/usb/hiface/pcm.c @@ -24,7 +24,7 @@ struct pcm_urb { struct hiface_chip *chip; - struct urb instance; + struct urb *instance; struct usb_anchor submitted; u8 *buffer; }; @@ -193,7 +193,7 @@ static void hiface_pcm_stream_stop(struct pcm_runtime *rt) if (!time) usb_kill_anchored_urbs( &rt->out_urbs[i].submitted); - usb_kill_urb(&rt->out_urbs[i].instance); + usb_kill_urb(rt->out_urbs[i].instance); } rt->stream_state = STREAM_DISABLED; @@ -215,9 +215,9 @@ static int hiface_pcm_stream_start(struct pcm_runtime *rt) rt->stream_state = STREAM_STARTING; for (i = 0; i < PCM_N_URBS; i++) { memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE); - usb_anchor_urb(&rt->out_urbs[i].instance, + usb_anchor_urb(rt->out_urbs[i].instance, &rt->out_urbs[i].submitted); - ret = usb_submit_urb(&rt->out_urbs[i].instance, + ret = usb_submit_urb(rt->out_urbs[i].instance, GFP_ATOMIC); if (ret) { hiface_pcm_stream_stop(rt); @@ -334,7 +334,7 @@ static void hiface_pcm_out_urb_handler(struct urb *usb_urb) if (do_period_elapsed) snd_pcm_period_elapsed(sub->instance); - ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC); + ret = usb_submit_urb(out_urb->instance, GFP_ATOMIC); if (ret < 0) goto out_fail; @@ -492,16 +492,18 @@ static int hiface_pcm_init_urb(struct pcm_urb *urb, void (*handler)(struct urb *)) { urb->chip = chip; - usb_init_urb(&urb->instance); + urb->instance = usb_alloc_urb(0, GFP_KERNEL); + if (!urb->instance) + return -ENOMEM; urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL); if (!urb->buffer) return -ENOMEM; - usb_fill_bulk_urb(&urb->instance, chip->dev, + usb_fill_bulk_urb(urb->instance, chip->dev, usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer, PCM_PACKET_SIZE, handler, urb); - if (usb_urb_ep_type_check(&urb->instance)) + if (usb_urb_ep_type_check(urb->instance)) return -EINVAL; init_usb_anchor(&urb->submitted); @@ -520,24 +522,26 @@ void hiface_pcm_abort(struct hiface_chip *chip) } } -static void hiface_pcm_destroy(struct hiface_chip *chip) +static void hiface_pcm_destroy(struct pcm_runtime *rt) { - struct pcm_runtime *rt = chip->pcm; int i; - for (i = 0; i < PCM_N_URBS; i++) - kfree(rt->out_urbs[i].buffer); + if (!rt) + return; - kfree(chip->pcm); - chip->pcm = NULL; + if (rt->chip) + rt->chip->pcm = NULL; + + for (i = 0; i < PCM_N_URBS; i++) { + usb_free_urb(rt->out_urbs[i].instance); + kfree(rt->out_urbs[i].buffer); + } + kfree(rt); } static void hiface_pcm_free(struct snd_pcm *pcm) { - struct pcm_runtime *rt = pcm->private_data; - - if (rt) - hiface_pcm_destroy(rt->chip); + hiface_pcm_destroy(pcm->private_data); } int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) @@ -587,8 +591,6 @@ int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) return 0; error: - for (i = 0; i < PCM_N_URBS; i++) - kfree(rt->out_urbs[i].buffer); - kfree(rt); + hiface_pcm_destroy(rt); return ret; } From 9fe49dbc023e82dfaee7b245997d820d01742a9a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 3 Sep 2026 18:04:39 +0200 Subject: [PATCH 0967/2366] ALSA: usb: 6fire: Avoid embedded URBs The USB 6fire driver uses URBs embedded in different structs for PCM, MIDI and communication, and this is basically a buggy implementation nowadays; since a URB is managed with a refcount, this may lead to a UAF when the URB is released asynchronously. For addressing the problem, this patch converts those embedded URBs to ones that are properly allocated via usb_alloc_urb(). The pcm_urb.packets[] is gone, as it's allocated by usb_alloc_urb(), hence it's found in urb.iso_frame_desc[] instead. The conversions are rather straightforward; each embedded struct urb is changed to a pointer, and its callers are updated accordingly. The resource for those structs are released in the common destructor functions (usb6fire_comm_free(), etc), which are called at both the init error path and the disconnect. No functional changes, only compile-tested. Link: https://lore.kernel.org/20260903130757.0668310a.michal.pecio@gmail.com Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260903160458.1938392-4-tiwai@suse.de --- sound/usb/6fire/comm.c | 42 +++++++++----- sound/usb/6fire/comm.h | 2 +- sound/usb/6fire/midi.c | 43 +++++++++----- sound/usb/6fire/midi.h | 2 +- sound/usb/6fire/pcm.c | 128 ++++++++++++++++++++++++----------------- sound/usb/6fire/pcm.h | 5 +- 6 files changed, 136 insertions(+), 86 deletions(-) diff --git a/sound/usb/6fire/comm.c b/sound/usb/6fire/comm.c index d3b7cab85699..510d310824e9 100644 --- a/sound/usb/6fire/comm.c +++ b/sound/usb/6fire/comm.c @@ -21,7 +21,6 @@ enum { static void usb6fire_comm_init_urb(struct comm_runtime *rt, struct urb *urb, u8 *buffer, void *context, void(*handler)(struct urb *urb)) { - usb_init_urb(urb); urb->transfer_buffer = buffer; urb->pipe = usb_sndintpipe(rt->chip->dev, COMM_EP); urb->complete = handler; @@ -142,6 +141,19 @@ static int usb6fire_comm_write16(struct comm_runtime *rt, u8 request, return ret; } +static void usb6fire_comm_free(struct comm_runtime *rt) +{ + if (!rt) + return; + + if (rt->chip) + rt->chip->comm = NULL; + + usb_free_urb(rt->receiver); + kfree(rt->receiver_buffer); + kfree(rt); +} + int usb6fire_comm_init(struct sfire_chip *chip) { struct comm_runtime *rt = kzalloc_obj(struct comm_runtime); @@ -153,14 +165,18 @@ int usb6fire_comm_init(struct sfire_chip *chip) rt->receiver_buffer = kzalloc(COMM_RECEIVER_BUFSIZE, GFP_KERNEL); if (!rt->receiver_buffer) { - kfree(rt); - return -ENOMEM; + ret = -ENOMEM; + goto error; } - urb = &rt->receiver; + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + ret = -ENOMEM; + goto error; + } + rt->receiver = urb; rt->serial = 1; rt->chip = chip; - usb_init_urb(urb); rt->init_urb = usb6fire_comm_init_urb; rt->write8 = usb6fire_comm_write8; rt->write16 = usb6fire_comm_write16; @@ -175,13 +191,15 @@ int usb6fire_comm_init(struct sfire_chip *chip) urb->interval = 1; ret = usb_submit_urb(urb, GFP_KERNEL); if (ret < 0) { - kfree(rt->receiver_buffer); - kfree(rt); dev_err(&chip->dev->dev, "cannot create comm data receiver."); - return ret; + goto error; } chip->comm = rt; return 0; + + error: + usb6fire_comm_free(rt); + return ret; } void usb6fire_comm_abort(struct sfire_chip *chip) @@ -189,14 +207,10 @@ void usb6fire_comm_abort(struct sfire_chip *chip) struct comm_runtime *rt = chip->comm; if (rt) - usb_poison_urb(&rt->receiver); + usb_poison_urb(rt->receiver); } void usb6fire_comm_destroy(struct sfire_chip *chip) { - struct comm_runtime *rt = chip->comm; - - kfree(rt->receiver_buffer); - kfree(rt); - chip->comm = NULL; + usb6fire_comm_free(chip->comm); } diff --git a/sound/usb/6fire/comm.h b/sound/usb/6fire/comm.h index 2447d7ecf179..89976f510f6c 100644 --- a/sound/usb/6fire/comm.h +++ b/sound/usb/6fire/comm.h @@ -19,7 +19,7 @@ enum /* settings for comm */ struct comm_runtime { struct sfire_chip *chip; - struct urb receiver; + struct urb *receiver; u8 *receiver_buffer; u8 serial; /* urb serial */ diff --git a/sound/usb/6fire/midi.c b/sound/usb/6fire/midi.c index 6b0bb096f27a..279b449936e7 100644 --- a/sound/usb/6fire/midi.c +++ b/sound/usb/6fire/midi.c @@ -66,7 +66,7 @@ static void usb6fire_midi_out_trigger( struct snd_rawmidi_substream *alsa_sub, int up) { struct midi_runtime *rt = alsa_sub->rmidi->private_data; - struct urb *urb = &rt->out_urb; + struct urb *urb = rt->out_urb; __s8 ret; guard(spinlock_irqsave)(&rt->out_lock); @@ -137,6 +137,19 @@ static const struct snd_rawmidi_ops in_ops = { .trigger = usb6fire_midi_in_trigger }; +static void usb6fire_midi_free(struct midi_runtime *rt) +{ + if (!rt) + return; + + if (rt->chip) + rt->chip->midi = NULL; + + usb_free_urb(rt->out_urb); + kfree(rt->out_buffer); + kfree(rt); +} + int usb6fire_midi_init(struct sfire_chip *chip) { int ret; @@ -148,8 +161,14 @@ int usb6fire_midi_init(struct sfire_chip *chip) rt->out_buffer = kzalloc(MIDI_BUFSIZE, GFP_KERNEL); if (!rt->out_buffer) { - kfree(rt); - return -ENOMEM; + ret = -ENOMEM; + goto error; + } + + rt->out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!rt->out_urb) { + ret = -ENOMEM; + goto error; } rt->chip = chip; @@ -160,15 +179,13 @@ int usb6fire_midi_init(struct sfire_chip *chip) spin_lock_init(&rt->in_lock); spin_lock_init(&rt->out_lock); - comm_rt->init_urb(comm_rt, &rt->out_urb, rt->out_buffer, rt, + comm_rt->init_urb(comm_rt, rt->out_urb, rt->out_buffer, rt, usb6fire_midi_out_handler); ret = snd_rawmidi_new(chip->card, "6FireUSB", 0, 1, 1, &rt->instance); if (ret < 0) { - kfree(rt->out_buffer); - kfree(rt); dev_err(&chip->dev->dev, "unable to create midi.\n"); - return ret; + goto error; } rt->instance->private_data = rt; strscpy(rt->instance->name, "DMX6FireUSB MIDI"); @@ -182,6 +199,10 @@ int usb6fire_midi_init(struct sfire_chip *chip) chip->midi = rt; return 0; + + error: + usb6fire_midi_free(rt); + return ret; } void usb6fire_midi_abort(struct sfire_chip *chip) @@ -189,14 +210,10 @@ void usb6fire_midi_abort(struct sfire_chip *chip) struct midi_runtime *rt = chip->midi; if (rt) - usb_poison_urb(&rt->out_urb); + usb_poison_urb(rt->out_urb); } void usb6fire_midi_destroy(struct sfire_chip *chip) { - struct midi_runtime *rt = chip->midi; - - kfree(rt->out_buffer); - kfree(rt); - chip->midi = NULL; + usb6fire_midi_free(chip->midi); } diff --git a/sound/usb/6fire/midi.h b/sound/usb/6fire/midi.h index 47640c845903..8716ab8a863a 100644 --- a/sound/usb/6fire/midi.h +++ b/sound/usb/6fire/midi.h @@ -22,7 +22,7 @@ struct midi_runtime { spinlock_t in_lock; spinlock_t out_lock; struct snd_rawmidi_substream *out; - struct urb out_urb; + struct urb *out_urb; u8 out_serial; /* serial number of out packet */ u8 *out_buffer; int buffer_offset; diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c index d2e274b731fe..21789db6657d 100644 --- a/sound/usb/6fire/pcm.c +++ b/sound/usb/6fire/pcm.c @@ -138,8 +138,8 @@ static void usb6fire_pcm_stream_stop(struct pcm_runtime *rt) rt->stream_state = STREAM_STOPPING; for (i = 0; i < PCM_N_URBS; i++) { - usb_kill_urb(&rt->in_urbs[i].instance); - usb_kill_urb(&rt->out_urbs[i].instance); + usb_kill_urb(rt->in_urbs[i].instance); + usb_kill_urb(rt->out_urbs[i].instance); } ctrl_rt->usb_streaming = false; ctrl_rt->update_streaming(ctrl_rt); @@ -161,13 +161,13 @@ static int usb6fire_pcm_stream_start(struct pcm_runtime *rt) rt->stream_state = STREAM_STARTING; for (i = 0; i < PCM_N_URBS; i++) { for (k = 0; k < PCM_N_PACKETS_PER_URB; k++) { - packet = &rt->in_urbs[i].packets[k]; + packet = &rt->in_urbs[i].instance->iso_frame_desc[k]; packet->offset = k * rt->in_packet_size; packet->length = rt->in_packet_size; packet->actual_length = 0; packet->status = 0; } - ret = usb_submit_urb(&rt->in_urbs[i].instance, + ret = usb_submit_urb(rt->in_urbs[i].instance, GFP_ATOMIC); if (ret) { usb6fire_pcm_stream_stop(rt); @@ -197,6 +197,7 @@ static void usb6fire_pcm_capture(struct pcm_substream *sub, struct pcm_urb *urb) unsigned int total_length = 0; struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance); struct snd_pcm_runtime *alsa_rt = sub->instance->runtime; + struct usb_iso_packet_descriptor *isoc; u32 *src = NULL; u32 *dest = (u32 *) (alsa_rt->dma_area + sub->dma_off * (alsa_rt->frame_bits >> 3)); @@ -207,8 +208,9 @@ static void usb6fire_pcm_capture(struct pcm_substream *sub, struct pcm_urb *urb) for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { /* at least 4 header bytes for valid packet. * after that: 32 bits per sample for analog channels */ - if (urb->packets[i].actual_length > 4) - frame_count = (urb->packets[i].actual_length - 4) + isoc = &urb->instance->iso_frame_desc[i]; + if (isoc->actual_length > 4) + frame_count = (isoc->actual_length - 4) / (rt->in_n_analog << 2); else frame_count = 0; @@ -220,7 +222,7 @@ static void usb6fire_pcm_capture(struct pcm_substream *sub, struct pcm_urb *urb) else return; src++; /* skip leading 4 bytes of every packet */ - total_length += urb->packets[i].length; + total_length += isoc->length; for (frame = 0; frame < frame_count; frame++) { memcpy(dest, src, bytes_per_frame); dest += alsa_rt->channels; @@ -244,6 +246,7 @@ static void usb6fire_pcm_playback(struct pcm_substream *sub, int frame_count; struct pcm_runtime *rt = snd_pcm_substream_chip(sub->instance); struct snd_pcm_runtime *alsa_rt = sub->instance->runtime; + struct usb_iso_packet_descriptor *isoc; u32 *src = (u32 *) (alsa_rt->dma_area + sub->dma_off * (alsa_rt->frame_bits >> 3)); u32 *src_end = (u32 *) (alsa_rt->dma_area + alsa_rt->buffer_size @@ -263,8 +266,9 @@ static void usb6fire_pcm_playback(struct pcm_substream *sub, for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { /* at least 4 header bytes for valid packet. * after that: 32 bits per sample for analog channels */ - if (urb->packets[i].length > 4) - frame_count = (urb->packets[i].length - 4) + isoc = &urb->instance->iso_frame_desc[i]; + if (isoc->length > 4) + frame_count = (isoc->length - 4) / (rt->out_n_analog << 2); else frame_count = 0; @@ -289,6 +293,7 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) struct pcm_urb *out_urb = in_urb->peer; struct pcm_runtime *rt = in_urb->chip->pcm; struct pcm_substream *sub; + struct usb_iso_packet_descriptor *isoc_out, *isoc_in; bool period_elapsed; int total_length = 0; int frame_count; @@ -299,11 +304,13 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING) return; - for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) - if (in_urb->packets[i].status) { + for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { + isoc_in = &in_urb->instance->iso_frame_desc[i]; + if (isoc_in->status) { rt->panic = true; return; } + } if (rt->stream_state == STREAM_DISABLED) { dev_err(&rt->chip->dev->dev, @@ -328,12 +335,13 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) /* setup out urb structure */ for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { - out_urb->packets[i].offset = total_length; - out_urb->packets[i].length = (in_urb->packets[i].actual_length - - 4) / (rt->in_n_analog << 2) + isoc_out = &out_urb->instance->iso_frame_desc[i]; + isoc_in = &in_urb->instance->iso_frame_desc[i]; + isoc_out->offset = total_length; + isoc_out->length = (isoc_in->actual_length - 4) / (rt->in_n_analog << 2) * (rt->out_n_analog << 2) + 4; - out_urb->packets[i].status = 0; - total_length += out_urb->packets[i].length; + isoc_out->status = 0; + total_length += isoc_out->length; } memset(out_urb->buffer, 0, total_length); @@ -354,9 +362,10 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) /* setup the 4th byte of each sample (0x40 for analog channels) */ dest = out_urb->buffer; - for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) - if (out_urb->packets[i].length >= 4) { - frame_count = (out_urb->packets[i].length - 4) + for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { + isoc_out = &out_urb->instance->iso_frame_desc[i]; + if (isoc_out->length >= 4) { + frame_count = (isoc_out->length - 4) / (rt->out_n_analog << 2); *(dest++) = 0xaa; *(dest++) = 0xaa; @@ -370,8 +379,10 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) *(dest++) = 0x40; } } - usb_submit_urb(&out_urb->instance, GFP_ATOMIC); - usb_submit_urb(&in_urb->instance, GFP_ATOMIC); + } + + usb_submit_urb(out_urb->instance, GFP_ATOMIC); + usb_submit_urb(in_urb->instance, GFP_ATOMIC); } static void usb6fire_pcm_out_urb_handler(struct urb *usb_urb) @@ -534,22 +545,25 @@ static const struct snd_pcm_ops pcm_ops = { .pointer = usb6fire_pcm_pointer, }; -static void usb6fire_pcm_init_urb(struct pcm_urb *urb, - struct sfire_chip *chip, bool in, int ep, - void (*handler)(struct urb *)) +static int usb6fire_pcm_init_urb(struct pcm_urb *urb, + struct sfire_chip *chip, bool in, int ep, + void (*handler)(struct urb *)) { urb->chip = chip; - usb_init_urb(&urb->instance); - urb->instance.transfer_buffer = urb->buffer; - urb->instance.transfer_buffer_length = + urb->instance = usb_alloc_urb(PCM_N_PACKETS_PER_URB, GFP_KERNEL); + if (!urb->instance) + return -ENOMEM; + urb->instance->transfer_buffer = urb->buffer; + urb->instance->transfer_buffer_length = PCM_N_PACKETS_PER_URB * PCM_MAX_PACKET_SIZE; - urb->instance.dev = chip->dev; - urb->instance.pipe = in ? usb_rcvisocpipe(chip->dev, ep) + urb->instance->dev = chip->dev; + urb->instance->pipe = in ? usb_rcvisocpipe(chip->dev, ep) : usb_sndisocpipe(chip->dev, ep); - urb->instance.interval = 1; - urb->instance.complete = handler; - urb->instance.context = urb; - urb->instance.number_of_packets = PCM_N_PACKETS_PER_URB; + urb->instance->interval = 1; + urb->instance->complete = handler; + urb->instance->context = urb; + urb->instance->number_of_packets = PCM_N_PACKETS_PER_URB; + return 0; } static int usb6fire_pcm_buffers_init(struct pcm_runtime *rt) @@ -571,14 +585,23 @@ static int usb6fire_pcm_buffers_init(struct pcm_runtime *rt) return 0; } -static void usb6fire_pcm_buffers_destroy(struct pcm_runtime *rt) +static void usb6fire_pcm_free(struct pcm_runtime *rt) { int i; + if (!rt) + return; + + if (rt->chip) + rt->chip->pcm = NULL; + for (i = 0; i < PCM_N_URBS; i++) { + usb_free_urb(rt->out_urbs[i].instance); kfree(rt->out_urbs[i].buffer); + usb_free_urb(rt->in_urbs[i].instance); kfree(rt->in_urbs[i].buffer); } + kfree(rt); } int usb6fire_pcm_init(struct sfire_chip *chip) @@ -593,11 +616,8 @@ int usb6fire_pcm_init(struct sfire_chip *chip) return -ENOMEM; ret = usb6fire_pcm_buffers_init(rt); - if (ret) { - usb6fire_pcm_buffers_destroy(rt); - kfree(rt); - return ret; - } + if (ret) + goto error; rt->chip = chip; rt->stream_state = STREAM_DISABLED; @@ -609,10 +629,14 @@ int usb6fire_pcm_init(struct sfire_chip *chip) spin_lock_init(&rt->capture.lock); for (i = 0; i < PCM_N_URBS; i++) { - usb6fire_pcm_init_urb(&rt->in_urbs[i], chip, true, IN_EP, - usb6fire_pcm_in_urb_handler); - usb6fire_pcm_init_urb(&rt->out_urbs[i], chip, false, OUT_EP, - usb6fire_pcm_out_urb_handler); + ret = usb6fire_pcm_init_urb(&rt->in_urbs[i], chip, true, IN_EP, + usb6fire_pcm_in_urb_handler); + if (ret < 0) + goto error; + ret = usb6fire_pcm_init_urb(&rt->out_urbs[i], chip, false, OUT_EP, + usb6fire_pcm_out_urb_handler); + if (ret < 0) + goto error; rt->in_urbs[i].peer = &rt->out_urbs[i]; rt->out_urbs[i].peer = &rt->in_urbs[i]; @@ -620,10 +644,8 @@ int usb6fire_pcm_init(struct sfire_chip *chip) ret = snd_pcm_new(chip->card, "DMX6FireUSB", 0, 1, 1, &pcm); if (ret < 0) { - usb6fire_pcm_buffers_destroy(rt); - kfree(rt); dev_err(&chip->dev->dev, "cannot create pcm instance.\n"); - return ret; + goto error; } pcm->private_data = rt; @@ -636,6 +658,10 @@ int usb6fire_pcm_init(struct sfire_chip *chip) chip->pcm = rt; return 0; + + error: + usb6fire_pcm_free(rt); + return ret; } void usb6fire_pcm_abort(struct sfire_chip *chip) @@ -653,8 +679,8 @@ void usb6fire_pcm_abort(struct sfire_chip *chip) snd_pcm_stop_xrun(rt->capture.instance); for (i = 0; i < PCM_N_URBS; i++) { - usb_poison_urb(&rt->in_urbs[i].instance); - usb_poison_urb(&rt->out_urbs[i].instance); + usb_poison_urb(rt->in_urbs[i].instance); + usb_poison_urb(rt->out_urbs[i].instance); } } @@ -662,9 +688,5 @@ void usb6fire_pcm_abort(struct sfire_chip *chip) void usb6fire_pcm_destroy(struct sfire_chip *chip) { - struct pcm_runtime *rt = chip->pcm; - - usb6fire_pcm_buffers_destroy(rt); - kfree(rt); - chip->pcm = NULL; + usb6fire_pcm_free(chip->pcm); } diff --git a/sound/usb/6fire/pcm.h b/sound/usb/6fire/pcm.h index 5a092dfd69f5..b586fe220fd1 100644 --- a/sound/usb/6fire/pcm.h +++ b/sound/usb/6fire/pcm.h @@ -24,10 +24,7 @@ enum /* settings for pcm */ struct pcm_urb { struct sfire_chip *chip; - /* BEGIN DO NOT SEPARATE */ - struct urb instance; - struct usb_iso_packet_descriptor packets[PCM_N_PACKETS_PER_URB]; - /* END DO NOT SEPARATE */ + struct urb *instance; u8 *buffer; struct pcm_urb *peer; From b26a7a80e6bbf8dd17dacb127d12435d79375cf2 Mon Sep 17 00:00:00 2001 From: Roman Prucha Date: Thu, 3 Sep 2026 23:14:44 +0200 Subject: [PATCH 0968/2366] ALSA: ctxfi: Fix CA20K2 S/PDIF passthrough dao_rsc_init() encodes the DAIO configuration as conf = (desc->msr & 0x7) | (desc->passthru << 3); S/PDIF passthrough uses msr=1 and passthru=1, resulting in conf=9. daio_mgr_dao_init() masks conf with 0xf, but handles only values 1, 2, 4 and 8 when programming ATXCTL_NUC. As a result, conf=9 falls through to the default case and leaves NUC at its previous setting. On a Creative X-Fi Titanium HD SB1270 (CA20K2), this breaks AC3 IEC61937 passthrough when snd_ctxfi runs with reference_rate=48000,multiple=2. The receiver detects a non-audio stream but cannot decode the AC3 payload. With the unmodified driver, multiple=1 makes the same stream work. Handle conf=9 through the same NUC=0 path as conf=1. The change was runtime tested on the SB1270 with multiple=2 using IEC958 stereo PCM, pre-encoded AC3 IEC61937 passthrough and ALSA A52 live 5.1 encoding. Fixes: 26a9630c72eb ("ALSA: ctxfi: cthw20k2: fix mask on conf to allow 4 bits") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Roman Prucha Link: https://patch.msgid.link/20260903-ctxfi-spdif-conf9-fix-v1-1-5e4e3e1f801c@gmail.com Signed-off-by: Takashi Iwai --- sound/pci/ctxfi/cthw20k2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/pci/ctxfi/cthw20k2.c b/sound/pci/ctxfi/cthw20k2.c index 07e1490a6d17..36066ffe8784 100644 --- a/sound/pci/ctxfi/cthw20k2.c +++ b/sound/pci/ctxfi/cthw20k2.c @@ -994,6 +994,7 @@ static int daio_mgr_dao_init(struct hw *hw, void *blk, unsigned int idx, unsigne /* S/PDIF output */ switch ((conf & 0xf)) { case 1: + case 9: set_field(&ctl->txctl[idx], ATXCTL_NUC, 0); break; case 2: From 861111a14740e12c36d363e9830f8daa734279c9 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Fri, 4 Sep 2026 20:58:25 +0000 Subject: [PATCH 0969/2366] ALSA: usbusx2y: fix in04_last array size mismatch with in04_buf The in04_last array in struct usx2ydev is declared as char[24], but in04_buf is allocated as sizeof(struct us428_ctls) which is 21 bytes. In i_usx2y_in04_int(), when ctl_snapshot_last == -2 (initialization path): memcpy(usx2y->in04_last, usx2y->in04_buf, sizeof(usx2y->in04_last)); This copies 24 bytes from a 21-byte slab allocation, reading 3 bytes past the end of the source object. Introduce a USX2Y_IN04_SIZE constant defined as sizeof(struct us428_ctls) and use it consistently for the in04_last array, the in04_buf allocation, the URB transfer length, and the comparison loop, replacing the bare 24 and 21 literals throughout. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani Link: https://patch.msgid.link/20260904205826.4071119-1-tristmd@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/usx2y/usbusx2y.c | 6 +++--- sound/usb/usx2y/usbusx2y.h | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sound/usb/usx2y/usbusx2y.c b/sound/usb/usx2y/usbusx2y.c index 4190227c5a2a..200b708aacd6 100644 --- a/sound/usb/usx2y/usbusx2y.c +++ b/sound/usb/usx2y/usbusx2y.c @@ -196,7 +196,7 @@ static void i_usx2y_in04_int(struct urb *urb) memcpy(usx2y->in04_last, usx2y->in04_buf, sizeof(usx2y->in04_last)); us428ctls->ctl_snapshot_last = -1; } else { - for (i = 0; i < 21; i++) { + for (i = 0; i < USX2Y_IN04_SIZE; i++) { if (usx2y->in04_last[i] != ((char *)usx2y->in04_buf)[i]) { if (diff < 0) diff = i; @@ -305,7 +305,7 @@ int usx2y_in04_init(struct usx2ydev *usx2y) goto error; } - usx2y->in04_buf = kmalloc(21, GFP_KERNEL); + usx2y->in04_buf = kmalloc(USX2Y_IN04_SIZE, GFP_KERNEL); if (!usx2y->in04_buf) { err = -ENOMEM; goto error; @@ -313,7 +313,7 @@ int usx2y_in04_init(struct usx2ydev *usx2y) init_waitqueue_head(&usx2y->in04_wait_queue); usb_fill_int_urb(usx2y->in04_urb, usx2y->dev, usb_rcvintpipe(usx2y->dev, 0x4), - usx2y->in04_buf, 21, + usx2y->in04_buf, USX2Y_IN04_SIZE, i_usx2y_in04_int, usx2y, 10); if (usb_urb_ep_type_check(usx2y->in04_urb)) { diff --git a/sound/usb/usx2y/usbusx2y.h b/sound/usb/usx2y/usbusx2y.h index 6a76d04bf1c7..7b6deed17d4b 100644 --- a/sound/usb/usx2y/usbusx2y.h +++ b/sound/usb/usx2y/usbusx2y.h @@ -5,6 +5,8 @@ #include "../midi.h" #include "usbus428ctldefs.h" +#define USX2Y_IN04_SIZE sizeof(struct us428_ctls) + #define NRURBS 2 /* Default value used for nr of packs per urb. @@ -55,7 +57,7 @@ struct usx2ydev { int stride; struct urb *in04_urb; void *in04_buf; - char in04_last[24]; + char in04_last[USX2Y_IN04_SIZE]; unsigned int in04_int_calls; struct snd_usx2y_urb_seq *us04; wait_queue_head_t in04_wait_queue; From 8f5ef203abda9dd36b2af473c7b737d544f807bd Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Fri, 4 Sep 2026 20:58:26 +0000 Subject: [PATCH 0970/2366] ALSA: usbusx2y: validate URB actual_length in interrupt callback i_usx2y_in04_int() processes the interrupt URB data without checking urb->actual_length. A short transfer from a malfunctioning device would cause the handler to process uninitialized heap data from the kmalloc-allocated in04_buf, which is then copied to the mmap-accessible ctl_snapshot[] array. Fix by using kzalloc() for in04_buf to zero-initialize the buffer, and adding an actual_length check to skip processing on short transfers while still resubmitting the URB. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani Link: https://patch.msgid.link/20260904205826.4071119-2-tristmd@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/usx2y/usbusx2y.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/usb/usx2y/usbusx2y.c b/sound/usb/usx2y/usbusx2y.c index 200b708aacd6..108f9bddf9aa 100644 --- a/sound/usb/usx2y/usbusx2y.c +++ b/sound/usb/usx2y/usbusx2y.c @@ -189,6 +189,9 @@ static void i_usx2y_in04_int(struct urb *urb) return; } + if (urb->actual_length < USX2Y_IN04_SIZE) + goto resubmit; + if (us428ctls) { diff = -1; if (us428ctls->ctl_snapshot_last == -2) { @@ -253,6 +256,7 @@ static void i_usx2y_in04_int(struct urb *urb) if (err) dev_err(&urb->dev->dev, "in04_int() usb_submit_urb err=%i\n", err); +resubmit: urb->dev = usx2y->dev; usb_submit_urb(urb, GFP_ATOMIC); } @@ -305,7 +309,7 @@ int usx2y_in04_init(struct usx2ydev *usx2y) goto error; } - usx2y->in04_buf = kmalloc(USX2Y_IN04_SIZE, GFP_KERNEL); + usx2y->in04_buf = kzalloc(USX2Y_IN04_SIZE, GFP_KERNEL); if (!usx2y->in04_buf) { err = -ENOMEM; goto error; From 12cba0ce111c2caabf4ff24dc5538959813664c0 Mon Sep 17 00:00:00 2001 From: Krish Gulati Date: Sun, 6 Sep 2026 03:56:56 +0530 Subject: [PATCH 0971/2366] ALSA: hda/realtek: Add quirk for HP Omen 16-wd0xxx mute LED Add SND_PCI_QUIRK entry for HP Omen 16-wd0xxx (PCI SSID 103c:8ba9) using ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT, which controls the mute LED via a COEF bit write on the ALC245 codec. Without this quirk, the mute LED does not reflect mute state on this model. Tested on the affected hardware; mute LED now toggles correctly. Signed-off-by: Krish Gulati Link: https://patch.msgid.link/20260905222659.10247-1-krishgulati7@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 95b40a177d2b..f7786e1b0878 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7566,6 +7566,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b92, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), From 45b5beb60bf7bd41c55ef17f6f2d28b351fad6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dalfu=C3=9F?= Date: Sat, 5 Sep 2026 14:11:12 +0200 Subject: [PATCH 0972/2366] ALSA: usb-audio: Add boot quirk for Behringer CM1A MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a power cycle and reenumeration, the Behringer CM1A* leaves its MIDI endpoint inoperative. USB enumeration and driver binding complete successfully, but MIDI outputs remain pending. A GET_DESCRIPTOR request for the device descriptor, issued after USB configuration, makes the endpoint operational. Add a one time boot quirk to perform that request before ALSA initializes the device. * ID 1397:1234 BEHRINGER International GmbH CM1A Signed-off-by: Sebastian Dalfuß Link: https://patch.msgid.link/apwG4DRfNyvmRzyb@sedf.de Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 91938172912a..5dede66ea69e 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -849,6 +849,27 @@ static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev) return 0; } +/* + * A post configuration device descriptor read is needed to make the CM1A + * operational after reenumeration. + */ +static int snd_usb_cm1a_boot_quirk(struct usb_device *dev) +{ + struct usb_device_descriptor *desc __free(kfree) = kmalloc_obj(*desc); + int err; + + if (!desc) + return -ENOMEM; + + err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, sizeof(*desc)); + if (err < 0) { + dev_err(&dev->dev, "failed to read device descriptor: %d\n", err); + return err; + } + + return 0; +} + /* * Some sound cards from Native Instruments are in fact compliant to the USB * audio standard of version 2 and other approved USB standards, even though @@ -1681,6 +1702,8 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, switch (id) { case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ return snd_usb_motu_m_series_boot_quirk(dev); + case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ + return snd_usb_cm1a_boot_quirk(dev); } return 0; From 33fb59da49c4c3f5c2ec9f9d4447a56857a02c02 Mon Sep 17 00:00:00 2001 From: Jeffin Philip Date: Fri, 4 Sep 2026 18:44:37 +0530 Subject: [PATCH 0973/2366] RDMA/core: fix refcount bug in iwpm_get_nlmsg_request() iwpm_get_nlmsg_request() initializes refcount _after_ list_add_tail() making it accessible to global list where another CPU can kref_get() on nlmsg_request causing a refcount "addition on 0" bug. Fix this by initializing kref _before_ list_add_tail() so refcount for nlmsg_request can be incremented/decremented normally. In addition, also initialize every field before list_add_tail(). Reported-by: syzbot+bd317784d628820741b5@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=bd317784d628820741b5 Fixes: 30dc5e63d6a5 ("RDMA/core: Add support for iWARP Port Mapper user space service") Cc: stable@vger.kernel.org Signed-off-by: Jeffin Philip Link: https://patch.msgid.link/20260904131437.12917-1-jeffinphilip14@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/iwpm_util.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/core/iwpm_util.c b/drivers/infiniband/core/iwpm_util.c index 990cf928b32a..51af8c1c49f9 100644 --- a/drivers/infiniband/core/iwpm_util.c +++ b/drivers/infiniband/core/iwpm_util.c @@ -314,10 +314,6 @@ struct iwpm_nlmsg_request *iwpm_get_nlmsg_request(__u32 nlmsg_seq, if (!nlmsg_request) return NULL; - spin_lock_irqsave(&iwpm_nlmsg_req_lock, flags); - list_add_tail(&nlmsg_request->inprocess_list, &iwpm_nlmsg_req_list); - spin_unlock_irqrestore(&iwpm_nlmsg_req_lock, flags); - kref_init(&nlmsg_request->kref); kref_get(&nlmsg_request->kref); nlmsg_request->nlmsg_seq = nlmsg_seq; @@ -326,6 +322,11 @@ struct iwpm_nlmsg_request *iwpm_get_nlmsg_request(__u32 nlmsg_seq, nlmsg_request->err_code = 0; sema_init(&nlmsg_request->sem, 1); down(&nlmsg_request->sem); + + spin_lock_irqsave(&iwpm_nlmsg_req_lock, flags); + list_add_tail(&nlmsg_request->inprocess_list, &iwpm_nlmsg_req_list); + spin_unlock_irqrestore(&iwpm_nlmsg_req_lock, flags); + return nlmsg_request; } From 536b523b407397c8d3967c020ce7aad70a0ea030 Mon Sep 17 00:00:00 2001 From: Chen Pei Date: Wed, 2 Sep 2026 14:14:51 +0800 Subject: [PATCH 0974/2366] bpf, riscv: Make arena support depend on ZACAS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The arena range tree allocates its nodes with kmalloc_nolock() since commit f8c67d8550ee ("bpf: Use kmalloc_nolock() in range tree"). kmalloc_nolock() requires slab caches with cmpxchg128 support (__CMPXCHG_DOUBLE); on riscv cmpxchg128 is provided by the ZACAS extension. On systems without ZACAS every arena map creation fails with a misleading -ENOMEM. Report the missing support instead: make bpf_jit_supports_arena() return system_has_cmpxchg128() where it is defined, so arena map creation fails with -EOPNOTSUPP on systems without ZACAS. The macro is only defined when both CONFIG_RISCV_ISA_ZACAS and CONFIG_TOOLCHAIN_HAS_ZACAS are enabled, so guard it with #ifdef the same way mm/slab.h consumes it, and reject arena otherwise. This matches how arena BPF_CMPXCHG instructions are already gated on ZACAS in bpf_jit_supports_insn(). Fixes: f8c67d8550ee ("bpf: Use kmalloc_nolock() in range tree") Signed-off-by: Chen Pei Acked-by: Pu Lehui Acked-by: Björn Töpel Cc: stable@vger.kernel.org Link: https://lore.kernel.org/bpf/20260902061451.1416-1-cp0613@linux.alibaba.com Signed-off-by: Kumar Kartikeya Dwivedi --- arch/riscv/net/bpf_jit_comp64.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c index 74efe4b138d2..151031e97a24 100644 --- a/arch/riscv/net/bpf_jit_comp64.c +++ b/arch/riscv/net/bpf_jit_comp64.c @@ -2128,7 +2128,15 @@ bool bpf_jit_supports_ptr_xchg(void) bool bpf_jit_supports_arena(void) { - return true; + /* + * The arena range tree uses kmalloc_nolock(), which needs + * cmpxchg128, provided by ZACAS on riscv. + */ +#ifdef system_has_cmpxchg128 + return system_has_cmpxchg128(); +#else + return false; +#endif } bool bpf_jit_supports_insn(struct bpf_insn *insn, bool in_arena) From df2908090cda368b01ff43709f51890076c56157 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 6 Sep 2026 15:07:20 -0700 Subject: [PATCH 0975/2366] Linux 7.3-rc2 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4ad67b737af7..66654fa71655 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc1 +EXTRAVERSION = -rc2 NAME = Baby Opossum Posse # *DOCUMENTATION* From 89ff16f0713917303210c560eec5cd0c13bd651f Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 5 Sep 2026 12:53:44 -1000 Subject: [PATCH 0976/2366] sched_ext: scx_qmap: Fix pending partition work handoff qmap can leave partition work pending with no runner. The effective-cap callback publishes its request after failing to acquire part_busy, while redistribute() checks for pending work before releasing it. Either ordering can miss a request arriving as the current runner finishes, delaying the update until the round-robin timer runs. Publish requests before trying to become the runner and release part_busy before checking for more work. Have all holders drain pending requests after releasing it, including the stats flush. Distinguish mask refreshes from repartitions so an effective-cap update only rebuilds the partition when a repartition was also requested. Fixes: e9151ed5c944 ("tools/sched_ext: scx_qmap - Expand hierarchical sub-scheduling") Reported-by: Andrea Righi Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- tools/sched_ext/scx_qmap.bpf.c | 77 ++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index 062bb22ee65c..bda56c37acb5 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -1295,11 +1295,16 @@ struct { __type(value, struct round_robin_timer); } round_robin_timer SEC(".maps"); +enum part_pending_flags { + PART_REFRESH = BIT_U64(0), + PART_REDISTRIBUTE = BIT_U64(1), +}; + /* * Partition update synchronization. qa.part can be written from concurrent * contexts. This single-runner guard admits one writer at a time without * holding a lock across the grant/revoke kfuncs. part_pending coalesces - * repartition requests that arrive while it is held. + * refresh and repartition requests that arrive while it is held. * * They live in .bss, not the arena: rr_advance() runs from a bpf_timer * callback, where the verifier rejects atomic ops on arena memory. @@ -1668,33 +1673,46 @@ __noinline void apply_partition(void) } } -/* - * Recompute the split off the node's held caps and apply it. The contexts this - * runs from (the sub-sched and cgroup callbacks, the rr timer) are not - * serialized by the kernel, so a single runner does the work. A caller that - * finds the guard held leaves part_pending set; the holder drains it before - * releasing, with the rr timer as a backstop. +/** + * execute_partition - Run pending partition updates + * + * The rr timer is the backstop if the loop reaches its iteration limit. */ -static void redistribute(void) +static void execute_partition(void) { + u64 pending; s32 i; - __sync_fetch_and_or(&part_pending, 1); - - if (!part_try_start()) - return; - bpf_for(i, 0, 1024) { - __sync_fetch_and_and(&part_pending, 0); - /* charge elapsed time to the current partition before rebuilding it */ - account_alloc(); - compute_partition(); - apply_partition(); + if (!part_try_start()) + break; + + pending = __sync_fetch_and_and(&part_pending, 0); + if (pending & PART_REDISTRIBUTE) { + /* charge elapsed time before repartitioning */ + account_alloc(); + compute_partition(); + apply_partition(); + } else if (pending & PART_REFRESH) { + refresh_usable(); + } + + /* + * Requests are published before trying the guard. Releasing it + * before checking pending work ensures a racing request is + * either observed here or handled by a caller that acquires the + * guard. + */ + part_end(); if (!__sync_fetch_and_or(&part_pending, 0)) break; } +} - part_end(); +static void redistribute(void) +{ + __sync_fetch_and_or(&part_pending, PART_REDISTRIBUTE); + execute_partition(); } /* @@ -1708,6 +1726,7 @@ int flush_alloc(void *ctx) if (part_try_start()) { account_alloc(); part_end(); + execute_partition(); } return 0; } @@ -1765,9 +1784,7 @@ static void rr_advance(void) part_end(); - /* a resplit queued while we held the guard supersedes this rotation */ - if (__sync_fetch_and_or(&part_pending, 0)) - redistribute(); + execute_partition(); } /* advance the time-shared cid pool every round_robin_ns */ @@ -2041,20 +2058,8 @@ void BPF_STRUCT_OPS(qmap_sub_ecaps_updated, s32 cid, u64 before, u64 after) cmask_clear(cid, &qa.avail_cids.mask); } - /* - * When another runner holds the partition guard, set part_pending: - * redistribute() drains it before releasing and rr_advance() checks it - * after, so the deferred refresh lands by the next rr tick. A - * repartition that lost the guard to us runs here. - */ - if (part_try_start()) { - refresh_usable(); - part_end(); - if (__sync_fetch_and_or(&part_pending, 0)) - redistribute(); - } else { - __sync_fetch_and_or(&part_pending, 1); - } + __sync_fetch_and_or(&part_pending, PART_REFRESH); + execute_partition(); } SCX_OPS_CID_DEFINE(qmap_ops, From 2ac74c6db40adaa29c50cbb281ae9a6f63de18e1 Mon Sep 17 00:00:00 2001 From: John Hubbard Date: Sun, 6 Sep 2026 14:58:22 -0700 Subject: [PATCH 0977/2366] rust: allow `clippy::as_underscore` in the generated bindings A CLIPPY=1 build emitted about 15000 `as _` conversion warnings, all of them in bindgen's generated output and none in hand-written code. [ The lint messages look like: error: using `as _` conversion --> rust/bindings/bindings_generated.rs:18947:9 | 18947 | self._bitfield_1.get_const::<0usize, 16u8>() as u32 as _ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- | | | help: consider giving the type explicitly: `u32` | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#as_underscore = note: `-D clippy::as-underscore` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::as_underscore)]` - Miguel ] bindgen 0.73 returns each bitfield read through a trailing `as _`, and 0.72 returns it through a transmute, which the lint ignores. The bindings and uapi crates allow `clippy::all` over the generated code. That group does not cover `clippy::as_underscore`, a restriction lint. Allow `clippy::as_underscore` by name in the bindings and uapi crates. Assisted-by: LLM Signed-off-by: John Hubbard Link: https://patch.msgid.link/20260906215822.1201022-1-jhubbard@nvidia.com Cc: stable@vger.kernel.org # Needed in 6.12.y and later (Rust is pinned in older LTSs). [ Removed CI sentence. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/bindings/lib.rs | 1 + rust/uapi/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index 812f8e5a08d5..ad24c920b919 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -22,6 +22,7 @@ #![feature(cfi_encoding)] #[allow(dead_code)] +#[allow(clippy::as_underscore)] #[allow(clippy::cast_lossless)] #[allow(clippy::ptr_as_ptr)] #[allow(clippy::ref_as_ptr)] diff --git a/rust/uapi/lib.rs b/rust/uapi/lib.rs index 797ead5b5626..2df0340e63d1 100644 --- a/rust/uapi/lib.rs +++ b/rust/uapi/lib.rs @@ -10,6 +10,7 @@ #![no_std] #![allow( clippy::all, + clippy::as_underscore, clippy::cast_lossless, clippy::ptr_as_ptr, clippy::ref_as_ptr, From 5ba79d37403d86082ab4083b0f51ec3008a942cb Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Fri, 3 Jul 2026 18:58:35 +0200 Subject: [PATCH 0978/2366] powerpc/ps3: Fix repository.c build failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GCC fails to build ps3_defconfig with the following errors: arch/powerpc/platforms/ps3/repository.c: In function ‘make_first_field.constprop’: arch/powerpc/platforms/ps3/repository.c:78:9: error: ‘strnlen’ specified bound 8 exceeds source size 3 [-Werror=stringop-overread] 78 | memcpy((char *)&n, text, strnlen(text, sizeof(n))); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ arch/powerpc/platforms/ps3/repository.c: In function ‘make_first_field.constprop’: arch/powerpc/platforms/ps3/repository.c:78:9: error: ‘strnlen’ specified bound 8 exceeds source size 4 [-Werror=stringop-overread] 78 | memcpy((char *)&n, text, strnlen(text, sizeof(n))); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The current use of strnlen(text, sizeof(n)) triggers -Wstringop-overread when text is a short string literal that is smaller than sizeof(n), such as "bi" or "bus". Use strlen(text) instead and clamp the copy length to sizeof(n) before memcpy(). Drop the redundant char * cast while at it. Fixes: f94a84a09148 ("powerpc/ps3: refactor strncpy usage") Cc: stable@vger.kernel.org Signed-off-by: Thorsten Blum Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260703165834.137242-2-thorsten.blum@linux.dev --- arch/powerpc/platforms/ps3/repository.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/platforms/ps3/repository.c b/arch/powerpc/platforms/ps3/repository.c index b8c030eab138..0cc755ac3e7f 100644 --- a/arch/powerpc/platforms/ps3/repository.c +++ b/arch/powerpc/platforms/ps3/repository.c @@ -6,6 +6,8 @@ * Copyright 2006 Sony Corp. */ +#include + #include #include "platform.h" @@ -74,8 +76,9 @@ static void _dump_node(unsigned int lpar_id, u64 n1, u64 n2, u64 n3, u64 n4, static u64 make_first_field(const char *text, u64 index) { u64 n = 0; + size_t len = min(strlen(text), sizeof(n)); - memcpy((char *)&n, text, strnlen(text, sizeof(n))); + memcpy(&n, text, len); return PS3_VENDOR_ID_NONE + (n >> 32) + index; } From f789d291bbdac6bc02d9d77e0141e138626c17a8 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 20 Aug 2026 20:58:06 -0700 Subject: [PATCH 0979/2366] xfs: fix media verification ioctl for internal rt volumes A media scan of a filesystem containing an internal rt volume produced an error in xfs_scrub phase 6 complaining about a truncated realtime device. The rt device wasn't truncated, but the media scan code thought we were trying to start a scan past the end of m_rtdev_targp. That in turn is an alias for m_ddev_targp, but in xfs_configure_buftarg we set nr_sectors to the size of the data section. We don't account for an internal realtime section, so the kernel doesn't scan any part of it. Oops. Reproducer: # mkfs.xfs -f /dev/sda -r zoned=1 -d rtinherit=1 # mount /dev/sda /mnt # dd if=/dev/zero of=/mnt/a bs=1024k count=100 # sync # xfs_info /mnt meta-data=/dev/sda isize=512 agcount=4, agsize=32768 blks = sectsz=512 attr=2, projid32bit=1 = crc=1 finobt=1, sparse=1, rmapbt=1 = reflink=0 bigtime=1 inobtcount=1 nrext64=1 = exchange=1 metadir=1 data = bsize=4096 blocks=131072, imaxpct=25 = sunit=0 swidth=0 blks naming =version 2 bsize=4096 ascii-ci=0, ftype=1, parent=1 log =internal log bsize=4096 blocks=16384, version=2 = sectsz=512 sunit=0 blks, lazy-count=1 realtime =internal extsz=4096 blocks=1114112, rtextents=1114112 = rgcount=17 rgsize=65536 extents = zoned=1 start=131072 reserved=53248 IOWS: 512M data volume, 3.1G internal rt section. Now let's try some media verification: # xfs_io -c 'verifymedia -d' -c 'verifymedia -r' /mnt verified 536870912/536870912 bytes at offset 0 512 MiB, 1 ops; 0.0496 sec (10.067 GiB/sec and 20.1345 ops/sec) verified 536870912/536870912 bytes at offset 0 512 MiB, 1 ops; 0.0409 sec (12.222 GiB/sec and 24.4439 ops/sec) Notice how xfs_io says we only verified 512M of the rt volume? If you run btrace in the background you'll see that we read the first 512M of the volume (aka the data section) twice and never read anything from the rt section. An earlier fix tried messing with the buftarg geometry, but I've decided on a more targetted fix for the media verification code. All we have to do is calculate the starting and ending daddr for the device that we're verifying, and clamp the user's input values to that range. This leads to some bogosity in the output reporting: # xfs_io -c 'verifymedia -d' -c 'verifymedia -r' /mnt/t verified 536870912/536870912 bytes at offset 0 512 MiB, 1 ops; 0.0606 sec (8.248 GiB/sec and 16.4968 ops/sec) verified 5100273664/5100273664 bytes at offset 0 4.750 GiB, 1 ops; 0.3329 sec (14.267 GiB/sec and 3.0035 ops/sec) Because we don't have a way to report that we didn't really do anything at all for that first 512M of address space of the rt "device". But at least we're no longer ignoring real media. (Note that the fsmap/bmap/fiemap calls all report physical addresses for the internal rt volume as offsets from the start of the data device, and the media verifier call consumes the same. We baked that into the user-visible behavior in 6.15, so we're stuck with that sparse hole at the beginning.) Cc: stable@vger.kernel.org # v6.15 Fixes: bdc03eb5f98f6f ("xfs: allow internal RT devices for zoned mode") Signed-off-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_verify_media.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/fs/xfs/xfs_verify_media.c b/fs/xfs/xfs_verify_media.c index 5ead3976d511..b75c81f8fcc0 100644 --- a/fs/xfs/xfs_verify_media.c +++ b/fs/xfs/xfs_verify_media.c @@ -268,6 +268,8 @@ xfs_verify_media( struct xfs_buftarg *btp = NULL; struct bio *bio; struct folio *folio; + xfs_daddr_t dev_start = 0; + xfs_daddr_t dev_end = 0; xfs_daddr_t daddr; uint64_t bbcount; int error = 0; @@ -277,24 +279,33 @@ xfs_verify_media( switch (me->me_dev) { case XFS_DEV_DATA: btp = mp->m_ddev_targp; + dev_end = XFS_FSB_TO_BB(mp, mp->m_sb.sb_dblocks); break; case XFS_DEV_LOG: - if (mp->m_logdev_targp != mp->m_ddev_targp) + if (mp->m_logdev_targp != mp->m_ddev_targp) { btp = mp->m_logdev_targp; + dev_end = XFS_FSB_TO_BB(mp, mp->m_sb.sb_logblocks); + } break; case XFS_DEV_RT: btp = mp->m_rtdev_targp; + dev_start = XFS_FSB_TO_BB(mp, mp->m_sb.sb_rtstart); + dev_end = XFS_FSB_TO_BB(mp, mp->m_sb.sb_rtstart + + mp->m_sb.sb_rblocks); break; } if (!btp) return -ENODEV; /* - * If the caller told us to verify beyond the end of the disk, tell the - * user exactly where that was. + * If the caller told us to verify before the start or beyond the end + * of the disk volume, tell the user exactly where the volume starts + * and ends. */ - if (me->me_end_daddr > btp->bt_nr_sectors) - me->me_end_daddr = btp->bt_nr_sectors; + if (me->me_end_daddr > dev_end) + me->me_end_daddr = dev_end; + if (me->me_start_daddr < dev_start) + me->me_start_daddr = dev_start; /* start and end have to be aligned to the lba size */ if (!IS_ALIGNED(BBTOB(me->me_start_daddr | me->me_end_daddr), @@ -323,8 +334,7 @@ xfs_verify_media( * verifying. */ daddr = me->me_start_daddr; - bbcount = min_t(sector_t, me->me_end_daddr, btp->bt_nr_sectors) - - me->me_start_daddr; + bbcount = me->me_end_daddr - me->me_start_daddr; folio = xfs_verify_alloc_folio(xfs_verify_iosize(me, btp, bbcount)); if (!folio) From ff99a5f6cbcc9c4810a8dac46fe76473539513f9 Mon Sep 17 00:00:00 2001 From: Hans Holmberg Date: Wed, 26 Aug 2026 14:32:19 +0200 Subject: [PATCH 0980/2366] xfs: prevent race in zoned space reservations xfs_zoned_add_available() checks whether the reservation list is empty before adding blocks to the available-space counter. This check is not serialized against a task adding itself to the reservation list however. This allows the space provider to observe an empty list, after which a reserver can enqueue itself and retry the counter before the new space is added. The provider then adds the space and returns without waking the now-eligible reserver, leaving it asleep until GC or another event provides a wakeup, potentially adding seconds to max write latency. Take the reservation lock before updating the counter and checking the list. Use list_empty() because the list is now inspected under its lock. Taking a per-mount lock when handing back space is far from ideal, but benchmarking with null_blk showed no measurable performance regression. Fixes: 0bb2193056b5 ("xfs: add support for zoned space reservations") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260609075655.1698743-1-hch@lst.de?part=2 Signed-off-by: Hans Holmberg Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_space_resv.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_zone_space_resv.c b/fs/xfs/xfs_zone_space_resv.c index 5c6e6ef627e4..7aa3c74fb2e0 100644 --- a/fs/xfs/xfs_zone_space_resv.c +++ b/fs/xfs/xfs_zone_space_resv.c @@ -85,13 +85,13 @@ xfs_zoned_add_available( struct xfs_zone_info *zi = mp->m_zone_info; struct xfs_zone_reservation *reservation; - if (list_empty_careful(&zi->zi_reclaim_reservations)) { - xfs_add_freecounter(mp, XC_FREE_RTAVAILABLE, count_fsb); + spin_lock(&zi->zi_reservation_lock); + xfs_add_freecounter(mp, XC_FREE_RTAVAILABLE, count_fsb); + if (list_empty(&zi->zi_reclaim_reservations)) { + spin_unlock(&zi->zi_reservation_lock); return; } - spin_lock(&zi->zi_reservation_lock); - xfs_add_freecounter(mp, XC_FREE_RTAVAILABLE, count_fsb); count_fsb = xfs_sum_freecounter(mp, XC_FREE_RTAVAILABLE); list_for_each_entry(reservation, &zi->zi_reclaim_reservations, entry) { if (reservation->count_fsb > count_fsb) From a23eca88448e52eb1a81549862df7adce794fafb Mon Sep 17 00:00:00 2001 From: Lin Jiapeng Date: Tue, 28 Jul 2026 15:19:10 +0800 Subject: [PATCH 0981/2366] xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN When exchanging two full-file ranges, xmi_can_exchange_reflink_flags() can move the reflink inode flag from the file that currently has it to the other file, as long as exactly one side is marked. This assumes that the file contents, and therefore all shared extents, are exchanged. That assumption is not true when XFS_EXCHMAPS_INO1_WRITTEN is set. xfs_exchmaps_can_skip_mapping() can skip hole and unwritten mappings from file1, so an exchange can complete without moving every mapping that the earlier flag-swap decision accounted for. In that case the post-operation cleanup can clear the reflink flag from an inode that still owns shared written extents. Later writes then take the non-reflink write path and may update blocks that should still have been protected by CoW, which shows up as data corruption between reflink-related files. Fix this by disabling the reflink flag exchange whenever XFS_EXCHMAPS_INO1_WRITTEN is requested. The contents exchange can still proceed; the conservative outcome is that both inodes keep the reflink flag. The regular reflink flag cleanup path can drop the extra flag later once the inode no longer has shared extents. Reported-by: Lin Jiapeng (TencentOS Red Team) Fixes: 966ceafc7a43 ("xfs: create deferred log items for file mapping exchanges") Cc: stable@vger.kernel.org # v6.10 Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Lin Jiapeng Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_exchmaps.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 3efed37cb98a..49eda8d0994d 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -959,6 +959,16 @@ xmi_can_exchange_reflink_flags( { struct xfs_mount *mp = req->ip1->i_mount; + /* + * The INO1_WRITTEN optimization can skip exchanging hole and + * unwritten mappings, which means we cannot guarantee that all + * shared extents actually moved to the other file. Clearing the + * reflink flag of an inode that still holds shared extents breaks + * the CoW write path, so refuse to exchange the flags in that case. + */ + if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN) + return false; + /* * The INO1_WRITTEN optimization can skip exchanging hole and * unwritten mappings, which means we cannot guarantee that all From cf3a01684f323dd905d83230b7cb705fabdbcb7b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:38 +0200 Subject: [PATCH 0982/2366] xfs: fix the lock annotation on xfs_iget_cache_hit The newer clang context analysis requires __releases_shared for the RCU pseudo-lock. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_icache.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c index a857b8aa255c..82dac88e3c4c 100644 --- a/fs/xfs/xfs_icache.c +++ b/fs/xfs/xfs_icache.c @@ -497,7 +497,8 @@ xfs_iget_cache_hit( struct xfs_inode *ip, xfs_ino_t ino, int flags, - int lock_flags) __releases(RCU) + int lock_flags) + __releases_shared(RCU) { struct inode *inode = VFS_I(ip); struct xfs_mount *mp = ip->i_mount; From 2078c2d6e1748df6c4c2b28f06797cba4ecda85c Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:39 +0200 Subject: [PATCH 0983/2366] xfs: fix the lock annotation in xfs_extent_busy_update_extent Name the correct lock. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_extent_busy.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_extent_busy.c b/fs/xfs/xfs_extent_busy.c index 41cf0605ec22..6da8c1f938aa 100644 --- a/fs/xfs/xfs_extent_busy.c +++ b/fs/xfs/xfs_extent_busy.c @@ -161,8 +161,8 @@ xfs_extent_busy_update_extent( xfs_agblock_t fbno, xfs_extlen_t flen, bool userdata) - __releases(&eb->eb_lock) - __acquires(&eb->eb_lock) + __releases(&xg->xg_busy_extents->eb_lock) + __acquires(&xg->xg_busy_extents->eb_lock) { struct xfs_extent_busy_tree *eb = xg->xg_busy_extents; xfs_agblock_t fend = fbno + flen; From b2ae7f243583e795b1eb3f8977cccfd7e37a8dfc Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:40 +0200 Subject: [PATCH 0984/2366] xfs: fix the lock annotation in xfs_mru_cache_lookup Name the actual lock. Unlike sparse, clang wants the annotation to be correct. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_mru_cache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_mru_cache.c b/fs/xfs/xfs_mru_cache.c index d61ec8cb126d..3f3af2e2e31c 100644 --- a/fs/xfs/xfs_mru_cache.c +++ b/fs/xfs/xfs_mru_cache.c @@ -520,7 +520,7 @@ xfs_mru_cache_lookup( if (elem) { list_del(&elem->list_node); _xfs_mru_cache_list_insert(mru, elem); - __release(mru_lock); /* help sparse not be stupid */ + __release(&mru->lock); } else spin_unlock(&mru->lock); From 64e1f211d97b42f6e12bef8e6decc207f8871a2d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:41 +0200 Subject: [PATCH 0985/2366] xfs: improve lock annotations in the log code Improve the __acquires and __releases annotations so that the new clang code that is a bit more picky than sparse is happy. This involves passing an explicit struct xlog argument in a few places because alias analysis can't figure out it is the same lock when dereferencing changing iclogs. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log.c | 31 +++++++++++++++++++------------ fs/xfs/xfs_log_cil.c | 2 +- fs/xfs/xfs_log_priv.h | 4 ++-- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/fs/xfs/xfs_log.c b/fs/xfs/xfs_log.c index f807f8f4f705..0294ac277f35 100644 --- a/fs/xfs/xfs_log.c +++ b/fs/xfs/xfs_log.c @@ -470,6 +470,8 @@ xlog_state_release_iclog( struct xlog *log, struct xlog_in_core *iclog, struct xlog_ticket *ticket) + __releases(&log->l_icloglock) + __acquires(&log->l_icloglock) { bool last_ref; @@ -744,13 +746,16 @@ xfs_log_mount_cancel( */ static inline int xlog_force_iclog( + struct xlog *log, struct xlog_in_core *iclog) + __releases(&log->l_icloglock) + __acquires(&log->l_icloglock) { atomic_inc(&iclog->ic_refcnt); iclog->ic_flags |= XLOG_ICL_NEED_FLUSH | XLOG_ICL_NEED_FUA; if (iclog->ic_state == XLOG_STATE_ACTIVE) - xlog_state_switch_iclogs(iclog->ic_log, iclog, 0); - return xlog_state_release_iclog(iclog->ic_log, iclog, NULL); + xlog_state_switch_iclogs(log, iclog, 0); + return xlog_state_release_iclog(log, iclog, NULL); } /* @@ -778,11 +783,10 @@ xlog_wait_iclog_completion(struct xlog *log) */ int xlog_wait_on_iclog( + struct xlog *log, struct xlog_in_core *iclog) - __releases(iclog->ic_log->l_icloglock) + __releases(log->l_icloglock) { - struct xlog *log = iclog->ic_log; - trace_xlog_iclog_wait_on(iclog, _RET_IP_); if (!xlog_is_shutdown(log) && iclog->ic_state != XLOG_STATE_ACTIVE && @@ -879,8 +883,8 @@ xlog_unmount_write( spin_lock(&log->l_icloglock); iclog = log->l_iclog; - error = xlog_force_iclog(iclog); - xlog_wait_on_iclog(iclog); + error = xlog_force_iclog(log, iclog); + xlog_wait_on_iclog(log, iclog); if (tic) { trace_xfs_log_umount_write(log, tic); @@ -2741,14 +2745,17 @@ xlog_state_switch_iclogs( */ static int xlog_force_and_check_iclog( + struct xlog *log, struct xlog_in_core *iclog, bool *completed) + __releases(&log->l_icloglock) + __acquires(&log->l_icloglock) { xfs_lsn_t lsn = be64_to_cpu(iclog->ic_header->h_lsn); int error; *completed = false; - error = xlog_force_iclog(iclog); + error = xlog_force_iclog(log, iclog); if (error) return error; @@ -2825,7 +2832,7 @@ xfs_log_force( /* We have exclusive access to this iclog. */ bool completed; - if (xlog_force_and_check_iclog(iclog, &completed)) + if (xlog_force_and_check_iclog(log, iclog, &completed)) goto out_error; if (completed) @@ -2850,7 +2857,7 @@ xfs_log_force( iclog->ic_flags |= XLOG_ICL_NEED_FLUSH | XLOG_ICL_NEED_FUA; if (flags & XFS_LOG_SYNC) - return xlog_wait_on_iclog(iclog); + return xlog_wait_on_iclog(log, iclog); out_unlock: spin_unlock(&log->l_icloglock); return 0; @@ -2920,7 +2927,7 @@ xlog_force_lsn( &log->l_icloglock); return -EAGAIN; } - if (xlog_force_and_check_iclog(iclog, &completed)) + if (xlog_force_and_check_iclog(log, iclog, &completed)) goto out_error; if (log_flushed) *log_flushed = 1; @@ -2948,7 +2955,7 @@ xlog_force_lsn( } if (flags & XFS_LOG_SYNC) - return xlog_wait_on_iclog(iclog); + return xlog_wait_on_iclog(log, iclog); out_unlock: spin_unlock(&log->l_icloglock); return 0; diff --git a/fs/xfs/xfs_log_cil.c b/fs/xfs/xfs_log_cil.c index 639f875a8fb2..ae1ed16aeb2f 100644 --- a/fs/xfs/xfs_log_cil.c +++ b/fs/xfs/xfs_log_cil.c @@ -1556,7 +1556,7 @@ xlog_cil_push_work( * iclogs older than ic_prev. Hence we only need to wait * on the most recent older iclog here. */ - xlog_wait_on_iclog(ctx->commit_iclog->ic_prev); + xlog_wait_on_iclog(log, ctx->commit_iclog->ic_prev); spin_lock(&log->l_icloglock); } diff --git a/fs/xfs/xfs_log_priv.h b/fs/xfs/xfs_log_priv.h index cf1e4ce61a8c..6d9673c41cdf 100644 --- a/fs/xfs/xfs_log_priv.h +++ b/fs/xfs/xfs_log_priv.h @@ -605,8 +605,8 @@ xlog_wait( remove_wait_queue(wq, &wait); } -int xlog_wait_on_iclog(struct xlog_in_core *iclog) - __releases(iclog->ic_log->l_icloglock); +int xlog_wait_on_iclog(struct xlog *log, struct xlog_in_core *iclog) + __releases(log->l_icloglock); /* Calculate the distance between two LSNs in bytes */ static inline uint64_t From 912a5b8e344ad1e3cb6aefedf243404d522ad822 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:42 +0200 Subject: [PATCH 0986/2366] xfs: add lock annotations to xfs_try_open_zone Improve the __acquires and __releases annotations so that the new clang code that is a bit more picky than sparse is happy. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_alloc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index bdbb60cc5d5b..28c1e48909fa 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -475,6 +475,8 @@ static struct xfs_open_zone * xfs_try_open_zone( struct xfs_mount *mp, enum rw_hint write_hint) + __releases(&mp->m_zone_info->zi_open_zones_lock) + __acquires(&mp->m_zone_info->zi_open_zones_lock) { struct xfs_zone_info *zi = mp->m_zone_info; struct xfs_open_zone *oz; From 2d1b21fcc9f9463f939103d13d6a0bf05a529901 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:43 +0200 Subject: [PATCH 0987/2366] xfs: add lock annotations to xlog_state_shutdown_callbacks Sparse used to get away without these despite dropping and reacquiring l_icloglock Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/xfs_log.c b/fs/xfs/xfs_log.c index 0294ac277f35..2a34611d81f6 100644 --- a/fs/xfs/xfs_log.c +++ b/fs/xfs/xfs_log.c @@ -422,6 +422,8 @@ xfs_log_reserve( static void xlog_state_shutdown_callbacks( struct xlog *log) + __releases(&log->l_icloglock) + __acquires(&log->l_icloglock) { struct xlog_in_core *iclog; LIST_HEAD(cb_list); From 166089a856859d5f3e2b85c6ffd70da084238439 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:44 +0200 Subject: [PATCH 0988/2366] xfs: add a lock annotation to xlog_cil_push_background This is required to make the clang context analysis happy, which is more strict than the old sparse lock context tracking. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log_cil.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/xfs_log_cil.c b/fs/xfs/xfs_log_cil.c index ae1ed16aeb2f..166531018ce4 100644 --- a/fs/xfs/xfs_log_cil.c +++ b/fs/xfs/xfs_log_cil.c @@ -1627,6 +1627,7 @@ xlog_cil_push_work( static void xlog_cil_push_background( struct xlog *log) + __releases_shared(&log->l_cilp->xc_ctx_lock) { struct xfs_cil *cil = log->l_cilp; int space_used = atomic_read(&cil->xc_ctx->space_used); From ed4abd8617ba348802575a0ae8e01e6c0f7eb2cf Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 11:45:45 +0200 Subject: [PATCH 0989/2366] xfs: add lock annotations to xfs_ail_delete* Pass up the __must_hold as clang requires it, and also fix the formatting of the __must_hold on xfs_ail_check to match how we do it elsewhere. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_trans_ail.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_trans_ail.c b/fs/xfs/xfs_trans_ail.c index 99a9bf3762b7..f955479a08fd 100644 --- a/fs/xfs/xfs_trans_ail.c +++ b/fs/xfs/xfs_trans_ail.c @@ -33,7 +33,7 @@ STATIC void xfs_ail_check( struct xfs_ail *ailp, struct xfs_log_item *lip) - __must_hold(&ailp->ail_lock) + __must_hold(&ailp->ail_lock) { struct xfs_log_item *prev_lip; struct xfs_log_item *next_lip; @@ -321,6 +321,7 @@ static void xfs_ail_delete( struct xfs_ail *ailp, struct xfs_log_item *lip) + __must_hold(&ailp->ail_lock) { xfs_ail_check(ailp, lip); list_del(&lip->li_ail); @@ -899,6 +900,7 @@ xfs_lsn_t xfs_ail_delete_one( struct xfs_ail *ailp, struct xfs_log_item *lip) + __must_hold(&ailp->ail_lock) { struct xfs_log_item *mlip = xfs_ail_min(ailp); xfs_lsn_t lsn = lip->li_lsn; From 6176d21d7bd609632c5b7e87a3adb9be29ec1e72 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Mon, 10 Aug 2026 17:06:13 -0600 Subject: [PATCH 0990/2366] xfs: initialise error in xfs_defer_finish_one() xfs_defer_finish_one() declares error without an initialiser and only assigns it inside the loop over dfp->dfp_work. When that list is empty the loop body never runs, control falls through to the "Done with the dfp, free it" path, and the function returns an indeterminate value. An item-less pending item reaches this through xfs_defer_add_barrier(), which xfs_reap_ag_blocks() uses on any CONFIG_XFS_ONLINE_REPAIR kernel. xfs_defer_finish_noroll() treats any non-EAGAIN return as fatal, so a non-zero stack value turns a successful barrier into a SHUTDOWN_CORRUPT_INCORE in the middle of a repair. Zero is the correct result: reaching the free path means the item loop drained without a non-zero error. Fixes: 3f3cec031099 ("xfs: force small EFIs for reaping btree extents") Cc: stable@vger.kernel.org Signed-off-by: Javier Tia Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_defer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c index 89501e8bd2f8..843c33304441 100644 --- a/fs/xfs/libxfs/xfs_defer.c +++ b/fs/xfs/libxfs/xfs_defer.c @@ -583,7 +583,7 @@ xfs_defer_finish_one( const struct xfs_defer_op_type *ops = dfp->dfp_ops; struct xfs_btree_cur *state = NULL; struct list_head *li, *n; - int error; + int error = 0; trace_xfs_defer_pending_finish(tp->t_mountp, dfp); From 91c15cee394e9958898ef1ed55d9ced4c26e2800 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Mon, 10 Aug 2026 17:06:14 -0600 Subject: [PATCH 0991/2366] xfs: give the deferred barrier op type a name xfs_barrier_defer_type is the only xfs_defer_op_type with no .name. Every other one carries a short string used for tracing and reporting: attr, bmap, extent_free, agfl_free, rtextent_free, refcount, rtrefcount, rmap, rtrmap and exchmaps. That has been harmless because nothing dereferences the field, but it leaves a NULL in a table where every other entry is populated, so the first caller to print it gets "(null)" in the kernel and undefined behaviour in the userspace libxfs build of this file, where xfs_alert lands in fprintf. xfs_defer_add() already treats a missing member of this table as worth shutting the filesystem down for, so an unpopulated one is out of step with how the file handles its own ops tables. Signed-off-by: Javier Tia Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_defer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c index 843c33304441..75f0d37914d5 100644 --- a/fs/xfs/libxfs/xfs_defer.c +++ b/fs/xfs/libxfs/xfs_defer.c @@ -229,6 +229,7 @@ xfs_defer_barrier_cancel_item( } static const struct xfs_defer_op_type xfs_barrier_defer_type = { + .name = "barrier", .max_items = 1, .create_intent = xfs_defer_barrier_create_intent, .abort_intent = xfs_defer_barrier_abort_intent, From d058f24b163a8d63866ac6bbe5f06b41aabe97d7 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Mon, 10 Aug 2026 17:06:15 -0600 Subject: [PATCH 0992/2366] xfs: report the error that made deferred work shut down the fs When a deferred operation fails and shuts the filesystem down, xfs_defer_finish_noroll() reports neither the errno nor which operation originated it, so the log cannot tell a transient -ENOSPC from real corruption. Report the operation type, errno and remaining reservation. trace_xfs_defer_finish_error() runs after xfs_force_shutdown(), which BUGs under fs.xfs.panic_mask and so never fires for the first failure; move it ahead of the shutdown and mirror it to xfs_alert() for systems without tracing armed. Capture the op name while the item is live (dfp is freed once its work list drains) and suppress the alert once the fs is already down. Signed-off-by: Javier Tia Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_defer.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c index 75f0d37914d5..3152acdc335d 100644 --- a/fs/xfs/libxfs/xfs_defer.c +++ b/fs/xfs/libxfs/xfs_defer.c @@ -656,6 +656,7 @@ xfs_defer_finish_noroll( struct xfs_trans **tp) { struct xfs_defer_pending *dfp = NULL; + const char *what = "chain"; int error = 0; LIST_HEAD(dop_pending); LIST_HEAD(dop_paused); @@ -705,9 +706,17 @@ xfs_defer_finish_noroll( struct xfs_defer_pending, dfp_list); if (!dfp) break; + what = dfp->dfp_ops->name; error = xfs_defer_finish_one(*tp, dfp); if (error && error != -EAGAIN) goto out_shutdown; + /* + * A finished item is no longer a candidate for a later + * failure. An -EAGAIN one is not finished, so it keeps the + * attribution across the roll that completes it. + */ + if (!error) + what = "chain"; } /* Requeue the paused items in the outgoing transaction. */ @@ -719,8 +728,12 @@ xfs_defer_finish_noroll( out_shutdown: list_splice_tail_init(&dop_paused, &dop_pending); xfs_defer_trans_abort(*tp, &dop_pending); - xfs_force_shutdown((*tp)->t_mountp, SHUTDOWN_CORRUPT_INCORE); trace_xfs_defer_finish_error(*tp, error); + if (!xfs_is_shutdown((*tp)->t_mountp)) + xfs_alert((*tp)->t_mountp, + "deferred %s work failed, error %d, %u blocks reserved", + what, error, (*tp)->t_blk_res); + xfs_force_shutdown((*tp)->t_mountp, SHUTDOWN_CORRUPT_INCORE); xfs_defer_cancel_list((*tp)->t_mountp, &dop_pending); xfs_defer_cancel(*tp); return error; From 6c0fc3cb4e879927b338b4fcb6de6a25c0a67608 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Mon, 10 Aug 2026 17:06:16 -0600 Subject: [PATCH 0993/2366] xfs: correct the parent pointer space reservation comment The comment on xfs_parent_calc_space_res() claims parent pointers are "always the first attr in an attr tree". They are not: a parent pointer is recorded per dirent, so by the Nth hardlink the attr fork is already in leaf or node format. The reservation is still correct, because XFS_DAENTER_SPACE_RES() covers a split at every level of a maximum-depth attr dabtree whatever format the fork is in, but anyone auditing a shortfall here is led by the comment to look for a bug that is not there. Rewrite the comment to state what actually bounds the result, and record why the double split allowance and the extent-add term differ from xfs_attr_calc_size(). Signed-off-by: Javier Tia Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_trans_space.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_trans_space.c b/fs/xfs/libxfs/xfs_trans_space.c index 9b8f495c9049..c4cd547033e5 100644 --- a/fs/xfs/libxfs/xfs_trans_space.c +++ b/fs/xfs/libxfs/xfs_trans_space.c @@ -22,8 +22,23 @@ xfs_parent_calc_space_res( unsigned int namelen) { /* - * Parent pointers are always the first attr in an attr tree, and never - * larger than a block + * A parent pointer is recorded per dirent, so an inode with N links + * carries N of them and the attr fork can already be in leaf or node + * format when one is added. That does not affect the reservation: + * XFS_DAENTER_SPACE_RES covers a split at every level of a + * maximum-depth attr dabtree, whatever format the fork is in now. + * + * The name is a dirent name and the value is a struct xfs_parent_rec, + * so the leaf entry is always local and never exceeds 272 bytes. + * Parent pointers require V5, hence a 1k minimum block size, so the + * entry always stays under half a block and this needs none of the + * double split allowance that xfs_attr_calc_size() makes. + * + * The second term hands a byte count to a macro whose parameter counts + * mappings, so it asks for more extent-add allowance than the single + * mapping a parent pointer adds - how much more depends on the block + * size. It over-reserves either way, which is why it is left alone: + * correcting the unit would shrink a reservation that is only generous. */ return XFS_DAENTER_SPACE_RES(mp, XFS_ATTR_FORK) + XFS_NEXTENTADD_SPACE_RES(mp, namelen, XFS_ATTR_FORK); From 8e4ebb6afaa34bd2e8ce52da231003d24111c2d6 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Mon, 10 Aug 2026 17:06:17 -0600 Subject: [PATCH 0994/2366] xfs: initialise args->total for parent pointer updates xfs_parent_da_args_init() builds an xfs_da_args from a zeroed xfs_parent_args (kmem_cache_zalloc), leaving args->total == 0. xfs_da_grow_inode_int() treats that field as a running block reservation and subtracts from it; because it is an xfs_extlen_t (uint32_t), the first attr-fork growth wraps it to ~0U. That defeats the free-space check in xfs_alloc_space_available(), and when it coincides with an AG that has exactly zero available blocks the allocation is clamped to maxlen 0 and returns -ENOSPC, which xfs_defer_finish_noroll() escalates to a filesystem shutdown. Set args->total the way the log recovery path does (xfs_attri_recover_work(), xfs_attr_item.c:706), in the add and replace paths that can grow the fork. Removals and lookups never grow it, so they leave the field alone, matching that switch. Fixes: b7c62d90c12c ("xfs: parent pointer attribute creation") Cc: stable@vger.kernel.org # v6.10 Signed-off-by: Javier Tia Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_parent.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_parent.c b/fs/xfs/libxfs/xfs_parent.c index 8d111c9b6527..a2f2f5fa640e 100644 --- a/fs/xfs/libxfs/xfs_parent.c +++ b/fs/xfs/libxfs/xfs_parent.c @@ -193,7 +193,7 @@ xfs_parent_addname( const struct xfs_name *parent_name, struct xfs_inode *child) { - int error; + int error, local; error = xfs_parent_iread_extents(tp, child); if (error) @@ -203,6 +203,10 @@ xfs_parent_addname( xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child, I_INO(child), parent_name); + /* Growing the attr fork needs a real reservation in args->total. */ + ppargs->args.total = xfs_attr_calc_size(&ppargs->args, &local); + ASSERT(local); + return xfs_attr_setname(&ppargs->args, 0); } @@ -239,7 +243,7 @@ xfs_parent_replacename( const struct xfs_name *new_name, struct xfs_inode *child) { - int error; + int error, local; error = xfs_parent_iread_extents(tp, child); if (error) @@ -249,6 +253,10 @@ xfs_parent_replacename( xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child, I_INO(child), old_name); + /* Growing the attr fork needs a real reservation in args->total. */ + ppargs->args.total = xfs_attr_calc_size(&ppargs->args, &local); + ASSERT(local); + xfs_inode_to_parent_rec(&ppargs->new_rec, new_dp); ppargs->args.new_name = new_name->name; From 0fe77e57588b989450d668f7c978bb0264c5c340 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Mon, 10 Aug 2026 17:06:18 -0600 Subject: [PATCH 0995/2366] xfs: assert the reservation covers each da fork growth xfs_da_grow_inode_int() subtracts the blocks it just allocated from args->total, the caller's remaining block reservation. The subtraction is unsigned, so a caller that reaches it with too small a total wraps the field instead of failing, and every allocation afterwards runs with a bogus reservation. Assert the remaining reservation still covers the step, so an under-reserved or uninitialised total trips in debug builds instead of silently wrapping. Suggested-by: Darrick J. Wong Signed-off-by: Javier Tia Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_da_btree.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/libxfs/xfs_da_btree.c b/fs/xfs/libxfs/xfs_da_btree.c index 7938d2324e87..8cbdd6574755 100644 --- a/fs/xfs/libxfs/xfs_da_btree.c +++ b/fs/xfs/libxfs/xfs_da_btree.c @@ -2384,6 +2384,7 @@ xfs_da_grow_inode_int( } /* account for newly allocated blocks in reserved blocks total */ + ASSERT(args->total >= dp->i_nblocks - nblks); args->total -= dp->i_nblocks - nblks; out_free_map: From 9f84792b40d0c96833341602144574bf0bd14a6a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 26 Aug 2026 22:31:10 -0700 Subject: [PATCH 0996/2366] xfs: don't spin forever on zero-length dirents when salvaging them LOLLM noticed that xrep_dir_recover_data can spin forever if it encounters an unused dirent that claims to have length zero. Fix that, and prevent the same thing from happening with a zero-length entry. Cc: stable@vger.kernel.org # v6.10 Fixes: b1991ee3e7cf85 ("xfs: online repair of directories") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir_repair.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index 1c088cfba10e..0c1224d05d57 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -484,18 +484,24 @@ xrep_dir_recover_data( while (offset < end) { struct xfs_dir2_data_unused *dup = bp->b_addr + offset; struct xfs_dir2_data_entry *dep = bp->b_addr + offset; + unsigned int advance; if (xchk_should_terminate(rd->sc, &error)) return error; /* Skip unused entries. */ if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { + if (!dup->length) + break; offset += be16_to_cpu(dup->length); continue; } /* Don't walk off the end of the block. */ - offset += xfs_dir2_data_entsize(rd->sc->mp, dep->namelen); + advance = xfs_dir2_data_entsize(rd->sc->mp, dep->namelen); + if (!advance) + break; + offset += advance; if (offset > end) break; From 865b751e75039fc07838b3200f9740256653da9a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 26 Aug 2026 22:31:25 -0700 Subject: [PATCH 0997/2366] xfs: don't stash removename operations with unknown ftype LOLLM notices that the behavior of xrep_dir_replay_update changes based on the ftype recorded in the stashed removename information. It also notices that the unlink iops sometimes set that ftype to FT_UNKNOWN because the regular directory tree update code paths don't need to know the ftype of the child. Unfortunately, this results in incorrect link counts, which eventually trips link count errors in later phases of xfs_scrub, or in xfs_repair. Fix this by creating a second xfs_name with the type set correctly. Cc: stable@vger.kernel.org # v6.10 Fixes: 8559b21a64d983 ("xfs: implement live updates for directory repairs") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir_repair.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index 0c1224d05d57..31a23c5f386a 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -1381,9 +1381,24 @@ xrep_dir_live_update( if (p->delta > 0) error = xrep_dir_stash_createname(rd, p->name, I_INO(p->ip)); - else - error = xrep_dir_stash_removename(rd, p->name, + else { + /* + * xfs_dentry_to_name in unlink or rename-exchange can + * pass us names with ftype FT_UNKNOWN, but we really + * must know the ftype of the child that is being + * removed so that we can do nlink updates correctly + * without holding inode references. + */ + struct xfs_name name = { + .name = p->name->name, + .len = p->name->len, + .type = xfs_mode_to_ftype( + VFS_IC(p->ip)->i_mode), + }; + + error = xrep_dir_stash_removename(rd, &name, I_INO(p->ip)); + } mutex_unlock(&rd->pscan.lock); if (error) goto out_abort; From ed799148e0d63ef41ab60ce98963a1dc423090d3 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 26 Aug 2026 22:31:41 -0700 Subject: [PATCH 0998/2366] xfs: log the tempip after we convert it to extents format LOLLM points out that xrep_symlink_swap_prep converts sc->tempip to an extents format file prior to the atomic swap, but incorrectly logs sc->ip immediately afterwards. Fix that, and the other problem that we're supposed to tell xfs_trans_log_inode what to log and don't. Cc: stable@vger.kernel.org # v6.10 Fixes: 2651923d8d8db0 ("xfs: online repair of symbolic links") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/symlink_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/symlink_repair.c b/fs/xfs/scrub/symlink_repair.c index 91c86ea0e0f1..181961364233 100644 --- a/fs/xfs/scrub/symlink_repair.c +++ b/fs/xfs/scrub/symlink_repair.c @@ -291,7 +291,7 @@ xrep_symlink_swap_prep( if (error) return error; - xfs_trans_log_inode(sc->tp, sc->ip, 0); + xfs_trans_log_inode(sc->tp, sc->tempip, logflags); error = xfs_defer_finish(&sc->tp); if (error) From cdc4a083adf7bf15a0722c5bd292a35e00112006 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 26 Aug 2026 22:31:56 -0700 Subject: [PATCH 0999/2366] xfs: fix parent rec lookup initialization in xrep_metapath_unlink LOLLM notices that xrep_metapath_unlink looks for a parent pointer in the child metafile that it's removing, but initializes the parent handle using the child. This is obviously incorrect, so fix that. Cc: stable@vger.kernel.org # v6.13 Fixes: 0d2c636e489c11 ("xfs: repair metadata directory file path connectivity") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/metapath.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/metapath.c b/fs/xfs/scrub/metapath.c index ff1ff762b300..9f44c82910ea 100644 --- a/fs/xfs/scrub/metapath.c +++ b/fs/xfs/scrub/metapath.c @@ -397,7 +397,7 @@ xrep_metapath_unlink( /* Figure out if we're removing a parent pointer too. */ if (xfs_has_parent(mp)) { - xfs_inode_to_parent_rec(&rec, ip); + xfs_inode_to_parent_rec(&rec, mpath->dp); error = xfs_parent_lookup(sc->tp, ip, &mpath->xname, &rec, &mpath->pptr_args); switch (error) { From 9dd6cc92a736a7943dd29776b6eb1938c7f9b94c Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 26 Aug 2026 22:32:12 -0700 Subject: [PATCH 1000/2366] xfs: handle reconnecting metadir subdirectories A longstanding weakness of the metapath repair code is that it can only reattach non-directories to the metadata directory tree. Let's fix that by allowing reconnection of subdirectories. Note that with the initial users of metadir (rtgroups and quota), there's no way to mount a filesystem with broken /rtgroups or /quota subdirectories, so this code won't be all that useful until something adds deeper directory trees. But we shouldn't leave a logic bomb for those futures users wherein we get the link count wrong for a subdir. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/metapath.c | 83 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/metapath.c b/fs/xfs/scrub/metapath.c index 9f44c82910ea..a760523f40e6 100644 --- a/fs/xfs/scrub/metapath.c +++ b/fs/xfs/scrub/metapath.c @@ -23,6 +23,7 @@ #include "xfs_rtgroup.h" #include "xfs_rtrmap_btree.h" #include "xfs_rtrefcount_btree.h" +#include "xfs_ag.h" #include "scrub/scrub.h" #include "scrub/common.h" #include "scrub/trace.h" @@ -348,12 +349,78 @@ xchk_metapath( } #ifdef CONFIG_XFS_ONLINE_REPAIR +/* + * Given a directory @dp, an existing inode @ip, and a @name, link @ip into @dp + * under the given @name. + */ +static int +xrep_metadir_add_child( + struct xchk_metapath *mpath, + xfs_ino_t old_dotdot) +{ + struct xfs_trans *tp = mpath->sc->tp; + struct xfs_dir_update *du = &mpath->du; + struct xfs_inode *dp = du->dp; + const struct xfs_name *name = du->name; + struct xfs_inode *ip = du->ip; + struct xfs_mount *mp = tp->t_mountp; + const unsigned int resblks = mpath->link_resblks; + int error; + + /* + * The metadata file shouldn't be on the unlinked list, but we'll fix + * it if that is the case. + */ + if (VFS_I(ip)->i_nlink == 0) { + struct xfs_perag *pag; + + pag = xfs_perag_get(mp, XFS_INO_TO_AGNO(mp, I_INO(ip))); + error = xfs_iunlink_remove(tp, pag, ip); + xfs_perag_put(pag); + if (error) + return error; + } + + error = xfs_dir_createname(tp, dp, name, I_INO(ip), resblks); + if (error) + return error; + + xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE); + + xfs_bumplink(tp, ip); + + /* update dotdot entry in child */ + if (S_ISDIR(VFS_I(ip)->i_mode)) { + xfs_bumplink(tp, dp); + + /* Replace the dotdot entry in the child */ + if (old_dotdot != I_INO(dp)) { + error = xfs_dir_replace(tp, ip, &xfs_name_dotdot, + I_INO(dp), resblks); + if (error) + return error; + } + } + + /* Update the child's parent pointer */ + if (du->ppargs) { + error = xfs_parent_addname(tp, du->ppargs, dp, name, ip); + if (error) + return error; + } + + xfs_dir_update_hook(dp, ip, 1, name); + return 0; +} + /* Create the dirent represented by the final component of the path. */ STATIC int xrep_metapath_link( struct xchk_metapath *mpath) { struct xfs_scrub *sc = mpath->sc; + xfs_ino_t old_dotdot = NULLFSINO; + int error; mpath->du.dp = mpath->dp; mpath->du.name = &mpath->xname; @@ -366,7 +433,21 @@ xrep_metapath_link( trace_xrep_metapath_link(sc, mpath->path, mpath->dp, I_INO(sc->ip)); - return xfs_dir_add_child(sc->tp, mpath->link_resblks, &mpath->du); + if (S_ISDIR(VFS_I(sc->ip)->i_mode)) { + error = xchk_dir_lookup(sc, sc->ip, &xfs_name_dotdot, + &old_dotdot); + if (error && error != -ENOENT) + return error; + + /* + * subdir didn't give us a dotdot entry, so we just give up + * and let the repair get marked as failed. + */ + if (old_dotdot == NULLFSINO) + return 0; + } + + return xrep_metadir_add_child(mpath, old_dotdot); } /* Remove the dirent at the final component of the path. */ From 7538ba528cfd6f176076186c1b1678fdca1c197b Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 26 Aug 2026 22:32:28 -0700 Subject: [PATCH 1001/2366] xfs: lock the healthmon when inserting unmount event LOLLM complains that xfs_healthmon_unmount does an unlocked insert of the unmount event into the health monitor's event list. Fix that. Cc: stable@vger.kernel.org # v7.0 Fixes: 25ca57fa3624ca ("xfs: convey filesystem unmount events to the health monitor") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index 4521ffdab9f1..3ae5f4496ad1 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -272,6 +272,8 @@ __xfs_healthmon_insert( { struct timespec64 now; + lockdep_assert_held(&hm->lock); + ktime_get_coarse_real_ts64(&now); event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; @@ -294,6 +296,8 @@ __xfs_healthmon_push( { struct timespec64 now; + lockdep_assert_held(&hm->lock); + ktime_get_coarse_real_ts64(&now); event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; @@ -415,8 +419,10 @@ xfs_healthmon_unmount( * There's nothing actionable for userspace after an unmount. Once * we've inserted the unmount event, hm no longer owns that event. */ + mutex_lock(&hm->lock); __xfs_healthmon_insert(hm, hm->unmount_event); hm->unmount_event = NULL; + mutex_unlock(&hm->lock); xfs_healthmon_detach(hm); xfs_healthmon_put(hm); From 4164b1e3d728c7cc05e0c1171068aa046542ecc3 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Fri, 21 Aug 2026 17:03:37 -0500 Subject: [PATCH 1002/2366] xfs: fix reclaimed page accounting in xfs_buf_free To obtain nr. of pages in "size" bytes, we need howmany(size, PAGE_SIZE) not howmany(size, PAGE_SHIFT). This over-reports reclaim by orders of magnitude, up to 4096x on a 64k page system. Fixes: e2874632a621 ("xfs: use vmalloc instead of vm_map_area for buffer backing memory") Cc: stable@vger.kernel.org # v6.15+ Signed-off-by: Eric Sandeen Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index ee7c2e9c0340..836515e8feee 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -139,7 +139,7 @@ xfs_buf_free( ASSERT(list_empty(&bp->b_lru)); if (!xfs_buftarg_is_mem(bp->b_target) && size >= PAGE_SIZE) - mm_account_reclaimed_pages(howmany(size, PAGE_SHIFT)); + mm_account_reclaimed_pages(howmany(size, PAGE_SIZE)); if (is_vmalloc_addr(bp->b_addr)) vfree(bp->b_addr); From 5344402f2c65984cee01a053fd1fa92c44b6aa7d Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Fri, 21 Aug 2026 17:26:39 -0500 Subject: [PATCH 1003/2366] xfs: mark slab-allocated xfs_buf backing memory as __GFP_RECLAIMABLE xfs_bufs have a shrinker and are therefore reclaimable, as is the memory backing them. Mark slab-allocated backing memory as __GFP_RECLAIMABLE in the kmalloc path so that it is accounted properly. Signed-off-by: Eric Sandeen Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index 836515e8feee..8256c1d13ce2 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -176,7 +176,7 @@ xfs_buf_alloc_kmem( ASSERT(is_power_of_2(size)); ASSERT(size < PAGE_SIZE); - bp->b_addr = kmalloc(size, gfp_mask); + bp->b_addr = kmalloc(size, gfp_mask | __GFP_RECLAIMABLE); if (!bp->b_addr) return -ENOMEM; From 48d2b8351bae6d40b44f544fe5540868a55872b2 Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Tue, 1 Sep 2026 11:13:48 +0530 Subject: [PATCH 1004/2366] xfs: release alleged child inode on metapath unlink error If xchk_metapath_ilock_parent_and_child() fails after xchk_iget() succeeds, release the inode reference before returning. Fixes: 0d2c636e489c ("xfs: repair metadata directory file path connectivity") Cc: stable@vger.kernel.org # v6.13 Signed-off-by: Anuj Gupta Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/metapath.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/scrub/metapath.c b/fs/xfs/scrub/metapath.c index a760523f40e6..e0ee7d9b903f 100644 --- a/fs/xfs/scrub/metapath.c +++ b/fs/xfs/scrub/metapath.c @@ -637,6 +637,8 @@ xrep_metapath_try_unlink( error = xchk_metapath_ilock_parent_and_child(mpath, ip); if (error) { xchk_trans_cancel(sc); + if (ip) + xchk_irele(sc, ip); return error; } xfs_trans_ijoin(sc->tp, mpath->dp, 0); From 05cff7c2b79f76c7cfe90613a60e16aaaa051ef7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:41:59 -0700 Subject: [PATCH 1005/2366] xfs: fix unit conversions in per_binval computation LOLLM noticed that we're doing the unit conversion in the per_binval computation backwards -- xfs_buf_inval_log_space's second parameter is supposed to be in bytes, but max_binval is in units of fsblocks. Hence the conversion should be FSB -> B, not the other way around. Cc: stable@vger.kernel.org # v6.18 Fixes: b2311ec6778fcd ("xfs: compute per-AG extent reap limits dynamically") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/reap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/reap.c b/fs/xfs/scrub/reap.c index fcd14c1703ea..496c6eab555e 100644 --- a/fs/xfs/scrub/reap.c +++ b/fs/xfs/scrub/reap.c @@ -601,7 +601,7 @@ xreap_configure_agextent_limits( /* Maximum overhead of invalidating one buffer. */ const unsigned int per_binval = - xfs_buf_inval_log_space(1, XFS_B_TO_FSBT(mp, max_binval)); + xfs_buf_inval_log_space(1, XFS_FSB_TO_B(mp, max_binval)); /* * For each transaction in a reap chain, we can delete some number of @@ -680,7 +680,7 @@ xreap_configure_agcow_limits( /* Overhead of invalidating one buffer */ const unsigned int per_binval = - xfs_buf_inval_log_space(1, XFS_B_TO_FSBT(mp, max_binval)); + xfs_buf_inval_log_space(1, XFS_FSB_TO_B(mp, max_binval)); /* * For each transaction in a reap chain, we can delete some number of From eacb8479507756c3305994e87fb2fb1183827e98 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:42:15 -0700 Subject: [PATCH 1006/2366] xfs: fix short ifork reaping computation in xreap_bmapi_binval LOLLM got really confused about the update to imap->br_blockcount in xreap_bmapi_binval if xreap_inc_binval returns false. The intent of this code is that we shorten the imap to whatever length of space we invalidated so that the next iteration through the loop will start wherever we left off. Unfortunately, the calculation sets br_blockcount to the amount of *unfinished* work, which means that we pointlessly re-scan blocks that we already reaped. This is benign, but we should fix the computation anyway. Cc: stable@vger.kernel.org # v6.10 Fixes: 5befb047b9f4de ("xfs: add the ability to reap entire inode forks") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/reap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/reap.c b/fs/xfs/scrub/reap.c index 496c6eab555e..f698b9be3dd1 100644 --- a/fs/xfs/scrub/reap.c +++ b/fs/xfs/scrub/reap.c @@ -1399,7 +1399,7 @@ xreap_bmapi_binval( * far we've gotten. */ if (!xreap_inc_binval(rs)) { - imap->br_blockcount = agbno_next - bno; + imap->br_blockcount = bno - agbno; goto out; } } From 72d0a3e4405c1353869bfbbc67c7b8a29df7afd0 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:42:31 -0700 Subject: [PATCH 1007/2366] xfs: fix name string recording in slowpath pptr tracepoints LOLLM observes that we memcpy from the xfs_name object, not the name string pointed to by the xfs_name. Fix that. Cc: stable@vger.kernel.org # v6.10 Fixes: b961c8bf1fc3d0 ("xfs: deferred scrub of dirents") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/trace.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 14aa0ec1f09e..362c6d39e9f5 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -1640,7 +1640,7 @@ DECLARE_EVENT_CLASS(xchk_pptr_class, __entry->dev = ip->i_mount->m_super->s_dev; __entry->ino = I_INO(ip); __entry->namelen = name->len; - memcpy(__get_str(name), name, name->len); + memcpy(__get_str(name), name->name, name->len); __entry->far_ino = far_ino; ), TP_printk("dev %d:%d ino 0x%llx name '%.*s' far_ino 0x%llx", From 2eac8d01d2c776fc26b0ac74aebdf91bc4490891 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:42:47 -0700 Subject: [PATCH 1008/2366] xfs: don't leak dqacct if rhashtable insertion fails LOLLM observes that xqcheck_mod_live_ino_dqtrx doesn't free the newly allocated dqa object if rhashtable insertion fails. Fix this leak. Cc: stable@vger.kernel.org # v6.9 Fixes: 200491875ce144 ("xfs: track quota updates during live quotacheck") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quotacheck.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/quotacheck.c b/fs/xfs/scrub/quotacheck.c index c199d128538e..c32030a05440 100644 --- a/fs/xfs/scrub/quotacheck.c +++ b/fs/xfs/scrub/quotacheck.c @@ -263,8 +263,10 @@ xqcheck_mod_live_ino_dqtrx( dqa->tx_id = p->tx_id; error = rhashtable_insert_fast(&xqc->shadow_dquot_acct, &dqa->hash, xqcheck_dqacct_hash_params); - if (error) + if (error) { + kfree(dqa); goto out_abort; + } } /* Find the shadow dqtrx (or an empty slot) here. */ From aa301322f72f82f26e4ba0826018d41388ab9896 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:43:02 -0700 Subject: [PATCH 1009/2366] xfs: fix the rtrmap and rtrefcount _maxlevels_ondisk functions The _maxlevels_ondisk functions are used to compute the size of in-memory btree cursors for each btree type. Unfortunately, LOLLM noticed that the rtrmap and rtrefcount versions of these functions forget to account for the inode root, which means that we could access beyond the end of the cursor given a sufficiently large btree. Fix this. Cc: stable@vger.kernel.org # v6.14 Fixes: 9abe03a0e4f978 ("xfs: introduce realtime refcount btree ondisk definitions") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtrefcount_btree.c | 7 +++++-- fs/xfs/libxfs/xfs_rtrmap_btree.c | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c index 22acc1411aac..e2950dbe2068 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c @@ -489,8 +489,11 @@ xfs_rtrefcountbt_maxlevels_ondisk(void) minrecs[0] = xfs_rtrefcountbt_block_maxrecs(blocklen, true) / 2; minrecs[1] = xfs_rtrefcountbt_block_maxrecs(blocklen, false) / 2; - /* We need at most one record for every block in an rt group. */ - return xfs_btree_compute_maxlevels(minrecs, XFS_MAX_RGBLOCKS); + /* + * We need at most one record for every block in an rt group, and + * one extra level for the inode root. + */ + return xfs_btree_compute_maxlevels(minrecs, XFS_MAX_RGBLOCKS) + 1; } int __init diff --git a/fs/xfs/libxfs/xfs_rtrmap_btree.c b/fs/xfs/libxfs/xfs_rtrmap_btree.c index c264bc5651c0..0cb2113d5b40 100644 --- a/fs/xfs/libxfs/xfs_rtrmap_btree.c +++ b/fs/xfs/libxfs/xfs_rtrmap_btree.c @@ -716,10 +716,12 @@ xfs_rtrmapbt_maxlevels_ondisk(void) * happens, which means that we must compute the max height based on * what the btree will look like if it consumes almost all the blocks * in the data device due to maximal sharing factor. + * + * Add one extra level for the inode root. */ max_dblocks = -1U; /* max ag count */ max_dblocks *= XFS_MAX_CRC_AG_BLOCKS; - return xfs_btree_space_to_height(minrecs, max_dblocks); + return xfs_btree_space_to_height(minrecs, max_dblocks) + 1; } int __init From 022d5f5fce7f0b6125d404eb74044e6238aef369 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:43:34 -0700 Subject: [PATCH 1010/2366] xfs: fix xfs_rtrmapbt_mem_cursor for non-rmap filesystems It's possible to construct an in-memory rtrmap btree for filesystems that don't have the rmap feature enabled. The kernel doesn't do this, but xfs_repair will, if asked to reindex a filesystem that has rtreflink enabled but not rtrmap. Therefore, we must create the cursor with enough levels to handle a maximally sized btree possible. Note that the rtrmapbt btree cursor slab creates objects large enough to handle xfs_rtrmap_maxlevels_ondisk() levels, so setting bc_nlevels to the same value isn't costing us any extra memory. Cc: stable@vger.kernel.org # v6.14 Fixes: 4a61f12eb11958 ("xfs: create a shadow rmap btree during realtime rmap repair") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtrmap_btree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_rtrmap_btree.c b/fs/xfs/libxfs/xfs_rtrmap_btree.c index 0cb2113d5b40..a15e460a1ec7 100644 --- a/fs/xfs/libxfs/xfs_rtrmap_btree.c +++ b/fs/xfs/libxfs/xfs_rtrmap_btree.c @@ -618,7 +618,7 @@ xfs_rtrmapbt_mem_cursor( struct xfs_btree_cur *cur; cur = xfs_btree_alloc_cursor(mp, tp, &xfs_rtrmapbt_mem_ops, - mp->m_rtrmap_maxlevels, xfs_rtrmapbt_cur_cache); + xfs_rtrmapbt_maxlevels_ondisk(), xfs_rtrmapbt_cur_cache); cur->bc_mem.xfbtree = xfbt; cur->bc_nlevels = xfbt->nlevels; cur->bc_group = xfs_group_hold(rtg_group(rtg)); From 5287e56cba3be4a64bff9f73bce5964fda2590dc Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:43:50 -0700 Subject: [PATCH 1011/2366] xfs: preserve owner on in-memory btree creation LOLLM points out a minor bug where a higher level function creating an in-memory btree is required to pass in an owner number, but the creation function erases that. In-memory btrees are ephemeral so this really doesn't matter except for debugging. But let's fix this papercut. Cc: stable@vger.kernel.org # v6.9 Fixes: a095686a238352 ("xfs: support in-memory btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_btree_mem.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/libxfs/xfs_btree_mem.c b/fs/xfs/libxfs/xfs_btree_mem.c index 37136a70e56d..1d83a4251cee 100644 --- a/fs/xfs/libxfs/xfs_btree_mem.c +++ b/fs/xfs/libxfs/xfs_btree_mem.c @@ -117,6 +117,7 @@ xfbtree_init( struct xfs_buftarg *btp, const struct xfs_btree_ops *ops) { + unsigned long long owner = xfbt->owner; unsigned int blocklen = xfbtree_rec_bytes(mp, ops); unsigned int keyptr_len; int error; @@ -133,6 +134,7 @@ xfbtree_init( memset(xfbt, 0, sizeof(*xfbt)); xfbt->target = btp; + xfbt->owner = owner; /* Set up min/maxrecs for this btree. */ keyptr_len = ops->key_len + sizeof(__be64); From f1930bc578095409c2dcfca6e4e898b24f0f0de6 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:44:05 -0700 Subject: [PATCH 1012/2366] xfs: don't leak new_bp if xfs_btree_bload_drop_buf fails LOLLM observes that in xfs_btree_bload_prep_block, xfs_btree_bload_drop_buf can hit an IO error if writing the delwri buffer list to disk fails. In this case, we fail to release new_bp, which means we lose a locked buffer. Fix that. Cc: stable@vger.kernel.org # v6.8 Fixes: e069d549705e49 ("xfs: constrain dirty buffers while formatting a staged btree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_btree_staging.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_btree_staging.c b/fs/xfs/libxfs/xfs_btree_staging.c index 7314dab4bcfb..561fd2c2e950 100644 --- a/fs/xfs/libxfs/xfs_btree_staging.c +++ b/fs/xfs/libxfs/xfs_btree_staging.c @@ -336,8 +336,10 @@ xfs_btree_bload_prep_block( xfs_btree_set_sibling(cur, *blockp, &new_ptr, XFS_BB_RIGHTSIB); ret = xfs_btree_bload_drop_buf(bbl, buffers_list, bpp); - if (ret) + if (ret) { + xfs_buf_relse(new_bp); return ret; + } /* Initialize the new btree block. */ xfs_btree_init_block_cur(cur, new_bp, level, nr_this_block); From b71ae66863e4320a3b7313b53bb4d65f1718d58b Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:44:21 -0700 Subject: [PATCH 1013/2366] xfs: actually recover intended file sizes in xfs_xmi_item_recover_intent LOLLM points out that xfs_xmi_item_recover_intent doesn't actually restore the isize1 and isize2 fields that were recovered from an unfinished exchmaps log intent item. Instead, xfs_exchmaps_init_intent sets the wrong isize values from the recovered inodes, with the result that the file sizes are not set correctly when item recovery finishes. Fix this by restoring isize[12] from the log item. Cc: stable@vger.kernel.org # v6.10 Fixes: 966ceafc7a4371 ("xfs: create deferred log items for file mapping exchanges") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_exchmaps_item.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_exchmaps_item.c b/fs/xfs/xfs_exchmaps_item.c index c3745d33e54e..dd5d92ca1010 100644 --- a/fs/xfs/xfs_exchmaps_item.c +++ b/fs/xfs/xfs_exchmaps_item.c @@ -344,7 +344,17 @@ xfs_xmi_validate( if (!xfs_verify_fileext(mp, xlf->xmi_startoff1, xlf->xmi_blockcount)) return false; - return xfs_verify_fileext(mp, xlf->xmi_startoff2, xlf->xmi_blockcount); + if (!xfs_verify_fileext(mp, xlf->xmi_startoff2, xlf->xmi_blockcount)) + return false; + + if (xlf->xmi_flags & XFS_EXCHMAPS_SET_SIZES) { + if ((int64_t)xlf->xmi_isize1 < 0) + return false; + if ((int64_t)xlf->xmi_isize2 < 0) + return false; + } + + return true; } /* @@ -403,6 +413,13 @@ xfs_xmi_item_recover_intent( *ipp1 = ip1; *ipp2 = ip2; xmi = xfs_exchmaps_init_intent(req); + + /* Restore intended file sizes from recovered logged item */ + if (req->flags & XFS_EXCHMAPS_SET_SIZES) { + xmi->xmi_isize1 = xlf->xmi_isize1; + xmi->xmi_isize2 = xlf->xmi_isize2; + } + xfs_defer_add_item(dfp, &xmi->xmi_list); return xmi; From 365fe37e10ea75840165f13322aa8481ea11dfef Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:44:37 -0700 Subject: [PATCH 1014/2366] xfs: don't modify file attributes or poke fsnotify for dry runs I noticed that we shouldn't be removing file privileges when doing a dry run of an exchange-range operation. LOLLM also points out that a dry run shouldn't poke fsnotify because we don't actually change the files. Fix both by gating them on !DRY_RUN. Cc: stable@vger.kernel.org # v6.10 Fixes: 42672471f938cd ("xfs: bind together the front and back ends of the file range exchange code") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_exchrange.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index 94965a6c2187..c69ecd6a19de 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -504,6 +504,9 @@ xfs_exchange_range_finish( { int error; + if (fxr->flags & XFS_EXCHANGE_RANGE_DRY_RUN) + return 0; + error = file_remove_privs(fxr->file1); if (error) return error; @@ -783,9 +786,12 @@ xfs_exchange_range( if (ret) return ret; - fsnotify_modify(fxr->file1); - if (fxr->file2 != fxr->file1) - fsnotify_modify(fxr->file2); + if (!(fxr->flags & XFS_EXCHANGE_RANGE_DRY_RUN)) { + fsnotify_modify(fxr->file1); + if (fxr->file2 != fxr->file1) + fsnotify_modify(fxr->file2); + } + return 0; } From c83d1ef97ee3b0b92797d4b4932a3a813ba05b86 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:44:52 -0700 Subject: [PATCH 1015/2366] xfs: fix bnobt repair space reservation disposal failure LOLLM complains that we don't bubble failures from xrep_abt_dispose_one upwards in the callstack. A failure to clean up the space used (or reserved but not used) by the new bnobt/cntbt should be reported. Cc: stable@vger.kernel.org # v6.8 Fixes: 4bdfd7d15747b1 ("xfs: repair free space btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/alloc_repair.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/fs/xfs/scrub/alloc_repair.c b/fs/xfs/scrub/alloc_repair.c index dce6ab0429dc..95e318e4f3a6 100644 --- a/fs/xfs/scrub/alloc_repair.c +++ b/fs/xfs/scrub/alloc_repair.c @@ -571,7 +571,7 @@ xrep_abt_dispose_one( * allocation, and blocks that didn't get used can be freed via the usual * (deferred) means. */ -STATIC void +STATIC int xrep_abt_dispose_reservations( struct xrep_abt *ra, int error) @@ -582,9 +582,13 @@ xrep_abt_dispose_reservations( goto junkit; list_for_each_entry_safe(resv, n, &ra->new_bnobt.resv_list, list) { - error = xrep_abt_dispose_one(ra, resv); - if (error) + int error2 = xrep_abt_dispose_one(ra, resv); + + if (error2) { + if (!error) + error = error2; goto junkit; + } } junkit: @@ -596,6 +600,7 @@ xrep_abt_dispose_reservations( xrep_newbt_cancel(&ra->new_bnobt); xrep_newbt_cancel(&ra->new_cntbt); + return error; } /* Retrieve free space data for bulk load. */ @@ -801,7 +806,9 @@ xrep_abt_build_new_trees( goto err_newbt; /* Dispose of any unused blocks and the accounting information. */ - xrep_abt_dispose_reservations(ra, error); + error = xrep_abt_dispose_reservations(ra, error); + if (error) + return error; return xrep_roll_ag_trans(sc); @@ -812,8 +819,7 @@ xrep_abt_build_new_trees( xfs_btree_del_cursor(cnt_cur, error); xfs_btree_del_cursor(bno_cur, error); err_newbt: - xrep_abt_dispose_reservations(ra, error); - return error; + return xrep_abt_dispose_reservations(ra, error); } /* From 58a0c7578b25b578c16dea7db2493cfa3a08ecc2 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:46:41 -0700 Subject: [PATCH 1016/2366] xfs: always set xfs_healthmon::first_event when inserting at front of list LOLLM complains that while __xfs_healthmon_insert is supposed to insert an event at the head of the list, it doesn't do that correctly if the list isn't empty. In that case it *should* make our new event point to the current head, and then make the head point to the new event, but it doesn't actually update the head so we never see the new event. Fix this by always reassigning first_event. A subsequent patch will clean this up to use a standard list_head, but I felt it important to call out the bug fix first. Cc: stable@vger.kernel.org # v7.0 Fixes: b3a289a2a9397b ("xfs: create event queuing, formatting, and discovery infrastructure") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Reviewed-by: Anuj Gupta Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index 3ae5f4496ad1..a4efc084a8fc 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -278,8 +278,7 @@ __xfs_healthmon_insert( event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; event->next = hm->first_event; - if (!hm->first_event) - hm->first_event = event; + hm->first_event = event; if (!hm->last_event) hm->last_event = event; xfs_healthmon_bump_events(hm); From 10b1d5fd7189986a0cdd90dde181089b4b2fe40e Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:46:56 -0700 Subject: [PATCH 1017/2366] xfs: move healthmon event merge tracepoint Move the tracepoint into the predicate function so that the list conversion in the next patch is easier. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index a4efc084a8fc..acd41a2b3a1c 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -192,7 +192,7 @@ xfs_healthmon_merge_events( case XFS_HEALTHMON_LOST: existing->lostcount += new->lostcount; - return true; + goto out_merge; case XFS_HEALTHMON_SICK: case XFS_HEALTHMON_CORRUPT: @@ -200,19 +200,19 @@ xfs_healthmon_merge_events( switch (existing->domain) { case XFS_HEALTHMON_FS: existing->fsmask |= new->fsmask; - return true; + goto out_merge; case XFS_HEALTHMON_AG: case XFS_HEALTHMON_RTGROUP: if (existing->group == new->group){ existing->grpmask |= new->grpmask; - return true; + goto out_merge; } return false; case XFS_HEALTHMON_INODE: if (existing->ino == new->ino && existing->gen == new->gen) { existing->imask |= new->imask; - return true; + goto out_merge; } return false; default: @@ -224,18 +224,18 @@ xfs_healthmon_merge_events( case XFS_HEALTHMON_SHUTDOWN: /* yes, we can race to shutdown */ existing->flags |= new->flags; - return true; + goto out_merge; case XFS_HEALTHMON_MEDIA_ERROR: /* physically adjacent errors can merge */ if (existing->daddr + existing->bbcount == new->daddr) { existing->bbcount += new->bbcount; - return true; + goto out_merge; } if (new->daddr + new->bbcount == existing->daddr) { existing->daddr = new->daddr; existing->bbcount += new->bbcount; - return true; + goto out_merge; } return false; @@ -250,18 +250,22 @@ xfs_healthmon_merge_events( if (existing->fpos + existing->flen == new->fpos) { existing->flen += new->flen; - return true; + goto out_merge; } if (new->fpos + new->flen == existing->fpos) { existing->fpos = new->fpos; existing->flen += new->flen; - return true; + goto out_merge; } return false; } return false; + +out_merge: + trace_xfs_healthmon_merge(hm, existing); + return true; } /* Insert an event onto the start of the queue. */ @@ -325,7 +329,6 @@ xfs_healthmon_clear_lost_prev( struct xfs_healthmon_event *event = NULL; if (xfs_healthmon_merge_events(hm->last_event, &lost_event)) { - trace_xfs_healthmon_merge(hm, hm->last_event); wake_up(&hm->wait); goto cleared; } @@ -373,7 +376,6 @@ xfs_healthmon_push( /* Try to merge with the newest event */ if (xfs_healthmon_merge_events(hm->last_event, template)) { - trace_xfs_healthmon_merge(hm, hm->last_event); wake_up(&hm->wait); goto out_unlock; } From 9097f5c03f038cea83d174b6d322d7b2678a3c99 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:47:12 -0700 Subject: [PATCH 1018/2366] xfs: port healthmon event list to list_head Simplify the healthmon codebase by porting the single-link event list to a standard list_head. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 36 +++++++++++++++--------------------- fs/xfs/xfs_healthmon.h | 5 ++--- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index acd41a2b3a1c..073b74e490df 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -87,12 +87,10 @@ xfs_healthmon_put( struct xfs_healthmon *hm) { if (refcount_dec_and_test(&hm->ref)) { - struct xfs_healthmon_event *event; - struct xfs_healthmon_event *next = hm->first_event; + struct xfs_healthmon_event *event, *s; - while ((event = next) != NULL) { + list_for_each_entry_safe(event, s, &hm->event_list, entry) { trace_xfs_healthmon_drop(hm, event); - next = event->next; kfree(event); } @@ -173,9 +171,13 @@ static inline void xfs_healthmon_bump_lost(struct xfs_healthmon *hm) */ static bool xfs_healthmon_merge_events( - struct xfs_healthmon_event *existing, + struct xfs_healthmon *hm, const struct xfs_healthmon_event *new) { + struct xfs_healthmon_event *existing = + list_last_entry_or_null(&hm->event_list, struct + xfs_healthmon_event, entry); + if (!existing) return false; @@ -281,10 +283,7 @@ __xfs_healthmon_insert( ktime_get_coarse_real_ts64(&now); event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; - event->next = hm->first_event; - hm->first_event = event; - if (!hm->last_event) - hm->last_event = event; + list_add(&event->entry, &hm->event_list); xfs_healthmon_bump_events(hm); wake_up(&hm->wait); @@ -304,12 +303,7 @@ __xfs_healthmon_push( ktime_get_coarse_real_ts64(&now); event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; - if (!hm->first_event) - hm->first_event = event; - if (hm->last_event) - hm->last_event->next = event; - hm->last_event = event; - event->next = NULL; + list_add_tail(&event->entry, &hm->event_list); xfs_healthmon_bump_events(hm); wake_up(&hm->wait); @@ -328,7 +322,7 @@ xfs_healthmon_clear_lost_prev( }; struct xfs_healthmon_event *event = NULL; - if (xfs_healthmon_merge_events(hm->last_event, &lost_event)) { + if (xfs_healthmon_merge_events(hm, &lost_event)) { wake_up(&hm->wait); goto cleared; } @@ -375,7 +369,7 @@ xfs_healthmon_push( } /* Try to merge with the newest event */ - if (xfs_healthmon_merge_events(hm->last_event, template)) { + if (xfs_healthmon_merge_events(hm, template)) { wake_up(&hm->wait); goto out_unlock; } @@ -901,11 +895,10 @@ xfs_healthmon_format_pop( return NULL; mutex_lock(&hm->lock); - event = hm->first_event; + event = list_first_entry_or_null(&hm->event_list, + struct xfs_healthmon_event, entry); if (event) { - if (hm->last_event == event) - hm->last_event = NULL; - hm->first_event = event->next; + list_del_init(&event->entry); hm->events--; trace_xfs_healthmon_pop(hm, event); @@ -1205,6 +1198,7 @@ xfs_ioc_health_monitor( return -ENOMEM; hm->dev = mp->m_super->s_dev; refcount_set(&hm->ref, 1); + INIT_LIST_HEAD(&hm->event_list); mutex_init(&hm->lock); init_waitqueue_head(&hm->wait); diff --git a/fs/xfs/xfs_healthmon.h b/fs/xfs/xfs_healthmon.h index 0e936507037f..fa3deb187a2b 100644 --- a/fs/xfs/xfs_healthmon.h +++ b/fs/xfs/xfs_healthmon.h @@ -31,8 +31,7 @@ struct xfs_healthmon { struct mutex lock; /* list of event objects */ - struct xfs_healthmon_event *first_event; - struct xfs_healthmon_event *last_event; + struct list_head event_list; /* preallocated event for unmount */ struct xfs_healthmon_event *unmount_event; @@ -110,7 +109,7 @@ enum xfs_healthmon_domain { }; struct xfs_healthmon_event { - struct xfs_healthmon_event *next; + struct list_head entry; enum xfs_healthmon_type type; enum xfs_healthmon_domain domain; From 4c98464e12fcfe3a71646f8efa5d3f7f1b5e2bed Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:47:28 -0700 Subject: [PATCH 1019/2366] xfs: merge healthmon insert/push helpers These functions are basically the same except for where in the queue the new event is added. Refactor them as a single function that takes an action verb to tell us where; and rename the tracepoints to describe directly what happens. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 52 +++++++++++++++++++----------------------- fs/xfs/xfs_trace.h | 4 ++-- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index 073b74e490df..e012c7545da0 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -270,10 +270,16 @@ xfs_healthmon_merge_events( return true; } -/* Insert an event onto the start of the queue. */ +enum insert_where { + INSERT_HEAD, + INSERT_TAIL, +}; + +/* Add an event onto the start or the end of the queue. */ static inline void __xfs_healthmon_insert( struct xfs_healthmon *hm, + enum insert_where where, struct xfs_healthmon_event *event) { struct timespec64 now; @@ -283,31 +289,21 @@ __xfs_healthmon_insert( ktime_get_coarse_real_ts64(&now); event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; - list_add(&event->entry, &hm->event_list); + switch (where) { + case INSERT_HEAD: + trace_xfs_healthmon_insert_head(hm, event); + + list_add(&event->entry, &hm->event_list); + break; + case INSERT_TAIL: + trace_xfs_healthmon_insert_tail(hm, event); + + list_add_tail(&event->entry, &hm->event_list); + break; + } + xfs_healthmon_bump_events(hm); wake_up(&hm->wait); - - trace_xfs_healthmon_insert(hm, event); -} - -/* Push an event onto the end of the queue. */ -static inline void -__xfs_healthmon_push( - struct xfs_healthmon *hm, - struct xfs_healthmon_event *event) -{ - struct timespec64 now; - - lockdep_assert_held(&hm->lock); - - ktime_get_coarse_real_ts64(&now); - event->time_ns = (now.tv_sec * NSEC_PER_SEC) + now.tv_nsec; - - list_add_tail(&event->entry, &hm->event_list); - xfs_healthmon_bump_events(hm); - wake_up(&hm->wait); - - trace_xfs_healthmon_push(hm, event); } /* Deal with any previously lost events */ @@ -333,7 +329,7 @@ xfs_healthmon_clear_lost_prev( if (!event) return -ENOMEM; - __xfs_healthmon_push(hm, event); + __xfs_healthmon_insert(hm, INSERT_TAIL, event); cleared: hm->lost_prev_event = 0; return 0; @@ -386,7 +382,7 @@ xfs_healthmon_push( goto out_unlock; } - __xfs_healthmon_push(hm, event); + __xfs_healthmon_insert(hm, INSERT_TAIL, event); out_unlock: mutex_unlock(&hm->lock); @@ -415,7 +411,7 @@ xfs_healthmon_unmount( * we've inserted the unmount event, hm no longer owns that event. */ mutex_lock(&hm->lock); - __xfs_healthmon_insert(hm, hm->unmount_event); + __xfs_healthmon_insert(hm, INSERT_HEAD, hm->unmount_event); hm->unmount_event = NULL; mutex_unlock(&hm->lock); @@ -1214,7 +1210,7 @@ xfs_ioc_health_monitor( } running_event->type = XFS_HEALTHMON_RUNNING; running_event->domain = XFS_HEALTHMON_MOUNT; - __xfs_healthmon_insert(hm, running_event); + __xfs_healthmon_insert(hm, INSERT_HEAD, running_event); /* * Preallocate the unmount event so that we can't fail to notify the diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index f333c938fbd9..6aa379c2cf0c 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -6139,8 +6139,8 @@ DEFINE_EVENT(xfs_healthmon_event_class, name, \ TP_PROTO(const struct xfs_healthmon *hm, \ const struct xfs_healthmon_event *event), \ TP_ARGS(hm, event)) -DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_insert); -DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_push); +DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_insert_head); +DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_insert_tail); DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_pop); DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_format); DEFINE_HEALTHMONEVENT_EVENT(xfs_healthmon_format_overflow); From 74eeb68a628dbc4a8f976351ad2f1ef5463513ee Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:47:43 -0700 Subject: [PATCH 1020/2366] xfs: check healthmon outbuffer space correctly LOLLM notices that the outbuf space check in xfs_healthmon_format_pop isn't quite correct -- it checks that there's enough space to write a xfs_healthmon_event object, but the outbuffer is supposed to contain xfs_health_monitor_event objects. Fix this by adding a helper, and refactoring all three outbuf size checks to use it. Cc: stable@vger.kernel.org # v7.0 Fixes: b3a289a2a9397b ("xfs: create event queuing, formatting, and discovery infrastructure") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index e012c7545da0..78c87761ac89 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -735,6 +735,13 @@ static const unsigned int type_map[] = { [XFS_HEALTHMON_DATALOST] = XFS_HEALTH_MONITOR_TYPE_DATALOST, }; +static inline bool +xfs_healthmon_check_outbuffer_space(const struct xfs_healthmon *hm) +{ + return hm->bufhead + sizeof(struct xfs_health_monitor_event) <= + hm->bufsize; +} + /* Render event as a V0 structure */ STATIC int xfs_healthmon_format_v0( @@ -801,10 +808,10 @@ xfs_healthmon_format_v0( break; } - ASSERT(hm->bufhead + sizeof(hme) <= hm->bufsize); + ASSERT(xfs_healthmon_check_outbuffer_space(hm)); /* copy formatted object to the outbuf */ - if (hm->bufhead + sizeof(hme) <= hm->bufsize) { + if (xfs_healthmon_check_outbuffer_space(hm)) { memcpy(hm->buffer + hm->bufhead, &hme, sizeof(hme)); hm->bufhead += sizeof(hme); } @@ -887,7 +894,11 @@ xfs_healthmon_format_pop( { struct xfs_healthmon_event *event; - if (hm->bufhead + sizeof(*event) > hm->bufsize) + /* + * Don't bother if there's not enough space to format even one event in + * the outbuffer. + */ + if (!xfs_healthmon_check_outbuffer_space(hm)) return NULL; mutex_lock(&hm->lock); From 295f2cfd3e2c814c2ecd2c1d522bc31c5288e414 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:47:59 -0700 Subject: [PATCH 1021/2366] xfs: bump lost_prev_errors if we lose even the healthmon lost event LOLLM observes that we don't bump xfs_healthmon::lost_prev_event even if we can't allocate or queue a LOST event, which means that events can disappear silently when things are going very wrong. Bump the counter to avoid this problem. Cc: stable@vger.kernel.org # v7.0 Fixes: b3a289a2a9397b ("xfs: create event queuing, formatting, and discovery infrastructure") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index 78c87761ac89..b57fa033cec4 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -326,8 +326,10 @@ xfs_healthmon_clear_lost_prev( if (hm->events < XFS_HEALTHMON_MAX_EVENTS) event = kmemdup(&lost_event, sizeof(struct xfs_healthmon_event), GFP_NOFS); - if (!event) + if (!event) { + xfs_healthmon_bump_lost(hm); return -ENOMEM; + } __xfs_healthmon_insert(hm, INSERT_TAIL, event); cleared: From 014c1aff607a839b8ddc732da919b94560ae58c2 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:48:14 -0700 Subject: [PATCH 1022/2366] xfs: report nonexistent parents as a filesystem corruption LOLLM noticed that when the directory tree scrubber tries to walk up a parent pointer but the parent inumber doesn't point to an allocated inode, we allow the EINVAL/ENOENT error code to bubble up to userspace. That's not right, we should be reporting that as a cross-referencing error so that someone runs the parent pointer checker. Also add a termination check to xchk_dirpath_step_up because it's a loop body function. Cc: stable@vger.kernel.org # v6.10 Fixes: 928b721a11789a ("xfs: teach online scrub to find directory tree structure problems") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree.c | 30 ++++++++++++++++++++++++++++-- fs/xfs/scrub/trace.h | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/dirtree.c b/fs/xfs/scrub/dirtree.c index b2cf6e5439d9..717cbac29562 100644 --- a/fs/xfs/scrub/dirtree.c +++ b/fs/xfs/scrub/dirtree.c @@ -368,12 +368,38 @@ xchk_dirpath_step_up( struct xfs_inode *dp; xfs_ino_t parent_ino = be64_to_cpu(dl->pptr_rec.p_ino); unsigned int lock_mode; - int error; + int error = 0; + + if (xchk_should_terminate(sc, &error)) + return error; /* Grab and lock the parent directory. */ error = xchk_iget(sc, parent_ino, &dp); - if (error) + switch (error) { + case -EINVAL: + case -ENOENT: + mutex_lock(&dl->lock); + + if (dl->stale) { + /* live update detected a change in this path */ + error = -ESTALE; + } else { + /* inode doesn't exist, path invalid */ + error = -EFSCORRUPTED; + + trace_xchk_dirpath_badino(dl->sc, path->path_nr, + path->nr_steps, &dl->xname, + &dl->pptr_rec); + } + + mutex_unlock(&dl->lock); return error; + case 0: + /* keep going */ + break; + default: + return error; + } lock_mode = xfs_ilock_attr_map_shared(dp); mutex_lock(&dl->lock); diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 362c6d39e9f5..0f5adc293962 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -1706,6 +1706,39 @@ DEFINE_EVENT(xchk_dirtree_class, name, \ DEFINE_XCHK_DIRTREE_EVENT(xchk_dirtree_create_path); DEFINE_XCHK_DIRTREE_EVENT(xchk_dirpath_walk_upwards); +TRACE_EVENT(xchk_dirpath_badino, + TP_PROTO(struct xfs_scrub *sc, unsigned int path_nr, + unsigned int step_nr, const struct xfs_name *name, + const struct xfs_parent_rec *pptr), + TP_ARGS(sc, path_nr, step_nr, name, pptr), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(unsigned int, path_nr) + __field(unsigned int, step_nr) + __field(xfs_ino_t, parent_ino) + __field(unsigned int, parent_gen) + __field(unsigned int, namelen) + __dynamic_array(char, name, name->len) + ), + TP_fast_assign( + __entry->dev = sc->mp->m_super->s_dev; + __entry->path_nr = path_nr; + __entry->step_nr = step_nr; + __entry->parent_ino = be64_to_cpu(pptr->p_ino); + __entry->parent_gen = be32_to_cpu(pptr->p_gen); + __entry->namelen = name->len; + memcpy(__get_str(name), name->name, name->len); + ), + TP_printk("dev %d:%d path %u step %u parent_ino 0x%llx parent_gen 0x%x name '%.*s'", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->path_nr, + __entry->step_nr, + __entry->parent_ino, + __entry->parent_gen, + __entry->namelen, + __get_str(name)) +); + DECLARE_EVENT_CLASS(xchk_dirpath_class, TP_PROTO(struct xfs_scrub *sc, struct xfs_inode *ip, unsigned int path_nr, unsigned int step_nr, From 1a441c6842da75c5b862cc1c9f7969d6a93b54b9 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:48:30 -0700 Subject: [PATCH 1023/2366] xfs: destroy seen inode bitmap when we fail to add a dirpath LOLLM observes a memory leak in xchk_dirtree_create_path if we create the directory path object but appending the name to the path fails. When this happens, we don't tear down the (empty) seen inode bitmap. This is a pretty trivial error, but let's not leave logic bombs. Do the same for a similar bug in xrep_dirtree_create_adoption_path. Cc: stable@vger.kernel.org # v6.10 Fixes: 928b721a11789a ("xfs: teach online scrub to find directory tree structure problems") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree.c | 1 + fs/xfs/scrub/dirtree_repair.c | 1 + 2 files changed, 2 insertions(+) diff --git a/fs/xfs/scrub/dirtree.c b/fs/xfs/scrub/dirtree.c index 717cbac29562..9b0ab2316612 100644 --- a/fs/xfs/scrub/dirtree.c +++ b/fs/xfs/scrub/dirtree.c @@ -259,6 +259,7 @@ xchk_dirtree_create_path( dl->nr_paths++; return 0; out_path: + xino_bitmap_destroy(&path->seen_inodes); kfree(path); return error; } diff --git a/fs/xfs/scrub/dirtree_repair.c b/fs/xfs/scrub/dirtree_repair.c index bbf6acf6fd40..8acd55b8c769 100644 --- a/fs/xfs/scrub/dirtree_repair.c +++ b/fs/xfs/scrub/dirtree_repair.c @@ -618,6 +618,7 @@ xrep_dirtree_create_adoption_path( return 0; out_path: + xino_bitmap_destroy(&path->seen_inodes); kfree(path); return error; } From 8c71ad4d4f3e20c30b663bd292526fcbc4d3913f Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:48:45 -0700 Subject: [PATCH 1024/2366] xfs: signal inode btree xref error if get_rec returns an error LOLLM points out that xchk_finobt_xref_inobt and xchk_inobt_xref_finobt both ignore errors being returned from the xfs_btree_get_rec function and proceed with a (possibly stale) "true" value for has_record. If the *simple* btree record checks fail during cross-referencing, we can immediately conclude that there's a cross-referncing error in the other btree. On those grounds, we can bubble up the returned error instead of wasting time cross-referencing with garbage. Cc: stable@vger.kernel.org # v6.4 Fixes: bc0f3b55467e1b ("xfs: directly cross-reference the inode btrees with each other") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/ialloc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/xfs/scrub/ialloc.c b/fs/xfs/scrub/ialloc.c index 19c0b1b2a787..9270ad075fe0 100644 --- a/fs/xfs/scrub/ialloc.c +++ b/fs/xfs/scrub/ialloc.c @@ -85,6 +85,8 @@ xchk_inobt_xref_finobt( goto no_record; error = xfs_inobt_get_rec(cur, &frec, &has_record); + if (error) + return error; if (!has_record) return -EFSCORRUPTED; @@ -188,6 +190,8 @@ xchk_finobt_xref_inobt( goto no_record; error = xfs_inobt_get_rec(cur, &irec, &has_record); + if (error) + return error; if (!has_record) return -EFSCORRUPTED; From 0fc67528f54bb91dac22093749b425207c0fc245 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:49:01 -0700 Subject: [PATCH 1025/2366] xfs: truncate quota file correctly when repairing quota file LOLLM noticed that xrep_quota_data_fork screws up the unit handling when it computes the offset at which to start truncating the quota file. max_dquid_off is the file block offset containing the highest possible dquot, and xfs_bunmapi_range takes the starting file block offset. Therefore, it makes no sense to multiply max_dquid_off by the blocksize; all we need to do is start truncating at the next block. Cc: stable@vger.kernel.org # v6.8 Fixes: a5b91555403e3a ("xfs: repair quotas") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota_repair.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index 487bd4f68ebb..ca3ac6728339 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -455,8 +455,7 @@ xrep_quota_data_fork( if (truncate) { /* Erase everything after the block containing the max dquot */ - error = xfs_bunmapi_range(&sc->tp, sc->ip, 0, - max_dqid_off * sc->mp->m_sb.sb_blocksize, + error = xfs_bunmapi_range(&sc->tp, sc->ip, 0, max_dqid_off + 1, XFS_MAX_FILEOFF); if (error) goto out; From de20f7014917d661afc03d9a1c157d0ae2775b49 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:49:52 -0700 Subject: [PATCH 1026/2366] xfs: compute dquot checksum after resetting dd_lsn in repair LOLLM complains that xrep_quota_block updates dd_lsn after calculating the crc of the ondisk dquot. That's clearly broken, so fix that. Cc: stable@vger.kernel.org # v6.8 Fixes: a5b91555403e3a ("xfs: repair quotas") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota_repair.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index ca3ac6728339..5c22bb6ccffa 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -363,11 +363,18 @@ xrep_quota_block( ddq->d_rtbcount, &ddq->d_rtbtimer, defq->rtb.time); + /* + * This transaction operates on raw disk buffers, so we don't + * have a dquot log item to assign the LSN for us. Instead, + * set it to zero so that log recovery will always replay any + * logged dquot item atop this buffer. + */ + dqblk->dd_lsn = 0; + /* We only support v5 filesystems so always set these. */ uuid_copy(&dqblk->dd_uuid, &sc->mp->m_sb.sb_meta_uuid); xfs_update_cksum((char *)dqblk, sizeof(struct xfs_dqblk), XFS_DQUOT_CRC_OFF); - dqblk->dd_lsn = 0; } switch (dqtype) { case XFS_DQTYPE_USER: From d7f97be48cbe3751e44a45373c0e91e93b976f98 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:49:32 -0700 Subject: [PATCH 1027/2366] xfs: fix backwards skipping logic in xrep_quota_block LOLLM complains about the logic in xrep_quota_block that skips reinitializing the ondisk dquot if there aren't any problems that would impede a dqiterate walk later. I got the type checking logic backwards, which is the source of the problem. Fix that. Cc: stable@vger.kernel.org # v6.8 Fixes: a5b91555403e3a ("xfs: repair quotas") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index 5c22bb6ccffa..59302e8afc7e 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -325,7 +325,7 @@ xrep_quota_block( * If there's nothing that would impede a dqiterate, we're * done. */ - if ((ddq->d_type & XFS_DQTYPE_REC_MASK) != dqtype || + if ((ddq->d_type & XFS_DQTYPE_REC_MASK) == dqtype && id == be32_to_cpu(ddq->d_id)) { xfs_trans_brelse(sc->tp, bp); return 0; From e8b01aaafffe6b852325debdf1ef4b13ccea1cd7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 1 Sep 2026 22:49:47 -0700 Subject: [PATCH 1028/2366] xfs: fix backwards mergeability logic in refcount scrubber When we start the refcount or rtrefcount btree scanners, prev_rec is initialized to all zeroes. This is done so that the record mergeability checks skip the first record because you must have two records to compare. Unfortunately, I got the logic backwards, so scrub has never complained about mergeable refcountbt records. Fix this bug that LOLLM noticed. Cc: stable@vger.kernel.org # v6.4 Fixes: db0502b39c21d1 ("xfs: flag refcount btree records that could be merged") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/refcount.c | 2 +- fs/xfs/scrub/rtrefcount.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/refcount.c b/fs/xfs/scrub/refcount.c index 4e1bf23e5b89..f2addaf13c58 100644 --- a/fs/xfs/scrub/refcount.c +++ b/fs/xfs/scrub/refcount.c @@ -410,7 +410,7 @@ xchk_refcount_mergeable( const struct xfs_refcount_irec *r1 = &rrc->prev_rec; /* Ignore if prev_rec is not yet initialized. */ - if (r1->rc_blockcount > 0) + if (r1->rc_blockcount == 0) return false; if (r1->rc_domain != r2->rc_domain) diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index 4e7c540c8d23..de100178f41c 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -375,7 +375,7 @@ xchk_rtrefcount_mergeable( const struct xfs_refcount_irec *r1 = &rrc->prev_rec; /* Ignore if prev_rec is not yet initialized. */ - if (r1->rc_blockcount > 0) + if (r1->rc_blockcount == 0) return false; if (r1->rc_startblock + r1->rc_blockcount != r2->rc_startblock) From 79ab1af2034b2ad10c5f18910937b938d2ad2219 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:51:05 -0700 Subject: [PATCH 1029/2366] xfs: cross-reference the rtgroup superblock extent, not block LOLLM noticed that when libxfs creates a realtime superblock, it will create an rtrmapbt record covering the entire rtextent in which the superblock lives. However, the cross-referencing checks only look for the first block, which means that we can miss a corrupt rtrmap record. That will get picked up by the rtrmap scrubber, but we should make the rgsuper scrubber more robust anyway. Cc: stable@vger.kernel.org # v6.13 Fixes: 3f1bdf50ab1b9c ("xfs: scrub the realtime group superblock") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rgsuper.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/rgsuper.c b/fs/xfs/scrub/rgsuper.c index 2bd2c0351b35..6e2abe5dc27c 100644 --- a/fs/xfs/scrub/rgsuper.c +++ b/fs/xfs/scrub/rgsuper.c @@ -36,8 +36,10 @@ xchk_rgsuperblock_xref( if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) return; - xchk_xref_is_used_rt_space(sc, xfs_rgbno_to_rtb(sc->sr.rtg, 0), 1); - xchk_xref_is_only_rt_owned_by(sc, 0, 1, &XFS_RMAP_OINFO_FS); + xchk_xref_is_used_rt_space(sc, xfs_rgbno_to_rtb(sc->sr.rtg, 0), + sc->mp->m_sb.sb_rextsize); + xchk_xref_is_only_rt_owned_by(sc, 0, sc->mp->m_sb.sb_rextsize, + &XFS_RMAP_OINFO_FS); } int From c3085f6c7cca7c162248519ce8d763047cdd8acd Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:51:21 -0700 Subject: [PATCH 1030/2366] xfs: use the rtgroup extent count to find rtrefcount gaps LOLLM noticed an anachronism from the early days of rtrefcount where the refcount btree would handle 64-bit block numbers -- we pass rtblocks into the gap finder, but rtrefcount btrees are sharded by rtgroup now. This isn't really a problem for us since we're only looking for overlapping rtrmap records to flag, but let's fix this sillyness. Also fix some stale comments. Cc: stable@vger.kernel.org # v6.14 Fixes: 30f47950dc2eba ("xfs: check reference counts of gaps between rt refcount records") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtrefcount.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index de100178f41c..a024d50cf9d2 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -428,7 +428,7 @@ static inline void xchk_rtrefcountbt_xref_gaps( struct xfs_scrub *sc, struct xchk_rtrefcbt_records *rrc, - xfs_rtblock_t bno) + xfs_rgblock_t bno) { struct xfs_rmap_irec low; struct xfs_rmap_irec high; @@ -538,7 +538,7 @@ xchk_refcount_xref_rmap( xchk_btree_xref_set_corrupt(sc, sc->sr.rmap_cur, 0); } -/* Scrub the refcount btree for some AG. */ +/* Scrub the refcount btree for some rtgroup. */ int xchk_rtrefcountbt( struct xfs_scrub *sc) @@ -564,10 +564,10 @@ xchk_rtrefcountbt( /* * Check that all blocks between the last refcount > 1 record and the - * end of the rt volume have at most one reverse mapping. + * end of the rtgroup have at most one reverse mapping. */ - xchk_rtrefcountbt_xref_gaps(sc, &rrc, sc->mp->m_sb.sb_rblocks); - + xchk_rtrefcountbt_xref_gaps(sc, &rrc, + xfs_rtx_to_rgbno(sc->sr.rtg, sc->mp->m_sb.sb_rgextents)); xchk_refcount_xref_rmap(sc, &btree_oinfo, rrc.cow_blocks); return 0; From 0d43368844a75ad13561a1198a3b027940730756 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:51:36 -0700 Subject: [PATCH 1031/2366] xfs: strengthen the "is cow staging" helpers in scrub LOLLM pointed out a bug in both of the refcount scrub predicates that determine if a range of blocks is marked as CoW staging in the btree. While it compares blockcount < len, this isn't enough to determine that the CoW staging record is at least as large as the range passed into the helper. Fix both of them. Cc: stable@vger.kernel.org # v4.16 Fixes: f6d5fc21fdc713 ("xfs: cross-reference refcount btree during scrub") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/refcount.c | 6 +++++- fs/xfs/scrub/rtrefcount.c | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/refcount.c b/fs/xfs/scrub/refcount.c index f2addaf13c58..f8c51d8fbb3d 100644 --- a/fs/xfs/scrub/refcount.c +++ b/fs/xfs/scrub/refcount.c @@ -581,8 +581,12 @@ xchk_xref_is_cow_staging( if (rc.rc_domain != XFS_REFC_DOMAIN_COW) xchk_btree_xref_set_corrupt(sc, sc->sa.refc_cur, 0); + /* Can't start after bno */ + if (rc.rc_startblock > agbno) + xchk_btree_xref_set_corrupt(sc, sc->sa.refc_cur, 0); + /* Must be at least as long as what was passed in */ - if (rc.rc_blockcount < len) + if (rc.rc_startblock + rc.rc_blockcount < agbno + len) xchk_btree_xref_set_corrupt(sc, sc->sa.refc_cur, 0); } diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index a024d50cf9d2..652d6b78b7a0 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -609,8 +609,12 @@ xchk_xref_is_rt_cow_staging( if (rc.rc_domain != XFS_REFC_DOMAIN_COW) xchk_btree_xref_set_corrupt(sc, sc->sr.refc_cur, 0); + /* Can't start after bno */ + if (rc.rc_startblock > bno) + xchk_btree_xref_set_corrupt(sc, sc->sr.refc_cur, 0); + /* Must be at least as long as what was passed in */ - if (rc.rc_blockcount < len) + if (rc.rc_startblock + rc.rc_blockcount < bno + len) xchk_btree_xref_set_corrupt(sc, sc->sr.refc_cur, 0); } From 3f9fd694fa429e89fe6de51b22b0ed5fb8b2daf4 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:51:52 -0700 Subject: [PATCH 1032/2366] xfs: fix rtrefcount btree block counting in scrub LOLLM started on a long tangent about how xchk_refcount_xref_rmap shouldn't nope out if sc->sa.rmap_cur isn't set, because nothing ever sets that field. It's right about the condition, but misses the bigger problem, which is that to count the rtrefcount btree blocks, we have to walk all rmap records in each AG in the data section. That was papered over by the incorrect !sc->sa.rmap_cur test. In other words, we need a perag iteration loop here. Restructure the code to do that, and now it'll all work properly. Fix the confusing function name prefix. Cc: stable@vger.kernel.org # v6.14 Fixes: c27929670de144 ("xfs: scrub the realtime refcount btree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtrefcount.c | 64 +++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index 652d6b78b7a0..3d916d71a135 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -20,6 +20,7 @@ #include "xfs_metafile.h" #include "xfs_rtrefcount_btree.h" #include "xfs_rtalloc.h" +#include "xfs_ag.h" #include "scrub/scrub.h" #include "scrub/common.h" #include "scrub/btree.h" @@ -504,30 +505,75 @@ xchk_rtrefcountbt_rec( return 0; } +/* Count the number of blocks used by the rtrefcount btree file in this AG. */ +static int +xchk_rtrefcount_count_agblocks( + struct xfs_scrub *sc, + xfs_agnumber_t agno, + const struct xfs_owner_info *btree_oinfo, + xfs_filblks_t *blocks) +{ + xfs_filblks_t agblocks = 0; + int error; + + error = xchk_ag_init_existing(sc, agno, &sc->sa); + if (error) + goto out_free; + + /* + * If we don't have an rmap cursor, we can't complete the cross + * referencing, so return EFSCORRUPTED to end the loop and trigger the + * XFAIL flag. + */ + if (!sc->sa.rmap_cur) { + error = -EFSCORRUPTED; + goto out_free; + } + + error = xchk_count_rmap_ownedby_ag(sc, sc->sa.rmap_cur, btree_oinfo, + &agblocks); + if (error) + goto out_free; + + *blocks += agblocks; +out_free: + xchk_ag_free(sc, &sc->sa); + return error; +} + /* Make sure we have as many refc blocks as the rmap says. */ STATIC void -xchk_refcount_xref_rmap( +xchk_rtrefcount_xref_rmap( struct xfs_scrub *sc, const struct xfs_owner_info *btree_oinfo, xfs_extlen_t cow_blocks) { xfs_filblks_t refcbt_blocks = 0; - xfs_filblks_t blocks; - int error; + xfs_filblks_t blocks = 1; /* one for the iroot */ + xfs_agnumber_t agno; + int error = 0; - if (!sc->sr.rmap_cur || !sc->sa.rmap_cur || xchk_skip_xref(sc->sm)) + if (!xfs_has_rmapbt(sc->mp) || xchk_skip_xref(sc->sm)) return; /* Check that we saw as many refcbt blocks as the rmap knows about. */ error = xfs_btree_count_blocks(sc->sr.refc_cur, &refcbt_blocks); if (!xchk_btree_process_error(sc, sc->sr.refc_cur, 0, &error)) return; - error = xchk_count_rmap_ownedby_ag(sc, sc->sa.rmap_cur, btree_oinfo, - &blocks); - if (!xchk_should_check_xref(sc, &error, &sc->sa.rmap_cur)) + + for (agno = 0; agno < sc->mp->m_sb.sb_agcount; agno++) { + error = xchk_rtrefcount_count_agblocks(sc, agno, btree_oinfo, + &blocks); + if (error) + break; + } + if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error)) return; if (blocks != refcbt_blocks) - xchk_btree_xref_set_corrupt(sc, sc->sa.rmap_cur, 0); + xchk_fblock_xref_set_corrupt(sc, XFS_DATA_FORK, 0); + + if (!sc->sr.rmap_cur || xchk_skip_xref(sc->sm)) + return; /* Check that we saw as many cow blocks as the rmap knows about. */ error = xchk_count_rmap_ownedby_ag(sc, sc->sr.rmap_cur, @@ -568,7 +614,7 @@ xchk_rtrefcountbt( */ xchk_rtrefcountbt_xref_gaps(sc, &rrc, xfs_rtx_to_rgbno(sc->sr.rtg, sc->mp->m_sb.sb_rgextents)); - xchk_refcount_xref_rmap(sc, &btree_oinfo, rrc.cow_blocks); + xchk_rtrefcount_xref_rmap(sc, &btree_oinfo, rrc.cow_blocks); return 0; } From 6b760b3232b3efc8bcc7c165e76300bc1c9140c5 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:52:07 -0700 Subject: [PATCH 1033/2366] xfs: make the rtsummary repair fix the file size too LOLLM noticed that the rtsummary repair code will create a new rtsummary with the correct file size, but it won't force the new file size to be set on the existing rtsummary file, leaving the rtsummary corrupt. Fix this by setting up the tempfile mapping-exchange to run to the end of both files, which is the magic offset needed to reset the file size. Cc: stable@vger.kernel.org # v6.10 Fixes: abf039e2e4afde ("xfs: online repair of realtime summaries") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtsummary_repair.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/rtsummary_repair.c b/fs/xfs/scrub/rtsummary_repair.c index f065c3e51ce2..ed763290aec1 100644 --- a/fs/xfs/scrub/rtsummary_repair.c +++ b/fs/xfs/scrub/rtsummary_repair.c @@ -164,9 +164,10 @@ xrep_rtsummary( /* * Now exchange the contents. Nothing in repair uses the temporary * buffer, so we can reuse it for the tempfile exchrange information. + * Use XFS_MAX_FILEOFF here so that we correct the rtsummary file size. */ error = xrep_tempexch_trans_reserve(sc, XFS_DATA_FORK, 0, - rts->rsumblocks, &rts->tempexch); + XFS_MAX_FILEOFF, &rts->tempexch); if (error) return error; From 4d0624679ae29b469016f1ce4714be58582ed06a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 2 Sep 2026 22:52:23 -0700 Subject: [PATCH 1034/2366] xfs: count escaped corruption errors in scrub stats The main scrub code will quietly turn bubbled-up EFSCORRUPTED and EFSBADCRC errors into corruption errors. These aren't recorded in the scrub stats code (says LOLLM) so do that now. Cc: stable@vger.kernel.org # v6.6 Fixes: d7a74cad8f4513 ("xfs: track usage statistics of online fsck") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/scrub.c | 3 +-- fs/xfs/scrub/stats.c | 28 +++++++++++++++++++--------- fs/xfs/scrub/stats.h | 4 ++-- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c index 8742445c86f4..12c228b7f477 100644 --- a/fs/xfs/scrub/scrub.c +++ b/fs/xfs/scrub/scrub.c @@ -765,8 +765,7 @@ xfs_scrub_metadata( out_teardown: error = xchk_teardown(sc, error); out_sc: - if (error != -ENOENT) - xchk_stats_merge(mp, sm, &run); + xchk_stats_merge(mp, sm, error, &run); kfree(sc); out: trace_xchk_done(XFS_I(file_inode(file)), sm, error); diff --git a/fs/xfs/scrub/stats.c b/fs/xfs/scrub/stats.c index ef3f6abdb706..76f2515188d1 100644 --- a/fs/xfs/scrub/stats.c +++ b/fs/xfs/scrub/stats.c @@ -188,31 +188,37 @@ STATIC void xchk_stats_merge_one( struct xchk_stats *cs, const struct xfs_scrub_metadata *sm, + int error, const struct xchk_stats_run *run) { struct xchk_scrub_stats *css; + unsigned int sm_flags = sm->sm_flags; if (sm->sm_type >= XFS_SCRUB_TYPE_NR) { ASSERT(sm->sm_type < XFS_SCRUB_TYPE_NR); return; } + /* caller applies this same transformation after we return */ + if (error == -EFSCORRUPTED || error == -EFSBADCRC) + sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; + css = &cs->cs_stats[sm->sm_type]; spin_lock(&css->css_lock); css->invocations++; - if (!(sm->sm_flags & XFS_SCRUB_OFLAG_UNCLEAN)) + if (!(sm_flags & XFS_SCRUB_OFLAG_UNCLEAN)) css->clean++; - if (sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) + if (sm_flags & XFS_SCRUB_OFLAG_CORRUPT) css->corrupt++; - if (sm->sm_flags & XFS_SCRUB_OFLAG_PREEN) + if (sm_flags & XFS_SCRUB_OFLAG_PREEN) css->preen++; - if (sm->sm_flags & XFS_SCRUB_OFLAG_XFAIL) + if (sm_flags & XFS_SCRUB_OFLAG_XFAIL) css->xfail++; - if (sm->sm_flags & XFS_SCRUB_OFLAG_XCORRUPT) + if (sm_flags & XFS_SCRUB_OFLAG_XCORRUPT) css->xcorrupt++; - if (sm->sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE) + if (sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE) css->incomplete++; - if (sm->sm_flags & XFS_SCRUB_OFLAG_WARNING) + if (sm_flags & XFS_SCRUB_OFLAG_WARNING) css->warning++; css->retries += run->retries; css->checktime_us += howmany_64(run->scrub_ns, NSEC_PER_USEC); @@ -230,10 +236,14 @@ void xchk_stats_merge( struct xfs_mount *mp, const struct xfs_scrub_metadata *sm, + int error, const struct xchk_stats_run *run) { - xchk_stats_merge_one(&global_stats, sm, run); - xchk_stats_merge_one(mp->m_scrub_stats, sm, run); + if (error == -ENOENT) + return; + + xchk_stats_merge_one(&global_stats, sm, error, run); + xchk_stats_merge_one(mp->m_scrub_stats, sm, error, run); } /* debugfs boilerplate */ diff --git a/fs/xfs/scrub/stats.h b/fs/xfs/scrub/stats.h index b358ad8d8b90..221052b95dd0 100644 --- a/fs/xfs/scrub/stats.h +++ b/fs/xfs/scrub/stats.h @@ -27,7 +27,7 @@ void xchk_stats_register(struct xchk_stats *cs, struct dentry *parent); void xchk_stats_unregister(struct xchk_stats *cs); void xchk_stats_merge(struct xfs_mount *mp, const struct xfs_scrub_metadata *sm, - const struct xchk_stats_run *run); + int error, const struct xchk_stats_run *run); static inline u64 xchk_stats_now(void) { return ktime_get_ns(); } static inline u64 xchk_stats_elapsed_ns(u64 since) @@ -53,7 +53,7 @@ static inline u64 xchk_stats_elapsed_ns(u64 since) # define xchk_stats_unregister(cs) ((void)0) # define xchk_stats_now() (0) # define xchk_stats_elapsed_ns(x) (0 * (x)) -# define xchk_stats_merge(mp, sm, run) ((void)0) +# define xchk_stats_merge(mp, sm, error, run) ((void)0) #endif /* CONFIG_XFS_ONLINE_SCRUB_STATS */ #endif /* __XFS_SCRUB_STATS_H__ */ From 3837c3f29fbc3b8c12bebf5c62741e2befe3482a Mon Sep 17 00:00:00 2001 From: Magdalena Schulfer Date: Tue, 1 Sep 2026 14:57:47 +0200 Subject: [PATCH 1035/2366] accel/ivpu: Validate full buffer range in ivpu_to_cpu_addr Add a size parameter to ivpu_to_cpu_addr() and validate that the whole [vpu_addr, vpu_addr + size) range stays within the BO. Cc: stable@vger.kernel.org Fixes: 647371a6609d ("accel/ivpu: Add GEM buffer object management") Signed-off-by: Magdalena Schulfer Signed-off-by: Dawid Osuchowski Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260901125749.404338-2-dawid.osuchowski@linux.intel.com --- drivers/accel/ivpu/ivpu_gem.h | 14 +++++++++++--- drivers/accel/ivpu/ivpu_ipc.c | 7 ++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_gem.h b/drivers/accel/ivpu/ivpu_gem.h index 0c3350f22b55..b1ae020a4fc2 100644 --- a/drivers/accel/ivpu/ivpu_gem.h +++ b/drivers/accel/ivpu/ivpu_gem.h @@ -87,15 +87,23 @@ static inline bool ivpu_bo_is_resident(struct ivpu_bo *bo) return !!bo->base.pages; } -static inline void *ivpu_to_cpu_addr(struct ivpu_bo *bo, u32 vpu_addr) +static inline void *ivpu_to_cpu_addr(struct ivpu_bo *bo, u64 vpu_addr, u64 size) { + u64 bo_size = ivpu_bo_size(bo); + u64 offset; + if (vpu_addr < bo->vpu_addr) return NULL; - if (vpu_addr >= (bo->vpu_addr + ivpu_bo_size(bo))) + if (size > bo_size) return NULL; - return ivpu_bo_vaddr(bo) + (vpu_addr - bo->vpu_addr); + offset = vpu_addr - bo->vpu_addr; + + if (offset > bo_size - size) + return NULL; + + return ivpu_bo_vaddr(bo) + offset; } static inline u32 cpu_to_vpu_addr(struct ivpu_bo *bo, void *cpu_addr) diff --git a/drivers/accel/ivpu/ivpu_ipc.c b/drivers/accel/ivpu/ivpu_ipc.c index 62607ec8ca8f..8e960293b77a 100644 --- a/drivers/accel/ivpu/ivpu_ipc.c +++ b/drivers/accel/ivpu/ivpu_ipc.c @@ -79,7 +79,7 @@ ivpu_ipc_tx_prepare(struct ivpu_device *vdev, struct ivpu_ipc_consumer *cons, return -ENOMEM; } - tx_buf = ivpu_to_cpu_addr(ipc->mem_tx, tx_buf_vpu_addr); + tx_buf = ivpu_to_cpu_addr(ipc->mem_tx, tx_buf_vpu_addr, sizeof(*tx_buf)); if (drm_WARN_ON(&vdev->drm, !tx_buf)) { gen_pool_free(ipc->mm_tx, tx_buf_vpu_addr, sizeof(*tx_buf)); return -EIO; @@ -420,7 +420,7 @@ void ivpu_ipc_irq_handler(struct ivpu_device *vdev) return; } - ipc_hdr = ivpu_to_cpu_addr(ipc->mem_rx, vpu_addr); + ipc_hdr = ivpu_to_cpu_addr(ipc->mem_rx, vpu_addr, sizeof(*ipc_hdr)); if (!ipc_hdr) { ivpu_warn_ratelimited(vdev, "IPC msg 0x%x out of range\n", vpu_addr); continue; @@ -429,7 +429,8 @@ void ivpu_ipc_irq_handler(struct ivpu_device *vdev) jsm_msg = NULL; if (ipc_hdr->channel != IVPU_IPC_CHAN_BOOT_MSG) { - jsm_msg = ivpu_to_cpu_addr(ipc->mem_rx, ipc_hdr->data_addr); + jsm_msg = ivpu_to_cpu_addr(ipc->mem_rx, ipc_hdr->data_addr, + sizeof(*jsm_msg)); if (!jsm_msg) { ivpu_warn_ratelimited(vdev, "JSM msg 0x%x out of range\n", ipc_hdr->data_addr); From 0724afc55c77c36c7feb9a7264b02aa7593c5c2d Mon Sep 17 00:00:00 2001 From: Magdalena Schulfer Date: Tue, 1 Sep 2026 14:57:48 +0200 Subject: [PATCH 1036/2366] accel/ivpu: Validate firmware log buffer metadata The tracing log headers parsed by fw_log_print_buffer() reside in DMA-shared BOs that the NPU firmware can write to. fw_log_from_bo() validated log->header_size and log->size, but fw_log_print_buffer() re-read those same fields from shared memory afterwards, allowing a TOCTOU where firmware changes them between the check and the use, and making the host dereference out-of-bounds addresses while printing logs. Snapshot the validated values once with READ_ONCE() and pass them down explicitly in a new struct ivpu_fw_log_desc instead of re-reading them from the shared struct. Cc: stable@vger.kernel.org Fixes: d4e4257afa6e ("accel/ivpu: Add firmware tracing support") Signed-off-by: Magdalena Schulfer Signed-off-by: Dawid Osuchowski Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260901125749.404338-3-dawid.osuchowski@linux.intel.com --- drivers/accel/ivpu/ivpu_fw_log.c | 76 +++++++++++++++++++------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_fw_log.c b/drivers/accel/ivpu/ivpu_fw_log.c index 716467aa3156..4f9055aa9d33 100644 --- a/drivers/accel/ivpu/ivpu_fw_log.c +++ b/drivers/accel/ivpu/ivpu_fw_log.c @@ -26,10 +26,17 @@ MODULE_PARM_DESC(fw_log_level, " error=" __stringify(IVPU_FW_LOG_ERROR) " fatal=" __stringify(IVPU_FW_LOG_FATAL)); +struct ivpu_fw_log_desc { + struct vpu_tracing_buffer_header *log; + u32 header_size; + u32 size; +}; + static int fw_log_from_bo(struct ivpu_device *vdev, struct ivpu_bo *bo, u32 *offset, - struct vpu_tracing_buffer_header **out_log) + struct ivpu_fw_log_desc *desc) { struct vpu_tracing_buffer_header *log; + u32 header_size, size; if ((*offset + sizeof(*log)) > ivpu_bo_size(bo)) return -EINVAL; @@ -39,26 +46,32 @@ static int fw_log_from_bo(struct ivpu_device *vdev, struct ivpu_bo *bo, u32 *off if (log->vpu_canary_start != VPU_TRACING_BUFFER_CANARY) return -EINVAL; - if (log->header_size < sizeof(*log) || log->header_size > 1024) { - ivpu_dbg(vdev, FW_BOOT, "Invalid header size 0x%x\n", log->header_size); + header_size = READ_ONCE(log->header_size); + size = READ_ONCE(log->size); + + if (header_size < sizeof(*log) || header_size > 1024) { + ivpu_dbg(vdev, FW_BOOT, "Invalid header size 0x%x\n", header_size); return -EINVAL; } - if (log->size < log->header_size) { - ivpu_dbg(vdev, FW_BOOT, "Invalid log size 0x%x\n", log->size); + if ((char *)log + size > (char *)ivpu_bo_vaddr(bo) + ivpu_bo_size(bo)) { + ivpu_dbg(vdev, FW_BOOT, "Invalid log size 0x%x\n", size); return -EINVAL; } - if ((char *)log + log->size > (char *)ivpu_bo_vaddr(bo) + ivpu_bo_size(bo)) { - ivpu_dbg(vdev, FW_BOOT, "Invalid log size 0x%x\n", log->size); + if (size < header_size) { + ivpu_dbg(vdev, FW_BOOT, "Invalid log size 0x%x < header size 0x%x\n", + size, header_size); return -EINVAL; } - *out_log = log; - *offset += log->size; + desc->log = log; + desc->header_size = header_size; + desc->size = size; + *offset += size; ivpu_dbg(vdev, FW_BOOT, "FW log name \"%s\", write offset 0x%x size 0x%x, wrap count %d, hdr version %d size %d format %d, alignment %d", - log->name, log->write_index, log->size, log->wrap_count, log->header_version, - log->header_size, log->format, log->alignment); + log->name, log->write_index, size, log->wrap_count, log->header_version, + header_size, log->format, log->alignment); return 0; } @@ -94,11 +107,12 @@ static void fw_log_print_lines(char *buffer, u32 size, struct drm_printer *p) drm_printf(p, "%s", line); } -static void fw_log_print_buffer(struct vpu_tracing_buffer_header *log, const char *prefix, +static void fw_log_print_buffer(struct ivpu_fw_log_desc *desc, const char *prefix, bool only_new_msgs, struct drm_printer *p) { - char *log_data = (void *)log + log->header_size; - u32 data_size = log->size - log->header_size; + struct vpu_tracing_buffer_header *log = desc->log; + char *log_data = (void *)log + desc->header_size; + u32 data_size = desc->size - desc->header_size; u32 log_start = only_new_msgs ? READ_ONCE(log->read_index) : 0; u32 log_end = READ_ONCE(log->write_index); @@ -134,11 +148,11 @@ static void fw_log_print_all_in_bo(struct ivpu_device *vdev, const char *name, struct ivpu_bo *bo, bool only_new_msgs, struct drm_printer *p) { - struct vpu_tracing_buffer_header *log; + struct ivpu_fw_log_desc desc; u32 next = 0; - while (fw_log_from_bo(vdev, bo, &next, &log) == 0) - fw_log_print_buffer(log, name, only_new_msgs, p); + while (fw_log_from_bo(vdev, bo, &next, &desc) == 0) + fw_log_print_buffer(&desc, name, only_new_msgs, p); } void ivpu_fw_log_print(struct ivpu_device *vdev, bool only_new_msgs, struct drm_printer *p) @@ -149,36 +163,36 @@ void ivpu_fw_log_print(struct ivpu_device *vdev, bool only_new_msgs, struct drm_ void ivpu_fw_log_mark_read(struct ivpu_device *vdev) { - struct vpu_tracing_buffer_header *log; + struct ivpu_fw_log_desc desc; u32 next; next = 0; - while (fw_log_from_bo(vdev, vdev->fw->mem_log_crit, &next, &log) == 0) { - log->read_index = READ_ONCE(log->write_index); - log->read_wrap_count = READ_ONCE(log->wrap_count); + while (fw_log_from_bo(vdev, vdev->fw->mem_log_crit, &next, &desc) == 0) { + desc.log->read_index = READ_ONCE(desc.log->write_index); + desc.log->read_wrap_count = READ_ONCE(desc.log->wrap_count); } next = 0; - while (fw_log_from_bo(vdev, vdev->fw->mem_log_verb, &next, &log) == 0) { - log->read_index = READ_ONCE(log->write_index); - log->read_wrap_count = READ_ONCE(log->wrap_count); + while (fw_log_from_bo(vdev, vdev->fw->mem_log_verb, &next, &desc) == 0) { + desc.log->read_index = READ_ONCE(desc.log->write_index); + desc.log->read_wrap_count = READ_ONCE(desc.log->wrap_count); } } void ivpu_fw_log_reset(struct ivpu_device *vdev) { - struct vpu_tracing_buffer_header *log; + struct ivpu_fw_log_desc desc; u32 next; next = 0; - while (fw_log_from_bo(vdev, vdev->fw->mem_log_crit, &next, &log) == 0) { - log->read_index = 0; - log->read_wrap_count = 0; + while (fw_log_from_bo(vdev, vdev->fw->mem_log_crit, &next, &desc) == 0) { + desc.log->read_index = 0; + desc.log->read_wrap_count = 0; } next = 0; - while (fw_log_from_bo(vdev, vdev->fw->mem_log_verb, &next, &log) == 0) { - log->read_index = 0; - log->read_wrap_count = 0; + while (fw_log_from_bo(vdev, vdev->fw->mem_log_verb, &next, &desc) == 0) { + desc.log->read_index = 0; + desc.log->read_wrap_count = 0; } } From 95bf070f3225dc7175725438c916ad321d42fe45 Mon Sep 17 00:00:00 2001 From: Dawid Osuchowski Date: Tue, 1 Sep 2026 14:57:49 +0200 Subject: [PATCH 1037/2366] accel/ivpu: Limit firmware log name prints to field size The name in struct vpu_tracing_buffer_header is a fixed-size array populated by the NPU firmware. It is expected to be NUL-terminated, but nothing on the host side enforces this, so printing it with an unbounded string conversion would read past the field if the terminator is ever missing and expose adjacent bytes of the shared tracing BO through dmesg and the debugfs FW log output. Print at most as many characters as the name field holds, so the output never runs past it even if the string is not NUL-terminated. Cc: stable@vger.kernel.org Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260827102339.281799-1-dawid.osuchowski@linux.intel.com?part=2 Fixes: d4e4257afa6e ("accel/ivpu: Add firmware tracing support") Signed-off-by: Dawid Osuchowski Reviewed-by: Karol Wachowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260901125749.404338-4-dawid.osuchowski@linux.intel.com --- drivers/accel/ivpu/ivpu_fw_log.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_fw_log.c b/drivers/accel/ivpu/ivpu_fw_log.c index 4f9055aa9d33..9eafc42120b6 100644 --- a/drivers/accel/ivpu/ivpu_fw_log.c +++ b/drivers/accel/ivpu/ivpu_fw_log.c @@ -69,9 +69,9 @@ static int fw_log_from_bo(struct ivpu_device *vdev, struct ivpu_bo *bo, u32 *off *offset += size; ivpu_dbg(vdev, FW_BOOT, - "FW log name \"%s\", write offset 0x%x size 0x%x, wrap count %d, hdr version %d size %d format %d, alignment %d", - log->name, log->write_index, size, log->wrap_count, log->header_version, - header_size, log->format, log->alignment); + "FW log name \"%.*s\", write offset 0x%x size 0x%x, wrap count %d, hdr version %d size %d format %d, alignment %d", + (int)ARRAY_SIZE(log->name), log->name, log->write_index, size, log->wrap_count, + log->header_version, header_size, log->format, log->alignment); return 0; } @@ -123,7 +123,8 @@ static void fw_log_print_buffer(struct ivpu_fw_log_desc *desc, const char *prefi if (log->wrap_count == log->read_wrap_count) { if (log_end <= log_start) { - drm_printf(p, "==== %s \"%s\" log empty ====\n", prefix, log->name); + drm_printf(p, "==== %s \"%.*s\" log empty ====\n", prefix, + (int)ARRAY_SIZE(log->name), log->name); return; } } else if (log->wrap_count == log->read_wrap_count + 1) { @@ -133,7 +134,8 @@ static void fw_log_print_buffer(struct ivpu_fw_log_desc *desc, const char *prefi log_start = log_end; } - drm_printf(p, "==== %s \"%s\" log start ====\n", prefix, log->name); + drm_printf(p, "==== %s \"%.*s\" log start ====\n", prefix, (int)ARRAY_SIZE(log->name), + log->name); if (log_end > log_start) { fw_log_print_lines(log_data + log_start, log_end - log_start, p); } else { @@ -141,7 +143,8 @@ static void fw_log_print_buffer(struct ivpu_fw_log_desc *desc, const char *prefi fw_log_print_lines(log_data, log_end, p); } drm_printf(p, "\n\x1b[0m"); /* add new line and clear formatting */ - drm_printf(p, "==== %s \"%s\" log end ====\n", prefix, log->name); + drm_printf(p, "==== %s \"%.*s\" log end ====\n", prefix, (int)ARRAY_SIZE(log->name), + log->name); } static void From 157dcb8230a4e882e099ec4d1cb1957b0b2c9b26 Mon Sep 17 00:00:00 2001 From: Zizhi Wo Date: Sat, 5 Sep 2026 09:03:54 +0800 Subject: [PATCH 1038/2366] xfs: remove several unused and never-implemented declarations Over time a number of function declarations in various headers have become stale: either their implementations were removed when their last callers went away, or they were never implemented in the first place. None of them refer to anything anymore. Remove the following dead declarations and the unused stub: - xlog_assign_tail_lsn() and xlog_assign_tail_lsn_locked() - xfs_iext_realloc() - xfs_buf_iodone() - xfs_scrub_tester() and xfs_scrub_setup_inode_bmap_data() (never implemented placeholders) - the !CONFIG_XFS_ONLINE_REPAIR stub of xrep_tempfile_iolock_both() Signed-off-by: Zizhi Wo Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/common.h | 1 - fs/xfs/scrub/scrub.h | 1 - fs/xfs/scrub/tempfile.h | 4 ---- fs/xfs/xfs_buf_item.h | 1 - fs/xfs/xfs_inode.h | 1 - fs/xfs/xfs_log.h | 2 -- 6 files changed, 10 deletions(-) diff --git a/fs/xfs/scrub/common.h b/fs/xfs/scrub/common.h index 9d627fd50687..f0f073a93413 100644 --- a/fs/xfs/scrub/common.h +++ b/fs/xfs/scrub/common.h @@ -74,7 +74,6 @@ int xchk_setup_ag_rmapbt(struct xfs_scrub *sc); int xchk_setup_ag_refcountbt(struct xfs_scrub *sc); int xchk_setup_inode(struct xfs_scrub *sc); int xchk_setup_inode_bmap(struct xfs_scrub *sc); -int xchk_setup_inode_bmap_data(struct xfs_scrub *sc); int xchk_setup_directory(struct xfs_scrub *sc); int xchk_setup_xattr(struct xfs_scrub *sc); int xchk_setup_symlink(struct xfs_scrub *sc); diff --git a/fs/xfs/scrub/scrub.h b/fs/xfs/scrub/scrub.h index 6d7d3523b71f..737a5d6db15f 100644 --- a/fs/xfs/scrub/scrub.h +++ b/fs/xfs/scrub/scrub.h @@ -261,7 +261,6 @@ static inline int xchk_nothing(struct xfs_scrub *sc) } /* Metadata scrubbers */ -int xchk_tester(struct xfs_scrub *sc); int xchk_superblock(struct xfs_scrub *sc); int xchk_agf(struct xfs_scrub *sc); int xchk_agfl(struct xfs_scrub *sc); diff --git a/fs/xfs/scrub/tempfile.h b/fs/xfs/scrub/tempfile.h index 71c1b54599c3..d44ed43bafe0 100644 --- a/fs/xfs/scrub/tempfile.h +++ b/fs/xfs/scrub/tempfile.h @@ -39,10 +39,6 @@ int xrep_tempfile_roll_trans(struct xfs_scrub *sc); void xrep_tempfile_copyout_local(struct xfs_scrub *sc, int whichfork); bool xrep_is_tempfile(const struct xfs_inode *ip); #else -static inline void xrep_tempfile_iolock_both(struct xfs_scrub *sc) -{ - xchk_ilock(sc, XFS_IOLOCK_EXCL); -} # define xrep_is_tempfile(ip) (false) # define xrep_tempfile_adjust_directory_tree(sc) (0) # define xrep_tempfile_rele(sc) diff --git a/fs/xfs/xfs_buf_item.h b/fs/xfs/xfs_buf_item.h index 3159325dd17b..28c79989d725 100644 --- a/fs/xfs/xfs_buf_item.h +++ b/fs/xfs/xfs_buf_item.h @@ -60,7 +60,6 @@ static inline void xfs_buf_dquot_iodone(struct xfs_buf *bp) { } #endif /* CONFIG_XFS_QUOTA */ -void xfs_buf_iodone(struct xfs_buf *); bool xfs_buf_log_check_iovec(struct kvec *iovec); unsigned int xfs_buf_inval_log_space(unsigned int map_count, diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h index 34c1038ebfcd..1602027cd0aa 100644 --- a/fs/xfs/xfs_inode.h +++ b/fs/xfs/xfs_inode.h @@ -585,7 +585,6 @@ uint xfs_ilock_attr_map_shared(struct xfs_inode *); int xfs_ifree(struct xfs_trans *, struct xfs_inode *); int xfs_itruncate_extents_flags(struct xfs_trans **, struct xfs_inode *, int, xfs_fsize_t, int); -void xfs_iext_realloc(xfs_inode_t *, int, int); int xfs_log_force_inode(struct xfs_inode *ip); void xfs_iunpin_wait(xfs_inode_t *); diff --git a/fs/xfs/xfs_log.h b/fs/xfs/xfs_log.h index ca66429bf6c9..f715695e8fcb 100644 --- a/fs/xfs/xfs_log.h +++ b/fs/xfs/xfs_log.h @@ -105,8 +105,6 @@ int xfs_log_mount(struct xfs_mount *mp, int num_bblocks); int xfs_log_mount_finish(struct xfs_mount *mp); void xfs_log_mount_cancel(struct xfs_mount *); -xfs_lsn_t xlog_assign_tail_lsn(struct xfs_mount *mp); -xfs_lsn_t xlog_assign_tail_lsn_locked(struct xfs_mount *mp); void xfs_log_space_wake(struct xfs_mount *mp); int xfs_log_reserve(struct xfs_mount *mp, int length, int count, struct xlog_ticket **ticket, bool permanent); From 45a5f7285f835adb3b74c9344c09a7bd2c4fb664 Mon Sep 17 00:00:00 2001 From: Madhavan Srinivasan Date: Tue, 18 Aug 2026 09:20:37 +0530 Subject: [PATCH 1039/2366] MAINTAINERS: powerpc: Add Ritesh and Shrikanth Ritesh and Shrikanth has been helping in the powerpc mailing list patch reviews, adding them as reviewers. Acked-by: Shrikanth Hegde Acked-by: Ritesh Harjani (IBM) Acked-by: Christophe Leroy (CS GROUP) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260818035037.613186-1-maddy@linux.ibm.com --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..342cc5400fb4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15027,6 +15027,8 @@ M: Madhavan Srinivasan R: Michael Ellerman R: Nicholas Piggin R: Christophe Leroy (CS GROUP) +R: Ritesh Harjani (IBM) +R: Shrikanth Hegde L: linuxppc-dev@lists.ozlabs.org S: Supported W: https://github.com/linuxppc/wiki/wiki From 10e59fbdef13597836bd6459095caa02c80af3d7 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Tue, 1 Sep 2026 10:23:09 +0800 Subject: [PATCH 1040/2366] media: v4l2-h264: Fix memcmp() size in B1 reference list comparison In v4l2_h264_build_b_ref_lists(), the B0/B1 list equality check passes the entry count builder->num_valid to memcmp() instead of a byte size. Since struct v4l2_h264_reference is two bytes (fields and index), only half of each list is compared, so distinct lists can be wrongly treated as equal and trigger an incorrect swap(b1_reflist[0], b1_reflist[1]). Change the memcmp() size argument to sizeof(b1_reflist[0]) * builder->num_valid so that the full byte length of both reference lists is compared. Fixes: 624922a2739b ("media: v4l2-core: Add helpers to build the H264 P/B0/B1 reflists") Suggested-by: Nicolas Dufresne Cc: stable@vger.kernel.org Signed-off-by: Haotian Zhang Reviewed-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-h264.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/v4l2-core/v4l2-h264.c b/drivers/media/v4l2-core/v4l2-h264.c index c00197d095e7..2323f559c6a3 100644 --- a/drivers/media/v4l2-core/v4l2-h264.c +++ b/drivers/media/v4l2-core/v4l2-h264.c @@ -440,7 +440,8 @@ v4l2_h264_build_b_ref_lists(const struct v4l2_h264_reflist_builder *builder, } if (builder->num_valid > 1 && - !memcmp(b1_reflist, b0_reflist, builder->num_valid)) + !memcmp(b1_reflist, b0_reflist, + sizeof(b1_reflist[0]) * builder->num_valid)) swap(b1_reflist[0], b1_reflist[1]); print_ref_list_b(builder, b0_reflist, 0); From dc694a9929f7cb9c88ef91e45eb982b7bbe5a477 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:18:58 -0400 Subject: [PATCH 1041/2366] media: v4l2-ctrls: validate HEVC tile counts The stateless HEVC decoders read num_tile_columns_minus1 + 1 entries from column_width_minus1[] and num_tile_rows_minus1 + 1 from row_height_minus1[] and use them as tile-loop bounds, but std_validate_compound() does not bound these u8 counts. Reject a V4L2_CTRL_TYPE_HEVC_PPS with tiling enabled whose tile counts exceed the uAPI array capacity, mirroring the existing compound-control range checks. Fixes: 256fa3920874 ("media: v4l: Add definitions for HEVC stateless decoding") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Benjamin Gaignard Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-core.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 5b8a594fb9e2..9b6121a3a2d2 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -1253,6 +1253,18 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx, p_hevc_pps->flags &= ~V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED; + } else { + /* + * These count the entries the stateless HEVC drivers + * read from column_width_minus1[] / row_height_minus1[] + * and use as tile-loop bounds. + */ + if (p_hevc_pps->num_tile_columns_minus1 >= + ARRAY_SIZE(p_hevc_pps->column_width_minus1)) + return -EINVAL; + if (p_hevc_pps->num_tile_rows_minus1 >= + ARRAY_SIZE(p_hevc_pps->row_height_minus1)) + return -EINVAL; } if (p_hevc_pps->flags & From 439058ced617fbb3febc017b9e93bb7387f309e0 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:18:59 -0400 Subject: [PATCH 1042/2366] media: v4l2-ctrls: validate AV1 tile counts The stateless AV1 decoders use tile_info.tile_cols and tile_rows as loop bounds and as indices into the mi_*_starts[] and *_in_sbs_minus_1[] arrays, as the divisor for context_update_tile_id, and their product bounds the per-tile descriptor buffers, but std_validate_compound() does not bound these u8 fields. Reject a V4L2_CTRL_TYPE_AV1_FRAME whose tile_cols or tile_rows exceeds V4L2_AV1_MAX_TILE_COLS / _ROWS, or whose product exceeds V4L2_AV1_MAX_TILE_COUNT. A zero tile count is left to the consuming driver so the zero-initialised control that existing userspace submits is still accepted. Fixes: 9de30f579980 ("media: Add AV1 uAPI") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Benjamin Gaignard Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-core.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 9b6121a3a2d2..648b88c868bc 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -793,10 +793,30 @@ static int validate_av1_film_grain(struct v4l2_ctrl_av1_film_grain *fg) return 0; } +static int validate_av1_tile_info(struct v4l2_av1_tile_info *t) +{ + /* + * tile_cols and tile_rows index the per-tile descriptor arrays and + * bound the tile loops in the stateless AV1 drivers; the product + * bounds the total tile descriptor count. + */ + if (t->tile_cols > V4L2_AV1_MAX_TILE_COLS || + t->tile_rows > V4L2_AV1_MAX_TILE_ROWS) + return -EINVAL; + + if ((u32)t->tile_cols * t->tile_rows > V4L2_AV1_MAX_TILE_COUNT) + return -EINVAL; + + return 0; +} + static int validate_av1_frame(struct v4l2_ctrl_av1_frame *f) { int ret = 0; + ret = validate_av1_tile_info(&f->tile_info); + if (ret) + return ret; ret = validate_av1_quantization(&f->quantization); if (ret) return ret; From 592dd4f8442a13bed6e946d73d3164ba38b33bbd Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:19:00 -0400 Subject: [PATCH 1043/2366] media: hevc: add bounded tile-count helpers The stateless HEVC decoders compute the number of tile columns and rows from num_tile_columns_minus1 / num_tile_rows_minus1 and clamp it to the column_width_minus1[] / row_height_minus1[] capacity before using it as a loop bound. Add shared helpers in a new so the rkvdec and hantro drivers do not each open-code the min_t() clamp. Signed-off-by: Michael Bommarito Assisted-by: Claude:claude-opus-4-8 Fixes: 256fa3920874 ("media: v4l: Add definitions for HEVC stateless decoding") Cc: stable@vger.kernel.org Reviewed-by: Benjamin Gaignard Signed-off-by: Hans Verkuil --- include/media/v4l2-hevc.h | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 include/media/v4l2-hevc.h diff --git a/include/media/v4l2-hevc.h b/include/media/v4l2-hevc.h new file mode 100644 index 000000000000..973c96be16be --- /dev/null +++ b/include/media/v4l2-hevc.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Helper functions for HEVC stateless codecs. + */ + +#ifndef _MEDIA_V4L2_HEVC_H +#define _MEDIA_V4L2_HEVC_H + +#include +#include + +/** + * v4l2_hevc_pps_num_tile_columns - number of HEVC tile columns, bounded + * @pps: the V4L2 HEVC PPS control + * + * Return the number of tile columns (num_tile_columns_minus1 + 1) clamped to + * the capacity of column_width_minus1[]. The control validation already + * rejects out-of-range counts; this keeps the consuming drivers bounded too. + */ +static inline unsigned int +v4l2_hevc_pps_num_tile_columns(const struct v4l2_ctrl_hevc_pps *pps) +{ + return min_t(unsigned int, pps->num_tile_columns_minus1 + 1, + ARRAY_SIZE(pps->column_width_minus1)); +} + +/** + * v4l2_hevc_pps_num_tile_rows - number of HEVC tile rows, bounded + * @pps: the V4L2 HEVC PPS control + * + * Return the number of tile rows (num_tile_rows_minus1 + 1) clamped to the + * capacity of row_height_minus1[]. + */ +static inline unsigned int +v4l2_hevc_pps_num_tile_rows(const struct v4l2_ctrl_hevc_pps *pps) +{ + return min_t(unsigned int, pps->num_tile_rows_minus1 + 1, + ARRAY_SIZE(pps->row_height_minus1)); +} + +#endif /* _MEDIA_V4L2_HEVC_H */ From 81ad46bb33d8fd279aaa33af5296c648814c964b Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:19:01 -0400 Subject: [PATCH 1044/2366] media: rkvdec: bound HEVC tile loops and PPS id to the array capacity compute_tiles_uniform() and compute_tiles_non_uniform() loop over num_tile_columns_minus1 + 1 / num_tile_rows_minus1 + 1 entries, and assemble_hw_pps() writes one COLUMN_WIDTH / ROW_HEIGHT register per tile and indexes priv_tbl->param_set[] by pic_parameter_set_id, all taken from the untrusted PPS. Use the bounded v4l2_hevc_pps_num_tile_columns() / v4l2_hevc_pps_num_tile_rows() helpers for the tile loops, and bail out of assemble_hw_pps() before indexing priv_tbl->param_set[] with an out-of-range pic_parameter_set_id, so the writes stay within the hardware tables. Fixes: 3595375c2301 ("media: rkvdec: Add HEVC backend") Fixes: c9a59dc2acc7 ("media: rkvdec: Add HEVC support for the VDPU381 variant") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Signed-off-by: Hans Verkuil --- .../platform/rockchip/rkvdec/rkvdec-hevc-common.c | 14 ++++++++++---- .../media/platform/rockchip/rkvdec/rkvdec-hevc.c | 7 +++++-- .../platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c | 2 ++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c index 9c4a6093af32..2b8e04dd1572 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc-common.c @@ -16,6 +16,7 @@ */ #include +#include #include #include "rkvdec.h" @@ -37,15 +38,17 @@ void compute_tiles_uniform(struct rkvdec_hevc_run *run, u16 log2_min_cb_size, s32 pic_in_cts_height, u16 *column_width, u16 *row_height) { const struct v4l2_ctrl_hevc_pps *pps = run->pps; + unsigned int num_cols = v4l2_hevc_pps_num_tile_columns(pps); + unsigned int num_rows = v4l2_hevc_pps_num_tile_rows(pps); int i; - for (i = 0; i < pps->num_tile_columns_minus1 + 1; i++) + for (i = 0; i < num_cols; i++) column_width[i] = ((i + 1) * pic_in_cts_width) / (pps->num_tile_columns_minus1 + 1) - (i * pic_in_cts_width) / (pps->num_tile_columns_minus1 + 1); - for (i = 0; i < pps->num_tile_rows_minus1 + 1; i++) + for (i = 0; i < num_rows; i++) row_height[i] = ((i + 1) * pic_in_cts_height) / (pps->num_tile_rows_minus1 + 1) - (i * pic_in_cts_height) / @@ -57,17 +60,20 @@ void compute_tiles_non_uniform(struct rkvdec_hevc_run *run, u16 log2_min_cb_size s32 pic_in_cts_height, u16 *column_width, u16 *row_height) { const struct v4l2_ctrl_hevc_pps *pps = run->pps; + unsigned int num_cols = v4l2_hevc_pps_num_tile_columns(pps); + unsigned int num_rows = v4l2_hevc_pps_num_tile_rows(pps); s32 sum = 0; int i; - for (i = 0; i < pps->num_tile_columns_minus1; i++) { + /* The last tile entry is written after the loop, so iterate one less. */ + for (i = 0; i < num_cols - 1; i++) { column_width[i] = pps->column_width_minus1[i] + 1; sum += column_width[i]; } column_width[i] = pic_in_cts_width - sum; sum = 0; - for (i = 0; i < pps->num_tile_rows_minus1; i++) { + for (i = 0; i < num_rows - 1; i++) { row_height[i] = pps->row_height_minus1[i] + 1; sum += row_height[i]; } diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c index ff3942f91c5d..88e90c438eb9 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-hevc.c @@ -12,6 +12,7 @@ * Jeffy Chen */ +#include #include #include "rkvdec.h" @@ -135,6 +136,8 @@ static void assemble_hw_pps(struct rkvdec_ctx *ctx, * packet unit). so the driver copy SPS/PPS information to the exact PPS * packet unit for HW accessing. */ + if (pps->pic_parameter_set_id >= ARRAY_SIZE(priv_tbl->param_set)) + return; hw_ps = &priv_tbl->param_set[pps->pic_parameter_set_id]; memset(hw_ps, 0, sizeof(*hw_ps)); @@ -253,9 +256,9 @@ static void assemble_hw_pps(struct rkvdec_ctx *ctx, if (pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED) { /* Userspace also provide column width and row height for uniform spacing */ - for (i = 0; i <= pps->num_tile_columns_minus1; i++) + for (i = 0; i < v4l2_hevc_pps_num_tile_columns(pps); i++) WRITE_PPS(pps->column_width_minus1[i], COLUMN_WIDTH(i)); - for (i = 0; i <= pps->num_tile_rows_minus1; i++) + for (i = 0; i < v4l2_hevc_pps_num_tile_rows(pps); i++) WRITE_PPS(pps->row_height_minus1[i], ROW_HEIGHT(i)); } else { WRITE_PPS(DIV_ROUND_UP(sps->pic_width_in_luma_samples, ctb_size_y) - 1, diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c index d07c74679552..e1936e87f45b 100644 --- a/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c +++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-vdpu381-hevc.c @@ -145,6 +145,8 @@ static void assemble_hw_pps(struct rkvdec_ctx *ctx, * packet unit). so the driver copy SPS/PPS information to the exact PPS * packet unit for HW accessing. */ + if (pps->pic_parameter_set_id >= ARRAY_SIZE(priv_tbl->param_set)) + return; hw_ps = &priv_tbl->param_set[pps->pic_parameter_set_id]; memset(hw_ps, 0, sizeof(*hw_ps)); From 06236b094c899c22c12ac5097935eb6719293de8 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:19:02 -0400 Subject: [PATCH 1045/2366] media: verisilicon: hantro: bound G2 HEVC tile loop to the buffer capacity prepare_tile_info_buffer() writes one entry per tile into the tile_sizes DMA buffer, sized for a grid equal to the PPS uAPI array capacity. Use the bounded v4l2_hevc_pps_num_tile_columns() / v4l2_hevc_pps_num_tile_rows() helpers so the loops stay inside the buffer. Fixes: cb5dd5a0fa51 ("media: hantro: Introduce G2/HEVC decoder") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Benjamin Gaignard Signed-off-by: Hans Verkuil --- drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c index e8c2e83379de..e7a7c7a42467 100644 --- a/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c +++ b/drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c @@ -5,6 +5,8 @@ * Copyright (C) 2020 Safran Passenger Innovations LLC */ +#include + #include "hantro_hw.h" #include "hantro_g2_regs.h" @@ -15,8 +17,8 @@ static void prepare_tile_info_buffer(struct hantro_ctx *ctx) const struct v4l2_ctrl_hevc_pps *pps = ctrls->pps; const struct v4l2_ctrl_hevc_sps *sps = ctrls->sps; u16 *p = (u16 *)((u8 *)ctx->hevc_dec.tile_sizes.cpu); - unsigned int num_tile_rows = pps->num_tile_rows_minus1 + 1; - unsigned int num_tile_cols = pps->num_tile_columns_minus1 + 1; + unsigned int num_tile_rows = v4l2_hevc_pps_num_tile_rows(pps); + unsigned int num_tile_cols = v4l2_hevc_pps_num_tile_columns(pps); unsigned int pic_width_in_ctbs, pic_height_in_ctbs; unsigned int max_log2_ctb_size, ctb_size; bool tiles_enabled, uniform_spacing; From b84f6533a8ed2fd7b282fc7ab4b8efadc745a89c Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:19:03 -0400 Subject: [PATCH 1046/2366] media: verisilicon: rockchip: guard VPU981 AV1 divisor and tile buffer rockchip_vpu981_av1_dec_set_tile_info() divides context_update_tile_id by tile_info->tile_cols and writes one descriptor per tile into the tile_info DMA buffer, which holds AV1_MAX_TILES entries; tile_cols and tile_rows come from the bitstream. Guard the division against a zero tile_cols by initialising the context-update values to zero and computing them only when tile_cols is non-zero, and stop the descriptor writes once the tile_info buffer is full. The tile geometry written to the hardware registers is left unmodified; the per-dimension and total tile bounds are enforced by the control validation. Fixes: 727a400686a2 ("media: verisilicon: Add Rockchip AV1 decoder") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Benjamin Gaignard Signed-off-by: Hans Verkuil --- .../verisilicon/rockchip_vpu981_hw_av1_dec.c | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c index e4e21ad37323..fd00dbd79fe4 100644 --- a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c +++ b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c @@ -578,16 +578,30 @@ static void rockchip_vpu981_av1_dec_set_tile_info(struct hantro_ctx *ctx) const struct v4l2_av1_tile_info *tile_info = &ctrls->frame->tile_info; const struct v4l2_ctrl_av1_tile_group_entry *group_entry = ctrls->tile_group_entry; - int context_update_y = - tile_info->context_update_tile_id / tile_info->tile_cols; - int context_update_x = - tile_info->context_update_tile_id % tile_info->tile_cols; - int context_update_tile_id = - context_update_x * tile_info->tile_rows + context_update_y; + int context_update_y = 0; + int context_update_x = 0; + int context_update_tile_id = 0; u8 *dst = av1_dec->tile_info.cpu; + u8 *dst_end = dst + av1_dec->tile_info.size; struct hantro_dev *vpu = ctx->dev; int tile0, tile1; + /* + * tile_cols and tile_rows are bounded by the V4L2 control validation + * (V4L2_AV1_MAX_TILE_{COLS,ROWS} and V4L2_AV1_MAX_TILE_COUNT). Guard + * the divisor here, and keep the descriptor writes within the + * AV1_MAX_TILES tile_info buffer below; the register values use the + * unmodified tile geometry. + */ + if (tile_info->tile_cols) { + context_update_y = + tile_info->context_update_tile_id / tile_info->tile_cols; + context_update_x = + tile_info->context_update_tile_id % tile_info->tile_cols; + context_update_tile_id = + context_update_x * tile_info->tile_rows + context_update_y; + } + memset(dst, 0, av1_dec->tile_info.size); for (tile0 = 0; tile0 < tile_info->tile_cols; tile0++) { @@ -598,6 +612,10 @@ static void rockchip_vpu981_av1_dec_set_tile_info(struct hantro_ctx *ctx) tile_info->height_in_sbs_minus_1[tile1] + 1; u32 x0 = tile_info->width_in_sbs_minus_1[tile0] + 1; + /* Stop once the tile_info descriptor buffer is full. */ + if (dst + 16 > dst_end) + break; + /* tile size in SB units (width,height) */ *dst++ = x0; *dst++ = 0; @@ -622,6 +640,8 @@ static void rockchip_vpu981_av1_dec_set_tile_info(struct hantro_ctx *ctx) *dst++ = (end >> 16) & 255; *dst++ = (end >> 24) & 255; } + if (dst + 16 > dst_end) + break; } hantro_reg_write(vpu, &av1_multicore_expect_context_update, !!(context_update_x == 0)); From 367db8b23c26a913d76ed70457bbcd781c422b49 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:19:04 -0400 Subject: [PATCH 1047/2366] media: verisilicon: rockchip: reject AV1 frames exceeding the tile capacity rockchip_vpu981_av1_dec_set_tile_info() indexes the tile group entry array by tile1 * tile_cols + tile0, reading up to tile_cols * tile_rows entries, lays out one descriptor per tile in the AV1_MAX_TILES tile_info buffer, and programs the real tile_cols / tile_rows into the hardware. The tile group entry control is a dynamic array sized to the number of entries userspace submitted, independent of tile_cols / tile_rows, so a frame that claims more tiles than entries reads past the array. A frame that claims more than AV1_MAX_TILES tiles also leaves the hardware programmed for more tiles than the descriptor buffer holds. Reject both in prepare_run(): tile_cols * tile_rows must not exceed the submitted entry count or AV1_MAX_TILES. The entry count is read via v4l2_ctrl_find() (ctrl->elems). This mirrors the bound the mediatek AV1 decoder already enforces. Fixes: 727a400686a2 ("media: verisilicon: Add Rockchip AV1 decoder") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Benjamin Gaignard Signed-off-by: Hans Verkuil --- .../verisilicon/rockchip_vpu981_hw_av1_dec.c | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c index fd00dbd79fe4..00aa566a4ccd 100644 --- a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c +++ b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c @@ -431,20 +431,39 @@ static int rockchip_vpu981_av1_dec_prepare_run(struct hantro_ctx *ctx) { struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + const struct v4l2_av1_tile_info *tile_info; + struct v4l2_ctrl *tge; + u32 num_tiles; ctrls->sequence = hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_SEQUENCE); if (WARN_ON(!ctrls->sequence)) return -EINVAL; - ctrls->tile_group_entry = - hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY); - if (WARN_ON(!ctrls->tile_group_entry)) + tge = v4l2_ctrl_find(&ctx->ctrl_handler, + V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY); + if (WARN_ON(!tge)) return -EINVAL; + ctrls->tile_group_entry = tge->p_cur.p; ctrls->frame = hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_FRAME); if (WARN_ON(!ctrls->frame)) return -EINVAL; + /* + * rockchip_vpu981_av1_dec_set_tile_info() indexes the tile group + * entry array by tile1 * tile_cols + tile0, so it reads up to + * tile_cols * tile_rows entries, and lays out one descriptor per tile + * in the AV1_MAX_TILES tile_info buffer while programming the real + * tile geometry into the hardware. Reject a frame that claims more + * tiles than userspace submitted, or more than the hardware tile + * buffer holds, so the read stays in bounds and the programmed + * geometry matches the descriptors written. + */ + tile_info = &ctrls->frame->tile_info; + num_tiles = (u32)tile_info->tile_cols * tile_info->tile_rows; + if (num_tiles > tge->elems || num_tiles > AV1_MAX_TILES) + return -EINVAL; + ctrls->film_grain = hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_FILM_GRAIN); From 37bef2170d4c88fc3d708eecf3ef0f4032bc1372 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 16 Jun 2026 22:19:05 -0400 Subject: [PATCH 1048/2366] media: mediatek: vcodec: bound AV1 tile-start copy to the array capacity vdec_av1_slice_setup_tile() copies tile_cols + 1 / tile_rows + 1 entries into mi_col_starts[] / mi_row_starts[] from the bitstream tile_info. Bound the copy to the array capacity. Fixes: 0934d3759615 ("media: mediatek: vcodec: separate decoder and encoder") Assisted-by: Claude:claude-opus-4-8 Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Signed-off-by: Hans Verkuil --- .../mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c index 2d622e85f827..49d9b4a72387 100644 --- a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c +++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_av1_req_lat_if.c @@ -1299,11 +1299,12 @@ static void vdec_av1_slice_setup_tile(struct vdec_av1_slice_frame *frame, tile->uniform_tile_spacing_flag = BIT_FLAG(ctrl_tile, V4L2_AV1_TILE_INFO_FLAG_UNIFORM_TILE_SPACING); - for (i = 0; i < tile->tile_cols + 1; i++) + /* Bound the copy to the mi_col_starts[]/mi_row_starts[] capacity. */ + for (i = 0; i < tile->tile_cols + 1 && i < V4L2_AV1_MAX_TILE_COLS + 1; i++) tile->mi_col_starts[i] = ALIGN(ctrl_tile->mi_col_starts[i], BIT(mib_size_log2)) >> mib_size_log2; - for (i = 0; i < tile->tile_rows + 1; i++) + for (i = 0; i < tile->tile_rows + 1 && i < V4L2_AV1_MAX_TILE_ROWS + 1; i++) tile->mi_row_starts[i] = ALIGN(ctrl_tile->mi_row_starts[i], BIT(mib_size_log2)) >> mib_size_log2; } From b7313376809292f0e6bf2d5750225c8b66e9ccda Mon Sep 17 00:00:00 2001 From: Nick Pegg Date: Sun, 6 Sep 2026 08:55:37 -0700 Subject: [PATCH 1049/2366] ALSA: usb-audio: Add quirk flags for Behringer UV1 The Behringer UV1 is a microphone audio processor with a USB audio interface, which experiences periodic stutters unless implicit_fb is used. This seems to be a similar device to the Behringer UMC series, so I copied the quirks from those. I've confirmed that my own UV1 works great with these flags set. Signed-off-by: Nick Pegg Link: https://patch.msgid.link/20260906155616.1625465-1-nick@nickpegg.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 5dede66ea69e..c5870901a8d4 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2413,6 +2413,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x1397, 0x050c, /* Behringer Flow 8 */ QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), + DEVICE_FLG(0x1397, 0x0510, /* Behringer UV1 */ + QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ From ad7265b29995ff12b2ce834a6a6162d616462362 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 7 Sep 2026 09:55:44 +0200 Subject: [PATCH 1050/2366] .get_maintainer.ignore: add myself Since I've touched so many things all over I get CC'ed on far too many things - add myself here to avoid that. I'm also listed in MAINTAINERS for the right things. Signed-off-by: Johannes Berg --- .get_maintainer.ignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.get_maintainer.ignore b/.get_maintainer.ignore index 5ad082b4dd03..d19b725fd803 100644 --- a/.get_maintainer.ignore +++ b/.get_maintainer.ignore @@ -4,6 +4,8 @@ Alyssa Rosenzweig Askar Safin Christoph Hellwig Jeff Kirsher +Johannes Berg +Johannes Berg Marc Gonzalez Nathan Chancellor Ralf Baechle From 113a9796effe3376d2ec5aabcca1fef4fef4cd62 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Mon, 12 Aug 2024 16:19:48 +0200 Subject: [PATCH 1051/2366] tick/broadcast: Plug clockevents replacement race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 朱恺乾 reported and decoded the following race condition when a broadcast device is replaced: CPUA CPUB __tick_broadcast_oneshot_control() bc = tick_broadcast_device.evtdev; tick_install_broadcast_device(dev) clockevents_exchange_device(cur, dev) shutdown(cur); detach(cur); cur->handler = noop; tick_broadcast_device.evtdev = dev; tick_broadcast_set_event(bc, next_event); <- FAIL: arms a detached device. If the original broadcast device has a restricted interrupt affinity mask and the last CPU in that mask goes offline then the BUG() in tick_cleanup_dead_cpu() triggers because the clockevent device is not in detached state. The reason for this is that tick_install_broadcast_device() is not serialized vs. tick broadcast operations. The obvious cure is to serialize tick_install_broadcast_device() with tick_broadcast_lock against a concurrent tick broadcast operation. That requires to split clockevents_exchange_device() into two parts, one which does the exchange, shutdown and detach operation and the other which drops the module reference count. This is required because the module reference cannot be dropped while holding tick_broadcast_lock. Let clockevents_exchange_device() do both operations as before, but let the broadcast device code take the two step approach and do the device exchange under tick_broadcast_lock and drop the module reference count after releasing it. Fixes: f8381cba04ba ("[PATCH] tick-management: broadcast functionality") Reported-by: 朱恺乾 Signed-off-by: Thomas Gleixner Signed-off-by: Thomas Gleixner Reviewed-by: Bradley Morgan Tested-by: 刘术高 Cc: stable@vger.kernel.org Link: https://patch.msgid.link/87cymdsu0r.ffs@tglx --- kernel/time/clockevents.c | 35 +++++++++++++++++++++-------------- kernel/time/tick-broadcast.c | 36 ++++++++++++++++++++++-------------- kernel/time/tick-internal.h | 2 ++ 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index 0014d163f989..62ad7c000386 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c @@ -615,6 +615,24 @@ void clockevents_handle_noop(struct clock_event_device *dev) { } +void __clockevents_exchange_device(struct clock_event_device *old, + struct clock_event_device *new) +{ + /* + * Caller releases a clock event device. We queue it into the + * released list and do a notify add later. + */ + if (old) { + clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED); + list_move(&old->list, &clockevents_released); + } + + if (new) { + WARN_ON(!clockevent_state_detached(new)); + clockevents_shutdown(new); + } +} + /** * clockevents_exchange_device - release and request clock devices * @old: device to release (can be NULL) @@ -626,20 +644,9 @@ void clockevents_handle_noop(struct clock_event_device *dev) void clockevents_exchange_device(struct clock_event_device *old, struct clock_event_device *new) { - /* - * Caller releases a clock event device. We queue it into the - * released list and do a notify add later. - */ - if (old) { + __clockevents_exchange_device(old, new); + if (old) module_put(old->owner); - clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED); - list_move(&old->list, &clockevents_released); - } - - if (new) { - BUG_ON(!clockevent_state_detached(new)); - clockevents_shutdown(new); - } } /** @@ -699,7 +706,7 @@ void tick_offline_cpu(unsigned int cpu) if (cpumask_test_cpu(cpu, dev->cpumask) && cpumask_weight(dev->cpumask) == 1 && !tick_is_broadcast_device(dev)) { - BUG_ON(!clockevent_state_detached(dev)); + WARN_ON(!clockevent_state_detached(dev)); list_del(&dev->list); } } diff --git a/kernel/time/tick-broadcast.c b/kernel/time/tick-broadcast.c index 115e0bf01276..bda3d2391a60 100644 --- a/kernel/time/tick-broadcast.c +++ b/kernel/time/tick-broadcast.c @@ -165,23 +165,31 @@ static bool tick_set_oneshot_wakeup_device(struct clock_event_device *newdev, */ void tick_install_broadcast_device(struct clock_event_device *dev, int cpu) { - struct clock_event_device *cur = tick_broadcast_device.evtdev; + struct clock_event_device *cur; - if (tick_set_oneshot_wakeup_device(dev, cpu)) - return; + scoped_guard(raw_spinlock_irqsave, &tick_broadcast_lock) { - if (!tick_check_broadcast_device(cur, dev)) - return; + if (tick_set_oneshot_wakeup_device(dev, cpu)) + return; - if (!try_module_get(dev->owner)) - return; + cur = tick_broadcast_device.evtdev; + if (!tick_check_broadcast_device(cur, dev)) + return; - clockevents_exchange_device(cur, dev); + if (!try_module_get(dev->owner)) + return; + + __clockevents_exchange_device(cur, dev); + if (cur) + cur->event_handler = clockevents_handle_noop; + WRITE_ONCE(tick_broadcast_device.evtdev, dev); + if (!cpumask_empty(tick_broadcast_mask)) + tick_broadcast_start_periodic(dev); + } + + /* Module release must be outside of the lock */ if (cur) - cur->event_handler = clockevents_handle_noop; - tick_broadcast_device.evtdev = dev; - if (!cpumask_empty(tick_broadcast_mask)) - tick_broadcast_start_periodic(dev); + module_put(cur->owner); if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT)) return; @@ -1218,7 +1226,7 @@ int tick_broadcast_oneshot_active(void) */ bool tick_broadcast_oneshot_available(void) { - struct clock_event_device *bc = tick_broadcast_device.evtdev; + struct clock_event_device *bc = READ_ONCE(tick_broadcast_device.evtdev); return bc ? bc->features & CLOCK_EVT_FEAT_ONESHOT : false; } @@ -1226,7 +1234,7 @@ bool tick_broadcast_oneshot_available(void) #else int __tick_broadcast_oneshot_control(enum tick_broadcast_state state) { - struct clock_event_device *bc = tick_broadcast_device.evtdev; + struct clock_event_device *bc = READ_ONCE(tick_broadcast_device.evtdev); if (!bc || (bc->features & CLOCK_EVT_FEAT_HRTIMER)) return -EBUSY; diff --git a/kernel/time/tick-internal.h b/kernel/time/tick-internal.h index 182974c4f21b..65680db95053 100644 --- a/kernel/time/tick-internal.h +++ b/kernel/time/tick-internal.h @@ -55,6 +55,8 @@ static inline void clockevent_set_state(struct clock_event_device *dev, } extern void clockevents_shutdown(struct clock_event_device *dev); +extern void __clockevents_exchange_device(struct clock_event_device *old, + struct clock_event_device *new); extern void clockevents_exchange_device(struct clock_event_device *old, struct clock_event_device *new); extern void clockevents_switch_state(struct clock_event_device *dev, From 954f7a48fa2ae7310c67729fb556caf726783436 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sat, 5 Sep 2026 10:43:57 +0200 Subject: [PATCH 1052/2366] entry: Guard syscall_enter_audit() invocation with CONFIG_AUDITSYSCALL A bunch of older cross compilers notably RISCV64 and S390 fail to eliminate the dead code when CONFIG_AUDITSYSCALL=n. The code in question is: if (unlikely(audit_context()) syscall_enter_audit(regs); and in case of CONFIG_AUDITSYSCALL=n: static inline struct audit_context *audit_context(void) { return NULL; } which should make the compiler eliminate the syscall_enter_audit() call. But a RISV64 GCC12 cross compiler translates that into: if (unlikely(audit_context())) 1c34: 00000097 auipc ra,0x0 1c38: 000080e7 jalr ra # 1c34 <.L785> 1c3c: c511 beqz a0,1c48 <.L787> syscall_enter_audit(regs); 1c3e: 8526 mv a0,s1 1c40: 00000097 auipc ra,0x0 1c44: 000080e7 jalr ra # 1c40 <.L785+0xc> and then claims in the failing link: include/asm-generic/preempt.h:54:(.noinstr.text+0x1a20): undefined reference to 'syscall_enter_audit' which is obviously hallucination. Add an explicit IS_ENABLED(CONFIG_AUDITSYSCALL) check into the condition to cure this compiler madness. Fixes: 6f25517010dd ("entry: Rework syscall_audit_enter()") Reported-by: kernel test robot Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://patch.msgid.link/87tso45bqq.ffs@fw13 Closes: https://lore.kernel.org/oe-kbuild-all/202609031938.ZvZZaRQy-lkp@intel.com/ --- include/linux/entry-common.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index 6574b7183c01..fa2854fed1f2 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -102,7 +102,14 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l if (unlikely(work & SYSCALL_WORK_SYSCALL_TRACEPOINT)) trace_syscall_enter(regs); - if (unlikely(audit_context())) + /* + * The config check works around broken compilers which fail to + * eliminate the dead code in case of CONFIG_AUDITSYSCALL=n as they + * insist on creating a always false runtime condition based on + * audit_context() which returns NULL in that case. The explicit + * IS_ENABLED() check makes that madness go away. + */ + if (IS_ENABLED(CONFIG_AUDITSYSCALL) && unlikely(audit_context())) syscall_enter_audit(regs); return true; From 2c6dc792538260a8087ac5b22c31b3b8e47c85d6 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Sat, 22 Aug 2026 14:29:00 +0200 Subject: [PATCH 1053/2366] landlock: Fix use-after-free of the source's parent directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit current_check_refer_path() reads old_dentry->d_parent without holding a reference nor a lock on it, and then dereferences it in collect_domain_accesses() and in the audit record. A reference on a child does not pin its parent: __d_move() reassigns dentry->d_parent and drops the reference the child held on its former parent. hook_path_rename() is not affected because the rename path calls lock_rename() before the hook, so the source cannot be reparented under it. hook_path_link() has no such protection: filename_linkat() holds a reference on the source dentry but neither locks nor references its parent, so a concurrent rename(2) can reparent the source while security_path_link() runs, and the former parent can then be removed and freed while the hook walks it. A process can trigger this after entering a Landlock domain that handles at least one filesystem access right. The process can then race a linkat(2) loop against rename(2) and rmdir(2): BUG: KASAN: slab-use-after-free in collect_domain_accesses+0x278/0x290 Read of size 4 at addr ffff888160bd53f4 by task llrepro2/549 collect_domain_accesses+0x278/0x290 current_check_refer_path+0x952/0x1120 security_path_link+0x1be/0x320 filename_linkat+0x342/0x6d0 __x64_sys_linkat+0xfa/0x150 Freed by task 562: kmem_cache_free+0x139/0x4c0 i_callback+0x4b/0x80 rcu_core+0x7dc/0x10a0 Take a reference on the dentry selected as the source parent, using dget() for the common-mount-root case and dget_parent() otherwise. Release it after the hierarchy walk and synchronous audit logging. Cc: stable@vger.kernel.org Fixes: b91c3e4ea756 ("landlock: Add support for file reparenting with LANDLOCK_ACCESS_FS_REFER") Signed-off-by: Norbert Szetei Reviewed-by: Günther Noack Tested-by: Günther Noack Link: https://patch.msgid.link/E9CDD9E6-E960-4DE2-B1AC-5667D52ABB3E@doyensec.com [mic: Clarify the caller, reachability, and reference handling] Signed-off-by: Mickaël Salaün --- security/landlock/fs.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 30aa6ce13590..330a1871bf94 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -1298,11 +1298,12 @@ static int current_check_refer_path(struct dentry *const old_dentry, /* * old_dentry may be the root of the common mount point and * !IS_ROOT(old_dentry) at the same time (e.g. with open_tree() and - * OPEN_TREE_CLONE). We do not need to call dget(old_parent) because - * we keep a reference to old_dentry. + * OPEN_TREE_CLONE). Pin the dentry used as old_parent in either case. + * Otherwise, dget_parent() safely fetches and pins the current parent + * against a concurrent rename(2). */ - old_parent = (old_dentry == mnt_dir.dentry) ? old_dentry : - old_dentry->d_parent; + old_parent = (old_dentry == mnt_dir.dentry) ? dget(old_dentry) : + dget_parent(old_dentry); /* new_dir->dentry is equal to new_dentry->d_parent */ allow_parent1 = collect_domain_accesses(subject->domain, mnt_dir.dentry, @@ -1311,8 +1312,10 @@ static int current_check_refer_path(struct dentry *const old_dentry, allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry, new_dir->dentry, &layer_masks_parent2); - if (allow_parent1 && allow_parent2) + if (allow_parent1 && allow_parent2) { + dput(old_parent); return 0; + } /* * To be able to compare source and destination domain access rights, @@ -1324,8 +1327,10 @@ static int current_check_refer_path(struct dentry *const old_dentry, subject->domain, &mnt_dir, access_request_parent1, &layer_masks_parent1, &request1, old_dentry, access_request_parent2, &layer_masks_parent2, &request2, - exchange ? new_dentry : NULL)) + exchange ? new_dentry : NULL)) { + dput(old_parent); return 0; + } if (request1.access) { request1.audit.u.path.dentry = old_parent; @@ -1335,6 +1340,7 @@ static int current_check_refer_path(struct dentry *const old_dentry, request2.audit.u.path.dentry = new_dir->dentry; landlock_log_denial(subject, &request2); } + dput(old_parent); /* * This prioritizes EACCES over EXDEV for all actions, including From 96bf9831fbf423b8104f7948cd8fe7007ecfb46c Mon Sep 17 00:00:00 2001 From: Chengyu Zhu Date: Mon, 7 Sep 2026 16:33:19 +0800 Subject: [PATCH 1054/2366] erofs: delimit inode_share cache key components Previously, inode_share keys were encoded as follows: fingerprint || domain_id It would be better to have a separator between the fingerprint and domain ID so that the fingerprint won't be parsed as part of a domain ID. Change the key encoding as follows: domain_id || '\0' || fingerprint Since domain_id is a NUL-terminated string, this makes the in-memory key indices unambiguous. Signed-off-by: Chengyu Zhu Reviewed-by: Gao Xiang Fixes: e0bf7d1c074d ("erofs: support user-defined fingerprint name") Signed-off-by: Gao Xiang --- fs/erofs/xattr.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fs/erofs/xattr.c b/fs/erofs/xattr.c index df7ea019526d..57cfb7520782 100644 --- a/fs/erofs/xattr.c +++ b/fs/erofs/xattr.c @@ -620,8 +620,8 @@ int erofs_xattr_fill_inode_fingerprint(struct erofs_inode_fingerprint *fp, { struct erofs_sb_info *sbi = EROFS_SB(inode->i_sb); struct erofs_xattr_prefix_item *prefix; + int domainlen, valuelen, base_index; const char *infix; - int valuelen, base_index; if (!test_opt(&sbi->opt, INODE_SHARE)) return -EOPNOTSUPP; @@ -633,17 +633,18 @@ int erofs_xattr_fill_inode_fingerprint(struct erofs_inode_fingerprint *fp, valuelen = erofs_getxattr(inode, base_index, infix, NULL, 0); if (valuelen <= 0 || valuelen > (1 << sbi->blkszbits)) return -EFSCORRUPTED; - fp->size = valuelen + (domain_id ? strlen(domain_id) : 0); + domainlen = strlen(domain_id); + fp->size = domainlen + 1 + valuelen; fp->opaque = kmalloc(fp->size, GFP_KERNEL); if (!fp->opaque) return -ENOMEM; + memcpy(fp->opaque, domain_id, domainlen + 1); if (valuelen != erofs_getxattr(inode, base_index, infix, - fp->opaque, valuelen)) { + fp->opaque + domainlen + 1, valuelen)) { kfree(fp->opaque); fp->opaque = NULL; return -EFSCORRUPTED; } - memcpy(fp->opaque + valuelen, domain_id, fp->size - valuelen); return 0; } #endif From e7557b9ef7a87570cbd0873a163de05bde80c39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Mon, 7 Sep 2026 12:35:01 +0200 Subject: [PATCH 1055/2366] selftests/landlock: Test abstract socket trace name limits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The landlock_deny_scope_abstract_unix_socket event captures binary socket names with __string_len(), whose dynamic field reserves an extra byte for the NUL terminator. The printer subtracts this byte before escaping the content. Exercise the minimum accepted address length, which has no name content, and the maximum sockaddr_un length, which has 107 content bytes. Check the exact trace output at both boundaries. The existing stream and datagram variants share this event, so the boundary variants only need the stream path. Because these boundary names are fixed, run the fixture in a private network namespace. Abstract UNIX socket names are scoped by network namespace, preventing concurrent bind() calls from colliding. The lower-bound test confirms that the subtraction recovers zero instead of underflowing. Cc: Günther Noack Link: https://patch.msgid.link/CAL4aGcVcT0VWVFmGi_vLqxxZ9KdOHfGXYZtKjBdvoUyFjbu5=A@mail.gmail.com Link: https://patch.msgid.link/20260907103503.109461-1-mic@digikod.net Signed-off-by: Mickaël Salaün --- .../landlock/scoped_abstract_unix_test.c | 76 +++++++++++++------ 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c index 6dbe863ea571..5dc0debacb2a 100644 --- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c +++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c @@ -1222,7 +1222,7 @@ FIXTURE_SETUP(trace_unix) int ret; set_cap(_metadata, CAP_SYS_ADMIN); - ASSERT_EQ(0, unshare(CLONE_NEWNS)); + ASSERT_EQ(0, unshare(CLONE_NEWNS | CLONE_NEWNET)); ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)); ret = tracefs_fixture_setup(); @@ -1252,6 +1252,11 @@ FIXTURE_TEARDOWN(trace_unix) clear_cap(_metadata, CAP_SYS_ADMIN); } +static const char + trace_unix_max_name[sizeof(((struct sockaddr_un *)0)->sun_path)] = { + [0 ... sizeof(trace_unix_max_name) - 2] = 'x', + }; + /* clang-format off */ FIXTURE_VARIANT(trace_unix) { /* clang-format on */ @@ -1259,6 +1264,8 @@ FIXTURE_VARIANT(trace_unix) { bool sandbox; bool sandbox_target; /* Peer owned by a domain: peer_domain != 0. */ int expect_denied; + const char *name; /* NULL generates a PID-based binary name. */ + size_t name_len; }; /* clang-format off */ @@ -1281,6 +1288,26 @@ FIXTURE_VARIANT_ADD(trace_unix, stream_allowed) { .sandbox_target = false, .expect_denied = 0, }; +/* Stream: lower abstract-name length boundary. */ +FIXTURE_VARIANT_ADD(trace_unix, stream_denied_empty_name) { + .sock_type = SOCK_STREAM, + .sandbox = true, + .sandbox_target = false, + .expect_denied = 1, + .name = "", + .name_len = 0, +}; + +/* Stream: upper abstract-name length boundary. */ +FIXTURE_VARIANT_ADD(trace_unix, stream_denied_max_name) { + .sock_type = SOCK_STREAM, + .sandbox = true, + .sandbox_target = false, + .expect_denied = 1, + .name = trace_unix_max_name, + .name_len = sizeof(trace_unix_max_name) - 1, +}; + /* Datagram: sandboxed client sendto() an unsandboxed peer (peer_domain=0). */ FIXTURE_VARIANT_ADD(trace_unix, dgram_denied) { .sock_type = SOCK_DGRAM, .sandbox = true, @@ -1304,12 +1331,11 @@ FIXTURE_VARIANT_ADD(trace_unix, dgram_allowed) { /* * A sandboxed thread reaching an abstract unix socket peer through connect(2) * (stream) or sendto(2) (datagram) is denied and emits - * landlock_deny_scope_abstract_unix_socket. The abstract name is crafted with - * a space and an embedded NUL followed by an "END" marker to check the - * tracepoint escaping and its length handling (a raw space would break the - * sun_path field regex; strlen() would truncate at the NUL and drop "END"). - * peer_pid is only meaningful for a stream peer (a datagram peer has no - * SO_PEERCRED), so it is asserted only there. + * landlock_deny_scope_abstract_unix_socket. The default abstract name has a + * space and an embedded NUL followed by an "END" marker to check escaping and + * binary length handling. Additional stream variants cover the minimum and + * maximum abstract-name lengths. peer_pid is only meaningful for a stream peer + * (a datagram peer has no SO_PEERCRED), so it is asserted only there. */ TEST_F(trace_unix, deny_scope_unix) { @@ -1336,12 +1362,19 @@ TEST_F(trace_unix, deny_scope_unix) ASSERT_LE(0, server_fd); addr.sun_path[0] = '\0'; - name_len = snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, - "landlock_trace_test_%d ", getpid()); - addr.sun_path[1 + name_len] = '\0'; - memcpy(addr.sun_path + 1 + name_len + 1, "END", 3); - addr_len = - offsetof(struct sockaddr_un, sun_path) + 1 + name_len + 1 + 3; + if (variant->name) { + ASSERT_LE(variant->name_len, sizeof(addr.sun_path) - 1); + memcpy(addr.sun_path + 1, variant->name, variant->name_len); + name_len = variant->name_len; + } else { + name_len = snprintf(addr.sun_path + 1, + sizeof(addr.sun_path) - 1, + "landlock_trace_test_%d ", getpid()); + addr.sun_path[1 + name_len] = '\0'; + memcpy(addr.sun_path + 1 + name_len + 1, "END", 3); + name_len += 1 + 3; + } + addr_len = offsetof(struct sockaddr_un, sun_path) + 1 + name_len; ASSERT_EQ(0, bind(server_fd, (struct sockaddr *)&addr, addr_len)); if (variant->sock_type == SOCK_STREAM) @@ -1430,19 +1463,18 @@ TEST_F(trace_unix, deny_scope_unix) count, buf); } - /* - * sun_path is escaped: a raw space would break this field's [^ ]*$ - * regex, so a successful extract proves the space was escaped, and its - * full length is honored: the "END" marker after the embedded NUL must - * survive (strlen() would truncate it at the NUL). - */ ASSERT_EQ(0, tracefs_extract_field( buf, REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(TRACE_TASK), "sun_path", field, sizeof(field))); - EXPECT_NE(NULL, strstr(field, "END")) - { - TH_LOG("sun_path truncated or unescaped: %s", field); + if (variant->name) { + EXPECT_STREQ(variant->name, field); + } else { + /* An embedded NUL must not truncate the following marker. */ + EXPECT_NE(NULL, strstr(field, "END")) + { + TH_LOG("sun_path truncated or unescaped: %s", field); + } } /* peer_pid is the parent's PID for a stream peer (0 for datagram). */ From 353a95f1cd8da8a5436a3f070be07d2f484486cd Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 09:23:25 +0200 Subject: [PATCH 1056/2366] syscall_user_dispatch: Use CONFIG_SYSCTL for sysctl guard Commit 8d75c338f0bc ("sysctl: remove CONFIG_PROC_SYSCTL, it just mirrors CONFIG_SYSCTL") removed CONFIG_PROC_SYSCTL, but the sysctl added by commit 5b6e32ba7b59 ("syscall_user_dispatch: Add kernel.syscall_user_dispatch sysctl") is still guarded by it. Now that both commits are merged, kernel.syscall_user_dispatch is no longer registered. syscall_user_dispatch_allowed defaults to true. SUD therefore remains available, but administrators cannot disable new activations. Use CONFIG_SYSCTL for the guard and documentation. Fixes: 5b6e32ba7b59 ("syscall_user_dispatch: Add kernel.syscall_user_dispatch sysctl") Assisted-by: Codex:gpt-5.6-sol Acked-by: Oleg Nesterov Reviewed-by: Joel Granados Signed-off-by: Karl Mehltretter Acked-by: Randy Dunlap Reviewed-by: Bradley Morgan Signed-off-by: Joel Granados --- Documentation/admin-guide/sysctl/kernel.rst | 2 +- kernel/entry/syscall_user_dispatch.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/admin-guide/sysctl/kernel.rst b/Documentation/admin-guide/sysctl/kernel.rst index b6328cd0f43e..ffea61d448eb 100644 --- a/Documentation/admin-guide/sysctl/kernel.rst +++ b/Documentation/admin-guide/sysctl/kernel.rst @@ -1416,7 +1416,7 @@ Controls whether userspace may arm Syscall User Dispatch via == =================================================================== Only present when the kernel is built with ``CONFIG_SYSCALL_USER_DISPATCH`` -and ``CONFIG_PROC_SYSCTL``. +and ``CONFIG_SYSCTL``. sysctl_writes_strict diff --git a/kernel/entry/syscall_user_dispatch.c b/kernel/entry/syscall_user_dispatch.c index 2002c7aae435..59c861866941 100644 --- a/kernel/entry/syscall_user_dispatch.c +++ b/kernel/entry/syscall_user_dispatch.c @@ -178,7 +178,7 @@ int syscall_user_dispatch_set_config(struct task_struct *task, unsigned long siz (char __user *)(uintptr_t)cfg.selector); } -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL static const struct ctl_table syscall_user_dispatch_sysctls[] = { { .procname = "syscall_user_dispatch", @@ -195,4 +195,4 @@ static int __init syscall_user_dispatch_sysctl_init(void) return 0; } late_initcall(syscall_user_dispatch_sysctl_init); -#endif /* CONFIG_PROC_SYSCTL */ +#endif /* CONFIG_SYSCTL */ From d5bcf9ccaa357396089bbfa47fc82b093f109b1e Mon Sep 17 00:00:00 2001 From: Fangyu Yu Date: Tue, 1 Sep 2026 21:39:18 +0800 Subject: [PATCH 1057/2366] iommu/riscv: Add command queue lock Add a raw spinlock to the RISC-V IOMMU queue state so command queue publishing can be serialized by a later change. Fixes: 856c0cfe5c5f ("iommu/riscv: Command and fault queue support") Signed-off-by: Fangyu Yu Reviewed-by: Nutty Liu Signed-off-by: Joerg Roedel --- drivers/iommu/riscv/iommu.c | 1 + drivers/iommu/riscv/iommu.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c index cec3ddd7ab10..2c0dcc90cf85 100644 --- a/drivers/iommu/riscv/iommu.c +++ b/drivers/iommu/riscv/iommu.c @@ -1560,6 +1560,7 @@ int riscv_iommu_init(struct riscv_iommu_device *iommu) int rc; RISCV_IOMMU_QUEUE_INIT(&iommu->cmdq, CQ); + raw_spin_lock_init(&iommu->cmdq.lock); RISCV_IOMMU_QUEUE_INIT(&iommu->fltq, FQ); rc = riscv_iommu_init_check(iommu); diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h index 46df79dd5495..5676001548cc 100644 --- a/drivers/iommu/riscv/iommu.h +++ b/drivers/iommu/riscv/iommu.h @@ -12,6 +12,7 @@ #define _RISCV_IOMMU_H_ #include +#include #include #include @@ -23,6 +24,7 @@ struct riscv_iommu_queue { atomic_t prod; /* unbounded producer allocation index */ atomic_t head; /* unbounded shadow ring buffer consumer index */ atomic_t tail; /* unbounded shadow ring buffer producer index */ + raw_spinlock_t lock; /* serialize queue publishing */ unsigned int mask; /* index mask, queue length - 1 */ unsigned int irq; /* allocated interrupt number */ struct riscv_iommu_device *iommu; /* iommu device handling the queue when active */ From ca58afa40946acd252a50fa4d4a86f15847a3d7d Mon Sep 17 00:00:00 2001 From: Fangyu Yu Date: Tue, 1 Sep 2026 21:39:19 +0800 Subject: [PATCH 1058/2366] iommu/riscv: Serialize command queue publishing Serialize command queue publishing so software producer state advances only after a command is written and the hardware tail is updated. Wait for hardware consumption outside the queue lock when the command queue is full so other CPUs are not blocked behind a long poll. Fixes: 856c0cfe5c5f ("iommu/riscv: Command and fault queue support") Signed-off-by: Fangyu Yu Signed-off-by: Joerg Roedel --- drivers/iommu/riscv/iommu.c | 102 +++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c index 2c0dcc90cf85..e335beb70e42 100644 --- a/drivers/iommu/riscv/iommu.c +++ b/drivers/iommu/riscv/iommu.c @@ -382,77 +382,93 @@ static int riscv_iommu_queue_wait(struct riscv_iommu_queue *queue, (int)(cons - index) > 0, 0, timeout_us); } -/* Enqueue an entry and wait to be processed if timeout_us > 0 - * - * Error handling for IOMMU hardware not responding in reasonable time - * will be added as separate patch series along with other RAS features. - * For now, only report hardware failure and continue. - */ +static int riscv_iommu_queue_wait_for_space(struct riscv_iommu_queue *queue, + unsigned int last) +{ + unsigned int head; + unsigned int tail; + unsigned int hw_head; + unsigned long flags; + int ret; + + ret = riscv_iommu_readl_timeout(queue->iommu, Q_HEAD(queue), hw_head, + !(hw_head & ~queue->mask) && hw_head != last, + 0, RISCV_IOMMU_QUEUE_TIMEOUT); + if (ret) + return ret; + + raw_spin_lock_irqsave(&queue->lock, flags); + head = atomic_read(&queue->head); + tail = atomic_read(&queue->tail); + if ((tail - head) >= queue->mask) { + last = Q_ITEM(queue, head); + /* + * Re-read hw_head under the lock so that it is consistent with + * the freshly computed 'last'. Using the pre-lock snapshot + * could produce a stale value that wraps around relative to the + * new 'last', advancing the shadow head past entries that have + * not yet been consumed by the hardware. + */ + hw_head = riscv_iommu_readl(queue->iommu, Q_HEAD(queue)); + if (!(hw_head & ~queue->mask) && hw_head != last) + atomic_add((hw_head - last) & queue->mask, &queue->head); + } + raw_spin_unlock_irqrestore(&queue->lock, flags); + + return 0; +} + +/* Enqueue an entry and publish it to the hardware queue. */ static unsigned int riscv_iommu_queue_send(struct riscv_iommu_queue *queue, void *entry, size_t entry_size) { unsigned int prod; unsigned int head; - unsigned int tail; unsigned long flags; + int ret; - /* Do not preempt submission flow. */ - local_irq_save(flags); + /* 1. Wait for space availability and reserve the next slot. */ + for (;;) { + raw_spin_lock_irqsave(&queue->lock, flags); - /* 1. Allocate some space in the queue */ - prod = atomic_inc_return(&queue->prod) - 1; - head = atomic_read(&queue->head); + prod = atomic_read(&queue->tail); + head = atomic_read(&queue->head); - /* 2. Wait for space availability. */ - if ((prod - head) > queue->mask) { - if (readx_poll_timeout(atomic_read, &queue->head, - head, (prod - head) < queue->mask, - 0, RISCV_IOMMU_QUEUE_TIMEOUT)) + if ((prod - head) < queue->mask) + break; + + head = Q_ITEM(queue, head); + raw_spin_unlock_irqrestore(&queue->lock, flags); + + ret = riscv_iommu_queue_wait_for_space(queue, head); + if (ret) goto err_busy; - } else if ((prod - head) == queue->mask) { - const unsigned int last = Q_ITEM(queue, head); - - if (riscv_iommu_readl_timeout(queue->iommu, Q_HEAD(queue), head, - !(head & ~queue->mask) && head != last, - 0, RISCV_IOMMU_QUEUE_TIMEOUT)) - goto err_busy; - atomic_add((head - last) & queue->mask, &queue->head); } - /* 3. Store entry in the ring buffer */ + /* 2. Store entry in the ring buffer. */ memcpy(queue->base + Q_ITEM(queue, prod) * entry_size, entry, entry_size); - /* 4. Wait for all previous entries to be ready */ - if (readx_poll_timeout(atomic_read, &queue->tail, tail, prod == tail, - 0, RISCV_IOMMU_QUEUE_TIMEOUT)) - goto err_busy; - - /* - * 5. Make sure the ring buffer update (whether in normal or I/O memory) is - * completed and visible before signaling the tail doorbell to fetch - * the next command. 'fence ow, ow' - */ + /* 3. Make sure the entry is visible before updating the queue tail. */ dma_wmb(); riscv_iommu_writel(queue->iommu, Q_TAIL(queue), Q_ITEM(queue, prod + 1)); /* - * 6. Make sure the doorbell write to the device has finished before updating - * the shadow tail index in normal memory. 'fence o, w' + * 4. Make sure the doorbell write to the device has finished before + * updating the shadow tail index in normal memory. 'fence o, w' */ #ifdef CONFIG_MMIOWB mmiowb(); #endif - atomic_inc(&queue->tail); + atomic_set(&queue->tail, prod + 1); + atomic_set(&queue->prod, prod + 1); - /* 7. Complete submission and restore local interrupts */ - local_irq_restore(flags); + raw_spin_unlock_irqrestore(&queue->lock, flags); return prod; err_busy: - local_irq_restore(flags); + /* Report the failure and continue; full RAS recovery is not implemented. */ dev_err_once(queue->iommu->dev, "Hardware error: command enqueue failed\n"); - return prod; } From 4c50bec3d54288230aafb7fe3d2930d42beb14fd Mon Sep 17 00:00:00 2001 From: Fangyu Yu Date: Tue, 1 Sep 2026 21:39:20 +0800 Subject: [PATCH 1059/2366] iommu/riscv: Avoid waiting on failed command enqueue Do not wait for IOFENCE.C completion when the command failed to enter the queue. The command was not published to hardware, so waiting for its producer index can only report a misleading execution timeout. Fixes: 856c0cfe5c5f ("iommu/riscv: Command and fault queue support") Signed-off-by: Fangyu Yu Signed-off-by: Joerg Roedel --- drivers/iommu/riscv/iommu.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c index e335beb70e42..fe8e6d0f8a23 100644 --- a/drivers/iommu/riscv/iommu.c +++ b/drivers/iommu/riscv/iommu.c @@ -419,8 +419,9 @@ static int riscv_iommu_queue_wait_for_space(struct riscv_iommu_queue *queue, } /* Enqueue an entry and publish it to the hardware queue. */ -static unsigned int riscv_iommu_queue_send(struct riscv_iommu_queue *queue, - void *entry, size_t entry_size) +static int riscv_iommu_queue_send(struct riscv_iommu_queue *queue, + void *entry, size_t entry_size, + unsigned int *out_prod) { unsigned int prod; unsigned int head; @@ -462,14 +463,16 @@ static unsigned int riscv_iommu_queue_send(struct riscv_iommu_queue *queue, atomic_set(&queue->tail, prod + 1); atomic_set(&queue->prod, prod + 1); - raw_spin_unlock_irqrestore(&queue->lock, flags); + if (out_prod) + *out_prod = prod; - return prod; + raw_spin_unlock_irqrestore(&queue->lock, flags); + return 0; err_busy: /* Report the failure and continue; full RAS recovery is not implemented. */ dev_err_once(queue->iommu->dev, "Hardware error: command enqueue failed\n"); - return prod; + return ret; } /* @@ -508,7 +511,7 @@ static irqreturn_t riscv_iommu_cmdq_process(int irq, void *data) static void riscv_iommu_cmd_send(struct riscv_iommu_device *iommu, struct riscv_iommu_command *cmd) { - riscv_iommu_queue_send(&iommu->cmdq, cmd, sizeof(*cmd)); + riscv_iommu_queue_send(&iommu->cmdq, cmd, sizeof(*cmd), NULL); } /* Send IOFENCE.C command and wait for all scheduled commands to complete. */ @@ -517,9 +520,12 @@ static void riscv_iommu_cmd_sync(struct riscv_iommu_device *iommu, { struct riscv_iommu_command cmd; unsigned int prod; + int ret; riscv_iommu_cmd_iofence(&cmd); - prod = riscv_iommu_queue_send(&iommu->cmdq, &cmd, sizeof(cmd)); + ret = riscv_iommu_queue_send(&iommu->cmdq, &cmd, sizeof(cmd), &prod); + if (ret) + return; if (!timeout_us) return; From 20db6573301e66cd65ebf6c130b6563c69374d9d Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Tue, 18 Aug 2026 21:13:17 +0200 Subject: [PATCH 1060/2366] iommu/s390: Fix NULL dereference in iova_to_phys() with ZPCI_TABLE_TYPE_RFX When using a 5-level translation table via ZPCI_TABLE_TYPE_RFX get_rso_from_iova() returns NULL when the region-first entry is invalid. Yet in get_rto_from_iova() the region-second origin rso is not checked to be non-NULL before accessing rso[rsx] leading to a NULL pointer dereference instead of a NULL return when iova_to_phys() is called on a unmapped IOVA. Fix this by adding the missing NULL check. Cc: stable@vger.kernel.org Fixes: 81244074b518 ("iommu/s390: allow larger region tables") Signed-off-by: Niklas Schnelle Reviewed-by: Benjamin Block Reviewed-by: Matthew Rosato Reviewed-by: Farhan Ali Signed-off-by: Joerg Roedel --- drivers/iommu/s390-iommu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iommu/s390-iommu.c b/drivers/iommu/s390-iommu.c index f148f559ac56..58ca7727b7f2 100644 --- a/drivers/iommu/s390-iommu.c +++ b/drivers/iommu/s390-iommu.c @@ -974,6 +974,8 @@ static unsigned long *get_rto_from_iova(struct s390_domain *domain, case ZPCI_TABLE_TYPE_RFX: case ZPCI_TABLE_TYPE_RSX: rso = get_rso_from_iova(domain, iova); + if (!rso) + return NULL; rsx = calc_rsx(iova); rse = READ_ONCE(rso[rsx]); if (!reg_entry_isvalid(rse)) From 00a7dd64888d6dd72110b40e2824a088cf7b7386 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 19 Aug 2026 05:23:49 +0200 Subject: [PATCH 1061/2366] iommu/amd: Do not reallocate GA log buffers on resume Commit c5e1a1eb9279 ("iommu/amd: Simplify and Consolidate Virtual APIC (AVIC) Enablement") moved the GA log allocation from iommu_init_pci() to enable_iommus_vapic(), which is called on every resume. iommu_init_ga_log() assigns iommu->ga_log and iommu->ga_log_tail unconditionally. Each resume therefore replaces the boot-time pointers and leaks both old allocations. The function also uses GFP_KERNEL from a syscore resume callback, where interrupts are disabled and the non-boot CPUs are offline. Return early if both buffers are already allocated. Clear the pointers in free_ga_log() so a partial allocation failure cannot leave ga_log dangling. Fixes: c5e1a1eb9279 ("iommu/amd: Simplify and Consolidate Virtual APIC (AVIC) Enablement") Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Mehltretter Reviewed-by: Vasant Hegde Reviewed-by: Ankit Soni Signed-off-by: Joerg Roedel --- drivers/iommu/amd/init.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c index 40726dfef273..c6b106d5921e 100644 --- a/drivers/iommu/amd/init.c +++ b/drivers/iommu/amd/init.c @@ -909,7 +909,9 @@ static void free_ga_log(struct amd_iommu *iommu) { #ifdef CONFIG_IRQ_REMAP iommu_free_pages(iommu->ga_log); + iommu->ga_log = NULL; iommu_free_pages(iommu->ga_log_tail); + iommu->ga_log_tail = NULL; #endif } @@ -956,6 +958,9 @@ static int iommu_init_ga_log(struct amd_iommu *iommu) if (WARN_ON_ONCE(!AMD_IOMMU_GUEST_IR_VAPIC(amd_iommu_guest_ir))) return -EINVAL; + if (iommu->ga_log && iommu->ga_log_tail) + return 0; + iommu->ga_log = iommu_alloc_pages_node_sz(nid, GFP_KERNEL, GA_LOG_SIZE); if (!iommu->ga_log) goto err_out; From eb29b7bbc8ba28bbb0b9fdd655e931e1d1fa625c Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Mon, 24 Aug 2026 06:29:07 +0000 Subject: [PATCH 1062/2366] iommu/amd: Fix premature break in init_iommu_one() again Commit 283d245468a2 ("iommu/amd: Fix premature break in init_iommu_one()") unintentionally broke older platforms - such as the ASRockRack B550D4-4L - where the BIOS advertises incorrect IOMMU features. Move the HATDis check ahead of the GASup check, and re-introduce the break inside the GASup check to restore correct behavior on affected platforms. This is a short-term fix to resolve the regression. Longer term, we should rework how EFRs are tracked and prioritize the MMIO-advertised EFR over the one reported via IVRS. That requires more extensive changes and will be addressed separately. Fixes: 283d245468a2 ("iommu/amd: Fix premature break in init_iommu_one()") Reported-by: Andreas Juch Closes: https://lore.kernel.org/linux-iommu/07b2d390-f7a0-47e2-bc2c-eb0853acf52e@juch.cc/ Tested-by: Andreas Juch Signed-off-by: Vasant Hegde Signed-off-by: Joerg Roedel --- drivers/iommu/amd/init.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c index c6b106d5921e..edcc187b8f14 100644 --- a/drivers/iommu/amd/init.c +++ b/drivers/iommu/amd/init.c @@ -1922,19 +1922,20 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h, else iommu->mmio_phys_end = MMIO_CNTR_CONF_OFFSET; - /* XT and GAM require GA mode. */ - if ((h->efr_reg & (0x1 << IOMMU_EFR_GASUP_SHIFT)) == 0) { - amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_LEGACY; - } else { - if (h->efr_reg & BIT(IOMMU_EFR_XTSUP_SHIFT)) - amd_iommu_xt_mode = IRQ_REMAP_X2APIC_MODE; - } - if (h->efr_attr & BIT(IOMMU_IVHD_ATTR_HATDIS_SHIFT)) { pr_warn_once("Host Address Translation is not supported.\n"); amd_iommu_hatdis = true; } + /* XT and GAM require GA mode. */ + if ((h->efr_reg & (0x1 << IOMMU_EFR_GASUP_SHIFT)) == 0) { + amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_LEGACY; + break; + } else { + if (h->efr_reg & BIT(IOMMU_EFR_XTSUP_SHIFT)) + amd_iommu_xt_mode = IRQ_REMAP_X2APIC_MODE; + } + early_iommu_features_init(iommu, h); break; From fa5c0827f0b7bac6d0a188f10118151769ae68fd Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Tue, 25 Aug 2026 15:35:54 +0530 Subject: [PATCH 1063/2366] iommu/amd: Fix ineffective error check in nested domain allocation amd_iommu_pdom_id_alloc() returns an int: a domain ID on success, or the negative errno from ida_alloc_range() when the ID space is exhausted or memory is short. amd_iommu_alloc_domain_nested() stores that return value in gdom_info->hdom_id, which is a u32, and only then tests it: gdom_info->hdom_id = amd_iommu_pdom_id_alloc(); if (gdom_info->hdom_id <= 0) { The assignment discards the sign, so -ENOSPC becomes 0xffffffe4 and the test never fires. The nested domain is then set up with a host domain ID that was never allocated, instead of the allocation failing with -ENOSPC. Keep the value in an int, test it there, and store it only once it is known to be valid, which is what the other amd_iommu_pdom_id_alloc() callers already do. Fixes: 757d2b1fdf5b ("iommu/amd: Introduce gDomID-to-hDomID Mapping and handle parent domain invalidation") Signed-off-by: Hemanth Selam Reviewed-by: Vasant Hegde Signed-off-by: Joerg Roedel --- drivers/iommu/amd/nested.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/amd/nested.c b/drivers/iommu/amd/nested.c index 63b53b29e029..f1c7987fc585 100644 --- a/drivers/iommu/amd/nested.c +++ b/drivers/iommu/amd/nested.c @@ -96,7 +96,7 @@ struct iommu_domain * amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags, const struct iommu_user_data *user_data) { - int ret; + int ret, hdom_id; unsigned long irqflags; struct nested_domain *ndom; struct guest_domain_mapping_info *gdom_info; @@ -161,8 +161,8 @@ amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags, } /* The gDomID does not exist. We allocate new hdom_id */ - gdom_info->hdom_id = amd_iommu_pdom_id_alloc(); - if (gdom_info->hdom_id <= 0) { + hdom_id = amd_iommu_pdom_id_alloc(); + if (hdom_id <= 0) { __xa_cmpxchg(&aviommu->gdomid_array, ndom->gdom_id, gdom_info, NULL, GFP_ATOMIC); xa_unlock_irqrestore(&aviommu->gdomid_array, irqflags); @@ -170,6 +170,7 @@ amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags, goto out_err_gdom_info; } + gdom_info->hdom_id = hdom_id; ndom->gdom_info = gdom_info; refcount_set(&gdom_info->users, 1); From adbd8a08208dc64bb1381f51b4f11ffdce1343fa Mon Sep 17 00:00:00 2001 From: Daasaradhi Mannava Date: Sat, 5 Sep 2026 15:49:00 +0000 Subject: [PATCH 1064/2366] MAINTAINERS: Drop the nonexistent vsi-iommu.h file entry Commit 917ace84b770 ("iommu: Add verisilicon IOMMU driver") added the VERISILICON IOMMU DRIVER section, including a file entry for include/linux/vsi-iommu.h. That header is not present in the tree and no file includes it; the driver in drivers/iommu/vsi-iommu.c is self-contained. scripts/get_maintainer.pl --self-test=patterns reports the pattern as matching no file. Drop the stale entry so the section only lists files that exist. Assisted-by: LLM Signed-off-by: Daasaradhi Mannava Reviewed-by: Benjamin Gaignard Signed-off-by: Joerg Roedel --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb07770..0c4ef770807f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -28607,7 +28607,6 @@ L: iommu@lists.linux.dev S: Maintained F: Documentation/devicetree/bindings/iommu/verisilicon,iommu.yaml F: drivers/iommu/vsi-iommu.c -F: include/linux/vsi-iommu.h VF610 NAND DRIVER M: Stefan Agner From cdb669a3b8f844aca71fc3224990157d61562165 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Mon, 24 Aug 2026 11:14:19 +0800 Subject: [PATCH 1065/2366] HID: quirks: add ALWAYS_POLL quirk for SDINNOVATION gaming keyboard The SDINNOVATION gaming keyboard (USB ID 36ae:feab) stops reporting input events after its RGB lighting mode is switched about twice. Disabling USB autosuspend and unbinding the other HID interfaces make no difference; the issue does not occur on Windows. HID_QUIRK_ALWAYS_POLL alone resolves it, verified on 7.1.8 via usbhid.quirks=0x36ae:0xfeab:0x400. Reported-by: Marco Carvalho Link: https://bugzilla.redhat.com/show_bug.cgi?id=2514627 Cc: stable@vger.kernel.org Signed-off-by: Junjie Cao Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-ids.h | 3 +++ drivers/hid/hid-quirks.c | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index b3aca5aa9176..8b4f4b02aec0 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1296,6 +1296,9 @@ #define USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD 0xa006 #define USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD 0xa064 +#define USB_VENDOR_ID_SDINNOVATION 0x36ae +#define USB_DEVICE_ID_SDINNOVATION_GAMING_KBD 0xfeab + #define USB_VENDOR_ID_SEMICO 0x1a2c #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD 0x0023 #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD2 0x0027 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 8a0b51d47040..c8c6b29fc04d 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -186,6 +186,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_2), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_PRO), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X65), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SDINNOVATION, USB_DEVICE_ID_SDINNOVATION_GAMING_KBD), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD2), HID_QUIRK_NO_INIT_REPORTS }, { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS }, { HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET }, From e317326d1755ea054b98e7a4833b929157461c35 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 19 Aug 2026 21:05:17 +0200 Subject: [PATCH 1066/2366] hwmon: (ltc4282) Make sure clk_init_data is fully initialized The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. ltc428_clk_provider_setup() does not fill in any parent clocks, and assumes that init.num_parents is NULL. However, the latter in uninitialized, and thus may cause a crash. Make sure all members are fully initialized, to fix such bugs, and to avoid future breakage when converting drivers to a different method for specifying the parents. Fixes: cbc29538dbf7d740 ("hwmon: Add driver for LTC4282") Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/8ec3c5cbd2df675a938f090470f5da5f22008517.1787165329.git.geert+renesas@glider.be Reviewed-by: Brian Masney Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc4282.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c index b1675dc5b3c7..54ba4b8542e9 100644 --- a/drivers/hwmon/ltc4282.c +++ b/drivers/hwmon/ltc4282.c @@ -1106,7 +1106,7 @@ static const struct clk_ops ltc4282_ops = { static int ltc428_clk_provider_setup(struct ltc4282_state *st, struct device *dev) { - struct clk_init_data init; + struct clk_init_data init = {}; int ret; if (!IS_ENABLED(CONFIG_COMMON_CLK)) From 9607c245ca6674955e5e43e3606db410ae9e0b90 Mon Sep 17 00:00:00 2001 From: Nikhil Gurudasani Date: Wed, 19 Aug 2026 23:37:01 +0530 Subject: [PATCH 1067/2366] hwmon: (mcp9982) Propagate one-shot polling errors When a device is in standby, the driver starts a one-shot conversion and polls the BUSY flag before reading temperature, alarm, or fault data. The poll result is currently ignored. Therefore, a timeout or a status-register read failure can be hidden by a later successful read, causing stale data to be returned as valid. Return the polling error before reading the requested attribute. Fixes: e2fe950f34e5 ("hwmon: add support for MCP998X") Cc: stable@vger.kernel.org Signed-off-by: Nikhil Gurudasani Link: https://patch.msgid.link/20260819180701.34797-1-nikhilgurudasani314@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/mcp9982.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/mcp9982.c b/drivers/hwmon/mcp9982.c index 9e19e2697e25..3918dc36c946 100644 --- a/drivers/hwmon/mcp9982.c +++ b/drivers/hwmon/mcp9982.c @@ -395,6 +395,8 @@ static int mcp9982_read(struct device *dev, enum hwmon_sensor_types type, u32 at reg_status, !(reg_status & MCP9982_STATUS_BUSY), MCP9982_WAKE_UP_TIME_US, MCP9982_WAKE_UP_TIME_US * 10); + if (ret) + return ret; break; } break; From a2471ed17b0e6ff7bfb6b2ea8e6e5b04c309d293 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 19 Aug 2026 03:33:17 +0000 Subject: [PATCH 1068/2366] hwmon: (gpio-fan) Fix use-after-free in alarm work fan_alarm_irq_handler() queues fan_data->alarm_work, but nothing cancels it. fan_alarm_notify() dereferences fan_data and its hwmon device. On unbind, devres frees the interrupt, which only waits for the handler itself, and then releases the hwmon device and fan_data, so a pending fan_alarm_notify() can run after those frees. Replace INIT_WORK() with devm_work_autocancel(), registered before devm_request_irq(). The devres cleanup then frees the interrupt first, so no new work can be queued, and cancels the work while fan_data and the hwmon device are still alive. This issue was found by an in-house static analysis tool. Fixes: d6fe1360f42e ("hwmon: add generic GPIO fan driver") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260819033317.446191-1-fanwu01@zju.edu.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/gpio-fan.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index 084828e1e281..7f36e5f6f223 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -84,6 +85,7 @@ static DEVICE_ATTR_RO(fan1_alarm); static int fan_alarm_init(struct gpio_fan_data *fan_data) { int alarm_irq; + int err; struct device *dev = fan_data->dev; /* @@ -94,7 +96,11 @@ static int fan_alarm_init(struct gpio_fan_data *fan_data) if (alarm_irq <= 0) return 0; - INIT_WORK(&fan_data->alarm_work, fan_alarm_notify); + err = devm_work_autocancel(dev, &fan_data->alarm_work, + fan_alarm_notify); + if (err) + return err; + irq_set_irq_type(alarm_irq, IRQ_TYPE_EDGE_BOTH); return devm_request_irq(dev, alarm_irq, fan_alarm_irq_handler, IRQF_SHARED, "GPIO fan alarm", fan_data); From b4fffa75c1d6f87e6dc6191dec900f2b5bd23a1c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 20 Aug 2026 21:40:48 -0700 Subject: [PATCH 1069/2366] Documentation/hwmon: Document hwmon_notify_event() The hwmon core provides hwmon_notify_event() for drivers to report events such as alarm or fault conditions to userspace via sysfs notifications and uevents, as well as to the thermal subsystem for temperature sensors. However, this function is not documented in the hwmon kernel API guide. Add the function prototype and description of hwmon_notify_event() to Documentation/hwmon/hwmon-kernel-api.rst. Cc: Kalesh AP Reviewed-by: Kalesh AP Fixes: 1597b374af222 ("hwmon: Add notification support") Signed-off-by: Guenter Roeck --- Documentation/hwmon/hwmon-kernel-api.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Documentation/hwmon/hwmon-kernel-api.rst b/Documentation/hwmon/hwmon-kernel-api.rst index 9fcde32a140d..c3eb433a78f6 100644 --- a/Documentation/hwmon/hwmon-kernel-api.rst +++ b/Documentation/hwmon/hwmon-kernel-api.rst @@ -42,6 +42,9 @@ register/unregister functions:: char *devm_hwmon_sanitize_name(struct device *dev, const char *name); + int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel); + void hwmon_lock(struct device *dev); void hwmon_unlock(struct device *dev); @@ -90,6 +93,18 @@ implemented in the driver, or debugfs functions, hwmon_lock() and hwmon_unlock() can be used to ensure that calls to those functions are serialized. Those functions also support guard() and scoped_guard() variants. +Drivers can call hwmon_notify_event() to notify userspace and the thermal +subsystem when a hardware monitoring event (such as an alarm or a fault +condition) occurs or clears. The parameters are the hwmon device, the sensor +type, the attribute identifier associated with the event (such as +hwmon_temp_max_alarm or hwmon_fan_fault), and the sensor channel number. +hwmon_notify_event() generates a sysfs event (calling sysfs_notify()) and a +udev event with the attribute name passed in the NAME environment property +(e.g., "NAME=temp1_max_alarm"). If the event is for a temperature sensor and +the sensor is attached to a thermal zone, it also notifies the thermal +subsystem to update the thermal zone. hwmon_notify_event() returns 0 on +success or a negative error code on failure. + Using devm_hwmon_device_register_with_info() -------------------------------------------- From 354ccc99b2dc8ba0cf6d4de34e520bcf6ecca5c2 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 20 Aug 2026 10:51:50 -0700 Subject: [PATCH 1070/2366] hwmon: Fix potential UAF in pec_store Sashiko reports: In pec_store(), a guard(mutex)(&hwdev->lock) is taken. If the chip write operation returns an error other than -EOPNOTSUPP, the code jumps to the put label, which calls put_device(hdev). If this drops the final reference, the device is freed. When the function then returns, the guard cleanup function runs and attempts to unlock the freed mutex. Use scoped_guard() instead of guard() to avoid the problem. Fixes: 3ad2a7b9b15d5 ("hwmon: Serialize accesses in hwmon core") Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 41755910a25a..3e65fc6d25eb 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -371,18 +371,17 @@ static ssize_t pec_store(struct device *dev, const struct device_attribute *deva * handling is not required. */ hwdev = to_hwmon_device(hdev); - guard(mutex)(&hwdev->lock); - if (hwdev->chip->ops->write) { - err = hwdev->chip->ops->write(hdev, hwmon_chip, hwmon_chip_pec, 0, val); - if (err && err != -EOPNOTSUPP) - goto put; + scoped_guard(mutex, &hwdev->lock) { + if (hwdev->chip->ops->write) { + err = hwdev->chip->ops->write(hdev, hwmon_chip, hwmon_chip_pec, 0, val); + if (err && err != -EOPNOTSUPP) + goto put; + } + if (!val) + client->flags &= ~I2C_CLIENT_PEC; + else + client->flags |= I2C_CLIENT_PEC; } - - if (!val) - client->flags &= ~I2C_CLIENT_PEC; - else - client->flags |= I2C_CLIENT_PEC; - err = count; put: put_device(hdev); From 8afc94bfb0ffdfc4a168081785820aa4818d1d23 Mon Sep 17 00:00:00 2001 From: Jared Kangas Date: Thu, 20 Aug 2026 06:09:21 -0700 Subject: [PATCH 1071/2366] hwmon: (ina2xx) Acquire hwmon_lock in shunt_resistor_show() shunt_resistor_store() currently acquires hwmon_lock to set data->rshunt, but the corresponding access in shunt_resistor_show() is unprotected. Acquire the lock in shunt_resistor_show() as well to ensure proper synchronization. Fixes: 3ad867001c91 ("hwmon: (ina2xx) fix sysfs shunt resistor read access") Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260729162836.89BDF1F00A3A@smtp.kernel.org/ Signed-off-by: Jared Kangas Link: https://patch.msgid.link/20260820-upstream-ina2xx-in0-curr1-alarms-v2-1-fdce35abc41e@redhat.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 5c6dc2c370d8..f1f988d099a5 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -883,8 +883,12 @@ static ssize_t shunt_resistor_show(struct device *dev, struct device_attribute *da, char *buf) { struct ina2xx_data *data = dev_get_drvdata(dev); + long rshunt; - return sysfs_emit(buf, "%li\n", data->rshunt); + scoped_guard(hwmon_lock, dev) { + rshunt = data->rshunt; + } + return sysfs_emit(buf, "%li\n", rshunt); } static ssize_t shunt_resistor_store(struct device *dev, From 2bf98a6af10388215398a30d723ff1f6ff5ce4f7 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 20 Aug 2026 22:19:13 -0700 Subject: [PATCH 1072/2366] hwmon: Ensure that 'dev' passed to hwmon_notify_event() is a hwmon device The device parameter of hwmon_notify_event() must be a hardware monitoring device. Since this is easy to get wrong, and since passing a non-hwmon device may result in a crash, generate a warning traceback and abort if a wrong device class is passed as parameter. Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 3e65fc6d25eb..10d2df3efdfa 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -318,6 +318,11 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) return 1; } +static bool is_hwmon_device(struct device *dev) +{ + return dev->class == &hwmon_class; +} + #if IS_REACHABLE(CONFIG_I2C) /* @@ -338,7 +343,7 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) static int hwmon_match_device(struct device *dev, const void *data) { - return dev->class == &hwmon_class; + return is_hwmon_device(dev); } static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy, @@ -781,6 +786,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, const char *template; int base; + if (WARN(!is_hwmon_device(dev), "%s is not a hardware monitoring device\n", + dev_name(dev))) + return -EINVAL; if (type >= ARRAY_SIZE(__templates)) return -EINVAL; if (attr >= __templates_size[type]) From 3d44ab826e0244a8b9eaf0f1f604f4cb8b890325 Mon Sep 17 00:00:00 2001 From: Jared Kangas Date: Thu, 20 Aug 2026 06:09:22 -0700 Subject: [PATCH 1073/2366] hwmon: (ina2xx) Parameterize ina2xx_data in ina226_alert_read() Mirror ina226_alert_limit_read/write and use struct ina2xx_data instead of struct regmap in ina226_alert_read's parameters. Signed-off-by: Jared Kangas Link: https://patch.msgid.link/20260820-upstream-ina2xx-in0-curr1-alarms-v2-2-fdce35abc41e@redhat.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index f1f988d099a5..0fd17a6d4f90 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -498,12 +498,12 @@ static int ina2xx_chip_read(struct device *dev, u32 attr, long *val) return 0; } -static int ina226_alert_read(struct regmap *regmap, u32 mask, long *val) +static int ina226_alert_read(struct ina2xx_data *data, u32 mask, long *val) { unsigned int regval; int ret; - ret = regmap_read_bypassed(regmap, INA226_MASK_ENABLE, ®val); + ret = regmap_read_bypassed(data->regmap, INA226_MASK_ENABLE, ®val); if (ret) return ret; @@ -538,9 +538,9 @@ static int ina2xx_in_read(struct device *dev, u32 attr, int channel, long *val) return ina226_alert_limit_read(data, over_voltage_mask, voltage_reg, val); case hwmon_in_lcrit_alarm: - return ina226_alert_read(regmap, under_voltage_mask, val); + return ina226_alert_read(data, under_voltage_mask, val); case hwmon_in_crit_alarm: - return ina226_alert_read(regmap, over_voltage_mask, val); + return ina226_alert_read(data, over_voltage_mask, val); default: return -EOPNOTSUPP; } @@ -597,7 +597,7 @@ static int ina2xx_power_read(struct device *dev, u32 attr, long *val) return ina226_alert_limit_read(data, INA226_POWER_OVER_LIMIT_MASK, INA2XX_POWER, val); case hwmon_power_crit_alarm: - return ina226_alert_read(data->regmap, INA226_POWER_OVER_LIMIT_MASK, val); + return ina226_alert_read(data, INA226_POWER_OVER_LIMIT_MASK, val); default: return -EOPNOTSUPP; } @@ -639,9 +639,9 @@ static int ina2xx_curr_read(struct device *dev, u32 attr, long *val) return ina226_alert_limit_read(data, INA226_SHUNT_OVER_VOLTAGE_MASK, INA2XX_CURRENT, val); case hwmon_curr_lcrit_alarm: - return ina226_alert_read(regmap, INA226_SHUNT_UNDER_VOLTAGE_MASK, val); + return ina226_alert_read(data, INA226_SHUNT_UNDER_VOLTAGE_MASK, val); case hwmon_curr_crit_alarm: - return ina226_alert_read(regmap, INA226_SHUNT_OVER_VOLTAGE_MASK, val); + return ina226_alert_read(data, INA226_SHUNT_OVER_VOLTAGE_MASK, val); default: return -EOPNOTSUPP; } From e92b9208415a90e9cc1d923c5d8c058dde77be68 Mon Sep 17 00:00:00 2001 From: Jared Kangas Date: Thu, 20 Aug 2026 06:09:23 -0700 Subject: [PATCH 1074/2366] hwmon: (ina2xx) Replace masks with enum in alert functions Instead of passing an explicit mask to alert/limit functions like ina226_alert_read(), introduce an enum ina2xx_alert_type that can be converted to a mask internally. This semantically separates current from shunt voltage in helpers that use function masks, which previously saw the same mask for the two functions. Signed-off-by: Jared Kangas Link: https://patch.msgid.link/20260820-upstream-ina2xx-in0-curr1-alarms-v2-3-fdce35abc41e@redhat.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 90 +++++++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 0fd17a6d4f90..75e97e30bdcd 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -129,6 +129,17 @@ enum ina2xx_ids { sy24655 }; +enum ina2xx_alert_type { + INA2XX_ALERT_NONE, + INA2XX_ALERT_CURRENT_LOW, + INA2XX_ALERT_CURRENT_HIGH, + INA2XX_ALERT_POWER_HIGH, + INA2XX_ALERT_BUS_VOLTAGE_LOW, + INA2XX_ALERT_BUS_VOLTAGE_HIGH, + INA2XX_ALERT_SHUNT_VOLTAGE_LOW, + INA2XX_ALERT_SHUNT_VOLTAGE_HIGH, +}; + struct ina2xx_config { u16 config_default; bool has_alerts; /* chip supports alerts and limits */ @@ -428,16 +439,43 @@ static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val) } } -static int ina226_alert_limit_read(struct ina2xx_data *data, u32 mask, int reg, long *val) +static u32 ina2xx_alert_type_to_mask(enum ina2xx_alert_type alert) +{ + switch (alert) { + case INA2XX_ALERT_CURRENT_LOW: + case INA2XX_ALERT_SHUNT_VOLTAGE_LOW: + return INA226_SHUNT_UNDER_VOLTAGE_MASK; + case INA2XX_ALERT_CURRENT_HIGH: + case INA2XX_ALERT_SHUNT_VOLTAGE_HIGH: + return INA226_SHUNT_OVER_VOLTAGE_MASK; + case INA2XX_ALERT_BUS_VOLTAGE_LOW: + return INA226_BUS_UNDER_VOLTAGE_MASK; + case INA2XX_ALERT_BUS_VOLTAGE_HIGH: + return INA226_BUS_OVER_VOLTAGE_MASK; + case INA2XX_ALERT_POWER_HIGH: + return INA226_POWER_OVER_LIMIT_MASK; + case INA2XX_ALERT_NONE: + return 0; + default: + /* programmer error */ + WARN_ON_ONCE(1); + return 0; + } +} + +static int ina226_alert_limit_read(struct ina2xx_data *data, enum ina2xx_alert_type alert, + int reg, long *val) { struct regmap *regmap = data->regmap; int regval; + u32 mask; int ret; ret = regmap_read(regmap, INA226_MASK_ENABLE, ®val); if (ret) return ret; + mask = ina2xx_alert_type_to_mask(alert); if (regval & mask) { ret = regmap_read(regmap, INA226_ALERT_LIMIT, ®val); if (ret) @@ -449,9 +487,11 @@ static int ina226_alert_limit_read(struct ina2xx_data *data, u32 mask, int reg, return 0; } -static int ina226_alert_limit_write(struct ina2xx_data *data, u32 mask, int reg, long val) +static int ina226_alert_limit_write(struct ina2xx_data *data, enum ina2xx_alert_type alert, + int reg, long val) { struct regmap *regmap = data->regmap; + u32 mask; int ret; if (val < 0) @@ -472,9 +512,11 @@ static int ina226_alert_limit_write(struct ina2xx_data *data, u32 mask, int reg, if (ret < 0) return ret; - if (val) + if (val) { + mask = ina2xx_alert_type_to_mask(alert); return regmap_update_bits(regmap, INA226_MASK_ENABLE, INA226_ALERT_CONFIG_MASK, mask); + } return 0; } @@ -498,15 +540,17 @@ static int ina2xx_chip_read(struct device *dev, u32 attr, long *val) return 0; } -static int ina226_alert_read(struct ina2xx_data *data, u32 mask, long *val) +static int ina226_alert_read(struct ina2xx_data *data, enum ina2xx_alert_type alert, long *val) { unsigned int regval; + u32 mask; int ret; ret = regmap_read_bypassed(data->regmap, INA226_MASK_ENABLE, ®val); if (ret) return ret; + mask = ina2xx_alert_type_to_mask(alert); *val = (regval & mask) && (regval & INA226_ALERT_FUNCTION_FLAG); return 0; @@ -515,10 +559,10 @@ static int ina226_alert_read(struct ina2xx_data *data, u32 mask, long *val) static int ina2xx_in_read(struct device *dev, u32 attr, int channel, long *val) { int voltage_reg = channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE; - u32 under_voltage_mask = channel ? INA226_BUS_UNDER_VOLTAGE_MASK - : INA226_SHUNT_UNDER_VOLTAGE_MASK; - u32 over_voltage_mask = channel ? INA226_BUS_OVER_VOLTAGE_MASK - : INA226_SHUNT_OVER_VOLTAGE_MASK; + enum ina2xx_alert_type under_voltage_alert = channel ? INA2XX_ALERT_BUS_VOLTAGE_LOW + : INA2XX_ALERT_SHUNT_VOLTAGE_LOW; + enum ina2xx_alert_type over_voltage_alert = channel ? INA2XX_ALERT_BUS_VOLTAGE_HIGH + : INA2XX_ALERT_SHUNT_VOLTAGE_HIGH; struct ina2xx_data *data = dev_get_drvdata(dev); struct regmap *regmap = data->regmap; unsigned int regval; @@ -532,15 +576,15 @@ static int ina2xx_in_read(struct device *dev, u32 attr, int channel, long *val) *val = ina2xx_get_value(data, voltage_reg, regval); break; case hwmon_in_lcrit: - return ina226_alert_limit_read(data, under_voltage_mask, + return ina226_alert_limit_read(data, under_voltage_alert, voltage_reg, val); case hwmon_in_crit: - return ina226_alert_limit_read(data, over_voltage_mask, + return ina226_alert_limit_read(data, over_voltage_alert, voltage_reg, val); case hwmon_in_lcrit_alarm: - return ina226_alert_read(data, under_voltage_mask, val); + return ina226_alert_read(data, under_voltage_alert, val); case hwmon_in_crit_alarm: - return ina226_alert_read(data, over_voltage_mask, val); + return ina226_alert_read(data, over_voltage_alert, val); default: return -EOPNOTSUPP; } @@ -594,10 +638,10 @@ static int ina2xx_power_read(struct device *dev, u32 attr, long *val) case hwmon_power_average: return sy24655_average_power_read(data, SY24655_EIN, val); case hwmon_power_crit: - return ina226_alert_limit_read(data, INA226_POWER_OVER_LIMIT_MASK, + return ina226_alert_limit_read(data, INA2XX_ALERT_POWER_HIGH, INA2XX_POWER, val); case hwmon_power_crit_alarm: - return ina226_alert_read(data, INA226_POWER_OVER_LIMIT_MASK, val); + return ina226_alert_read(data, INA2XX_ALERT_POWER_HIGH, val); default: return -EOPNOTSUPP; } @@ -633,15 +677,15 @@ static int ina2xx_curr_read(struct device *dev, u32 attr, long *val) *val = ina2xx_get_value(data, INA2XX_CURRENT, regval); return 0; case hwmon_curr_lcrit: - return ina226_alert_limit_read(data, INA226_SHUNT_UNDER_VOLTAGE_MASK, + return ina226_alert_limit_read(data, INA2XX_ALERT_CURRENT_LOW, INA2XX_CURRENT, val); case hwmon_curr_crit: - return ina226_alert_limit_read(data, INA226_SHUNT_OVER_VOLTAGE_MASK, + return ina226_alert_limit_read(data, INA2XX_ALERT_CURRENT_HIGH, INA2XX_CURRENT, val); case hwmon_curr_lcrit_alarm: - return ina226_alert_read(data, INA226_SHUNT_UNDER_VOLTAGE_MASK, val); + return ina226_alert_read(data, INA2XX_ALERT_CURRENT_LOW, val); case hwmon_curr_crit_alarm: - return ina226_alert_read(data, INA226_SHUNT_OVER_VOLTAGE_MASK, val); + return ina226_alert_read(data, INA2XX_ALERT_CURRENT_HIGH, val); default: return -EOPNOTSUPP; } @@ -685,12 +729,12 @@ static int ina2xx_in_write(struct device *dev, u32 attr, int channel, long val) switch (attr) { case hwmon_in_lcrit: return ina226_alert_limit_write(data, - channel ? INA226_BUS_UNDER_VOLTAGE_MASK : INA226_SHUNT_UNDER_VOLTAGE_MASK, + channel ? INA2XX_ALERT_BUS_VOLTAGE_LOW : INA2XX_ALERT_SHUNT_VOLTAGE_LOW, channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE, val); case hwmon_in_crit: return ina226_alert_limit_write(data, - channel ? INA226_BUS_OVER_VOLTAGE_MASK : INA226_SHUNT_OVER_VOLTAGE_MASK, + channel ? INA2XX_ALERT_BUS_VOLTAGE_HIGH : INA2XX_ALERT_SHUNT_VOLTAGE_HIGH, channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE, val); default: @@ -705,7 +749,7 @@ static int ina2xx_power_write(struct device *dev, u32 attr, long val) switch (attr) { case hwmon_power_crit: - return ina226_alert_limit_write(data, INA226_POWER_OVER_LIMIT_MASK, + return ina226_alert_limit_write(data, INA2XX_ALERT_POWER_HIGH, INA2XX_POWER, val); default: return -EOPNOTSUPP; @@ -719,10 +763,10 @@ static int ina2xx_curr_write(struct device *dev, u32 attr, long val) switch (attr) { case hwmon_curr_lcrit: - return ina226_alert_limit_write(data, INA226_SHUNT_UNDER_VOLTAGE_MASK, + return ina226_alert_limit_write(data, INA2XX_ALERT_CURRENT_LOW, INA2XX_CURRENT, val); case hwmon_curr_crit: - return ina226_alert_limit_write(data, INA226_SHUNT_OVER_VOLTAGE_MASK, + return ina226_alert_limit_write(data, INA2XX_ALERT_CURRENT_HIGH, INA2XX_CURRENT, val); default: return -EOPNOTSUPP; From 35760f5efd7bfa7a44a3831f47e19fe9cbafc905 Mon Sep 17 00:00:00 2001 From: Jared Kangas Date: Thu, 20 Aug 2026 06:09:24 -0700 Subject: [PATCH 1075/2366] hwmon: (ina2xx) Decouple in0 and curr1 alarms INA2XX current limits are converted into shunt voltage limits internally using the shunt resistor value. Once a current limit's corresponding voltage limit is written to the hardware, shunt voltage and current alarms are indistinguishable from each other. This causes two issues: 1. in0/curr1 alarms may be unintentionally cleared by reading from the opposite input's alarm. 2. When a limit for either in0 (shunt voltage) or curr1 (current) is set, both of their alarms are triggered, and both of their limits read nonzero. An example of this behavior on an INA231: # cd /sys/class/hwmon/hwmon0 # head {curr1,in0}_input ==> curr1_input <== 1713 ==> in0_input <== 2 # echo 1800 >curr1_lcrit # head {curr1,in0}_lcrit_alarm ==> curr1_lcrit_alarm <== 1 ==> in0_lcrit_alarm <== 0 # head {in0,curr1}_lcrit_alarm ==> in0_lcrit_alarm <== 1 ==> curr1_lcrit_alarm <== 0 # head {in0,curr1}_lcrit_alarm ==> in0_lcrit_alarm <== 1 ==> curr1_lcrit_alarm <== 1 This is because curr1 uses the same underlying masks (INA226_SHUNT_*_VOLTAGE_MASK) as in0 on the hardware. As a result, ina2xx_{curr,in}_read() both read the shunt voltage alarms/limits without considering whether the voltage or current is currently set. To fix this, track the active alarm type in ina2xx_data and guard alarm/limit reads with a check that returns zero if the active alarm is for a different type. The new field is initialized based on the MASK_ENABLE register's set function, assuming voltage instead of current when the shunt voltage mask is set. After this fix, the alarms only read back 1 if their corresponding limit is set: # echo 0 >curr1_lcrit # head {curr1,in0}_lcrit_alarm ==> curr1_lcrit_alarm <== 0 ==> in0_lcrit_alarm <== 0 # echo 9999 >curr1_lcrit # head {curr1,in0}_lcrit_alarm ==> curr1_lcrit_alarm <== 1 ==> in0_lcrit_alarm <== 0 # echo 9999 >in0_lcrit # head {curr1,in0}_lcrit_alarm ==> curr1_lcrit_alarm <== 0 ==> in0_lcrit_alarm <== 1 Fixes: 4d5c2d986757 ("hwmon: (ina2xx) Add support for current limits") Signed-off-by: Jared Kangas Link: https://patch.msgid.link/20260820-upstream-ina2xx-in0-curr1-alarms-v2-4-fdce35abc41e@redhat.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 65 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 75e97e30bdcd..19b35f3bf3a3 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -159,6 +160,7 @@ struct ina2xx_data { const struct ina2xx_config *config; enum ina2xx_ids chip; + enum ina2xx_alert_type active_alert; long rshunt; long current_lsb_uA; long power_lsb_uW; @@ -463,6 +465,35 @@ static u32 ina2xx_alert_type_to_mask(enum ina2xx_alert_type alert) } } +static enum ina2xx_alert_type ina2xx_mask_to_alert_type(u32 mask) +{ + int top_bit = fls(mask & INA226_ALERT_CONFIG_MASK); + + if (!top_bit) + return INA2XX_ALERT_NONE; + + /* + * Multiple bits may be set, with the highest-set function taking + * precedence according to the datasheet. Shunt voltage masks are + * assumed to map to voltage monitoring rather than current monitoring, + * since the latter isn't directly implemented in the hardware. + */ + switch (BIT(top_bit - 1)) { + case INA226_SHUNT_OVER_VOLTAGE_MASK: + return INA2XX_ALERT_SHUNT_VOLTAGE_HIGH; + case INA226_SHUNT_UNDER_VOLTAGE_MASK: + return INA2XX_ALERT_SHUNT_VOLTAGE_LOW; + case INA226_BUS_OVER_VOLTAGE_MASK: + return INA2XX_ALERT_BUS_VOLTAGE_HIGH; + case INA226_BUS_UNDER_VOLTAGE_MASK: + return INA2XX_ALERT_BUS_VOLTAGE_LOW; + case INA226_POWER_OVER_LIMIT_MASK: + return INA2XX_ALERT_POWER_HIGH; + default: + return INA2XX_ALERT_NONE; + } +} + static int ina226_alert_limit_read(struct ina2xx_data *data, enum ina2xx_alert_type alert, int reg, long *val) { @@ -471,6 +502,12 @@ static int ina226_alert_limit_read(struct ina2xx_data *data, enum ina2xx_alert_t u32 mask; int ret; + /* Avoid nonzero reads from inactive alerts caused by shared limit register */ + if (data->active_alert != alert) { + *val = 0; + return 0; + } + ret = regmap_read(regmap, INA226_MASK_ENABLE, ®val); if (ret) return ret; @@ -506,6 +543,7 @@ static int ina226_alert_limit_write(struct ina2xx_data *data, enum ina2xx_alert_ INA226_ALERT_CONFIG_MASK, 0); if (ret < 0) return ret; + data->active_alert = INA2XX_ALERT_NONE; ret = regmap_write(regmap, INA226_ALERT_LIMIT, ina226_alert_to_reg(data, reg, val)); @@ -514,9 +552,13 @@ static int ina226_alert_limit_write(struct ina2xx_data *data, enum ina2xx_alert_ if (val) { mask = ina2xx_alert_type_to_mask(alert); - return regmap_update_bits(regmap, INA226_MASK_ENABLE, - INA226_ALERT_CONFIG_MASK, mask); + ret = regmap_update_bits(regmap, INA226_MASK_ENABLE, + INA226_ALERT_CONFIG_MASK, mask); + if (ret < 0) + return ret; + data->active_alert = alert; } + return 0; } @@ -546,6 +588,15 @@ static int ina226_alert_read(struct ina2xx_data *data, enum ina2xx_alert_type al u32 mask; int ret; + /* + * With alert latching, reading alerts from hardware also clears the + * alert, so return early if the alert is inactive. + */ + if (data->active_alert != alert) { + *val = 0; + return 0; + } + ret = regmap_read_bypassed(data->regmap, INA226_MASK_ENABLE, ®val); if (ret) return ret; @@ -988,6 +1039,16 @@ static int ina2xx_init(struct device *dev, struct ina2xx_data *data) if (data->config->has_alerts) { bool active_high = device_property_read_bool(dev, "ti,alert-polarity-active-high"); + unsigned int mask_enable; + + /* + * Infer active alert from MASK_ENABLE in case it's already + * configured (e.g., by a past probe or firmware) + */ + ret = regmap_read(regmap, INA226_MASK_ENABLE, &mask_enable); + if (ret < 0) + return ret; + data->active_alert = ina2xx_mask_to_alert_type(mask_enable); regmap_update_bits(regmap, INA226_MASK_ENABLE, INA226_ALERT_LATCH_ENABLE | INA226_ALERT_POLARITY, From 013c5a93e8062014177d68af799e8867cf03958d Mon Sep 17 00:00:00 2001 From: Antonin Godard Date: Tue, 18 Aug 2026 10:08:40 +0200 Subject: [PATCH 1076/2366] Documentation: hwmon: replace full-width colon by a standard ASCII colon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It prevented the pdfdocs target to complete, prompting the following error: Latexmk: ====Problematic refs and citations with line #s in .tex file: Missing character: There is no : (U+FF1A) in font DejaVu Serif/OT:script=latn;l Fixes: 69001f21ded78 ("hwmon: document: add gpd-fan") Signed-off-by: Antonin Godard Link: https://patch.msgid.link/20260818-doc-hwmon-remove-confusable-v2-1-c1dff1ec01cd@bootlin.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/gpd-fan.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/hwmon/gpd-fan.rst b/Documentation/hwmon/gpd-fan.rst index 29527a77fe88..b27657d33056 100644 --- a/Documentation/hwmon/gpd-fan.rst +++ b/Documentation/hwmon/gpd-fan.rst @@ -67,7 +67,7 @@ pwm1_enable at full speed. Write "1" to set to manual, write "2" to let the EC control decide fan speed. Read this attribute to see current status. - NB:In consideration of the safety of the device, when setting to manual mode, + NB: In consideration of the safety of the device, when setting to manual mode, the pwm speed will be set to the maximum value (255) by default. You can set a different value by writing pwm1 later. From 100eb7c7d0b28c52ad1b25d51c316fdc46c27c71 Mon Sep 17 00:00:00 2001 From: hanzhijian Date: Fri, 21 Aug 2026 19:57:20 +0800 Subject: [PATCH 1077/2366] hwmon: (yogafan) fix non-kernel-doc comment The file description comment starts with "/**" which is reserved for kernel-doc comments, triggering a kernel-doc checker warning. Change it to a plain "/*" comment since it does not document any function or struct. Fixes: c67c248ca406a ("hwmon: (yogafan) Add support for Lenovo Yoga/Legion fan monitoring") Signed-off-by: hanzhijian Link: https://patch.msgid.link/20260821115720.2017516-1-hanzhijian1991@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/yogafan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c index 48fa5148d9e2..278cb089b0fd 100644 --- a/drivers/hwmon/yogafan.c +++ b/drivers/hwmon/yogafan.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/** +/* * yoga_fan.c - Lenovo Yoga/Legion Fan Hardware Monitoring Driver * * Provides fan speed monitoring for Lenovo Yoga, Legion, and IdeaPad From 06b7cf395b1fb652a50db39674a759658fbfba0d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 21 Aug 2026 07:49:15 -0700 Subject: [PATCH 1078/2366] hwmon: (sht4x) Add missing locks Sashiko reports: Heater sysfs callbacks (heater_enable_store, heater_power_store, and heater_time_store) are exposed to data races without the hwmon lock. If a user-space process reads hwmon data while another process enables the heater, heater_enable_store() executes without holding hwmon_lock(dev). This can interleave I2C commands and mutate shared state (data->heating_complete and data->data_pending) concurrently with sht4x_read_values(), leading to corrupted I2C sequences. Fixes: 53dfa12299c1 ("hwmon: (sht4x) Rely on subsystem locking") Cc: Alessandro Zini Signed-off-by: Guenter Roeck Link: https://patch.msgid.link/20260821144916.2889031-1-linux@roeck-us.net --- drivers/hwmon/sht4x.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/hwmon/sht4x.c b/drivers/hwmon/sht4x.c index 9cace0e8acda..7a0dc2ed723d 100644 --- a/drivers/hwmon/sht4x.c +++ b/drivers/hwmon/sht4x.c @@ -277,6 +277,8 @@ static ssize_t heater_enable_store(struct device *dev, heating_time_bound = 1100; } + guard(hwmon_lock)(dev); + if (time_before(jiffies, data->heating_complete)) return -EBUSY; @@ -314,6 +316,8 @@ static ssize_t heater_power_store(struct device *dev, if (power != 20 && power != 110 && power != 200) return -EINVAL; + guard(hwmon_lock)(dev); + data->heater_power = power; return count; @@ -344,6 +348,8 @@ static ssize_t heater_time_store(struct device *dev, if (time != 100 && time != 1000) return -EINVAL; + guard(hwmon_lock)(dev); + data->heater_time = time; return count; From 70c33e211b2b78830f76c908e5236b77ffde63a0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 21 Aug 2026 07:49:16 -0700 Subject: [PATCH 1079/2366] hwmon: (sht4x) Fix return value from heater_enable_store() Sashiko reports: The return value in heater_enable_store() causes an unexpected write failure in user-space. When the heater is successfully enabled, the function returns 0 instead of count: drivers/hwmon/sht4x.c:heater_enable_store() { ... data->heating_complete = jiffies + msecs_to_jiffies(heating_time_bound); data->data_pending = true; return 0; } Returning 0 signals to VFS that no bytes were processed. Standard user-space tools will retry the write with the remaining bytes. On the retry, time_before(jiffies, data->heating_complete) evaluates to true, and the function immediately fails with -EBUSY. Return count as expected to fix the problem. Fixes: 0eed6fc3d2b9e ("hwmon: (sht4x): add heater support") Cc: Antoni Pokusinski Cc: Alessandro Zini Signed-off-by: Guenter Roeck Link: https://patch.msgid.link/20260821144916.2889031-2-linux@roeck-us.net --- drivers/hwmon/sht4x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/sht4x.c b/drivers/hwmon/sht4x.c index 7a0dc2ed723d..a97dda9e92dc 100644 --- a/drivers/hwmon/sht4x.c +++ b/drivers/hwmon/sht4x.c @@ -288,7 +288,7 @@ static ssize_t heater_enable_store(struct device *dev, data->heating_complete = jiffies + msecs_to_jiffies(heating_time_bound); data->data_pending = true; - return 0; + return count; } static ssize_t heater_power_show(struct device *dev, From 5a0aacaa2d593d7582ecfe289529b937b6dc5d3c Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Fri, 28 Aug 2026 17:54:13 +0700 Subject: [PATCH 1080/2366] hwmon: (applesmc) fix key backlight workqueue leak on register failure applesmc_create_key_backlight() allocates applesmc_led_wq before calling led_classdev_register(). When register fails, the error is returned to applesmc_init(), which jumps to out_light_sysfs and skips applesmc_release_key_backlight(), leaking the workqueue. Destroy the workqueue on the register failure path. The bug was introduced when the inline init block was refactored into a helper that returns errors directly, dropping the old out_light_wq unwind label. Fixes: 0b0b5dff8967 ("hwmon: (applesmc) Simplify feature sysfs handling") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Link: https://patch.msgid.link/20260828105413.2401385-1-congnt264@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/applesmc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 00e603b5e401..d0baa10502f7 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -1128,12 +1128,17 @@ static void applesmc_release_light_sensor(void) static int applesmc_create_key_backlight(void) { + int ret; + if (!smcreg.has_key_backlight) return 0; applesmc_led_wq = create_singlethread_workqueue("applesmc-led"); if (!applesmc_led_wq) return -ENOMEM; - return led_classdev_register(&pdev->dev, &applesmc_backlight); + ret = led_classdev_register(&pdev->dev, &applesmc_backlight); + if (ret) + destroy_workqueue(applesmc_led_wq); + return ret; } static void applesmc_release_key_backlight(void) From 32689f0fc54fd801f1cd11637666e534984cb04a Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:31:03 +0800 Subject: [PATCH 1081/2366] ASoC: bcm: bcm63xx: Publish the OF module aliases The BCM63xx I2S platform driver matches brcm,bcm63xx-i2s using snd_soc_bcm_audio_match. With SND_BCM63XX_I2S_WHISTLER=m, the platform bus emits an OF modalias but snd-soc-63xx does not publish that table. Export the existing OF IDs for module autoloading. The PCM companion and the probe path remain unchanged. Fixes: 88eb404ccc3e ("ASoC: brcm: Add DSL/PON SoC audio driver") Signed-off-by: hpp.iscas Link: https://patch.msgid.link/20260905133103.63432-1-hppiscas@163.com Signed-off-by: Mark Brown --- sound/soc/bcm/bcm63xx-i2s-whistler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/bcm/bcm63xx-i2s-whistler.c b/sound/soc/bcm/bcm63xx-i2s-whistler.c index c47ed1e6ea2b..14d111fe29d8 100644 --- a/sound/soc/bcm/bcm63xx-i2s-whistler.c +++ b/sound/soc/bcm/bcm63xx-i2s-whistler.c @@ -285,6 +285,7 @@ static const struct of_device_id snd_soc_bcm_audio_match[] = { {.compatible = "brcm,bcm63xx-i2s"}, { } }; +MODULE_DEVICE_TABLE(of, snd_soc_bcm_audio_match); #endif static struct platform_driver bcm63xx_i2s_driver = { From a7c1290eef60711c10289c056ad32ed1f2b47b12 Mon Sep 17 00:00:00 2001 From: Vasileios Almpanis Date: Thu, 30 Jul 2026 11:30:24 +0200 Subject: [PATCH 1082/2366] configfs: pin the symlink target's dirent instead of chasing ->ci_dentry create_link() reads the target's configfs_dirent from item->ci_dentry->d_fsdata, relying on the item reference taken by get_target(). That reference pins the item, not its dentry: the dentry is pinned by DCACHE_PERSISTENT, which configfs_remove_dir() releases via simple_rmdir() while the item is still alive. A symlink racing with rmdir of its target can therefore find ->ci_dentry freed and its dirent released, triggering WARN_ON(!atomic_read(&sd->s_count)) in configfs_get(). Take the dirent in get_target() as well, under ->d_lock and atomically with the item reference, and pass it down to create_link(). A hashed dentry has not been killed yet, so its ->d_fsdata reference keeps the dirent alive there. Cc: stable@vger.kernel.org Fixes: 7063fbf22611 ("[PATCH] configfs: User-driven configuration filesystem") Signed-off-by: Vasileios Almpanis Tested-by: Breno Leitao Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260730093435.195441-2-vasilisalmpanis@gmail.com Signed-off-by: Breno Leitao --- fs/configfs/symlink.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c index 31eb28b27309..3b31c714400f 100644 --- a/fs/configfs/symlink.c +++ b/fs/configfs/symlink.c @@ -76,9 +76,9 @@ static int configfs_get_target_path(struct config_item *item, static int create_link(struct config_item *parent_item, struct config_item *item, + struct configfs_dirent *target_sd, struct dentry *dentry) { - struct configfs_dirent *target_sd = item->ci_dentry->d_fsdata; char *body; int ret; @@ -115,6 +115,7 @@ static int create_link(struct config_item *parent_item, static int get_target(const char *symname, struct config_item **target, + struct configfs_dirent **target_sd, struct super_block *sb) { struct path path __free(path_put) = {}; @@ -125,7 +126,20 @@ static int get_target(const char *symname, struct config_item **target, return ret; if (path.dentry->d_sb != sb) return -EPERM; - *target = configfs_get_config_item(path.dentry); + /* + * A hashed dentry guarantees that neither the item nor the dirent + * have been released yet, as removals unhash before dropping. + * Grab both references here. An item reference alone would not keep + * ->ci_dentry alive. + */ + spin_lock(&path.dentry->d_lock); + if (!d_unhashed(path.dentry)) { + struct configfs_dirent *sd = path.dentry->d_fsdata; + + *target = config_item_get(sd->s_element); + *target_sd = configfs_get(sd); + } + spin_unlock(&path.dentry->d_lock); if (!*target) return -ENOENT; return 0; @@ -139,6 +153,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir, struct configfs_dirent *sd; struct config_item *parent_item; struct config_item *target_item = NULL; + struct configfs_dirent *target_sd = NULL; const struct config_item_type *type; sd = dentry->d_parent->d_fsdata; @@ -182,7 +197,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir, * AV, a thoroughly annoyed bastard. */ inode_unlock(dir); - ret = get_target(symname, &target_item, dentry->d_sb); + ret = get_target(symname, &target_item, &target_sd, dentry->d_sb); inode_lock(dir); if (ret) goto out_put; @@ -196,13 +211,14 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir, ret = type->ct_item_ops->allow_link(parent_item, target_item); if (!ret) { mutex_lock(&configfs_symlink_mutex); - ret = create_link(parent_item, target_item, dentry); + ret = create_link(parent_item, target_item, target_sd, dentry); mutex_unlock(&configfs_symlink_mutex); if (ret && type->ct_item_ops->drop_link) type->ct_item_ops->drop_link(parent_item, target_item); } + configfs_put(target_sd); config_item_put(target_item); out_put: From f06c2d26d1999d37e93299db0ecead04ca7d0b9f Mon Sep 17 00:00:00 2001 From: Vasileios Almpanis Date: Thu, 30 Jul 2026 11:30:25 +0200 Subject: [PATCH 1083/2366] configfs: unhash the dentry before dropping the item in rmdir configfs_get_config_item() treats a hashed dentry as proof that sd->s_element is a live config_item. configfs_rmdir() breaks that: simple_rmdir() leaves the dentry hashed, the last reference to the item is dropped right after, and the dentry is only unhashed by d_delete() once ->rmdir() has returned. configfs_symlink() resolves its target holding no lock on it, so get_target() can land in that window: BUG: KASAN: slab-use-after-free in config_item_get+0x26/0x90 get_target fs/configfs/symlink.c:128 [inline] configfs_symlink+0x4ab/0x1030 fs/configfs/symlink.c:185 Unhash in configfs_remove_dir(), while the item is still guaranteed to be there. A reference obtained just before that stays harmless, as create_link() rechecks CONFIGFS_USET_DROPPING, already set by configfs_detach_prep(). Both configfs_unregister_subsystem() paths d_drop() after detaching, so this only makes rmdir match them. Reported-by: syzbot+6b16e3d085833cbf3e25@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6b16e3d085833cbf3e25 Fixes: 7063fbf22611 ("[PATCH] configfs: User-driven configuration filesystem") Cc: stable@vger.kernel.org Signed-off-by: Vasileios Almpanis Tested-by: Breno Leitao Reviewed-by: Breno Leitao Link: https://patch.msgid.link/20260730093435.195441-3-vasilisalmpanis@gmail.com Signed-off-by: Breno Leitao --- fs/configfs/dir.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c index 3c88f13f1ca2..eda80c2a2d38 100644 --- a/fs/configfs/dir.c +++ b/fs/configfs/dir.c @@ -416,6 +416,15 @@ static void configfs_remove_dir(struct dentry *d) if (d_really_is_positive(d)) { if (unlikely(simple_rmdir(d_inode(parent), d))) pr_warn("remove_dir (%pd): attributes remain", d); + else + /* + * configfs_get_config_item() takes a hashed dentry as + * proof that ->s_element is still alive. Our caller + * is about to drop the last reference to the item and + * the VFS will not unhash until after we return, so + * unhash it here. + */ + d_drop(d); } pr_debug(" o %pd removing done (%d)\n", d, d_count(d)); From d112159df5c6cc5ee6ab91cc32bf6ed29939df38 Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:31:33 +0800 Subject: [PATCH 1084/2366] ASoC: Intel: SST: Publish the PCI module aliases The legacy SST PCI driver matches Intel Tangier devices using intel_sst_ids, but its only explicit module alias is "sst". That alias does not match PCI modalias events when this driver is built as a module. Publish its PCI table. The independently configurable SOF driver does not provide aliases for the legacy SST module. Fixes: f533a035e4da ("ASoC: Intel: mrfld - create separate module for pci part") Signed-off-by: hpp.iscas Link: https://patch.msgid.link/20260905133133.63661-1-hppiscas@163.com Signed-off-by: Mark Brown --- sound/soc/intel/atom/sst/sst_pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/intel/atom/sst/sst_pci.c b/sound/soc/intel/atom/sst/sst_pci.c index 44bb11c69490..1a53c993b57f 100644 --- a/sound/soc/intel/atom/sst/sst_pci.c +++ b/sound/soc/intel/atom/sst/sst_pci.c @@ -167,6 +167,7 @@ static const struct pci_device_id intel_sst_ids[] = { { PCI_DEVICE_DATA(INTEL, SST_TNG, 0) }, { 0, } }; +MODULE_DEVICE_TABLE(pci, intel_sst_ids); static struct pci_driver sst_driver = { .name = SST_DRV_NAME, From 776924b2d9c451fc9dcc40673d17ff255bbc0fef Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Mon, 24 Aug 2026 18:47:58 +0200 Subject: [PATCH 1085/2366] btrfs: set space_info before adding new free space in btrfs_make_block_group() btrfs_make_block_group() calls btrfs_add_new_free_space() before assigning cache->space_info. On a zoned filesystem that ends up in __btrfs_add_free_space_zoned(), which dereferences block_group->space_info and thus hits a NULL pointer dereference when a non-initial free space range is added (e.g. during relocation). Assign cache->space_info before the btrfs_add_new_free_space() call. Reviewed-by: Boris Burkov Signed-off-by: Johannes Thumshirn Signed-off-by: David Sterba --- fs/btrfs/block-group.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index 830460a40e86..ee182369254c 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -3074,6 +3074,18 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran return ERR_PTR(ret); } + /* + * Ensure the corresponding space_info object is created and + * assigned to our block group. We want our bg to be added to the rbtree + * with its ->space_info set. + * + * On a zoned filesystem btrfs_add_new_free_space() ends up in + * __btrfs_add_free_space_zoned(), which dereferences + * block_group->space_info, so it has to be set beforehand. + */ + cache->space_info = space_info; + ASSERT(cache->space_info); + ret = btrfs_add_new_free_space(cache, chunk_offset, chunk_offset + size, NULL); btrfs_free_excluded_extents(cache); if (ret) { @@ -3081,14 +3093,6 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran return ERR_PTR(ret); } - /* - * Ensure the corresponding space_info object is created and - * assigned to our block group. We want our bg to be added to the rbtree - * with its ->space_info set. - */ - cache->space_info = space_info; - ASSERT(cache->space_info); - ret = btrfs_add_block_group_cache(cache); if (ret) { btrfs_remove_free_space_cache(cache); From 36f9aafa46f5b9fecf92d9218c5574f1ef6b4907 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 3 Sep 2026 13:15:46 +0100 Subject: [PATCH 1086/2366] btrfs: fix unnecessary transaction commit fallback from btrfs_log_all_parents() When btrfs_log_all_parents() returns without doing any work (because all parent directories were already logged), it returns 1, which is propagated up the fsync call chain up to btrfs_log_dentry_safe(), and that causes btrfs_sync_file() to trigger am unnecessary transaction commit. This all happens because the call to btrfs_search_slot() in btrfs_log_all_parents() always returns 1, as there can not be any inode ref keys with an offset 0 (an invalid inode number), so if the while loop below it does not do any work because all parent directories were already logged, the 'ret' variable remains with a value of 1, which is then returned up the call chain to btrfs_sync_file(). Fix this by setting 'ret' to 0 after the call to btrfs_search_slot(). Fixes: 0f24ea456ae1 ("btrfs: tracepoints: add trace event for btrfs_log_all_parents()") Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/tree-log.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index 7ba7b6098aa5..a00094604e54 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -7286,6 +7286,22 @@ static int btrfs_log_all_parents(struct btrfs_trans_handle *trans, ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); if (ret < 0) goto out; + /* + * There can't be an inode ref key with offset 0 because inode numbers + * start at BTRFS_FIRST_FREE_OBJECTID. + */ + if (WARN_ON_ONCE(ret == 0)) { + btrfs_err(trans->fs_info, + "found inode ref key with offset 0 for root %llu inode %llu", + btrfs_root_id(root), ino); + ret = BTRFS_LOG_FORCE_COMMIT; + goto out; + } + /* + * Set to 0 so that in case we don't do any work below, we won't return + * 1 and trigger an unnecessary transaction commit. + */ + ret = 0; while (true) { struct extent_buffer *leaf = path->nodes[0]; From 2a4513ab53361360329b7ad1496d666b7433289e Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 3 Sep 2026 16:16:32 +0100 Subject: [PATCH 1087/2366] btrfs: tree-checker: validate key offset for inode ref keys For a subvolume tree, the offset of an inode ref key corresponds to an inode number, and that must always be within the range: [ BTRFS_FIRST_FREE_OBJECTID (256), BTRFS_LAST_FREE_OBJECTID (-256) ] Add a check for that in check_inode_ref(). Sashiko complained about such check missing in another unrelated patch. Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index 0ce91396b517..a4447c57c2a4 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -1909,6 +1909,16 @@ static int check_inode_ref(struct extent_buffer *leaf, return -EUCLEAN; } + if (unlikely(btrfs_is_fstree(btrfs_header_owner(leaf)) && + (key->offset < BTRFS_FIRST_FREE_OBJECTID || + key->offset > BTRFS_LAST_FREE_OBJECTID))) { + inode_ref_err(leaf, slot, + "invalid offset for ref key, have %llu expect [%llu, %lld]", + key->offset, BTRFS_FIRST_FREE_OBJECTID, + BTRFS_LAST_FREE_OBJECTID); + return -EUCLEAN; + } + ptr = btrfs_item_ptr_offset(leaf, slot); end = ptr + btrfs_item_size(leaf, slot); while (ptr < end) { From b18f0f8334e6d7e4ed4baf1f404658537659cb57 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 3 Sep 2026 16:24:43 +0100 Subject: [PATCH 1088/2366] btrfs: tree-checker: validate parent field for inode extref items For a subvolume tree, the parent field of an inode extref item corresponds to an inode number, and that must always be within the range: [ BTRFS_FIRST_FREE_OBJECTID (256), BTRFS_LAST_FREE_OBJECTID (-256) ] Add a check for that in check_inode_extref(). Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index a4447c57c2a4..83f7b0aaab21 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -1962,12 +1962,14 @@ static int check_inode_extref(struct extent_buffer *leaf, { unsigned long ptr = btrfs_item_ptr_offset(leaf, slot); unsigned long end = ptr + btrfs_item_size(leaf, slot); + const bool is_fstree = btrfs_is_fstree(btrfs_header_owner(leaf)); if (unlikely(!check_prev_ino(leaf, key, slot, prev_key))) return -EUCLEAN; while (ptr < end) { struct btrfs_inode_extref *extref = (struct btrfs_inode_extref *)ptr; + u64 parent; u16 namelen; if (unlikely(ptr + sizeof(*extref) > end)) { @@ -1977,6 +1979,16 @@ static int check_inode_extref(struct extent_buffer *leaf, return -EUCLEAN; } + parent = btrfs_inode_extref_parent(leaf, extref); + if (unlikely(is_fstree && (parent < BTRFS_FIRST_FREE_OBJECTID || + parent > BTRFS_LAST_FREE_OBJECTID))) { + inode_ref_err(leaf, slot, + "invalid parent for extref key, have %llu expect [%llu, %lld]", + parent, BTRFS_FIRST_FREE_OBJECTID, + BTRFS_LAST_FREE_OBJECTID); + return -EUCLEAN; + } + namelen = btrfs_inode_extref_name_len(leaf, extref); if (unlikely(ptr + sizeof(*extref) + namelen > end)) { inode_ref_err(leaf, slot, From 09f1294ee2abee7fe1c2d600671498b7642e0fe0 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 3 Sep 2026 17:24:16 +0100 Subject: [PATCH 1089/2366] btrfs: tree-checker: validate name length for extref items We are validating the name length of inode ref items, but we miss the same validation for extref items. Sashiko pointed this out while reviewing other patch. Add the missing validation, similar to what was done in commit 3dc22abc21f5 ("btrfs: tree-checker: validate INODE_REF's namelen"). Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index 83f7b0aaab21..ab5abbb475e2 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -1990,6 +1990,13 @@ static int check_inode_extref(struct extent_buffer *leaf, } namelen = btrfs_inode_extref_name_len(leaf, extref); + if (unlikely(namelen == 0 || namelen > BTRFS_NAME_LEN)) { + inode_ref_err(leaf, slot, + "invalid inode extref name length, has %u expect [1, %u]", + namelen, BTRFS_NAME_LEN); + return -EUCLEAN; + } + if (unlikely(ptr + sizeof(*extref) + namelen > end)) { inode_ref_err(leaf, slot, "inode extref overflow, ptr %lu end %lu namelen %u", From 387d744fa7e499d2c3748a4e60e02ebb24e7fb16 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Tue, 25 Aug 2026 03:36:03 +0200 Subject: [PATCH 1090/2366] netfilter: nfnetlink_log: cope with concurrent instance destruction Instances are refcounted. However, only memory release happens on the 1 -> 0 transition; the unlink from hashes can occur with any refcount. Uncooperative userspace can force a situation where a queue is pending for destruction from netlink event while a different socket with same portid processes an UNBIND request. With right timing, this will unhash the instance again: Oops: general protection fault, [..] Call Trace: nfulnl_recv_config+0x31a/0xd50 nfnetlink_rcv_msg+0x7c2/0xeb0 Fixes: 0597f2680d66 ("[NETFILTER]: Add new "nfnetlink_log" userspace packet logging facility") Reported-by: Eulgyu Kim Reported-by: Jaeyoung Chung Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nfnetlink_log.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/net/netfilter/nfnetlink_log.c b/net/netfilter/nfnetlink_log.c index 9d7fec570abe..d923f2cb1398 100644 --- a/net/netfilter/nfnetlink_log.c +++ b/net/netfilter/nfnetlink_log.c @@ -228,13 +228,18 @@ static void __nfulnl_flush(struct nfulnl_instance *inst); static void __instance_destroy(struct nfulnl_instance *inst) { + spin_lock(&inst->lock); + if (inst->copy_mode == NFULNL_COPY_DISABLED) { + /* attempt to UNBIND a queue already pending + * destruction via netlink close event. Ignore. + */ + spin_unlock(&inst->lock); + return; + } + /* first pull it out of the global list */ hlist_del_rcu(&inst->hlist); - /* then flush all pending packets from skb */ - - spin_lock(&inst->lock); - /* lockless readers wont be able to use us */ inst->copy_mode = NFULNL_COPY_DISABLED; From 0bd7ed1a3263c26cf38fffc035b539a70d88667b Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Tue, 25 Aug 2026 12:28:36 +0200 Subject: [PATCH 1091/2366] netfilter: arp_tables: remove the 32bit compat interface This feature is required to use 32bit arptables binary on 64bit kernels. It's already off in many distributions including Debian and Fedora for many years. Zap arptables first, it's the most esoteric of the 4 flavors. Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- include/linux/netfilter_arp/arp_tables.h | 19 - net/ipv4/netfilter/arp_tables.c | 472 +---------------------- net/netfilter/Kconfig | 2 +- 3 files changed, 4 insertions(+), 489 deletions(-) diff --git a/include/linux/netfilter_arp/arp_tables.h b/include/linux/netfilter_arp/arp_tables.h index 05631a25e622..8b8d472eff34 100644 --- a/include/linux/netfilter_arp/arp_tables.h +++ b/include/linux/netfilter_arp/arp_tables.h @@ -56,23 +56,4 @@ void arpt_unregister_table(struct net *net, const char *name); extern unsigned int arpt_do_table(void *priv, struct sk_buff *skb, const struct nf_hook_state *state); -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT -#include - -struct compat_arpt_entry { - struct arpt_arp arp; - __u16 target_offset; - __u16 next_offset; - compat_uint_t comefrom; - struct compat_xt_counters counters; - unsigned char elems[]; -}; - -static inline struct xt_entry_target * -compat_arpt_get_target(struct compat_arpt_entry *e) -{ - return (void *)e + e->target_offset; -} - -#endif /* CONFIG_COMPAT */ #endif /* _ARPTABLES_H */ diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c index a87e07e80d0d..db307fa49f3f 100644 --- a/net/ipv4/netfilter/arp_tables.c +++ b/net/ipv4/netfilter/arp_tables.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -724,80 +723,6 @@ static int copy_entries_to_user(unsigned int total_size, return ret; } -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT -static void compat_standard_from_user(void *dst, const void *src) -{ - int v = *(compat_int_t *)src; - - if (v > 0) - v += xt_compat_calc_jump(NFPROTO_ARP, v); - memcpy(dst, &v, sizeof(v)); -} - -static int compat_standard_to_user(void __user *dst, const void *src) -{ - compat_int_t cv = *(int *)src; - - if (cv > 0) - cv -= xt_compat_calc_jump(NFPROTO_ARP, cv); - return copy_to_user(dst, &cv, sizeof(cv)) ? -EFAULT : 0; -} - -static int compat_calc_entry(const struct arpt_entry *e, - const struct xt_table_info *info, - const void *base, struct xt_table_info *newinfo) -{ - const struct xt_entry_target *t; - unsigned int entry_offset; - int off, i, ret; - - off = sizeof(struct arpt_entry) - sizeof(struct compat_arpt_entry); - entry_offset = (void *)e - base; - - t = arpt_get_target_c(e); - off += xt_compat_target_offset(t->u.kernel.target); - newinfo->size -= off; - ret = xt_compat_add_offset(NFPROTO_ARP, entry_offset, off); - if (ret) - return ret; - - for (i = 0; i < NF_ARP_NUMHOOKS; i++) { - if (info->hook_entry[i] && - (e < (struct arpt_entry *)(base + info->hook_entry[i]))) - newinfo->hook_entry[i] -= off; - if (info->underflow[i] && - (e < (struct arpt_entry *)(base + info->underflow[i]))) - newinfo->underflow[i] -= off; - } - return 0; -} - -static int compat_table_info(const struct xt_table_info *info, - struct xt_table_info *newinfo) -{ - struct arpt_entry *iter; - const void *loc_cpu_entry; - int ret; - - if (!newinfo || !info) - return -EINVAL; - - /* we dont care about newinfo->entries */ - memcpy(newinfo, info, offsetof(struct xt_table_info, entries)); - newinfo->initial_entries = 0; - loc_cpu_entry = info->entries; - ret = xt_compat_init_offsets(NFPROTO_ARP, info->number); - if (ret) - return ret; - xt_entry_foreach(iter, loc_cpu_entry, info->size) { - ret = compat_calc_entry(iter, info, loc_cpu_entry, newinfo); - if (ret != 0) - return ret; - } - return 0; -} -#endif - static int get_info(struct net *net, void __user *user, const int *len) { char name[XT_TABLE_MAXNAMELEN]; @@ -811,23 +736,11 @@ static int get_info(struct net *net, void __user *user, const int *len) return -EFAULT; name[XT_TABLE_MAXNAMELEN-1] = '\0'; -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT - if (in_compat_syscall()) - xt_compat_lock(NFPROTO_ARP); -#endif t = xt_request_find_table_lock(net, NFPROTO_ARP, name); if (!IS_ERR(t)) { struct arpt_getinfo info; const struct xt_table_info *private = t->private; -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT - struct xt_table_info tmp; - if (in_compat_syscall()) { - ret = compat_table_info(private, &tmp); - xt_compat_flush_offsets(NFPROTO_ARP); - private = &tmp; - } -#endif memset(&info, 0, sizeof(info)); info.valid_hooks = t->valid_hooks; memcpy(info.hook_entry, private->hook_entry, @@ -846,10 +759,7 @@ static int get_info(struct net *net, void __user *user, const int *len) module_put(t->me); } else ret = PTR_ERR(t); -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT - if (in_compat_syscall()) - xt_compat_unlock(NFPROTO_ARP); -#endif + return ret; } @@ -1059,367 +969,6 @@ static int do_add_counters(struct net *net, sockptr_t arg, unsigned int len) return ret; } -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT -struct compat_arpt_replace { - char name[XT_TABLE_MAXNAMELEN]; - u32 valid_hooks; - u32 num_entries; - u32 size; - u32 hook_entry[NF_ARP_NUMHOOKS]; - u32 underflow[NF_ARP_NUMHOOKS]; - u32 num_counters; - compat_uptr_t counters; - struct compat_arpt_entry entries[]; -}; - -static inline void compat_release_entry(struct compat_arpt_entry *e) -{ - struct xt_entry_target *t; - - t = compat_arpt_get_target(e); - module_put(t->u.kernel.target->me); -} - -static int -check_compat_entry_size_and_hooks(struct compat_arpt_entry *e, - struct xt_table_info *newinfo, - unsigned int *size, - const unsigned char *base, - const unsigned char *limit) -{ - struct xt_entry_target *t; - struct xt_target *target; - unsigned int entry_offset; - int ret, off; - - if ((unsigned long)e % __alignof__(struct compat_arpt_entry) != 0 || - (unsigned char *)e + sizeof(struct compat_arpt_entry) >= limit || - (unsigned char *)e + e->next_offset > limit) - return -EINVAL; - - if (e->next_offset < sizeof(struct compat_arpt_entry) + - sizeof(struct compat_xt_entry_target)) - return -EINVAL; - - if (!arp_checkentry(&e->arp)) - return -EINVAL; - - ret = xt_compat_check_entry_offsets(e, e->elems, e->target_offset, - e->next_offset); - if (ret) - return ret; - - off = sizeof(struct arpt_entry) - sizeof(struct compat_arpt_entry); - entry_offset = (void *)e - (void *)base; - - t = compat_arpt_get_target(e); - target = xt_request_find_target(NFPROTO_ARP, t->u.user.name, - t->u.user.revision); - if (IS_ERR(target)) { - ret = PTR_ERR(target); - goto out; - } - t->u.kernel.target = target; - - off += xt_compat_target_offset(target); - *size += off; - ret = xt_compat_add_offset(NFPROTO_ARP, entry_offset, off); - if (ret) - goto release_target; - - return 0; - -release_target: - module_put(t->u.kernel.target->me); -out: - return ret; -} - -static void -compat_copy_entry_from_user(struct compat_arpt_entry *e, void **dstptr, - unsigned int *size, - struct xt_table_info *newinfo, unsigned char *base) -{ - struct xt_entry_target *t; - struct arpt_entry *de; - unsigned int origsize; - int h; - - origsize = *size; - de = *dstptr; - memcpy(de, e, sizeof(struct arpt_entry)); - memcpy(&de->counters, &e->counters, sizeof(e->counters)); - - *dstptr += sizeof(struct arpt_entry); - *size += sizeof(struct arpt_entry) - sizeof(struct compat_arpt_entry); - - de->target_offset = e->target_offset - (origsize - *size); - t = compat_arpt_get_target(e); - xt_compat_target_from_user(t, dstptr, size); - - de->next_offset = e->next_offset - (origsize - *size); - for (h = 0; h < NF_ARP_NUMHOOKS; h++) { - if ((unsigned char *)de - base < newinfo->hook_entry[h]) - newinfo->hook_entry[h] -= origsize - *size; - if ((unsigned char *)de - base < newinfo->underflow[h]) - newinfo->underflow[h] -= origsize - *size; - } -} - -static int translate_compat_table(struct net *net, - struct xt_table_info **pinfo, - void **pentry0, - const struct compat_arpt_replace *compatr) -{ - unsigned int i, j; - struct xt_table_info *newinfo, *info; - void *pos, *entry0, *entry1; - struct compat_arpt_entry *iter0; - struct arpt_replace repl; - unsigned int size; - int ret; - - info = *pinfo; - entry0 = *pentry0; - size = compatr->size; - info->number = compatr->num_entries; - - j = 0; - xt_compat_lock(NFPROTO_ARP); - ret = xt_compat_init_offsets(NFPROTO_ARP, compatr->num_entries); - if (ret) - goto out_unlock; - /* Walk through entries, checking offsets. */ - xt_entry_foreach(iter0, entry0, compatr->size) { - ret = check_compat_entry_size_and_hooks(iter0, info, &size, - entry0, - entry0 + compatr->size); - if (ret != 0) - goto out_unlock; - ++j; - } - - ret = -EINVAL; - if (j != compatr->num_entries) - goto out_unlock; - - ret = -ENOMEM; - newinfo = xt_alloc_table_info(size); - if (!newinfo) - goto out_unlock; - - memset(newinfo->entries, 0, size); - - newinfo->number = compatr->num_entries; - for (i = 0; i < NF_ARP_NUMHOOKS; i++) { - newinfo->hook_entry[i] = compatr->hook_entry[i]; - newinfo->underflow[i] = compatr->underflow[i]; - } - entry1 = newinfo->entries; - pos = entry1; - size = compatr->size; - xt_entry_foreach(iter0, entry0, compatr->size) - compat_copy_entry_from_user(iter0, &pos, &size, - newinfo, entry1); - - /* all module references in entry0 are now gone */ - - xt_compat_flush_offsets(NFPROTO_ARP); - xt_compat_unlock(NFPROTO_ARP); - - memcpy(&repl, compatr, sizeof(*compatr)); - - for (i = 0; i < NF_ARP_NUMHOOKS; i++) { - repl.hook_entry[i] = newinfo->hook_entry[i]; - repl.underflow[i] = newinfo->underflow[i]; - } - - repl.num_counters = 0; - repl.counters = NULL; - repl.size = newinfo->size; - ret = translate_table(net, newinfo, entry1, &repl); - if (ret) - goto free_newinfo; - - *pinfo = newinfo; - *pentry0 = entry1; - xt_free_table_info(info); - return 0; - -free_newinfo: - xt_free_table_info(newinfo); - return ret; -out_unlock: - xt_compat_flush_offsets(NFPROTO_ARP); - xt_compat_unlock(NFPROTO_ARP); - xt_entry_foreach(iter0, entry0, compatr->size) { - if (j-- == 0) - break; - compat_release_entry(iter0); - } - return ret; -} - -static int compat_do_replace(struct net *net, sockptr_t arg, unsigned int len) -{ - int ret; - struct compat_arpt_replace tmp; - struct xt_table_info *newinfo; - void *loc_cpu_entry; - struct arpt_entry *iter; - - if (len < sizeof(tmp)) - return -EINVAL; - if (copy_from_sockptr(&tmp, arg, sizeof(tmp)) != 0) - return -EFAULT; - - /* overflow check */ - if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters)) - return -ENOMEM; - if (tmp.num_counters == 0) - return -EINVAL; - if ((u64)len < (u64)tmp.size + sizeof(tmp)) - return -EINVAL; - - tmp.name[sizeof(tmp.name)-1] = 0; - - newinfo = xt_alloc_table_info(tmp.size); - if (!newinfo) - return -ENOMEM; - - loc_cpu_entry = newinfo->entries; - if (copy_from_sockptr_offset(loc_cpu_entry, arg, sizeof(tmp), - tmp.size) != 0) { - ret = -EFAULT; - goto free_newinfo; - } - - ret = translate_compat_table(net, &newinfo, &loc_cpu_entry, &tmp); - if (ret != 0) - goto free_newinfo; - - ret = __do_replace(net, tmp.name, tmp.valid_hooks, newinfo, - tmp.num_counters, compat_ptr(tmp.counters)); - if (ret) - goto free_newinfo_untrans; - return 0; - - free_newinfo_untrans: - xt_entry_foreach(iter, loc_cpu_entry, newinfo->size) - cleanup_entry(iter, net); - free_newinfo: - xt_free_table_info(newinfo); - return ret; -} - -static int compat_copy_entry_to_user(struct arpt_entry *e, void __user **dstptr, - compat_uint_t *size, - struct xt_counters *counters, - unsigned int i) -{ - struct xt_entry_target *t; - struct compat_arpt_entry __user *ce; - u_int16_t target_offset, next_offset; - compat_uint_t origsize; - int ret; - - origsize = *size; - ce = *dstptr; - if (copy_to_user(ce, e, offsetof(struct compat_arpt_entry, counters)) || - copy_to_user(&ce->counters, &counters[i], sizeof(counters[i]))) - return -EFAULT; - - *dstptr += sizeof(struct compat_arpt_entry); - *size -= sizeof(struct arpt_entry) - sizeof(struct compat_arpt_entry); - - target_offset = e->target_offset - (origsize - *size); - - t = arpt_get_target(e); - ret = xt_compat_target_to_user(t, dstptr, size); - if (ret) - return ret; - next_offset = e->next_offset - (origsize - *size); - if (put_user(target_offset, &ce->target_offset) != 0 || - put_user(next_offset, &ce->next_offset) != 0) - return -EFAULT; - return 0; -} - -static int compat_copy_entries_to_user(unsigned int total_size, - struct xt_table *table, - void __user *userptr) -{ - struct xt_counters *counters; - const struct xt_table_info *private = table->private; - void __user *pos; - unsigned int size; - int ret = 0; - unsigned int i = 0; - struct arpt_entry *iter; - - counters = alloc_counters(table); - if (IS_ERR(counters)) - return PTR_ERR(counters); - - pos = userptr; - size = total_size; - xt_entry_foreach(iter, private->entries, total_size) { - ret = compat_copy_entry_to_user(iter, &pos, - &size, counters, i++); - if (ret != 0) - break; - } - vfree(counters); - return ret; -} - -struct compat_arpt_get_entries { - char name[XT_TABLE_MAXNAMELEN]; - compat_uint_t size; - struct compat_arpt_entry entrytable[]; -}; - -static int compat_get_entries(struct net *net, - struct compat_arpt_get_entries __user *uptr, - int *len) -{ - int ret; - struct compat_arpt_get_entries get; - struct xt_table *t; - - if (*len < sizeof(get)) - return -EINVAL; - if (copy_from_user(&get, uptr, sizeof(get)) != 0) - return -EFAULT; - if (*len != sizeof(struct compat_arpt_get_entries) + get.size) - return -EINVAL; - - get.name[sizeof(get.name) - 1] = '\0'; - - xt_compat_lock(NFPROTO_ARP); - t = xt_find_table_lock(net, NFPROTO_ARP, get.name); - if (!IS_ERR(t)) { - const struct xt_table_info *private = t->private; - struct xt_table_info info; - - ret = compat_table_info(private, &info); - if (!ret && get.size == info.size) { - ret = compat_copy_entries_to_user(private->size, - t, uptr->entrytable); - } else if (!ret) - ret = -EAGAIN; - - xt_compat_flush_offsets(NFPROTO_ARP); - module_put(t->me); - xt_table_unlock(t); - } else - ret = PTR_ERR(t); - - xt_compat_unlock(NFPROTO_ARP); - return ret; -} -#endif - static int do_arpt_set_ctl(struct sock *sk, int cmd, sockptr_t arg, unsigned int len) { @@ -1432,12 +981,7 @@ static int do_arpt_set_ctl(struct sock *sk, int cmd, sockptr_t arg, switch (cmd) { case ARPT_SO_SET_REPLACE: -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT - if (in_compat_syscall()) - ret = compat_do_replace(sock_net(sk), arg, len); - else -#endif - ret = do_replace(sock_net(sk), arg, len); + ret = do_replace(sock_net(sk), arg, len); break; case ARPT_SO_SET_ADD_COUNTERS: @@ -1466,12 +1010,7 @@ static int do_arpt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len break; case ARPT_SO_GET_ENTRIES: -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT - if (in_compat_syscall()) - ret = compat_get_entries(sock_net(sk), user, len); - else -#endif - ret = get_entries(sock_net(sk), user, len); + ret = get_entries(sock_net(sk), user, len); break; case ARPT_SO_GET_REVISION_TARGET: { @@ -1568,11 +1107,6 @@ static struct xt_target arpt_builtin_tg[] __read_mostly = { .name = XT_STANDARD_TARGET, .targetsize = sizeof(int), .family = NFPROTO_ARP, -#ifdef CONFIG_NETFILTER_XTABLES_COMPAT - .compatsize = sizeof(compat_int_t), - .compat_from_user = compat_standard_from_user, - .compat_to_user = compat_standard_to_user, -#endif }, { .name = XT_ERROR_TARGET, diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index 4c04cd8d40a2..09874c26fd13 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig @@ -739,7 +739,7 @@ config NETFILTER_XTABLES_COMPAT bool "Netfilter Xtables 32bit support" depends on COMPAT help - This option provides a translation layer to run 32bit arp,ip(6),ebtables + This option provides a translation layer to run 32bit ip(6),ebtables binaries on 64bit kernels. If unsure, say N. From da4afc5a956d407443988e97a4d4ca14c2e999c7 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Tue, 25 Aug 2026 15:11:24 +0200 Subject: [PATCH 1092/2366] netfilter: ip6_tables: set F_PROTO when proto value is nonzero The ip6tables traverser doesn't search the extension header chain unless userspace did set the IP6T_F_PROTO flag. This also means that userspace that sets the e->ipv6.proto flag can bypass the protocol check for the rule by not setting this flag. That in turn means that all ip6_tables modules and targets that want to reject rules without '-p' flag MUST also check for that flag. Not all do, likely because they got copied from iptables which lacks this flag (no extension headers). Instead of fixing up all the relevant targets, emulate ip6tables behaviour in the kernel (like nft_compat.c) and set the flag if the protocol is set. Reported-by: Zhiling Zou Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/ipv6/netfilter/ip6_tables.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c index f42fb96ef64b..313c4aac377a 100644 --- a/net/ipv6/netfilter/ip6_tables.c +++ b/net/ipv6/netfilter/ip6_tables.c @@ -647,6 +647,11 @@ check_entry_size_and_hooks(struct ip6t_entry *e, /* Clear counters and comefrom */ e->counters = ((struct xt_counters) { 0, 0 }); e->comefrom = 0; + + /* set F_PROTO, else ip6_packet_match won't do the right thing. */ + if (e->ipv6.proto) + e->ipv6.flags |= IP6T_F_PROTO; + return 0; } From 7a099b347fef536a84068076e2d384f044e5cfc5 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Tue, 25 Aug 2026 17:27:24 +0200 Subject: [PATCH 1093/2366] netfilter: report NLM_F_DUMP_FILTERED when all is filtered out NLM_F_DUMP_FILTERED is only set on data elements in the conntrack dump. But when everything is filtered out it is confusing for the user space, since the flag is not reported anymore and it looks like the table was empty, which may or may not be the case. 'answer_flags' were introduced precisely for this use case, and the conntrack dump should set the flag in there in case the filtering was applied. This is important, for example, to be able to tell if the filters are supported or not by the kernel without modifying the kernel state. With the proper reporting of NLM_F_DUMP_FILTERED on NLMSG_DONE, an application in user space can just try and dump with an arbitrary filter without worrying that there could be no matching entry. The reported flag will signal that the filtering was applied and therefore supported. Fixes: cb8aa9a3affb ("netfilter: ctnetlink: add kernel side filtering for dump") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conntrack_netlink.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index 9b4e29557ec3..579ada063b1b 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -1077,6 +1077,8 @@ static int ctnetlink_start(struct netlink_callback *cb) } cb->data = filter; + if (filter) + cb->answer_flags = NLM_F_DUMP_FILTERED; return 0; } From f3e6ef13e24c9f26dca0d35de57fcdf04f78e378 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Fri, 4 Sep 2026 19:56:24 +0900 Subject: [PATCH 1094/2366] regulator: pf1550: fix which regulator is notified The interrupt handler distinguishes the rail that reported the fault, but the body ignores it. Every SW interrupt walks the regulator array looking for the name "SW3" and every LDO interrupt looks for "LDO3", so an over-current on SW1 is reported to the consumers of SW3 while the consumers of SW1 hear nothing. The lookup itself is unreliable as well. rdev_get_name() returns the device tree regulator-name property whenever the board supplies one, and only falls back to the name in the driver descriptor when it does not. The binding example for this device sets regulator-name to "sw3" and "ldo3", which strcmp() does not match against the upper case literals used here, so a board that follows the documentation gets no over-current notification at all. A board that names its rails after the schematic does not match either. No other driver in the tree selects a notification target this way. Replace the name lookup with rdev_get_id(), which returns the descriptor id set by the driver and cannot be overridden from the device tree, and take both the id and the event from a table indexed by the interrupt. The die temperature interrupts keep notifying every regulator since they report a chip wide condition. Fixes: 7320d41c29bb ("regulator: pf1550: Add support for regulator") Signed-off-by: Donggeun Yoo Link: https://patch.msgid.link/20260904105624.48577-1-donggeunyoo.kernel@gmail.com Signed-off-by: Mark Brown --- drivers/regulator/pf1550-regulator.c | 82 ++++++++++++++-------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/drivers/regulator/pf1550-regulator.c b/drivers/regulator/pf1550-regulator.c index 610eac9bb9cb..ceee553a84b2 100644 --- a/drivers/regulator/pf1550-regulator.c +++ b/drivers/regulator/pf1550-regulator.c @@ -283,63 +283,61 @@ static struct pf1550_desc pf1550_regulators[] = { PF_LDO1(PF1550, "ldo3", LDO3, 0x1f, pf1550_ldo13_volts), }; +/* + * The _LS interrupts indicate an over-current event. The _HS + * interrupts, which are more accurate and can detect catastrophic + * faults, issue an error event. The current limit FAULT interrupt is + * similar to the _HS. + */ +static const struct pf1550_regulator_irq { + unsigned int event; + u8 id; +} pf1550_regulator_irqs[] = { + [PF1550_PMIC_IRQ_SW1_LS] = { REGULATOR_EVENT_OVER_CURRENT_WARN, PF1550_SW1 }, + [PF1550_PMIC_IRQ_SW2_LS] = { REGULATOR_EVENT_OVER_CURRENT_WARN, PF1550_SW2 }, + [PF1550_PMIC_IRQ_SW3_LS] = { REGULATOR_EVENT_OVER_CURRENT_WARN, PF1550_SW3 }, + [PF1550_PMIC_IRQ_SW1_HS] = { REGULATOR_EVENT_OVER_CURRENT, PF1550_SW1 }, + [PF1550_PMIC_IRQ_SW2_HS] = { REGULATOR_EVENT_OVER_CURRENT, PF1550_SW2 }, + [PF1550_PMIC_IRQ_SW3_HS] = { REGULATOR_EVENT_OVER_CURRENT, PF1550_SW3 }, + [PF1550_PMIC_IRQ_LDO1_FAULT] = { REGULATOR_EVENT_OVER_CURRENT, PF1550_LDO1 }, + [PF1550_PMIC_IRQ_LDO2_FAULT] = { REGULATOR_EVENT_OVER_CURRENT, PF1550_LDO2 }, + [PF1550_PMIC_IRQ_LDO3_FAULT] = { REGULATOR_EVENT_OVER_CURRENT, PF1550_LDO3 }, +}; + static irqreturn_t pf1550_regulator_irq_handler(int irq, void *data) { + const struct pf1550_regulator_irq *map; struct pf1550_regulator_info *info = data; struct device *dev = info->dev; struct platform_device *pdev = to_platform_device(dev); int i, irq_type = -1; - unsigned int event; for (i = 0; i < PF1550_REGULATOR_IRQ_NR; i++) if (irq == platform_get_irq(pdev, i)) irq_type = i; - switch (irq_type) { - /* The _LS interrupts indicate over-current event. The _HS interrupts - * which are more accurate and can detect catastrophic faults, issue - * an error event. The current limit FAULT interrupt is similar to the - * _HS' - */ - case PF1550_PMIC_IRQ_SW1_LS: - case PF1550_PMIC_IRQ_SW2_LS: - case PF1550_PMIC_IRQ_SW3_LS: - event = REGULATOR_EVENT_OVER_CURRENT_WARN; - for (i = 0; i < PF1550_MAX_REGULATOR; i++) - if (!strcmp(rdev_get_name(info->rdevs[i]), "SW3")) - regulator_notifier_call_chain(info->rdevs[i], - event, NULL); - break; - case PF1550_PMIC_IRQ_SW1_HS: - case PF1550_PMIC_IRQ_SW2_HS: - case PF1550_PMIC_IRQ_SW3_HS: - event = REGULATOR_EVENT_OVER_CURRENT; - for (i = 0; i < PF1550_MAX_REGULATOR; i++) - if (!strcmp(rdev_get_name(info->rdevs[i]), "SW3")) - regulator_notifier_call_chain(info->rdevs[i], - event, NULL); - break; - case PF1550_PMIC_IRQ_LDO1_FAULT: - case PF1550_PMIC_IRQ_LDO2_FAULT: - case PF1550_PMIC_IRQ_LDO3_FAULT: - event = REGULATOR_EVENT_OVER_CURRENT; - for (i = 0; i < PF1550_MAX_REGULATOR; i++) - if (!strcmp(rdev_get_name(info->rdevs[i]), "LDO3")) - regulator_notifier_call_chain(info->rdevs[i], - event, NULL); - break; - case PF1550_PMIC_IRQ_TEMP_110: - case PF1550_PMIC_IRQ_TEMP_125: - event = REGULATOR_EVENT_OVER_TEMP; + /* The die temperature concerns every rail. */ + if (irq_type == PF1550_PMIC_IRQ_TEMP_110 || + irq_type == PF1550_PMIC_IRQ_TEMP_125) { for (i = 0; i < PF1550_MAX_REGULATOR; i++) regulator_notifier_call_chain(info->rdevs[i], - event, NULL); - break; - default: - dev_err(dev, "regulator interrupt: irq %d occurred\n", - irq_type); + REGULATOR_EVENT_OVER_TEMP, + NULL); + return IRQ_HANDLED; } + if (irq_type < 0 || irq_type >= (int)ARRAY_SIZE(pf1550_regulator_irqs)) { + dev_err(dev, "regulator interrupt: irq %d occurred\n", irq_type); + return IRQ_HANDLED; + } + + map = &pf1550_regulator_irqs[irq_type]; + + for (i = 0; i < PF1550_MAX_REGULATOR; i++) + if (rdev_get_id(info->rdevs[i]) == map->id) + regulator_notifier_call_chain(info->rdevs[i], + map->event, NULL); + return IRQ_HANDLED; } From 9c3882ec10399c14c59b7e4599d33c4395367c37 Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:32:10 +0800 Subject: [PATCH 1095/2366] ASoC: mt6351: Publish the OF module alias The MT6351 codec platform driver uses mt6351_of_match to bind devices with compatible mediatek,mt6351-sound. The codec can be a separate module, but the OF table is not exported to module alias metadata. Publish the existing table without changing codec matching, register access or the machine-driver configuration. Fixes: a74d51ba0e17 ("ASoC: add mt6351 codec driver") Signed-off-by: hpp.iscas Link: https://patch.msgid.link/20260905133210.63803-1-hppiscas@163.com Signed-off-by: Mark Brown --- sound/soc/codecs/mt6351.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/mt6351.c b/sound/soc/codecs/mt6351.c index 1768c249650d..3be0491a2588 100644 --- a/sound/soc/codecs/mt6351.c +++ b/sound/soc/codecs/mt6351.c @@ -1478,6 +1478,7 @@ static const struct of_device_id mt6351_of_match[] = { {.compatible = "mediatek,mt6351-sound",}, {} }; +MODULE_DEVICE_TABLE(of, mt6351_of_match); static struct platform_driver mt6351_codec_driver = { .driver = { From 1017911fcc03584b6854b1b8f0aafeb25f5a8d25 Mon Sep 17 00:00:00 2001 From: Sascha Bischoff Date: Mon, 7 Sep 2026 16:49:50 +0000 Subject: [PATCH 1096/2366] irqchip/gic-v5: Preserve ICC_CR0_EL1 state In addition to EN, ICC_CR0_EL1 contains other fields, such as LINK and LINK_IDLE. The driver only needs to modify EN, and must preserve the values of all other fields when enabling or disabling the CPU interface. Define the missing LINK and LINK_IDLE fields, and use read-modify-write accesses to update EN without affecting the rest of ICC_CR0_EL1. Fixes: 7ec80fb3f025 ("irqchip/gic-v5: Add GICv5 PPI support") Reported-by: Sashiko Signed-off-by: Sascha Bischoff Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260907164945.714545-1-sascha.bischoff@arm.com Closes: https://lore.kernel.org/r/20260807121703.D4B7A1F00A3A@smtp.kernel.org --- arch/arm64/tools/sysreg | 4 +++- drivers/irqchip/irq-gic-v5.c | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg index 94bf065c8ac7..e2d37ee221b8 100644 --- a/arch/arm64/tools/sysreg +++ b/arch/arm64/tools/sysreg @@ -3761,7 +3761,9 @@ Sysreg ICC_CR0_EL1 3 1 12 0 1 Res0 63:39 Field 38 PID Field 37:32 IPPT -Res0 31:1 +Res0 31:3 +Field 2 LINK_IDLE +Field 1 LINK Field 0 EN EndSysreg diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c index ac2d423b1723..5f2551cf077d 100644 --- a/drivers/irqchip/irq-gic-v5.c +++ b/drivers/irqchip/irq-gic-v5.c @@ -974,7 +974,8 @@ static void gicv5_cpu_disable_interrupts(void) { u64 cr0; - cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 0); + cr0 = read_sysreg_s(SYS_ICC_CR0_EL1); + cr0 &= ~ICC_CR0_EL1_EN_MASK; write_sysreg_s(cr0, SYS_ICC_CR0_EL1); isb(); } @@ -991,7 +992,8 @@ static void gicv5_cpu_enable_interrupts(void) pcr = FIELD_PREP(ICC_PCR_EL1_PRIORITY, GICV5_IRQ_PRI_MI); write_sysreg_s(pcr, SYS_ICC_PCR_EL1); - cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 1); + cr0 = read_sysreg_s(SYS_ICC_CR0_EL1); + cr0 |= ICC_CR0_EL1_EN_MASK; write_sysreg_s(cr0, SYS_ICC_CR0_EL1); } From 22728415a9cc8da859c7b95b49eb405fb3debd6a Mon Sep 17 00:00:00 2001 From: Aaron Welwood Date: Sun, 6 Sep 2026 21:17:38 -0600 Subject: [PATCH 1097/2366] ASoC: amd: yc: add quirk for Acer Nitro AN17-41 internal mic The Acer Nitro AN17-41 uses "RB" as its board vendor and has no entry in yc_acp_quirk_table, so acp6x_probe() finds no DMI match, registers no card, and the internal digital microphone records only silence. Add a quirk entry for it so the DMIC is enabled. Signed-off-by: Aaron Welwood Link: https://patch.msgid.link/20260907031738.17257-1-abwelwood@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index c64c727e1034..385fede6d77f 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -388,6 +388,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "RB"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN17-41"), + } + }, { .driver_data = &acp6x_card, .matches = { From 75d276e5bb68778b2916f98a2bc30f142ebadc64 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Mon, 17 Aug 2026 22:32:29 +0000 Subject: [PATCH 1098/2366] virtio_ring: fix stale descriptor flags after a failed packed add In a packed ring the AVAIL and USED bits sit in the descriptor itself, so writing them makes that descriptor available. Those bit combinations flip meaning on every round of the ring, tracked by a wrap counter, so invalidating or validating a descriptor means inverting both bits. Commit 1ce9e6055fa0 ("virtio_ring: introduce packed ring support") has virtqueue_add_packed() make every descriptor of a chain available as it maps the chain, and write the head last. The device consumes the ring in order and stops at a head that is not available yet, so it never reaches the rest. When vring_map_one_sg() fails partway, unmap_release unmaps the segments and restores avail_used_flags, but the descriptors it wrote to in the ring stay marked with AVAIL and USED bits. The head is now the only entry that keeps the device from consuming these stale entries. For example, the ring would look like this now. Z - pre-previous command A - previous command B - aborted command C - current command [A1 DONE] [A2 DONE] [B2] [B3] [Z1 DONE] When the driver now attempts to issue the C command, the next add starts at the same head as B. If C spans less descriptors than B, there is no end marker because AVAIL and USED bits were still in place. And that means the device will start interpreting these stale entries (B2/B3) as another command entry, which then blocks the queue. This effect typically happens in swiotlb configurations under memory pressure, because vring_map_one_sg() can then fail with larger I/O requests which then leads to command abortions. There are broadly 2 ways to avoid leaving those flags behind: 1) Defer those flags too until the chain is complete. 2) Rewrite those flags for the previous wrap counter. Implement the second option in both packed add paths. The first option traverses the chain a second time on every successful add. The second option invalidates all added descriptors when any add fails. With this patch applied, a packed virtqueue keeps completing requests after a failed add. Fixes: 1ce9e6055fa0 ("virtio_ring: introduce packed ring support") Fixes: f6a15d854986 ("virtio_ring: add in order support") Assisted-by: Kiro:claude-opus-5 checkpatch sparse Signed-off-by: Alexander Graf Signed-off-by: Michael S. Tsirkin Message-ID: <20260817223229.28954-1-graf@amazon.com> --- drivers/virtio/virtio_ring.c | 38 ++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index 5c169fbb418a..db678f5a80e0 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -1670,7 +1670,7 @@ static inline int virtqueue_add_packed(struct vring_virtqueue *vq, struct scatterlist *sg; unsigned int i, n, c, descs_used, err_idx, len; __le16 head_flags, flags; - u16 head, id, prev, curr, avail_used_flags; + u16 head, id, prev, curr, avail_used_flags, unpub_flags; int err; START_USE(vq); @@ -1798,15 +1798,30 @@ static inline int virtqueue_add_packed(struct vring_virtqueue *vq, curr = vq->free_head; vq->packed.avail_used_flags = avail_used_flags; + unpub_flags = avail_used_flags ^ (1 << VRING_PACKED_DESC_F_AVAIL | + 1 << VRING_PACKED_DESC_F_USED); for (n = 0; n < total_sg; n++) { if (i == err_idx) break; + /* + * The mapping loop made every descriptor but the head + * available. Stamp the previous wrap counter's AVAIL and USED + * bits on those, so that a later and shorter chain at this head + * does not leave one of them available beyond its own last + * descriptor. Marking them used instead would hand + * is_used_desc_packed() a completion we never made. + */ + if (i != head) + desc[i].flags = cpu_to_le16(unpub_flags); vring_unmap_extra_packed(vq, &vq->packed.desc_extra[curr]); curr = vq->packed.desc_extra[curr].next; i++; - if (i >= vq->packed.vring.num) + if (i >= vq->packed.vring.num) { i = 0; + unpub_flags ^= 1 << VRING_PACKED_DESC_F_AVAIL | + 1 << VRING_PACKED_DESC_F_USED; + } } END_USE(vq); @@ -1828,7 +1843,7 @@ static inline int virtqueue_add_packed_in_order(struct vring_virtqueue *vq, struct scatterlist *sg; unsigned int i, n, sg_count, err_idx, total_in_len = 0; __le16 head_flags, flags; - u16 head, avail_used_flags; + u16 head, avail_used_flags, unpub_flags; bool avail_wrap_counter; int err; @@ -1955,14 +1970,29 @@ static inline int virtqueue_add_packed_in_order(struct vring_virtqueue *vq, i = head; vq->packed.avail_used_flags = avail_used_flags; vq->packed.avail_wrap_counter = avail_wrap_counter; + unpub_flags = avail_used_flags ^ (1 << VRING_PACKED_DESC_F_AVAIL | + 1 << VRING_PACKED_DESC_F_USED); for (n = 0; n < total_sg; n++) { if (i == err_idx) break; + /* + * The mapping loop made every descriptor but the head + * available. Stamp the previous wrap counter's AVAIL and USED + * bits on those, so that a later and shorter chain at this head + * does not leave one of them available beyond its own last + * descriptor. Marking them used instead would hand + * is_used_desc_packed() a completion we never made. + */ + if (i != head) + desc[i].flags = cpu_to_le16(unpub_flags); vring_unmap_extra_packed(vq, &vq->packed.desc_extra[i]); i++; - if (i >= vq->packed.vring.num) + if (i >= vq->packed.vring.num) { i = 0; + unpub_flags ^= 1 << VRING_PACKED_DESC_F_AVAIL | + 1 << VRING_PACKED_DESC_F_USED; + } } END_USE(vq); From 3f9a0fceb730f5107d52421ead5568eae25a0049 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 21 Aug 2026 23:39:53 +0200 Subject: [PATCH 1099/2366] virtio: fix use-after-free in unregister_virtio_device() device_unregister() is device_del() plus put_device(). When the caller holds no extra reference, that drops the last one and runs the release callback, which for several transports frees the memory the embedded struct virtio_device sits in. unregister_virtio_device() then calls virtio_debug_device_exit(), which reads dev->debugfs_dir out of the freed object. Affected transports are the ones whose release callback frees and whose remove path takes no reference: virtio_mmio, virtio_vdpa, virtio_uml, mlxbf-tmfifo and virtio_ccw. virtio_pci is unaffected because virtio_pci_remove() brackets the call with get_device() and put_device(). Remove the debugfs entries before the device can go away. They are only accessed through the protected debugfs interface, so debugfs_remove_recursive() waits for in-progress file operations before returning. Tearing them down while the device is still alive is therefore safe. Reproduced on User-Mode Linux with CONFIG_KASAN and CONFIG_VIRTIO_DEBUG by unbinding a virtio-uml device: BUG: KASAN: slab-use-after-free in virtio_debug_device_exit+0x36/0x4d Read of size 8 at addr 00000000616e0b10 by task init/1 __asan_report_load8_noabort virtio_debug_device_exit+0x36/0x4d unregister_virtio_device+0x48/0x75 virtio_uml_remove platform_remove device_release_driver_internal unbind_store Freed by task 1: kfree virtio_uml_release_dev device_release kobject_put put_device device_unregister With this applied, the report is gone and unbind is clean. Fixes: 96a8326d69ff ("virtio: add debugfs infrastructure to allow to debug virtio features") Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260821213953.76906-1-kmehltretter@gmail.com> --- drivers/virtio/virtio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/virtio/virtio.c b/drivers/virtio/virtio.c index 75bb4ffe3b87..b6c9e927bef5 100644 --- a/drivers/virtio/virtio.c +++ b/drivers/virtio/virtio.c @@ -604,8 +604,8 @@ void unregister_virtio_device(struct virtio_device *dev) { int index = dev->index; /* save for after device release */ - device_unregister(&dev->dev); virtio_debug_device_exit(dev); + device_unregister(&dev->dev); ida_free(&virtio_index_ida, index); } EXPORT_SYMBOL_GPL(unregister_virtio_device); From 894f98e73983f37354214a89a3a7fd35bf9e3072 Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Wed, 19 Aug 2026 10:12:30 +0800 Subject: [PATCH 1100/2366] virtio_console: do not free control-out buffers on remove __send_control_msg() publishes &portdev->cpkt as the control-out virtqueue cookie. remove_vqs() walks every virtqueue and passes leftover cookies to free_buf(), which treats them as struct port_buffer and reads sgpages. If a control message is still on c_ovq when the device is unbound, free_buf() reads past the ports_device object. KASAN reported slab-out-of-bounds in free_buf(): free_buf remove_vqs virtcons_remove unbind_store The object was the ports_device allocated in virtcons_probe(). Drain c_ovq without freeing. The packet lives in portdev and is released with it. Fixes: a7a69ec0d8e4 ("virtio_console: free buffers after reset") Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260819021230.292696-1-physicalmtea@gmail.com> --- drivers/char/virtio_console.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 7f6cbe851d1e..019bcae81af5 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -1964,13 +1964,28 @@ static const struct file_operations portdev_fops = { static void remove_vqs(struct ports_device *portdev) { struct virtqueue *vq; + bool multiport = use_multiport(portdev); virtio_device_for_each_vq(portdev->vdev, vq) { struct port_buffer *buf; + unsigned int len; - flush_bufs(vq, true); - while ((buf = virtqueue_detach_unused_buf(vq))) - free_buf(buf, true); + /* + * c_ovq cookies are &portdev->cpkt, not port_buffer. + * Detach them but do not free_buf(). + */ + if (multiport && vq == portdev->c_ovq) { + spin_lock(&portdev->c_ovq_lock); + while (virtqueue_get_buf(vq, &len)) + ; + while (virtqueue_detach_unused_buf(vq)) + ; + spin_unlock(&portdev->c_ovq_lock); + } else { + flush_bufs(vq, true); + while ((buf = virtqueue_detach_unused_buf(vq))) + free_buf(buf, true); + } cond_resched(); } portdev->vdev->config->del_vqs(portdev->vdev); From ccb1dc7c527f8c925925cf92afc76ae590dac311 Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Mon, 10 Aug 2026 09:03:00 +0800 Subject: [PATCH 1101/2366] vhost/vdpa: reject VRING_NUM larger than device max vhost_vring_set_num() accepts any non-zero power-of-two queue size that fits in 16 bits. vhost-vdpa then passes that value to set_vq_num() without comparing it with get_vq_num_max(). A process with access to /dev/vhost-vdpa-* can therefore configure a queue larger than the device advertises. With vdpa_sim, the worker can walk descriptors beyond the mapped descriptor ring. KASAN reports a 16-byte out-of-bounds read, corresponding to one vring_desc, in the vringh IOTLB path: BUG: KASAN: out-of-bounds in _copy_from_iter Read of size 16 copy_from_iotlb copydesc_iotlb vringh_getdesc_iotlb vdpasim_net_work Cache get_vq_num_max() immediately after reset. Some backends derive it from writable queue-size state, so querying it after SET_NUM may return the current size instead of the device capability. Invalidate the cached value before reset so a failed reset leaves SET_NUM disabled. For VHOST_SET_VRING_NUM, copy the complete vring state once and use the same index and size for validation, vq->num, and set_vq_num(). This ensures that validation and use operate on the same copied values. Fixes: 4c8cf31885f6 ("vhost: introduce vDPA-based backend") Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260810010300.132959-1-physicalmtea@gmail.com> --- drivers/vhost/vdpa.c | 44 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index c3d913bd7cac..4eb1eb5e5c79 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -58,6 +58,7 @@ struct vhost_vdpa { struct cdev cdev; atomic_t opened; u32 nvqs; + u16 vq_num_max; int virtio_id; int minor; struct eventfd_ctx *config_ctx; @@ -236,7 +237,9 @@ static void vhost_vdpa_unsetup_vq_irq(struct vhost_vdpa *v, u16 qid) static int _compat_vdpa_reset(struct vhost_vdpa *v) { struct vdpa_device *vdpa = v->vdpa; + const struct vdpa_config_ops *ops = vdpa->config; u32 flags = 0; + int ret; v->suspended = false; @@ -246,7 +249,14 @@ static int _compat_vdpa_reset(struct vhost_vdpa *v) VDPA_RESET_F_CLEAN_MAP : 0; } - return vdpa_reset(vdpa, flags); + v->vq_num_max = 0; + ret = vdpa_reset(vdpa, flags); + if (!ret) { + /* Some backends derive the max from mutable queue state. */ + v->vq_num_max = ops->get_vq_num_max(vdpa); + } + + return ret; } static int vhost_vdpa_reset(struct vhost_vdpa *v) @@ -648,9 +658,15 @@ static long vhost_vdpa_vring_ioctl(struct vhost_vdpa *v, unsigned int cmd, u32 idx; long r; - r = get_user(idx, (u32 __user *)argp); - if (r < 0) - return r; + if (cmd == VHOST_SET_VRING_NUM) { + if (copy_from_user(&s, argp, sizeof(s))) + return -EFAULT; + idx = s.index; + } else { + r = get_user(idx, (u32 __user *)argp); + if (r < 0) + return r; + } if (idx >= v->nvqs) return -ENOBUFS; @@ -659,6 +675,23 @@ static long vhost_vdpa_vring_ioctl(struct vhost_vdpa *v, unsigned int cmd, vq = &v->vqs[idx]; switch (cmd) { + case VHOST_SET_VRING_NUM: + mutex_lock(&vq->mutex); + if (vq->private_data) { + r = -EBUSY; + } else if (!s.num || s.num > 0xffff || + s.num > v->vq_num_max || + (s.num & (s.num - 1))) { + r = -EINVAL; + } else { + vq->num = s.num; + r = 0; + } + mutex_unlock(&vq->mutex); + if (r) + return r; + ops->set_vq_num(vdpa, idx, s.num); + return 0; case VHOST_VDPA_SET_VRING_ENABLE: if (copy_from_user(&s, argp, sizeof(s))) return -EFAULT; @@ -772,9 +805,6 @@ static long vhost_vdpa_vring_ioctl(struct vhost_vdpa *v, unsigned int cmd, ops->set_vq_cb(vdpa, idx, &cb); break; - case VHOST_SET_VRING_NUM: - ops->set_vq_num(vdpa, idx, vq->num); - break; } return r; From e74a9fa50749b9940b4fb13199652325e08d3c4a Mon Sep 17 00:00:00 2001 From: Yu Zhang Date: Fri, 7 Aug 2026 20:00:24 +1000 Subject: [PATCH 1102/2366] vhost-vdpa: don't install the eventfd_ctx_fdget() error in config_ctx vhost_vdpa_set_config_call() swaps the eventfd_ctx_fdget() return value into v->config_ctx before checking it, so on failure the field briefly holds an ERR_PTR: ctx = fd == VHOST_FILE_UNBIND ? NULL : eventfd_ctx_fdget(fd); swap(ctx, v->config_ctx); if (!IS_ERR_OR_NULL(ctx)) eventfd_ctx_put(ctx); if (IS_ERR(v->config_ctx)) { long ret = PTR_ERR(v->config_ctx); v->config_ctx = NULL; return ret; } Commit 0bde59c1723a ("vhost-vdpa: set v->config_ctx to NULL if eventfd_ctx_fdget() fails") added that clearing, and spelled out the invariant the rest of the file relies on: "we consider 'v->config_ctx' valid if it is not NULL". The window between the swap and the clearing still breaks it. vhost_vdpa_config_cb() only tests for NULL, so a config interrupt delivered inside the window hands the ERR_PTR to eventfd_signal(). Check the fd before installing it instead. That closes the window and matches how vhost_vring_ioctl() handles the same failure for the vq call fd. It also stops a rejected fd from tearing down a config interrupt that was working: until now the swap replaced the live context and put it, so after an EBADF the device silently stopped delivering config interrupts until userspace installed a new fd. Fixes: 776f395004d8 ("vhost_vdpa: Support config interrupt in vdpa") Signed-off-by: Yu Zhang Signed-off-by: Michael S. Tsirkin Message-ID: <20260807100025.19750-2-yuz08559@gmail.com> --- drivers/vhost/vdpa.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index 4eb1eb5e5c79..3e5165b7c094 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -546,18 +546,14 @@ static long vhost_vdpa_set_config_call(struct vhost_vdpa *v, u32 __user *argp) return -EFAULT; ctx = fd == VHOST_FILE_UNBIND ? NULL : eventfd_ctx_fdget(fd); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + swap(ctx, v->config_ctx); - if (!IS_ERR_OR_NULL(ctx)) + if (ctx) eventfd_ctx_put(ctx); - if (IS_ERR(v->config_ctx)) { - long ret = PTR_ERR(v->config_ctx); - - v->config_ctx = NULL; - return ret; - } - v->vdpa->config->set_config_cb(v->vdpa, &cb); return 0; From 62be4e3e5f5f947fbf765b914cebdc478f715d12 Mon Sep 17 00:00:00 2001 From: Yu Zhang Date: Fri, 7 Aug 2026 20:00:25 +1000 Subject: [PATCH 1103/2366] vhost-vdpa: protect config_ctx from being freed under the config callback vhost_vdpa_config_cb() loads v->config_ctx and signals it without taking a reference and without holding any lock: struct eventfd_ctx *config_ctx = v->config_ctx; if (config_ctx) eventfd_signal(config_ctx); VHOST_VDPA_SET_CONFIG_CALL replaces that field and drops what is normally the last reference to the old context: swap(ctx, v->config_ctx); if (ctx) eventfd_ctx_put(ctx); eventfd_ctx_put() drops the last kref and frees the context immediately, with no RCU grace period, so a callback that has already loaded the pointer goes on to dereference freed memory. The two sides share no lock: the ioctl runs under vhost_dev.mutex, while the parent invokes the callback from its own interrupt or workqueue context. This is not the reopen refcount underflow fixed by commit f6bbf0010ba0 ("vhost-vdpa: fix use-after-free of v->config_ctx"), which was about vhost_vdpa_config_put() leaving a stale pointer behind. Here the pointer is maintained correctly and it is the read side that is unprotected. With VDUSE as the parent this is reachable from userspace with access to /dev/vduse (root by default). VDUSE_DEV_INJECT_CONFIG_IRQ queues dev->inject, and vduse_dev_irq_inject() runs the callback under VDUSE's own dev->irq_lock, which vhost does not hold. vduse_dev_reset() does flush_work(&dev->inject), but VHOST_VDPA_SET_CONFIG_CALL never goes through reset, so an inject already in flight is not waited for. A process that injects config interrupts on the VDUSE fd while another thread swaps the call fd on the vhost-vdpa fd hits it in seconds: BUG: KASAN: slab-use-after-free in native_queued_spin_lock_slowpath Read of size 4 at addr ffff888107d21808 by task kworker/u17:1/2993 Workqueue: vduse-irq vduse_dev_irq_inject Call Trace: native_queued_spin_lock_slowpath+0x97/0x5b0 _raw_spin_lock_irqsave+0xd4/0xe0 eventfd_signal_mask+0x69/0x120 vhost_vdpa_config_cb+0x34/0x50 vduse_dev_irq_inject+0x46/0x60 process_one_work+0x468/0x950 Allocated by task 2992: do_eventfd+0x50/0x200 __x64_sys_eventfd2+0x2e/0x40 Freed by task 2992: eventfd_ctx_put+0xb9/0xc0 vhost_vdpa_unlocked_ioctl+0x116c/0x2190 Add a spinlock covering every access to config_ctx, so the callback either signals a context that is still alive or observes NULL, and the put happens only once no callback can reach the old value. Clearing the parent's callback before the put would not be enough: of the in-tree set_config_cb() implementations only VDUSE takes a lock, the rest store the pointer unlocked, so that would not order against an in-flight invocation. Fixes: 776f395004d8 ("vhost_vdpa: Support config interrupt in vdpa") Signed-off-by: Yu Zhang Signed-off-by: Michael S. Tsirkin Message-ID: <20260807100025.19750-3-yuz08559@gmail.com> --- drivers/vhost/vdpa.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c index 3e5165b7c094..a31786796d4c 100644 --- a/drivers/vhost/vdpa.c +++ b/drivers/vhost/vdpa.c @@ -62,6 +62,8 @@ struct vhost_vdpa { int virtio_id; int minor; struct eventfd_ctx *config_ctx; + /* Serialises vhost_vdpa_config_cb() against config_ctx being replaced. */ + spinlock_t config_lock; int in_batch; struct vdpa_iova_range range; u32 batch_asid; @@ -195,10 +197,12 @@ static irqreturn_t vhost_vdpa_virtqueue_cb(void *private) static irqreturn_t vhost_vdpa_config_cb(void *private) { struct vhost_vdpa *v = private; - struct eventfd_ctx *config_ctx = v->config_ctx; + unsigned long flags; - if (config_ctx) - eventfd_signal(config_ctx); + spin_lock_irqsave(&v->config_lock, flags); + if (v->config_ctx) + eventfd_signal(v->config_ctx); + spin_unlock_irqrestore(&v->config_lock, flags); return IRQ_HANDLED; } @@ -528,15 +532,22 @@ static long vhost_vdpa_get_vring_num(struct vhost_vdpa *v, u16 __user *argp) static void vhost_vdpa_config_put(struct vhost_vdpa *v) { - if (v->config_ctx) { - eventfd_ctx_put(v->config_ctx); - v->config_ctx = NULL; - } + struct eventfd_ctx *ctx; + unsigned long flags; + + spin_lock_irqsave(&v->config_lock, flags); + ctx = v->config_ctx; + v->config_ctx = NULL; + spin_unlock_irqrestore(&v->config_lock, flags); + + if (ctx) + eventfd_ctx_put(ctx); } static long vhost_vdpa_set_config_call(struct vhost_vdpa *v, u32 __user *argp) { struct vdpa_callback cb; + unsigned long flags; int fd; struct eventfd_ctx *ctx; @@ -549,8 +560,14 @@ static long vhost_vdpa_set_config_call(struct vhost_vdpa *v, u32 __user *argp) if (IS_ERR(ctx)) return PTR_ERR(ctx); + spin_lock_irqsave(&v->config_lock, flags); swap(ctx, v->config_ctx); + spin_unlock_irqrestore(&v->config_lock, flags); + /* + * The callback can no longer reach the old context, so this is the + * last reference to it. + */ if (ctx) eventfd_ctx_put(ctx); @@ -1639,6 +1656,7 @@ static int vhost_vdpa_probe(struct vdpa_device *vdpa) } atomic_set(&v->opened, 0); + spin_lock_init(&v->config_lock); v->minor = minor; v->vdpa = vdpa; v->nvqs = vdpa->nvqs; From d14d693adb055e98ca705822ba6daebc18602d9a Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Wed, 5 Aug 2026 11:29:37 +0800 Subject: [PATCH 1104/2366] virtio_mmio: disable IRQ wake before free_irq When the DT node has "wakeup-source", vm_find_vqs() calls enable_irq_wake() on the shared IRQ, but vm_del_vqs() freed that IRQ without a matching disable_irq_wake(). That leaves a wake reference behind and can warn on later free_irq()/request_irq() cycles. Record whether enable_irq_wake() succeeded, and disable it in vm_del_vqs() before free_irq(). Fixes: 02213273f72a ("virtio_mmio: add support to set IRQ of a virtio device as wakeup source") Cc: stable@vger.kernel.org Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260805032937.1606737-1-xiongweimin@kylinos.cn> --- drivers/virtio/virtio_mmio.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/virtio/virtio_mmio.c b/drivers/virtio/virtio_mmio.c index 316f03b97356..faae58e3401a 100644 --- a/drivers/virtio/virtio_mmio.c +++ b/drivers/virtio/virtio_mmio.c @@ -88,6 +88,9 @@ struct virtio_mmio_device { void __iomem *base; unsigned long version; + + /* True if enable_irq_wake() succeeded for the shared IRQ. */ + bool wake_irq_enabled; }; /* Configuration interface */ @@ -336,11 +339,17 @@ static void vm_del_vqs(struct virtio_device *vdev) { struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev); struct virtqueue *vq, *n; + int irq = platform_get_irq(vm_dev->pdev, 0); list_for_each_entry_safe(vq, n, &vdev->vqs, list) vm_del_vq(vq); - free_irq(platform_get_irq(vm_dev->pdev, 0), vm_dev); + if (vm_dev->wake_irq_enabled) { + disable_irq_wake(irq); + vm_dev->wake_irq_enabled = false; + } + + free_irq(irq, vm_dev); } static void vm_synchronize_cbs(struct virtio_device *vdev) @@ -467,8 +476,9 @@ static int vm_find_vqs(struct virtio_device *vdev, unsigned int nvqs, if (err) return err; - if (of_property_read_bool(vm_dev->pdev->dev.of_node, "wakeup-source")) - enable_irq_wake(irq); + if (of_property_read_bool(vm_dev->pdev->dev.of_node, "wakeup-source") && + !enable_irq_wake(irq)) + vm_dev->wake_irq_enabled = true; for (i = 0; i < nvqs; ++i) { struct virtqueue_info *vqi = &vqs_info[i]; From 6601d5a00899e7fa7e6b2d18113cee385ed3801b Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Thu, 6 Aug 2026 08:58:09 +0800 Subject: [PATCH 1105/2366] vdpa/pds: check virtqueue notify mapping vp_modern_map_vq_notify() can fail and return NULL. Check the notify mapping while adding a pds vDPA device and use the existing teardown path instead of storing a NULL doorbell pointer in the virtqueue state. Signed-off-by: Xiong Weimin Reviewed-by: Brett Creeley Signed-off-by: Michael S. Tsirkin Message-ID: <20260806005809.1875257-1-xiongweimin@kylinos.cn> --- drivers/vdpa/pds/vdpa_dev.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/vdpa/pds/vdpa_dev.c b/drivers/vdpa/pds/vdpa_dev.c index 43426bd971ac..77d679f6763d 100644 --- a/drivers/vdpa/pds/vdpa_dev.c +++ b/drivers/vdpa/pds/vdpa_dev.c @@ -731,6 +731,12 @@ static int pds_vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name, notify = vp_modern_map_vq_notify(&pdsv->vdpa_aux->vd_mdev, i, &pdsv->vqs[i].notify_pa); + if (!notify) { + err = -EINVAL; + dev_err(dev, "Fail to map vq notify %d\n", i); + goto err_unmap; + } + pds_vdpa_init_vqs_entry(pdsv, i, notify); } From 9ab9b4f4eb4288588707ec359ac3d5b7ccf07fa6 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Tue, 4 Aug 2026 17:26:07 +0800 Subject: [PATCH 1106/2366] vdpa: alibaba: Keep DRIVER_OK clear if IRQ setup fails If requesting MSI-X interrupts fails while DRIVER_OK is being set, leave the device status unchanged instead of advertising a ready device without working interrupts. Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260804092608.1344269-1-xiongweimin@kylinos.cn> --- drivers/vdpa/alibaba/eni_vdpa.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/alibaba/eni_vdpa.c b/drivers/vdpa/alibaba/eni_vdpa.c index fd6fdba46094..1288402d3dd8 100644 --- a/drivers/vdpa/alibaba/eni_vdpa.c +++ b/drivers/vdpa/alibaba/eni_vdpa.c @@ -216,7 +216,10 @@ static void eni_vdpa_set_status(struct vdpa_device *vdpa, u8 status) if (status & VIRTIO_CONFIG_S_DRIVER_OK && !(s & VIRTIO_CONFIG_S_DRIVER_OK)) { - eni_vdpa_request_irq(eni_vdpa); + if (eni_vdpa_request_irq(eni_vdpa)) { + WARN_ON(1); + return; + } } vp_legacy_set_status(ldev, status); From e847542ab0545c73354849126150206c29d83929 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Wed, 5 Aug 2026 09:51:53 +0800 Subject: [PATCH 1107/2366] vdpa: solidrun: Free IRQs after request failure Unwind IRQs already requested by snet_request_irqs() before returning a VQ IRQ request error so a later DRIVER_OK retry starts from a clean state. The IRQs are requested and freed while the PCI device remains bound, so the driver cannot wait for devres cleanup at detach time. Fixes: 51a8f9d7f587 ("virtio: vdpa: new SolidNET DPU driver.") Cc: stable@vger.kernel.org # v6.3+ Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <178589471328.1556376.15570536900532373521@kylinos.cn> --- drivers/vdpa/solidrun/snet_main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/solidrun/snet_main.c b/drivers/vdpa/solidrun/snet_main.c index 28d55315df2a..3e2cea1e45f3 100644 --- a/drivers/vdpa/solidrun/snet_main.c +++ b/drivers/vdpa/solidrun/snet_main.c @@ -418,11 +418,15 @@ static int snet_request_irqs(struct pci_dev *pdev, struct snet *snet) snet->vqs[i]->irq_name, snet->vqs[i]); if (ret) { SNET_ERR(pdev, "Failed to request IRQ\n"); - return ret; + goto err_free_irqs; } snet->vqs[i]->irq = irq; } return 0; + +err_free_irqs: + snet_free_irqs(snet); + return ret; } static void snet_set_status(struct vdpa_device *vdev, u8 status) From 4d470be71196ca0ce302e6623454533dc31b465b Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Wed, 5 Aug 2026 09:51:52 +0800 Subject: [PATCH 1108/2366] vdpa: ifcvf: Put device on unsupported feature error Route unsupported provisioned features through the common error path after vdpa_alloc_device() so the allocated device and adapter pointer are released consistently. Fixes: 46fc0917bbab ("vDPA/ifcvf: implement features provisioning") Cc: stable@vger.kernel.org # v6.3+ Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <178589471294.1556376.4816776800128323034@kylinos.cn> --- drivers/vdpa/ifcvf/ifcvf_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/ifcvf/ifcvf_main.c b/drivers/vdpa/ifcvf/ifcvf_main.c index ab6d6ab3b3d8..2af1cec95884 100644 --- a/drivers/vdpa/ifcvf/ifcvf_main.c +++ b/drivers/vdpa/ifcvf/ifcvf_main.c @@ -724,7 +724,8 @@ static int ifcvf_vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name, if (config->device_features & ~device_features) { IFCVF_ERR(pdev, "The provisioned features 0x%llx are not supported by this device with features 0x%llx\n", config->device_features, device_features); - return -EINVAL; + ret = -EINVAL; + goto err; } device_features &= config->device_features; } From 6519ca235131c3281a83cc9e8b05af709ab98a89 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Tue, 4 Aug 2026 17:26:36 +0800 Subject: [PATCH 1109/2366] vdpa: octeon_ep: Check dev_set_name() in dev add Handle dev_set_name() failures before registering the vDPA device so allocation is unwound through the existing put_device() path. Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260804092636.1344431-1-xiongweimin@kylinos.cn> --- drivers/vdpa/octeon_ep/octep_vdpa_main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/vdpa/octeon_ep/octep_vdpa_main.c b/drivers/vdpa/octeon_ep/octep_vdpa_main.c index 23e280a29209..85a3d35ea1e4 100644 --- a/drivers/vdpa/octeon_ep/octep_vdpa_main.c +++ b/drivers/vdpa/octeon_ep/octep_vdpa_main.c @@ -600,6 +600,8 @@ static int octep_vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name, ret = dev_set_name(&vdpa_dev->dev, "%s", name); else ret = dev_set_name(&vdpa_dev->dev, "vdpa%u", vdpa_dev->index); + if (ret) + goto vdpa_dev_put; ret = _vdpa_register_device(&oct_vdpa->vdpa, oct_hw->nr_vring); if (ret) { From ca2c2165a02e499b591a367224346a7e52664d9c Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Tue, 4 Aug 2026 17:26:49 +0800 Subject: [PATCH 1110/2366] virtio-vdpa: Use queue id when setting vq affinity When optional queues are skipped, pass the compressed vDPA queue id to set_vq_affinity() so affinity is applied to the queue that was actually created. Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260804092649.1344478-1-xiongweimin@kylinos.cn> --- drivers/virtio/virtio_vdpa.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/virtio/virtio_vdpa.c b/drivers/virtio/virtio_vdpa.c index de2af696de6c..6bcf4567a929 100644 --- a/drivers/virtio/virtio_vdpa.c +++ b/drivers/virtio/virtio_vdpa.c @@ -352,7 +352,7 @@ static int virtio_vdpa_find_vqs(struct virtio_device *vdev, unsigned int nvqs, continue; } - vqs[i] = virtio_vdpa_setup_vq(vdev, queue_idx++, vqi->callback, + vqs[i] = virtio_vdpa_setup_vq(vdev, queue_idx, vqi->callback, vqi->name, vqi->ctx); if (IS_ERR(vqs[i])) { err = PTR_ERR(vqs[i]); @@ -360,7 +360,8 @@ static int virtio_vdpa_find_vqs(struct virtio_device *vdev, unsigned int nvqs, } if (has_affinity) - ops->set_vq_affinity(vdpa, i, &masks[i]); + ops->set_vq_affinity(vdpa, queue_idx, &masks[i]); + queue_idx++; } cb.callback = virtio_vdpa_config_cb; From 0a8693f00c408d85f086ad85d29e7030bf1e2055 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Tue, 1 Sep 2026 17:48:00 +0800 Subject: [PATCH 1111/2366] vdpa_sim_blk: reject out-of-range sector starts vdpasim_blk_check_range() logs an invalid start sector but continues validating the request. The subsequent unsigned capacity subtraction can underflow and let an out-of-range buffer offset reach the data path. The invalid offset is used by three request paths. VIRTIO_BLK_T_OUT copies guest data to blk->buffer + offset through vringh_iov_pull_iotlb(), causing an out-of-bounds write in _copy_from_iter() or memcpy(). VIRTIO_BLK_T_IN copies from blk->buffer + offset to the guest through vringh_iov_push_iotlb(), causing an out-of-bounds read in _copy_to_iter(). VIRTIO_BLK_T_WRITE_ZEROES passes blk->buffer + offset to memset(), causing an out-of-bounds write. Reject starts at or beyond the capacity before the subtraction. Treat the capacity boundary as invalid because the IN and OUT paths round byte counts down to sectors for validation but later copy the original byte counts. A sub-sector request at the capacity boundary would otherwise still access past the end of the buffer. I found this bug myself, though the patch was written with AI assistance. Fixes: 7d189f617f83 ("vdpa_sim_blk: implement ramdisk behaviour") Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260901094800.25475-1-linfeng.sun.dev@gmail.com> --- drivers/vdpa/vdpa_sim/vdpa_sim_blk.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim_blk.c b/drivers/vdpa/vdpa_sim/vdpa_sim_blk.c index f70f454dde8e..76dd5b0828d7 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim_blk.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim_blk.c @@ -79,10 +79,11 @@ static void vdpasim_blk_buffer_unlock(struct vdpasim_blk *blk) static bool vdpasim_blk_check_range(struct vdpasim *vdpasim, u64 start_sector, u64 num_sectors, u64 max_sectors) { - if (start_sector > VDPASIM_BLK_CAPACITY) { + if (start_sector >= VDPASIM_BLK_CAPACITY) { dev_dbg(&vdpasim->vdpa.dev, "starting sector exceeds the capacity - start: 0x%llx capacity: 0x%x\n", start_sector, VDPASIM_BLK_CAPACITY); + return false; } if (num_sectors > max_sectors) { From 0d195797a80b77f2ec56718cd26d3ee65d0093e8 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Tue, 1 Sep 2026 17:48:42 +0800 Subject: [PATCH 1112/2366] vdpa_sim_net: check TX pull result before RX copy vringh_iov_pull_iotlb() returns a signed byte count. A failed TX pull is currently added to the unsigned byte counter and then passed as a size_t length to receive_filter() and vringh_iov_push_iotlb(). A negative error can therefore become a large length in the RX path. Handle non-positive pull results before every length use. Count the TX error and complete the consumed TX descriptor with zero bytes. I found this bug myself, though the patch was written with AI assistance. Fixes: cfe226892913 ("vdpa_sim: filter destination mac address") Assisted-by: OpenAI-Codex:GPT-5 Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260901094842.25875-1-linfeng.sun.dev@gmail.com> --- drivers/vdpa/vdpa_sim/vdpa_sim_net.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/vdpa_sim/vdpa_sim_net.c b/drivers/vdpa/vdpa_sim/vdpa_sim_net.c index 29fd14ce5860..a6514b5ccd86 100644 --- a/drivers/vdpa/vdpa_sim/vdpa_sim_net.c +++ b/drivers/vdpa/vdpa_sim/vdpa_sim_net.c @@ -225,10 +225,15 @@ static void vdpasim_net_work(struct vdpasim *vdpasim) break; } - ++tx_pkts; read = vringh_iov_pull_iotlb(&txq->vring, &txq->out_iov, net->buffer, PAGE_SIZE); + if (read <= 0) { + ++tx_errors; + vdpasim_net_complete(txq, 0); + continue; + } + ++tx_pkts; tx_bytes += read; if (!receive_filter(vdpasim, read)) { From 7034e6c8dadaf4a2c95669890095ebafa8d9cee7 Mon Sep 17 00:00:00 2001 From: Laurent Vivier Date: Tue, 18 Aug 2026 15:39:13 +0200 Subject: [PATCH 1113/2366] MAINTAINERS: Add a section for virtio-rng At Michael's request, add a MAINTAINERS entry for the virtio-rng driver and list myself as its maintainer. I already maintain the corresponding QEMU implementation. Cc: Michael S. Tsirkin Signed-off-by: Laurent Vivier Signed-off-by: Michael S. Tsirkin Message-ID: <20260818133913.162471-1-lvivier@redhat.com> --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb07770..35ddf814de94 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29017,6 +29017,13 @@ S: Maintained F: drivers/nvdimm/nd_virtio.c F: drivers/nvdimm/virtio_pmem.c +VIRTIO RNG DRIVER +M: Laurent Vivier +L: virtualization@lists.linux.dev +S: Maintained +F: drivers/char/hw_random/virtio-rng.c +F: include/uapi/linux/virtio_rng.h + VIRTIO RTC DRIVER M: Peter Hilber L: virtualization@lists.linux.dev From 84cd1f879968ae75da15c25de4cb390428e89e6d Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Thu, 3 Sep 2026 12:13:33 +0800 Subject: [PATCH 1114/2366] vhost: limit outstanding IOTLB misses per virtqueue vhost allocates a message node whenever address translation misses. If userspace reads these messages without resolving them, repeated virtqueue kicks can grow the pending message list until the host runs out of memory. Virtqueue processing stops at the first translation miss and cannot make progress until userspace installs a mapping. Keep a pointer to that outstanding message in the virtqueue and suppress additional misses until the node is resolved or discarded. The pointer remains set while the message is queued for reading, copied to userspace, or waiting on the pending list. Clear it under the IOTLB lock when the owning node is freed. This bounds outstanding miss messages by the fixed number of virtqueues without introducing an arbitrary queue limit. Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260903-fix-kernel-panic-in-vhost_iotlb_miss_pending_list-v1-1-39b8cd427978@gmail.com> --- drivers/vhost/vhost.c | 38 +++++++++++++++++++++++++++++++++----- drivers/vhost/vhost.h | 3 +++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 14637cff0bd4..02588b64b1bb 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -392,6 +392,7 @@ static void vhost_vq_reset(struct vhost_dev *dev, vq->busyloop_timeout = 0; vq->umem = NULL; vq->iotlb = NULL; + vq->iotlb_miss = NULL; rcu_assign_pointer(vq->worker, NULL); vhost_vring_call_reset(&vq->call_ctx); __vhost_vq_meta_reset(vq); @@ -1180,6 +1181,21 @@ void vhost_dev_stop(struct vhost_dev *dev) } EXPORT_SYMBOL_GPL(vhost_dev_stop); +static void vhost_free_msg_locked(struct vhost_msg_node *node) +{ + if (node->vq->iotlb_miss == node) + node->vq->iotlb_miss = NULL; + kfree(node); +} + +static void vhost_free_msg(struct vhost_dev *dev, + struct vhost_msg_node *node) +{ + spin_lock(&dev->iotlb_lock); + vhost_free_msg_locked(node); + spin_unlock(&dev->iotlb_lock); +} + void vhost_clear_msg(struct vhost_dev *dev) { struct vhost_msg_node *node, *n; @@ -1188,12 +1204,12 @@ void vhost_clear_msg(struct vhost_dev *dev) list_for_each_entry_safe(node, n, &dev->read_list, node) { list_del(&node->node); - kfree(node); + vhost_free_msg_locked(node); } list_for_each_entry_safe(node, n, &dev->pending_list, node) { list_del(&node->node); - kfree(node); + vhost_free_msg_locked(node); } spin_unlock(&dev->iotlb_lock); @@ -1602,7 +1618,7 @@ static void vhost_iotlb_notify_vq(struct vhost_dev *d, vq_msg->type == VHOST_IOTLB_MISS) { vhost_poll_queue(&node->vq->poll); list_del(&node->node); - kfree(node); + vhost_free_msg_locked(node); } } @@ -1816,7 +1832,7 @@ ssize_t vhost_chr_read_iter(struct vhost_dev *dev, struct iov_iter *to, ret = copy_to_iter(start, size, to); if (ret != size || msg->type != VHOST_IOTLB_MISS) { - kfree(node); + vhost_free_msg(dev, node); return ret; } vhost_enqueue_msg(dev, &dev->pending_list, node); @@ -1848,7 +1864,19 @@ static int vhost_iotlb_miss(struct vhost_virtqueue *vq, u64 iova, int access) msg->iova = iova; msg->perm = access; - vhost_enqueue_msg(dev, &dev->read_list, node); + spin_lock(&dev->iotlb_lock); + /* VQ processing stops at the first miss until userspace resolves it. */ + if (vq->iotlb_miss) { + spin_unlock(&dev->iotlb_lock); + kfree(node); + return 0; + } + + vq->iotlb_miss = node; + list_add_tail(&node->node, &dev->read_list); + spin_unlock(&dev->iotlb_lock); + + wake_up_interruptible_poll(&dev->wait, EPOLLIN | EPOLLRDNORM); return 0; } diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index 0192ade6e749..fa76b7d44662 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -29,6 +29,7 @@ struct vhost_work { struct vhost_worker; struct vhost_dev; +struct vhost_msg_node; struct vhost_worker_ops { int (*create)(struct vhost_worker *worker, struct vhost_dev *dev, @@ -148,6 +149,8 @@ struct vhost_virtqueue { /* Protected by virtqueue mutex. */ struct vhost_iotlb *umem; struct vhost_iotlb *iotlb; + /* Protected by dev->iotlb_lock. */ + struct vhost_msg_node *iotlb_miss; void *private_data; VIRTIO_DECLARE_FEATURES(acked_features); u64 acked_backend_features; From 8dd505a45de0e63829d8bb4116b1d66db64813be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eugenio=20P=C3=A9rez?= Date: Thu, 3 Sep 2026 10:18:31 +0200 Subject: [PATCH 1115/2366] =?UTF-8?q?virtio:=20add=20Eugenio=20P=C3=A9rez?= =?UTF-8?q?=20as=20Maintainer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eugenio Pérez Reviewed-by: Stefano Garzarella Signed-off-by: Michael S. Tsirkin Message-ID: <20260903081831.2129729-1-eperezma@redhat.com> --- MAINTAINERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 35ddf814de94..fdbd46fce4b3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -28867,8 +28867,8 @@ F: include/uapi/linux/virtio_console.h VIRTIO CORE M: "Michael S. Tsirkin" M: Jason Wang +M: Eugenio Pérez R: Xuan Zhuo -R: Eugenio Pérez L: virtualization@lists.linux.dev S: Maintained F: Documentation/ABI/testing/sysfs-bus-vdpa @@ -28945,7 +28945,7 @@ F: include/uapi/linux/virtio_gpu.h VIRTIO HOST (VHOST) M: "Michael S. Tsirkin" M: Jason Wang -R: Eugenio Pérez +M: Eugenio Pérez L: kvm@vger.kernel.org L: virtualization@lists.linux.dev L: netdev@vger.kernel.org @@ -29000,8 +29000,8 @@ F: include/uapi/linux/virtio_mem.h VIRTIO NET DRIVER M: "Michael S. Tsirkin" M: Jason Wang +M: Eugenio Pérez R: Xuan Zhuo -R: Eugenio Pérez L: netdev@vger.kernel.org L: virtualization@lists.linux.dev S: Maintained From 93fa09455fb1a9624b73d42ac1f83771f4818e80 Mon Sep 17 00:00:00 2001 From: Andrew Stellman Date: Fri, 4 Sep 2026 10:13:18 -0400 Subject: [PATCH 1116/2366] virtio-pci: return IRQ_HANDLED after non-zero ISR vp_interrupt() reads the ISR before dispatching config-change and vring handling. Reading the ISR also clears it, so once the read returns non-zero the interrupt was from this device and has already been consumed. Currently vp_interrupt() returns the result of vp_vring_interrupt(). For a config-change interrupt with no vring work, that can return IRQ_NONE even though the ISR was non-zero and the interrupt was handled. Call vp_vring_interrupt() for any queue work, but once the ISR is non-zero return IRQ_HANDLED. Tested with QEMU virtio-blk-pci forced to INTx using vectors=0 and pci=nomsi. On an idle device, 200 config-change interrupts were generated using QMP block_resize. Before this change, irq_handler_exit reported ret=unhandled and /proc/irq/11/spurious increased from 0 to 200 unhandled interrupts. After this change, irq_handler_exit reported ret=handled and the unhandled count remained at 0. The issue was found during an LLM-assisted Quality Playbook review. Fixes: 77cf524654a8 ("virtio_pci: split up vp_interrupt") Suggested-by: Michael S. Tsirkin Assisted-by: LLM Signed-off-by: Andrew Stellman Message-ID: <20260904141318.30278-1-astellman@stellman-greene.com> Signed-off-by: Michael S. Tsirkin --- drivers/virtio/virtio_pci_common.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c index 10371ecbc054..b90c174450b2 100644 --- a/drivers/virtio/virtio_pci_common.c +++ b/drivers/virtio/virtio_pci_common.c @@ -120,7 +120,9 @@ static irqreturn_t vp_interrupt(int irq, void *opaque) if (isr & VIRTIO_PCI_ISR_CONFIG) vp_config_changed(irq, opaque); - return vp_vring_interrupt(irq, opaque); + vp_vring_interrupt(irq, opaque); + + return IRQ_HANDLED; } static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors, From c952e607cb4aa3640e5ae07243d3f609dac94424 Mon Sep 17 00:00:00 2001 From: Dongli Zhang Date: Sun, 2 Aug 2026 10:24:55 -0700 Subject: [PATCH 1117/2366] vhost-scsi: use kvzalloc for vq array allocation vhost_scsi_open() allocates one "struct vhost_scsi_virtqueue" for each virtqueue. With large max_io_vqs values, this array can require a high-order contiguous allocation and trigger a page allocator warning. hv# cat /sys/module/vhost_scsi/parameters/max_io_vqs 256 [ 766.075787] ------------[ cut here ]------------ [ 766.077030] WARNING: mm/page_alloc.c:5280 at __alloc_frozen_pages_noprof+0x32c/0x15c0, CPU#23: qemu-system-x86/5964 ... ... [ 766.080351] RIP: 0010:__alloc_frozen_pages_noprof+0x32c/0x15c0 ... ... [ 766.085813] Call Trace: [ 766.085969] [ 766.086098] ? srso_alias_return_thunk+0x5/0xfbef5 [ 766.086365] ? context_struct_compute_av+0x38a/0x4b0 [ 766.086652] alloc_pages_mpol+0x9f/0x170 [ 766.086883] ___kmalloc_large_node+0xb6/0xd0 [ 766.087124] ? srso_alias_return_thunk+0x5/0xfbef5 [ 766.087389] __kmalloc_large_node_noprof+0x18/0xa0 [ 766.087655] __kmalloc_noprof+0x3a0/0x440 [ 766.087877] ? vhost_scsi_open+0xcb/0x2d0 [vhost_scsi] [ 766.088162] vhost_scsi_open+0xcb/0x2d0 [vhost_scsi] [ 766.088449] misc_open+0x123/0x160 [ 766.088679] chrdev_open+0xb1/0x230 [ 766.088885] ? __pfx_chrdev_open+0x10/0x10 [ 766.089157] do_dentry_open+0x11a/0x470 [ 766.089389] vfs_open+0x29/0xf0 [ 766.089596] path_openat+0x7c0/0x1100 [ 766.089821] do_file_open+0xdd/0x190 [ 766.090032] ? srso_alias_return_thunk+0x5/0xfbef5 [ 766.090332] do_sys_openat2+0x7e/0x100 [ 766.090601] __x64_sys_openat+0x51/0xa0 [ 766.090857] do_syscall_64+0xfe/0x590 [ 766.091087] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 766.091411] RIP: 0033:0x7f9525a11fa6 The array does not require physical contiguity, so allocate it with kvzalloc_objs() and free it with kvfree(). Signed-off-by: Dongli Zhang Reviewed-by: Mike Christie Reviewed-by: Stefan Hajnoczi Signed-off-by: Michael S. Tsirkin Message-ID: <20260802172534.260047-2-dongli.zhang@oracle.com> --- drivers/vhost/scsi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 7a1f39a327da..223549313ce9 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -2312,7 +2312,7 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) if (!vs->old_inflight) goto err_inflight; - vs->vqs = kmalloc_objs(*vs->vqs, nvqs, GFP_KERNEL | __GFP_ZERO); + vs->vqs = kvzalloc_objs(*vs->vqs, nvqs); if (!vs->vqs) goto err_vqs; @@ -2348,7 +2348,7 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) return 0; err_local_vqs: - kfree(vs->vqs); + kvfree(vs->vqs); err_vqs: kfree(vs->old_inflight); err_inflight: @@ -2369,7 +2369,7 @@ static int vhost_scsi_release(struct inode *inode, struct file *f) vhost_dev_stop(&vs->dev); vhost_dev_cleanup(&vs->dev); kfree(vs->dev.vqs); - kfree(vs->vqs); + kvfree(vs->vqs); kfree(vs->old_inflight); kvfree(vs); return 0; From 4e3ec5b1b427e02082e8b3491731f8c3bcf85c53 Mon Sep 17 00:00:00 2001 From: Dongli Zhang Date: Sun, 2 Aug 2026 10:24:56 -0700 Subject: [PATCH 1118/2366] vhost-scsi: clamp max_io_vqs module parameter max_io_vqs is currently validated only when a vhost-scsi device is opened. This allows sysfs to show values larger than the driver will actually use, e.g. writing 2048 succeeds even though vhost_scsi_open() later clamps it to VHOST_SCSI_MAX_IO_VQ. This makes the sysfs value differ from the value that will actually be used. hv# echo 2048 > /sys/module/vhost_scsi/parameters/max_io_vqs hv# cat /sys/module/vhost_scsi/parameters/max_io_vqs 2048 [ 315.630495] Invalid max_io_vqs of 2048. Using 1024. Keep accepting out-of-range values for compatibility, but clamp them in the module parameter setter and store the effective value. This preserves the existing behavior that invalid values do not make module loading or sysfs writes fail. It also makes reads report the value that will actually be used. With the parameter value kept in range, remove the duplicate validation from vhost_scsi_open(). Signed-off-by: Dongli Zhang Reviewed-by: Mike Christie Reviewed-by: Stefan Hajnoczi Signed-off-by: Michael S. Tsirkin Message-ID: <20260802172534.260047-3-dongli.zhang@oracle.com> --- drivers/vhost/scsi.c | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 223549313ce9..4f8c0260bc9e 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -210,7 +210,37 @@ static const int vhost_scsi_bits[] = { #define VHOST_SCSI_MAX_EVENT 128 static unsigned vhost_scsi_max_io_vqs = 128; -module_param_named(max_io_vqs, vhost_scsi_max_io_vqs, uint, 0644); + +static int vhost_scsi_set_max_io_vqs(const char *val, + const struct kernel_param *kp) +{ + unsigned int max_io_vqs; + int ret; + + ret = kstrtouint(val, 0, &max_io_vqs); + if (ret) + return ret; + + if (max_io_vqs > VHOST_SCSI_MAX_IO_VQ) { + pr_err("Invalid max_io_vqs of %u. Using %u.\n", + max_io_vqs, VHOST_SCSI_MAX_IO_VQ); + max_io_vqs = VHOST_SCSI_MAX_IO_VQ; + } else if (!max_io_vqs) { + pr_err("Invalid max_io_vqs of 0. Using 1.\n"); + max_io_vqs = 1; + } + + WRITE_ONCE(vhost_scsi_max_io_vqs, max_io_vqs); + return 0; +} + +static const struct kernel_param_ops vhost_scsi_max_io_vqs_op = { + .set = vhost_scsi_set_max_io_vqs, + .get = param_get_uint, +}; + +module_param_cb(max_io_vqs, &vhost_scsi_max_io_vqs_op, + &vhost_scsi_max_io_vqs, 0644); MODULE_PARM_DESC(max_io_vqs, "Set the max number of IO virtqueues a vhost scsi device can support. The default is 128. The max is 1024."); struct vhost_scsi_virtqueue { @@ -2290,21 +2320,14 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) struct vhost_scsi_virtqueue *svq; struct vhost_scsi *vs; struct vhost_virtqueue **vqs; - int r = -ENOMEM, i, nvqs = vhost_scsi_max_io_vqs; + int r = -ENOMEM, i, nvqs; vs = kvzalloc_obj(*vs); if (!vs) goto err_vs; vs->inline_sg_cnt = vhost_scsi_inline_sg_cnt; - if (nvqs > VHOST_SCSI_MAX_IO_VQ) { - pr_err("Invalid max_io_vqs of %d. Using %d.\n", nvqs, - VHOST_SCSI_MAX_IO_VQ); - nvqs = VHOST_SCSI_MAX_IO_VQ; - } else if (nvqs == 0) { - pr_err("Invalid max_io_vqs of %d. Using 1.\n", nvqs); - nvqs = 1; - } + nvqs = READ_ONCE(vhost_scsi_max_io_vqs); nvqs += VHOST_SCSI_VQ_IO; vs->old_inflight = kmalloc_objs(*vs->old_inflight, nvqs, From 7474f3a61043934e9c351febc56f4d85cd5ddc96 Mon Sep 17 00:00:00 2001 From: Nikhil Date: Sun, 30 Aug 2026 04:24:57 +0530 Subject: [PATCH 1119/2366] vduse: do not take dev->rwsem in the virtqueue kick path vduse_vq_kick() runs in the context of the vdpa .kick_vq callback. With the virtio_vdpa bus driver that callback is invoked by virtqueue_notify() from the virtio device driver, which may be an atomic context: virtio-blk kicks from ->queue_rq(), which blk-mq dispatches under rcu_read_lock() (the tag set does not use BLK_MQ_F_BLOCKING), and virtio-net kicks from its xmit path with the tx queue lock held. Commit b282418bc366 ("vduse: Add suspend") made vduse_vq_kick() take dev->rwsem for reading in order to check dev->suspended. down_read() may sleep, so with CONFIG_DEBUG_ATOMIC_SLEEP the first I/O on a VDUSE-backed virtio-blk device bound to virtio_vdpa now triggers: BUG: sleeping function called from invalid context at kernel/locking/rwsem.c:1573 in_atomic(): 0, irqs_disabled(): 0, non_block: 0, pid: 27, name: kworker/1:0H preempt_count: 0, expected: 0 RCU nest depth: 1, expected: 0 3 locks held by kworker/1:0H/27: #0: ((wq_completion)kblockd){+.+.}-{0:0}, at: process_one_work+0xac7/0xcf0 #1: ((work_completion)(&(&hctx->run_work)->work)){+.+.}-{0:0}, at: process_one_work+0x51f/0xcf0 #2: (rcu_read_lock){....}-{1:3}, at: blk_mq_run_work_fn+0x119/0x220 Workqueue: kblockd blk_mq_run_work_fn Call Trace: dump_stack_lvl+0x80/0xa0 __might_resched+0x231/0x370 down_read+0x73/0x330 vduse_vq_kick+0x30/0x120 virtio_vdpa_notify+0x63/0x80 virtqueue_notify+0x45/0x70 virtio_queue_rq+0x19d/0x300 blk_mq_dispatch_rq_list+0x269/0xe20 __blk_mq_sched_dispatch_requests+0x761/0xa60 blk_mq_sched_dispatch_requests+0x6b/0xc0 blk_mq_run_work_fn+0x143/0x220 process_one_work+0x581/0xcf0 worker_thread+0x2fc/0x5a0 kthread+0x1cc/0x210 ret_from_fork+0x3c4/0x540 ret_from_fork_asm+0x1a/0x30 Without CONFIG_DEBUG_ATOMIC_SLEEP, a kick that finds the rwsem write-locked by vduse_dev_reset() or vduse_vdpa_suspend() blocks inside an RCU read-side critical section. The vhost_vdpa path kicks from the vhost worker, i.e. process context, which is why this went unnoticed. Check dev->suspended under vq->kick_lock instead, which the kick path already takes, and have vduse_vdpa_suspend() cycle every virtqueue's kick_lock after setting the flag. A kick that observed suspended == false has thus finished signalling before suspend returns, which is the guarantee the rwsem used to provide. The flag is now also read outside the rwsem, so access it with READ_ONCE()/WRITE_ONCE(). Fixes: b282418bc366 ("vduse: Add suspend") Signed-off-by: Nikhil Signed-off-by: Michael S. Tsirkin Message-ID: <20260829225457.1037867-1-nikhilljatt@gmail.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 9891cd2cf712..766789a7bbfa 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -506,7 +506,7 @@ static void vduse_dev_reset(struct vduse_dev *dev) } scoped_guard(rwsem_write, &dev->rwsem) { - dev->suspended = false; + WRITE_ONCE(dev->suspended, false); dev->status = 0; dev->driver_features = 0; dev->generation++; @@ -567,11 +567,17 @@ static int vduse_vdpa_set_vq_address(struct vdpa_device *vdpa, u16 idx, static void vduse_vq_kick(struct vduse_virtqueue *vq) { - guard(rwsem_read)(&vq->dev->rwsem); - if (vq->dev->suspended) + /* + * This runs in the context of the vdpa kick_vq op, which may be + * atomic (e.g. virtio-blk kicks from blk-mq dispatch under + * rcu_read_lock()), so dev->rwsem must not be taken here. + * dev->suspended is checked under kick_lock instead and + * vduse_vdpa_suspend() cycles every kick_lock after setting it. + */ + guard(spinlock)(&vq->kick_lock); + if (READ_ONCE(vq->dev->suspended)) return; - guard(spinlock)(&vq->kick_lock); scoped_guard(spinlock_bh, &vq->ready_lock) if (!vq->ready) return; @@ -946,7 +952,17 @@ static int vduse_vdpa_suspend(struct vdpa_device *vdpa) ret = vduse_dev_msg_sync(dev, &msg); if (ret == 0) { scoped_guard(rwsem_write, &dev->rwsem) - dev->suspended = true; + WRITE_ONCE(dev->suspended, true); + + /* + * Kicks check dev->suspended under kick_lock without taking + * the rwsem: cycle each kick_lock so that no kick that has + * already passed the check is still in flight after this. + */ + for (u32 i = 0; i < dev->vq_num; i++) { + spin_lock(&dev->vqs[i]->kick_lock); + spin_unlock(&dev->vqs[i]->kick_lock); + } cancel_work_sync(&dev->inject); for (u32 i = 0; i < dev->vq_num; i++) From fa2c25b4add57888acfa89e398389e267bff3dcf Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Sun, 30 Aug 2026 10:33:54 +0800 Subject: [PATCH 1120/2366] vduse: validate virtqueue alignment vduse_validate_config() only checks the upper bound of vq_align. Invalid values can therefore reach vring_create_virtqueue_map(). The split-ring helpers use align - 1 as a bit mask, so the alignment must be a non-zero power of two. A zero value makes vring_size() drop the descriptor and available-ring part and vring_init() leave the used ring pointer NULL. The VIRTIO spec requires the used ring to start at an address aligned to at least 4 bytes. Reject values below VRING_USED_ALIGN_SIZE as well as non-power-of-two values before they reach the virtio ring helpers. Opening a virtio-net device created with vq_align=0 triggered: BUG: KASAN: null-ptr-deref in virtqueue_kick_prepare_split+0xe3/0x100 Read of size 2 at addr 0000000000000000 by task systemd-network/1062 Call Trace (relevant frames): dump_stack_lvl print_report kasan_report __asan_load2 virtqueue_kick_prepare_split+0xe3/0x100 virtqueue_kick_prepare+0x40/0x60 try_fill_recv+0x857/0x1250 virtnet_open+0x189/0x460 __dev_open+0x225/0x390 __dev_change_flags+0x368/0x3b0 netif_change_flags+0x56/0xc0 do_setlink.isra.0+0x68c/0x1e30 Validate the value before it reaches the virtio ring helpers. Fixes: c8a6153b6c59 ("vduse: Introduce VDUSE - vDPA Device in Userspace") Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260830023354.115333-1-physicalmtea@gmail.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 766789a7bbfa..4dea4d6a3855 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -2227,7 +2227,9 @@ static bool vduse_validate_config(struct vduse_dev_config *config, return false; } - if (config->vq_align > PAGE_SIZE) + if (config->vq_align < VRING_USED_ALIGN_SIZE || + !is_power_of_2(config->vq_align) || + config->vq_align > PAGE_SIZE) return false; if (config->config_size > PAGE_SIZE) From e4f4761879a230aa59e569102a6ab9851847d833 Mon Sep 17 00:00:00 2001 From: Jia Jia Date: Fri, 28 Aug 2026 16:57:21 +0800 Subject: [PATCH 1121/2366] vhost: invalidate vring access on IOTLB transitions When VIRTIO_F_ACCESS_PLATFORM changes, cached vring pointers and IOTLB metadata are interpreted in a different address space. Keeping them across the transition can leave stale ring mappings in use. Clearing d->iotlb before taking the VQ locks also lets a worker observe a transient NULL d->iotlb and fall back to d->umem while translating a descriptor. Add a common vhost_clear_device_iotlb() helper for vhost-net and vhost-vsock. Take all VQ mutexes in index order before dropping the device-wide IOTLB, invalidate each VQ's cached ring access and metadata, clear pending IOTLB messages, and free the old table after the handoff. This serializes the transition with workers and prevents mixed address space mappings. On the first direct-to-IOTLB transition, invalidate the cached vring addresses. When an existing device IOTLB is replaced, preserve the GIOVA ring addresses and reset only the metadata cache. After clearing ACCESS_PLATFORM, userspace must configure the vring addresses for the new address mode. vhost_vq_invalidate_access() clears desc, avail, and used together. Treat the VQ as invalidated only when all three are NULL, since a single GIOVA address may legitimately be zero. Fixes: 6b1e6cc7855b ("vhost: new device IOTLB API") Fixes: e13a6915a03f ("vhost/vsock: add IOTLB API support") Suggested-by: Michael S. Tsirkin Signed-off-by: Jia Jia Signed-off-by: Michael S. Tsirkin Message-ID: <20260828085721.57816-1-physicalmtea@gmail.com> --- drivers/vhost/net.c | 2 ++ drivers/vhost/vhost.c | 57 ++++++++++++++++++++++++++++++++++++++++++- drivers/vhost/vhost.h | 1 + drivers/vhost/vsock.c | 2 ++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index c25929dd4425..2cc730729e08 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -1705,6 +1705,8 @@ static int vhost_net_set_features(struct vhost_net *n, const u64 *features) if (virtio_features_test_bit(features, VIRTIO_F_ACCESS_PLATFORM)) { if (vhost_init_device_iotlb(&n->dev)) goto out_unlock; + } else { + vhost_clear_device_iotlb(&n->dev); } for (i = 0; i < VHOST_NET_VQ_MAX; ++i) { diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 02588b64b1bb..44cac11b68d2 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -344,6 +344,17 @@ static void __vhost_vq_meta_reset(struct vhost_virtqueue *vq) vq->meta_iotlb[j] = NULL; } +/* Caller must hold the virtqueue mutex. */ +static void vhost_vq_invalidate_access(struct vhost_virtqueue *vq) +{ + vq->desc = NULL; + vq->avail = NULL; + vq->used = NULL; + vq->log_used = false; + vq->log_addr = -1ull; + __vhost_vq_meta_reset(vq); +} + static void vhost_vq_meta_reset(struct vhost_dev *d) { int i; @@ -1946,6 +1957,13 @@ int vq_meta_prefetch(struct vhost_virtqueue *vq) { unsigned int num = vq->num; + /* + * vhost_vq_invalidate_access() clears all three addresses together. + * A single zero address may be a valid GIOVA in IOTLB mode. + */ + if (!vq->desc && !vq->avail && !vq->used) + return 0; + if (!vq->iotlb) return 1; @@ -2315,6 +2333,40 @@ long vhost_vring_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *arg } EXPORT_SYMBOL_GPL(vhost_vring_ioctl); +/* Caller must hold the device mutex. */ +void vhost_clear_device_iotlb(struct vhost_dev *d) +{ + struct vhost_iotlb *iotlb; + int i; + + iotlb = d->iotlb; + if (!iotlb) + return; + + vhost_dev_lock_vqs(d); + + /* + * vhost_dev_lock_vqs() takes all VQ mutexes in index order. Drop the + * device-wide view while they are held, then clear each per-VQ view + * and its cached ring access before releasing the locks. Workers + * cannot observe a mixed address-space state during this handoff. + */ + d->iotlb = NULL; + + for (i = 0; i < d->nvqs; ++i) { + struct vhost_virtqueue *vq = d->vqs[i]; + + vq->iotlb = NULL; + vhost_vq_invalidate_access(vq); + } + + vhost_dev_unlock_vqs(d); + vhost_clear_msg(d); + vhost_iotlb_free(iotlb); + wake_up_interruptible_poll(&d->wait, EPOLLIN | EPOLLRDNORM); +} +EXPORT_SYMBOL_GPL(vhost_clear_device_iotlb); + int vhost_init_device_iotlb(struct vhost_dev *d) { struct vhost_iotlb *niotlb, *oiotlb; @@ -2335,7 +2387,10 @@ int vhost_init_device_iotlb(struct vhost_dev *d) mutex_lock(&vq->mutex); vq->iotlb = niotlb; - __vhost_vq_meta_reset(vq); + if (oiotlb) + __vhost_vq_meta_reset(vq); + else + vhost_vq_invalidate_access(vq); mutex_unlock(&vq->mutex); } diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index fa76b7d44662..39e6121f7525 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -280,6 +280,7 @@ ssize_t vhost_chr_read_iter(struct vhost_dev *dev, struct iov_iter *to, int noblock); ssize_t vhost_chr_write_iter(struct vhost_dev *dev, struct iov_iter *from); +void vhost_clear_device_iotlb(struct vhost_dev *d); int vhost_init_device_iotlb(struct vhost_dev *d); void vhost_iotlb_map_free(struct vhost_iotlb *iotlb, diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index 9aaab6bb8061..abed1fbcf66c 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -868,6 +868,8 @@ static int vhost_vsock_set_features(struct vhost_vsock *vsock, u64 features) if ((features & (1ULL << VIRTIO_F_ACCESS_PLATFORM))) { if (vhost_init_device_iotlb(&vsock->dev)) goto err; + } else { + vhost_clear_device_iotlb(&vsock->dev); } vsock->seqpacket_allow = features & (1ULL << VIRTIO_VSOCK_F_SEQPACKET); From 81489b32a21c9360f8750d1fb600155d27452e19 Mon Sep 17 00:00:00 2001 From: Xiong Weimin Date: Wed, 5 Aug 2026 11:29:31 +0800 Subject: [PATCH 1122/2366] virtio_input: reset device if input_register_device() fails Probe marks the device DRIVER_OK with virtio_device_ready() before calling input_register_device(). If registration fails, the error path cleared vi->ready and called del_vqs() while the device was still live, so the device could keep DMA to queues that were already torn down. Match remove/freeze: call virtio_reset_device() on that path before tearing down the virtqueues. Fixes: 271c865161c5 ("Add virtio-input driver.") Signed-off-by: Xiong Weimin Signed-off-by: Michael S. Tsirkin Message-ID: <20260805032931.1606652-1-xiongweimin@kylinos.cn> --- drivers/virtio/virtio_input.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/virtio/virtio_input.c b/drivers/virtio/virtio_input.c index deec24e8e682..1a87be4c88cf 100644 --- a/drivers/virtio/virtio_input.c +++ b/drivers/virtio/virtio_input.c @@ -331,6 +331,7 @@ static int virtinput_probe(struct virtio_device *vdev) spin_lock_irqsave(&vi->lock, flags); vi->ready = false; spin_unlock_irqrestore(&vi->lock, flags); + virtio_reset_device(vdev); err_mt_init_slots: input_free_device(vi->idev); err_input_alloc: From d7808b37da0a619cf1fa541c2384e783fecc2480 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 17:20:58 +0200 Subject: [PATCH 1123/2366] virtio_input: stop callbacks before unregistering input device virtinput_remove() unregisters the input device before resetting the virtio device. virtinput_recv_events() drops vi->lock around input_event(), so clearing vi->ready does not stop a callback that passed the entry check. It can still use vi->idev, requeue buffers and kick the queue. Reset first, as virtinput_freeze() already does. With the preceding core change, reset waits for callbacks before input_unregister_device() can free vi->idev. Recheck vi->ready after taking the lock again: keep draining completed events so an input packet is not truncated, but stop requeueing buffers and kicking the queue. With evdev attached, input_unregister_handle() currently waits for an RCU grace period, which also waits out IRQ callbacks. This masks the lifetime bug on PCI and MMIO, but does not protect sleepable callbacks on other transports. Fixes: 271c865161c5 ("Add virtio-input driver.") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Michael S. Tsirkin Message-ID: <20260905152059.89560-3-kmehltretter@gmail.com> --- drivers/virtio/virtio_input.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/virtio/virtio_input.c b/drivers/virtio/virtio_input.c index 1a87be4c88cf..e3bd0b9616f9 100644 --- a/drivers/virtio/virtio_input.c +++ b/drivers/virtio/virtio_input.c @@ -49,9 +49,12 @@ static void virtinput_recv_events(struct virtqueue *vq) le16_to_cpu(event->code), le32_to_cpu(event->value)); spin_lock_irqsave(&vi->lock, flags); + if (!vi->ready) + continue; virtinput_queue_evtbuf(vi, event); } - virtqueue_kick(vq); + if (vi->ready) + virtqueue_kick(vq); } spin_unlock_irqrestore(&vi->lock, flags); } @@ -351,8 +354,9 @@ static void virtinput_remove(struct virtio_device *vdev) vi->ready = false; spin_unlock_irqrestore(&vi->lock, flags); - input_unregister_device(vi->idev); + /* Callbacks use vi->idev. */ virtio_reset_device(vdev); + input_unregister_device(vi->idev); while ((buf = virtqueue_detach_unused_buf(vi->sts)) != NULL) kfree(buf); vdev->config->del_vqs(vdev); From 883e78c9e6007c91be96e99a36c35baf41bc8ed5 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 7 Sep 2026 10:36:45 +0100 Subject: [PATCH 1124/2366] ASoC: cs35l56: Fix race between kexec and snd_soc_register_component() Use a reboot notifier and a mutex to prevent snd_soc_register_component() from racing with a kexec reboot. This prevents snd_soc_register_component() from manipulating device lists while device_shutdown() is walking them. Commit 1d80a4792f1de ("ASoC: cs35l56: Fix probe deadlock waiting for SoundWire enumeration") moved snd_soc_register_component() out of probe() into a workqueue item. See the description in that commit for a detailed explanation. That change introduces a race between snd_soc_register_component() and kexec. The reboot notifier and mutex prevent the shutdown race. There is one remaining race with KEXEC_JUMP because it does not invoke reboot notifiers or freeze freezable workqueues. But KEXEC_JUMP is rarely used and is supported on only two architectures (x86 and SuperH). It does not appear to be enabled by default in any distro. It is also unlikely there will be a KEXEC_JUMP before snd_soc_register_component() has had the opportunity to execute. Fixing this can be deferred to a future patch. Fixes: 1d80a4792f1de ("ASoC: cs35l56: Fix probe deadlock waiting for SoundWire enumeration") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260907093645.27407-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56.c | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 890429ab0dfb..35d210626627 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -18,9 +18,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -37,6 +39,13 @@ #include "wm_adsp.h" #include "cs35l56.h" +/* + * snd_soc_register_component() can call component_probe() on all instances + * in a card, so deferred registration must be protected across all instances. + */ +static DEFINE_MUTEX(cs35l56_component_register_lock); +static bool cs35l56_shutting_down; + void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) { /* @@ -1957,6 +1966,11 @@ static void cs35l56_component_register_work(struct work_struct *work) component_register_work); int ret; + guard(mutex)(&cs35l56_component_register_lock); + + if (cs35l56_shutting_down) + return; + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(cs35l56->base.dev, pm_err); ret = PM_RUNTIME_ACQUIRE_ERR(&pm_err); if (ret) { @@ -2217,6 +2231,37 @@ EXPORT_NS_GPL_DEV_PM_OPS(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE) = { }; #endif +static int cs35l56_reboot_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + guard(mutex)(&cs35l56_component_register_lock); + cs35l56_shutting_down = true; + + return NOTIFY_DONE; +} + +static struct notifier_block cs35l56_reboot_notifier = { + .notifier_call = cs35l56_reboot_notify, +}; + +static int __init cs35l56_modinit(void) +{ + /* + * Use reboot notifier to prevent race between shutdown and + * snd_soc_register_component(). Driver shutdown() callback would + * run too late, after device_shutdown() is already walking the + * device list that component registration can modify. + */ + return register_reboot_notifier(&cs35l56_reboot_notifier); +} +module_init(cs35l56_modinit); + +static void __exit cs35l56_modexit(void) +{ + unregister_reboot_notifier(&cs35l56_reboot_notifier); +} +module_exit(cs35l56_modexit); + MODULE_DESCRIPTION("ASoC CS35L56 driver"); MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED"); MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB"); From 74f27fc8642b7e8d139796f8c18ee46df393c2b2 Mon Sep 17 00:00:00 2001 From: Nagamani PV Date: Tue, 1 Sep 2026 17:53:44 +0200 Subject: [PATCH 1125/2366] s390/qeth: allow bridgeport queries despite OS_MISMATCH When HiperSockets interfaces on the same VCHID span different OS families, reads of the sysfs attributes bridge_role and bridge_state fail with -EPERM if bridge port ownership belongs to another OS family. As a result, userspace tools such as 'lszdev -ii' cannot retrieve bridge_role and bridge_state, even though firmware returns valid bridge port data for QUERY_BRIDGE_PORTS requests. The firmware reports IPA_RC_SBP_IQD_OS_MISMATCH (0x0010) to indicate that bridge port ownership belongs to a different OS family. For QUERY_BRIDGE_PORTS operations, firmware still returns valid bridge port data (role=none, state=inactive) together with a primary return code of 0x0000 (success). Allow QUERY_BRIDGE_PORTS requests to return the bridge port data provided by the firmware despite OS_MISMATCH. To make the OS family mismatch visible to userspace, represent the firmware-reported role "none" as "none (OS family mismatch)" while preserving the reported bridge_state. The behavior for non-QUERY bridge port commands is unchanged; SET operations continue to return -EPERM when another OS family owns the bridge port. This restores readability of bridge_role and bridge_state. Fixes: 1b05cf6285c1 ("qeth: Include error message for "OS Mismatch"") Cc: stable@vger.kernel.org Suggested-by: Halil Pasic Reviewed-by: Alexandra Winter Signed-off-by: Nagamani PV Link: https://patch.msgid.link/20260901155344.3561483-1-nagamani@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/s390/net/qeth_l2.h | 3 ++- drivers/s390/net/qeth_l2_main.c | 26 ++++++++++++++++++++++---- drivers/s390/net/qeth_l2_sys.c | 7 ++++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/drivers/s390/net/qeth_l2.h b/drivers/s390/net/qeth_l2.h index 7c646e2fed7e..f94975e970ca 100644 --- a/drivers/s390/net/qeth_l2.h +++ b/drivers/s390/net/qeth_l2.h @@ -13,7 +13,8 @@ extern const struct attribute_group *qeth_l2_attr_groups[]; int qeth_bridgeport_query_ports(struct qeth_card *card, enum qeth_sbp_roles *role, - enum qeth_sbp_states *state); + enum qeth_sbp_states *state, + bool *os_mismatch); int qeth_bridgeport_setrole(struct qeth_card *card, enum qeth_sbp_roles role); int qeth_bridgeport_an_set(struct qeth_card *card, int enable); diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c index a9e7d1d637a2..2935c2ecc314 100644 --- a/drivers/s390/net/qeth_l2_main.c +++ b/drivers/s390/net/qeth_l2_main.c @@ -1158,7 +1158,7 @@ static void qeth_l2_setup_bridgeport_attrs(struct qeth_card *card) qeth_bridgeport_setrole(card, card->options.sbp.role); /* Let the callback function refresh the stored role value. */ qeth_bridgeport_query_ports(card, &card->options.sbp.role, - NULL); + NULL, NULL); } if (card->options.sbp.hostnotification) { if (qeth_bridgeport_an_set(card, 1)) @@ -1545,6 +1545,7 @@ struct _qeth_sbp_cbctl { struct { enum qeth_sbp_roles *role; enum qeth_sbp_states *state; + bool *os_mismatch; } qports; } data; }; @@ -1721,10 +1722,19 @@ static int qeth_bridgeport_query_ports_cb(struct qeth_card *card, struct qeth_ipa_cmd *cmd = (struct qeth_ipa_cmd *) data; struct _qeth_sbp_cbctl *cbctl = (struct _qeth_sbp_cbctl *)reply->param; struct qeth_sbp_port_data *qports; + u16 sbp_rc; int rc; QETH_CARD_TEXT(card, 2, "brqprtcb"); - rc = qeth_bridgeport_makerc(card, cmd); + sbp_rc = cmd->data.sbp.hdr.return_code; + + /* on OS family mismatch, query still returns valid port data; + * treat as success + */ + if (sbp_rc == IPA_RC_SBP_IQD_OS_MISMATCH && !cmd->hdr.return_code) + rc = 0; + else + rc = qeth_bridgeport_makerc(card, cmd); if (rc) return rc; @@ -1740,6 +1750,9 @@ static int qeth_bridgeport_query_ports_cb(struct qeth_card *card, if (cbctl->data.qports.state) *cbctl->data.qports.state = qports->entry[0].state; } + if (cbctl->data.qports.os_mismatch) + *cbctl->data.qports.os_mismatch = + (sbp_rc == IPA_RC_SBP_IQD_OS_MISMATCH); return 0; } @@ -1748,13 +1761,17 @@ static int qeth_bridgeport_query_ports_cb(struct qeth_card *card, * @card: qeth_card structure pointer. * @role: Role of the port: 0-none, 1-primary, 2-secondary. * @state: State of the port: 0-inactive, 1-standby, 2-active. + * @os_mismatch: if non-NULL, set to true when firmware reports + * OS family mismatch. * * Returns negative errno-compatible error indication or 0 on success. * - * 'role' and 'state' are not updated in case of hardware operation failure. + * 'role', 'state' and 'os_mismatch' are not updated in case of + * hardware operation failure. */ int qeth_bridgeport_query_ports(struct qeth_card *card, - enum qeth_sbp_roles *role, enum qeth_sbp_states *state) + enum qeth_sbp_roles *role, enum qeth_sbp_states *state, + bool *os_mismatch) { struct qeth_cmd_buffer *iob; struct _qeth_sbp_cbctl cbctl = { @@ -1762,6 +1779,7 @@ int qeth_bridgeport_query_ports(struct qeth_card *card, .qports = { .role = role, .state = state, + .os_mismatch = os_mismatch, }, }, }; diff --git a/drivers/s390/net/qeth_l2_sys.c b/drivers/s390/net/qeth_l2_sys.c index 7f592f912517..7101be62eb1d 100644 --- a/drivers/s390/net/qeth_l2_sys.c +++ b/drivers/s390/net/qeth_l2_sys.c @@ -15,6 +15,7 @@ static ssize_t qeth_bridge_port_role_state_show(struct device *dev, { struct qeth_card *card = dev_get_drvdata(dev); enum qeth_sbp_states state = QETH_SBP_STATE_INACTIVE; + bool os_mismatch = false; int rc = 0; char *word; @@ -25,7 +26,7 @@ static ssize_t qeth_bridge_port_role_state_show(struct device *dev, if (qeth_card_hw_is_reachable(card) && card->options.sbp.supported_funcs) rc = qeth_bridgeport_query_ports(card, - &card->options.sbp.role, &state); + &card->options.sbp.role, &state, &os_mismatch); if (!rc) { if (show_state) switch (state) { @@ -52,6 +53,10 @@ static ssize_t qeth_bridge_port_role_state_show(struct device *dev, if (rc) QETH_CARD_TEXT_(card, 2, "SBP%02x:%02x", card->options.sbp.role, state); + else if (!show_state && + card->options.sbp.role == QETH_SBP_ROLE_NONE && + os_mismatch) + rc = sysfs_emit(buf, "%s (OS family mismatch)\n", word); else rc = sysfs_emit(buf, "%s\n", word); } From 94fd4debd2e3a69cf93e766c8b328a810c228119 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 2 Sep 2026 20:21:50 +0000 Subject: [PATCH 1126/2366] af_unix: Update last skb marker in manage_oob(). Fahad Alharbi reported that blocking recv(MSG_PEEK) could hog CPU due to OOB skb. In the following cases, manage_oob() skips OOB skb(s) and returns NULL for the last recv(MSG_PEEK): socketpair(AF_UNIX, SOCK_STREAM, 0, sk); 1) skb -> OOB skb -> NULL send(sk[0], "ab", 2, MSG_OOB); recv(sk[1], buf, 0, MSG_PEEK); 2) skb -> consumed OOB skb -> NULL send(sk[0], "ab", 2, MSG_OOB); recv(sk[1], buf, 1, MSG_OOB); recv(sk[1], buf, 0, MSG_PEEK); 3) consumed OOB skb -> OOB skb -> NULL send(sk[0], "a", 1, MSG_OOB); recv(sk[1], buf, 0, MSG_OOB); send(sk[0], "b", 1, MSG_OOB); recv(sk[1], buf, 1, MSG_PEEK); Then, @copied is 0 in unix_stream_read_generic() (zero-length buffer, or non-OOB skb is not yet consumed), and unix_stream_data_wait() is called. However, it returns immediately because @last is not updated in unix_stream_read_generic(), and the thread busy-waits for a new skb. Let's update @last in manage_oob(). For MSG_PEEK, @last is updated with the skipped OOB, and for the non-peek case, @last matches the returned value (when !copied) because OOB is unlinked. Note that manage_oob() is inlined and no stack canary is added. Fixes: 22dd70eb2c3d ("af_unix: Don't peek OOB data without MSG_OOB.") Reported-by: Fahad Alharbi Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260902202202.892676-2-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/unix/af_unix.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 13f9926bf205..6861370062df 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -2812,8 +2812,8 @@ static int unix_stream_recv_urg(struct unix_stream_read_state *state) return 1; } -static struct sk_buff *manage_oob(struct sk_buff *skb, struct sock *sk, - int flags, int copied) +static struct sk_buff *manage_oob(struct sk_buff *skb, struct sk_buff **last, + struct sock *sk, int flags, int copied) { struct sk_buff *read_skb = NULL, *unread_skb = NULL; struct unix_sock *u = unix_sk(sk); @@ -2827,11 +2827,13 @@ static struct sk_buff *manage_oob(struct sk_buff *skb, struct sock *sk, if (copied && (!u->oob_skb || skb == u->oob_skb)) { skb = NULL; } else if (flags & MSG_PEEK) { + *last = skb; skb = skb_peek_next(skb, &sk->sk_receive_queue); } else { read_skb = skb; skb = skb_peek_next(skb, &sk->sk_receive_queue); __skb_unlink(read_skb, &sk->sk_receive_queue); + *last = skb; } if (!skb) @@ -2850,8 +2852,10 @@ static struct sk_buff *manage_oob(struct sk_buff *skb, struct sock *sk, __skb_unlink(skb, &sk->sk_receive_queue); unread_skb = skb; skb = skb_peek(&sk->sk_receive_queue); + *last = skb; } } else if (!sock_flag(sk, SOCK_URGINLINE)) { + *last = skb; skb = skb_peek_next(skb, &sk->sk_receive_queue); } @@ -2971,7 +2975,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state, again: #if IS_ENABLED(CONFIG_AF_UNIX_OOB) if (skb) { - skb = manage_oob(skb, sk, flags, copied); + skb = manage_oob(skb, &last, sk, flags, copied); if (!skb && copied) { unix_state_unlock(sk); break; From 6e5ee08eb5858d175da6768d75d163817b6a9d4a Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 2 Sep 2026 20:21:51 +0000 Subject: [PATCH 1127/2366] af_unix: Return immediately when manage_oob() returns NULL for 0-length buffer. Fahad Alharbi reported that recv(0, MSG_PEEK) triggers busy-wait in unix_stream_read_generic() if recv() is blocking and the last skb in the queue is MSG_OOB skb. In such a situation, TCP returns 0 immediately regardless of blocking or non-blocking. Let's follow the behaviour. Fixes: 314001f0bf92 ("af_unix: Add OOB support") Reported-by: Fahad Alharbi Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260902202202.892676-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/unix/af_unix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 6861370062df..2da1017f8873 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -2976,7 +2976,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state, #if IS_ENABLED(CONFIG_AF_UNIX_OOB) if (skb) { skb = manage_oob(skb, &last, sk, flags, copied); - if (!skb && copied) { + if (!skb && (copied || !state->size)) { unix_state_unlock(sk); break; } From ca0b0a86873e8ded39b7fb196dbdc615d9a9a0e4 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 2 Sep 2026 20:21:52 +0000 Subject: [PATCH 1128/2366] selftest: af_unix: Add zero-buffer test for msg_oob.c The previous patches fixed two issues related to zero-length buffer with MSG_PEEK for MSG_OOB skb. Let's add corresponding tests in msg_oob.c. Without this series: # FAILED: 50 / 60 tests passed. # Totals: pass:50 fail:10 xfail:0 xpass:0 skip:0 error:0 With this series: # PASSED: 60 / 60 tests passed. # Totals: pass:60 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260902202202.892676-4-kuniyu@google.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/af_unix/msg_oob.c | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tools/testing/selftests/net/af_unix/msg_oob.c b/tools/testing/selftests/net/af_unix/msg_oob.c index 1b499d56656c..f051d79f7a8e 100644 --- a/tools/testing/selftests/net/af_unix/msg_oob.c +++ b/tools/testing/selftests/net/af_unix/msg_oob.c @@ -290,6 +290,25 @@ static void __setinlinepair(struct __test_metadata *_metadata, } } +static void __setblockingpair(struct __test_metadata *_metadata, + FIXTURE_DATA(msg_oob) *self) +{ + int i; + + for (i = 0; i < 2; i++) { + int ret, old_flags, flags; + + old_flags = fcntl(self->fd[i * 2 + 1], F_GETFL, 0); + ASSERT_NE(-1, old_flags); + + ret = fcntl(self->fd[i * 2 + 1], F_SETFL, old_flags & ~O_NONBLOCK); + ASSERT_EQ(0, ret); + + flags = fcntl(self->fd[i * 2 + 1], F_GETFL, 0); + ASSERT_EQ(old_flags & ~O_NONBLOCK, flags); + } +} + static void __siocatmarkpair(struct __test_metadata *_metadata, FIXTURE_DATA(msg_oob) *self, bool oob_head) @@ -347,6 +366,9 @@ static void __resetpair(struct __test_metadata *_metadata, #define setinlinepair() \ __setinlinepair(_metadata, self) +#define setblockingpair() \ + __setblockingpair(_metadata, self) + #define resetpair(reset) \ __resetpair(_metadata, self, variant, reset) @@ -888,4 +910,49 @@ TEST_F(msg_oob, inline_ex_oob_siocatmark) resetpair(true); } +TEST_F(msg_oob, zero_buf_oob) +{ + sendpair("a", 1, MSG_OOB); + recvpair("", 0, 0, 0); +} + +TEST_F(msg_oob, zero_buf_oob_blocking) +{ + sendpair("a", 1, MSG_OOB); + setblockingpair(); + recvpair("", 0, 0, 0); +} + +TEST_F(msg_oob, zero_buf_non_oob_oob) +{ + sendpair("ab", 2, MSG_OOB); + recvpair("", 0, 0, 0); +} + +TEST_F(msg_oob, zero_buf_non_oob_oob_blocking) +{ + sendpair("ab", 2, MSG_OOB); + setblockingpair(); + recvpair("", 0, 0, 0); +} + +TEST_F(msg_oob, zero_buf_ex_oob_oob) +{ + sendpair("a", 1, MSG_OOB); + recvpair("a", 1, 1, MSG_OOB); + + sendpair("b", 1, MSG_OOB); + recvpair("", 0, 0, 0); +} + +TEST_F(msg_oob, zero_buf_ex_oob_oob_blocking) +{ + sendpair("a", 1, MSG_OOB); + recvpair("a", 1, 1, MSG_OOB); + + sendpair("b", 1, MSG_OOB); + setblockingpair(); + recvpair("", 0, 0, 0); +} + TEST_HARNESS_MAIN From b83641e0ab8b20eefcc4cdc5a059f897375291a2 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Tue, 1 Sep 2026 22:57:11 +0300 Subject: [PATCH 1129/2366] net: ipv4: Fix UDP length overflow with PMTU discover and big MTU This commit bounds cork->base.fragsize to IP_MAX_MTU to avoid a possible overflow of UDP length that triggers a WARN in udp_set_len_short when setsockopt IP_MTU_DISCOVER is set to IP_PMTUDISC_PROBE, and a large packet is sent over a netdev with an unusually large MTU. Steps to reproduce: 1. Set device MTU bigger than IP_MAX_MTU + 20. cork->base.fragsize will be set to that MTU in ip_setup_cork. 2. Set IP_MTU_DISCOVER to IP_PMTUDISC_PROBE. It lets maxnonfragsize be set to device MTU (cork->fragsize) in __ip_append_data, rather than to IP_MAX_MTU. 3. Send 65528 bytes of payload (+8 bytes of UDP header, +20 bytes of IPv4 header). Device MTU allows it (it's only one byte bigger than IP_MAX_MTU + IPv4 header, and the device MTU is bigger than that). 4. The UDP length in the built packet is 65536, which overflows the 16-bit length field and triggers the WARN in udp_set_len_short. Note: IP_PMTUDISC_DO with IPv4 is safe, because ip_dst_mtu_maybe_forward always clamps at IP_MAX_MTU, unlike ip6_dst_mtu_maybe_forward. The Fixes tag points at the first commit where I could reproduce the overflow with IPv4 and IP_PMTUDISC_PROBE. Fixes: daba287b299e ("ipv4: fix DO and PROBE pmtu mode regarding local fragmentation with UFO/CORK") Reported-by: syzbot+ce13c07d96d04716eaa2@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a6a966c.86abc875.e5c3d.0054.GAE@google.com/ Signed-off-by: Alice Mikityanska Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260901195714.673548-2-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- net/ipv4/ip_output.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index 74e095b6b7ca..a24cc8ee11d3 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -1303,6 +1303,7 @@ static int ip_setup_cork(struct sock *sk, struct inet_cork *cork, cork->fragsize = ip_sk_use_pmtu(sk) ? dst4_mtu(&rt->dst) : READ_ONCE(rt->dst.dev->mtu); + cork->fragsize = min(cork->fragsize, IP_MAX_MTU); if (!inetdev_valid_mtu(cork->fragsize)) return -ENETUNREACH; From 0ae10b6be49b425827659b23bcce498f80eb7182 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Tue, 1 Sep 2026 22:57:12 +0300 Subject: [PATCH 1130/2366] net: ipv6: Fix UDP length overflow with PMTU discover and big MTU This commit bounds cork->base.fragsize to IP6_MAX_MTU for UDP sockets to avoid a possible overflow of UDP length that triggers a WARN in udp_set_len_short when setsockopt IPV6_MTU_DISCOVER is set to IPV6_PMTUDISC_DO or IPV6_PMTUDISC_PROBE, and a large packet is sent over a netdev with an unusually large MTU. Steps to reproduce (included in the new selftest): 1. Set device MTU bigger than IP6_MAX_MTU. cork->base.fragsize will be set to that MTU in ip6_setup_cork. 2. Set IPV6_MTU_DISCOVER to IPV6_PMTUDISC_PROBE or IPV6_PMTUDISC_DO. It lets maxnonfragsize be set to device MTU (cork->fragsize) in __ip6_append_data, rather than to IP6_MAX_MTU. 3. Send 65528 bytes of payload (+8 bytes of UDP header, +40 bytes of IPv6 header). Device MTU allows it (it's only one byte bigger than IP6_MAX_MTU, and the device MTU is bigger than that). 4. The UDP length in the built packet is 65536, which overflows the 16-bit length field and triggers the WARN in udp_set_len_short. To avoid breaking sending UDP jumbograms over raw IPv6 sockets, limit the change to UDP sockets only. The original overflow bug with IPv6 and IPV6_PMTUDISC_DO seems to predate git history (verified reproduction on 2.6.21), was fixed later, and then reappeared in commit 427faee167bc ("net: ipv6: introduce ip6_dst_mtu_maybe_forward"), which is chosen as the Fixes tag here. The overflow with IPV6_PMTUDISC_PROBE reproduces since its introduction in commit 628a5c561890 ("[INET]: Add IP(V6)_PMTUDISC_RPOBE"). Fixes: 427faee167bc ("net: ipv6: introduce ip6_dst_mtu_maybe_forward") Reported-by: syzbot+ce13c07d96d04716eaa2@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a6a966c.86abc875.e5c3d.0054.GAE@google.com/ Signed-off-by: Alice Mikityanska Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260901195714.673548-3-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_output.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c index 8fc4766c8da9..550965058991 100644 --- a/net/ipv6/ip6_output.c +++ b/net/ipv6/ip6_output.c @@ -1432,6 +1432,8 @@ static int ip6_setup_cork(struct sock *sk, struct inet_cork_full *cork, if (frag_size && frag_size < mtu) mtu = frag_size; + if (sk_is_udp(sk)) + mtu = min(mtu, IP6_MAX_MTU); cork->base.fragsize = mtu; cork->base.gso_size = ipc6->gso_size; cork->base.tx_flags = 0; From 18a9a4342136c5ae954b37d961c374d369615de2 Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Tue, 1 Sep 2026 22:57:13 +0300 Subject: [PATCH 1131/2366] selftests: net: Test UDP length overflow with PMTU discover and big MTU Two previous commits fixed overflow of UDP length when setsockopt IP(V6)_MTU_DISCOVER is set to IPV6_PMTUDISC_DO or IP(V6)_PMTUDISC_PROBE, and a large packet is sent over a netdev with an unusually large MTU. This commit adds the selftests that replicate the described steps to reproduce for IPv6 and IPv4, and also one more test that ensures that sending UDP jumbograms over a raw socket is still possible after the fix. Signed-off-by: Alice Mikityanska Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260901195714.673548-4-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/cork_fragsize.py | 187 +++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100755 tools/testing/selftests/net/cork_fragsize.py diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 517c09d60bef..3ee3378f8b26 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -25,6 +25,7 @@ TEST_PROGS := \ cmsg_so_mark.sh \ cmsg_so_priority.sh \ cmsg_time.sh \ + cork_fragsize.py \ double_udp_encap.sh \ drop_monitor_tests.sh \ ecmp_rehash.sh \ diff --git a/tools/testing/selftests/net/cork_fragsize.py b/tools/testing/selftests/net/cork_fragsize.py new file mode 100755 index 000000000000..7afd643d07ec --- /dev/null +++ b/tools/testing/selftests/net/cork_fragsize.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +'''Test possible UDP length overflow in udp_send_skb/udp_v6_send_skb.''' + +import errno +import gzip +import os +import socket +import struct +import subprocess +from contextlib import contextmanager + +from lib.py import ( + KsftNamedVariant, + KsftSkipEx, + NetNS, + NetNSEnter, + defer, + ip, + ksft_eq, + ksft_exit, + ksft_pr, + ksft_raises, + ksft_run, + ksft_true, + ksft_variants, +) + +IP_MTU_DISCOVER = 10 +IP_PMTUDISC_PROBE = 3 +IPV6_MTU_DISCOVER = 23 +IPV6_PMTUDISC_DO = 2 +IPV6_PMTUDISC_PROBE = 3 +IPV6_TLV_JUMBO = 194 + + +def check_kernel_config(option: str) -> bool | None: + ''' + Check whether the option is enabled in the config of the running kernel. + Returns None if the config is not found; otherwise returns True/False + depending on the option value in the config. + ''' + + for filename, method in [ + ('/proc/config.gz', gzip.open), + (f'/boot/config-{os.uname().release}', open), + ]: + try: + with method(filename, 'rt') as config: + for line in config: + if line.rstrip() == f'{option}=y': + return True + return False + except OSError: + continue + return None + + +def assert_debug_kernel() -> None: + ''' + Skip the test if CONFIG_DEBUG_NET is not set in the kernel config. + ''' + + res = check_kernel_config('CONFIG_DEBUG_NET') + if res is None: + ksft_pr("WARN: Can't read kernel config; assuming debug kernel, and running the test") + elif not res: + raise KsftSkipEx('CONFIG_DEBUG_NET is not set') + + +def check_dmesg_clean(func: str) -> bool: + ''' + Check if the given function produced a WARN in dmesg. + ''' + + with subprocess.Popen(['dmesg'], stdout=subprocess.PIPE) as dmesg: + res = subprocess.run(['grep', '-q', f'WARNING:.*{func}'], stdin=dmesg.stdout, check=False) + return res.returncode != 0 and dmesg.returncode == 0 + + +@contextmanager +def dummy_netdev(ns: NetNS, mtu: int, ipv6: bool) -> None: + ''' + Create a dummy netdev inside the given namespace, and tune it for the test. + ''' + + ip('link add dummy type dummy', ns=ns) + with defer(ip, 'link del dummy', ns=ns): + ip(f'link set dummy mtu {mtu}', ns=ns) + ip('link set dummy up', ns=ns) + flag = '-6' if ipv6 else '' + nodad = 'nodad' if ipv6 else '' + local = 'fd00::1/64' if ipv6 else '10.0.0.1/24' + remote = 'fd00::2' if ipv6 else '10.0.0.2' + ip(f'{flag} addr add {local} dev dummy {nodad}', ns=ns) + ip(f'{flag} neigh add {remote} lladdr 02:00:00:00:00:02 dev dummy nud permanent', ns=ns) + yield + + +@ksft_variants([ + KsftNamedVariant( + 'ipv6', + True, + socket.AF_INET6, + (socket.IPPROTO_IPV6, IPV6_MTU_DISCOVER, IPV6_PMTUDISC_DO), + 'fd00::2', + 'udp_v6_send_skb', + ), + KsftNamedVariant( + 'ipv4', + False, + socket.AF_INET, + (socket.IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUDISC_PROBE), + '10.0.0.2', + 'udp_send_skb', + ), +]) +def test_udp( + ipv6: bool, + af: socket.AddressFamily, + sockopts: tuple[int, int, int], + destip: str, + func: str +) -> None: + ''' + Test that sending an oversized UDP packet over a UDP socket doesn't overflow + the 16-bit length field in the UDP header, which could happen on older + kernels in udp_send_skb/udp_v6_send_skb. + + IPv4: The packet will be dropped with EMSGSIZE, but the overflow could + happen before it happens. The only way to test this is to check dmesg on + CONFIG_DEBUG_NET=y kernels that have udp_set_len_short with the warning. + + IPv6: The packet will be dropped with EMSGSIZE on fixed kernels, and will be + sent corrupted on older kernels. Test both: sendto must return EMSGSIZE, and + dmesg must be clean of warnings on CONFIG_DEBUG_NET=y kernels. + ''' + + if not ipv6: + assert_debug_kernel() + + with ( + NetNS() as ns, + dummy_netdev(ns, 65556 + 20 * ipv6, ipv6), + NetNSEnter(ns), + socket.socket(af, socket.SOCK_DGRAM) as fd, + ): + fd.setsockopt(*sockopts) + with ksft_raises(OSError) as e: + fd.sendto(b' ' * 65528, (destip, 1234)) + # IPv6: EMSGSIZE happens on kernels with the fix. + # IPv4: EMSGSIZE happens on both fixed and unfixed kernels, after the + # WARN is printed - ignore it and rely on the dmesg check. + if e.exception is not None: + ksft_eq(e.exception.errno, errno.EMSGSIZE) + + ksft_true(check_dmesg_clean(func), 'WARNING detected in dmesg') + + +def test_ipv6_jumbo() -> None: + ''' + Test that sending UDP jumbograms over a raw IPv6 socket works, despite + having the fix for oversized UDP packets. sendto must not raise an OSError + exception (when raised, the test fails automatically). + ''' + + with ( + NetNS() as ns, + dummy_netdev(ns, 65584, True), + NetNSEnter(ns), + socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_UDP) as fd, + ): + hopopts = struct.pack('!BBBBI', 0, 0, IPV6_TLV_JUMBO, 4, 65544) + fd.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_HOPOPTS, hopopts) + fd.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_CHECKSUM, 6) + fd.setsockopt(socket.IPPROTO_IPV6, IPV6_MTU_DISCOVER, IPV6_PMTUDISC_PROBE) + udp = struct.pack('!HHHH', 1234, 1234, 0, 0) + b' ' * 65528 + fd.sendto(udp, ('fd00::2', 0)) + + +if __name__ == "__main__": + ksft_run([ + test_udp, + test_ipv6_jumbo, + ]) + ksft_exit() From 199271ebc71c1e0913b2fad988a7bff330a8828a Mon Sep 17 00:00:00 2001 From: Alice Mikityanska Date: Tue, 1 Sep 2026 22:57:14 +0300 Subject: [PATCH 1132/2366] net: ipv6: Clamp to IP6_MAX_MTU in ip6_dst_mtu_maybe_forward Commit 427faee167bc ("net: ipv6: introduce ip6_dst_mtu_maybe_forward") dropped the IP6_MAX_MTU clamp that used to be present in ip6_mtu(). A similar IPv4 commit ac6627a28dbf ("net: ipv4: Consolidate ipv4_mtu and ip_dst_mtu_maybe_forward") preserves the IP_MAX_MTU clamp. Restore the upper bound in the IPv6 flow to avoid potential 16-bit overflows in forwarding paths. Fixes: 427faee167bc ("net: ipv6: introduce ip6_dst_mtu_maybe_forward") Signed-off-by: Alice Mikityanska Suggested-by: Willem de Bruijn Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260901195714.673548-5-alice.kernel@fastmail.im Signed-off-by: Jakub Kicinski --- include/net/ip6_route.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index c69f1c871922..b9e8d2b759e9 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -384,6 +384,8 @@ static inline unsigned int ip6_dst_mtu_maybe_forward(const struct dst_entry *dst rcu_read_unlock(); out: + mtu = min_t(unsigned int, mtu, IP6_MAX_MTU); + return mtu - lwtunnel_headroom(dst->lwtstate, mtu); } From 4ff75f130d1b84f65a6f35a8a0cbca52130127ef Mon Sep 17 00:00:00 2001 From: Sebastian Sjoholm Date: Thu, 3 Sep 2026 20:00:44 +0200 Subject: [PATCH 1133/2366] net: usb: qmi_wwan: add Quectel RG660QB Add support for the Quectel RG660QB 5G module (USB ID 2c7c:013d). Its QMI interface (interface 4) uses class/subclass/protocol ff/ff/ff like the other recent Quectel modules, so match it the same way. The remaining interfaces are handled by the option driver. Tested with an early sample of the module on a Quectel 5G EVB connected over USB 3 to a Raspberry Pi 5: qmicli talks to the module via /dev/cdc-wdm0. Signed-off-by: Sebastian Sjoholm Link: https://patch.msgid.link/20260903180044.6179-1-sebastian.sjoholm@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/qmi_wwan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index fdfdcf24ddcf..f51cf9cb9421 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -1084,6 +1084,7 @@ static const struct usb_device_id products[] = { }, {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0122)}, /* Quectel RG650V */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0125)}, /* Quectel EC25, EC20 R2.0 Mini PCIe */ + {QMI_MATCH_FF_FF_FF(0x2c7c, 0x013d)}, /* Quectel RG660QB */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0306)}, /* Quectel EP06/EG06/EM06 */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0512)}, /* Quectel EG12/EM12 */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0620)}, /* Quectel EM160R-GL */ From 3b55f350c68a0aceff108f47f9d31f47ebffaf7b Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Mon, 7 Sep 2026 14:10:24 +0200 Subject: [PATCH 1134/2366] bpf: Fix bpf_skb_change_tail wrt csum partial skbs Cilium generates ICMP "frag needed" replies from BPF when a LB DSR packet exceeds the egress MTU. The reply is built by first trimming the packet down to target size via bpf_skb_change_tail(), and then pushing the ICMP error headers in front of it. The trim is rejected for skbs which carry a checksum offload, e.g. TCP packets aggregated by GRO on ingress where tcp_gro_complete() leaves the skb as CHECKSUM_PARTIAL. __bpf_skb_min_len() raises the minimum length to the end of the L4 checksum field, so a trim to 42 bytes bails out with -EINVAL given a min_len of 52 in this case, and due to that the ICMP generator fails. This is not the case if GRO is turned off. Fix this bpf_skb_change_tail() restriction and drop the checksum offload when the new length no longer covers the checksum field. The BPF program rewrites the skb into an ICMP error and computes the checksum itself anyway. Fixes: 5293efe62df8 ("bpf: add bpf_skb_change_tail helper") Reported-by: Tom Hadlaw Reported-by: Yusuke Suzuki Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260907121025.1923656-1-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- net/core/filter.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 61940e753552..8513167a858a 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -3961,12 +3961,6 @@ static u32 __bpf_skb_min_len(const struct sk_buff *skb) if (offset > 0) min_len = offset; } - if (skb->ip_summed == CHECKSUM_PARTIAL) { - offset = skb_checksum_start_offset(skb) + - skb->csum_offset + sizeof(__sum16); - if (offset > 0) - min_len = offset; - } return min_len; } @@ -3983,6 +3977,11 @@ static int bpf_skb_grow_rcsum(struct sk_buff *skb, unsigned int new_len) static int bpf_skb_trim_rcsum(struct sk_buff *skb, unsigned int new_len) { + if (skb->ip_summed == CHECKSUM_PARTIAL && + new_len < skb_checksum_start_offset(skb) + skb->csum_offset + + sizeof(__sum16)) + skb->ip_summed = CHECKSUM_NONE; + return __skb_trim_rcsum(skb, new_len); } From 15e2565f1c43771af0bc5324971cabaad79ac286 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Mon, 7 Sep 2026 14:10:25 +0200 Subject: [PATCH 1135/2366] selftests/bpf: Add test for bpf_skb_change_tail on csum partial skbs Add a test which builds an ICMP error out of a TCP segment. A tcx prog on the client's egress side trims the first data segment down to the target size and pushes the ICMP error headers in front of it to then reflect the packet back to the sender. # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t tc_change_tail [...] #509 tc_change_tail:OK #510 tc_change_tail_pmtu:OK Summary: 2/0 PASSED, 0 SKIPPED, 0/0 FAILED Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260907121025.1923656-2-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- .../bpf/prog_tests/tc_change_tail_pmtu.c | 125 +++++++++++++++++ .../bpf/progs/test_tc_change_tail_pmtu.c | 129 ++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c create mode 100644 tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c diff --git a/tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c b/tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c new file mode 100644 index 000000000000..7acdbd5757a9 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#include "test_progs.h" +#include "network_helpers.h" +#include "test_tc_change_tail_pmtu.skel.h" + +#define CLIENT_NS "tc-change-tail-cli-ns" +#define SERVER_NS "tc-change-tail-srv-ns" +#define CLIENT_IP "192.168.1.1" +#define SERVER_IP "192.168.1.2" + +#define TEST_PMTU 1000 +#define TEST_MSS_MAX (TEST_PMTU - 20 - 20) +#define TIMEOUT_MS 3000 +#define XFER_BYTES 8192 + +void test_tc_change_tail_pmtu(void) +{ + LIBBPF_OPTS(bpf_tcx_opts, tcx_opts); + int mss_before = 0, mss_after = 0, ifindex, port; + int srv_fd = -1, srv_conn_fd = -1, cli_fd = -1; + struct test_tc_change_tail_pmtu *skel = NULL; + struct nstoken *nstoken = NULL; + static char buf[XFER_BYTES]; + socklen_t optlen; + ssize_t bytes; + size_t total; + + if (!ASSERT_OK(make_netns(CLIENT_NS), "make client ns")) + return; + if (!ASSERT_OK(make_netns(SERVER_NS), "make server ns")) + goto out_client_ns; + + nstoken = open_netns(CLIENT_NS); + if (!ASSERT_OK_PTR(nstoken, "open client ns")) + goto out; + SYS(out, "ip link add veth1 type veth peer name veth2 netns " SERVER_NS); + SYS(out, "ip -4 addr add " CLIENT_IP "/24 dev veth1"); + SYS(out, "ip link set veth1 up"); + ifindex = if_nametoindex("veth1"); + if (!ASSERT_NEQ(ifindex, 0, "if_nametoindex")) + goto out; + close_netns(nstoken); + nstoken = NULL; + + nstoken = open_netns(SERVER_NS); + if (!ASSERT_OK_PTR(nstoken, "open server ns")) + goto out; + SYS(out, "ip -4 addr add " SERVER_IP "/24 dev veth2"); + SYS(out, "ip link set veth2 up"); + srv_fd = start_server(AF_INET, SOCK_STREAM, SERVER_IP, 0, TIMEOUT_MS); + if (!ASSERT_OK_FD(srv_fd, "start server")) + goto out; + close_netns(nstoken); + nstoken = NULL; + + skel = test_tc_change_tail_pmtu__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open and load skeleton")) + goto out; + + port = get_socket_local_port(srv_fd); + if (!ASSERT_GE(port, 0, "get server port")) + goto out; + + skel->bss->server_port = port; + skel->bss->pmtu = TEST_PMTU; + + nstoken = open_netns(CLIENT_NS); + if (!ASSERT_OK_PTR(nstoken, "open client ns")) + goto out; + + skel->links.change_tail_icmp = + bpf_program__attach_tcx(skel->progs.change_tail_icmp, ifindex, + &tcx_opts); + if (!ASSERT_OK_PTR(skel->links.change_tail_icmp, "attach tcx")) + goto out; + + cli_fd = connect_to_fd(srv_fd, TIMEOUT_MS); + if (!ASSERT_OK_FD(cli_fd, "connect to server")) + goto out; + srv_conn_fd = accept(srv_fd, NULL, NULL); + if (!ASSERT_OK_FD(srv_conn_fd, "accept connection")) + goto out; + if (!ASSERT_OK(settimeo(srv_conn_fd, TIMEOUT_MS), "set server timeout")) + goto out; + + optlen = sizeof(mss_before); + if (!ASSERT_OK(getsockopt(cli_fd, IPPROTO_TCP, TCP_MAXSEG, &mss_before, + &optlen), "get mss before")) + goto out; + + bytes = send(cli_fd, buf, sizeof(buf), 0); + if (!ASSERT_EQ(bytes, (ssize_t)sizeof(buf), "send data")) + goto out; + + for (total = 0; total < sizeof(buf); total += bytes) { + bytes = recv(srv_conn_fd, buf, sizeof(buf), 0); + if (bytes <= 0) + break; + } + + ASSERT_EQ(total, sizeof(buf), "receive data"); + ASSERT_OK(skel->data->change_tail_ret, "change tail"); + ASSERT_OK(skel->bss->adjust_room_ret, "adjust room"); + ASSERT_TRUE(skel->bss->icmp_sent, "icmp sent"); + + optlen = sizeof(mss_after); + if (!ASSERT_OK(getsockopt(cli_fd, IPPROTO_TCP, TCP_MAXSEG, &mss_after, + &optlen), "get mss after")) + goto out; + + ASSERT_LT(mss_after, mss_before, "mss reduced"); + ASSERT_LE(mss_after, TEST_MSS_MAX, "mss below pmtu"); +out: + close(srv_conn_fd); + close(cli_fd); + close(srv_fd); + test_tc_change_tail_pmtu__destroy(skel); + close_netns(nstoken); + remove_netns(SERVER_NS); +out_client_ns: + remove_netns(CLIENT_NS); +} diff --git a/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c b/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c new file mode 100644 index 000000000000..5c4c07545bc9 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ICMP_SAMPLE_LEN (sizeof(struct iphdr) + 8) +#define ICMP_HDRS_LEN (sizeof(struct iphdr) + sizeof(struct icmphdr)) + +__be16 server_port = 0; +__u16 pmtu = 0; + +long change_tail_ret = 1; +long adjust_room_ret = 0; +bool icmp_sent = false; +bool icmp_err = false; + +static __always_inline __sum16 csum_fold(__wsum csum) +{ + csum = (csum & 0xffff) + (csum >> 16); + csum = (csum & 0xffff) + (csum >> 16); + + return (__sum16)~csum; +} + +SEC("tc/egress") +int change_tail_icmp(struct __sk_buff *skb) +{ + __u8 smac[ETH_ALEN], dmac[ETH_ALEN]; + void *data, *data_end; + struct icmphdr *icmp; + struct ethhdr *eth; + struct tcphdr *tcp; + __be32 saddr, daddr; + struct iphdr *ip; + __wsum csum; + + if (icmp_sent || icmp_err) + return TCX_PASS; + + data = (void *)(long)skb->data; + data_end = (void *)(long)skb->data_end; + + eth = data; + if ((void *)(eth + 1) > data_end) + return TCX_PASS; + if (eth->h_proto != bpf_htons(ETH_P_IP)) + return TCX_PASS; + + ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end) + return TCX_PASS; + if (ip->ihl != 5 || ip->protocol != IPPROTO_TCP) + return TCX_PASS; + + tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return TCX_PASS; + if (tcp->dest != server_port) + return TCX_PASS; + if (bpf_ntohs(ip->tot_len) <= sizeof(*ip) + tcp->doff * 4) + return TCX_PASS; + + __builtin_memcpy(smac, eth->h_source, ETH_ALEN); + __builtin_memcpy(dmac, eth->h_dest, ETH_ALEN); + saddr = ip->saddr; + daddr = ip->daddr; + + change_tail_ret = bpf_skb_change_tail(skb, ETH_HLEN + ICMP_SAMPLE_LEN, 0); + if (change_tail_ret) { + icmp_err = true; + return TCX_PASS; + } + + adjust_room_ret = bpf_skb_adjust_room(skb, ICMP_HDRS_LEN, + BPF_ADJ_ROOM_MAC, + BPF_F_ADJ_ROOM_NO_CSUM_RESET); + if (adjust_room_ret) { + icmp_err = true; + return TCX_DROP; + } + + data = (void *)(long)skb->data; + data_end = (void *)(long)skb->data_end; + + eth = data; + ip = (void *)(eth + 1); + icmp = (void *)(ip + 1); + if ((void *)icmp + sizeof(*icmp) + ICMP_SAMPLE_LEN > data_end) { + icmp_err = true; + return TCX_DROP; + } + + __builtin_memcpy(eth->h_dest, smac, ETH_ALEN); + __builtin_memcpy(eth->h_source, dmac, ETH_ALEN); + + __builtin_memset(icmp, 0, sizeof(*icmp)); + icmp->type = ICMP_DEST_UNREACH; + icmp->code = ICMP_FRAG_NEEDED; + icmp->un.frag.mtu = bpf_htons(pmtu); + + __builtin_memset(ip, 0, sizeof(*ip)); + ip->version = 4; + ip->ihl = 5; + ip->ttl = 64; + ip->protocol = IPPROTO_ICMP; + ip->tot_len = bpf_htons(ICMP_HDRS_LEN + ICMP_SAMPLE_LEN); + ip->saddr = daddr; + ip->daddr = saddr; + + csum = bpf_csum_diff(NULL, 0, (__be32 *)icmp, + sizeof(*icmp) + ICMP_SAMPLE_LEN, 0); + icmp->checksum = csum_fold(csum); + csum = bpf_csum_diff(NULL, 0, (__be32 *)ip, sizeof(*ip), 0); + ip->check = csum_fold(csum); + icmp_sent = true; + return bpf_redirect(skb->ifindex, BPF_F_INGRESS); +} + +char _license[] SEC("license") = "GPL"; From b7c0f8436f077e7f66c9f07714bd57068e2a0c31 Mon Sep 17 00:00:00 2001 From: Paul Hollinsky Date: Fri, 21 Aug 2026 01:13:25 -0700 Subject: [PATCH 1136/2366] drm/msm/adreno: Only check for PAS when a zap shader is present Commit 0be72be03ca7 ("drm/msm: Switch to generic PAS TZ APIs") replaced the qcom_scm_is_available() check in adreno_zap_shader_load() with qcom_pas_is_available(). These are not equivalent: the former reports whether the SCM transport is up, the latter whether the TrustZone firmware implements the peripheral authentication service. On SC7180 Chromebooks (trogdor) TZ does not implement PAS at all. SCM call-availability queries return 0 for every PAS command while other services answer normally: svc 0x06 cmd 0x01 IS_CALL_AVAIL -> 1 svc 0x02 cmd 0x01 PAS_INIT_IMAGE -> 0 svc 0x02 cmd 0x05 PAS_AUTH_RESET -> 0 svc 0x02 cmd 0x07 PAS_IS_SUPPORTED -> 0 svc 0x0c cmd 0x16 MP_ASSIGN -> 1 svc 0x05 cmd 0x01 IO_READ -> 1 so qcom_scm_probe() never registers a PAS backend and qcom_pas_is_available() is false for the lifetime of the boot. That on its own need not matter, because sc7180-trogdor.dtsi does /delete-node/ &gpu_zap_shader;, and the intended path for such a board is for zap_shader_load_mdt() to find no zap-shader child, clear zap_available, return -ENODEV, and let the caller fall back to SECVID_TRUST_CNTL. The problem is the ordering. zap_available is a static initialised to true and is only ever cleared inside zap_shader_load_mdt(), but adreno_zap_shader_load() consults PAS before calling it. The discovery that decides whether a zap shader is needed at all can therefore never run, the flag is never cleared, and every call returns -EPROBE_DEFER: adreno 5000000.gpu: [drm:adreno_zap_shader_load] *ERROR* PAS is not available msm_dpu ae01000.display-controller: [drm:adreno_load_gpu] *ERROR* gpu hw init failed: -517 Nothing retries that deferral, either. adreno_zap_shader_load() is called from a6xx_hw_init() rather than from probe, so the -EPROBE_DEFER is not a probe return value: it propagates up until adreno_load_gpu() returns NULL. load_gpu() re-attempts on every DRM open while priv->gpu is NULL, each open fails identically, and PAS cannot become available in between - which is why the error repeats and userspace stays on llvmpipe. Move the availability check into zap_shader_load_mdt(), behind the zap-shader node lookup, so the driver only consults PAS once it knows it needs PAS. Boards with no zap-shader node take the intended -ENODEV fallback without ever asking, and boards that do have one keep the qcom_pas_is_available() gate. Fixes: 0be72be03ca7 ("drm/msm: Switch to generic PAS TZ APIs") Link: https://lore.kernel.org/r/20260808034716.58888-1-phollinsky@holtechnik.com Signed-off-by: Paul Hollinsky Reviewed-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/747583/ Message-ID: <20260821081325.89088-1-phollinsky@holtechnik.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index 8cd2020d4b7e..ca5e4e560cde 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -52,6 +52,12 @@ static int zap_shader_load_mdt(struct msm_gpu *gpu, const char *fwname, return -ENODEV; } + /* We need PAS to be able to load the firmware */ + if (!qcom_pas_is_available()) { + DRM_DEV_ERROR(dev, "PAS is not available\n"); + return -EPROBE_DEFER; + } + ret = of_reserved_mem_region_to_resource(np, 0, &r); if (ret) { zap_available = false; @@ -170,18 +176,11 @@ static int zap_shader_load_mdt(struct msm_gpu *gpu, const char *fwname, int adreno_zap_shader_load(struct msm_gpu *gpu, u32 pasid) { struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu); - struct platform_device *pdev = gpu->pdev; /* Short cut if we determine the zap shader isn't available/needed */ if (!zap_available) return -ENODEV; - /* We need PAS to be able to load the firmware */ - if (!qcom_pas_is_available()) { - DRM_DEV_ERROR(&pdev->dev, "PAS is not available\n"); - return -EPROBE_DEFER; - } - return zap_shader_load_mdt(gpu, adreno_gpu->info->zapfw, pasid); } From 8d6cd188508513503805c156165de38e4e4a8615 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 3 Sep 2026 14:23:03 +0800 Subject: [PATCH 1137/2366] ipv6: flowlabel: cap duplicate leases per socket ipv6_flowlabel_get() allocates an ipv6_fl_socklist entry for every successful GET. The recheck path for a compatible existing flowlabel links another lease without applying any lease admission check. Repeated GET requests for one shareable label can therefore grow a socket's lease list without bound. Reject a new unprivileged lease once the socket already holds FL_MAX_PER_SOCK leases. Check this on the shared recheck path so reuse of a globally interned label, including the fl_intern() collision path, is covered as well. New-label admission remains under the existing mem_check() policy. Use capable(CAP_NET_ADMIN) rather than ns_capable(), matching mem_check(). An unprivileged user must not bypass the cap by creating a user namespace and a netns where they have CAP_NET_ADMIN, which would still consume host memory. Check the capability only when the socket reaches the limit, so successful unprivileged GET requests below the cap do not generate a capability audit. Do the admission check before updating linger and expires so a rejected GET does not refresh the shared label, matching the existing socket-list allocation failure path. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega Suggested-by: Ido Schimmel Signed-off-by: Zhiling Zou Reviewed-by: Eric Dumazet Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/83f8535972ff6e3741548476a1d50dec24c758be.1788415194.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_flowlabel.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/net/ipv6/ip6_flowlabel.c b/net/ipv6/ip6_flowlabel.c index 1ab5ad0dcf24..006585dc8b5c 100644 --- a/net/ipv6/ip6_flowlabel.c +++ b/net/ipv6/ip6_flowlabel.c @@ -461,6 +461,21 @@ fl_create(struct net *net, struct sock *sk, struct in6_flowlabel_req *freq, return NULL; } +static bool fl_sock_at_lease_limit(const struct sock *sk) +{ + const struct ipv6_fl_socklist *sfl; + int count = 0; + + rcu_read_lock(); + for_each_sk_fl_rcu(sk, sfl) { + if (++count >= FL_MAX_PER_SOCK) + break; + } + rcu_read_unlock(); + + return count >= FL_MAX_PER_SOCK; +} + static int mem_check(struct sock *sk) { const int unpriv_total_limit = FL_MAX_SIZE - (FL_MAX_SIZE / 4); @@ -679,6 +694,10 @@ static int ipv6_flowlabel_get(struct sock *sk, struct in6_flowlabel_req *freq, err = -ENOMEM; if (!sfl1) goto release; + err = -ENOBUFS; + if (fl_sock_at_lease_limit(sk) && + !capable(CAP_NET_ADMIN)) + goto release; if (fl->linger > fl1->linger) fl1->linger = fl->linger; if ((long)(fl->expires - fl1->expires) > 0) From 9868f5c077dfe0b606331f2e782484f91a5789a5 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Wed, 17 Jun 2026 11:43:03 -0500 Subject: [PATCH 1138/2366] EDAC/altera: Use parent device for devres in altr_portb_setup() Anchor the devres group and the devm-managed IRQ requests in altr_portb_setup() to the actual parent device (device->edac->dev) instead of the embedded struct device inside the copied per-port altr_edac_device_dev. This keeps devres_open_group(), devm_request_irq(), devres_remove_group() and devres_release_group() all referring to the same long-lived device so the group and the resources allocated inside it are torn down together. Fixes: 911049845d70 ("EDAC, altera: Add Arria10 SD-MMC EDAC support") Closes: https://sashiko.dev/#/patchset/20260503212558.2811480-1-dbgh9129%40gmail.com Assisted-by: LLM Signed-off-by: Dinh Nguyen Signed-off-by: Borislav Petkov (AMD) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260617164303.585555-1-dinguyen@kernel.org --- drivers/edac/altera_edac.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 1d1e2b5ca14c..68846f583eee 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -1534,7 +1534,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) altdev = dci->pvt_info; *altdev = *device; - if (!devres_open_group(&altdev->ddev, altr_portb_setup, GFP_KERNEL)) + if (!devres_open_group(device->edac->dev, altr_portb_setup, GFP_KERNEL)) return -ENOMEM; /* Update PortB specific values */ @@ -1562,7 +1562,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) rc = -ENODEV; goto err_release_group_1; } - rc = devm_request_irq(&altdev->ddev, altdev->sb_irq, + rc = devm_request_irq(device->edac->dev, altdev->sb_irq, prv->ecc_irq_handler, IRQF_TRIGGER_HIGH, ecc_name, altdev); if (rc) { @@ -1584,7 +1584,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) rc = -ENODEV; goto err_release_group_1; } - rc = devm_request_irq(&altdev->ddev, altdev->db_irq, + rc = devm_request_irq(device->edac->dev, altdev->db_irq, prv->ecc_irq_handler, IRQF_TRIGGER_HIGH, ecc_name, altdev); if (rc) { @@ -1604,13 +1604,13 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) list_add(&altdev->next, &altdev->edac->a10_ecc_devices); - devres_remove_group(&altdev->ddev, altr_portb_setup); + devres_remove_group(device->edac->dev, altr_portb_setup); return 0; err_release_group_1: edac_device_free_ctl_info(dci); - devres_release_group(&altdev->ddev, altr_portb_setup); + devres_release_group(device->edac->dev, altr_portb_setup); edac_printk(KERN_ERR, EDAC_DEVICE, "%s:Error setting up EDAC device: %d\n", ecc_name, rc); return rc; From fcf57d066444dfa5cb46080b1b61cde517249530 Mon Sep 17 00:00:00 2001 From: Ruairi Anthony Date: Mon, 7 Sep 2026 12:59:42 +0100 Subject: [PATCH 1139/2366] ALSA: hda/realtek: Add quirk for HP Elite Dragonfly Max G2 speaker The HP Elite Dragonfly Max G2 Notebook PC (SSID 103c:8890) uses an ALC285 codec but is missing from the existing HP quirk table, unlike the related "HP Elite Dragonfly G2" entry (SSID 103c:8716). Without the quirk the right speaker amplifier's GPIO is never initialized, leaving it silent while the left speaker plays normally. Apply the existing ALC285_FIXUP_HP_GPIO_AMP_INIT fixup used by the 103c:8716 entry, which resolves the issue. Verified with speaker-test on both channels and confirmed correct routing to both the downward- firing woofer and upward-firing tweeter on each side via the board's passive crossover. Signed-off-by: Ruairi Anthony Link: https://patch.msgid.link/20260907115942.19286-1-ruairi@ruairi.uk Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index f7786e1b0878..3abee617e86e 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7451,6 +7451,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x887c, "HP Laptop 14s-fq1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x888a, "HP ENVY x360 Convertible 15-eu0xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x888d, "HP ZBook Power 15.6 inch G8 Mobile Workstation PC", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8890, "HP Elite Dragonfly Max G2 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x8895, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED), SND_PCI_QUIRK(0x103c, 0x8896, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8898, "HP EliteBook 845 G8 Notebook PC", ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST), From aefdbd574a362dcf7569bada6d72f64a006b9fb9 Mon Sep 17 00:00:00 2001 From: Jason Andryuk Date: Tue, 25 Aug 2026 17:48:03 -0400 Subject: [PATCH 1140/2366] x86/amd_node: Fix potential NULL pointer dereference amd_smn_read/write() are exported functions around __amd_smn_rw(), so they are always available even if amd_smn_init() fails. In that case, 'amd_roots' is NULL and __amd_smn_rw() will access uninitialized memory. Then, commit: 83518453074d ("x86/amd_node: Add SMN offsets to exclusive region access") added the 'smn_exclusive' flag, which indicated the calls to pci_request_config_region_exclusive() succeeded, to prevent concurrent userspace access. Commit: 0a4b61d9c2e4 ("x86/amd_node: Fix AMD root device caching") re-ordered initialization so pci_request_config_region_exclusive() is called earlier and a failure exits amd_smn_init() before allocating 'amd_roots'. The setting of 'smn_exclusive' moved to the end of amd_smn_init(), after 'amd_roots' is allocated. It became redundant and can be removed. Replace 'smn_exclusive' with directly checking 'amd_roots', to fix a potential NULL pointer dereference and to simplify the logic. [ bp: Reorg commit message, touchup comment. ] [ mingo: Rebase & further touchups. ] Fixes: 77466b798d59 ("x86/amd_node: Remove dependency on AMD_NB") Signed-off-by: Jason Andryuk Signed-off-by: Borislav Petkov (AMD) Signed-off-by: Ingo Molnar Reviewed-by: Yazen Ghannam Reviewed-by: Mario Limonciello (AMD) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260825214805.39148-3-jason.andryuk@amd.com --- arch/x86/kernel/amd_node.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/arch/x86/kernel/amd_node.c b/arch/x86/kernel/amd_node.c index 408b9fd48349..762585775b5a 100644 --- a/arch/x86/kernel/amd_node.c +++ b/arch/x86/kernel/amd_node.c @@ -38,7 +38,6 @@ static struct pci_dev **amd_roots; /* Protect the PCI config register pairs used for SMN. */ static DEFINE_MUTEX(smn_mutex); -static bool smn_exclusive; #define SMN_INDEX_OFFSET 0x60 #define SMN_DATA_OFFSET 0x64 @@ -91,11 +90,16 @@ static int __amd_smn_rw(u8 i_off, u8 d_off, u16 node, u32 address, u32 *value, b if (node >= amd_num_nodes()) return err; - root = amd_roots[node]; - if (!root) + /* + * Uninitialized amd_roots indicates pci_request_config_region_exclusive() + * didn't run or failed and thus the kernel cannot rely on having + * exclusive access to SMN registers so prevent that. + */ + if (!amd_roots) return err; - if (!smn_exclusive) + root = amd_roots[node]; + if (!root) return err; guard(mutex)(&smn_mutex); @@ -313,8 +317,6 @@ static int __init amd_smn_init(void) debugfs_create_file("value", 0600, debugfs_dir, NULL, &smn_value_fops); } - smn_exclusive = true; - return 0; } From d2929113b15bfc06793b852aeba3d2db6d79fcc9 Mon Sep 17 00:00:00 2001 From: Jasjeet Rangi Date: Wed, 12 Aug 2026 16:15:13 -0600 Subject: [PATCH 1141/2366] x86/MCE/AMD: Fix inverted interrupt enablement during storm handling mce_amd_handle_storm() currently does the opposite of what storm handling needs: it enables thresholding interrupts when a storm is detected and disables them when the storm subsides. Flip the "on" function argument before passing it to threshold_restart_bank() as it should have been done. To clarify: "on" to mce_handle_storm() means, the storm is on now when "on" is true, and off when "on" is false. [ bp: Simplify. ] Fixes: 5c4663ed1eac ("x86/mce: Handle AMD threshold interrupt storms") Signed-off-by: Jasjeet Rangi Signed-off-by: Borislav Petkov (AMD) Signed-off-by: Ingo Molnar Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260812221514.598842-2-jrangi@purestorage.com --- arch/x86/kernel/cpu/mce/amd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/cpu/mce/amd.c b/arch/x86/kernel/cpu/mce/amd.c index f916fb4c5d13..1cc20b855b7e 100644 --- a/arch/x86/kernel/cpu/mce/amd.c +++ b/arch/x86/kernel/cpu/mce/amd.c @@ -865,7 +865,7 @@ static void amd_deferred_error_interrupt(void) void mce_amd_handle_storm(unsigned int bank, bool on) { - threshold_restart_bank(bank, on); + threshold_restart_bank(bank, !on); } static void amd_reset_thr_limit(unsigned int bank) From 48a4ee65e677559776349128e6a81a6041986c99 Mon Sep 17 00:00:00 2001 From: Linfeng Sun Date: Tue, 8 Sep 2026 15:31:51 +0800 Subject: [PATCH 1142/2366] vduse: return compat ioctl results directly The compat handler handles VDUSE_IOTLB_GET_FD and VDUSE_VQ_GET_INFO, but then calls the native handler. Their different command sizes make native dispatch return -ENOIOCTLCMD. For GET_FD, this overwrites receive_fd()'s return value after the descriptor is installed, leaking one fd per call. Return handled compat results directly and use native dispatch only for other commands. Fixes: 455a2a1af926 ("vduse: fix compat handling for VDUSE_IOTLB_GET_FD/VDUSE_VQ_GET_INFO") Signed-off-by: Linfeng Sun Signed-off-by: Michael S. Tsirkin Message-ID: <20260908-fix-vduse_dev_compat_ioctl-v1-1-62264d9bfb8d@gmail.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 4dea4d6a3855..49a231bdf948 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -1882,11 +1882,11 @@ static long vduse_dev_compat_ioctl(struct file *file, unsigned int cmd, break; } default: - ret = -ENOIOCTLCMD; - break; + return vduse_dev_ioctl(file, cmd, + (unsigned long)compat_ptr(arg)); } - return vduse_dev_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); + return ret; } #else #define vduse_dev_compat_ioctl compat_ptr_ioctl From 4e17b5007b6664559cdad2b2fe270526cf786b5b Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 1 Sep 2026 18:56:44 -0700 Subject: [PATCH 1143/2366] bnxt_en: Only restore LRO if the device supports TPA With a P5+ device with firmware that reports max_aggs_supported == 0, it is possible to make LRO settable by attaching and detaching an XDP program even though the device does not support TPA. Fix this by testing BNXT_SUPPORTS_TPA before restoring the feature bit. Fixes: f0aa6a37a3db ("eth: bnxt: always recalculate features after XDP clearing, fix null-deref") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260828190900.1767611-1-joe%40dama.to Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902015652.2421609-2-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 8c6e2ee6bee4..343d70a98134 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -5026,7 +5026,8 @@ void bnxt_set_rx_skb_mode(struct bnxt *bp, bool page_mode) bnxt_get_max_rings(bp, &rx, &tx, true); if (rx > 1) { bp->flags &= ~BNXT_FLAG_NO_AGG_RINGS; - bp->dev->hw_features |= NETIF_F_LRO; + if (BNXT_SUPPORTS_TPA(bp)) + bp->dev->hw_features |= NETIF_F_LRO; } } From 5ce7f36c334d723954855ac769ede2fe0e8f89c8 Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 1 Sep 2026 18:56:45 -0700 Subject: [PATCH 1144/2366] bnxt_en: Don't free the live ring's TPA state on queue restart failure bnxt_queue_mem_alloc() shallow copies the live RX ring into the clone: memcpy(clone, rxr, sizeof(*rxr)); the code currently clears pointers that the clone owns (such as rx_agg_bmap), but rx_tpa and rx_tpa_idx_map are left pointing at memory of the live ring that was cloned. If an allocation failure happens later and the err_free_tpa_info label is taken, the live ring's memory can be freed while still in use. Fix this by initializing the clone's pointers to NULL to prevent live ring state from being freed inadvertently. Fixes: bd649c5cc958 ("bnxt_en: handle tpa_info in queue API implementation") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260828190900.1767611-1-joe%40dama.to Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902015652.2421609-3-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 343d70a98134..aaf658976865 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -16357,6 +16357,8 @@ static int bnxt_queue_mem_alloc(struct net_device *dev, clone->need_head_pool = false; clone->rx_page_size = qcfg->rx_page_size; clone->rx_agg_bmap = NULL; + clone->rx_tpa = NULL; + clone->rx_tpa_idx_map = NULL; rc = bnxt_alloc_rx_page_pool(bp, clone, rxr->page_pool->p.nid); if (rc) From b814dfbfeb0a68c9a52073f2caa05a2d5247a329 Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 1 Sep 2026 18:56:46 -0700 Subject: [PATCH 1145/2366] bnxt_en: Propagate TPA buffer allocation failures in bnxt_queue_mem_alloc() bnxt_alloc_one_tpa_info_data() returns -ENOMEM as soon as one allocation fails. This leaves the remaining rxr->rx_tpa[] entries zeroed. bnxt_queue_mem_alloc() discards that return value, so the partially initialized ring is installed by bnxt_queue_start(). Since the agg_id is picked by the hardware and bnxt_alloc_agg_idx maps it to a SW index in rxr->rx_tpa[], it is possible that an uninitialized slot can be chosen which would hand a zero DMA address to the device. Fix this by checking the return value of bnxt_alloc_one_tpa_info_data and unwinding, freeing the ring buffers. Fixes: bd649c5cc958 ("bnxt_en: handle tpa_info in queue API implementation") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260828190900.1767611-1-joe%40dama.to Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902015652.2421609-4-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index aaf658976865..ae7150c7de1f 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -16402,11 +16402,16 @@ static int bnxt_queue_mem_alloc(struct net_device *dev, bnxt_alloc_one_rx_ring_skb(bp, clone, idx); if (bp->flags & BNXT_FLAG_AGG_RINGS) bnxt_alloc_one_rx_ring_netmem(bp, clone, idx); - if (bp->flags & BNXT_FLAG_TPA) - bnxt_alloc_one_tpa_info_data(bp, clone); + if (bp->flags & BNXT_FLAG_TPA) { + rc = bnxt_alloc_one_tpa_info_data(bp, clone); + if (rc) + goto err_free_rx_ring_skbs; + } return 0; +err_free_rx_ring_skbs: + bnxt_free_one_rx_ring_skbs(bp, clone); err_free_tpa_info: bnxt_free_one_tpa_info(bp, clone); err_free_rx_agg_ring: From 961e2a17c5e3559b3f8654d2daabdd25a42e770a Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 1 Sep 2026 18:56:47 -0700 Subject: [PATCH 1146/2366] bnxt_en: Handle buffer allocation failure in bnxt_rx_ring_reset() bnxt_rx_ring_reset() frees the ring buffers and then reallocates them, ignoring the result. bnxt_alloc_one_rx_ring() can fail in bnxt_alloc_one_tpa_info_data(), which returns -ENOMEM on the first failed allocation and leaves the remaining rxr->rx_tpa[] entries zeroed. The error isn't propagated up, so the loop in bnxt_rx_ring_reset continues and at the end the code re-enables TPA with partially unallocated rx_tpa array. This means that when the agg_id from hardware is mapped to a SW index in rxr->rx_tpa[], an uninitialized slot can be chosen which would hand a zero DMA address to the device. Fix this by falling back to a global reset, which is what the existing code already does when other functions fail, but unlike the other failure cases this particular failure has to return because TPA can't be re-enabled since the allocation failed. Fixes: 8fbf58e17dce ("bnxt_en: Implement RX ring reset in response to buffer errors.") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260828190900.1767611-1-joe%40dama.to Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902015652.2421609-5-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index ae7150c7de1f..a2283fd9cdfc 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -14628,7 +14628,14 @@ static void bnxt_rx_ring_reset(struct bnxt *bp) rxr->rx_sw_agg_prod = 0; rxr->rx_next_cons = 0; rxr->bnapi->in_reset = false; - bnxt_alloc_one_rx_ring(bp, i); + rc = bnxt_alloc_one_rx_ring(bp, i); + if (rc) { + netdev_warn(bp->dev, "RX ring reset failed to allocate buffers, rc = %d, falling back to global reset\n", + rc); + bnxt_reset_task(bp, true); + bnxt_rtnl_unlock_sp(bp); + return; + } cpr = &rxr->bnapi->cp_ring; cpr->sw_stats->rx.rx_resets++; if (bp->flags & BNXT_FLAG_AGG_RINGS) From 8e6a850c0746bb4be167aedf1ee57469fcda09a9 Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 1 Sep 2026 18:56:48 -0700 Subject: [PATCH 1147/2366] bnxt_en: Propagate RX ring init failures in bnxt_init_nic() bnxt_init_rx_rings() returns an error when bnxt_alloc_one_rx_ring() fails, but bnxt_init_nic() discards that return value and calls bnxt_init_chip(), which enables TPA. If an allocation fails, this could leave rxr->rx_tpa[] partially zeroed and TPA would be enabled over an array with zeroed entries. This would lead to a zeroed DMA address being handed out if the agg_idx is translated to a SW index at a zeroed entry. Fix this by propagating the error out of bnxt_init_nic(). Both callers already check its return value and unwind with bnxt_free_skbs() and bnxt_free_mem(), which tolerate a partially initialized RX ring. Fixes: c0c050c58d84 ("bnxt_en: New Broadcom ethernet driver.") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260828190900.1767611-1-joe%40dama.to Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902015652.2421609-6-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index a2283fd9cdfc..32c59b3d1cbc 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -11363,8 +11363,13 @@ static int bnxt_shutdown_nic(struct bnxt *bp, bool irq_re_init) static int bnxt_init_nic(struct bnxt *bp, bool irq_re_init) { + int rc; + bnxt_init_cp_rings(bp); - bnxt_init_rx_rings(bp); + rc = bnxt_init_rx_rings(bp); + if (rc) + return rc; + bnxt_init_tx_rings(bp); bnxt_init_ring_grps(bp, irq_re_init); bnxt_init_vnics(bp); From c0aceaf65b70b3c000e70dd867f3a673015f24ca Mon Sep 17 00:00:00 2001 From: Joe Damato Date: Tue, 1 Sep 2026 18:56:49 -0700 Subject: [PATCH 1148/2366] bnxt_en: Bound SW TPA IDs to prevent crashes FW supports up to 1024 concurrent TPAs, so the FW TPA ID is in the range 0..1023 (see commit ec4d8e7cf024 ("bnxt_en: Add TPA ID mapping logic for 57500 chips.")). bnxt_alloc_agg_idx is intended to wrap the FW ID down to a software ID which is used to index rxr->rx_tpa, and to generate a mapping between FW IDs and the wrapped software ID. On a 57608 with firmware version 233, the firmware advertises 32 concurrent TPAs. As of the commit under fixes, bp->max_tpa on this NIC is set to 32. If the software ID from bnxt_alloc_agg_idx is above 31, this results in an invalid address being loaded on this line: tpa_info = &rxr->rx_tpa[agg_id]; because rx_tpa is allocated with only bp->max_tpa (32) entries. Writes to tpa_info later in the code are out of bounds. This bug results in a crash at boot: Oops: general protection fault, kernel NULL pointer dereference 0x8: 0000 [#1] SMP NOPTI RIP: 0010:bnxt_rx_pkt+0xc0/0x1560 RSP: 0018:ffffc900009b8c78 EFLAGS: 00010246 RAX: 0000000000000000 RBX: 0000000000000048 RCX: 0000000206682516 RDX: ffffc900009b8db4 RSI: 0000000000000000 RDI: 01ffffff038fe1c0 RBP: ffffc9006e687480 R08: ffffc9006e687000 R09: 0000000000003048 R10: 0000000000000480 R11: ffff8881c6083900 R12: 0000000006682516 R13: ffff8881c6095400 R14: 0000000000000016 R15: ffff8881c6b66680 FS: 0000000000000000(0000) GS:ffff88fef3c77000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007fc8bda40584 CR3: 000000807c812001 CR4: 0000000008772ef0 PKRU: 55555554 Call Trace: ? __netif_receive_skb_list_core+0x1ca/0x250 __bnxt_poll_work+0x152/0x280 bnxt_poll_p5+0x1cd/0x480 __napi_poll+0x30/0x180 net_rx_action+0x20b/0x3b0 ? note_gp_changes+0x53/0xe0 ? tick_setup_sched_timer+0x180/0x180 ? __napi_schedule+0x9a/0xb0 ? bnxt_msix+0x24/0x30 handle_softirqs+0xdd/0x2c0 __irq_exit_rcu.llvm.3171231171502365008+0x47/0xf0 common_interrupt+0x85/0x90 asm_common_interrupt+0x22/0x40 This stack trace is from a crash triggered when an out of bounds rx_tpa is dereferenced. The invalid write mentioned above is silent in this particular crash. Fix this by allocating rx_tpa with bp->max_tpa rounded up to the next power of 2 (bp->max_tpa_roundup_size) entries and masking the FW TPA ID with that size, so the wrapped ID can never index past the end of the array. Fixes: 54c28fab2fa5 ("bnxt_en: Set bp->max_tpa according to what the FW supports") Reported-by: Raphael Cardoso Fernandes Suggested-by: Michael Chan Cc: stable@vger.kernel.org Signed-off-by: Joe Damato Link: https://patch.msgid.link/20260902015652.2421609-7-joe@dama.to Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 27 ++++++++++++++--------- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 2 +- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 32c59b3d1cbc..d7728d0c5b6e 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -1534,14 +1534,16 @@ static int bnxt_discard_rx(struct bnxt *bp, struct bnxt_cp_ring_info *cpr, return 0; } -static u16 bnxt_alloc_agg_idx(struct bnxt_rx_ring_info *rxr, u16 agg_id) +static u16 bnxt_alloc_agg_idx(struct bnxt *bp, struct bnxt_rx_ring_info *rxr, + u16 agg_id) { struct bnxt_tpa_idx_map *map = rxr->rx_tpa_idx_map; - u16 idx = agg_id & MAX_TPA_P5_MASK; + u16 idx = agg_id & (bp->max_tpa_roundup_size - 1); if (test_bit(idx, map->agg_idx_bmap)) { - idx = find_first_zero_bit(map->agg_idx_bmap, MAX_TPA_P5); - if (idx >= MAX_TPA_P5) + idx = find_first_zero_bit(map->agg_idx_bmap, + bp->max_tpa_roundup_size); + if (idx >= bp->max_tpa_roundup_size) return INVALID_HW_RING_ID; } __set_bit(idx, map->agg_idx_bmap); @@ -1606,7 +1608,7 @@ static void bnxt_tpa_start(struct bnxt *bp, struct bnxt_rx_ring_info *rxr, if (bp->flags & BNXT_FLAG_CHIP_P5_PLUS) { agg_id = TPA_START_AGG_ID_P5(tpa_start); - agg_id = bnxt_alloc_agg_idx(rxr, agg_id); + agg_id = bnxt_alloc_agg_idx(bp, rxr, agg_id); if (unlikely(agg_id == INVALID_HW_RING_ID)) { netdev_warn(bp->dev, "Unable to allocate agg ID for ring %d, agg 0x%x\n", rxr->bnapi->index, @@ -3604,7 +3606,7 @@ static void bnxt_free_one_tpa_info_data(struct bnxt *bp, { int i; - for (i = 0; i < bp->max_tpa; i++) { + for (i = 0; i < bp->max_tpa_roundup_size; i++) { struct bnxt_tpa_info *tpa_info = &rxr->rx_tpa[i]; u8 *data = tpa_info->data; @@ -3801,7 +3803,7 @@ static void bnxt_free_one_tpa_info(struct bnxt *bp, kfree(rxr->rx_tpa_idx_map); rxr->rx_tpa_idx_map = NULL; if (rxr->rx_tpa) { - for (i = 0; i < bp->max_tpa; i++) { + for (i = 0; i < bp->max_tpa_roundup_size; i++) { kfree(rxr->rx_tpa[i].agg_arr); rxr->rx_tpa[i].agg_arr = NULL; } @@ -3827,13 +3829,14 @@ static int bnxt_alloc_one_tpa_info(struct bnxt *bp, struct rx_agg_cmp *agg; int i; - rxr->rx_tpa = kzalloc_objs(struct bnxt_tpa_info, bp->max_tpa); + rxr->rx_tpa = kzalloc_objs(struct bnxt_tpa_info, + bp->max_tpa_roundup_size); if (!rxr->rx_tpa) return -ENOMEM; if (!(bp->flags & BNXT_FLAG_CHIP_P5_PLUS)) return 0; - for (i = 0; i < bp->max_tpa; i++) { + for (i = 0; i < bp->max_tpa_roundup_size; i++) { agg = kzalloc_objs(*agg, MAX_SKB_FRAGS); if (!agg) return -ENOMEM; @@ -3852,6 +3855,9 @@ static int bnxt_alloc_tpa_info(struct bnxt *bp) bp->max_tpa = MAX_TPA; if (bp->flags & BNXT_FLAG_CHIP_P5_PLUS) { + /* TPA is not supported at all, so there is nothing to + * allocate. + */ if (!bp->max_tpa_v2) return 0; bp->max_tpa = min_t(u16, bp->max_tpa_v2, MAX_TPA_P5); @@ -3859,6 +3865,7 @@ static int bnxt_alloc_tpa_info(struct bnxt *bp) if (bp->max_tpa <= 32 && BNXT_CHIP_P5(bp) && !BNXT_NPAR(bp)) bp->max_tpa = MAX_TPA_P5; } + bp->max_tpa_roundup_size = roundup_pow_of_two(bp->max_tpa); for (i = 0; i < bp->rx_nr_rings; i++) { struct bnxt_rx_ring_info *rxr = &bp->rx_ring[i]; @@ -4571,7 +4578,7 @@ static int bnxt_alloc_one_tpa_info_data(struct bnxt *bp, u8 *data; int i; - for (i = 0; i < bp->max_tpa; i++) { + for (i = 0; i < bp->max_tpa_roundup_size; i++) { data = __bnxt_alloc_rx_frag(bp, &mapping, rxr, GFP_KERNEL); if (!data) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index dc5a16ec5943..c673b2ce4a0d 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -789,7 +789,6 @@ struct nqe_cn { #define MAX_TPA 64 #define MAX_TPA_P5 256 -#define MAX_TPA_P5_MASK (MAX_TPA_P5 - 1) #define MAX_TPA_SEGS_P5 0x3f #if (BNXT_PAGE_SHIFT == 16) @@ -2381,6 +2380,7 @@ struct bnxt { u16 max_tpa_v2; u16 max_tpa; + u16 max_tpa_roundup_size; u32 rx_buf_size; u32 rx_buf_use_size; /* useable size */ u16 rx_offset; From 0523d5c52a450590bf5992bd6925394f3cc403e8 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 3 Sep 2026 12:36:51 +0000 Subject: [PATCH 1149/2366] net: macb: zero the link settings taprio reads back macb_taprio_setup_replace() calls phylink_ethtool_ksettings_get() with an uninitialised kset, and kset is not only an out-parameter. On a fixed link, or an in-band link with no PHY, phylink writes speed and duplex only if kset->base.rate_matching already reads RATE_MATCH_NONE, a field it never writes itself; in PHY mode before the PHY is attached it writes port and supported and nothing more. Either way the speed read back afterwards can be stack garbage. The ethtool core zeroes the structure on every path into the op, which is why its callers never see this; taprio is the only in-kernel caller passing its own variable. Fixes: 89934dbf169e ("net: macb: Add TAPRIO traffic scheduling support") Assisted-by: LLM Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260903123652.23900-2-f@lex.la Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cadence/macb_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 8469df0d89c3..9be28b4fddb8 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -4300,9 +4300,9 @@ static int macb_taprio_setup_replace(struct net_device *netdev, u64 total_on_time = 0, start_time_sec = 0, start_time = conf->base_time; u32 configured_queues = 0, speed = 0, start_time_nsec; struct macb_queue_enst_config *enst_queue; - struct tc_taprio_sched_entry *entry; + struct ethtool_link_ksettings kset = {}; struct macb *bp = netdev_priv(netdev); - struct ethtool_link_ksettings kset; + struct tc_taprio_sched_entry *entry; struct macb_queue *queue; u32 queue_mask; u8 queue_id; From 2b6c0e25a3d713c4032e45f212bdd9e14c50f8a0 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 3 Sep 2026 12:36:52 +0000 Subject: [PATCH 1150/2366] net: macb: reject an unknown link speed in the taprio setup speed is a u32, so SPEED_UNKNOWN arrives as 0xffffffff and passes the "speed <= 0" check, which only ever catches zero. That is what an autonegotiating link reports while it is down: the limit derived from the speed collapses to a nanosecond at most and the first entry fails with a misleading "exceeds hardware limit". Zero stays covered, it is what an interface that was never opened reports, and enst_max_hw_interval() divides by it. Say which case it was in the error. Fixes: 89934dbf169e ("net: macb: Add TAPRIO traffic scheduling support") Assisted-by: LLM Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260903123652.23900-3-f@lex.la Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cadence/macb_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 9be28b4fddb8..0e75339fa206 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -4329,8 +4329,8 @@ static int macb_taprio_setup_replace(struct net_device *netdev, } speed = kset.base.speed; - if (unlikely(speed <= 0)) { - netdev_err(netdev, "Invalid speed: %d\n", speed); + if (unlikely(speed == SPEED_UNKNOWN || !speed)) { + netdev_err(netdev, "Invalid speed %d, link-down?\n", speed); return -EINVAL; } From d3df7ed4683f8c1b35672a40bf20af6a08ef8ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Mon, 7 Sep 2026 12:36:08 +0200 Subject: [PATCH 1151/2366] landlock: Clean up ruleset validation checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit landlock_merge_ruleset() checks for a NULL ruleset after dereferencing it in lockdep_assert_held(). Move the assertion after the check so the defensive path remains effective. The mask-validation comment originated in landlock_add_fs_access_mask() to explain that its WARN_ON_ONCE() checked a caller invariant. It became self-referential when this helper and its network and scope counterparts were inlined into landlock_create_ruleset(). Restate the invariant without naming the caller. Keep both as defensive callee checks. Moving the assertion preserves the NULL check's ability to warn and return -EINVAL, while invalid masks remain warned about and masked. Reported-by: Günther Noack Closes: https://patch.msgid.link/aobYhIt3vcs2xN0b@google.com Closes: https://patch.msgid.link/aobasxUDQ8b7GYXl@google.com Reviewed-by: Günther Noack Link: https://patch.msgid.link/20260907103609.113325-1-mic@digikod.net Signed-off-by: Mickaël Salaün --- security/landlock/domain.c | 3 ++- security/landlock/ruleset.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/security/landlock/domain.c b/security/landlock/domain.c index 93c7104fd6b2..4031b581be07 100644 --- a/security/landlock/domain.c +++ b/security/landlock/domain.c @@ -439,10 +439,11 @@ landlock_merge_ruleset(struct landlock_domain *const parent, int err; might_sleep(); - lockdep_assert_held(&ruleset->lock); if (WARN_ON_ONCE(!ruleset)) return ERR_PTR(-EINVAL); + lockdep_assert_held(&ruleset->lock); + if (parent) { if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS) return ERR_PTR(-E2BIG); diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 0d07707523cd..a5d135d085cb 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -58,7 +58,7 @@ landlock_create_ruleset(const access_mask_t fs_access_mask, new_ruleset->id = landlock_get_id_range(1); #endif /* CONFIG_TRACEPOINTS */ - /* Should already be checked in landlock_create_ruleset(). */ + /* The caller must only pass supported access rights and scopes. */ if (fs_access_mask) { const access_mask_t mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS; From 3125751cd1de76a01b18daef399d6b88d159bd17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Mon, 7 Sep 2026 17:43:58 +0200 Subject: [PATCH 1152/2366] landlock: Bound escaped trace path output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filesystem paths may expand fourfold when trace text escapes spaces and other untrusted bytes. A sufficiently long representation can exhaust the shared scratch sequence. A sibling __print_flags() helper may then return an unterminated one-past pointer because TP_printk() argument ordering is unspecified. Use a fixed budget rather than the scratch space available at call time, so output does not vary with sibling evaluation order. Limit an untrusted string to three quarters of the trace sequence, leaving the rest for sibling helpers and final event metadata. Compute and commit complete escaped output transactionally so an exact fill cannot consume the terminating NUL or poison the scratch sequence. For strings that exceed the limit, retain the largest prefix ending at a complete escape unit, then append a raw UTF-8 ellipsis. Keep the helper's existing octal fallback so complete values remain unchanged. Hex fallback would consume the same four bytes per escaped byte without increasing the prefix or strengthening the marker. ESCAPE_NAP renders every non-ASCII input byte in octal, so legitimate data cannot reproduce the marker without being escaped. Cc: Günther Noack Link: https://patch.msgid.link/20260907154401.124362-1-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 70 +++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index f82588f6f90e..d05253afaf59 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -28,6 +28,16 @@ struct task_struct; #ifdef CREATE_TRACE_POINTS +/* About 6 KiB, leaving about 2 KiB for sibling helpers and fixed fields. */ +#define TRACE_UNTRUSTED_STR_OUTPUT_SIZE \ + (TRACE_SEQ_BUFFER_SIZE - TRACE_SEQ_BUFFER_SIZE / 4) + +/* + * A raw UTF-8 ellipsis (…) marks truncation and cannot collide with escaped + * input: ESCAPE_NAP renders every non-ASCII input byte in octal. + */ +#define TRACE_TRUNCATION_MARKER "\xe2\x80\xa6" + /* * Escapes @len bytes of an untrusted string into the trace sequence @p so it * cannot inject field separators or control characters into the ftrace text @@ -37,33 +47,59 @@ struct task_struct; * NUL-terminated or carries embedded NUL bytes (an abstract socket name) is * escaped in full instead of being truncated at the first NUL. * - * Return: a pointer into @p's buffer, or NULL if @src is NULL or the buffer is - * exhausted (normal when the trace buffer is full). + * Strings that exceed the output limit retain the largest complete escaped + * prefix followed by the truncation marker. + * + * Return: a pointer into @p's buffer, or NULL if @src is NULL or the fixed + * output reservation is unavailable. */ static inline const char * __trace_print_untrusted_str(struct trace_seq *p, const char *src, size_t len) { + const unsigned int escape_flags = ESCAPE_SPACE | ESCAPE_SPECIAL | + ESCAPE_NAP | ESCAPE_APPEND | + ESCAPE_OCTAL; + const size_t marker_len = sizeof(TRACE_TRUNCATION_MARKER) - 1; + size_t buf_size, prefix_len, prefix_size; int escaped_size; char *buf; - size_t buf_size = seq_buf_get_buf(&p->seq, &buf); - const char *ret = trace_seq_buffer_ptr(p); + const char *ret; - /* Buffer exhaustion is normal when the trace buffer is full. */ - if (!src || buf_size == 0) + buf_size = seq_buf_get_buf(&p->seq, &buf); + if (!src || buf_size < TRACE_UNTRUSTED_STR_OUTPUT_SIZE) return NULL; - escaped_size = - string_escape_mem(src, len, buf, buf_size, - ESCAPE_SPACE | ESCAPE_SPECIAL | ESCAPE_NAP | - ESCAPE_APPEND | ESCAPE_OCTAL, - " ='\"\\"); - if (unlikely(escaped_size >= buf_size)) { - /* We need some room for the final '\0'. */ - seq_buf_set_overflow(&p->seq); - p->full = 1; - return NULL; + ret = trace_seq_buffer_ptr(p); + escaped_size = string_escape_mem(src, len, buf, + TRACE_UNTRUSTED_STR_OUTPUT_SIZE, + escape_flags, " ='\"\\"); + if (likely(escaped_size < TRACE_UNTRUSTED_STR_OUTPUT_SIZE)) { + seq_buf_commit(&p->seq, escaped_size); + trace_seq_putc(p, 0); + return ret; } - seq_buf_commit(&p->seq, escaped_size); + + prefix_len = 0; + prefix_size = 0; + while (prefix_len < len) { + const char *const src_char = src + prefix_len; + int char_size; + + char_size = string_escape_mem(src_char, 1, NULL, 0, + escape_flags, " ='\"\\"); + if (char_size > TRACE_UNTRUSTED_STR_OUTPUT_SIZE - marker_len - + 1 - prefix_size) + break; + prefix_size += char_size; + prefix_len++; + } + + escaped_size = string_escape_mem(src, prefix_len, buf, prefix_size, + escape_flags, " ='\"\\"); + if (WARN_ON_ONCE(escaped_size != prefix_size)) + return NULL; + memcpy(buf + prefix_size, TRACE_TRUNCATION_MARKER, marker_len); + seq_buf_commit(&p->seq, prefix_size + marker_len); trace_seq_putc(p, 0); return ret; } From d41d0021a6ea3e9fcd14126a00fead47f981c46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Mon, 7 Sep 2026 17:43:59 +0200 Subject: [PATCH 1153/2366] landlock: Test trace path output boundaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use focused KUnit tests to exercise the renderer's internal boundary and composition contracts with synthetic scratch states, including both sibling-helper evaluation orders. Check the exact output and reservation boundaries, including a four-byte octal escape accepted at exact capacity and rejected one byte short. Also verify an unchanged cursor on failure, that bracketed process names and embedded NUL bytes remain data, and that input ellipsis bytes are escaped rather than mistaken for the raw truncation marker. The composition test requires generic trace output helpers. Enable CONFIG_FTRACE and CONFIG_SCHED_TRACER because the latter selects the otherwise-hidden CONFIG_TRACING support required by trace_print_flags_seq(). Use kselftests to exercise the complete tracefs path for both affected filesystem events. A valid path containing 2640 spaces exceeds the scratch output budget. Require its escaped prefix to end in the raw UTF-8 ellipsis while access_rights and blockers remain intact. This division keeps the exact safety contract compiler-independent while proving that real tracepoints preserve their surrounding symbolic fields. The end-to-end assertions fail after a full fix revert with both GCC and Clang, while the composition KUnit test fails if the scratch reserve is removed. Cc: Günther Noack Link: https://patch.msgid.link/20260907154401.124362-2-mic@digikod.net Signed-off-by: Mickaël Salaün --- security/landlock/.kunitconfig | 2 + security/landlock/trace.c | 182 ++++++++++++++++++ .../selftests/landlock/trace_fs_test.c | 160 +++++++++++++++ 3 files changed, 344 insertions(+) diff --git a/security/landlock/.kunitconfig b/security/landlock/.kunitconfig index f9423f01ac5b..fe36228d37ea 100644 --- a/security/landlock/.kunitconfig +++ b/security/landlock/.kunitconfig @@ -1,6 +1,8 @@ CONFIG_AUDIT=y +CONFIG_FTRACE=y CONFIG_KUNIT=y CONFIG_NET=y +CONFIG_SCHED_TRACER=y CONFIG_SECURITY=y CONFIG_SECURITY_LANDLOCK=y CONFIG_SECURITY_LANDLOCK_KUNIT_TEST=y diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 2ea7aac8d75d..8c21e5de6f0d 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -6,6 +6,7 @@ * Copyright © 2026 Cloudflare, Inc. */ +#include #include #include #include @@ -183,3 +184,184 @@ void landlock_trace_denial( break; } } + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void test_trace_seq_init(struct trace_seq *const seq, const size_t size) +{ + memset(seq, 0, sizeof(*seq)); + seq_buf_init(&seq->seq, seq->buffer, size); +} + +static void test_untrusted_str_data(struct kunit *const test) +{ + const char binary[] = { 'a', '\0', '<' }; + static const char ellipsis[] = "\xe2\x80\xa6"; + struct trace_seq *const seq = + kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); + const char *output; + + KUNIT_ASSERT_NOT_NULL(test, seq); + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, "", 10); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, ""); + + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, binary, sizeof(binary)); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "a\\000<"); + + /* Input ellipsis bytes are escaped and cannot mimic the raw marker. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, ellipsis, + sizeof(ellipsis) - 1); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "\\342\\200\\246"); +} + +static void test_untrusted_str_boundaries(struct kunit *const test) +{ + static const char escaped_space[] = "\\040"; + const size_t output_size = TRACE_UNTRUSTED_STR_OUTPUT_SIZE; + const size_t marker_len = sizeof(TRACE_TRUNCATION_MARKER) - 1; + const size_t escape_len = sizeof(escaped_space) - 1; + const size_t exact_prefix_len = + output_size - marker_len - 1 - escape_len; + const size_t short_prefix_len = exact_prefix_len + 1; + struct trace_seq *const seq = + kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); + char *const input = kunit_kmalloc(test, output_size + 1, GFP_KERNEL); + char *const expected = kunit_kmalloc(test, output_size, GFP_KERNEL); + const char *output; + + KUNIT_ASSERT_NOT_NULL(test, seq); + KUNIT_ASSERT_NOT_NULL(test, input); + KUNIT_ASSERT_NOT_NULL(test, expected); + + /* The escaped string and its trailing NUL exactly fit the limit. */ + memset(input, 'a', output_size - 1); + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, input, output_size - 1); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_EQ(test, seq->seq.len, output_size); + KUNIT_EXPECT_EQ(test, memcmp(output, input, output_size - 1), 0); + + /* Stop before a four-byte escape when only three bytes remain. */ + memset(input, 'a', short_prefix_len); + input[short_prefix_len] = ' '; + memset(input + short_prefix_len + 1, 'b', 5); + memset(expected, 'a', short_prefix_len); + memcpy(expected + short_prefix_len, TRACE_TRUNCATION_MARKER, + marker_len + 1); + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, input, short_prefix_len + 6); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, expected); + + /* Include a four-byte escape that exactly fills the prefix capacity. */ + memset(input, 'a', exact_prefix_len); + input[exact_prefix_len] = ' '; + memset(input + exact_prefix_len + 1, 'b', marker_len + 1); + memset(expected, 'a', exact_prefix_len); + memcpy(expected + exact_prefix_len, escaped_space, escape_len); + memcpy(expected + exact_prefix_len + escape_len, + TRACE_TRUNCATION_MARKER, marker_len + 1); + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, input, + exact_prefix_len + marker_len + 2); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, expected); + + /* Literal backslashes remain escaped in complete output. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, "/\\000", 5); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "/\\\\000"); +} + +static void test_untrusted_str_cursor(struct kunit *const test) +{ + const size_t padding_len = + TRACE_SEQ_BUFFER_SIZE - TRACE_UNTRUSTED_STR_OUTPUT_SIZE + 1; + struct trace_seq *const seq = + kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); + char *const padding = kunit_kzalloc(test, padding_len, GFP_KERNEL); + const char *output; + + KUNIT_ASSERT_NOT_NULL(test, seq); + KUNIT_ASSERT_NOT_NULL(test, padding); + + /* Accept available space exactly equal to the fixed reservation. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + trace_seq_putmem(seq, padding, padding_len - 1); + output = __trace_print_untrusted_str(seq, "/a", 2); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "/a"); + KUNIT_EXPECT_EQ(test, seq->seq.len, padding_len - 1 + sizeof("/a")); + + /* Reject one byte less without changing the scratch cursor. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + trace_seq_putmem(seq, padding, padding_len); + output = __trace_print_untrusted_str(seq, "/a", 2); + KUNIT_EXPECT_NULL(test, output); + KUNIT_EXPECT_EQ(test, seq->seq.len, padding_len); +} + +static void test_untrusted_str_composition(struct kunit *const test) +{ + static const struct trace_print_flags flags[] = { + { .mask = 1, .name = "read" }, + }; + const size_t output_size = TRACE_UNTRUSTED_STR_OUTPUT_SIZE; + const size_t prefix_len = output_size - sizeof(TRACE_TRUNCATION_MARKER); + struct trace_seq *const seq = + kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); + char *const expected = kunit_kmalloc(test, output_size, GFP_KERNEL); + char *const path = kunit_kmalloc(test, output_size, GFP_KERNEL); + const char *flags_output, *path_output; + + KUNIT_ASSERT_NOT_NULL(test, seq); + KUNIT_ASSERT_NOT_NULL(test, expected); + KUNIT_ASSERT_NOT_NULL(test, path); + memset(path, 'a', output_size); + memset(expected, 'a', prefix_len); + memcpy(expected + prefix_len, TRACE_TRUNCATION_MARKER, + sizeof(TRACE_TRUNCATION_MARKER)); + + /* Exercise both legal TP_printk() sibling evaluation orders. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + path_output = __trace_print_untrusted_str(seq, path, output_size); + flags_output = + trace_print_flags_seq(seq, "|", 1, flags, ARRAY_SIZE(flags)); + KUNIT_ASSERT_NOT_NULL(test, path_output); + KUNIT_EXPECT_STREQ(test, path_output, expected); + KUNIT_EXPECT_STREQ(test, flags_output, "read"); + + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + flags_output = + trace_print_flags_seq(seq, "|", 1, flags, ARRAY_SIZE(flags)); + path_output = __trace_print_untrusted_str(seq, path, output_size); + KUNIT_ASSERT_NOT_NULL(test, path_output); + KUNIT_EXPECT_STREQ(test, path_output, expected); + KUNIT_EXPECT_STREQ(test, flags_output, "read"); +} + +static struct kunit_case test_cases[] = { + /* clang-format off */ + KUNIT_CASE(test_untrusted_str_data), + KUNIT_CASE(test_untrusted_str_boundaries), + KUNIT_CASE(test_untrusted_str_cursor), + KUNIT_CASE(test_untrusted_str_composition), + {} + /* clang-format on */ +}; + +static struct kunit_suite test_suite = { + .name = "landlock_trace", + .test_cases = test_cases, +}; + +kunit_test_suite(test_suite); + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ diff --git a/tools/testing/selftests/landlock/trace_fs_test.c b/tools/testing/selftests/landlock/trace_fs_test.c index 5220f6a4bee1..4543a25c1f55 100644 --- a/tools/testing/selftests/landlock/trace_fs_test.c +++ b/tools/testing/selftests/landlock/trace_fs_test.c @@ -6,8 +6,10 @@ */ #define _GNU_SOURCE +#include #include #include +#include #include #include #include @@ -23,6 +25,63 @@ #define TRACE_TASK "trace_fs_test" +/* Mirrors TRACE_SEQ_SIZE, conservatively larger than the usable buffer. */ +#define TRACE_SEQUENCE_SIZE 8192 +#define OCTAL_ESCAPE_LEN 4 +#define LONG_PATH_COMPONENT_COUNT 11 +#define LONG_PATH_COMPONENT_LEN 240 +#define LONG_PATH_LEN \ + (LONG_PATH_COMPONENT_COUNT * (LONG_PATH_COMPONENT_LEN + 1) + \ + sizeof("/tmp")) +#define LONG_ESCAPED_PATH_LEN \ + (LONG_PATH_COMPONENT_COUNT * LONG_PATH_COMPONENT_LEN * OCTAL_ESCAPE_LEN) + +static_assert(LONG_ESCAPED_PATH_LEN > TRACE_SEQUENCE_SIZE, + "escaped path must exceed the trace sequence"); +static_assert(LONG_PATH_LEN < PATH_MAX, "path must fit in PATH_MAX"); + +static void create_long_path(struct __test_metadata *const _metadata, + char *path) +{ + size_t path_len; + + strcpy(path, "/tmp"); + path_len = strlen(path); + + set_cap(_metadata, CAP_SYS_ADMIN); + ASSERT_EQ(0, mount("tmpfs", "/tmp", "tmpfs", 0, NULL)); + clear_cap(_metadata, CAP_SYS_ADMIN); + + for (int i = 0; i < LONG_PATH_COMPONENT_COUNT; i++) { + path[path_len++] = '/'; + memset(path + path_len, ' ', LONG_PATH_COMPONENT_LEN); + path_len += LONG_PATH_COMPONENT_LEN; + path[path_len] = '\0'; + ASSERT_EQ(0, mkdir(path, 0700)); + } +} + +static void expect_truncated_path(struct __test_metadata *const _metadata, + const char *const trace, + const char *const event_regex) +{ + static const char marker[] = "\xe2\x80\xa6"; + char *path; + size_t path_len; + + path = malloc(TRACE_SEQUENCE_SIZE); + ASSERT_NE(NULL, path); + ASSERT_EQ(0, tracefs_extract_field(trace, event_regex, "path", path, + TRACE_SEQUENCE_SIZE)); + EXPECT_EQ(path, strstr(path, "/tmp/")); + EXPECT_NE(NULL, strstr(path, "\\040")); + + path_len = strlen(path); + ASSERT_LE(sizeof(marker) - 1, path_len); + EXPECT_STREQ(marker, path + path_len - (sizeof(marker) - 1)); + free(path); +} + /* * Like REGEX_DENY_ACCESS_FS(), but pins the logged field to a specific value * ("0" or "1") so a test can tell a suppressed (quiet) denial from a logged @@ -183,6 +242,107 @@ TEST_F(trace_fs, add_rule_fs) free(buf); } +/* + * Verifies that a path whose escaping exceeds the trace scratch sequence does + * not corrupt a sibling symbolic field. + */ +TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) +{ + static const char access_prefix[] = "execute|write_file|read_file|"; + static const char access_suffix[] = "|ioctl_dev|resolve_unix"; + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, + }; + struct landlock_path_beneath_attr path_beneath = { + .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE, + }; + char path[PATH_MAX]; + char *buf, field_buf[256]; + size_t field_len; + int ruleset_fd, count; + + create_long_path(_metadata, path); + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + path_beneath.parent_fd = open(path, O_PATH | O_DIRECTORY | O_CLOEXEC); + ASSERT_LE(0, path_beneath.parent_fd); + + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + &path_beneath, 0)); + ASSERT_EQ(0, close(path_beneath.parent_fd)); + ASSERT_EQ(0, close(ruleset_fd)); + + buf = tracefs_read_buf(); + ASSERT_NE(NULL, buf); + + count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + EXPECT_EQ(1, count) + { + TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf); + } + + /* + * The marker catches a full revert with any compiler. The symbolic + * field also catches scratch-sequence poisoning when the compiler + * evaluates the overflowing path first, as GCC currently does. + */ + ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), + "access_rights", field_buf, + sizeof(field_buf))); + EXPECT_EQ(0, + strncmp(field_buf, access_prefix, sizeof(access_prefix) - 1)); + EXPECT_EQ(NULL, strstr(field_buf, "|refer|")); + field_len = strlen(field_buf); + ASSERT_LE(sizeof(access_suffix) - 1, field_len); + EXPECT_STREQ(access_suffix, + field_buf + field_len - (sizeof(access_suffix) - 1)); + expect_truncated_path(_metadata, buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + + free(buf); +} + +/* + * Verifies that an overflowing denied path does not corrupt its sibling + * symbolic blockers field. + */ +TEST_F(trace_fs, deny_access_fs_escaped_path_overflow) +{ + char path[PATH_MAX]; + char *buf, field_buf[64]; + int count, err; + + create_long_path(_metadata, path); + ASSERT_EQ(0, tracefs_clear_buf()); + + sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR, + LANDLOCK_ACCESS_FS_READ_DIR, path); + + buf = tracefs_read_buf(); + ASSERT_NE(NULL, buf); + + count = tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)); + EXPECT_EQ(1, count) + { + TH_LOG("Expected 1 deny_access_fs event, got %d\n%s", count, + buf); + } + + /* + * The marker catches a full revert with any compiler. The symbolic + * field also catches scratch-sequence poisoning when the compiler + * evaluates the overflowing path first, as GCC currently does. + */ + err = tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), + "blockers", field_buf, sizeof(field_buf)); + ASSERT_EQ(0, err); + EXPECT_STREQ("read_dir", field_buf); + expect_truncated_path(_metadata, buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)); + + free(buf); +} + /* * Verifies that an allowed access emits check_rule events (rule matched during * pathwalk) but does NOT emit deny_access events (no denial). From a0de06d0da78a3db53de65dfd7452cc6d111f703 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:45:29 +0200 Subject: [PATCH 1154/2366] net: ethernet: cortina: Fix budget accounting The gmac_rx() function returns the remaining NAPI budget, but its caller treats the return value as the number of packets received. An idle poll therefore reports a full budget and remains scheduled. Return the number of received packets instead. Preserve the existing free queue refill accounting by adding that count directly; continuing to subtract it from the budget would invert the refill behavior. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Link: https://lore.kernel.org/r/20260509-gemini-ethernet-fixes-v1-4-6c5d20ddc35b@kernel.org Link: https://lore.kernel.org/r/20260512131456.189452-1-pabeni@redhat.com Assisted-by: LLM Reviewed-by: Joe Damato Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-ethernet-fixes-v2-1-2bbbd598ca6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cortina/gemini.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index 4c762229ce42..1d9824d1716c 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -1450,6 +1450,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) unsigned int frame_len, frag_len; struct gmac_rxdesc *rx = NULL; struct gmac_queue_page *gpage; + unsigned int received = 0; union gmac_rxdesc_0 word0; union gmac_rxdesc_1 word1; union gmac_rxdesc_3 word3; @@ -1545,7 +1546,8 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) napi_gro_frags(&port->napi); skb = NULL; frag_nr = 0; - --budget; + budget--; + received++; } continue; @@ -1565,7 +1567,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) port->rx_skb = skb; port->rx_frag_nr = frag_nr; writew(r, ptr_reg); - return budget; + return received; } static int gmac_napi_poll(struct napi_struct *napi, int budget) @@ -1586,7 +1588,7 @@ static int gmac_napi_poll(struct napi_struct *napi, int budget) ++port->rx_napi_exits; } - port->freeq_refill += (budget - received); + port->freeq_refill += received; if (port->freeq_refill > freeq_threshold) { port->freeq_refill -= freeq_threshold; geth_fill_freeq(geth, true); From baa26841cb9a2cdc7e0e99d6854a4e3359bf7393 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:45:30 +0200 Subject: [PATCH 1155/2366] net: ethernet: cortina: Finish RX updates before NAPI completion napi_complete_done() releases ownership of the NAPI instance, but the Gemini poll keeps the RX statistics writer section open and updates the free queue after calling it. A new poll can therefore start while the old writer is still active. Finish the statistics and free queue updates before releasing ownership. Only re-enable RX interrupts when napi_complete_done() reports successful completion. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Suggested-by: Joe Damato Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-ethernet-fixes-v2-2-2bbbd598ca6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cortina/gemini.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index 1d9824d1716c..6502220362cb 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -1581,12 +1581,10 @@ static int gmac_napi_poll(struct napi_struct *napi, int budget) u64_stats_update_begin(&port->rx_stats_syncp); received = gmac_rx(napi->dev, budget); - if (received < budget) { - napi_gro_flush(napi, false); - napi_complete_done(napi, received); - gmac_enable_rx_irq(napi->dev, 1); + if (received < budget) ++port->rx_napi_exits; - } + + u64_stats_update_end(&port->rx_stats_syncp); port->freeq_refill += received; if (port->freeq_refill > freeq_threshold) { @@ -1594,7 +1592,9 @@ static int gmac_napi_poll(struct napi_struct *napi, int budget) geth_fill_freeq(geth, true); } - u64_stats_update_end(&port->rx_stats_syncp); + if (received < budget && napi_complete_done(napi, received)) + gmac_enable_rx_irq(napi->dev, 1); + return received; } From b856c552f556bc0341c1dbe0bf88e630fd1dc4b7 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:45:31 +0200 Subject: [PATCH 1156/2366] net: ethernet: cortina: Count dropped frames as NAPI work The RX loop only consumes budget when it successfully delivers a frame. Error paths keep consuming descriptors without reducing the budget, so a stream of bad frames can process the entire receive ring in one poll. Move the budget accounting to a common end-of-frame path. This counts each completed frame as NAPI work whether it was delivered or dropped, matching the behavior of the vendor driver. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-ethernet-fixes-v2-3-2bbbd598ca6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cortina/gemini.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index 6502220362cb..33e9763b32fe 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -1501,7 +1501,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) skb = NULL; frag_nr = 0; } - continue; + goto next_desc; } page = gpage->page; @@ -1523,7 +1523,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) } else if (!skb) { put_page(page); - continue; + goto next_desc; } if (word3.bits32 & EOF_BIT) @@ -1546,10 +1546,8 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) napi_gro_frags(&port->napi); skb = NULL; frag_nr = 0; - budget--; - received++; } - continue; + goto next_desc; err_drop: if (skb) { @@ -1562,6 +1560,13 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) put_page(page); port->stats.rx_dropped++; + +next_desc: + /* Final or single-descriptor fragment, advance things */ + if (word3.bits32 & EOF_BIT) { + budget--; + received++; + } } port->rx_skb = skb; From 6520198c430c81bcc367f0dd5e32f2fb740b9d51 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:45:32 +0200 Subject: [PATCH 1157/2366] net: ethernet: cortina: Count RX drops once per frame The absence of a partial skb means either that the driver is not assembling a frame or that the current frame was already dropped. Consequently, repeated descriptor errors can increment rx_dropped more than once, while an orphaned descriptor chain can reach EOF without being counted at all. Track the dropping state across NAPI polls. Clear it at frame boundaries and route mapping failures and orphaned continuations through the common drop path so each discarded frame is counted exactly once. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Reported-by: Joe Damato Closes: https://lore.kernel.org/netdev/apdK5aMmvYssz35F@devvm20253.cco0.facebook.com/ Assisted-by: LLM Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-ethernet-fixes-v2-4-2bbbd598ca6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cortina/gemini.c | 41 +++++++++++++++------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index 33e9763b32fe..9ba8524fa371 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -124,6 +124,7 @@ struct gemini_ethernet_port { unsigned int rx_coalesce_nsecs; struct sk_buff *rx_skb; unsigned int rx_frag_nr; + bool rx_dropping; unsigned int freeq_refill; struct gmac_txq txq[TX_QUEUE_NUM]; @@ -1451,6 +1452,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) struct gmac_rxdesc *rx = NULL; struct gmac_queue_page *gpage; unsigned int received = 0; + bool dropping = port->rx_dropping; union gmac_rxdesc_0 word0; union gmac_rxdesc_1 word1; union gmac_rxdesc_3 word3; @@ -1472,6 +1474,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) w = rw.bits.wptr; while (budget && w != r) { + page = NULL; rx = port->rxq_ring + r; word0 = rx->word0; word1 = rx->word1; @@ -1485,6 +1488,16 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) frame_len = word1.bits.byte_count; page_offs = mapping & ~PAGE_MASK; + if (word3.bits32 & SOF_BIT) { + if (skb) { + napi_free_frags(&port->napi); + port->stats.rx_dropped++; + skb = NULL; + frag_nr = 0; + } + dropping = false; + } + if (!mapping) { netdev_err(netdev, "rxq[%u]: HW BUG: zero DMA desc\n", r); @@ -1495,24 +1508,11 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) gpage = gmac_get_queue_page(geth, port, mapping + PAGE_SIZE); if (!gpage) { dev_err(geth->dev, "could not find mapping\n"); - port->stats.rx_dropped++; - if (skb) { - napi_free_frags(&port->napi); - skb = NULL; - frag_nr = 0; - } - goto next_desc; + goto err_drop; } page = gpage->page; if (word3.bits32 & SOF_BIT) { - if (skb) { - napi_free_frags(&port->napi); - port->stats.rx_dropped++; - skb = NULL; - frag_nr = 0; - } - skb = gmac_skb_if_good_frame(port, word0, frame_len); if (!skb) goto err_drop; @@ -1522,8 +1522,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) frag_nr = 0; } else if (!skb) { - put_page(page); - goto next_desc; + goto err_drop; } if (word3.bits32 & EOF_BIT) @@ -1556,21 +1555,26 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) frag_nr = 0; } - if (mapping) + if (page) put_page(page); - port->stats.rx_dropped++; + if (!dropping) { + port->stats.rx_dropped++; + dropping = true; + } next_desc: /* Final or single-descriptor fragment, advance things */ if (word3.bits32 & EOF_BIT) { budget--; received++; + dropping = false; } } port->rx_skb = skb; port->rx_frag_nr = frag_nr; + port->rx_dropping = dropping; writew(r, ptr_reg); return received; } @@ -1900,6 +1904,7 @@ static int gmac_stop(struct net_device *netdev) napi_disable(&port->napi); port->rx_skb = NULL; port->rx_frag_nr = 0; + port->rx_dropping = false; gmac_enable_irq(netdev, 0); gmac_cleanup_rxq(netdev); From e89e88ad41d9f31c829c2af39c48313e8e48d5b0 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 3 Sep 2026 23:45:33 +0200 Subject: [PATCH 1158/2366] net: ethernet: cortina: Count RX descriptors for freeq refill The software free queue provides one buffer fragment for every descriptor moved to an RX queue. The refill heuristic instead advances by NAPI work, which counts frames. A fragmented or discarded frame can consume several queue entries while adding only one to the refill count. Count the RX descriptors as they are consumed and report that separately from NAPI work. Use the descriptor count to drive free queue refills. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Assisted-by: LLM Reviewed-by: Joe Damato Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260903-gemini-ethernet-fixes-v2-5-2bbbd598ca6e@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cortina/gemini.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index 9ba8524fa371..f08de623e6f7 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -1440,7 +1440,8 @@ static struct sk_buff *gmac_skb_if_good_frame(struct gemini_ethernet_port *port, return skb; } -static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) +static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget, + unsigned int *freeq_consumed) { struct gemini_ethernet_port *port = netdev_priv(netdev); unsigned short m = (1 << port->rxq_order) - 1; @@ -1448,6 +1449,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) void __iomem *ptr_reg = port->rxq_rwptr; unsigned int frag_nr = port->rx_frag_nr; struct sk_buff *skb = port->rx_skb; + unsigned int consumed = 0; unsigned int frame_len, frag_len; struct gmac_rxdesc *rx = NULL; struct gmac_queue_page *gpage; @@ -1483,6 +1485,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) r++; r &= m; + consumed++; frag_len = word0.bits.buffer_size; frame_len = word1.bits.byte_count; @@ -1575,6 +1578,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget) port->rx_skb = skb; port->rx_frag_nr = frag_nr; port->rx_dropping = dropping; + *freeq_consumed = consumed; writew(r, ptr_reg); return received; } @@ -1584,18 +1588,19 @@ static int gmac_napi_poll(struct napi_struct *napi, int budget) struct gemini_ethernet_port *port = netdev_priv(napi->dev); struct gemini_ethernet *geth = port->geth; unsigned int freeq_threshold; + unsigned int freeq_consumed; unsigned int received; freeq_threshold = 1 << (geth->freeq_order - 1); u64_stats_update_begin(&port->rx_stats_syncp); - received = gmac_rx(napi->dev, budget); + received = gmac_rx(napi->dev, budget, &freeq_consumed); if (received < budget) ++port->rx_napi_exits; u64_stats_update_end(&port->rx_stats_syncp); - port->freeq_refill += received; + port->freeq_refill += freeq_consumed; if (port->freeq_refill > freeq_threshold) { port->freeq_refill -= freeq_threshold; geth_fill_freeq(geth, true); From a7783e585360ee05dfe21d3173dbbe985c94f29e Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:01 +0200 Subject: [PATCH 1159/2366] wifi: cfg80211: don't get the radio mask for netdev-less wdevs cfg80211_calculate_bi_data() calls rdev_get_radio_mask() with wdev->netdev, which can be NULL and then crashes in mac80211. To avoid that, invert the order of checks since wdev->netdev is always valid for beaconing interfaces. Assisted-by: LLM Fixes: abb4cfe3661a ("wifi: cfg80211: extend interface combination check for multi-radio") Reported-by: syzbot+abff43d2d045e37c0bb2@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=abff43d2d045e37c0bb2 Link: https://patch.msgid.link/20260904165614.2056a8b7dc91.I7412c5062d8166ad6c81ee7252cec49dea19a60f@changeid Signed-off-by: Johannes Berg --- net/wireless/util.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/net/wireless/util.c b/net/wireless/util.c index 3e584d0ca3e2..408ebb10924f 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -2477,16 +2477,15 @@ static void cfg80211_calculate_bi_data(struct wiphy *wiphy, u32 new_beacon_int, if (wdev->valid_links) continue; + wdev_bi = cfg80211_wdev_bi(wdev); + if (!wdev_bi) + continue; + /* skip wdevs not active on the given wiphy radio */ if (radio_idx >= 0 && !(rdev_get_radio_mask(rdev, wdev->netdev) & BIT(radio_idx))) continue; - wdev_bi = cfg80211_wdev_bi(wdev); - - if (!wdev_bi) - continue; - if (!*beacon_int_gcd) { *beacon_int_gcd = wdev_bi; continue; From 48b2c5c628b09cf36cbeca53e0432fc2a7518be7 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:02 +0200 Subject: [PATCH 1160/2366] wifi: cfg80211: check IP header size in cfg80211_classify8021d() A frame that looks like IP can be transmitted, but be too short, so the DS field is read incorrectly: BUG: KMSAN: uninit-value in cfg80211_classify8021d+0x99d/0x12b0 net/wireless/util.c:1027 cfg80211_classify8021d+0x99d/0x12b0 net/wireless/util.c:1027 ieee80211_select_queue+0x37a/0x9e0 net/mac80211/wme.c:180 __ieee80211_subif_start_xmit+0x60f/0x1d90 net/mac80211/tx.c:4304 ieee80211_subif_start_xmit+0xa8/0x6d0 net/mac80211/tx.c:4538 ... packet_sendmsg+0x9173/0xa2a0 net/packet/af_packet.c:3108 Use skb_header_pointer() like the MPLS case. Assisted-by: LLM Fixes: e31a16d6f64e ("wireless: move some utility functions from mac80211 to cfg80211") Reported-by: syzbot+878ddc3962f792e9af59@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=878ddc3962f792e9af59 Link: https://patch.msgid.link/20260904165614.5e61a4c80b92.I37d68d3f406cb3b90b32e6943418d66070b65197@changeid Signed-off-by: Johannes Berg --- net/wireless/util.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/net/wireless/util.c b/net/wireless/util.c index 408ebb10924f..5429cf3cfd2f 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -1039,12 +1039,30 @@ unsigned int cfg80211_classify8021d(struct sk_buff *skb, } switch (skb->protocol) { - case htons(ETH_P_IP): - dscp = ipv4_get_dsfield(ip_hdr(skb)) & 0xfc; + case htons(ETH_P_IP): { + const struct iphdr *iph; + struct iphdr _iph; + + iph = skb_header_pointer(skb, sizeof(struct ethhdr), + sizeof(*iph), &_iph); + if (!iph) + return 0; + + dscp = ipv4_get_dsfield(iph) & 0xfc; break; - case htons(ETH_P_IPV6): - dscp = ipv6_get_dsfield(ipv6_hdr(skb)) & 0xfc; + } + case htons(ETH_P_IPV6): { + const struct ipv6hdr *ip6h; + struct ipv6hdr _ip6h; + + ip6h = skb_header_pointer(skb, sizeof(struct ethhdr), + sizeof(*ip6h), &_ip6h); + if (!ip6h) + return 0; + + dscp = ipv6_get_dsfield(ip6h) & 0xfc; break; + } case htons(ETH_P_MPLS_UC): case htons(ETH_P_MPLS_MC): { struct mpls_label mpls_tmp, *mpls; From 28cc4d5a75bb07d0eb2fa178db355b04483f5aca Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Tue, 8 Sep 2026 13:59:41 +0800 Subject: [PATCH 1161/2366] drm/sched: Create a fake device for KUnit tests The DRM scheduler KUnit tests pass NULL for the dev field in drm_sched_init_args, which NULL-pointer dereferences in the drm_sched_job trace event via dev_name() on sched->dev. Give the mock scheduler a device with kunit_device_register(), which is also cleaned up at test exit. A per-function counter keeps the device names unique, since some tests create several mock schedulers. Fixes: 5a99350794fe ("drm/sched: Add scheduler unit testing infrastructure and some basic tests") Cc: stable@vger.kernel.org Signed-off-by: Shixiong Ou Acked-by: Maxime Ripard [phasta: removed static variable init to 0 again] Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20260908055941.351486-1-oushixiong1025@163.com --- drivers/gpu/drm/scheduler/tests/mock_scheduler.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/gpu/drm/scheduler/tests/mock_scheduler.c b/drivers/gpu/drm/scheduler/tests/mock_scheduler.c index 8e9ae7d980eb..2dfa3efef210 100644 --- a/drivers/gpu/drm/scheduler/tests/mock_scheduler.c +++ b/drivers/gpu/drm/scheduler/tests/mock_scheduler.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2025 Valve Corporation */ +#include + #include "sched_tests.h" /* @@ -288,6 +290,7 @@ static const struct drm_sched_backend_ops drm_mock_scheduler_ops = { */ struct drm_mock_scheduler *drm_mock_sched_new(struct kunit *test, long timeout) { + static unsigned int instance; struct drm_sched_init_args args = { .ops = &drm_mock_scheduler_ops, .num_rqs = DRM_SCHED_PRIORITY_COUNT, @@ -297,11 +300,19 @@ struct drm_mock_scheduler *drm_mock_sched_new(struct kunit *test, long timeout) .name = "drm-mock-scheduler", }; struct drm_mock_scheduler *sched; + struct device *dev; + char name[64]; int ret; sched = kunit_kzalloc(test, sizeof(*sched), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, sched); + snprintf(name, sizeof(name), "%s-%u", args.name, instance++); + dev = kunit_device_register(test, name); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + args.dev = dev; + ret = drm_sched_init(&sched->base, &args); KUNIT_ASSERT_EQ(test, ret, 0); From 313f798f52a5e46b0e419d05f977d40aaeac4106 Mon Sep 17 00:00:00 2001 From: Qinyun Tan Date: Tue, 1 Sep 2026 16:32:31 +0800 Subject: [PATCH 1162/2366] drm/ast: create blend mode property on cursor plane Since commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed"), drm_mode_config_validate() warns when a plane exposes an alpha pixel format but not the "pixel blend mode" property. The ast cursor plane (ARGB4444, ARGB8888) trips this on driver load: [PLANE:37:plane-1] pixel format with alpha exposed but blend mode not setup WARNING: drivers/gpu/drm/drm_mode_config.c:872 at drm_mode_config_validate+0x48f/0x510 [drm] ... Call Trace: drm_dev_register+0x1ce/0x290 [drm] ast_pci_probe+0x19d/0x3f0 [ast] local_pci_probe+0x41/0x90 Per Thomas Zimmermann's review, the ASPEED documentation describes the hardware cursor as blending with straight (non-pre-multiplied) alpha, which corresponds to DRM_MODE_BLEND_COVERAGE. Expose a "pixel blend mode" property advertising only DRM_MODE_BLEND_COVERAGE to make the hardware semantics explicit and silence the warning. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Suggested-by: Thomas Zimmermann Reviewed-by: Thomas Zimmermann Tested-by: Thomas Zimmermann Signed-off-by: Qinyun Tan Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260901083234.1828755-2-qinyuntan@linux.alibaba.com --- drivers/gpu/drm/ast/ast_cursor.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/ast/ast_cursor.c b/drivers/gpu/drm/ast/ast_cursor.c index fd19c45f2abe..690d4cd1db5e 100644 --- a/drivers/gpu/drm/ast/ast_cursor.c +++ b/drivers/gpu/drm/ast/ast_cursor.c @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -355,6 +356,8 @@ int ast_cursor_plane_init(struct ast_device *ast) } drm_plane_helper_add(cursor_plane, &ast_cursor_plane_helper_funcs); drm_plane_enable_fb_damage_clips(cursor_plane); + drm_plane_create_blend_mode_property(cursor_plane, + BIT(DRM_MODE_BLEND_COVERAGE)); return 0; } From b67f408a0c2427d5d7c682cfbdad87c4ff73265b Mon Sep 17 00:00:00 2001 From: Qinyun Tan Date: Tue, 1 Sep 2026 16:32:32 +0800 Subject: [PATCH 1163/2366] drm/qxl: create blend mode property on primary and cursor planes Since commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed"), drm_mode_config_validate() warns when a plane exposes an alpha pixel format but not the "pixel blend mode" property. Both the qxl primary and cursor planes expose ARGB8888 and trip this on driver load. qxl submits cursors as SPICE_CURSOR_TYPE_ALPHA, which the SPICE protocol explicitly defines as a "pre-multiplied ARGB8888 pixmap" (Spice Protocol, "Cursor channel definition" section [1]). This matches the blend mode userspace has always assumed when the property is not attached. Expose a "pixel blend mode" property advertising only DRM_MODE_BLEND_PREMULTI to make these semantics explicit and silence the warning. The primary plane is the bottom-most plane so its blend mode has no visible effect; advertise the same value there for consistency. No functional change. [1] https://www.spice-space.org/spice-protocol.html Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Reviewed-by: Thomas Zimmermann Signed-off-by: Qinyun Tan Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260901083234.1828755-3-qinyuntan@linux.alibaba.com --- drivers/gpu/drm/qxl/qxl_display.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c index 2fc41fb90aaa..0719fc6a52d5 100644 --- a/drivers/gpu/drm/qxl/qxl_display.c +++ b/drivers/gpu/drm/qxl/qxl_display.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -993,6 +994,9 @@ static struct drm_plane *qxl_create_plane(struct qxl_device *qdev, drm_plane_helper_add(plane, helper_funcs); + drm_plane_create_blend_mode_property(plane, + BIT(DRM_MODE_BLEND_PREMULTI)); + return plane; free_plane: From f2e64f450c1665732505dae8ee34b399da5a5100 Mon Sep 17 00:00:00 2001 From: Qinyun Tan Date: Tue, 1 Sep 2026 16:32:33 +0800 Subject: [PATCH 1164/2366] drm/virtio: create blend mode property on cursor plane Since commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed"), drm_mode_config_validate() warns when a plane exposes an alpha pixel format but not the "pixel blend mode" property. The virtio-gpu cursor plane (HOST_ARGB8888) trips this. The virtio-gpu specification does not define the cursor alpha semantics. The host forwards the cursor pixels verbatim to its display frontends, and the remote cursor protocols among them (SPICE alpha cursors, the VNC "Cursor With Alpha" encoding) both define pre-multiplied alpha, matching what userspace has always assumed when the property is not attached. Expose a "pixel blend mode" property advertising only DRM_MODE_BLEND_PREMULTI to make these semantics explicit and silence the warning. The primary plane only exposes HOST_XRGB8888, so the call is gated to the cursor. No functional change. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Reviewed-by: Thomas Zimmermann Signed-off-by: Qinyun Tan Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260901083234.1828755-4-qinyuntan@linux.alibaba.com --- drivers/gpu/drm/virtio/virtgpu_plane.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_plane.c b/drivers/gpu/drm/virtio/virtgpu_plane.c index 1d1b27ece62a..640815af4098 100644 --- a/drivers/gpu/drm/virtio/virtgpu_plane.c +++ b/drivers/gpu/drm/virtio/virtgpu_plane.c @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -609,6 +610,9 @@ struct drm_plane *virtio_gpu_plane_init(struct virtio_gpu_device *vgdev, if (type == DRM_PLANE_TYPE_PRIMARY) drm_plane_enable_fb_damage_clips(plane); + else if (type == DRM_PLANE_TYPE_CURSOR) + drm_plane_create_blend_mode_property(plane, + BIT(DRM_MODE_BLEND_PREMULTI)); return plane; } From 09f7fc4e8e307fcdaa506b3a06cd7b1acffd2584 Mon Sep 17 00:00:00 2001 From: Qinyun Tan Date: Tue, 1 Sep 2026 16:32:34 +0800 Subject: [PATCH 1165/2366] drm/vboxvideo: create blend mode property on planes Since commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed"), drm_mode_config_validate() warns when a plane exposes an alpha pixel format but not the "pixel blend mode" property. Both the vboxvideo primary and cursor planes expose ARGB8888 and trip this on driver load. VirtualBox draws the cursor through the host windowing system, which treats the guest-supplied pointer shape as straight (non-pre-multiplied) alpha: the host frontend loads the pixels verbatim into an unpremultiplied ARGB image before handing them to the host cursor APIs. This corresponds to DRM_MODE_BLEND_COVERAGE. Expose a "pixel blend mode" property advertising only DRM_MODE_BLEND_COVERAGE to make these semantics explicit and silence the warning. The primary plane's alpha channel is ignored by the host (opaque blit) and it is the bottom-most plane anyway; advertise the same value there for consistency. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Acked-by: Thomas Zimmermann Signed-off-by: Qinyun Tan Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260901083234.1828755-5-qinyuntan@linux.alibaba.com --- drivers/gpu/drm/vboxvideo/vbox_mode.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/vboxvideo/vbox_mode.c b/drivers/gpu/drm/vboxvideo/vbox_mode.c index 8e4e5fc9d3c5..3c41238a8268 100644 --- a/drivers/gpu/drm/vboxvideo/vbox_mode.c +++ b/drivers/gpu/drm/vboxvideo/vbox_mode.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -540,6 +541,9 @@ static struct drm_plane *vbox_create_plane(struct vbox_private *vbox, drm_plane_helper_add(plane, helper_funcs); + drm_plane_create_blend_mode_property(plane, + BIT(DRM_MODE_BLEND_COVERAGE)); + return plane; free_plane: From fedf002d7d08bee36693aacd1ade2ba39351ea91 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 10:04:26 +0200 Subject: [PATCH 1166/2366] drm/adp: Drop the select of the nonexistent CONFIG_DRM_KMS_DMA_HELPER There is no Kconfig symbol CONFIG_DRM_KMS_DMA_HELPER. The former CONFIG_DRM_KMS_CMA_HELPER was removed by commit 09717af7d13d ("drm: Remove CONFIG_DRM_KMS_CMA_HELPER option") before this driver was added, so the select does nothing. The driver already selects CONFIG_DRM_GEM_DMA_HELPER, which is what it needs. Remove the dead line. Fixes: 332122eba628 ("drm: adp: Add Apple Display Pipe driver") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260905080426.34224-1-kmehltretter@gmail.com --- drivers/gpu/drm/adp/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/adp/Kconfig b/drivers/gpu/drm/adp/Kconfig index 9fcc27eb200d..acfa21ee06d2 100644 --- a/drivers/gpu/drm/adp/Kconfig +++ b/drivers/gpu/drm/adp/Kconfig @@ -6,7 +6,6 @@ config DRM_ADP select DRM_KMS_HELPER select DRM_BRIDGE_CONNECTOR select DRM_DISPLAY_HELPER - select DRM_KMS_DMA_HELPER select DRM_GEM_DMA_HELPER select DRM_PANEL_BRIDGE select VIDEOMODE_HELPERS From f97802dd98b27e45c04293f9926f07642578b23f Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 10:03:44 +0200 Subject: [PATCH 1167/2366] drm/logicvc: Drop the select of the nonexistent CONFIG_DRM_KMS_DMA_HELPER CONFIG_DRM_KMS_CMA_HELPER was removed by commit 09717af7d13d ("drm: Remove CONFIG_DRM_KMS_CMA_HELPER option"). When commit 6bcfe8eaeef0 ("drm/fb: rename FB CMA helpers to FB DMA helpers") later renamed the select in this Kconfig to CONFIG_DRM_KMS_DMA_HELPER, no symbol of that name existed, and git log -S finds no Kconfig file that has defined one since. The select is silently ignored. The driver already selects CONFIG_DRM_GEM_DMA_HELPER, which is what it needs. Remove the dead line. Fixes: 6bcfe8eaeef0 ("drm/fb: rename FB CMA helpers to FB DMA helpers") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20260905080344.34077-1-kmehltretter@gmail.com --- drivers/gpu/drm/logicvc/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/logicvc/Kconfig b/drivers/gpu/drm/logicvc/Kconfig index 579a358ed5cf..11aae1626199 100644 --- a/drivers/gpu/drm/logicvc/Kconfig +++ b/drivers/gpu/drm/logicvc/Kconfig @@ -4,7 +4,6 @@ config DRM_LOGICVC depends on OF || COMPILE_TEST select DRM_CLIENT_SELECTION select DRM_KMS_HELPER - select DRM_KMS_DMA_HELPER select DRM_GEM_DMA_HELPER select REGMAP select REGMAP_MMIO From aba7b41faeecb7692458095ce6fafc341fe0b80e Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Tue, 1 Sep 2026 11:10:19 +0800 Subject: [PATCH 1168/2366] soundwire: cadence_master: wait and cancel cdns->work before clock stop A peripheral event could happen during the clock stop process. We need to wait for the event be handled before stopping the bus clock. Otherwise, we will get the IO transfer timed out issue. Fixes: af4cc917826f ("soundwire: cadence: mask Slave interrupt before stopping clock") Signed-off-by: Bard Liao Reviewed-by: David Lin Reviewed-by: Shuming Fan Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260901031019.233254-1-yung-chuan.liao@linux.intel.com Signed-off-by: Vinod Koul --- drivers/soundwire/cadence_master.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index e690237fe981..a0a63b76793e 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -1701,6 +1701,13 @@ int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake) return 0; } + /* + * wait for any in-flight peripheral event handling to complete before stopping the clock. + * No need to disable peripheral interrupts before canceling the work, as the peripheral + * interrupts are already masked before the work is scheduled. + */ + cancel_work_sync(&cdns->work); + /* * Before entering clock stop we mask the Slave * interrupts. This helps avoid having to deal with e.g. a From 4eef4ab3aa3a32725e5bc79032c722f9f4a90172 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Tue, 1 Sep 2026 14:33:18 +0200 Subject: [PATCH 1169/2366] s390/pai: Use PAI PMU index as parameter replacing event Use PAI PMU index value as function argument instead of pointer to struct perf_event. Only that index value is used inside functions pai_alloc_cpu() and pai_event_destroy_cpu(). No functional change. Signed-off-by: Thomas Richter Reviewed-by: Sumanth Korikkar Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/perf_pai.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/arch/s390/kernel/perf_pai.c b/arch/s390/kernel/perf_pai.c index 5c18c8b82ab7..109b15227d8f 100644 --- a/arch/s390/kernel/perf_pai.c +++ b/arch/s390/kernel/perf_pai.c @@ -140,16 +140,14 @@ static void pai_free(struct pai_mapptr *mp) /* Adjust usage counters and remove allocated memory when all users are * gone. */ -static void pai_event_destroy_cpu(struct perf_event *event, int cpu) +static void pai_event_destroy_cpu(int idx, int cpu) { - int idx = PAI_PMU_IDX(event); struct pai_mapptr *mp = per_cpu_ptr(pai_root[idx].mapptr, cpu); struct pai_map *cpump = mp->mapptr; mutex_lock(&pai_reserve_mutex); - debug_sprintf_event(paidbg, 5, "%s event %#llx idx %d cpu %d users %d " - "refcnt %u\n", __func__, event->attr.config, idx, - event->cpu, cpump->active_events, + debug_sprintf_event(paidbg, 5, "%s users %d refcnt %u\n", + __func__, cpump->active_events, refcount_read(&cpump->refcnt)); if (refcount_dec_and_test(&cpump->refcnt)) pai_free(mp); @@ -159,17 +157,17 @@ static void pai_event_destroy_cpu(struct perf_event *event, int cpu) static void pai_event_destroy(struct perf_event *event) { - int cpu; + int cpu = 0, idx = PAI_PMU_IDX(event); free_page(PAI_SAVE_AREA(event)); if (event->cpu == -1) { struct cpumask *mask = PAI_CPU_MASK(event); for_each_cpu(cpu, mask) - pai_event_destroy_cpu(event, cpu); + pai_event_destroy_cpu(idx, cpu); kfree(mask); } else { - pai_event_destroy_cpu(event, event->cpu); + pai_event_destroy_cpu(idx, event->cpu); } } @@ -241,12 +239,12 @@ static u64 paicrypt_getall(struct perf_event *event) * * Allocate the memory for the event. */ -static int pai_alloc_cpu(struct perf_event *event, int cpu) +static int pai_alloc_cpu(int idx, int cpu) { - int rc, idx = PAI_PMU_IDX(event); struct pai_map *cpump = NULL; bool need_paiext_cb = false; struct pai_mapptr *mp; + int rc; mutex_lock(&pai_reserve_mutex); /* Allocate root node */ @@ -318,6 +316,7 @@ static int pai_alloc_cpu(struct perf_event *event, int cpu) static int pai_alloc(struct perf_event *event) { + int idx = PAI_PMU_IDX(event); struct cpumask *maskptr; int cpu, rc = -ENOMEM; @@ -326,10 +325,10 @@ static int pai_alloc(struct perf_event *event) goto out; for_each_online_cpu(cpu) { - rc = pai_alloc_cpu(event, cpu); + rc = pai_alloc_cpu(idx, cpu); if (rc) { for_each_cpu(cpu, maskptr) - pai_event_destroy_cpu(event, cpu); + pai_event_destroy_cpu(idx, cpu); kfree(maskptr); goto out; } @@ -392,7 +391,7 @@ static int pai_event_init(struct perf_event *event, int idx) } if (event->cpu >= 0) - rc = pai_alloc_cpu(event, event->cpu); + rc = pai_alloc_cpu(idx, event->cpu); else rc = pai_alloc(event); if (rc) { From e8df39dacb7d98d2b2aea431ca652d9fadf5efa3 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Tue, 1 Sep 2026 14:33:19 +0200 Subject: [PATCH 1170/2366] s390/pai: Move locking to event init and delete Move mutex locking from per CPU allocation to event allocation. No functional change. Signed-off-by: Thomas Richter Reviewed-by: Sumanth Korikkar Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/perf_pai.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/arch/s390/kernel/perf_pai.c b/arch/s390/kernel/perf_pai.c index 109b15227d8f..c333158cc945 100644 --- a/arch/s390/kernel/perf_pai.c +++ b/arch/s390/kernel/perf_pai.c @@ -138,21 +138,19 @@ static void pai_free(struct pai_mapptr *mp) } /* Adjust usage counters and remove allocated memory when all users are - * gone. + * gone. Called under mutex_lock. */ static void pai_event_destroy_cpu(int idx, int cpu) { struct pai_mapptr *mp = per_cpu_ptr(pai_root[idx].mapptr, cpu); struct pai_map *cpump = mp->mapptr; - mutex_lock(&pai_reserve_mutex); debug_sprintf_event(paidbg, 5, "%s users %d refcnt %u\n", __func__, cpump->active_events, refcount_read(&cpump->refcnt)); if (refcount_dec_and_test(&cpump->refcnt)) pai_free(mp); pai_root_free(idx); - mutex_unlock(&pai_reserve_mutex); } static void pai_event_destroy(struct perf_event *event) @@ -160,6 +158,7 @@ static void pai_event_destroy(struct perf_event *event) int cpu = 0, idx = PAI_PMU_IDX(event); free_page(PAI_SAVE_AREA(event)); + mutex_lock(&pai_reserve_mutex); if (event->cpu == -1) { struct cpumask *mask = PAI_CPU_MASK(event); @@ -169,6 +168,7 @@ static void pai_event_destroy(struct perf_event *event) } else { pai_event_destroy_cpu(idx, event->cpu); } + mutex_unlock(&pai_reserve_mutex); } static void paicrypt_event_destroy(struct perf_event *event) @@ -232,12 +232,10 @@ static u64 paicrypt_getall(struct perf_event *event) return sum; } -/* Check concurrent access of counting and sampling for crypto events. - * This function is called in process context and it is save to block. - * When the event initialization functions fails, no other call back will - * be invoked. - * - * Allocate the memory for the event. +/* Allocate all per-CPU data structures. This function is called in + * process context and can block. In case of error all partly allocated + * memory is released and the reference counters adjusted correctly. + * Called under mutex_lock. */ static int pai_alloc_cpu(int idx, int cpu) { @@ -246,11 +244,10 @@ static int pai_alloc_cpu(int idx, int cpu) struct pai_mapptr *mp; int rc; - mutex_lock(&pai_reserve_mutex); /* Allocate root node */ rc = pai_root_alloc(idx); if (rc) - goto unlock; + goto out; /* Allocate node for this event */ mp = per_cpu_ptr(pai_root[idx].mapptr, cpu); @@ -308,12 +305,12 @@ static int pai_alloc_cpu(int idx, int cpu) */ pai_root_free(idx); } -unlock: - mutex_unlock(&pai_reserve_mutex); +out: /* If rc is non-zero, no increment of counter/sampler was done. */ return rc; } +/* Called under mutex_lock */ static int pai_alloc(struct perf_event *event) { int idx = PAI_PMU_IDX(event); @@ -390,10 +387,12 @@ static int pai_event_init(struct perf_event *event, int idx) } } + mutex_lock(&pai_reserve_mutex); if (event->cpu >= 0) rc = pai_alloc_cpu(idx, event->cpu); else rc = pai_alloc(event); + mutex_unlock(&pai_reserve_mutex); if (rc) { free_page(PAI_SAVE_AREA(event)); goto out; From 9ecc4d033879f7761f2df07e20cd2fbec00fd90b Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Tue, 1 Sep 2026 14:33:20 +0200 Subject: [PATCH 1171/2366] s390/pai: Support CPU hotplug for PMU PAI The command 'perf stat -e pai_crypto/CRYPTO_ALL/ -- ' crashes the kernel when CPUs are hotplug added during that run. Root cause is the missing allocation of per-CPU data structures for that new CPU. The allocation is dynamic and the first event that has task context creates such a structure for each online CPU. This is not sufficient. CPUs may be offline during event creation and can be set online during the perf run time. For example commands # echo 0 > /sys/devices/system/cpu/cpu1/online # perf stat -e cycles -i -- stress-ng -t10s --matrix X # sleep 1 # echo 1 > /sys/devices/system/cpu/cpu1/online Currently without a CPU hotplug handler, that new CPU has no per-CPU data infrastructure. The scheduler runs PMU call back function pai_add() to install the PMU support for that CPU before the task is being scheduled on that new CPU. In pai_add() instructions mp = this_cpu_ptr(pai_root[idx].mapptr); cpump = mp->mapptr; return a NULL pointer and the result is a kernel panic as variable cpump is used inside that function. Add CPU hotplug support for CPU add and delete and create the necessary per-CPU data infrastructure during CPU hotplug add processing. Same for CPU hotplug remove. This is done when the CPU is offline to ensure the data structures are available when CPU is made online and tasks are scheduled on it. [hca@linux.ibm.com: fixup error path in pai_init()] Cc: stable@vger.kernel.org # v6.19 Fixes: 582cc1b28e8c ("s390/pai_ext: Enable per-task and system-wide sampling event") Fixes: 9f66572f2889 ("s390/pai_crypto: Enable per-task and system-wide sampling event") Signed-off-by: Thomas Richter Reviewed-by: Jan Polensky Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/pai.h | 1 - arch/s390/kernel/perf_pai.c | 178 ++++++++++++++++++++++++++---------- 2 files changed, 128 insertions(+), 51 deletions(-) diff --git a/arch/s390/include/asm/pai.h b/arch/s390/include/asm/pai.h index 534d0320e2aa..a3456a36aaa7 100644 --- a/arch/s390/include/asm/pai.h +++ b/arch/s390/include/asm/pai.h @@ -76,7 +76,6 @@ static __always_inline void pai_kernel_exit(struct pt_regs *regs) } #define PAI_SAVE_AREA(x) ((x)->hw.event_base) -#define PAI_CPU_MASK(x) ((x)->hw.addr_filters) #define PAI_PMU_IDX(x) ((x)->hw.last_tag) #define PAI_SWLIST(x) (&(x)->hw.tp_list) diff --git a/arch/s390/kernel/perf_pai.c b/arch/s390/kernel/perf_pai.c index c333158cc945..013c3dae21ec 100644 --- a/arch/s390/kernel/perf_pai.c +++ b/arch/s390/kernel/perf_pai.c @@ -67,6 +67,7 @@ struct pai_mapptr { static struct pai_root { /* Anchor to per CPU data */ refcount_t refcnt; /* Overall active events */ + atomic_t tskctx; /* Overall per-task events */ struct pai_mapptr __percpu *mapptr; } pai_root[PAI_PMU_MAX]; @@ -93,14 +94,15 @@ struct pai_pmu { /* Define PAI PMU characteristics */ static struct pai_pmu pai_pmu[]; /* Forward declaration */ /* Free per CPU data when the last event is removed. */ -static void pai_root_free(int idx) +static void pai_root_free(int idx, int tasks) { - if (refcount_dec_and_test(&pai_root[idx].refcnt)) { + if (refcount_sub_and_test(tasks, &pai_root[idx].refcnt)) { free_percpu(pai_root[idx].mapptr); pai_root[idx].mapptr = NULL; } - debug_sprintf_event(paidbg, 5, "%s root[%d].refcount %d\n", __func__, - idx, refcount_read(&pai_root[idx].refcnt)); + debug_sprintf_event(paidbg, 5, "%s root[%d].refcount %d tskctx %d\n", + __func__, idx, refcount_read(&pai_root[idx].refcnt), + atomic_read(&pai_root[idx].tskctx)); } /* @@ -137,20 +139,36 @@ static void pai_free(struct pai_mapptr *mp) mp->mapptr = NULL; } -/* Adjust usage counters and remove allocated memory when all users are - * gone. Called under mutex_lock. - */ -static void pai_event_destroy_cpu(int idx, int cpu) +/* Called under mutex_lock */ +static void pai_event_destroy_cpu(int idx, int cpu, bool hotplug) { - struct pai_mapptr *mp = per_cpu_ptr(pai_root[idx].mapptr, cpu); - struct pai_map *cpump = mp->mapptr; + struct pai_mapptr *mp; + struct pai_map *cpump; + int tasks = 1; - debug_sprintf_event(paidbg, 5, "%s users %d refcnt %u\n", - __func__, cpump->active_events, - refcount_read(&cpump->refcnt)); - if (refcount_dec_and_test(&cpump->refcnt)) + /* Check reference count and return when all gone. + * 1. An event is installed on online CPU X. + * 2. CPU x is offlined and the per-CPU data is removed. + * 3. Event is destroyed via close system call. + */ + if (!refcount_read(&pai_root[idx].refcnt)) + return; /* No events at all */ + mp = per_cpu_ptr(pai_root[idx].mapptr, cpu); + if (!mp || !mp->mapptr) /* No events on that CPU */ + return; + + /* When hotplug is true, invocation is from CPU hotplug callback. + * Delete per-CPU resource and adjust refcnt when per-task events + * are currently active. This can be more than one. + * In this case adjust counters. + */ + if (hotplug) + tasks = atomic_read(&pai_root[idx].tskctx); + + cpump = mp->mapptr; + if (refcount_sub_and_test(tasks, &cpump->refcnt)) pai_free(mp); - pai_root_free(idx); + pai_root_free(idx, tasks); } static void pai_event_destroy(struct perf_event *event) @@ -158,17 +176,17 @@ static void pai_event_destroy(struct perf_event *event) int cpu = 0, idx = PAI_PMU_IDX(event); free_page(PAI_SAVE_AREA(event)); + cpus_read_lock(); mutex_lock(&pai_reserve_mutex); if (event->cpu == -1) { - struct cpumask *mask = PAI_CPU_MASK(event); - - for_each_cpu(cpu, mask) - pai_event_destroy_cpu(idx, cpu); - kfree(mask); + atomic_dec(&pai_root[idx].tskctx); + for_each_online_cpu(cpu) + pai_event_destroy_cpu(idx, cpu, false); } else { - pai_event_destroy_cpu(idx, event->cpu); + pai_event_destroy_cpu(idx, event->cpu, false); } mutex_unlock(&pai_reserve_mutex); + cpus_read_unlock(); } static void paicrypt_event_destroy(struct perf_event *event) @@ -232,17 +250,25 @@ static u64 paicrypt_getall(struct perf_event *event) return sum; } -/* Allocate all per-CPU data structures. This function is called in - * process context and can block. In case of error all partly allocated - * memory is released and the reference counters adjusted correctly. - * Called under mutex_lock. - */ -static int pai_alloc_cpu(int idx, int cpu) +/* Called under mutex_lock */ +static int pai_alloc_cpu(int idx, int cpu, bool hotplug) { struct pai_map *cpump = NULL; bool need_paiext_cb = false; struct pai_mapptr *mp; - int rc; + int tasks = 1, rc = 0; + + /* When hotplug is true, invocation is from CPU hotplug callback. + * Allocate per-CPU resource when per-task events are currently active. + * This can be more than one. In this case adjust all reference + * counters. Otherwise return, this ensures memory is only allocated + * when needed. + */ + if (hotplug) { + tasks = atomic_read(&pai_root[idx].tskctx); + if (!tasks) + goto out; + } /* Allocate root node */ rc = pai_root_alloc(idx); @@ -291,26 +317,42 @@ static int pai_alloc_cpu(int idx, int cpu) goto undo; } INIT_LIST_HEAD(&cpump->syswide_list); - refcount_set(&cpump->refcnt, 1); + refcount_set(&cpump->refcnt, tasks); rc = 0; } else { - refcount_inc(&cpump->refcnt); + refcount_add(tasks, &cpump->refcnt); } + /* If tasks is greater than 1, we are called from CPU hotplug path + * and need to adjust the pai_root[idx].refcnt by the number of + * per-process events. Function pai_root_alloc(idx) already + * incremented by one. Adjust for the rest. + */ + if (tasks > 1) + refcount_add(tasks - 1, &pai_root[idx].refcnt); undo: if (rc) { /* Error in allocation of event, decrement anchor. Since * the event in not created, its destroy() function is never * invoked. Adjust the reference counter for the anchor. + * The failure happened in the case of variable + * cpump == NULL branch above. The pai_root[XXX].refcnt has + * been incremented by one. Then the per-CPU allocation + * failed, so decrement it by one, regardless of tasks. */ - pai_root_free(idx); + pai_root_free(idx, 1); } out: /* If rc is non-zero, no increment of counter/sampler was done. */ return rc; } -/* Called under mutex_lock */ +/* Check concurrent access of counting and sampling for PAI events. + * This function is called in process context and it is safe to block. + * When the event initialization functions fails, no other call back will + * be invoked. + * Called under mutex_lock. + */ static int pai_alloc(struct perf_event *event) { int idx = PAI_PMU_IDX(event); @@ -322,24 +364,20 @@ static int pai_alloc(struct perf_event *event) goto out; for_each_online_cpu(cpu) { - rc = pai_alloc_cpu(idx, cpu); + rc = pai_alloc_cpu(idx, cpu, false); if (rc) { for_each_cpu(cpu, maskptr) - pai_event_destroy_cpu(idx, cpu); - kfree(maskptr); - goto out; + pai_event_destroy_cpu(idx, cpu, false); + goto undo; } cpumask_set_cpu(cpu, maskptr); } - /* - * On error all cpumask are freed and all events have been destroyed. - * Save of which CPUs data structures have been allocated for. - * Release them in pai_event_destroy call back function - * for this event. - */ - PAI_CPU_MASK(event) = maskptr; rc = 0; + /* Trace per-task events for CPU hotplug. */ + atomic_inc(&pai_root[idx].tskctx); +undo: + kfree(maskptr); out: return rc; } @@ -387,12 +425,14 @@ static int pai_event_init(struct perf_event *event, int idx) } } + cpus_read_lock(); mutex_lock(&pai_reserve_mutex); if (event->cpu >= 0) - rc = pai_alloc_cpu(idx, event->cpu); + rc = pai_alloc_cpu(idx, event->cpu, false); else rc = pai_alloc(event); mutex_unlock(&pai_reserve_mutex); + cpus_read_unlock(); if (rc) { free_page(PAI_SAVE_AREA(event)); goto out; @@ -1237,8 +1277,35 @@ static int __init paipmu_setup(void) return install_ok; } +static int pai_online_cpu(unsigned int cpu) +{ + int rc; + + mutex_lock(&pai_reserve_mutex); + rc = pai_alloc_cpu(PAI_PMU_CRYPTO, cpu, true); + if (rc) + goto out; + rc = pai_alloc_cpu(PAI_PMU_EXT, cpu, true); + if (rc) + pai_event_destroy_cpu(PAI_PMU_CRYPTO, cpu, true); +out: + mutex_unlock(&pai_reserve_mutex); + return rc; +} + +static int pai_offline_cpu(unsigned int cpu) +{ + mutex_lock(&pai_reserve_mutex); + pai_event_destroy_cpu(PAI_PMU_CRYPTO, cpu, true); + pai_event_destroy_cpu(PAI_PMU_EXT, cpu, true); + mutex_unlock(&pai_reserve_mutex); + return 0; +} + static int __init pai_init(void) { + int state, rc; + /* Setup s390dbf facility */ paidbg = debug_register("pai", 1, 1, 128); if (!paidbg) { @@ -1247,13 +1314,24 @@ static int __init pai_init(void) } debug_register_view(paidbg, &debug_sprintf_view); - if (!paipmu_setup()) { - /* No PMU registration, no need for debug buffer */ - debug_unregister_view(paidbg, &debug_sprintf_view); - debug_unregister(paidbg); - return -ENODEV; - } + /* CPUHP_BP_PREPARE_DYN --> before CPU is brought online */ + state = cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "perf/pai:prepare", + pai_online_cpu, pai_offline_cpu); + rc = state < 0 ? state : 0; + if (rc < 0) + goto out_debug; + + rc = -ENODEV; + if (!paipmu_setup()) + goto out_cpuhp; return 0; + +out_cpuhp: + cpuhp_remove_state(state); +out_debug: + debug_unregister_view(paidbg, &debug_sprintf_view); + debug_unregister(paidbg); + return rc; } device_initcall(pai_init); From b1eb31d533cdfcae1011ed53850d52f36afe5774 Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Thu, 3 Sep 2026 15:07:31 +0200 Subject: [PATCH 1172/2366] s390/debug: Fix NULL pointer dereference in debug_set_level() Commit a2cec6863709 ("s390/debug: Add s390dbf kernel parameter") incorrectly removed a null-id check from debug_set_level(), introducing a possible NULL pointer dereference for debug-API users that put debug_register() results unchecked into debug_set_level(). Fix this by moving the check from the internal _debug_set_level() variant back to the external debug_set_level() wrapper. Fixes: a2cec6863709 ("s390/debug: Add s390dbf kernel parameter") Signed-off-by: Mikhail Zaslonko Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/debug.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index 14d2b58ad093..e06abf1dbc21 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -1074,9 +1074,6 @@ static void _debug_set_level(debug_info_t *id, int new_level) { unsigned long flags; - if (!id) - return; - if (new_level == DEBUG_OFF_LEVEL) { pr_info("%s: switched off\n", id->name); } else if ((new_level > DEBUG_MAX_LEVEL) || (new_level < 0)) { @@ -1101,6 +1098,9 @@ static void _debug_set_level(debug_info_t *id, int new_level) */ void debug_set_level(debug_info_t *id, int new_level) { + if (!id) + return; + /* Level specified via kernel parameter takes precedence */ debug_get_param(id->name, &new_level, NULL); From 22d4210bf988047bd30803cd6ef5177f113e4004 Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Thu, 3 Sep 2026 15:07:32 +0200 Subject: [PATCH 1173/2366] s390/debug: Do not repeat parameter override notice on debug_set_level() Commit a2cec6863709 ("s390/debug: Add s390dbf kernel parameter") calls debug_get_param() from both debug_info_create() and debug_set_level(). Since debug_get_param() emits the override notice unconditionally, and drivers typically call debug_set_level() right after debug_register(), the same line is printed twice per debug area: s390dbf: 0.0.1234: override level to 6 s390dbf: 0.0.1234: override level to 6 For areas registered per device this is multiplied by the device count. With 's390dbf=0.0.*:6' a system with many DASDs emits a large number of redundant lines during boot. Add a quiet parameter to debug_get_param() and pass quiet=true from debug_set_level(), where the override has already been announced during registration. The remaining callers keep printing the notice. Signed-off-by: Mikhail Zaslonko Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/debug.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index e06abf1dbc21..cf411f203571 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -182,7 +182,7 @@ static struct debug_param_t { static int debug_param_num; /* functions */ -static void debug_get_param(const char *name, int *level, int *pages) +static void debug_get_param(const char *name, int *level, int *pages, bool quiet) { struct debug_param_t *p; int i; @@ -192,11 +192,13 @@ static void debug_get_param(const char *name, int *level, int *pages) if (!glob_match(p->name, name)) continue; if (level && p->level != PARAM_UNSET) { - pr_info("%s: override level to %d\n", name, p->level); + if (!quiet) + pr_info("%s: override level to %d\n", name, p->level); *level = p->level; } if (pages && p->pages != PARAM_UNSET) { - pr_info("%s: override pages to %d\n", name, p->pages); + if (!quiet) + pr_info("%s: override pages to %d\n", name, p->pages); *pages = p->pages; } } @@ -251,7 +253,7 @@ static int __init s390dbf_parse(char *arg) * regular memory allocations are possible. */ for (i = 0, id = __s390dbf_info; &id[i] < __s390dbf_info_end; i++) - debug_get_param(id[i]->name, &id[i]->level, NULL); + debug_get_param(id[i]->name, &id[i]->level, NULL, false); return rc; } @@ -395,7 +397,7 @@ static debug_info_t *debug_info_create(const char *name, int pages_per_area, int level = DEBUG_DEFAULT_LEVEL; debug_info_t *rc; - debug_get_param(name, &level, &pages_per_area); + debug_get_param(name, &level, &pages_per_area, false); rc = debug_info_alloc(name, pages_per_area, nr_areas, buf_size, level, ALL_AREAS); if (!rc) goto out; @@ -960,7 +962,7 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) return; } - debug_get_param(id->name, &id->level, &pages_per_area); + debug_get_param(id->name, &id->level, &pages_per_area, false); copy = debug_info_alloc("", pages_per_area, nr_areas, id->buf_size, id->level, ALL_AREAS); if (!copy) { @@ -1101,8 +1103,11 @@ void debug_set_level(debug_info_t *id, int new_level) if (!id) return; - /* Level specified via kernel parameter takes precedence */ - debug_get_param(id->name, &new_level, NULL); + /* + * Level specified via kernel parameter takes precedence. The override + * was already announced during registration, so stay quiet here. + */ + debug_get_param(id->name, &new_level, NULL, true); _debug_set_level(id, new_level); } From 0945285e6cd67ee87e9313fb10221aba5bd69c6a Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Thu, 3 Sep 2026 15:07:33 +0200 Subject: [PATCH 1174/2366] s390/debug: Fix race between debug area resize and event logging Trace functions check for non-NULL id->areas without lock to minimize overhead. This opens a race window where a NULL pointer dereference occurs if id->areas is set to NULL (e.g. via echo 0 > ../pages) after the check and before id->lock is taken. Fix this by rechecking id->areas under lock. Signed-off-by: Mikhail Zaslonko Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/kernel/debug.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index cf411f203571..b5bf8284dbfc 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -1283,7 +1283,7 @@ void debug_set_critical(void) debug_entry_t *debug_event_common(debug_info_t *id, int level, const void *buf, int len) { - debug_entry_t *active; + debug_entry_t *active = NULL; unsigned long flags; if (!debug_active || !id->areas) @@ -1294,6 +1294,8 @@ debug_entry_t *debug_event_common(debug_info_t *id, int level, const void *buf, } else { raw_spin_lock_irqsave(&id->lock, flags); } + if (!id->areas) + goto out; do { active = get_active_entry(id); memcpy(DEBUG_DATA(active), buf, min(len, id->buf_size)); @@ -1303,7 +1305,7 @@ debug_entry_t *debug_event_common(debug_info_t *id, int level, const void *buf, len -= id->buf_size; buf += id->buf_size; } while (len > 0); - +out: raw_spin_unlock_irqrestore(&id->lock, flags); return active; } @@ -1316,7 +1318,7 @@ EXPORT_SYMBOL(debug_event_common); debug_entry_t *debug_exception_common(debug_info_t *id, int level, const void *buf, int len) { - debug_entry_t *active; + debug_entry_t *active = NULL; unsigned long flags; if (!debug_active || !id->areas) @@ -1327,6 +1329,8 @@ debug_entry_t *debug_exception_common(debug_info_t *id, int level, } else { raw_spin_lock_irqsave(&id->lock, flags); } + if (!id->areas) + goto out; do { active = get_active_entry(id); memcpy(DEBUG_DATA(active), buf, min(len, id->buf_size)); @@ -1336,7 +1340,7 @@ debug_entry_t *debug_exception_common(debug_info_t *id, int level, len -= id->buf_size; buf += id->buf_size; } while (len > 0); - +out: raw_spin_unlock_irqrestore(&id->lock, flags); return active; } @@ -1362,7 +1366,7 @@ static inline int debug_count_numargs(char *string) debug_entry_t *__debug_sprintf_event(debug_info_t *id, int level, char *string, ...) { debug_sprintf_entry_t *curr_event; - debug_entry_t *active; + debug_entry_t *active = NULL; unsigned long flags; int numargs, idx; va_list ap; @@ -1377,6 +1381,8 @@ debug_entry_t *__debug_sprintf_event(debug_info_t *id, int level, char *string, } else { raw_spin_lock_irqsave(&id->lock, flags); } + if (!id->areas) + goto out; active = get_active_entry(id); curr_event = (debug_sprintf_entry_t *) DEBUG_DATA(active); va_start(ap, string); @@ -1385,6 +1391,7 @@ debug_entry_t *__debug_sprintf_event(debug_info_t *id, int level, char *string, curr_event->args[idx] = va_arg(ap, long); va_end(ap); debug_finish_entry(id, active, level, 0); +out: raw_spin_unlock_irqrestore(&id->lock, flags); return active; @@ -1397,7 +1404,7 @@ EXPORT_SYMBOL(__debug_sprintf_event); debug_entry_t *__debug_sprintf_exception(debug_info_t *id, int level, char *string, ...) { debug_sprintf_entry_t *curr_event; - debug_entry_t *active; + debug_entry_t *active = NULL; unsigned long flags; int numargs, idx; va_list ap; @@ -1413,6 +1420,8 @@ debug_entry_t *__debug_sprintf_exception(debug_info_t *id, int level, char *stri } else { raw_spin_lock_irqsave(&id->lock, flags); } + if (!id->areas) + goto out; active = get_active_entry(id); curr_event = (debug_sprintf_entry_t *)DEBUG_DATA(active); va_start(ap, string); @@ -1421,6 +1430,7 @@ debug_entry_t *__debug_sprintf_exception(debug_info_t *id, int level, char *stri curr_event->args[idx] = va_arg(ap, long); va_end(ap); debug_finish_entry(id, active, level, 1); +out: raw_spin_unlock_irqrestore(&id->lock, flags); return active; @@ -1663,9 +1673,11 @@ static void debug_flush(debug_info_t *id, int area) unsigned long flags; int i, j; - if (!id || !id->areas) + if (!id) return; raw_spin_lock_irqsave(&id->lock, flags); + if (!id->areas) + goto out; if (area == DEBUG_FLUSH_ALL) { id->active_area = 0; memset(id->active_entries, 0, id->nr_areas * sizeof(int)); @@ -1680,6 +1692,7 @@ static void debug_flush(debug_info_t *id, int area) for (i = 0; i < id->pages_per_area; i++) memset(id->areas[area][i], 0, PAGE_SIZE); } +out: raw_spin_unlock_irqrestore(&id->lock, flags); } From 15fa028589c3a2545f0bff355eff06d5844196bf Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:37:33 +0200 Subject: [PATCH 1175/2366] s390/crypto: Fix skcipher_walk return code handling in aes_s390 The return codes from skcipher_walk_virt() were not properly checked before entering the processing loops in ecb_aes_crypt() and ctr_aes_crypt(). If skcipher_walk_virt() fails, the walk structure may be in an undefined state, and attempting to process data could lead to incorrect behavior or accessing uninitialized memory. Add proper return code checking to ensure correct handling of the walk initialization and walk advance and eventually return to the caller with that return code. Fixes: 7988fb2c03c8 ("crypto: s390/aes - convert to skcipher API") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 5.5+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/aes_s390.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/s390/crypto/aes_s390.c b/arch/s390/crypto/aes_s390.c index 62edc66d5478..366ce22d3623 100644 --- a/arch/s390/crypto/aes_s390.c +++ b/arch/s390/crypto/aes_s390.c @@ -129,7 +129,7 @@ static int ecb_aes_crypt(struct skcipher_request *req, unsigned long modifier) return fallback_skcipher_crypt(sctx, req, modifier); ret = skcipher_walk_virt(&walk, req, false); - while ((nbytes = walk.nbytes) != 0) { + while (!ret && ((nbytes = walk.nbytes) != 0)) { /* only use complete blocks */ n = nbytes & ~(AES_BLOCK_SIZE - 1); cpacf_km(sctx->fc | modifier, sctx->key, @@ -233,7 +233,7 @@ static int cbc_aes_crypt(struct skcipher_request *req, unsigned long modifier) return ret; memcpy(param.iv, walk.iv, AES_BLOCK_SIZE); memcpy(param.key, sctx->key, sctx->key_len); - while ((nbytes = walk.nbytes) != 0) { + while (!ret && ((nbytes = walk.nbytes) != 0)) { /* only use complete blocks */ n = nbytes & ~(AES_BLOCK_SIZE - 1); cpacf_kmc(sctx->fc | modifier, ¶m, @@ -359,7 +359,7 @@ static int xts_aes_crypt(struct skcipher_request *req, unsigned long modifier) memcpy(xts_param.key + offset, xts_ctx->key, xts_ctx->key_len); memcpy(xts_param.init, pcc_param.xts, 16); - while ((nbytes = walk.nbytes) != 0) { + while (!ret && ((nbytes = walk.nbytes) != 0)) { /* only use complete blocks */ n = nbytes & ~(AES_BLOCK_SIZE - 1); cpacf_km(xts_ctx->fc | modifier, xts_param.key + offset, @@ -487,7 +487,7 @@ static int fullxts_aes_crypt(struct skcipher_request *req, unsigned long modifi memcpy(fxts_param.tweak, req->iv, AES_BLOCK_SIZE); fxts_param.nap[0] = 0x01; /* initial alpha power (1, little-endian) */ - while ((nbytes = walk.nbytes) != 0) { + while (!ret && ((nbytes = walk.nbytes) != 0)) { /* only use complete blocks */ n = nbytes & ~(AES_BLOCK_SIZE - 1); cpacf_km(xts_ctx->fc | modifier, fxts_param.key + offset, @@ -577,7 +577,7 @@ static int ctr_aes_crypt(struct skcipher_request *req) locked = mutex_trylock(&ctrblk_lock); ret = skcipher_walk_virt(&walk, req, false); - while ((nbytes = walk.nbytes) >= AES_BLOCK_SIZE) { + while (!ret && ((nbytes = walk.nbytes) >= AES_BLOCK_SIZE)) { n = AES_BLOCK_SIZE; if (nbytes >= 2*AES_BLOCK_SIZE && locked) @@ -596,7 +596,7 @@ static int ctr_aes_crypt(struct skcipher_request *req) /* * final block may be < AES_BLOCK_SIZE, copy only nbytes */ - if (nbytes) { + if (!ret && nbytes) { memset(buf, 0, AES_BLOCK_SIZE); memcpy(buf, walk.src.virt.addr, nbytes); cpacf_kmctr(sctx->fc, sctx->key, buf, buf, From 8b7c3b6914f19caf648d05726a86af6326d3c2c6 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:37:34 +0200 Subject: [PATCH 1176/2366] s390/crypto: Fix missing scrub of temp buffers with AES ctr and gcm algorithm In function ctr_aes_crypt() there is a buffer used to process remaining bytes < AES_BLOCK_SIZE. This buffer was not scrubbed and thus could lead to expose of unwanted data. When the buffer is used explicitly scrub it at the end of the code block to avoid exposure of maybe sensitive data. In a similar way the function gcm_aes_crypt() hat an error path where the CPACF param block was not scrubbed. Instead of return early now these error paths go to end of function where explicit scrubbing is done. Similar with the buffers which are part of the gcm_sg_walk structs from the variables gw_in and gw_out. Fixes: d07f951903fa ("crypto: s390/aes - Fix buffer overread in CTR mode") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.8+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/aes_s390.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/arch/s390/crypto/aes_s390.c b/arch/s390/crypto/aes_s390.c index 366ce22d3623..10561aa687c7 100644 --- a/arch/s390/crypto/aes_s390.c +++ b/arch/s390/crypto/aes_s390.c @@ -604,6 +604,7 @@ static int ctr_aes_crypt(struct skcipher_request *req) memcpy(walk.dst.virt.addr, buf, nbytes); crypto_inc(walk.iv, AES_BLOCK_SIZE); ret = skcipher_walk_done(&walk, 0); + memzero_explicit(buf, sizeof(buf)); } return ret; @@ -895,10 +896,14 @@ static int gcm_aes_crypt(struct aead_request *req, unsigned int flags) gw_in.ptr, aad_bytes); n = aad_bytes + pc_bytes; - if (gcm_in_walk_done(&gw_in, n) != n) - return -ENOMEM; - if (gcm_out_walk_done(&gw_out, n) != n) - return -ENOMEM; + if (gcm_in_walk_done(&gw_in, n) != n) { + ret = -ENOMEM; + goto out; + } + if (gcm_out_walk_done(&gw_out, n) != n) { + ret = -ENOMEM; + goto out; + } aadlen -= aad_bytes; pclen -= pc_bytes; } while (aadlen + pclen > 0); @@ -910,7 +915,10 @@ static int gcm_aes_crypt(struct aead_request *req, unsigned int flags) } else scatterwalk_map_and_copy(param.t, req->dst, len, taglen, 1); +out: memzero_explicit(¶m, sizeof(param)); + memzero_explicit(gw_in.buf, sizeof(gw_in.buf)); + memzero_explicit(gw_out.buf, sizeof(gw_out.buf)); return ret; } From d1c44a7d085473173bb360b7218a43699c3f56c7 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:37:35 +0200 Subject: [PATCH 1177/2366] s390/crypto: Fix use of mutex in atomic context The AES CTR implementation used a mutex to lock one page of exclusive memory for fast CTR processing. Unfortunately a mutex is not save to use in atomic or interrupt context. So use a binary semaphore instead which is save to use in such environments. Furthermore rework the code to get rid of conditional locking. So restructure the AES CRT code by extracting the main loop into a separate function and just give in information about the (locked) page can be used or not (is not locked). Fixes: 7988fb2c03c8 ("crypto: s390/aes - convert to skcipher API") Suggested-by: Heiko Carstens Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 5.5+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/aes_s390.c | 63 +++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/arch/s390/crypto/aes_s390.c b/arch/s390/crypto/aes_s390.c index 10561aa687c7..0be6fa779d2c 100644 --- a/arch/s390/crypto/aes_s390.c +++ b/arch/s390/crypto/aes_s390.c @@ -26,14 +26,14 @@ #include #include #include -#include #include +#include #include #include #include static u8 *ctrblk; -static DEFINE_MUTEX(ctrblk_lock); +static DEFINE_SEMAPHORE(ctrblk_sem, 1); static cpacf_mask_t km_functions, kmc_functions, kmctr_functions, kma_functions; @@ -562,46 +562,61 @@ static unsigned int __ctrblk_init(u8 *ctrptr, u8 *iv, unsigned int nbytes) return n; } +static int __ctr_aes_crypt(struct s390_aes_ctx *sctx, + struct skcipher_walk *walk, bool locked) +{ + unsigned int n, nbytes; + int ret = 0; + u8 *ctrptr; + + while (!ret && ((nbytes = walk->nbytes) >= AES_BLOCK_SIZE)) { + n = AES_BLOCK_SIZE; + if (nbytes >= 2 * AES_BLOCK_SIZE && locked) + n = __ctrblk_init(ctrblk, walk->iv, nbytes); + ctrptr = (n > AES_BLOCK_SIZE) ? ctrblk : walk->iv; + cpacf_kmctr(sctx->fc, sctx->key, walk->dst.virt.addr, + walk->src.virt.addr, n, ctrptr); + if (ctrptr == ctrblk) + memcpy(walk->iv, ctrptr + n - AES_BLOCK_SIZE, + AES_BLOCK_SIZE); + crypto_inc(walk->iv, AES_BLOCK_SIZE); + ret = skcipher_walk_done(walk, nbytes - n); + } + + return ret; +} + static int ctr_aes_crypt(struct skcipher_request *req) { struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); struct s390_aes_ctx *sctx = crypto_skcipher_ctx(tfm); - u8 buf[AES_BLOCK_SIZE], *ctrptr; struct skcipher_walk walk; - unsigned int n, nbytes; - int ret, locked; + u8 buf[AES_BLOCK_SIZE]; + int ret; if (unlikely(!sctx->fc)) return fallback_skcipher_crypt(sctx, req, 0); - locked = mutex_trylock(&ctrblk_lock); - ret = skcipher_walk_virt(&walk, req, false); - while (!ret && ((nbytes = walk.nbytes) >= AES_BLOCK_SIZE)) { - n = AES_BLOCK_SIZE; + if (ret) + return ret; - if (nbytes >= 2*AES_BLOCK_SIZE && locked) - n = __ctrblk_init(ctrblk, walk.iv, nbytes); - ctrptr = (n > AES_BLOCK_SIZE) ? ctrblk : walk.iv; - cpacf_kmctr(sctx->fc, sctx->key, walk.dst.virt.addr, - walk.src.virt.addr, n, ctrptr); - if (ctrptr == ctrblk) - memcpy(walk.iv, ctrptr + n - AES_BLOCK_SIZE, - AES_BLOCK_SIZE); - crypto_inc(walk.iv, AES_BLOCK_SIZE); - ret = skcipher_walk_done(&walk, nbytes - n); + if (down_trylock(&ctrblk_sem) == 0) { + ret = __ctr_aes_crypt(sctx, &walk, true); + up(&ctrblk_sem); + } else { + ret = __ctr_aes_crypt(sctx, &walk, false); } - if (locked) - mutex_unlock(&ctrblk_lock); + /* * final block may be < AES_BLOCK_SIZE, copy only nbytes */ - if (!ret && nbytes) { + if (!ret && walk.nbytes > 0) { memset(buf, 0, AES_BLOCK_SIZE); - memcpy(buf, walk.src.virt.addr, nbytes); + memcpy(buf, walk.src.virt.addr, walk.nbytes); cpacf_kmctr(sctx->fc, sctx->key, buf, buf, AES_BLOCK_SIZE, walk.iv); - memcpy(walk.dst.virt.addr, buf, nbytes); + memcpy(walk.dst.virt.addr, buf, walk.nbytes); crypto_inc(walk.iv, AES_BLOCK_SIZE); ret = skcipher_walk_done(&walk, 0); memzero_explicit(buf, sizeof(buf)); From 403648373816ae8eb76fe4a393836b0d65fa8f85 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:30 +0200 Subject: [PATCH 1178/2366] s390/crypto: Fix return code handling at skcipher_walk_done in PAES algorithms All the 4 PAES cipher processing loops were not checking the return value of skcipher_walk_done() immediately after calling it. This could lead to error masking when both the walk operation failed and a subsequent key conversion was needed (k < n condition). Add immediate error checks after skcipher_walk_done() in all main processing loops (ECB, CBC, CTR, XTS modes) to ensure walk errors are properly propagated and not masked by subsequent operations. With that comes a slight rework around the skcipher_walk_done() invocation. It is now necessary to check if the walk has already been finalized (walk->nbytes is then 0) or not to avoid double de-allocation of resources held by the walk. Fixes: 6cd87cb5ef6c ("s390/crypto: Rework protected key AES for true asynch support") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 38 +++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 973436592318..89785ab95e6b 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -432,8 +432,11 @@ static int ecb_paes_do_crypt(struct s390_paes_ctx *ctx, n = nbytes & ~(AES_BLOCK_SIZE - 1); k = cpacf_km(ctx->fc | req_ctx->modifier, param, walk->dst.virt.addr, walk->src.virt.addr, n); - if (k) + if (k) { rc = skcipher_walk_done(walk, nbytes - k); + if (rc) + goto out; + } if (k < n) { if (!maysleep) { rc = -EKEYEXPIRED; @@ -495,7 +498,7 @@ static int ecb_paes_crypt(struct skcipher_request *req, unsigned long modifier) atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS) + if (rc != -EINPROGRESS && walk->nbytes) skcipher_walk_done(walk, rc); out: @@ -549,7 +552,7 @@ static int ecb_paes_do_one_request(struct crypto_engine *engine, void *areq) rc = ecb_paes_do_crypt(ctx, req_ctx, tested, true); if (rc == -EKEYEXPIRED) { return pkey_handle_expired(); - } else if (rc) { + } else if (rc && walk->nbytes) { skcipher_walk_done(walk, rc); } @@ -690,6 +693,8 @@ static int cbc_paes_do_crypt(struct s390_paes_ctx *ctx, if (k) { memcpy(walk->iv, param->iv, AES_BLOCK_SIZE); rc = skcipher_walk_done(walk, nbytes - k); + if (rc) + goto out; } if (k < n) { if (!maysleep) { @@ -752,7 +757,7 @@ static int cbc_paes_crypt(struct skcipher_request *req, unsigned long modifier) atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS) + if (rc != -EINPROGRESS && walk->nbytes) skcipher_walk_done(walk, rc); out: @@ -806,7 +811,7 @@ static int cbc_paes_do_one_request(struct crypto_engine *engine, void *areq) rc = cbc_paes_do_crypt(ctx, req_ctx, tested, true); if (rc == -EKEYEXPIRED) { return pkey_handle_expired(); - } else if (rc) { + } else if (rc && walk->nbytes) { skcipher_walk_done(walk, rc); } @@ -968,6 +973,11 @@ static int ctr_paes_do_crypt(struct s390_paes_ctx *ctx, AES_BLOCK_SIZE); crypto_inc(walk->iv, AES_BLOCK_SIZE); rc = skcipher_walk_done(walk, nbytes - k); + if (rc) { + if (locked) + mutex_unlock(&ctrblk_lock); + goto out; + } } if (k < n) { if (!maysleep) { @@ -1061,7 +1071,7 @@ static int ctr_paes_crypt(struct skcipher_request *req) atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS) + if (rc != -EINPROGRESS && walk->nbytes) skcipher_walk_done(walk, rc); out: @@ -1105,7 +1115,7 @@ static int ctr_paes_do_one_request(struct crypto_engine *engine, void *areq) rc = ctr_paes_do_crypt(ctx, req_ctx, tested, true); if (rc == -EKEYEXPIRED) { return pkey_handle_expired(); - } else if (rc) { + } else if (rc && walk->nbytes) { skcipher_walk_done(walk, rc); } @@ -1283,8 +1293,11 @@ static int xts_paes_do_crypt_fullkey(struct s390_pxts_ctx *ctx, n = nbytes & ~(AES_BLOCK_SIZE - 1); k = cpacf_km(ctx->fc | req_ctx->modifier, param->key + offset, walk->dst.virt.addr, walk->src.virt.addr, n); - if (k) + if (k) { rc = skcipher_walk_done(walk, nbytes - k); + if (rc) + goto out; + } if (k < n) { if (!maysleep) { rc = -EKEYEXPIRED; @@ -1377,8 +1390,11 @@ static int xts_paes_do_crypt_2keys(struct s390_pxts_ctx *ctx, n = nbytes & ~(AES_BLOCK_SIZE - 1); k = cpacf_km(ctx->fc | req_ctx->modifier, param->key + offset, walk->dst.virt.addr, walk->src.virt.addr, n); - if (k) + if (k) { rc = skcipher_walk_done(walk, nbytes - k); + if (rc) + goto out; + } if (k < n) { if (!maysleep) { rc = -EKEYEXPIRED; @@ -1485,7 +1501,7 @@ static inline int xts_paes_crypt(struct skcipher_request *req, unsigned long mod atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS) + if (rc != -EINPROGRESS && walk->nbytes) skcipher_walk_done(walk, rc); out: @@ -1539,7 +1555,7 @@ static int xts_paes_do_one_request(struct crypto_engine *engine, void *areq) rc = xts_paes_do_crypt(ctx, req_ctx, tested, true); if (rc == -EKEYEXPIRED) { return pkey_handle_expired(); - } else if (rc) { + } else if (rc && walk->nbytes) { skcipher_walk_done(walk, rc); } From 19a218b46b2471d370c11fb52040f36ac8d05d23 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:31 +0200 Subject: [PATCH 1179/2366] s390/crypto: Fix missing scrub of temp buffers with PAES algorithm In function ctr_paes_do_crypt() there is a buffer used to process remaining bytes < AES_BLOCK_SIZE. This buffer was not scrubbed and thus could lead to expose of unwanted data. Rework the code to explicitly scrub the buffer at the end of the function to avoid exposure of maybe sensitive data. In function __xts_2keys_prep_param() change the existing scrub to clean the whole param block instead of just the key field. Fixes: 6cd87cb5ef6c ("s390/crypto: Rework protected key AES for true asynch support") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 89785ab95e6b..9c8f9e2570f2 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -1026,6 +1026,7 @@ static int ctr_paes_do_crypt(struct s390_paes_ctx *ctx, } out: + memzero_explicit(buf, sizeof(buf)); pr_debug("rc=%d\n", rc); return rc; } @@ -1350,7 +1351,7 @@ static inline int __xts_2keys_prep_param(struct s390_pxts_ctx *ctx, memcpy(param->init, pcc_param.xts, 16); } - memzero_explicit(pcc_param.key, sizeof(pcc_param.key)); + memzero_explicit(&pcc_param, sizeof(pcc_param)); return rc; } From 749990db95d45cc3d96aab999ac7f110259b200c Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:32 +0200 Subject: [PATCH 1180/2366] s390/crypto: Fix use of mutex in atomic context in PAES The PAES CTR implementation used a mutex to lock one page of exclusive memory for fast CTR processing. Unfortunately a mutex is not save to use in atomic or interrupt context. So use a binary semaphore instead which is save to use in such environments. Furthermore rework the code to get rid of conditional locking. So restructure the PAES CRT code by extracting the main loop into a separate function and just give in information about the (locked) page can be used or not (is not locked). Fixes: 6cd87cb5ef6c ("s390/crypto: Rework protected key AES for true asynch support") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 113 +++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 53 deletions(-) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 9c8f9e2570f2..991a26766d40 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include @@ -45,7 +45,7 @@ module_param_named(clrkey, pkey_clrkey_allowed, bool, 0444); MODULE_PARM_DESC(clrkey, "Allow clear key material (default N)"); static u8 *ctrblk; -static DEFINE_MUTEX(ctrblk_lock); +static DEFINE_SEMAPHORE(ctrblk_sem, 1); static cpacf_mask_t km_functions, kmc_functions, kmctr_functions; @@ -919,15 +919,62 @@ static inline unsigned int __ctrblk_init(u8 *ctrptr, u8 *iv, unsigned int nbytes return n; } +static int __ctr_paes_do_crypt(struct s390_paes_ctx *ctx, + struct ctr_param *param, + struct skcipher_walk *walk, + bool tested, bool maysleep, bool locked) +{ + unsigned int nbytes, n, k; + u8 *ctrptr; + int rc = 0; + + /* + * Note that in case of partial processing or failure the walk + * is NOT unmapped here. So a follow up task may reuse the walk + * or in case of unrecoverable failure needs to unmap it. + */ + while ((nbytes = walk->nbytes) >= AES_BLOCK_SIZE) { + n = AES_BLOCK_SIZE; + if (nbytes >= 2 * AES_BLOCK_SIZE && locked) + n = __ctrblk_init(ctrblk, walk->iv, nbytes); + ctrptr = (n > AES_BLOCK_SIZE) ? ctrblk : walk->iv; + k = cpacf_kmctr(ctx->fc, param, walk->dst.virt.addr, + walk->src.virt.addr, n, ctrptr); + if (k) { + if (ctrptr == ctrblk) + memcpy(walk->iv, ctrptr + k - AES_BLOCK_SIZE, + AES_BLOCK_SIZE); + crypto_inc(walk->iv, AES_BLOCK_SIZE); + rc = skcipher_walk_done(walk, nbytes - k); + if (rc) + goto out; + } + if (k < n) { + if (!maysleep) { + rc = -EKEYEXPIRED; + goto out; + } + rc = paes_convert_key(ctx, tested); + if (rc) + goto out; + spin_lock_bh(&ctx->pk_lock); + memcpy(param->key, ctx->pk.protkey, sizeof(param->key)); + spin_unlock_bh(&ctx->pk_lock); + } + } + +out: + return rc; +} + static int ctr_paes_do_crypt(struct s390_paes_ctx *ctx, struct s390_pctr_req_ctx *req_ctx, bool tested, bool maysleep) { struct ctr_param *param = &req_ctx->param; struct skcipher_walk *walk = &req_ctx->walk; - u8 buf[AES_BLOCK_SIZE], *ctrptr; - unsigned int nbytes, n, k; - int pk_state, locked, rc = 0; + u8 buf[AES_BLOCK_SIZE]; + int pk_state, rc = 0; if (!req_ctx->param_init_done) { /* fetch and check protected key state */ @@ -953,57 +1000,17 @@ static int ctr_paes_do_crypt(struct s390_paes_ctx *ctx, if (rc) goto out; - locked = mutex_trylock(&ctrblk_lock); - - /* - * Note that in case of partial processing or failure the walk - * is NOT unmapped here. So a follow up task may reuse the walk - * or in case of unrecoverable failure needs to unmap it. - */ - while ((nbytes = walk->nbytes) >= AES_BLOCK_SIZE) { - n = AES_BLOCK_SIZE; - if (nbytes >= 2 * AES_BLOCK_SIZE && locked) - n = __ctrblk_init(ctrblk, walk->iv, nbytes); - ctrptr = (n > AES_BLOCK_SIZE) ? ctrblk : walk->iv; - k = cpacf_kmctr(ctx->fc, param, walk->dst.virt.addr, - walk->src.virt.addr, n, ctrptr); - if (k) { - if (ctrptr == ctrblk) - memcpy(walk->iv, ctrptr + k - AES_BLOCK_SIZE, - AES_BLOCK_SIZE); - crypto_inc(walk->iv, AES_BLOCK_SIZE); - rc = skcipher_walk_done(walk, nbytes - k); - if (rc) { - if (locked) - mutex_unlock(&ctrblk_lock); - goto out; - } - } - if (k < n) { - if (!maysleep) { - if (locked) - mutex_unlock(&ctrblk_lock); - rc = -EKEYEXPIRED; - goto out; - } - rc = paes_convert_key(ctx, tested); - if (rc) { - if (locked) - mutex_unlock(&ctrblk_lock); - goto out; - } - spin_lock_bh(&ctx->pk_lock); - memcpy(param->key, ctx->pk.protkey, sizeof(param->key)); - spin_unlock_bh(&ctx->pk_lock); - } + if (down_trylock(&ctrblk_sem) == 0) { + rc = __ctr_paes_do_crypt(ctx, param, walk, tested, maysleep, true); + up(&ctrblk_sem); + } else { + rc = __ctr_paes_do_crypt(ctx, param, walk, tested, maysleep, false); } - if (locked) - mutex_unlock(&ctrblk_lock); /* final block may be < AES_BLOCK_SIZE, copy only nbytes */ - if (nbytes) { + if (!rc && walk->nbytes > 0) { memset(buf, 0, AES_BLOCK_SIZE); - memcpy(buf, walk->src.virt.addr, nbytes); + memcpy(buf, walk->src.virt.addr, walk->nbytes); while (1) { if (cpacf_kmctr(ctx->fc, param, buf, buf, AES_BLOCK_SIZE, @@ -1020,7 +1027,7 @@ static int ctr_paes_do_crypt(struct s390_paes_ctx *ctx, memcpy(param->key, ctx->pk.protkey, sizeof(param->key)); spin_unlock_bh(&ctx->pk_lock); } - memcpy(walk->dst.virt.addr, buf, nbytes); + memcpy(walk->dst.virt.addr, buf, walk->nbytes); crypto_inc(walk->iv, AES_BLOCK_SIZE); rc = skcipher_walk_done(walk, 0); } From 5b97b969030c099333d973be420edef0d6452e0f Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:33 +0200 Subject: [PATCH 1181/2366] s390/crypto: Fix missing cra_flags in paes_s390 The 4 algorithms implemented in paes_s390 never had any cra_flags set. So add code which sets the cra_flag to CRYPTO_ALG_ASYNC and CRYPTO_ALG_NO_FALLBACK. Fixes: 4ccd065a69df ("crypto: ahash - Add support for drivers with no fallback") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.17+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 991a26766d40..5b7664031ea3 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -570,6 +570,7 @@ static struct skcipher_engine_alg ecb_paes_alg = { .base.cra_name = "ecb(paes)", .base.cra_driver_name = "ecb-paes-s390", .base.cra_priority = 401, /* combo: aes + ecb + 1 */ + .base.cra_flags = CRYPTO_ALG_ASYNC | CRYPTO_ALG_NO_FALLBACK, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct s390_paes_ctx), .base.cra_module = THIS_MODULE, @@ -829,6 +830,7 @@ static struct skcipher_engine_alg cbc_paes_alg = { .base.cra_name = "cbc(paes)", .base.cra_driver_name = "cbc-paes-s390", .base.cra_priority = 402, /* cbc-paes-s390 + 1 */ + .base.cra_flags = CRYPTO_ALG_ASYNC | CRYPTO_ALG_NO_FALLBACK, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct s390_paes_ctx), .base.cra_module = THIS_MODULE, @@ -1141,6 +1143,7 @@ static struct skcipher_engine_alg ctr_paes_alg = { .base.cra_name = "ctr(paes)", .base.cra_driver_name = "ctr-paes-s390", .base.cra_priority = 402, /* ecb-paes-s390 + 1 */ + .base.cra_flags = CRYPTO_ALG_ASYNC | CRYPTO_ALG_NO_FALLBACK, .base.cra_blocksize = 1, .base.cra_ctxsize = sizeof(struct s390_paes_ctx), .base.cra_module = THIS_MODULE, @@ -1581,6 +1584,7 @@ static struct skcipher_engine_alg xts_paes_alg = { .base.cra_name = "xts(paes)", .base.cra_driver_name = "xts-paes-s390", .base.cra_priority = 402, /* ecb-paes-s390 + 1 */ + .base.cra_flags = CRYPTO_ALG_ASYNC | CRYPTO_ALG_NO_FALLBACK, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct s390_pxts_ctx), .base.cra_module = THIS_MODULE, From 3fec882c33d9b61983ee31456777f623dccd1a35 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:34 +0200 Subject: [PATCH 1182/2366] s390/crypto: Fix handling of EBUSY in PAES when req is pushed to crypto engine When a request is transferred to the engine via crypto_transfer_skcipher_request_to_engine() there are two return codes signaling a successful transfer: EINPROGRESS and EBUSY. However the correct handling of EBUSY was missing and has been added as a return code indicating a successful transfer to the crypto engine. Fixes: 6cd87cb5ef6c ("s390/crypto: Rework protected key AES for true asynch support") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 5b7664031ea3..93e0e54ba2e8 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -463,6 +463,7 @@ static int ecb_paes_crypt(struct skcipher_request *req, unsigned long modifier) struct s390_paes_ctx *ctx = crypto_skcipher_ctx(tfm); struct skcipher_walk *walk = &req_ctx->walk; bool tested = crypto_skcipher_tested(tfm); + bool cleanup = true; int rc; /* @@ -494,15 +495,17 @@ static int ecb_paes_crypt(struct skcipher_request *req, unsigned long modifier) if (rc == 0 || rc == -EKEYEXPIRED) { atomic_inc(&ctx->via_engine_ctr); rc = crypto_transfer_skcipher_request_to_engine(paes_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS && walk->nbytes) + if (cleanup && walk->nbytes) skcipher_walk_done(walk, rc); out: - if (rc != -EINPROGRESS) + if (cleanup) memzero_explicit(&req_ctx->param, sizeof(req_ctx->param)); pr_debug("rc=%d\n", rc); return rc; @@ -723,6 +726,7 @@ static int cbc_paes_crypt(struct skcipher_request *req, unsigned long modifier) struct s390_paes_ctx *ctx = crypto_skcipher_ctx(tfm); struct skcipher_walk *walk = &req_ctx->walk; bool tested = crypto_skcipher_tested(tfm); + bool cleanup = true; int rc; /* @@ -754,15 +758,17 @@ static int cbc_paes_crypt(struct skcipher_request *req, unsigned long modifier) if (rc == 0 || rc == -EKEYEXPIRED) { atomic_inc(&ctx->via_engine_ctr); rc = crypto_transfer_skcipher_request_to_engine(paes_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS && walk->nbytes) + if (cleanup && walk->nbytes) skcipher_walk_done(walk, rc); out: - if (rc != -EINPROGRESS) + if (cleanup) memzero_explicit(&req_ctx->param, sizeof(req_ctx->param)); pr_debug("rc=%d\n", rc); return rc; @@ -1047,6 +1053,7 @@ static int ctr_paes_crypt(struct skcipher_request *req) struct s390_paes_ctx *ctx = crypto_skcipher_ctx(tfm); struct skcipher_walk *walk = &req_ctx->walk; bool tested = crypto_skcipher_tested(tfm); + bool cleanup = true; int rc; /* @@ -1077,15 +1084,17 @@ static int ctr_paes_crypt(struct skcipher_request *req) if (rc == 0 || rc == -EKEYEXPIRED) { atomic_inc(&ctx->via_engine_ctr); rc = crypto_transfer_skcipher_request_to_engine(paes_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS && walk->nbytes) + if (cleanup && walk->nbytes) skcipher_walk_done(walk, rc); out: - if (rc != -EINPROGRESS) + if (cleanup) memzero_explicit(&req_ctx->param, sizeof(req_ctx->param)); pr_debug("rc=%d\n", rc); return rc; @@ -1477,6 +1486,7 @@ static inline int xts_paes_crypt(struct skcipher_request *req, unsigned long mod struct s390_pxts_ctx *ctx = crypto_skcipher_ctx(tfm); struct skcipher_walk *walk = &req_ctx->walk; bool tested = crypto_skcipher_tested(tfm); + bool cleanup = true; int rc; /* @@ -1508,15 +1518,17 @@ static inline int xts_paes_crypt(struct skcipher_request *req, unsigned long mod if (rc == 0 || rc == -EKEYEXPIRED) { atomic_inc(&ctx->via_engine_ctr); rc = crypto_transfer_skcipher_request_to_engine(paes_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&ctx->via_engine_ctr); } - if (rc != -EINPROGRESS && walk->nbytes) + if (cleanup && walk->nbytes) skcipher_walk_done(walk, rc); out: - if (rc != -EINPROGRESS) + if (cleanup) memzero_explicit(&req_ctx->param, sizeof(req_ctx->param)); pr_debug("rc=%d\n", rc); return rc; From 330148371401de474b656eaf521861f12ec1a1ce Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:35 +0200 Subject: [PATCH 1183/2366] s390/crypto: Fix handling of EBUSY in PHMAC when req is pushed to crypto engine When a request is transferred to the engine via crypto_transfer_hash_request_to_engine() there are two return codes signaling a successful transfer: EINPROGRESS and EBUSY. However the correct handling of EBUSY was missing and has been added as a return code indicating a successful transfer to the crypto engine. Fixes: cbbc675506cc ("crypto: s390 - New s390 specific protected key hash phmac") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.17+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/phmac_s390.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/arch/s390/crypto/phmac_s390.c b/arch/s390/crypto/phmac_s390.c index 020a1beb2e22..532fe6c4e89c 100644 --- a/arch/s390/crypto/phmac_s390.c +++ b/arch/s390/crypto/phmac_s390.c @@ -62,8 +62,10 @@ static inline int hwh_prepare(struct ahash_request *req, */ static inline int hwh_advance(struct hash_walk_helper *hwh, int n) { - if (n < 0) + if (n < 0) { + hwh->walkbytes = n; return crypto_hash_walk_done(&hwh->walk, n); + } hwh->walkbytes -= n; hwh->walkaddr += n; @@ -606,6 +608,7 @@ static int phmac_update(struct ahash_request *req) struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm); struct kmac_sha2_ctx *kmac_ctx = &req_ctx->kmac_ctx; struct hash_walk_helper *hwh = &req_ctx->hwh; + bool cleanup = true; int rc; /* prep the walk in the request context */ @@ -629,12 +632,15 @@ static int phmac_update(struct ahash_request *req) req_ctx->async_op = OP_UPDATE; atomic_inc(&tfm_ctx->via_engine_ctr); rc = crypto_transfer_hash_request_to_engine(phmac_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&tfm_ctx->via_engine_ctr); } - if (rc != -EINPROGRESS) { - hwh_advance(hwh, rc); + if (cleanup) { + if (hwh->walkbytes > 0) + hwh_advance(hwh, rc); memzero_explicit(kmac_ctx, sizeof(*kmac_ctx)); } @@ -649,6 +655,7 @@ static int phmac_final(struct ahash_request *req) struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm); struct kmac_sha2_ctx *kmac_ctx = &req_ctx->kmac_ctx; + bool cleanup = true; int rc = 0; /* Try synchronous operation if no active engine usage */ @@ -667,12 +674,14 @@ static int phmac_final(struct ahash_request *req) req_ctx->async_op = OP_FINAL; atomic_inc(&tfm_ctx->via_engine_ctr); rc = crypto_transfer_hash_request_to_engine(phmac_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&tfm_ctx->via_engine_ctr); } out: - if (rc != -EINPROGRESS) + if (cleanup) memzero_explicit(kmac_ctx, sizeof(*kmac_ctx)); pr_debug("rc=%d\n", rc); return rc; @@ -685,6 +694,7 @@ static int phmac_finup(struct ahash_request *req) struct phmac_tfm_ctx *tfm_ctx = crypto_ahash_ctx(tfm); struct kmac_sha2_ctx *kmac_ctx = &req_ctx->kmac_ctx; struct hash_walk_helper *hwh = &req_ctx->hwh; + bool cleanup = true; int rc; /* prep the walk in the request context */ @@ -716,15 +726,17 @@ static int phmac_finup(struct ahash_request *req) /* req->async_op has been set to either OP_FINUP or OP_FINAL */ atomic_inc(&tfm_ctx->via_engine_ctr); rc = crypto_transfer_hash_request_to_engine(phmac_crypto_engine, req); - if (rc != -EINPROGRESS) + if (rc == -EINPROGRESS || rc == -EBUSY) + cleanup = false; + else atomic_dec(&tfm_ctx->via_engine_ctr); } - if (rc != -EINPROGRESS) + if (cleanup && hwh->walkbytes > 0) hwh_advance(hwh, rc); out: - if (rc != -EINPROGRESS) + if (cleanup) memzero_explicit(kmac_ctx, sizeof(*kmac_ctx)); pr_debug("rc=%d\n", rc); return rc; From ac1481320110b803ab9b79ab4d2ca11a74fc05f2 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:36 +0200 Subject: [PATCH 1184/2366] s390/crypto: Fix wrong return code to engine in asynch callbacks When crypto_finalize_hash_request() or crypto_finalize_skcipher_request() explicitly completes a request, the do_one_request callback must return 0 to indicate successful handling. Returning a negative error code causes the crypto engine to assume the driver failed to take ownership and triggers a second completion via crypto_request_complete(), resulting in a double completion. This pattern occurs in paes_s390.c 4 times and once in phmac_s390.c. Fixed in phmac_do_one_request() and all four paes do_one_request callbacks (ecb, cbc, ctr, xts) by returning 0 after explicit finalization instead of propagating the error code. Fixes: 6cd87cb5ef6c ("s390/crypto: Rework protected key AES for true asynch support") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 8 ++++---- arch/s390/crypto/phmac_s390.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index 93e0e54ba2e8..a4b972459f52 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -565,7 +565,7 @@ static int ecb_paes_do_one_request(struct crypto_engine *engine, void *areq) atomic_dec(&ctx->via_engine_ctr); crypto_finalize_skcipher_request(engine, req, rc); local_bh_enable(); - return rc; + return 0; } static struct skcipher_engine_alg ecb_paes_alg = { @@ -828,7 +828,7 @@ static int cbc_paes_do_one_request(struct crypto_engine *engine, void *areq) atomic_dec(&ctx->via_engine_ctr); crypto_finalize_skcipher_request(engine, req, rc); local_bh_enable(); - return rc; + return 0; } static struct skcipher_engine_alg cbc_paes_alg = { @@ -1144,7 +1144,7 @@ static int ctr_paes_do_one_request(struct crypto_engine *engine, void *areq) atomic_dec(&ctx->via_engine_ctr); crypto_finalize_skcipher_request(engine, req, rc); local_bh_enable(); - return rc; + return 0; } static struct skcipher_engine_alg ctr_paes_alg = { @@ -1588,7 +1588,7 @@ static int xts_paes_do_one_request(struct crypto_engine *engine, void *areq) atomic_dec(&ctx->via_engine_ctr); crypto_finalize_skcipher_request(engine, req, rc); local_bh_enable(); - return rc; + return 0; } static struct skcipher_engine_alg xts_paes_alg = { diff --git a/arch/s390/crypto/phmac_s390.c b/arch/s390/crypto/phmac_s390.c index 532fe6c4e89c..283a00754a06 100644 --- a/arch/s390/crypto/phmac_s390.c +++ b/arch/s390/crypto/phmac_s390.c @@ -926,7 +926,7 @@ static int phmac_do_one_request(struct crypto_engine *engine, void *areq) atomic_dec(&tfm_ctx->via_engine_ctr); crypto_finalize_hash_request(engine, req, rc); local_bh_enable(); - return rc; + return 0; } #define S390_ASYNC_PHMAC_ALG(x) \ From 7a08507ea5b4d06ad8d269287913573f34467565 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Mon, 31 Aug 2026 10:38:37 +0200 Subject: [PATCH 1185/2366] s390/crypto: Map EBUSY to EIO when key conversion fails repeatedly When hardware persistently returns -EBUSY after exhausting retries, the error propagates to crypto_finalize_*_request(). The crypto API's completion wrapper treats -EBUSY as a queueing status and swallows it, preventing the completion callback from firing. This causes callers using crypto_wait_req() to block indefinitely. Translate persistent -EBUSY to -EIO after retry exhaustion to ensure proper error propagation and callback invocation. Fixes: 6cd87cb5ef6c ("s390/crypto: Rework protected key AES for true asynch support") Signed-off-by: Harald Freudenberger Reviewed-by: Holger Dengler Cc: stable@vger.kernel.org # 6.16+ Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/paes_s390.c | 4 ++++ arch/s390/crypto/phmac_s390.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/arch/s390/crypto/paes_s390.c b/arch/s390/crypto/paes_s390.c index a4b972459f52..f987bcbe8f35 100644 --- a/arch/s390/crypto/paes_s390.c +++ b/arch/s390/crypto/paes_s390.c @@ -220,6 +220,10 @@ static inline int convert_key(const u8 *key, unsigned int keylen, xflags); } + /* But finally map -EBUSY to -EIO to indicate an IO failure */ + if (rc == -EBUSY) + rc = -EIO; + out: pr_debug("rc=%d\n", rc); return rc; diff --git a/arch/s390/crypto/phmac_s390.c b/arch/s390/crypto/phmac_s390.c index 283a00754a06..bbf8a6809ecb 100644 --- a/arch/s390/crypto/phmac_s390.c +++ b/arch/s390/crypto/phmac_s390.c @@ -341,6 +341,10 @@ static inline int convert_key(const u8 *key, unsigned int keylen, xflags); } + /* But finally map -EBUSY to -EIO to indicate an IO failure */ + if (rc == -EBUSY) + rc = -EIO; + out: pr_debug("rc=%d\n", rc); return rc; From dc2136341be9835e70ba7c6b36904cf3683fd029 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 31 Aug 2026 10:38:38 +0200 Subject: [PATCH 1186/2366] s390/crypto: Enable CONTEXT_ANALYSIS Enable CONTEXT_ANALYSIS since s390's crypto code compiles now without warnings. Reviewed-by: Harald Freudenberger Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/crypto/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/s390/crypto/Makefile b/arch/s390/crypto/Makefile index 48aeb0c0ffbd..1d6420813935 100644 --- a/arch/s390/crypto/Makefile +++ b/arch/s390/crypto/Makefile @@ -3,6 +3,8 @@ # Cryptographic API # +CONTEXT_ANALYSIS := y + obj-$(CONFIG_CRYPTO_AES_S390) += aes_s390.o obj-$(CONFIG_CRYPTO_PAES_S390) += paes_s390.o obj-$(CONFIG_S390_PRNG) += prng.o From fb5a08026a68be0ec5f660588311fb1091bf5d58 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 16 Aug 2026 00:51:09 +0100 Subject: [PATCH 1187/2366] mips: econet: fix unmet dependencies for ECONET ECONET selects EARLY_PRINTK_8250, SERIAL_OF_PLATFORM, and SERIAL_8250 without ensuring their dependencies, EARLY_PRINTK and TTY are met. This causes unmet dependencies: WARNING: unmet direct dependencies detected for SERIAL_8250 Depends on [n]: TTY [=n] && HAS_IOMEM [=y] && !S390 Selected by [y]: - ECONET [=y] WARNING: unmet direct dependencies detected for EARLY_PRINTK_8250 Depends on [n]: EARLY_PRINTK [=n] && USE_GENERIC_EARLY_PRINTK_8250 [=y] Selected by [y]: - ECONET [=y] WARNING: unmet direct dependencies detected for SERIAL_OF_PLATFORM Depends on [n]: TTY [=n] && HAS_IOMEM [=y] && SERIAL_8250 [=y] && OF [=y] Selected by [y]: - ECONET [=y] However, in the discussion of v1 of this patch, Arnd pointed out that these selects don't belong here in the first place. [1] Let's remove them to resolve the unmet dependencies. These unmet dependencies were found by kconfirm, a static analysis tool for Kconfig. Fixes: 79ee1d20e37c ("mips: econet: Fix incorrect Kconfig dependencies") Fixes: 35fb26f94dfa ("mips: Add EcoNet MIPS platform support") Suggested-by: Arnd Bergmann Signed-off-by: Julian Braha Tested-by: Caleb James DeLisle Signed-off-by: Thomas Bogendoerfer --- arch/mips/Kconfig | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index e2eb9627bd14..c3f69def2008 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -396,10 +396,7 @@ config ECONET bool "EcoNet MIPS family" select BOOT_RAW select DEBUG_ZBOOT if DEBUG_KERNEL - select EARLY_PRINTK_8250 select ECONET_EN751221_TIMER - select SERIAL_8250 - select SERIAL_OF_PLATFORM select SYS_SUPPORTS_BIG_ENDIAN select SYS_HAS_CPU_MIPS32_R1 select SYS_HAS_CPU_MIPS32_R2 From e261bfd3c042bccd25c82b04de079bdf8bdd8c3a Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Mon, 17 Aug 2026 22:03:44 +0000 Subject: [PATCH 1188/2366] MIPS: config: Add EcoNet EN751221 defconfig Add config based on OpenWrt for EN751221 boards. Of note: EcoNet bootloader has a 7.2MB kernel size limit so ZBOOT is assumed to be needed in all cases. Signed-off-by: Caleb James DeLisle Tested-by: Randy Dunlap Signed-off-by: Thomas Bogendoerfer --- MAINTAINERS | 1 + arch/mips/configs/econet_en751221_defconfig | 264 ++++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 arch/mips/configs/econet_en751221_defconfig diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..9b9e20d039f6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9324,6 +9324,7 @@ F: Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.y F: Documentation/devicetree/bindings/mips/econet.yaml F: Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml F: arch/mips/boot/dts/econet/ +F: arch/mips/configs/econet_en751221_defconfig F: arch/mips/econet/ F: drivers/clocksource/timer-econet-en751221.c F: drivers/irqchip/irq-econet-en751221.c diff --git a/arch/mips/configs/econet_en751221_defconfig b/arch/mips/configs/econet_en751221_defconfig new file mode 100644 index 000000000000..b35d33fe4044 --- /dev/null +++ b/arch/mips/configs/econet_en751221_defconfig @@ -0,0 +1,264 @@ +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_KERNEL_XZ=y +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +# CONFIG_CROSS_MEMORY_ATTACH is not set +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_PREEMPT=y +CONFIG_CGROUPS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_RDMA=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_BPF=y +CONFIG_NAMESPACES=y +# CONFIG_TIME_NS is not set +CONFIG_USER_NS=y +CONFIG_BLK_DEV_INITRD=y +# CONFIG_RD_GZIP is not set +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_RD_XZ is not set +# CONFIG_RD_LZO is not set +# CONFIG_RD_LZ4 is not set +# CONFIG_RD_ZSTD is not set +# CONFIG_INITRAMFS_PRESERVE_MTIME is not set +CONFIG_LD_DEAD_CODE_DATA_ELIMINATION=y +CONFIG_EXPERT=y +# CONFIG_SGETMASK_SYSCALL is not set +# CONFIG_IO_URING is not set +# CONFIG_RSEQ is not set +# CONFIG_CACHESTAT_SYSCALL is not set +CONFIG_ECONET=y +CONFIG_CPU_MIPS32_R2=y +CONFIG_ZBOOT_LOAD_ADDRESS=0x80020000 +CONFIG_ARCH_FORCE_MAX_ORDER=11 +# CONFIG_MIPS_FP_SUPPORT is not set +CONFIG_NR_CPUS=2 +CONFIG_HZ_100=y +CONFIG_MIPS_RAW_APPENDED_DTB=y +# CONFIG_SCHED_SMT is not set +CONFIG_JUMP_LABEL=y +# CONFIG_STACKPROTECTOR_STRONG is not set +# CONFIG_GCC_PLUGINS is not set +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +# CONFIG_BLOCK_LEGACY_AUTOLOAD is not set +CONFIG_BLK_DEV_THROTTLING=y +# CONFIG_BLK_DEBUG_FS is not set +CONFIG_PARTITION_ADVANCED=y +CONFIG_OF_PARTITION=y +# CONFIG_MQ_IOSCHED_DEADLINE is not set +# CONFIG_MQ_IOSCHED_KYBER is not set +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_SLAB_FREELIST_RANDOM=y +CONFIG_SLAB_FREELIST_HARDENED=y +# CONFIG_COMPAT_BRK is not set +CONFIG_LRU_GEN=y +CONFIG_LRU_GEN_ENABLED=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_MROUTE=y +CONFIG_IP_MROUTE_MULTIPLE_TABLES=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_SYN_COOKIES=y +# CONFIG_INET_DIAG is not set +CONFIG_TCP_CONG_ADVANCED=y +# CONFIG_TCP_CONG_BIC is not set +# CONFIG_TCP_CONG_WESTWOOD is not set +# CONFIG_TCP_CONG_HTCP is not set +# CONFIG_IPV6_SIT is not set +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y +CONFIG_IPV6_PIMSM_V2=y +CONFIG_IPV6_SEG6_LWTUNNEL=y +CONFIG_MPTCP=y +CONFIG_NETFILTER=y +# CONFIG_NETFILTER_EGRESS is not set +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_MARK=y +CONFIG_NF_CONNTRACK_ZONES=y +CONFIG_NF_CONNTRACK_PROCFS=y +CONFIG_NF_CONNTRACK_TIMEOUT=y +# CONFIG_NF_CT_PROTO_SCTP is not set +CONFIG_NF_TABLES=m +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_NUMGEN=m +CONFIG_NFT_CT=m +CONFIG_NFT_FLOW_OFFLOAD=m +CONFIG_NFT_LOG=m +CONFIG_NFT_LIMIT=m +CONFIG_NFT_MASQ=m +CONFIG_NFT_REDIR=m +CONFIG_NFT_NAT=m +CONFIG_NFT_QUOTA=m +CONFIG_NFT_REJECT=m +CONFIG_NFT_HASH=m +CONFIG_NFT_FIB_INET=m +CONFIG_NF_FLOW_TABLE_INET=m +CONFIG_NF_FLOW_TABLE=m +CONFIG_NFT_FIB_IPV4=m +CONFIG_NF_TABLES_ARP=y +CONFIG_NF_LOG_IPV4=m +CONFIG_NFT_FIB_IPV6=m +CONFIG_NF_LOG_IPV6=m +CONFIG_NF_TABLES_BRIDGE=m +CONFIG_BRIDGE=y +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_FQ_CODEL=y +CONFIG_NET_SCH_DEFAULT=y +CONFIG_DEFAULT_FQ_CODEL=y +CONFIG_NET_SWITCHDEV=y +CONFIG_NET_L3_MASTER_DEV=y +CONFIG_RFKILL=y +# CONFIG_LWTUNNEL_BPF is not set +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCIEAER=y +# CONFIG_PCIEASPM is not set +CONFIG_PCI_MSI=y +# CONFIG_VGA_ARB is not set +CONFIG_PCIE_MEDIATEK=y +CONFIG_MTD=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_CFI=y +CONFIG_MTD_CFI_INTELEXT=y +CONFIG_MTD_CFI_AMDSTD=y +CONFIG_MTD_COMPLEX_MAPPINGS=y +CONFIG_MTD_SPI_NAND=y +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_BEB_LIMIT=13 +CONFIG_MTD_UBI_BLOCK=y +CONFIG_NETDEVICES=y +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_LITEX is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +CONFIG_PPP=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOE=m +CONFIG_PPP_ASYNC=m +# CONFIG_WLAN_VENDOR_ADMTEK is not set +# CONFIG_WLAN_VENDOR_ATH is not set +# CONFIG_WLAN_VENDOR_ATMEL is not set +# CONFIG_WLAN_VENDOR_BROADCOM is not set +# CONFIG_WLAN_VENDOR_INTEL is not set +# CONFIG_WLAN_VENDOR_INTERSIL is not set +# CONFIG_WLAN_VENDOR_MARVELL is not set +# CONFIG_WLAN_VENDOR_MEDIATEK is not set +# CONFIG_WLAN_VENDOR_MICROCHIP is not set +# CONFIG_WLAN_VENDOR_PURELIFI is not set +# CONFIG_WLAN_VENDOR_RALINK is not set +# CONFIG_WLAN_VENDOR_REALTEK is not set +# CONFIG_WLAN_VENDOR_RSI is not set +# CONFIG_WLAN_VENDOR_SILABS is not set +# CONFIG_WLAN_VENDOR_ST is not set +# CONFIG_WLAN_VENDOR_TI is not set +# CONFIG_WLAN_VENDOR_ZYDAS is not set +# CONFIG_WLAN_VENDOR_QUANTENNA is not set +# CONFIG_INPUT is not set +# CONFIG_SERIO is not set +# CONFIG_VT is not set +# CONFIG_LEGACY_PTYS is not set +# CONFIG_LEGACY_TIOCSTI is not set +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_OF_PLATFORM=y +# CONFIG_HW_RANDOM is not set +# CONFIG_DEVMEM is not set +CONFIG_SPI=y +# CONFIG_PTP_1588_CLOCK is not set +CONFIG_GPIOLIB=y +# CONFIG_HWMON is not set +CONFIG_WATCHDOG=y +CONFIG_MFD_SYSCON=y +# CONFIG_USB_PCI is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CLASS_MULTICOLOR=y +CONFIG_LEDS_GPIO=m +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_NETDEV=y +CONFIG_STAGING=y +# CONFIG_MIPS_PLATFORM_DEVICES is not set +CONFIG_COMMON_CLK_EN7523=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_RESET_CONTROLLER=y +CONFIG_PHY_ECONET_PCIE=y +# CONFIG_DNOTIFY is not set +CONFIG_FANOTIFY=y +CONFIG_OVERLAY_FS=y +# CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW is not set +# CONFIG_PROC_PAGE_MONITOR is not set +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_SUMMARY=y +CONFIG_JFFS2_FS_XATTR=y +CONFIG_JFFS2_COMPRESSION_OPTIONS=y +CONFIG_UBIFS_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_FILE_DIRECT=y +CONFIG_SQUASHFS_COMPILE_DECOMP_MULTI_PERCPU=y +# CONFIG_SQUASHFS_ZLIB is not set +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_EMBEDDED=y +CONFIG_KEYS=y +CONFIG_INIT_STACK_NONE=y +CONFIG_FORTIFY_SOURCE=y +CONFIG_HARDENED_USERCOPY=y +CONFIG_LIST_HARDENED=y +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_CCM=y +CONFIG_CRYPTO_GCM=y +# CONFIG_CRYPTO_HW is not set +# CONFIG_XZ_DEC_X86 is not set +# CONFIG_XZ_DEC_POWERPC is not set +# CONFIG_XZ_DEC_ARM is not set +# CONFIG_XZ_DEC_ARMTHUMB is not set +# CONFIG_XZ_DEC_ARM64 is not set +# CONFIG_XZ_DEC_SPARC is not set +# CONFIG_XZ_DEC_RISCV is not set +CONFIG_PRINTK_TIME=y +# CONFIG_DEBUG_MISC is not set +CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y +CONFIG_DEBUG_INFO_REDUCED=y +CONFIG_FRAME_WARN=1024 +CONFIG_STRIP_ASM_SYMS=y +CONFIG_MAGIC_SYSRQ=y +# CONFIG_MAGIC_SYSRQ_SERIAL is not set +CONFIG_DEBUG_FS=y +# CONFIG_SLUB_DEBUG is not set +CONFIG_SCHED_STACK_END_CHECK=y +CONFIG_PANIC_ON_OOPS=y +CONFIG_PANIC_TIMEOUT=1 +CONFIG_RCU_CPU_STALL_TIMEOUT=60 +# CONFIG_RCU_TRACE is not set +# CONFIG_FTRACE is not set From bbc448c541ed90d52f9ec22f17394304e3bf170e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Fri, 28 Aug 2026 16:07:33 +0200 Subject: [PATCH 1189/2366] mips: select CONFIG_WEAK_REORDERING_BEYOND_LLSC from CONFIG_EYEQ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On I6500 CPU cores, lld and scd give no ordering guarantees (same as all other instructions). To respect the assumption that arch_cmpxchg() is fully ordered, we must inject sync instructions above and below our lld/scd loops using the already in place WEAK_REORDERING_BEYOND_LLSC infrastructure. Otherwise, bad things can happen: [ 34.054496] CPU 3 Unable to handle kernel paging request at virtual address 0000000000000000, epc == a80000080838e01c, ra == a80000080838dfc4 [ 34.054559] Oops[#1]: [ 34.069561] CPU: 3 UID: 0 PID: 170 Comm: pipe_race Not tainted 7.2.0-rc6-01553-gb73c35220968-dirty #103 VOLUNTARY [ 34.079932] Hardware name: Mobile EyeQ5 MP5 Evaluation board [ 34.085592] $ 0 : 0000000000000000 0000000000000001 0000000000000000 0000000000000000 [ 34.093616] $ 4 : a800000808ee2618 000000000b7a879d 0000000000001000 0000000000000000 [ 34.101638] $ 8 : 0000000000e3f2c9 0000000000000000 a800000808a2a9f8 0000000000000000 [ 34.109660] $12 : a8000008139ffcd8 ffffffff84080018 a80000080837fae0 7878787878787878 [ 34.117682] $16 : a800000807e82940 0000000000001000 0000000000000000 0000000000000000 [ 34.125704] $20 : a800000802920e00 a8000008139ffdf8 a800000802649400 0000000000e3f2c9 [ 34.133726] $24 : 0000000000000006 00000001200406e0 [ 34.141783] $28 : a8000008139fc000 a8000008139ffd10 0000000000e3f2c8 a80000080838dfc4 [ 34.149837] epc : a80000080838e01c anon_pipe_read+0xd4/0x428 [ 34.155697] ra : a80000080838dfc4 anon_pipe_read+0x7c/0x428 [ 34.161549] Status: 140000e3 KX SX UX KERNEL EXL IE [ 34.166551] Cause : 40800408 (ExcCode 02) [ 34.170574] BadVA : 0000000000000000 [ 34.174161] PrId : 0001b028 (MIPS I6500) [ 34.178183] Process pipe_race (pid: 170, threadinfo=000000005ca35720, task=00000000e1013890, tls=000000014ebbb780) [ 34.188568] Stack : a800000802649400 0000000000000000 0000000000000000 a8000008139ffdd0 [ 34.196623] 0000000000000fba a800000808ee0000 0000000000000001 a8000008130c3e80 [ 34.204676] a8000008080d1280 a8000008139ffd58 a8000008139ffd58 1dbd2b22ea1dd500 [ 34.212729] a800000802649400 a800000808ee0000 ffffffffffffffea 0000000000000001 [ 34.220783] 0000000000001000 0000000000000000 00000001200ae518 ffffffffffffffff [ 34.228836] 000000fffbe0e530 a80000080837edf4 000000fffbe0e530 0000000000000000 [ 34.236890] 0000000000000000 0000000000000000 000000014ebb55a0 0000000000001000 [ 34.244943] 0000000000000001 a800000802649400 0000000000000000 0000000000000000 [ 34.252996] 0000000000000000 0000400400000000 0000000000000000 1dbd2b22ea1dd500 [ 34.261049] 00000000140000e3 a800000802649400 a800000802649400 a800000808ee0000 [ 34.269103] ... [ 34.271568] Call Trace: [ 34.274026] [] anon_pipe_read+0xd4/0x428 [ 34.279533] [] vfs_read+0x25c/0x318 [ 34.284607] [] ksys_read+0x104/0x138 [ 34.289763] [] syscall_common+0x44/0x68 [ 34.295187] [ 34.296689] Code: f84000cf 02209825 de020010 d8400004 02002825 0040f809 02802025 f84000c3 [ 34.306504] [ 34.308099] ---[ end trace 0000000000000000 ]--- My initial reproducer was the xdp-tools test suite. A standalone reproducer would be an lld/scd loop that, when the read is reordered by the CPU, triggers a fault. We can achieve this from userspace by stressing an anonymous pipe, which uses a mutex. Program used: // SPDX-License-Identifier: GPL-2.0 // pipe_race.c - reproducer for MIPS LL/SC reordering vs fs/pipe.c // // Two userspace processes on an anonymous pipe: // parent = writer: tight write() loop // child = reader: tight read() loop #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include int main(void) { long wrsize = 70; // bytes per write() long rdsize = 4096; // bytes per read() int pfd[2]; char *buf; pid_t pid; int ret; ret = pipe(pfd); assert(!ret); pid = fork(); assert(pid >= 0); if (pid == 0) { /* reader */ close(pfd[1]); buf = malloc(rdsize); assert(buf); for (;;) { ssize_t n = read(pfd[0], buf, rdsize); if (n < 0 && errno == EINTR) continue; if (n <= 0) _exit(n < 0 ? 1 : 0); } } close(pfd[0]); /* writer */ buf = malloc(wrsize); assert(buf); memset(buf, 'x', wrsize); for (;;) { ssize_t n = write(pfd[1], buf, wrsize); if (n < 0 && errno == EINTR) continue; if (n != wrsize) break; } kill(pid, SIGKILL); wait(NULL); return 0; } Fixes: fbe0fae601b7 ("MIPS: mobileye: Add EyeQ6H support") Cc: stable@vger.kernel.org Signed-off-by: Théo Lebrun Reviewed-by: Jiaxun Yang Signed-off-by: Thomas Bogendoerfer --- arch/mips/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index c3f69def2008..d7c67cebe065 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -658,6 +658,7 @@ config EYEQ select USB_UHCI_BIG_ENDIAN_MMIO if CPU_BIG_ENDIAN select USE_OF select HOTPLUG_PARALLEL if HOTPLUG_CPU + select WEAK_REORDERING_BEYOND_LLSC help Select this to build a kernel supporting EyeQ SoC from Mobileye. From 126f16e0a1b353c2ba5c7e2c8626cfa865934f9f Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 1 Sep 2026 19:33:55 +0000 Subject: [PATCH 1190/2366] MIPS: Octeon: apply USB FDT fixups also when USB is modular The uctl/usbn device-tree fixups in octeon_prune_device_tree() - which set the board's USB reference-clock frequency and type from __cvmx_helper_board_usb_get_clock_type() - are guarded by "#ifdef CONFIG_USB", which is false when USB is built as a module. The fixups then silently disappear and octeon-hcd sees whatever default the DTS carries (12MHz crystal in octeon_3xxx.dts), leaving the PHY dead or the bus erroring on boards with a different reference clock. Use IS_ENABLED() so USB=m gets the same fixups as USB=y. Fixes: 7fd57ab9d9cf ("MIPS: Octeon: Fix compile error when USB is not enabled.") Assisted-by: Claude:claude-opus-5 Signed-off-by: Orgad Shaneh Signed-off-by: Thomas Bogendoerfer --- arch/mips/cavium-octeon/octeon-platform.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/mips/cavium-octeon/octeon-platform.c b/arch/mips/cavium-octeon/octeon-platform.c index 47677b5d7ed0..f53c98372e0b 100644 --- a/arch/mips/cavium-octeon/octeon-platform.c +++ b/arch/mips/cavium-octeon/octeon-platform.c @@ -18,7 +18,7 @@ #include #include -#ifdef CONFIG_USB +#if IS_ENABLED(CONFIG_USB) #include #include #include @@ -1080,7 +1080,7 @@ int __init octeon_prune_device_tree(void) ; } -#ifdef CONFIG_USB +#if IS_ENABLED(CONFIG_USB) /* OHCI/UHCI USB */ alias_prop = fdt_getprop(initial_boot_params, aliases, "uctl", NULL); From 71c610aeb1770302ac9c9e0b9a4ecd37f1311928 Mon Sep 17 00:00:00 2001 From: Kazuki Hanai Date: Tue, 8 Sep 2026 20:00:53 +0900 Subject: [PATCH 1191/2366] ALSA: us122l: Prevent write upgrades for read mappings The hwdep mmap callback rejects read-buffer mappings that are initially writable, but leaves VM_MAYWRITE set on mappings created with PROT_READ. A process that can open the hwdep node O_RDWR can later use mprotect() to make the mapping writable. The read allocation begins with struct usb_stream. Its read_size member is used by the fault handler to decide which pages belong to the read buffer. The read VMA intentionally remains expandable because pcm_usb_stream uses mremap() after reading that size. Changing read_size first can therefore map and access pages beyond the allocation. The same member is also consumed by usb_stream_free(), where changing it can make free_pages_exact() release pages outside the allocation. Clear VM_MAYWRITE for read-buffer mappings after rejecting an initially writable VMA. This keeps the separate output-buffer mapping writable while preventing later permission upgrades. Fixes: 030a07e44129 ("ALSA: Add USB US122L driver") Cc: stable@vger.kernel.org Signed-off-by: Kazuki Hanai Link: https://patch.msgid.link/20260908110053.2950767-1-hnkz.64@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/usx2y/us122l.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index f00b53346abd..a5db0d044ef9 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -180,8 +180,11 @@ static int usb_stream_hwdep_mmap(struct snd_hwdep *hw, guard(mutex)(&us122l->mutex); s = us122l->sk.s; read = offset < s->read_size; - if (read && area->vm_flags & VM_WRITE) - return -EPERM; + if (read) { + if (area->vm_flags & VM_WRITE) + return -EPERM; + vm_flags_clear(area, VM_MAYWRITE); + } /* if userspace tries to mmap beyond end of our buffer, fail */ if (size > PAGE_ALIGN(read ? s->read_size : s->write_size)) { dev_warn(hw->card->dev, "%s: size %lu > %u\n", __func__, From 7284788743121ec8bed556b00f830dc52ad9955d Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 8 Sep 2026 22:41:53 +0900 Subject: [PATCH 1192/2366] ALSA: hda: Report a change when only the channel status bytes move The put() callback of "IEC958 Playback Default" returns whether the converted register value moved. The convert_from_spdif_status() helper reads part of the first two channel status bytes and none of the last two, while the get() callback returns all four. So a write that lands only in the bits it does not read changes what userspace reads back and reports no change. Of the 31 bits above the mode bit, 20 are such bits in consumer mode and 29 in professional mode. The core notifies only on a positive return. Toggling status[2] bit 0 on an HDA HDMI codec moves the read-back from 04 00 00 00 to 04 00 01 00 with no event. Toggling the non-audio bit in status[0] gives one. Compare the stored status as well, the way the ac97 code does. The write to the codec stays gated on the converted value. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: HyeongJun An Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260908134153.1614273-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/common/codec.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 7d17d773cfbf..5d9b53bd64ea 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -2277,6 +2277,7 @@ static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol, int idx = kcontrol->private_value; struct hda_spdif_out *spdif; hda_nid_t nid; + unsigned int old_status; unsigned short val; int change; @@ -2285,6 +2286,7 @@ static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol, guard(mutex)(&codec->spdif_mutex); spdif = snd_array_elem(&codec->spdif_out, idx); nid = spdif->nid; + old_status = spdif->status; spdif->status = ucontrol->value.iec958.status[0] | ((unsigned int)ucontrol->value.iec958.status[1] << 8) | ((unsigned int)ucontrol->value.iec958.status[2] << 16) | @@ -2295,7 +2297,7 @@ static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol, spdif->ctls = val; if (change && nid != (u16)-1) set_dig_out_convert(codec, nid, val & 0xff, (val >> 8) & 0xff); - return change; + return change || spdif->status != old_status; } #define snd_hda_spdif_out_switch_info snd_ctl_boolean_mono_info From cf4d35896621b7298eef51b7a465e5c0cb22f670 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 14:39:44 -0300 Subject: [PATCH 1193/2366] smb: client: fix uid/gid override in getattr with posix extensions When mounting with 'multiuser,posix' options, cifs_getattr() overrides the server-provided uid/gid with the current process's fsuid/fsgid. This is because the condition only checks for unix extensions (tcon->unix_ext) but not posix extensions (tcon->posix_extensions). With SMB3 POSIX extensions, the server provides real uid/gid values just like with unix extensions, so they should be preserved rather than replaced with the caller's credentials. Add a tcon->posix_extensions check to the condition so that uid/gid from the server are properly reported in stat results. Reported-by: Arthur Lesuisse Closes: https://lore.kernel.org/r/DB9P190MB2012266F6B8DECBE5D26A1798DB52@DB9P190MB2012.EURP190.PROD.OUTLOOK.COM Suggested-by: Arthur Lesuisse Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/inode.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 12ed8db10e00..49f9993ad567 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2992,14 +2992,14 @@ int cifs_getattr(struct mnt_idmap *idmap, const struct path *path, stat->attributes |= STATX_ATTR_ENCRYPTED; /* - * If on a multiuser mount without unix extensions or cifsacl being - * enabled, and the admin hasn't overridden them, set the ownership - * to the fsuid/fsgid of the current process. + * If on a multiuser mount without unix extensions, posix extensions + * or cifsacl being enabled, and the admin hasn't overridden them, + * set the ownership to the fsuid/fsgid of the current process. */ sbflags = cifs_sb_flags(cifs_sb); if ((sbflags & CIFS_MOUNT_MULTIUSER) && !(sbflags & CIFS_MOUNT_CIFS_ACL) && - !tcon->unix_ext) { + !tcon->unix_ext && !tcon->posix_extensions) { if (!(sbflags & CIFS_MOUNT_OVERR_UID)) stat->uid = current_fsuid(); if (!(sbflags & CIFS_MOUNT_OVERR_GID)) From 18a72975e9f35aadecc75b031f693f2d1f49308f Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 14:40:04 -0300 Subject: [PATCH 1194/2366] smb: client: honor forceuid/forcegid when mapping SIDs to uid/gid When the administrator mounts with forceuid or forcegid (uid=/gid= mount options), they expect all files to appear owned by the specified user/group. However, several code paths unconditionally called sid_to_id() to overwrite cf_uid/cf_gid with server-provided values, ignoring the administrator's explicit override: - smb311_posix_info_to_fattr() (stat via POSIX extensions) - cifs_posix_to_fattr() (readdir via POSIX extensions) - parse_sec_desc() (CIFS ACL ownership mapping) This allowed an untrusted server to dictate local file ownership even when the mount was configured to force specific uid/gid values. Fix all three call sites to check CIFS_MOUNT_OVERR_UID and CIFS_MOUNT_OVERR_GID before calling sid_to_id(), following the same pattern already used by cifs_unix_basic_to_fattr() for unix extensions. Closes: https://sashiko.dev/#/patchset/20260906155816.603278-1-pc%40manguebit.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/cifsacl.c | 29 ++++++++++++++++++----------- fs/smb/client/inode.c | 9 +++++++-- fs/smb/client/readdir.c | 9 +++++++-- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index 213a421bf8e9..def8908dd7e9 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -1346,6 +1346,7 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb, { int rc = 0; struct smb_sid *owner_sid_ptr, *group_sid_ptr; + unsigned int sbflags = cifs_sb_flags(cifs_sb); struct smb_acl *dacl_ptr; /* no need for SACL ptr */ char *end_of_acl; __u32 dacloffset, osidoffset, gsidoffset; @@ -1364,17 +1365,21 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb, cifs_dbg(NOISY, "revision %d type 0x%x ooffset 0x%x goffset 0x%x sacloffset 0x%x dacloffset 0x%x\n", pntsd->revision, pntsd->type, osidoffset, gsidoffset, le32_to_cpu(pntsd->sacloffset), dacloffset); -/* cifs_dump_mem("owner_sid: ", owner_sid_ptr, 64); */ + fattr->cf_uid = cifs_sb->ctx->linux_uid; + fattr->cf_gid = cifs_sb->ctx->linux_gid; + rc = sid_from_sd(pntsd, acl_len, osidoffset, &owner_sid_ptr); if (rc) { cifs_dbg(FYI, "%s: Error %d parsing Owner SID\n", __func__, rc); return rc; } - rc = sid_to_id(cifs_sb, owner_sid_ptr, fattr, SIDOWNER); - if (rc) { - cifs_dbg(FYI, "%s: Error %d mapping Owner SID to uid\n", - __func__, rc); - return rc; + if (!(sbflags & CIFS_MOUNT_OVERR_UID)) { + rc = sid_to_id(cifs_sb, owner_sid_ptr, fattr, SIDOWNER); + if (rc) { + cifs_dbg(FYI, "%s: Error %d mapping Owner SID to uid\n", + __func__, rc); + return rc; + } } rc = sid_from_sd(pntsd, acl_len, gsidoffset, &group_sid_ptr); @@ -1383,11 +1388,13 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb, __func__, rc); return rc; } - rc = sid_to_id(cifs_sb, group_sid_ptr, fattr, SIDGROUP); - if (rc) { - cifs_dbg(FYI, "%s: Error %d mapping Group SID to gid\n", - __func__, rc); - return rc; + if (!(sbflags & CIFS_MOUNT_OVERR_GID)) { + rc = sid_to_id(cifs_sb, group_sid_ptr, fattr, SIDGROUP); + if (rc) { + cifs_dbg(FYI, "%s: Error %d mapping Group SID to gid\n", + __func__, rc); + return rc; + } } if (dacloffset) { diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 49f9993ad567..1fe0ef0a95db 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -851,6 +851,7 @@ static void smb311_posix_info_to_fattr(struct cifs_fattr *fattr, struct smb311_posix_qinfo *info = &data->posix_fi; struct cifs_sb_info *cifs_sb = CIFS_SB(sb); struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); + unsigned int sbflags = cifs_sb_flags(cifs_sb); memset(fattr, 0, sizeof(*fattr)); @@ -895,8 +896,12 @@ static void smb311_posix_info_to_fattr(struct cifs_fattr *fattr, fattr->cf_symlink_target = data->symlink_target; data->symlink_target = NULL; } - sid_to_id(cifs_sb, &data->posix_owner, fattr, SIDOWNER); - sid_to_id(cifs_sb, &data->posix_group, fattr, SIDGROUP); + fattr->cf_uid = cifs_sb->ctx->linux_uid; + fattr->cf_gid = cifs_sb->ctx->linux_gid; + if (!(sbflags & CIFS_MOUNT_OVERR_UID)) + sid_to_id(cifs_sb, &data->posix_owner, fattr, SIDOWNER); + if (!(sbflags & CIFS_MOUNT_OVERR_GID)) + sid_to_id(cifs_sb, &data->posix_group, fattr, SIDGROUP); cifs_dbg(FYI, "POSIX query info: mode 0x%x uniqueid 0x%llx nlink %d\n", fattr->cf_mode, fattr->cf_uniqueid, fattr->cf_nlink); diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c index 32a75afca8f5..1ea84f4ada39 100644 --- a/fs/smb/client/readdir.c +++ b/fs/smb/client/readdir.c @@ -242,6 +242,7 @@ static void cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info, struct cifs_sb_info *cifs_sb) { + unsigned int sbflags = cifs_sb_flags(cifs_sb); struct smb2_posix_info_parsed parsed; posix_info_parse(info, NULL, &parsed); @@ -281,8 +282,12 @@ cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info, le32_to_cpu(info->ReparseTag), le32_to_cpu(info->Mode)); - sid_to_id(cifs_sb, &parsed.owner, fattr, SIDOWNER); - sid_to_id(cifs_sb, &parsed.group, fattr, SIDGROUP); + fattr->cf_uid = cifs_sb->ctx->linux_uid; + fattr->cf_gid = cifs_sb->ctx->linux_gid; + if (!(sbflags & CIFS_MOUNT_OVERR_UID)) + sid_to_id(cifs_sb, &parsed.owner, fattr, SIDOWNER); + if (!(sbflags & CIFS_MOUNT_OVERR_GID)) + sid_to_id(cifs_sb, &parsed.group, fattr, SIDGROUP); } static void __dir_info_to_fattr(struct cifs_fattr *fattr, const void *info) From cd2b2b57921d4caa7875e83198bb2aa71254328b Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 14:40:23 -0300 Subject: [PATCH 1195/2366] smb: client: fix WSL reparse point uid/gid override wsl_to_fattr() unconditionally overwrites cf_uid/cf_gid with values from WSL extended attributes ($LXUID/$LXGID), ignoring the forceuid and forcegid mount options. Fix this by initializing cf_uid/cf_gid to the mount defaults and gating the $LXUID/$LXGID EA parsing on forceuid/forcegid. Closes: https://sashiko.dev/#/patchset/20260906190803.667489-1-pc%40manguebit.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 5cc5b0410d48..178da801e775 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1137,10 +1137,14 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, struct cifs_sb_info *cifs_sb, u32 tag, struct cifs_fattr *fattr) { + unsigned int sbflags = cifs_sb_flags(cifs_sb); struct smb2_file_full_ea_info *ea; bool have_xattr_dev = false; u32 next = 0; + fattr->cf_uid = cifs_sb->ctx->linux_uid; + fattr->cf_gid = cifs_sb->ctx->linux_gid; + switch (tag) { case IO_REPARSE_TAG_LX_SYMLINK: fattr->cf_mode |= S_IFLNK; @@ -1177,11 +1181,13 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, nlen = ea->ea_name_length; v = (void *)((u8 *)ea->ea_data + ea->ea_name_length + 1); - if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen)) - fattr->cf_uid = wsl_make_kuid(cifs_sb, v); - else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen)) - fattr->cf_gid = wsl_make_kgid(cifs_sb, v); - else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) { + if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen)) { + if (!(sbflags & CIFS_MOUNT_OVERR_UID)) + fattr->cf_uid = wsl_make_kuid(cifs_sb, v); + } else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen)) { + if (!(sbflags & CIFS_MOUNT_OVERR_GID)) + fattr->cf_gid = wsl_make_kgid(cifs_sb, v); + } else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) { /* File type in reparse point tag and in xattr mode must match. */ if (S_DT(fattr->cf_mode) != S_DT(le32_to_cpu(*(__le32 *)v))) return false; From da6e25842431982d5a53cf00d925b98c690f4467 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 14:40:39 -0300 Subject: [PATCH 1196/2366] smb: client: avoid using uninitialized SIDs in cifs_posix_to_fattr() cifs_posix_to_fattr() ignores the return value of posix_info_parse(). When a malformed POSIX directory entry is encountered (e.g. invalid SID lengths from an untrusted server), posix_info_parse() returns -1 without populating the 'parsed' struct. The uninitialized stack memory in parsed.owner and parsed.group is then passed to sid_to_id(), which processes the garbage bytes and passes them to request_key() to construct a SID string, potentially leaking kernel stack contents to the userspace idmap daemon. Fix this by checking the return value and skipping the SID-to-id mapping when parsing fails. The remaining fattr fields (timestamps, mode, etc.) are populated directly from the 'info' pointer so they are unaffected. Closes: https://sashiko.dev/#/patchset/20260906172005.627163-1-pc%40manguebit.org Closes: https://sashiko.dev/#/patchset/20260906181540.647469-1-pc%40manguebit.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/readdir.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c index 1ea84f4ada39..9530e5b01564 100644 --- a/fs/smb/client/readdir.c +++ b/fs/smb/client/readdir.c @@ -244,8 +244,9 @@ cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info, { unsigned int sbflags = cifs_sb_flags(cifs_sb); struct smb2_posix_info_parsed parsed; + int rc; - posix_info_parse(info, NULL, &parsed); + rc = posix_info_parse(info, NULL, &parsed); memset(fattr, 0, sizeof(*fattr)); fattr->cf_uniqueid = le64_to_cpu(info->Inode); @@ -284,10 +285,15 @@ cifs_posix_to_fattr(struct cifs_fattr *fattr, struct smb2_posix_info *info, fattr->cf_uid = cifs_sb->ctx->linux_uid; fattr->cf_gid = cifs_sb->ctx->linux_gid; - if (!(sbflags & CIFS_MOUNT_OVERR_UID)) - sid_to_id(cifs_sb, &parsed.owner, fattr, SIDOWNER); - if (!(sbflags & CIFS_MOUNT_OVERR_GID)) - sid_to_id(cifs_sb, &parsed.group, fattr, SIDGROUP); + if (rc < 0) { + cifs_dbg(VFS, "%s: failed to parse SIDs: %d\n", + __func__, rc); + } else { + if (!(sbflags & CIFS_MOUNT_OVERR_UID)) + sid_to_id(cifs_sb, &parsed.owner, fattr, SIDOWNER); + if (!(sbflags & CIFS_MOUNT_OVERR_GID)) + sid_to_id(cifs_sb, &parsed.group, fattr, SIDGROUP); + } } static void __dir_info_to_fattr(struct cifs_fattr *fattr, const void *info) From fa7a2cfcf1e6117fc478cae6809c66c518740969 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 16:01:04 -0300 Subject: [PATCH 1197/2366] smb: client: fix file type corruption in wsl_to_fattr() Setting the file type in cf_mode without clearing the existing S_IFMT bits first is wrong as it corrupts the file type when cf_mode already has type bits set (e.g. S_IFREG | S_IFCHR == S_IFLNK). Clear S_IFMT before the switch statement. Closes: https://sashiko.dev/#/patchset/20260906172005.627163-1-pc%40manguebit.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 178da801e775..8a19dee564b8 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1145,6 +1145,7 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, fattr->cf_uid = cifs_sb->ctx->linux_uid; fattr->cf_gid = cifs_sb->ctx->linux_gid; + fattr->cf_mode &= ~S_IFMT; switch (tag) { case IO_REPARSE_TAG_LX_SYMLINK: fattr->cf_mode |= S_IFLNK; From 65d5dbdc089be42fc48a6f77bc6b648307f34b17 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 16:01:16 -0300 Subject: [PATCH 1198/2366] smb: client: fix file type corruption in posix_reparse_to_fattr() Setting the file type in cf_mode without clearing the existing S_IFMT bits first is wrong as it corrupts the file type when cf_mode already has type bits set (e.g. S_IFREG | S_IFCHR == S_IFLNK). Use a local ftype variable to collect the new file type and apply it after validation succeeds, clearing S_IFMT and setting the new type in a single assignment. This avoids stripping cf_mode on malformed reparse points where the function returns false early. Closes: https://sashiko.dev/#/patchset/20260906172005.627163-1-pc%40manguebit.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 8a19dee564b8..616ca2dbfac4 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1212,6 +1212,7 @@ static bool posix_reparse_to_fattr(struct cifs_sb_info *cifs_sb, struct cifs_open_info_data *data) { struct reparse_nfs_data_buffer *buf = (struct reparse_nfs_data_buffer *)data->reparse.buf; + umode_t ftype; if (buf == NULL) return true; @@ -1227,7 +1228,7 @@ static bool posix_reparse_to_fattr(struct cifs_sb_info *cifs_sb, WARN_ON_ONCE(1); return false; } - fattr->cf_mode |= S_IFCHR; + ftype = S_IFCHR; fattr->cf_rdev = reparse_mkdev(buf->DataBuffer); break; case NFS_SPECFILE_BLK: @@ -1235,22 +1236,23 @@ static bool posix_reparse_to_fattr(struct cifs_sb_info *cifs_sb, WARN_ON_ONCE(1); return false; } - fattr->cf_mode |= S_IFBLK; + ftype = S_IFBLK; fattr->cf_rdev = reparse_mkdev(buf->DataBuffer); break; case NFS_SPECFILE_FIFO: - fattr->cf_mode |= S_IFIFO; + ftype = S_IFIFO; break; case NFS_SPECFILE_SOCK: - fattr->cf_mode |= S_IFSOCK; + ftype = S_IFSOCK; break; case NFS_SPECFILE_LNK: - fattr->cf_mode |= S_IFLNK; + ftype = S_IFLNK; break; default: WARN_ON_ONCE(1); return false; } + fattr->cf_mode = (fattr->cf_mode & ~S_IFMT) | ftype; return true; } From 6bd360447941357e959414a525aa62576a448116 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 6 Sep 2026 16:01:24 -0300 Subject: [PATCH 1199/2366] smb: client: fix file type corruption in cifs_reparse_point_to_fattr() Setting the file type in cf_mode without clearing the existing S_IFMT bits first is wrong as it corrupts the file type when cf_mode already has type bits set (e.g. S_IFREG | S_IFLNK == S_IFDIR | S_IFREG). Clear S_IFMT before setting S_IFLNK for native and SMB1 symlinks. Closes: https://sashiko.dev/#/patchset/20260906181540.647469-1-pc%40manguebit.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 616ca2dbfac4..b6bded042e78 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1280,6 +1280,7 @@ bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb, break; case 0: /* SMB1 symlink */ case IO_REPARSE_TAG_SYMLINK: + fattr->cf_mode &= ~S_IFMT; fattr->cf_mode |= S_IFLNK; break; default: From 7e125889f1705fc6326679a3db3b4159f7a8c87e Mon Sep 17 00:00:00 2001 From: Yorick Rommers Date: Mon, 7 Sep 2026 14:12:28 +0200 Subject: [PATCH 1200/2366] ASoC: amd: acp-da7219-max98357a: don't bind on Raven/Picasso boards The "AMDI5682" ACPI HID is matched by two AMD ASoC machine drivers: cz-da7219-max98357a (this driver, Carrizo/Stoney) and acp3x-alc5682-max98357 (Raven/Picasso). cz-da7219-max98357a is linked first and probes the platform device first; its DAI links reference the Stoney ACP, which is absent on Raven/Picasso, so its card can never be instantiated there. This was harmless until commit 42d99857d6f0 ("ASoC: core: Move all users to deferrable card binding"): devm_snd_soc_register_card() now returns 0 for a card left pending instead of propagating -EPROBE_DEFER, so cz_probe() succeeds and permanently binds AMDI5682. acp3x-alc5682-max98357 never binds and the internal speakers and headphone jack get no card. Detect Raven/Picasso (and later) by the ACP3.x audio coprocessor's dedicated PCI function (1022:15e2); Carrizo/Stoney reach the ACP through the GPU driver and have no such device. Return -ENODEV so the driver core continues probing AMDI5682 with acp3x-alc5682-max98357. Fixes: 42d99857d6f0 ("ASoC: core: Move all users to deferrable card binding") Cc: stable@vger.kernel.org Signed-off-by: Yorick Rommers Tested-by: Yorick Rommers Link: https://patch.msgid.link/20260907121228.13754-1-yorick-rommers@hotmail.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-da7219-max98357a.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c index af559653e625..1ac729a58bb4 100644 --- a/sound/soc/amd/acp-da7219-max98357a.c +++ b/sound/soc/amd/acp-da7219-max98357a.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "acp.h" #include "../codecs/da7219.h" @@ -742,6 +743,18 @@ static const struct regulator_desc acp_da7219_desc = { .n_voltages = 1, }; +/* + * The ACP3.x+ (Raven/Picasso and later) audio coprocessor is a dedicated PCI + * function. Carrizo/Stoney - the only platforms handled by this driver - reach + * the ACP through the GPU driver and have no such device. + */ +#define ACP3X_PCI_DEV_ID 0x15e2 + +static const struct pci_device_id acp3x_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP3X_PCI_DEV_ID) }, + { 0, }, +}; + static int cz_probe(struct platform_device *pdev) { int ret; @@ -750,6 +763,16 @@ static int cz_probe(struct platform_device *pdev) struct regulator_dev *rdev; struct device *dev = &pdev->dev; + /* + * AMDI5682 is also matched by acp3x-alc5682-max98357 (Raven/Picasso). + * If the ACP3.x PCI function is present this is such a board; return + * -ENODEV so that driver binds instead. + */ + if (pci_dev_present(acp3x_pci_ids)) { + dev_info(dev, "ACP3.x PCI device present, deferring to acp3x-alc5682-max98357\n"); + return -ENODEV; + } + card = (struct snd_soc_card *)acp_soc_is_rltk_max(dev); if (!card) return -ENODEV; From f77de4c33f0edbb33411f92a35d7196965597e6d Mon Sep 17 00:00:00 2001 From: Jia Wang Date: Mon, 7 Sep 2026 17:54:44 +0800 Subject: [PATCH 1201/2366] spi: dt-bindings: snps,dw-apb-ssi: Add compatible for UltraRISC DP1000 SoC Add the SoC-specific compatible string and use the generic one as fallback for the UltraRISC DP1000 SPI controller. The DP1000 integrates two SPI controllers. SPI0 supports standard, dual and quad transfers with three native chip-select signals. SPI1 supports standard transfers with four native chip-select signals. Both controllers have one register range and separate reference and APB interface clocks. Signed-off-by: Jia Wang Acked-by: Conor Dooley Link: https://patch.msgid.link/20260907-ultrarisc-dts-v2-5-5eb4c97477c5@ultrarisc.com Signed-off-by: Mark Brown --- .../devicetree/bindings/spi/snps,dw-apb-ssi.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml index 1e70c6804d5d..e8d6d5d858b6 100644 --- a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml +++ b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml @@ -44,6 +44,21 @@ allOf: else: properties: starfive,sfc-filter-syscon: false + - if: + properties: + compatible: + contains: + const: ultrarisc,dp1000-spi + then: + properties: + reg: + maxItems: 1 + clocks: + minItems: 2 + clock-names: + minItems: 2 + required: + - clock-names properties: compatible: @@ -63,6 +78,7 @@ properties: - mscc,jaguar2-spi - sophgo,sg2042-spi - thead,th1520-spi + - ultrarisc,dp1000-spi - const: snps,dw-apb-ssi - description: Vendor controllers compatible with v1.01a items: From 159720704d9d652b64390c11fb971e15b0a78d23 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Tue, 8 Sep 2026 14:47:29 +0530 Subject: [PATCH 1202/2366] drm/drm_exec: fix up contended obj when num_objects is 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_exec_prepare_array() silently returns success without calling drm_exec_lock_contended() when num_objects is zero. This breaks the invariant upheld by drm_exec_lock_obj(), where every entry point into the locking sequence must first attempt to lock any previously contended object before proceeding. Drivers that chain multiple drm_exec_prepare_array() calls per drm_exec_until_all_locked() iteration (e.g. amdgpu's userq signal/wait ioctls, which prepare separate read and write BO arrays) can pass an empty array for one of the two calls. If contention is hit while preparing the non-empty array, exec->contended is set and the loop retries; on retry, the empty-array call preceding it is a no-op that never clears exec->contended, so drm_exec_retry_on_contention() immediately jumps back to the top of the loop without ever reaching the call that would resolve the contention. This spins forever. Fix it by having drm_exec_prepare_array() call drm_exec_lock_contended() directly when num_objects is zero, so a pending contended object dont loop infinitely. Fixes: 09593216bff1 ("drm: execution context for GEM buffers v7") CC: stable@vger.kernel.org # v6.6+ Signed-off-by: Sunil Khatri Link: https://lore.kernel.org/r/20260908091729.2749399-1-sunil.khatri@amd.com Reviewed-by: Christian König Signed-off-by: Christian König --- drivers/gpu/drm/drm_exec.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/drm_exec.c b/drivers/gpu/drm/drm_exec.c index 41034a5996ff..2453ec41360f 100644 --- a/drivers/gpu/drm/drm_exec.c +++ b/drivers/gpu/drm/drm_exec.c @@ -322,6 +322,19 @@ int drm_exec_prepare_array(struct drm_exec *exec, { int ret; + /* + * Make sure to lock a contended object even when no objects are + * given, otherwise drm_exec_retry_on_contention() would loop + * forever on patterns like: + * + * ret = drm_exec_prepare_array(exec, objs, num_objects, ...); + * drm_exec_retry_on_contention(exec); + * + * with num_objects == 0. + */ + if (!num_objects) + return drm_exec_lock_contended(exec); + for (unsigned int i = 0; i < num_objects; ++i) { ret = drm_exec_prepare_obj(exec, objects[i], num_fences); if (unlikely(ret)) From 8b0cc8707f65e0f51912e764e1b309b2559db1ec Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Tue, 18 Aug 2026 19:31:53 +0800 Subject: [PATCH 1203/2366] mmc: spi: reset bytes_xfered before retrying CRC failures mmc_spi_data_do() updates data->bytes_xfered after each block has been transferred successfully. If a later block in the same data request fails with a CRC error, data->bytes_xfered may therefore contain the number of bytes completed before the failing block. mmc_spi_request() has a private recovery path for such CRC failures. It sends STOP_TRANSMISSION, clears data->error and jumps back to crc_recover to issue the same command and data request again. However, it does not clear data->bytes_xfered before the retry. If the retry succeeds, the request is completed with the bytes from the failed attempt still included in data->bytes_xfered. For a multi-block request this can make the completed request report more bytes than were transferred by the successful retry, and can even exceed the request size when most blocks completed before the CRC error. This is most likely to be observed on MMC-over-SPI systems where long multi-block transfers occasionally hit a data CRC error but the mmc_spi-internal retry succeeds. The data itself is retried, but the completion accounting is not. Clear data->bytes_xfered together with data->error before repeating the request so the final completion reports only the bytes transferred by the successful attempt. Fixes: 061c6c847eeb ("mmc_spi: Recover from CRC errors for r/w operation over SPI.") Cc: stable@vger.kernel.org Signed-off-by: Xu Rao Signed-off-by: Ulf Hansson --- drivers/mmc/host/mmc_spi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/mmc_spi.c b/drivers/mmc/host/mmc_spi.c index b471a7795b4d..5217d713c826 100644 --- a/drivers/mmc/host/mmc_spi.c +++ b/drivers/mmc/host/mmc_spi.c @@ -952,6 +952,7 @@ static void mmc_spi_request(struct mmc_host *mmc, struct mmc_request *mrq) status = mmc_spi_command_send(host, mrq, &stop, 0); crc_retry--; mrq->data->error = 0; + mrq->data->bytes_xfered = 0; goto crc_recover; } From dd519eb8f66eaa205bbbdcb753588138a1d18414 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 8 Sep 2026 20:55:17 +0200 Subject: [PATCH 1204/2366] platform/x86: x86-android-tablets: fix gpio_secondary_fwnode_init() not working MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit acpi_bus_find_device_by_name() call returns a pointer to the device object on the ACPI bus, aka the ACPI companion device. gpio_secondary_fwnode_init() then continues with setting the secondary fwnode on this device. But this is not the actual physical device for the GPIO controller (e.g. the GPIO controller platform bus device). This mismatch is causing GPIO lookups by secondary fwnode to not work. Modify gpio_secondary_fwnode_init() to instead set the secondary fwnode of the first physical device associated with the ACPI companion device. This fixes the GPIO lookups not working. Fixes: 1448c2d2ca5c ("platform/x86: x86-android-tablets: enable fwnode matching of GPIO chips") Reviewed-by: Dmitry Torokhov Signed-off-by: Hans de Goede Link: https://patch.msgid.link/20260908185517.49047-1-johannes.goede@oss.qualcomm.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/x86-android-tablets/core.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index b028af1c9942..cfff7f5eac5d 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -390,6 +390,7 @@ static int gpio_secondary_fwnode_init(struct device *parent, { const struct software_node *const *swnode; struct fwnode_handle *fwnode; + struct device *phys_dev; int ret; if (!node_group) @@ -417,9 +418,15 @@ static int gpio_secondary_fwnode_init(struct device *parent, if (WARN_ON(!fwnode)) return -ENOENT; - set_secondary_fwnode(dev, fwnode); + phys_dev = acpi_get_first_physical_node(to_acpi_device(dev)); + if (!phys_dev) + return dev_err_probe(parent, -ENODEV, + "No physical device for ACPI GPIO dev: %pfwP\n", + fwnode); - ret = devm_add_action_or_reset(parent, gpio_secondary_unset, get_device(dev)); + set_secondary_fwnode(phys_dev, fwnode); + + ret = devm_add_action_or_reset(parent, gpio_secondary_unset, get_device(phys_dev)); if (ret) return ret; } From 7dd4c829bac2916be98a3e34b41daaba7f42b4c4 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 20 Jul 2026 22:58:46 +0900 Subject: [PATCH 1205/2366] idpf: disable DIM work before freeing q_vectors idpf never drains the Tx/Rx DIM works before freeing the memory they live in. tx_dim and rx_dim are embedded in struct idpf_q_vector, they are queued from the NAPI poll via net_dim(), and idpf_vport_intr_rel() ends with kfree(rsrc->q_vectors). Nothing in the driver cancels them. idpf_tx_dim_work() and idpf_rx_dim_work() then run on freed memory: idpf_vport_intr_write_itr() writes the ITR register through q_vector->intr_reg.tx_itr / rx_itr, void __iomem pointers loaded out of the freed q_vector. No configuration is needed to get there -- IDPF_ITR_IS_DYNAMIC() is defined as (itr_mode) and idpf_vport_alloc() initialises both modes to IDPF_ITR_DYNAMIC. Draining after idpf_vport_intr_napi_dis_all() is not enough on its own. idpf_net_dim() is called from inside the "if (napi_complete_done(napi, work_done))" branch of the poll, and napi_complete_done() has already cleared NAPIF_STATE_SCHED by then. napi_disable_locked() waits only while (val & (NAPIF_STATE_SCHED | NAPIF_STATE_NPSVC)), so napi_disable() can return while the poll tail is still queueing the work, and a plain cancel_work_sync() would be re-armed behind the drain. Use disable_work_sync(): schedule_work() on a work with a non-zero disable count is dropped by clear_pending_if_disabled() before __queue_work() is reached. Move idpf_init_dim() to idpf_vport_intr_alloc() so the works are initialised on every path that can reach the drain -- the three "goto intr_deinit" sites between idpf_vport_intr_init() and idpf_vport_intr_ena() get there without the enable side having run. Nothing re-enables them: rsrc->q_vectors is freed on every exit from idpf_vport_open() and on every idpf_vport_stop(), so the count dies with the object. It is a race, not a deterministic failure -- net_dim() only schedules once DIM_NEVENTS events have accumulated and the profile index changes. A KASAN ifup/ifdown loop under load is the way to see it. Fixes: c2d548cad150 ("idpf: add TX splitq napi poll support") Fixes: 3a8845af66ed ("idpf: add RX splitq napi poll support") Cc: # see patch description, needs adjustments for <= 6.9 Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Tested-by: Samuel Salin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 24 ++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 24b91be25676..9ba9c2952d78 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -4145,6 +4145,26 @@ static void idpf_vport_intr_ena_irq_all(struct idpf_vport *vport, writel(rsrc->noirq_dyn_ctl_ena, rsrc->noirq_dyn_ctl); } +/** + * idpf_vport_intr_dis_dim_all - Disable DIM work for all q_vectors + * @rsrc: pointer to queue and vector resources + * + * The DIM works are embedded in the q_vector array that + * idpf_vport_intr_rel() frees, and the poll arms them after + * napi_complete_done() has already cleared NAPI_STATE_SCHED. Disable + * rather than just cancel, so that a poll tail still running past + * napi_disable() cannot queue them again behind the drain. + */ +static void idpf_vport_intr_dis_dim_all(struct idpf_q_vec_rsrc *rsrc) +{ + for (u16 v_idx = 0; v_idx < rsrc->num_q_vectors; v_idx++) { + struct idpf_q_vector *q_vector = &rsrc->q_vectors[v_idx]; + + disable_work_sync(&q_vector->tx_dim.work); + disable_work_sync(&q_vector->rx_dim.work); + } +} + /** * idpf_vport_intr_deinit - Release all vector associations for the vport * @vport: main vport structure @@ -4155,6 +4175,7 @@ void idpf_vport_intr_deinit(struct idpf_vport *vport, { idpf_vport_intr_dis_irq_all(rsrc); idpf_vport_intr_napi_dis_all(rsrc); + idpf_vport_intr_dis_dim_all(rsrc); idpf_vport_intr_napi_del_all(rsrc); idpf_vport_intr_rel_irq(vport, rsrc); } @@ -4235,7 +4256,6 @@ static void idpf_vport_intr_napi_ena_all(struct idpf_q_vec_rsrc *rsrc) for (u16 q_idx = 0; q_idx < rsrc->num_q_vectors; q_idx++) { struct idpf_q_vector *q_vector = &rsrc->q_vectors[q_idx]; - idpf_init_dim(q_vector); napi_enable(&q_vector->napi); } } @@ -4578,6 +4598,8 @@ int idpf_vport_intr_alloc(struct idpf_vport *vport, q_coal = &user_config->q_coalesce[v_idx]; q_vector->vport = vport; + idpf_init_dim(q_vector); + q_vector->tx_itr_value = q_coal->tx_coalesce_usecs; q_vector->tx_intr_mode = q_coal->tx_intr_mode; q_vector->tx_itr_idx = VIRTCHNL2_ITR_IDX_1; From 650f197d8ea6ebbc9ce1c9fb0358258b65e74291 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 20 Jul 2026 23:35:10 +0900 Subject: [PATCH 1206/2366] idpf: disable PTM on probe failure and on remove idpf_probe() enables PCIe Precision Time Measurement with pci_enable_ptm(), which takes a reference on the device and on every PTM-capable device up the path to the PTM Root. Neither the probe error path nor idpf_remove() drops that reference, so the PTM enable counts of this device and of its upstream path stay elevated with no bound driver, and the device's PTM control bits remain set. pcim_enable_device() only arranges for pci_disable_device() and does not undo the PTM enable. Add the matching pci_disable_ptm() to the common unwind path. pci_enable_ptm() failure is not fatal here, so guard the call with pcie_ptm_enabled(): pci_disable_ptm() decrements dev->ptm_enable_cnt unconditionally and then recurses upstream, so calling it after a failed enable would drive this device's count negative and wrongly decrement parents shared with other endpoints. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 8d5e12c5921c ("idpf: add initial PTP support") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Aleksandr Loktionov Tested-by: Samuel Salin [TN moved call due to commit 6b284aa2ddf3 ("idpf: refactor idpf to use libie_pci APIs")] Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/ethernet/intel/idpf/idpf_main.c b/drivers/net/ethernet/intel/idpf/idpf_main.c index 9840580fbe51..129bccaa6baa 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_main.c +++ b/drivers/net/ethernet/intel/idpf/idpf_main.c @@ -106,6 +106,11 @@ static int idpf_dev_init(struct idpf_adapter *adapter, */ static void idpf_decfg_device(struct idpf_adapter *adapter) { + struct pci_dev *pdev = adapter->pdev; + + if (pcie_ptm_enabled(pdev)) + pci_disable_ptm(pdev); + libie_pci_unmap_all_mmio_regions(&adapter->ctlq_ctx.mmio_info); } From cc6d60ef92278a31ffc2e94966a0921b9646af18 Mon Sep 17 00:00:00 2001 From: Joshua Hay Date: Mon, 27 Jul 2026 16:08:48 -0700 Subject: [PATCH 1207/2366] idpf: account for VLAN header when parsing RSC packet header While parsing the header of a Receive Side Coalesced (RSC) packet, check if a VLAN tag is present and adjust the header parsing accordingly. Otherwise, Rx TCP traffic is completely broken for any VLAN interface whose underlying interface has RSC (rx-gro-hw) enabled. We only need to worry about one VLAN header since Rx packets with multiple VLAN headers are not candidates for RSC. Fixes: 3a8845af66edb ("idpf: add RX splitq napi poll support") Signed-off-by: Joshua Hay Reviewed-by: Emil Tantilov Reviewed-by: Aleksandr Loktionov Tested-by: Samuel Salin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 9ba9c2952d78..4311ffa30bb1 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -3299,6 +3299,7 @@ static int idpf_rx_rsc(struct idpf_rx_queue *rxq, struct sk_buff *skb, struct libeth_rx_pt decoded) { u16 rsc_segments, rsc_seg_len; + u16 l3_start = 0; bool ipv4, ipv6; int len; @@ -3321,7 +3322,10 @@ static int idpf_rx_rsc(struct idpf_rx_queue *rxq, struct sk_buff *skb, NAPI_GRO_CB(skb)->count = rsc_segments; skb_shinfo(skb)->gso_size = rsc_seg_len; - skb_reset_network_header(skb); + if (unlikely(eth_type_vlan(skb->protocol))) + l3_start = VLAN_HLEN; + + skb_set_network_header(skb, l3_start); if (ipv4) { struct iphdr *ipv4h = ip_hdr(skb); @@ -3329,7 +3333,7 @@ static int idpf_rx_rsc(struct idpf_rx_queue *rxq, struct sk_buff *skb, skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4; /* Reset and set transport header offset in skb */ - skb_set_transport_header(skb, sizeof(struct iphdr)); + skb_set_transport_header(skb, l3_start + sizeof(struct iphdr)); len = skb->len - skb_transport_offset(skb); /* Compute the TCP pseudo header checksum*/ @@ -3339,7 +3343,7 @@ static int idpf_rx_rsc(struct idpf_rx_queue *rxq, struct sk_buff *skb, struct ipv6hdr *ipv6h = ipv6_hdr(skb); skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6; - skb_set_transport_header(skb, sizeof(struct ipv6hdr)); + skb_set_transport_header(skb, l3_start + sizeof(struct ipv6hdr)); len = skb->len - skb_transport_offset(skb); tcp_hdr(skb)->check = ~tcp_v6_check(len, &ipv6h->saddr, &ipv6h->daddr, 0); From 53432c4c3e869076350aef319534431af8ba99c1 Mon Sep 17 00:00:00 2001 From: Jacob Keller Date: Mon, 6 Jul 2026 16:31:17 -0700 Subject: [PATCH 1208/2366] ice: add missing xa_destroy for sched_node_ids Commit 16dfa49406bc ("ice: Introduce new parameters in ice_sched_node") added a sched_node_ids xarray to the port info structure, but never called xa_destroy on it. Since xarrays can allocate internal memory, this can result in a memory leak even if every element in the xarray has been removed. The xarray is currently embedded in the port_info structure. This appears to have been done because its use is within functions that take the port_info as a primary argument. However, this complicates managing the lifecycle of the field. The port_info structure is allocated in ice_init_hw() using devm, and it is not released until the devm cleanup when the driver is unloaded. The ice_init_hw() function is called in many places, including devlink reload, and possibly during DDP load after updating the Tx scheduler layout. Adding a call of xa_destroy to the ice_deinit_hw() causes Sashiko to raise multiple concerns due to potential ordering issues and possible ways that port_info could be a dangling reference. To handle this, move the sched_node_ids out of port_info and into the hw structure. All users of the array already have a pointer to hw anyways, and there is only one sched_node_ids per adapter. While here, remove the overly verbose comment explaining the nature of the sched_node_ids xarray. Add the missing xa_destroy to the cleanup path and to ice_deinit_hw(), ensuring that we properly release the xarray memory. This was caught by Sashiko during development of unrelated code. Fixes: 16dfa49406bc ("ice: Introduce new parameters in ice_sched_node") Signed-off-by: Jacob Keller Reviewed-by: Aleksandr Loktionov Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_common.c | 9 ++++++--- drivers/net/ethernet/intel/ice/ice_sched.c | 4 ++-- drivers/net/ethernet/intel/ice/ice_type.h | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_common.c b/drivers/net/ethernet/intel/ice/ice_common.c index ef1ce106f81b..04633103e3e6 100644 --- a/drivers/net/ethernet/intel/ice/ice_common.c +++ b/drivers/net/ethernet/intel/ice/ice_common.c @@ -1051,14 +1051,13 @@ int ice_init_hw(struct ice_hw *hw) hw->evb_veb = true; - /* init xarray for identifying scheduling nodes uniquely */ - xa_init_flags(&hw->port_info->sched_node_ids, XA_FLAGS_ALLOC); + xa_init_flags(&hw->sched_node_ids, XA_FLAGS_ALLOC); /* Query the allocated resources for Tx scheduler */ status = ice_sched_query_res_alloc(hw); if (status) { ice_debug(hw, ICE_DBG_SCHED, "Failed to get scheduler allocated resources\n"); - goto err_unroll_alloc; + goto err_unroll_xarray; } ice_sched_get_psm_clk_freq(hw); @@ -1146,6 +1145,8 @@ int ice_init_hw(struct ice_hw *hw) ice_cleanup_fltr_mgmt_struct(hw); err_unroll_sched: ice_sched_cleanup_all(hw); +err_unroll_xarray: + xa_destroy(&hw->sched_node_ids); err_unroll_alloc: devm_kfree(ice_hw_to_dev(hw), hw->port_info); err_unroll_cqinit: @@ -1186,6 +1187,8 @@ void ice_deinit_hw(struct ice_hw *hw) /* Clear VSI contexts if not already cleared */ ice_clear_all_vsi_ctx(hw); + + xa_destroy(&hw->sched_node_ids); } /** diff --git a/drivers/net/ethernet/intel/ice/ice_sched.c b/drivers/net/ethernet/intel/ice/ice_sched.c index fff0c1afdb41..ffa18d86729a 100644 --- a/drivers/net/ethernet/intel/ice/ice_sched.c +++ b/drivers/net/ethernet/intel/ice/ice_sched.c @@ -371,7 +371,7 @@ void ice_free_sched_node(struct ice_port_info *pi, struct ice_sched_node *node) devm_kfree(ice_hw_to_dev(hw), node->children); kfree(node->name); - xa_erase(&pi->sched_node_ids, node->id); + xa_erase(&hw->sched_node_ids, node->id); devm_kfree(ice_hw_to_dev(hw), node); } @@ -977,7 +977,7 @@ ice_sched_add_elems(struct ice_port_info *pi, struct ice_sched_node *tc_node, if (!new_node->name) return -ENOMEM; - status = xa_alloc(&pi->sched_node_ids, &new_node->id, NULL, XA_LIMIT(0, UINT_MAX), + status = xa_alloc(&hw->sched_node_ids, &new_node->id, NULL, XA_LIMIT(0, UINT_MAX), GFP_KERNEL); if (status) { ice_debug(hw, ICE_DBG_SCHED, "xa_alloc failed for sched node status =%d\n", diff --git a/drivers/net/ethernet/intel/ice/ice_type.h b/drivers/net/ethernet/intel/ice/ice_type.h index d9a5c1aae7c2..cf147a212707 100644 --- a/drivers/net/ethernet/intel/ice/ice_type.h +++ b/drivers/net/ethernet/intel/ice/ice_type.h @@ -765,7 +765,6 @@ struct ice_port_info { /* List contain profile ID(s) and other params per layer */ struct list_head rl_prof_list[ICE_AQC_TOPO_MAX_LEVEL_NUM]; struct ice_qos_cfg qos_cfg; - struct xarray sched_node_ids; u8 is_vf:1; u8 is_custom_tx_enabled:1; }; @@ -930,6 +929,7 @@ struct ice_hw { u8 sw_entry_point_layer; u16 max_children[ICE_AQC_TOPO_MAX_LEVEL_NUM]; struct list_head agg_list; /* lists all aggregator */ + struct xarray sched_node_ids; struct ice_vsi_ctx *vsi_ctx[ICE_MAX_VSI]; u8 evb_veb; /* true for VEB, false for VEPA */ From b8bf9bfda5f62e11444e483c2b4aaff90c5cfc6b Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 20 Aug 2026 19:02:01 -0700 Subject: [PATCH 1209/2366] eth: ice: don't dereference pointers from TP_printk() After forwarding net-next during the v7.3 merge window we started seeing: TRACE EVENT ERROR: Event ice_tx_dim_work has double dereference in TP_printk: REC->q_vector->tx.tx_ring->q_index WARNING: kernel/trace/trace_events.c:420 at test_double_dereference.cold+0x39/0x4b this is due to extra checks added in tracing subsystem in commit b5cc230af5e5 ("tracing: Warn when an event dereferences a pointer in TP_printk()"). Printing happens long after the event was recorded, by which point the pointers may be invalid (the ring or the dim instance). Copy the eight scalars into the event instead. Fixes: 3089cf6d3caa ("ice: add tracepoints") Signed-off-by: Jakub Kicinski Tested-by: Alexander Nowlin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_trace.h | 64 ++++++++++++++-------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_trace.h b/drivers/net/ethernet/intel/ice/ice_trace.h index 4f35ef8d6b29..7568c917cdbe 100644 --- a/drivers/net/ethernet/intel/ice/ice_trace.h +++ b/drivers/net/ethernet/intel/ice/ice_trace.h @@ -63,23 +63,33 @@ DECLARE_EVENT_CLASS(ice_rx_dim_template, TP_PROTO(struct ice_q_vector *q_vector, struct dim *dim), TP_ARGS(q_vector, dim), - TP_STRUCT__entry(__field(struct ice_q_vector *, q_vector) - __field(struct dim *, dim) + TP_STRUCT__entry(__field(u16, q_index) + __field(u8, state) + __field(u8, profile_ix) + __field(u8, tune_state) + __field(u8, steps_right) + __field(u8, steps_left) + __field(u8, tired) __string(devname, q_vector->rx.rx_ring->netdev->name)), - TP_fast_assign(__entry->q_vector = q_vector; - __entry->dim = dim; + TP_fast_assign(__entry->q_index = q_vector->rx.rx_ring->q_index; + __entry->state = dim->state; + __entry->profile_ix = dim->profile_ix; + __entry->tune_state = dim->tune_state; + __entry->steps_right = dim->steps_right; + __entry->steps_left = dim->steps_left; + __entry->tired = dim->tired; __assign_str(devname);), TP_printk("netdev: %s Rx-Q: %d dim-state: %d dim-profile: %d dim-tune: %d dim-st-right: %d dim-st-left: %d dim-tired: %d", __get_str(devname), - __entry->q_vector->rx.rx_ring->q_index, - __entry->dim->state, - __entry->dim->profile_ix, - __entry->dim->tune_state, - __entry->dim->steps_right, - __entry->dim->steps_left, - __entry->dim->tired) + __entry->q_index, + __entry->state, + __entry->profile_ix, + __entry->tune_state, + __entry->steps_right, + __entry->steps_left, + __entry->tired) ); DEFINE_EVENT(ice_rx_dim_template, ice_rx_dim_work, @@ -90,23 +100,33 @@ DEFINE_EVENT(ice_rx_dim_template, ice_rx_dim_work, DECLARE_EVENT_CLASS(ice_tx_dim_template, TP_PROTO(struct ice_q_vector *q_vector, struct dim *dim), TP_ARGS(q_vector, dim), - TP_STRUCT__entry(__field(struct ice_q_vector *, q_vector) - __field(struct dim *, dim) + TP_STRUCT__entry(__field(u16, q_index) + __field(u8, state) + __field(u8, profile_ix) + __field(u8, tune_state) + __field(u8, steps_right) + __field(u8, steps_left) + __field(u8, tired) __string(devname, q_vector->tx.tx_ring->netdev->name)), - TP_fast_assign(__entry->q_vector = q_vector; - __entry->dim = dim; + TP_fast_assign(__entry->q_index = q_vector->tx.tx_ring->q_index; + __entry->state = dim->state; + __entry->profile_ix = dim->profile_ix; + __entry->tune_state = dim->tune_state; + __entry->steps_right = dim->steps_right; + __entry->steps_left = dim->steps_left; + __entry->tired = dim->tired; __assign_str(devname);), TP_printk("netdev: %s Tx-Q: %d dim-state: %d dim-profile: %d dim-tune: %d dim-st-right: %d dim-st-left: %d dim-tired: %d", __get_str(devname), - __entry->q_vector->tx.tx_ring->q_index, - __entry->dim->state, - __entry->dim->profile_ix, - __entry->dim->tune_state, - __entry->dim->steps_right, - __entry->dim->steps_left, - __entry->dim->tired) + __entry->q_index, + __entry->state, + __entry->profile_ix, + __entry->tune_state, + __entry->steps_right, + __entry->steps_left, + __entry->tired) ); DEFINE_EVENT(ice_tx_dim_template, ice_tx_dim_work, From dcaf83ead130d3067862599089b0999b3da140a4 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 2 Sep 2026 02:19:18 +0800 Subject: [PATCH 1210/2366] Bluetooth: Properly disable remote wakeup for MT7922/MT7925 on Ryzen platform It is reported that a remote wakeup could cause MT7922/MT7925's btusb interface completely unresponsive. Resetting the xHCI root hub doesn't help at all, and recovering from such a state needs a power cycle. All reports seen to be relevant to Ryzen-based laptops. These NICs are usually used as OEM components thanks to some sort of reference designs. Their popularity on other platforms is unclear. While there is still a chance that the quirk may exist on other platforms, be cautious and only apply the quirk to direct children of Ryzen platforms's root hubs for the time being. In most cases the root hub is on the SoC or PCH, which needs the quirk. Unfortunately, this can't distinguish root hubs on PCIe add-in cards. Such roughness should be acceptable, as PCIe USB controller add-in cards are less commonly used nowadays. On the other hand, applying the quirk doesn't hurt any functionalities either, as the device can still be used as a wakeup source if desired. Theoretically, we could retrieve the root hub's PCI vendor ID with some hierarchy magic, but that's too intrusive... Meanwhile, though device_set_wakeup_capable(false) is the correct fix for other NICs with fake remote wakeup capabilities, doing so for MT7922/MT7925 effectively prevents it from being used as wakeup sources as per userspace requests. Hence, return -EBUSY on runtime suspend to prevent the interface from being autosuspended while it's still opened, which has the same effect as device_set_wakeup_capable(false), since disabling remote wakeup simply causes the USB core to gate runtime autosuspend as well due to needs_remote_wakeup == 1. The interface can be safely autosuspended as long as remote wakeup is disabled, i.e., after closing the HCI device. Specifically, the interface may still take the advantage of remote wakeup in order to wake up the system from sleep if userspace has enabled it as a wakeup source. Fixes: e31d761628ad ("Bluetooth: btmtk: Disable remote wakeup for MT7922/MT7925") Tested-by: Rafael Passos Signed-off-by: Rong Zhang Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 10 ------ drivers/bluetooth/btusb.c | 73 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index c0ed51567ed4..9589caff925d 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -1374,16 +1374,6 @@ int btmtk_usb_setup(struct hci_dev *hdev) break; case 0x7922: case 0x7925: - /* - * A remote wakeup could cause the device completely unresponsive, and - * recovering from such a state needs a power cycle. - * - * Since the remote wakeup capability is super broken, just disable it - * to get rid of the troubles. The device can still be autosuspended - * when the bluetooth interface is closed. - */ - device_set_wakeup_capable(&btmtk_data->udev->dev, false); - fallthrough; case 0x7961: case 0x7902: case 0x6639: diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index d70a3e7a13f5..95f4640c60e4 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -6,6 +6,7 @@ * Copyright (C) 2005-2008 Marcel Holtmann */ +#include #include #include #include @@ -980,6 +981,7 @@ struct btqca_data { #define BTUSB_USE_ALT3_FOR_WBS 15 #define BTUSB_ALT6_CONTINUOUS_TX 16 #define BTUSB_HW_SSR_ACTIVE 17 +#define BTUSB_WAKEUP_BROKEN 18 struct btusb_data { struct hci_dev *hdev; @@ -2969,10 +2971,25 @@ static int btusb_send_frame_mtk(struct hci_dev *hdev, struct sk_buff *skb) } } +static inline bool platform_is_ryzen(void) +{ +#ifdef CONFIG_X86 + return boot_cpu_has(X86_FEATURE_ZEN); +#else + return false; +#endif +} + +static inline bool is_direct_child_of_root_hub(struct usb_device *udev) +{ + return udev->parent == udev->bus->root_hub; +} + static int btusb_mtk_setup(struct hci_dev *hdev) { struct btusb_data *data = hci_get_drvdata(hdev); struct btmtk_data *btmtk_data = hci_get_priv(hdev); + int err; /* MediaTek WMT vendor cmd requiring below USB resources to * complete the handshake. @@ -2989,7 +3006,40 @@ static int btusb_mtk_setup(struct hci_dev *hdev) btusb_mtk_claim_iso_intf(data); } - return btmtk_usb_setup(hdev); + err = btmtk_usb_setup(hdev); + if (err) + return err; + + switch (btmtk_data->dev_id) { + case 0x7922: + case 0x7925: + /* + * All reports seen to be relevant to Ryzen-based laptops. These + * NICs are usually used as OEM components thanks to some sort + * of reference designs. + * + * Their popularity on other platforms is unclear. While there + * is still a chance that the quirk may exist on other + * platforms, be cautious and only apply the quirk to direct + * children of Ryzen platforms's root hubs for the time being. + * + * In most cases the root hub is on the SoC or PCH, which needs + * the quirk. Unfortunately, this can't distinguish root hubs on + * PCIe add-in cards. Such roughness should be acceptable, as + * PCIe USB controller add-in cards are less commonly used + * nowadays. On the other hand, applying the quirk doesn't hurt + * any functionalities either, as the device can still be used + * as a wakeup source if desired. + * + * Theoretically, we could retrieve the root hub's PCI vendor ID + * with some hierarchy magic, but that's too intrusive... + */ + if (platform_is_ryzen() && is_direct_child_of_root_hub(data->udev)) + set_bit(BTUSB_WAKEUP_BROKEN, &data->flags); + break; + } + + return 0; } static int btusb_mtk_shutdown(struct hci_dev *hdev) @@ -4565,11 +4615,26 @@ static int btusb_suspend(struct usb_interface *intf, pm_message_t message) BT_DBG("intf %p", intf); - /* Don't auto-suspend if there are connections or discovery in - * progress; external suspend calls shall never fail. + /* + * It is reported that remote wakeup events could sometimes cause some + * adapters completely unresponsive. Resetting the xHCI root hub doesn't + * help at all, and recovering from such a state needs a power cycle. + * Since disabling remote wakeup simply causes the USB core to gate + * runtime autosuspend as well due to needs_remote_wakeup == 1, let's do + * this ourselves to make our life easier. The interface can be safely + * autosuspended as long as remote wakeup is disabled, i.e., after + * closing the HCI device. + * + * Don't auto-suspend if there are connections or discovery in progress. + * + * External suspend calls shall never fail. Specifically, a device with + * broken remote wakeup may still take the advantage of remote wakeup in + * order to wake up the system from sleep if userspace has enabled it as + * a wakeup source. */ if (PMSG_IS_AUTO(message) && - (hci_conn_count(data->hdev) || hci_discovery_active(data->hdev))) + ((test_bit(BTUSB_WAKEUP_BROKEN, &data->flags) && data->intf->needs_remote_wakeup) || + hci_conn_count(data->hdev) || hci_discovery_active(data->hdev))) return -EBUSY; if (data->suspend_count++) From 1c12c3117639e78940959d956519c758c57d0849 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Mon, 31 Aug 2026 12:13:10 -0400 Subject: [PATCH 1211/2366] Bluetooth: btusb: Fix UAF of btusb_data by rx_work btusb_close() and btusb_flush() cancel data->rx_work with the asynchronous cancel_delayed_work(), so if btusb_rx_work() is already running on another CPU it keeps running after the cancel returns. btusb_disconnect() calls hci_unregister_dev(), which invokes btusb_close(), and then frees the btusb_data. A still running btusb_rx_work() then dereferences the freed data: while ((skb = skb_dequeue(&data->acl_q))) data->recv_acl(data->hdev, skb); Use cancel_delayed_work_sync() instead. In btusb_close() the cancel also has to happen after btusb_stop_traffic(), otherwise an URB completion racing with the cancel can requeue the work right after it has been waited for. Fixes: 800fe5ec302e ("Bluetooth: btusb: Add support for queuing during polling interval") Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 95f4640c60e4..ddc44ca28722 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -2094,18 +2094,24 @@ static int btusb_close(struct hci_dev *hdev) BT_DBG("%s", hdev->name); - cancel_delayed_work(&data->rx_work); cancel_work_sync(&data->work); cancel_work_sync(&data->waker); - skb_queue_purge(&data->acl_q); - clear_bit(BTUSB_ISOC_RUNNING, &data->flags); clear_bit(BTUSB_BULK_RUNNING, &data->flags); clear_bit(BTUSB_INTR_RUNNING, &data->flags); clear_bit(BTUSB_DIAG_RUNNING, &data->flags); btusb_stop_traffic(data); + + /* rx_work must only be canceled once the URBs that can rearm it are + * gone, and it must be canceled synchronously since btusb_disconnect() + * frees the btusb_data it dereferences right after hci_unregister_dev(). + */ + cancel_delayed_work_sync(&data->rx_work); + + skb_queue_purge(&data->acl_q); + btusb_free_frags(data); err = usb_autopm_get_interface(data->intf); @@ -2131,7 +2137,7 @@ static int btusb_flush(struct hci_dev *hdev) BT_DBG("%s", hdev->name); - cancel_delayed_work(&data->rx_work); + cancel_delayed_work_sync(&data->rx_work); skb_queue_purge(&data->acl_q); From 83e3e515fd261600ed8491fb0a8bcdfb115c904e Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Thu, 3 Sep 2026 03:18:59 +0800 Subject: [PATCH 1212/2366] Bluetooth: btrtl: Don't leak return code when parsing firmware format v2 When key_id from chip is zero, rtlbt_parse_firmware_v2() intentionally ignores all security headers. However, the implementation simply breaks from a switch statement and leaks uninitialized return code `rc' (if the first section is a security one) or the previous section's `rc'. Fix it by really skipping a loop with `continue'. For consistency and readability, also do the same for the default case. Fixes: 9a24ce5e29b1 ("Bluetooth: btrtl: Firmware format v2 support") Cc: stable@vger.kernel.org Signed-off-by: Rong Zhang Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btrtl.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c index 03fa9409e3ee..d29813331603 100644 --- a/drivers/bluetooth/btrtl.c +++ b/drivers/bluetooth/btrtl.c @@ -591,7 +591,7 @@ static int rtlbt_parse_firmware_v2(struct hci_dev *hdev, * headers. */ if (!key_id) - break; + continue; rc = btrtl_parse_section(hdev, btrtl_dev, opcode, ptr, section_len); break; @@ -600,8 +600,7 @@ static int rtlbt_parse_firmware_v2(struct hci_dev *hdev, ptr, section_len); break; default: - rc = 0; - break; + continue; } if (rc < 0) { rtl_dev_err(hdev, "RTL: Parse section (%u) err %d", From 6436e1b5331b1aebf905c13e0880a37032719b75 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Thu, 3 Sep 2026 20:21:01 +0530 Subject: [PATCH 1213/2366] Bluetooth: btintel_pcie: validate packet_len before skb_put_data btintel_pcie_submit_rx_work() reads packet_len from rfh_hdr without checking if it exceeds the RX buffer size. An oversized packet_len can lead to an out-of-bounds read in skb_put_data(). Validate packet_len to ensure it is non-zero and does not exceed BTINTEL_PCIE_BUFFER_SIZE - sizeof(*rfh_hdr), logging an error when invalid. This issue was reported by Claude Mythos. It can be simulated either by using customized firmware configured to return an invalid packet_len or by modifying rfh_hdr->packet_len in the driver before calling btintel_pcie_submit_rx_work(). Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Signed-off-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index eec95e5f3dbb..968608932fb7 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1599,7 +1599,9 @@ static int btintel_pcie_submit_rx_work(struct btintel_pcie_data *data, u8 status rfh_hdr = buf; len = rfh_hdr->packet_len; - if (len <= 0) { + if (len == 0 || len > BTINTEL_PCIE_BUFFER_SIZE - sizeof(*rfh_hdr)) { + bt_dev_err(data->hdev, "Invalid packet_len %d (max %zu)", len, + BTINTEL_PCIE_BUFFER_SIZE - sizeof(*rfh_hdr)); ret = -EINVAL; goto resubmit; } From 3dd1b41f96aad08444be1b7626c89de2b9f2abd4 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Thu, 3 Sep 2026 20:21:02 +0530 Subject: [PATCH 1214/2366] Bluetooth: btintel_pcie: fix tx_handle bounds off-by-one Valid indices into txq->urbd0s/tfds/bufs are 0..txq->count-1, so tfd_index == txq->count is already out of range. Change the guard in btintel_pcie_msix_tx_handle() from '> txq->count' to '>= txq->count'. This issue was reported by Claude Mythos. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Signed-off-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 968608932fb7..6d9649776ae7 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1099,7 +1099,7 @@ static void btintel_pcie_msix_tx_handle(struct btintel_pcie_data *data) urbd0 = &txq->urbd0s[cr_tia]; - if (urbd0->tfd_index > txq->count) + if (urbd0->tfd_index >= txq->count) return; cr_tia = (cr_tia + 1) % txq->count; From 3621f78d43b0a9563d5ade68370434c532eea259 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Wed, 2 Sep 2026 13:16:26 -0400 Subject: [PATCH 1215/2366] Bluetooth: hci_sync: Fix not setting CE length properly Both hci_le_set_def_rate_sync() and hci_le_conn_rate_request_sync() were leaving Min_CE_Length and Max_CE_Length set to 0x0000, but the connection event length recommended in requests by a Peripheral has a valid range of 0x0001 to 0x7CFF (Time = N * 125 us, Time Range: 0.125 ms to 3.999875 s), so 0x0000 cannot be used. Set both to the minimum valid value, which is safe since the Controller is not required to use these values: BLUETOOTH CORE SPECIFICATION Version 6.2 | Vol 4, Part E 7.8.157. LE Connection Rate Request command 7.8.158. LE Set Default Rate Parameters command The Min_CE_Length and Max_CE_Length parameters provide the Controller with the expected minimum and maximum length of the connection events. The Controller is not required to use these values. Fixes: 2f8784cfe8a9 ("Bluetooth: Add support for Shorter Connection Interval (SCI) feature") Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index ffd7b37e7401..3ab5fa3dce96 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -4797,6 +4797,24 @@ static int hci_le_set_def_rate_sync(struct hci_dev *hdev) cp.cont_num = cpu_to_le16(0x0001); cp.supv_timeout = cpu_to_le16(0x000c); /* 120 ms */ + /* The connection event length recommended in requests by a Peripheral + * uses units of 125 us with a valid range of 0x0001 to 0x7CFF + * (0.125 ms to 3.999875 s), so 0x0000 cannot be used. Also note that + * the Controller is not required to use these values: + * + * BLUETOOTH CORE SPECIFICATION Version 6.2 | Vol 4, Part E + * 7.8.158. LE Set Default Rate Parameters command + * + * The Min_CE_Length and Max_CE_Length parameters provide the + * Controller with the expected minimum and maximum length of the + * connection events. The Controller is not required to use these + * values. + * + * So it is safe to just use the minimum. + */ + cp.min_ce_len = cpu_to_le16(0x0001); + cp.max_ce_len = cpu_to_le16(0x0001); + return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_DEF_RATE, sizeof(cp), &cp, HCI_CMD_TIMEOUT); } @@ -7467,8 +7485,24 @@ static int hci_le_conn_rate_request_sync(struct hci_dev *hdev, void *data) cp.max_latency = cpu_to_le16(params->max_latency); cp.cont_num = cpu_to_le16(params->cont_num); cp.supv_timeout = cpu_to_le16(params->rate_supv_timeout); - cp.min_ce_len = cpu_to_le16(0x0000); - cp.max_ce_len = cpu_to_le16(0x0000); + + /* The connection event length recommended in requests by a Peripheral + * uses units of 125 us with a valid range of 0x0001 to 0x7CFF + * (0.125 ms to 3.999875 s), so 0x0000 cannot be used. Also note that + * the Controller is not required to use these values: + * + * BLUETOOTH CORE SPECIFICATION Version 6.2 | Vol 4, Part E + * 7.8.157. LE Connection Rate Request command + * + * The Min_CE_Length and Max_CE_Length parameters provide the + * Controller with the expected minimum and maximum length of the + * connection events. The Controller is not required to use these + * values. + * + * So it is safe to just use the minimum. + */ + cp.min_ce_len = cpu_to_le16(0x0001); + cp.max_ce_len = cpu_to_le16(0x0001); hci_dev_unlock(hdev); From 3d8a8e81ea8ad8813d4c82a12ba53ecb597b217d Mon Sep 17 00:00:00 2001 From: Ivan Hu Date: Fri, 4 Sep 2026 13:30:07 +0800 Subject: [PATCH 1216/2366] Bluetooth: btmtk: Declare MT7920 (MT7961 1a) Bluetooth firmware btmtk_fw_get_filename() constructs the firmware name at runtime, so for the MT7920 variant (dev_id 0x7961 with fw_flavor set) it requests "mediatek/BT_RAM_CODE_MT7961_1a_2_hdr.bin" without ever declaring it via MODULE_FIRMWARE(). Tools that select firmware from module metadata (e.g. "modinfo -F firmware") therefore omit this blob, so request_firmware() fails and Bluetooth does not initialise on MT7920, even though the file is present in linux-firmware. Declare it with MODULE_FIRMWARE(), as the mt76 driver already does for the corresponding MT7920 wifi firmware. Fixes: 1cb63d80fff6 ("Bluetooth: btusb: Add support Mediatek MT7920") Signed-off-by: Ivan Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 1 + drivers/bluetooth/btmtk.h | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index 9589caff925d..26d525acd659 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -1577,5 +1577,6 @@ MODULE_FIRMWARE(FIRMWARE_MT7663); MODULE_FIRMWARE(FIRMWARE_MT7668); MODULE_FIRMWARE(FIRMWARE_MT7922); MODULE_FIRMWARE(FIRMWARE_MT7961); +MODULE_FIRMWARE(FIRMWARE_MT7920); MODULE_FIRMWARE(FIRMWARE_MT7925); MODULE_FIRMWARE(FIRMWARE_MT7927); diff --git a/drivers/bluetooth/btmtk.h b/drivers/bluetooth/btmtk.h index c83c24897c95..bc26148ec544 100644 --- a/drivers/bluetooth/btmtk.h +++ b/drivers/bluetooth/btmtk.h @@ -7,6 +7,7 @@ #define FIRMWARE_MT7922 "mediatek/BT_RAM_CODE_MT7922_1_1_hdr.bin" #define FIRMWARE_MT7902 "mediatek/BT_RAM_CODE_MT7902_1_1_hdr.bin" #define FIRMWARE_MT7961 "mediatek/BT_RAM_CODE_MT7961_1_2_hdr.bin" +#define FIRMWARE_MT7920 "mediatek/BT_RAM_CODE_MT7961_1a_2_hdr.bin" #define FIRMWARE_MT7925 "mediatek/mt7925/BT_RAM_CODE_MT7925_1_1_hdr.bin" #define FIRMWARE_MT7927 "mediatek/mt7927/BT_RAM_CODE_MT6639_2_1_hdr.bin" From 9b851b09b392da68bd715601f10a5adb2d8d19b8 Mon Sep 17 00:00:00 2001 From: Krystian Kaniewski Date: Fri, 4 Sep 2026 12:24:22 +0000 Subject: [PATCH 1217/2366] Bluetooth: hci_sysfs: Fix NULL pointer dereference in device_del() A NULL pointer dereference in klist_put() occurs when a child device (such as a BNEP network device in bnep_session) is concurrently being unregistered while hci_conn_del_sysfs() reparents child devices. This is caused by a race condition between hci_conn_del_sysfs() and concurrent child device unregistration (e.g. bnep_session calling unregister_netdev()). During device unregistration, device_del() snapshots a non-NULL parent pointer. Concurrently, hci_conn_del_sysfs() finds the child device using device_find_any_child() and calls device_move() to reparent it to NULL, which removes the node from its parent's klist and clears knode_parent. Subsequently, device_del() calls klist_del(&dev->p->knode_parent) using the stale parent snapshot, causing klist_put() to dereference knode_klist(n)->put on an already removed node, resulting in a NULL pointer dereference. This race was introduced by commit 27aabf27fd01 ("Bluetooth: fix use-after-free in device_for_each_child()"), which replaced device_find_child(..., __match_tty) with device_find_any_child() in hci_conn_del_sysfs(). That change was intended to avoid a use-after-free where conn->dev outlived its parent hdev->dev when child devices held references to conn->dev, because conn->dev only held a reference to hdev->dev while registered in sysfs. Fix the issue properly by taking an explicit reference to the parent device with get_device(&hdev->dev) in hci_conn_init_sysfs() and dropping it with put_device(parent) in bt_link_release() when the conn device is freed. This ensures that hdev->dev remains valid for the entire lifecycle of conn->dev, resolving the underlying use-after-free. With the parent reference held properly, restore the __match_tty filter in hci_conn_del_sysfs() so that device_move() is only invoked on persistent RFCOMM TTY devices as originally intended, eliminating the race condition with unregistering network devices. Fixes: 27aabf27fd01 ("Bluetooth: fix use-after-free in device_for_each_child()") Assisted-by: Gemini:gemini-3.7-flash syzbot Reported-by: syzbot+6df45dd3d03e1a9aca96@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6df45dd3d03e1a9aca96 Link: https://syzkaller.appspot.com/ai_job?id=f1c0e740-db21-40af-a9ff-84db0fd8b8bd Signed-off-by: Krystian Kaniewski Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sysfs.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/hci_sysfs.c b/net/bluetooth/hci_sysfs.c index 8957ce7c21b7..c2065abf753e 100644 --- a/net/bluetooth/hci_sysfs.c +++ b/net/bluetooth/hci_sysfs.c @@ -13,7 +13,10 @@ static const struct class bt_class = { static void bt_link_release(struct device *dev) { struct hci_conn *conn = to_hci_conn(dev); + struct device *parent = dev->parent; + kfree(conn); + put_device(parent); } static const struct device_type bt_link = { @@ -21,6 +24,16 @@ static const struct device_type bt_link = { .release = bt_link_release, }; +/* + * The rfcomm tty device will possibly retain even when conn + * is down, and sysfs doesn't support move zombie device, + * so we should move the device before conn device is destroyed. + */ +static int __match_tty(struct device *dev, const void *data) +{ + return !strncmp(dev_name(dev), "rfcomm", 6); +} + void hci_conn_init_sysfs(struct hci_conn *conn) { struct hci_dev *hdev = conn->hdev; @@ -29,7 +42,7 @@ void hci_conn_init_sysfs(struct hci_conn *conn) conn->dev.type = &bt_link; conn->dev.class = &bt_class; - conn->dev.parent = &hdev->dev; + conn->dev.parent = get_device(&hdev->dev); device_initialize(&conn->dev); } @@ -69,7 +82,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn) while (1) { struct device *dev; - dev = device_find_any_child(&conn->dev); + dev = device_find_child(&conn->dev, NULL, __match_tty); if (!dev) break; device_move(dev, NULL, DPM_ORDER_DEV_LAST); From f5a427b16e45210dee656b0860728f3d496dee85 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Fri, 4 Sep 2026 10:54:57 +0800 Subject: [PATCH 1218/2366] Bluetooth: btqcomsmd: destroy RPMsg endpoints before freeing hci_dev The command and ACL RPMsg endpoints store struct btqcomsmd as their callback private data. The receive callbacks dereference btq->hdev without taking an hci_dev reference. The current teardown order frees the hci_dev before destroying the RPMsg endpoints in both the hci_register_dev() error path and the driver remove path. If WCNSS delivers data in that window, the endpoint callback can run with an already freed hci_dev and pass it to the Bluetooth core. For qcom_smd endpoints, rpmsg_destroy_ept() closes the channel and clears the callback under the channel recv_lock. The receive path holds the same lock while invoking the callback, so destroying the endpoints first both prevents new callbacks and serializes with any callback already running. Destroy the command and ACL endpoints before hci_free_dev(). Keep hci_unregister_dev() first during remove so the HCI core stops issuing operations before the transport endpoints are shut down. In the full registration-error cleanup path, return directly after freeing the hci_dev to avoid falling through to the partial-construction labels and destroying the endpoints twice. Fixes: 5052de8deff5 ("soc: qcom: smd: Transition client drivers from smd to rpmsg") Fixes: 9a39a927be01 ("Bluetooth: btqcomsmd: Fix a resource leak in error handling paths in the probe function") Cc: stable@vger.kernel.org Acked-by: Bartosz Golaszewski Reviewed-by: Dmitry Baryshkov Signed-off-by: Xu Rao Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btqcomsmd.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btqcomsmd.c b/drivers/bluetooth/btqcomsmd.c index d2e13fcb6bab..d669ea4eb3eb 100644 --- a/drivers/bluetooth/btqcomsmd.c +++ b/drivers/bluetooth/btqcomsmd.c @@ -188,7 +188,10 @@ static int btqcomsmd_probe(struct platform_device *pdev) return 0; hci_free_dev: + rpmsg_destroy_ept(btq->cmd_channel); + rpmsg_destroy_ept(btq->acl_channel); hci_free_dev(hdev); + return ret; destroy_cmd_channel: rpmsg_destroy_ept(btq->cmd_channel); destroy_acl_channel: @@ -202,10 +205,11 @@ static void btqcomsmd_remove(struct platform_device *pdev) struct btqcomsmd *btq = platform_get_drvdata(pdev); hci_unregister_dev(btq->hdev); - hci_free_dev(btq->hdev); rpmsg_destroy_ept(btq->cmd_channel); rpmsg_destroy_ept(btq->acl_channel); + + hci_free_dev(btq->hdev); } static const struct of_device_id btqcomsmd_of_match[] = { From e486a891c412d9d82ee865987f4eead6196e1f96 Mon Sep 17 00:00:00 2001 From: Jiajia Liu Date: Fri, 4 Sep 2026 16:03:50 +0800 Subject: [PATCH 1219/2366] Bluetooth: btusb: mediatek: Fix leaked runtime PM reference in reset MT7925 on HP Pro Mini 260 sometimes timed out during reloading driver and reset usb device. btusb_suspend is not called again after closing bluetooth interface. usbcore: registered new interface driver btusb Bluetooth: hci0: HW/SW Version: 0x00000000, Build Time: 20260605184935 Bluetooth: hci0: Execution of wmt command timed out Bluetooth: hci0: Failed to send wmt patch dwnld (-110) Bluetooth: hci0: Failed to set up firmware (-110) usb 3-10: reset high-speed USB device number 4 using xhci_hcd Bluetooth: hci0: HW/SW Version: 0x00000000, Build Time: 20260605184935 Bluetooth: hci0: Device setup in 1856545 usecs Bluetooth: hci0: AOSP extensions version v1.00 Bluetooth: hci0: AOSP quality report is supported Bluetooth: MGMT ver 1.23 btusb_mtk_reset calls usb_autopm_get_interface to resume the device before driving the hardware reset, but never calls the matching usb_autopm_put_interface. Every hardware reset therefore leaks a PM usage reference of the interface, preventing the device from being runtime suspended again until it is unbound. Add the BTUSB_RESET flag. It is set before usb_queue_reset_device and is cleared in btusb_disconnect, which drops the reference as well. If the flag is already set when a new reset is requested, drop one reference. Also clear BTMTK_HW_RESET_ACTIVE if usb_autopm_get_interface fails, otherwise no further reset could ever be attempted. Fixes: 25b6d7593a3a ("Bluetooth: btmtk: introduce btmtk reset work") Assisted-by: Claude:qwen3.8-max Signed-off-by: Jiajia Liu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index ddc44ca28722..9372fb521575 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -982,6 +982,7 @@ struct btqca_data { #define BTUSB_ALT6_CONTINUOUS_TX 16 #define BTUSB_HW_SSR_ACTIVE 17 #define BTUSB_WAKEUP_BROKEN 18 +#define BTUSB_RESET 19 struct btusb_data { struct hci_dev *hdev; @@ -2931,8 +2932,11 @@ static int btusb_mtk_reset(struct hci_dev *hdev, void *rst_data) } err = usb_autopm_get_interface(data->intf); - if (err < 0) + if (err < 0) { + bt_dev_err(hdev, "Failed usb_autopm_get_interface: %d", err); + clear_bit(BTMTK_HW_RESET_ACTIVE, &btmtk_data->flags); return err; + } /* Release MediaTek ISO data interface */ btusb_mtk_release_iso_intf(hdev); @@ -2954,6 +2958,11 @@ static int btusb_mtk_reset(struct hci_dev *hdev, void *rst_data) err = btmtk_usb_subsys_reset(hdev, btmtk_data->dev_id); + if (test_and_set_bit(BTUSB_RESET, &data->flags)) { + bt_dev_err(hdev, "last usb reset failed? Resetting again"); + usb_autopm_put_interface_no_suspend(data->intf); + } + usb_queue_reset_device(data->intf); clear_bit(BTMTK_HW_RESET_ACTIVE, &btmtk_data->flags); @@ -4596,6 +4605,9 @@ static void btusb_disconnect(struct usb_interface *intf) if (data->reset_gpio) gpiod_put(data->reset_gpio); + if (test_and_clear_bit(BTUSB_RESET, &data->flags)) + usb_autopm_put_interface_no_suspend(data->intf); + if (intf == data->intf) { if (data->isoc) usb_driver_release_interface(&btusb_driver, data->isoc); From c93922dd316b7273a8667d29084632066fa8a2d3 Mon Sep 17 00:00:00 2001 From: Jiajia Liu Date: Fri, 4 Sep 2026 16:03:51 +0800 Subject: [PATCH 1220/2366] Bluetooth: btusb: Fix leaked runtime PM reference in btusb_reset btusb_reset calls usb_autopm_get_interface to resume the device before queuing a reset of it, but never calls the matching usb_autopm_put_interface. usb_queue_reset_device ends up in usb_reset_device(), and since btusb provides no pre_reset/post_reset callbacks the interface is merely unbound and rebound: the interface device object survives this cycle, and so does its PM usage count, which is not cleared when the driver is unbound. As a result every reset permanently leaks a PM usage reference, preventing the interface from being runtime suspended again until it is unbound. Set BTUSB_RESET flag before usb_queue_reset_device so that btusb_disconnect drops the reference. If the flag is already set, drop one reference. Fixes: c9209b269afd ("Bluetooth: btusb: Introduce generic USB reset") Assisted-by: Claude:qwen3.8-max Signed-off-by: Jiajia Liu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 9372fb521575..002b9f975710 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -1057,13 +1057,15 @@ static void btusb_reset(struct hci_dev *hdev) int err; data = hci_get_drvdata(hdev); - /* This is not an unbalanced PM reference since the device will reset */ err = usb_autopm_get_interface(data->intf); if (err) { bt_dev_err(hdev, "Failed usb_autopm_get_interface: %d", err); return; } + if (test_and_set_bit(BTUSB_RESET, &data->flags)) + usb_autopm_put_interface_no_suspend(data->intf); + bt_dev_err(hdev, "Resetting usb device."); usb_queue_reset_device(data->intf); } From f4c3e38111fd84c2c7ae5785755f4a4d476e1cba Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Tue, 8 Sep 2026 19:05:39 +0200 Subject: [PATCH 1221/2366] rust: allow `unknown_lints` in generated bindings for Rust < 1.88 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starting with bindgen 0.73.2 [1], `#[allow(unnecessary_transmutes)]` are used, even when `--rust-target 1.85` is passed. However, the lint was introduced in Rust 1.88.0. Thus building with older Rust versions warns like: error: unknown lint: `unnecessary_transmutes` --> rust/uapi/uapi_generated.rs:26294:13 | 26294 | #[allow(unnecessary_transmutes)] | ^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D unknown-lints` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(unknown_lints)]` Thus allow `unknown_lints` in the generated bindings -- only when building with older Rust versions. I have asked upstream if this is intentional [1], i.e. if we are supposed to always allow unknown lints in case `bindgen` uses such attributes, or whether it is an oversight. [ Emilio said it wasn't intentional -- we will work around it for now on the kernel side. - Miguel ] Cc: stable@vger.kernel.org # Needed in 6.12.y and later (Rust is pinned in older LTSs). Cc: Emilio Cobos Álvarez Link: https://github.com/rust-lang/rust-bindgen/pull/3455#issuecomment-5588526559 [1] Assisted-by: LLM Link: https://patch.msgid.link/20260908170539.345207-1-ojeda@kernel.org [ Removed the `cfg` for `allow(unnecessary_transmutes)` as suggested by Gary. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/bindings/lib.rs | 3 ++- rust/uapi/lib.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index ad24c920b919..439ab88a5da1 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -27,7 +27,8 @@ #[allow(clippy::ptr_as_ptr)] #[allow(clippy::ref_as_ptr)] #[allow(clippy::undocumented_unsafe_blocks)] -#[cfg_attr(CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES, allow(unnecessary_transmutes))] +#[cfg_attr(not(CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES), allow(unknown_lints))] +#[allow(unnecessary_transmutes)] #[cfg_attr( CONFIG_RUSTC_HAS_SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS, allow(suspicious_runtime_symbol_definitions) diff --git a/rust/uapi/lib.rs b/rust/uapi/lib.rs index 2df0340e63d1..003e6d4f7c4b 100644 --- a/rust/uapi/lib.rs +++ b/rust/uapi/lib.rs @@ -24,7 +24,8 @@ unreachable_pub, unsafe_op_in_unsafe_fn )] -#![cfg_attr(CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES, allow(unnecessary_transmutes))] +#![cfg_attr(not(CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES), allow(unknown_lints))] +#![allow(unnecessary_transmutes)] #![cfg_attr( CONFIG_RUSTC_HAS_SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS, allow(suspicious_runtime_symbol_definitions) From f6d61fe4c19cf448e5cba6d8767b4e6966f58606 Mon Sep 17 00:00:00 2001 From: Long Li Date: Wed, 2 Sep 2026 10:51:53 -0700 Subject: [PATCH 1222/2366] net: mana: Clear RDMA teardown and suspend state in mana_rdma_probe() mana_rdma_remove() sets gd->rdma_teardown to stop mana_rdma_service_handle() from acting on servicing events, but nothing ever clears it. A hardware service reset (GDMA_EQE_HWC_RESET_REQUEST) goes through mana_gd_suspend() -> mana_rdma_remove() and mana_gd_resume() -> mana_rdma_probe(), so from the first reset onwards every GDMA_EQE_HWC_SOC_SERVICE event returns early and RDMA suspend/resume servicing is silently dropped for the life of the device. gd->is_suspended has the same problem: it is set when servicing removes the adev and is cleared only by a matching resume. A reset while RDMA is suspended re-adds the adev but leaves is_suspended set, so a later resume event calls add_adev() on top of a live gd->adev and leaks it. This is currently masked by the rdma_teardown bug. Clear both in mana_rdma_probe(). On the reset path mana_rdma_remove() has closed the gate and drained the service workqueue, so clear is_suspended first and re-open the gate with smp_store_release(), paired with smp_load_acquire() in the handler, so the handler cannot observe an open gate with a stale is_suspended. On the initial probe path the gate was never closed and both flags are already clear. This does not order gd->adev, which add_adev() publishes afterwards. A servicing event arriving in that window is still dropped, as it is in mainline today on the initial probe path; closing it needs probe and the handler to be serialized and is left to a separate change. Fixes: 505cc26bcae0 ("net: mana: Add support for auxiliary device servicing events") Signed-off-by: Long Li Link: https://patch.msgid.link/20260902175153.3410560-1-longli@microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/mana_en.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 45a7520491a6..591fb4191d90 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -3987,7 +3987,8 @@ static void mana_rdma_service_handle(struct work_struct *work) struct device *dev = gd->gdma_context->dev; int ret; - if (READ_ONCE(gd->rdma_teardown)) + /* Pairs with the smp_store_release() in mana_rdma_probe(). */ + if (smp_load_acquire(&gd->rdma_teardown)) goto out; switch (serv_work->event) { @@ -4283,6 +4284,21 @@ int mana_rdma_probe(struct gdma_dev *gd) if (err) return err; + /* Clear the state left by a previous mana_rdma_remove() so servicing + * events are handled again after a reset cycle. + */ + gd->is_suspended = false; + + /* Publish is_suspended before re-opening the gate, so the handler + * cannot observe an open gate with a stale is_suspended. Pairs + * with the smp_load_acquire() in mana_rdma_service_handle(). This + * matters on the reset path, where mana_rdma_remove() closed the + * gate and drained the workqueue; on the initial probe path the + * gate was never closed and both flags are already clear. It does + * not order gd->adev, which add_adev() publishes below. + */ + smp_store_release(&gd->rdma_teardown, false); + err = add_adev(gd, "rdma"); if (err) mana_gd_deregister_device(gd); From 8dc5d98a16fa23c00999aecf10018c9f69fa5bf4 Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Thu, 3 Sep 2026 08:21:29 +0200 Subject: [PATCH 1223/2366] ppp_async: drop the errored frame instead of resetting its headroom ppp_receive_nonmp_frame() prepends a two-byte direction tag before running the pass/active BPF filters: *(__be16 *)skb_push(skb, 2) = htons(PPP_FILTER_INBOUND_TAG); Nothing on the receive path guarantees those two bytes of headroom. The frame-error path in ppp_async's process_input_packet() resets a reused skb's headroom to zero while claiming to restore it to a freshly allocated state - but a fresh skb from dev_alloc_skb() carries NET_SKB_PAD: err: if (skb) { /* make skb appear as freshly allocated */ skb_trim(skb, 0); skb_reserve(skb, - skb_headroom(skb)); } ap->rpkt still points at that skb, so the next frame is reassembled into it with no headroom at all. A peer that sends a bad-FCS frame followed by one beginning ff 03 then leaves a single byte of headroom by the time the filter tag is pushed, which lands one byte below skb->head: skbuff: skb_under_panic: len:49 put:2 head:ffff888003c10000 data:ffff888003c0ffff tail:0x30 end:0x640 dev: kernel BUG at net/core/skbuff.c:214! RIP: 0010:skb_panic+0x13e/0x230 Call Trace: skb_push+0xbd/0x100 ppp_receive_nonmp_frame+0x48a/0x1d10 ppp_input+0x4e9/0x2f80 ppp_async_process+0x2a/0xe0 tasklet_action_common+0x20f/0x8a0 handle_softirqs+0x18e/0x590 Kernel panic - not syncing: Fatal exception in interrupt Zeroing the headroom violates the NET_SKB_PAD guarantee that dev_alloc_skb() gives the rest of the receive path. Besides the filter panic above, when CCP compression is enabled ppp_decompress_frame() hands skb->data - 2 to ->decompress()/->incomp(), which then reads out of bounds before skb->head for the same reason. Rather than restore the headroom, drop the errored frame - as ppp_synctty already does on its error path - and clear ap->rpkt so the next frame is reassembled into a fresh skb with proper headroom. This is simpler and fixes both the filter under-panic and the CCP out-of-bounds read. The original V1 of this patch made room in ppp_receive_nonmp_frame() with skb_cow_head(); Eric pointed out that fixing the root cause in the transport is the right approach. Found by fuzzing the PPP receive path with a mutating peer on a pty; it is an interesting (remote) DoS: root configures PPP, the peer supplies two crashing frames. The reproducer (repro-ppp-skb.c, unchanged from v1) panics in about a second, and returns cleanly with this applied. Fixes: 6722e78c9005 ("[PPP]: handle misaligned accesses") Suggested-by: Eric Dumazet Signed-off-by: Vlatko Kosturjak Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/apkR6ZU+tqP2C3Fl@griffin.linux.hr Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_async.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/net/ppp/ppp_async.c b/drivers/net/ppp/ppp_async.c index ea7fe9608ffd..6e6e2b944128 100644 --- a/drivers/net/ppp/ppp_async.c +++ b/drivers/net/ppp/ppp_async.c @@ -742,11 +742,8 @@ process_input_packet(struct asyncppp *ap) err: /* frame had an error, remember that, reset SC_TOSS & SC_ESCAPE */ ap->state = SC_PREV_ERROR; - if (skb) { - /* make skb appear as freshly allocated */ - skb_trim(skb, 0); - skb_reserve(skb, - skb_headroom(skb)); - } + kfree_skb(skb); + ap->rpkt = NULL; } /* Called when the tty driver has data for us. Runs parallel with the From e24279bffec6c9aa3fef7e3c64bd4000aca9d698 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 12:06:08 +0200 Subject: [PATCH 1224/2366] MAINTAINERS: Update the so_txtime selftest path in SOCKET TIMESTAMPING Commit 5c6baef3885c ("selftests: drv-net: convert so_txtime to drv-net") moved the test to tools/testing/selftests/drivers/net/, but the SOCKET TIMESTAMPING entry still lists the old path and scripts/get_maintainer.pl --self-test=patterns reports it as matching nothing. Point the pattern at the new location. Signed-off-by: Karl Mehltretter Reviewed-by: Jason Xing Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260905100608.42539-1-kmehltretter@gmail.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index fc320b373656..6f2c8cb67c7d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25335,8 +25335,8 @@ F: Documentation/networking/timestamping.rst F: include/linux/net_tstamp.h F: include/uapi/linux/net_tstamp.h F: tools/testing/selftests/bpf/*/net_timestamping* +F: tools/testing/selftests/drivers/net/so_txtime.* F: tools/testing/selftests/net/*timestamp* -F: tools/testing/selftests/net/so_txtime.c SOEKRIS NET48XX LED SUPPORT M: Chris Boot From cdca92eddc025fdb90071be97738f7d55a65f8dd Mon Sep 17 00:00:00 2001 From: Naman Gulati Date: Fri, 4 Sep 2026 18:06:44 +0000 Subject: [PATCH 1225/2366] ipv6: null-check fib6_node before accessing in __ip6_del_rt_siblings() syzbot reported a null-ptr-deref in __ip6_del_rt_siblings() [0]. The stack trace hinted towards a null dereference of rt->fib6_node when fn->leaf is accessed in __ip6_del_rt_siblings(). With RTNL_FLAG_DOIT_UNLOCKED set, inet6_rtm_delroute() operations run concurrently without acquiring the RTNL lock. In ip6_route_del(), the route lookup happens under rcu_read_lock() without acquiring table->tb6_lock. Between ip6_route_del() looking up the route and __ip6_del_rt_siblings() acquiring table->tb6_lock, another thread can modify the routing table. For example, when an ECMP route is replaced via RTM_NEWROUTE with NLM_F_REPLACE, fib6_add_rt2node() unlinks all old siblings and sets iter->fib6_node = NULL. A reproducer was found that triggers this [1]. Add a check to ensure rt->fib6_node is non-null before accessing it. [0] KASAN: null-ptr-deref in range [0x0000000000000020-0x0000000000000027] RIP: 0010:__ip6_del_rt_siblings+0x31e/0x7c0 net/ipv6/route.c:4056 Call Trace: ip6_route_del+0x1054/0x1110 net/ipv6/route.c:4232 inet6_rtm_delroute+0x5d7/0x6d0 net/ipv6/route.c:5669 rtnetlink_rcv_msg+0x802/0xc00 net/core/rtnetlink.c:7132 netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2556 netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline] netlink_unicast+0x7f5/0x990 net/netlink/af_netlink.c:1345 netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1900 sock_sendmsg_nosec+0x13a/0x180 net/socket.c:800 __sock_sendmsg net/socket.c:815 [inline] ____sys_sendmsg+0x565/0x870 net/socket.c:2713 ___sys_sendmsg+0x2a5/0x360 net/socket.c:2767 __sys_sendmsg net/socket.c:2799 [inline] __do_sys_sendmsg net/socket.c:2804 [inline] __se_sys_sendmsg net/socket.c:2802 [inline] __x64_sys_sendmsg+0x1b7/0x290 net/socket.c:2802 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline] do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f [1] https://gist.github.com/NamanGulati/0766a1159b6ca61928faaf87425ff899 Fixes: bd11ff421d36 ("ipv6: Get rid of RTNL for SIOCDELRT and RTM_DELROUTE.") Reported-by: syzbot+a73e5ee0fd534fed75bd@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a9b03f9.04649fcc.10325f.0003.GAE@google.com Signed-off-by: Naman Gulati Reviewed-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Reviewed-by: Fernando Fernandez Mancera Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260904180645.706425-1-namangulati@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 9658939511e0..08bd68f1b5bb 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -4019,6 +4019,7 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) struct net *net = info->nl_net; struct sk_buff *skb = NULL; struct fib6_table *table; + struct fib6_node *fn; int err = -ENOENT; if (rt == net->ipv6.fib6_null_entry) @@ -4026,9 +4027,13 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) table = rt->fib6_table; spin_lock_bh(&table->tb6_lock); + fn = rcu_dereference_protected(rt->fib6_node, + lockdep_is_held(&table->tb6_lock)); + if (!fn) + goto out_unlock; + if (rt->fib6_nsiblings && cfg->fc_delete_all_nh) { struct fib6_info *sibling, *next_sibling; - struct fib6_node *fn; /* prefer to send a single notification with all hops */ skb = nlmsg_new(rt6_nlmsg_size(rt), GFP_ATOMIC); @@ -4051,8 +4056,6 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) * and emit a replace or delete notification, respectively. */ info->skip_notify_kernel = 1; - fn = rcu_dereference_protected(rt->fib6_node, - lockdep_is_held(&table->tb6_lock)); if (rcu_access_pointer(fn->leaf) == rt) { struct fib6_info *last_sibling, *replace_rt; From ba4ba11ed6eb8972c69070417fc27b48deb002e8 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Sun, 6 Sep 2026 10:21:09 +0200 Subject: [PATCH 1226/2366] net: openvswitch: fix use-after-free of the flow table mask array tbl_mask_array_realloc() retires the old mask_array before it stops being reachable: old = ovsl_dereference(tbl->mask_array); if (old) { ... call_rcu(&old->rcu, mask_array_rcu_cb); } rcu_assign_pointer(tbl->mask_array, new); call_rcu() only waits for read-side critical sections already in flight. tbl->mask_array still points at old between the call_rcu() and the rcu_assign_pointer(), so a reader entering ovs_flow_tbl_lookup_stats() in that window picks up old in a fresh critical section that the pending grace period does not cover. tbl_mask_array_realloc() runs in process context under ovs_mutex, so the window is preemptible and can outlast the grace period. Then mask_array_rcu_cb() frees old before the swap runs: BUG: KASAN: slab-use-after-free in flow_lookup.constprop.0+0x2bf/0x2f0 Read of size 8 at addr ffff888020b3e018 by task poc/741 flow_lookup.constprop.0+0x2bf/0x2f0 ovs_flow_tbl_lookup_stats+0x4a3/0x5c0 ovs_dp_process_packet+0x19c/0x710 ovs_vport_receive+0x243/0x390 internal_dev_xmit+0x81/0x170 Freed by task 728: kfree+0x16a/0x4e0 rcu_core+0x853/0x1030 Publish the new array before retiring the old one. The kfree_rcu() that call_rcu() replaced ran after the swap. Fixes: eac87c413bf9 ("net: openvswitch: reorder masks array based on usage") Cc: stable@vger.kernel.org Signed-off-by: Norbert Szetei Reviewed-by: Ilya Maximets Acked-by: Eelco Chaudron echaudro@redhat.com Link: https://patch.msgid.link/DE115F9C-2545-423E-A702-986FC952FD62@doyensec.com Signed-off-by: Jakub Kicinski --- net/openvswitch/flow_table.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/openvswitch/flow_table.c b/net/openvswitch/flow_table.c index 67d5b8c0fe79..1e0f9d193eb0 100644 --- a/net/openvswitch/flow_table.c +++ b/net/openvswitch/flow_table.c @@ -257,11 +257,13 @@ static int tbl_mask_array_realloc(struct flow_table *tbl, int size) if (ovsl_dereference(old->masks[i])) new->masks[new->count++] = old->masks[i]; } - call_rcu(&old->rcu, mask_array_rcu_cb); } rcu_assign_pointer(tbl->mask_array, new); + if (old) + call_rcu(&old->rcu, mask_array_rcu_cb); + return 0; } From 7a49e6b16f36b8e085521699adbca3e321b6dd0c Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 3 Sep 2026 14:56:14 +0800 Subject: [PATCH 1227/2366] net: bridge: use option bits for CFM/MRP frame handlers CFM and MRP register a global br_frame_type whose hlist_node is linked into the per-bridge frame_type_list when the first MEP/MRP instance is created. Enabling the protocol on multiple bridges therefore inserts the same node into multiple lists. Unregistering it on one bridge then corrupts list state belonging to another. These handlers can only be installed once per bridge, and they are uncommon. Track their per-bridge enable state with net_bridge option bits, which already live on the Rx hot cache line, and dispatch the matching handler directly from the receive path. Check both bits together first as an unlikely case. Remove the generic frame_type_list and br_frame_type helpers, which have had no other users since CFM and MRP were added. That shrinks struct net_bridge by 8 bytes and drops the list walk from the fast path. When neither protocol is compiled in, BR_CFM_MRP_OPTS is 0 and the compiler prunes the branch. Fixes: 90c628dd47ff ("net: bridge: extend the process of special frames") Fixes: dc32cbb3dbd7 ("bridge: cfm: Kernel space implementation of CFM. CCM frame RX added.") Cc: stable@vger.kernel.org Reported-by: Vega Suggested-by: Nikolay Aleksandrov Co-developed-by: Yilin Zhu Signed-off-by: Yilin Zhu Signed-off-by: Zhiling Zou Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/0345b9d5aa60ba416f6738ff1b87140f0a749cb8.1788417901.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/bridge/br_cfm.c | 11 +++-------- net/bridge/br_device.c | 1 - net/bridge/br_input.c | 35 ++++++++++++++--------------------- net/bridge/br_mrp.c | 13 +++---------- net/bridge/br_private.h | 26 +++++++++++++++----------- 5 files changed, 35 insertions(+), 51 deletions(-) diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c index dea56fffa1c1..9dcc97d63a6f 100644 --- a/net/bridge/br_cfm.c +++ b/net/bridge/br_cfm.c @@ -367,7 +367,7 @@ static u32 ccm_tlv_extract(struct sk_buff *skb, u32 index, } /* note: already called with rcu_read_lock */ -static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb) +int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb) { u32 mdlevel, interval, size, index, max; const struct br_cfm_common_hdr *hdr; @@ -489,11 +489,6 @@ static int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb) return 1; } -static struct br_frame_type cfm_frame_type __read_mostly = { - .type = cpu_to_be16(ETH_P_CFM), - .frame_handler = br_cfm_frame_rx, -}; - int br_cfm_mep_create(struct net_bridge *br, const u32 instance, struct br_cfm_mep_create *const create, @@ -559,7 +554,7 @@ int br_cfm_mep_create(struct net_bridge *br, INIT_DELAYED_WORK(&mep->ccm_tx_dwork, ccm_tx_work_expired); if (hlist_empty(&br->mep_list)) - br_add_frame(br, &cfm_frame_type); + br_opt_toggle(br, BROPT_CFM_ENABLED, true); hlist_add_tail_rcu(&mep->head, &br->mep_list); @@ -588,7 +583,7 @@ static void mep_delete_implementation(struct net_bridge *br, kfree_rcu(mep, rcu); if (hlist_empty(&br->mep_list)) - br_del_frame(br, &cfm_frame_type); + br_opt_toggle(br, BROPT_CFM_ENABLED, false); } int br_cfm_mep_delete(struct net_bridge *br, diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c index ff55dab73632..e01c44a90d84 100644 --- a/net/bridge/br_device.c +++ b/net/bridge/br_device.c @@ -503,7 +503,6 @@ void br_dev_setup(struct net_device *dev) spin_lock_init(&br->lock); INIT_LIST_HEAD(&br->port_list); INIT_HLIST_HEAD(&br->fdb_list); - INIT_HLIST_HEAD(&br->frame_type_list); #if IS_ENABLED(CONFIG_BRIDGE_MRP) INIT_HLIST_HEAD(&br->mrp_list); #endif diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c index d87a5f9fa92b..8bed72baf161 100644 --- a/net/bridge/br_input.c +++ b/net/bridge/br_input.c @@ -317,17 +317,25 @@ static int nf_hook_bridge_pre(struct sk_buff *skb, struct sk_buff **pskb) return RX_HANDLER_CONSUMED; } +#define BR_CFM_MRP_OPTS \ + ((IS_ENABLED(CONFIG_BRIDGE_CFM) ? BIT(BROPT_CFM_ENABLED) : 0UL) | \ + (IS_ENABLED(CONFIG_BRIDGE_MRP) ? BIT(BROPT_MRP_ENABLED) : 0UL)) + /* Return 0 if the frame was not processed otherwise 1 * note: already called with rcu_read_lock */ static int br_process_frame_type(struct net_bridge_port *p, struct sk_buff *skb) { - struct br_frame_type *tmp; + struct net_bridge *br = p->br; - hlist_for_each_entry_rcu(tmp, &p->br->frame_type_list, list) - if (unlikely(tmp->type == skb->protocol)) - return tmp->frame_handler(p, skb); + if (skb->protocol == htons(ETH_P_CFM) && + br_opt_get(br, BROPT_CFM_ENABLED)) + return br_cfm_frame_rx(p, skb); + + if (skb->protocol == htons(ETH_P_MRP) && + br_opt_get(br, BROPT_MRP_ENABLED)) + return br_mrp_process(p, skb); return 0; } @@ -425,7 +433,8 @@ static rx_handler_result_t br_handle_frame(struct sk_buff **pskb) } } - if (unlikely(br_process_frame_type(p, skb))) + if (unlikely((READ_ONCE(p->br->options) & BR_CFM_MRP_OPTS) && + br_process_frame_type(p, skb))) return RX_HANDLER_PASS; forward: @@ -467,19 +476,3 @@ rx_handler_func_t *br_get_rx_handler(const struct net_device *dev) return br_handle_frame; } - -void br_add_frame(struct net_bridge *br, struct br_frame_type *ft) -{ - hlist_add_head_rcu(&ft->list, &br->frame_type_list); -} - -void br_del_frame(struct net_bridge *br, struct br_frame_type *ft) -{ - struct br_frame_type *tmp; - - hlist_for_each_entry(tmp, &br->frame_type_list, list) - if (ft == tmp) { - hlist_del_rcu(&ft->list); - return; - } -} diff --git a/net/bridge/br_mrp.c b/net/bridge/br_mrp.c index ef16d0703924..dce6efa96c4c 100644 --- a/net/bridge/br_mrp.c +++ b/net/bridge/br_mrp.c @@ -6,13 +6,6 @@ static const u8 mrp_test_dmac[ETH_ALEN] = { 0x1, 0x15, 0x4e, 0x0, 0x0, 0x1 }; static const u8 mrp_in_test_dmac[ETH_ALEN] = { 0x1, 0x15, 0x4e, 0x0, 0x0, 0x3 }; -static int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb); - -static struct br_frame_type mrp_frame_type __read_mostly = { - .type = cpu_to_be16(ETH_P_MRP), - .frame_handler = br_mrp_process, -}; - static bool br_mrp_is_ring_port(struct net_bridge_port *p_port, struct net_bridge_port *s_port, struct net_bridge_port *port) @@ -486,7 +479,7 @@ static void br_mrp_del_impl(struct net_bridge *br, struct br_mrp *mrp) kfree_rcu(mrp, rcu); if (hlist_empty(&br->mrp_list)) - br_del_frame(br, &mrp_frame_type); + br_opt_toggle(br, BROPT_MRP_ENABLED, false); } /* Adds a new MRP instance. @@ -536,7 +529,7 @@ int br_mrp_add(struct net_bridge *br, struct br_mrp_instance *instance) rcu_assign_pointer(mrp->s_port, p); if (hlist_empty(&br->mrp_list)) - br_add_frame(br, &mrp_frame_type); + br_opt_toggle(br, BROPT_MRP_ENABLED, true); INIT_DELAYED_WORK(&mrp->test_work, br_mrp_test_work_expired); INIT_DELAYED_WORK(&mrp->in_test_work, br_mrp_in_test_work_expired); @@ -1241,7 +1234,7 @@ static int br_mrp_rcv(struct net_bridge_port *p, * normal forwarding. * note: already called with rcu_read_lock */ -static int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb) +int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb) { /* If there is no MRP instance do normal forwarding */ if (likely(!test_bit(BR_MRP_AWARE_BIT, &p->flags))) diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index d337b1cfb980..b01997ea9508 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -495,12 +495,13 @@ enum net_bridge_opts { BROPT_MST_ENABLED, BROPT_MDB_OFFLOAD_FAIL_NOTIFICATION, BROPT_FDB_LOCAL_VLAN_0, + BROPT_CFM_ENABLED, + BROPT_MRP_ENABLED, }; struct net_bridge { spinlock_t lock; spinlock_t hash_lock; - struct hlist_head frame_type_list; struct net_device *dev; unsigned long options; /* These fields are accessed on each packet */ @@ -932,16 +933,6 @@ int nbp_backup_change(struct net_bridge_port *p, struct net_device *backup_dev); int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb); rx_handler_func_t *br_get_rx_handler(const struct net_device *dev); -struct br_frame_type { - __be16 type; - int (*frame_handler)(struct net_bridge_port *port, - struct sk_buff *skb); - struct hlist_node list; -}; - -void br_add_frame(struct net_bridge *br, struct br_frame_type *ft); -void br_del_frame(struct net_bridge *br, struct br_frame_type *ft); - static inline bool br_rx_handler_check_rcu(const struct net_device *dev) { return rcu_dereference(dev->rx_handler) == br_get_rx_handler(dev); @@ -2080,6 +2071,7 @@ int br_mrp_parse(struct net_bridge *br, struct net_bridge_port *p, bool br_mrp_enabled(struct net_bridge *br); void br_mrp_port_del(struct net_bridge *br, struct net_bridge_port *p); int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br); +int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb); #else static inline int br_mrp_parse(struct net_bridge *br, struct net_bridge_port *p, struct nlattr *attr, int cmd, @@ -2103,6 +2095,11 @@ static inline int br_mrp_fill_info(struct sk_buff *skb, struct net_bridge *br) return 0; } +static inline int br_mrp_process(struct net_bridge_port *p, struct sk_buff *skb) +{ + return 0; +} + #endif /* br_cfm.c */ @@ -2111,6 +2108,7 @@ int br_cfm_parse(struct net_bridge *br, struct net_bridge_port *p, struct nlattr *attr, int cmd, struct netlink_ext_ack *extack); bool br_cfm_created(struct net_bridge *br); void br_cfm_port_del(struct net_bridge *br, struct net_bridge_port *p); +int br_cfm_frame_rx(struct net_bridge_port *port, struct sk_buff *skb); int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br); int br_cfm_status_fill_info(struct sk_buff *skb, struct net_bridge *br, @@ -2135,6 +2133,12 @@ static inline void br_cfm_port_del(struct net_bridge *br, { } +static inline int br_cfm_frame_rx(struct net_bridge_port *port, + struct sk_buff *skb) +{ + return 0; +} + static inline int br_cfm_config_fill_info(struct sk_buff *skb, struct net_bridge *br) { return -EOPNOTSUPP; From 7d059f390750152b9bd69df934198651b94fc26d Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Mon, 7 Sep 2026 23:08:55 +0200 Subject: [PATCH 1228/2366] net: macb: destroy the phylink instance on the probe error path macb_mii_init() creates a phylink instance on both of its success paths, but the probe unwind frees the netdev without destroying it, so a failing macb_alloc_tieoff() or register_netdev() leaks the instance. Destroy it at err_out_unregister_mdio, which is only reachable once macb_mii_init() has succeeded, so bp->phylink is valid there. Fixes: 7897b071ac3b ("net: macb: convert to phylink") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260907210856.1673589-2-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 0e75339fa206..50ecfa80c660 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -5980,6 +5980,7 @@ static int macb_probe(struct platform_device *pdev) mdiobus_unregister(bp->mii_bus); mdiobus_free(bp->mii_bus); } + phylink_destroy(bp->phylink); err_out_phy_exit: phy_exit(bp->phy); From 382a373d9ea7a6ac4de9c022385b6217f65ae3cc Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Mon, 7 Sep 2026 23:08:56 +0200 Subject: [PATCH 1229/2366] net: macb: put the "mdio" child node reference on success macb_mii_init() holds the reference returned by of_get_child_by_name() for macb_mdiobus_register() and drops it only on the error paths, so every successful probe leaks a node reference. On a CM5, overlay removal after four bind cycles reports OF: ERROR: memory leak, expected refcount 1 instead of 5 Drop the reference after registration, where __mdiobus_register() has already taken its own for the lifetime of the bus. Fixes: 8a6631f1cece ("net: macb: avoid redundant lookup for "mdio" child node in MDIO setup") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260907210856.1673589-3-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 50ecfa80c660..77dec2d6e3fb 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -1164,6 +1164,8 @@ static int macb_mii_init(struct macb *bp) if (err) goto err_out_unregister_bus; + of_node_put(mdio_np); + return 0; err_out_unregister_bus: From 796aa0547557e63338657ed1c487906f9fac4c73 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 8 Sep 2026 17:53:20 -0600 Subject: [PATCH 1230/2366] io_uring/rw: end write accounting from ->ki_complete Commit b000145e9907 moved both the fsnotify calls and the write accounting out of the kiocb completion handler and into the io_req_rw_complete() task_work. However, only the fsnotify part actually needed to move as it may sleep. Ending the write accounting is just a percpu_up_read() on the superblock writers sem. Deferring it is a problem, because it makes dropping SB_FREEZE_WRITE protection depend on the ring owner getting to running task_work. But the task may be blocked in freeze_super(), causing it to never get to that: task io-wq worker -------------------------------------------------------------- io_write() io_kiocb_start_write() (takes sb_writers, hidden from lockdep by __sb_writers_release) write_iter() -> -EIOCBQUEUED ioctl(FS_IOC_SHUTDOWN) bdev_freeze() freeze_super() percpu_down_write() <- waits for the reader above io_write() kiocb_start_write() percpu_down_read() <- queued behind the writer io_complete_rw() queues io_req_rw_complete() <- never runs, task is in D state End the write from io_complete_rw() instead, and leave only the fsnotify calls in task_work. Reported-by: syzbot+2eb3d983669d3e49d4fa@syzkaller.appspotmail.com Cc: stable@vger.kernel.org Fixes: b000145e9907 ("io_uring/rw: defer fsnotify calls to task context") Signed-off-by: Jens Axboe --- io_uring/rw.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/io_uring/rw.c b/io_uring/rw.c index 95106dd1d7eb..3e22f294bdf2 100644 --- a/io_uring/rw.c +++ b/io_uring/rw.c @@ -517,20 +517,25 @@ static void io_req_end_write(struct io_kiocb *req) } } -/* - * Trigger the notifications after having done some IO, and finish the write - * accounting, if any. - */ +/* Trigger the notifications after having done some IO. */ +static void io_req_io_notify(struct io_kiocb *req) +{ + struct io_rw *rw = io_kiocb_to_cmd(req, struct io_rw); + + if (rw->kiocb.ki_flags & IOCB_WRITE) + fsnotify_modify(req->file); + else + fsnotify_access(req->file); +} + +/* Finish write accounting and notify, for inline completions only. */ static void io_req_io_end(struct io_kiocb *req) { struct io_rw *rw = io_kiocb_to_cmd(req, struct io_rw); - if (rw->kiocb.ki_flags & IOCB_WRITE) { + if (rw->kiocb.ki_flags & IOCB_WRITE) io_req_end_write(req); - fsnotify_modify(req->file); - } else { - fsnotify_access(req->file); - } + io_req_io_notify(req); } static void __io_complete_rw_common(struct io_kiocb *req, long res) @@ -563,7 +568,7 @@ void io_req_rw_complete(struct io_tw_req tw_req, io_tw_token_t tw) { struct io_kiocb *req = tw_req.req; - io_req_io_end(req); + io_req_io_notify(req); if (req->flags & (REQ_F_BUFFER_SELECTED|REQ_F_BUFFER_RING)) req->cqe.flags |= io_put_kbuf(req, max(req->cqe.res, 0), NULL); @@ -577,6 +582,10 @@ static void io_complete_rw(struct kiocb *kiocb, long res) struct io_rw *rw = container_of(kiocb, struct io_rw, kiocb); struct io_kiocb *req = cmd_to_io_kiocb(rw); + /* ring owner may block in freeze_super() before task_work runs */ + if (kiocb->ki_flags & IOCB_WRITE) + io_req_end_write(req); + __io_complete_rw_common(req, res); io_req_set_res(req, io_fixup_rw_res(req, res), 0); req->io_task_work.func = io_req_rw_complete; From dcbd1c054848848a1937ca0768ce2bdbc31ae621 Mon Sep 17 00:00:00 2001 From: Gabriel Krisman Bertazi Date: Wed, 2 Sep 2026 20:00:40 -0300 Subject: [PATCH 1231/2366] io_uring/net: let io_recv_buf_select return the length of the buffer region In preparation to using this field as an upper limit to truncation, return the size of the allocated region. Fixes: ae98dbf43d75 ("io_uring/kbuf: add support for incremental buffer consumption") Cc: stable@vger.kernel.org Signed-off-by: Gabriel Krisman Bertazi Link: https://patch.msgid.link/20260902230041.1320658-2-krisman@suse.de Signed-off-by: Jens Axboe --- io_uring/net.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/io_uring/net.c b/io_uring/net.c index fbe719d86c46..647156c9331a 100644 --- a/io_uring/net.c +++ b/io_uring/net.c @@ -1108,6 +1108,7 @@ static int io_recv_buf_select(struct io_kiocb *req, struct io_async_msghdr *kmsg struct io_br_sel *sel, unsigned int issue_flags) { struct io_sr_msg *sr = io_kiocb_to_cmd(req, struct io_sr_msg); + size_t len; int ret; /* @@ -1153,13 +1154,14 @@ static int io_recv_buf_select(struct io_kiocb *req, struct io_async_msghdr *kmsg /* special case 1 vec, can be a fast path */ if (ret == 1) { sr->buf = arg.iovs[0].iov_base; - sr->len = arg.iovs[0].iov_len; + len = sr->len = arg.iovs[0].iov_len; goto map_ubuf; } iov_iter_init(&kmsg->msg.msg_iter, ITER_DEST, arg.iovs, ret, - arg.out_len); + arg.out_len); + len = arg.out_len; } else { - size_t len = sel->val; + len = sel->val; *sel = io_buffer_select(req, &len, sr->buf_group, issue_flags); if (!sel->addr) @@ -1173,7 +1175,7 @@ static int io_recv_buf_select(struct io_kiocb *req, struct io_async_msghdr *kmsg return ret; } - return 0; + return len; } int io_recv(struct io_kiocb *req, unsigned int issue_flags) From 1f6de65e3314519ce462bfd3a1d29e918f97e4e5 Mon Sep 17 00:00:00 2001 From: Peter Griffin Date: Tue, 8 Sep 2026 12:37:27 +0000 Subject: [PATCH 1232/2366] opp: fix use after free in _update_opp_table_clk() dev_pm_opp_put_opp_table() frees the opp_table which is subsquently used by dev_err_probe(). This causes an Oops during boot on gs101-oriole. cpu cpu0: error 000000006b6b6b6b: Couldn't find clock Unable to handle kernel paging request at virtual address 006b6b6b6b6b6cd3 ... Hardware name: Oriole (DT) pstate: 00400005 (nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : _of_add_table_indexed+0x80/0xbb0 lr : _of_add_table_indexed+0x6c/0xbb0 ... Call trace: _of_add_table_indexed+0x80/0xbb0 (P) dev_pm_opp_of_cpumask_add_table+0x70/0x120 dt_cpufreq_probe+0x23c/0x480 platform_probe+0x64/0xb8 Fixes: 84f05af0975c9 ("opp: Use clk_get_optional() to avoid leaving opp_table->clk as an error pointer") Signed-off-by: Peter Griffin Reviewed-by: Tudor Ambarus [ Viresh: use return value of dev_err_probe() ] Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 1e3b80a1f88e..1da7d86241ae 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1581,6 +1581,8 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, struct opp_table *opp_table, bool getclk) { + int ret; + /* * Return early if we don't need to get clk or we have already done it * earlier. @@ -1607,9 +1609,9 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, opp_table->clk = clk_get_optional(dev, NULL); if (IS_ERR(opp_table->clk)) { + ret = dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); dev_pm_opp_put_opp_table(opp_table); - dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); - return ERR_CAST(opp_table->clk); + return ERR_PTR(ret); } if (opp_table->clk) From 6cc27d82196385fe06853319f74312a7d8019726 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 2 Sep 2026 19:08:08 +0100 Subject: [PATCH 1233/2366] mm/vma: correctly unaccount on mmap_prepare() failure __mmap_setup() accounts memory for relevant mappings via: security_vm_enough_memory_mm() -> __vm_enough_memory() -> vm_acct_memory() If __mmap_setup() fails, this indicates that this accounting did not take place, and thus it's appropriate for __mmap_region() to jump to abort_munmap. However if call_mmap_prepare() fails, it also jumps there and any accounted memory is not correctly unaccounted. Fix this by handling each error separately. Link: https://lore.kernel.org/20260902-fix-unaccount-mmap_prepare-v1-1-ea070189fdfb@kernel.org Fixes: c84bf6dd2b83 ("mm: introduce new .mmap_prepare() file callback") Signed-off-by: Lorenzo Stoakes (ARM) Cc: Jann Horn Cc: Liam R. Howlett Cc: Pedro Falcato Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- mm/vma.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mm/vma.c b/mm/vma.c index 35e7a64855fa..f29abb30956b 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -2859,10 +2859,12 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, map.check_ksm_early = can_set_ksm_flags_early(&map); error = __mmap_setup(&map, &desc, uf); - if (!error && have_mmap_prepare) - error = call_mmap_prepare(&map, &desc); if (error) goto abort_munmap; + if (have_mmap_prepare) + error = call_mmap_prepare(&map, &desc); + if (error) + goto unacct_error; if (map.check_ksm_early) update_ksm_flags(&map); From 932cfb25e7ce98d1f93895671ec186a3087e4f80 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Wed, 2 Sep 2026 15:37:59 +0800 Subject: [PATCH 1234/2366] mm/shrinker: fix bogus set_shrinker_bit() with cgroup.memory=nokmem With cgroup.memory=nokmem, shrinker_memcg_alloc() bails out early and never allocates an id, so shrinker->id keeps the 0 it got from the kzalloc() in shrinker_alloc(). __list_lru_init() then copies that 0 into lru->shrinker_id, where it looks like a valid bit index. Nothing calls expand_shrinker_info() on nokmem either, so shrinker_nr_max stays 0 and every memcg ends up with an empty map (map_nr_max == 0). deferred_split_folio() hands a real memcg to __list_lru_add() regardless of whether the lru is memcg aware, so the first THP queued in a cgroup does set_shrinker_bit(memcg, nid, 0) and trips the bounds check: WARNING: mm/shrinker.c:212 at set_shrinker_bit+0x7d/0x90, CPU#126 Call Trace: deferred_split_folio+0x18c/0x220 map_anon_folio_pmd_nopf+0xdd/0x130 map_anon_folio_pmd_pf+0x14/0xb0 do_huge_pmd_anonymous_page+0x1a1/0x620 __handle_mm_fault+0xea9/0x10d0 handle_mm_fault+0xe5/0x320 do_user_addr_fault+0x1cc/0x870 exc_page_fault+0x81/0x1b0 asm_exc_page_fault+0x27/0x30 Harmless, the WARN_ON_ONCE() is what keeps the out of bounds unit[] read from happening, but the id should not look valid in the first place. Clear it before returning. Two other spots could paper over this: drop the id in __list_lru_init() when nokmem turns memcg_aware off, or make deferred_split_folio() pass NULL like list_lru_add_obj() does. Both leave shrinker->id lying around for the next caller, so fix it where the id is handed out. Link: https://lore.kernel.org/20260902073800.305481-1-jiayuan.chen@linux.dev Fixes: fafaeceb89a5 ("mm: switch deferred split shrinker to list_lru") Signed-off-by: Jiayuan Chen Acked-by: Shakeel Butt Cc: Usama Arif Cc: Dave Chinner Cc: Johannes Weiner Cc: Kairui Song Cc: Muchun Song Cc: Roman Gushchin Cc: Signed-off-by: Andrew Morton --- mm/shrinker.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/shrinker.c b/mm/shrinker.c index a70aab124a0e..7ec2a9704f6f 100644 --- a/mm/shrinker.c +++ b/mm/shrinker.c @@ -227,6 +227,8 @@ static int shrinker_memcg_alloc(struct shrinker *shrinker) { int id; + shrinker->id = -1; + if (mem_cgroup_disabled()) return -ENOSYS; if (mem_cgroup_kmem_disabled() && !(shrinker->flags & SHRINKER_NONSLAB)) From 7891fbb9512f127826e1d5dbf380ee212bd15eb0 Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Tue, 1 Sep 2026 20:38:40 -0700 Subject: [PATCH 1235/2366] mm/folio: EXPORT_SYMBOL_FOR_KVM(lru_cache_drain_for_folio) To simplify independent development in the KVM and MM subsystems, now export to KVM the lru_cache_drain_for_folio() which MM added in 7.3-rc1. Link: https://lore.kernel.org/lkml/bd6c9c74-e374-a9d3-ba1f-8b6f430894fc@google.com/T/#u Link: https://lore.kernel.org/02876cea-5727-2ca4-bead-73659ea6fec4@google.com Signed-off-by: Ackerley Tng Signed-off-by: Hugh Dickins Acked-by: Vlastimil Babka (SUSE) Suggested-by: David Hildenbrand Reviewed-by: Fuad Tabba Reviewed-by: Binbin Wu Cc: Matthew Wilcox (Oracle) Cc: Sean Christopherson Signed-off-by: Andrew Morton --- mm/folio.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm/folio.c b/mm/folio.c index c02dcea9c03c..50a6dbe55998 100644 --- a/mm/folio.c +++ b/mm/folio.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "internal.h" #include "page_alloc.h" @@ -926,6 +927,7 @@ void lru_cache_drain_for_folio(const struct folio *folio, *drained = LRU_CACHE_DRAINED_ALL; } } +EXPORT_SYMBOL_FOR_KVM(lru_cache_drain_for_folio); atomic_t lru_disable_count = ATOMIC_INIT(0); From 504981db4f69bdd28054fb98c96a3a67f7248dde Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sat, 5 Sep 2026 16:47:27 +0900 Subject: [PATCH 1236/2366] dma-coherent: report a failed reserved memory assignment rmem_dma_device_init() drops the return value of dma_assign_coherent_memory() and always reports success. That call fails with -EBUSY when the device already has a coherent pool, and the file allows only "*one* such region of memory" per device. of_reserved_mem_device_init_by_idx() reads the zero as success. It logs "assigned reserved memory node" for a region that was not assigned and records the pairing, so of_reserved_mem_device_release() later runs rmem_dma_device_release() for it. That clears dev->dma_mem without looking at which region it was called for, dropping the pool the device did get and leaving it on ordinary memory. dma_declare_coherent_memory() checks the same call and releases the memory on failure, and rmem_swiotlb_device_init() propagates its own errors. Return the error here as well, so a device tree that assigns two pools to one device fails the probe instead of half working. Fixes: 7bfa5ab6fa1b ("drivers: dma-coherent: add initialization from device tree") Signed-off-by: Donggeun Yoo Link: https://lore.kernel.org/r/20260905074727.108029-1-donggeunyoo.kernel@gmail.com Signed-off-by: Marek Szyprowski --- kernel/dma/coherent.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/dma/coherent.c b/kernel/dma/coherent.c index 45bbae947f4b..4d0266893bcc 100644 --- a/kernel/dma/coherent.c +++ b/kernel/dma/coherent.c @@ -352,8 +352,7 @@ static int rmem_dma_device_init(struct reserved_mem *rmem, struct device *dev) min_not_zero(dev->coherent_dma_mask, dev->bus_dma_limit)) dev_warn(dev, "reserved memory is beyond device's set DMA address range\n"); - dma_assign_coherent_memory(dev, mem); - return 0; + return dma_assign_coherent_memory(dev, mem); } static void rmem_dma_device_release(struct reserved_mem *rmem, From b7d7914a9ae3097e63d113007e4fb44d33d515b1 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sat, 5 Sep 2026 17:42:10 +0900 Subject: [PATCH 1237/2366] swiotlb: use the adjusted address for the highmem page lookup swiotlb_bounce() reads the page frame number from the slot's recorded orig_addr, then advances orig_addr by tlb_offset to reach the address the caller asked about. The highmem branch mixes the two: the offset within the page comes from the adjusted address, the page from the value before it. Once the adjustment crosses a page boundary the pair no longer describes one location, and the whole copy lands one page below the intended one for a positive tlb_offset, one above for a negative one. DMA_FROM_DEVICE writes the device data over the wrong page and leaves the intended one stale, DMA_TO_DEVICE feeds the device from a page the mapping may not cover. Partial syncs through dma_sync_single_range_for_*() are what make tlb_offset non-zero. The branch test is picked the same way, so a slot recorded in lowmem can be adjusted into highmem and the lowmem path then hands a highmem address to phys_to_virt(). Take both from orig_addr once it is final and keep pfn in the branch that uses it. PhysHighMem() asks the question straight from the address, as dma-debug already does. Fixes: 5f89468e2f06 ("swiotlb: manipulate orig_addr when tlb_addr has offset") Cc: stable@vger.kernel.org Signed-off-by: Donggeun Yoo Reviewed-by: Michael Kelley Link: https://lore.kernel.org/r/20260905084210.148255-1-donggeunyoo.kernel@gmail.com Signed-off-by: Marek Szyprowski --- kernel/dma/swiotlb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c index ded7016a46a7..aa2f1c4588b9 100644 --- a/kernel/dma/swiotlb.c +++ b/kernel/dma/swiotlb.c @@ -1019,7 +1019,6 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size int index = (tlb_addr - mem->start) >> IO_TLB_SHIFT; phys_addr_t orig_addr = mem->slots[index].orig_addr; size_t alloc_size = mem->slots[index].alloc_size; - unsigned long pfn = PFN_DOWN(orig_addr); unsigned char *vaddr = mem->vaddr + tlb_addr - mem->start; int tlb_offset; @@ -1052,7 +1051,8 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size size = alloc_size; } - if (PageHighMem(pfn_to_page(pfn))) { + if (PhysHighMem(orig_addr)) { + unsigned long pfn = PFN_DOWN(orig_addr); unsigned int offset = orig_addr & ~PAGE_MASK; struct page *page; unsigned int sz = 0; From a1c7570cedd03372812a5b693732880867babbca Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 12:01:24 +0300 Subject: [PATCH 1238/2366] x86/mm/pat: Acquire init_mm write lock on collapse to avoid UAF x86 implements page attribute modification using its Change Page Attributes (CPA) mechanism. This tracks properties of ranges such as cache mode through x86 page attributes, and as part of that logic manipulates kernel page tables. Since commit: 41d88484c71c ("x86/mm/pat: restore large ROX pages after fragmentation") ranges of kernel page table entries can be collapsed into huge page table entries as part of this logic. As part of this collapse, it frees the page tables which the collapsed entries previously pointed to, and it does so without any relevant locks being held to preclude concurrent kernel page table walkers. The only way this code can be reached is if CPA_COLLAPSE is specified, and this is only set in set_memory_rox() via: set_memory_rox() -> change_page_attr_set_clr() -> cpa_flush() -> cpa_collapse_large_pages() Notable users of this are execmem and BPF when manipulating executable mappings. However, this is problematic for ptdump as it walks ranges it does not own and thus runs the risk of a use-after-free on page tables freed underneath it. In addition, concurrent CPA collapse operations are possible which can also cause races. Resolve the issue by acquiring the mmap write lock on init_mm across the whole operation. It is safe to acquire a sleeping lock as all the callers invoke set_memory_rox() from process context and in any case, change_page_attr_set_clr() calls vm_unmap_alias() which ultimately takes a mutex, disallowing atomic context here. Fixes: 41d88484c71c ("x86/mm/pat: restore large ROX pages after fragmentation") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Kiryl Shutsemau (Meta) Reviewed-by: David Hildenbrand (Arm) Reviewed-by: Dave Hansen Reviewed-by: Will Deacon Reviewed-by: David Carlier Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Cc:stable@vger.kernel.org Link: https://patch.msgid.link/20260813-cpa-fixes-v2-1-39b4ff90f91d@kernel.org --- arch/x86/mm/pat/set_memory.c | 15 ++++++++++++++- include/linux/mmap_lock.h | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index c38faf39ce15..4abddd763882 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -409,7 +410,7 @@ static void __cpa_flush_tlb(void *data) static int collapse_large_pages(unsigned long addr, struct list_head *pgtables); -static void cpa_collapse_large_pages(struct cpa_data *cpa) +static void __cpa_collapse_large_pages(struct cpa_data *cpa) { unsigned long start, addr, end; struct ptdesc *ptdesc, *tmp; @@ -443,6 +444,18 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa) } } +static void cpa_collapse_large_pages(struct cpa_data *cpa) +{ + /* + * Take the mmap write lock on init_mm to: + * - Avoid a use-after-free if raced by ptdump (which takes its own + * write lock on init_mm). + * - Serialise concurrent CPA walkers. + */ + scoped_guard(mmap_write_lock, &init_mm) + __cpa_collapse_large_pages(cpa); +} + static void cpa_flush(struct cpa_data *cpa, int cache) { unsigned int i; diff --git a/include/linux/mmap_lock.h b/include/linux/mmap_lock.h index bec0eab6ef03..b8a13b8d36a4 100644 --- a/include/linux/mmap_lock.h +++ b/include/linux/mmap_lock.h @@ -630,6 +630,8 @@ static inline void mmap_read_unlock(struct mm_struct *mm) DEFINE_GUARD(mmap_read_lock, struct mm_struct *, mmap_read_lock(_T), mmap_read_unlock(_T)) DEFINE_GUARD_COND(mmap_read_lock, _try, mmap_read_trylock(_T)) +DEFINE_GUARD(mmap_write_lock, struct mm_struct *, + mmap_write_lock(_T), mmap_write_unlock(_T)) static inline void mmap_read_unlock_non_owner(struct mm_struct *mm) { From d5d8b8662e6e5a565b47a0388640e88402f23274 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 12:01:25 +0300 Subject: [PATCH 1239/2366] x86/mm/pat: Acquire init_mm read lock on attribute changes to avoid UAF A previous commit protected against races between ptdump and CPA collapse, however one still exists between attribute changes and collapse as reported by Denis V. Lunev (linked). When an attribute change arises, a lockless page table walker obtains a PTE entry, which is later written to via set_pte_atomic(): ... -> change_page_attr_set_clr() -> __change_page_attr_set_clr() -> __change_page_attr() -> _lookup_address_cpa() -> lookup_address_in_pgd_attr() -> [ lockless page table walker ] -> set_pte_atomic() There is nothing preventing a concurrent CPA collapse which can free the PTE that was retrieved here, resulting in a use-after-free. With the mmap write lock taken on init_mm over CPA collapse, resolve this race by acquiring an mmap read lock on init_mm over __change_page_attr_set_clr(). This locks across the whole operation over which the walk and the PTE entry write occurs, solving the race. It is safe to do this here, as no spinlocks are held upon entry to __change_page_attr_set_clr(). However, the lock must not be held over an allocation, as allocation can trigger reclaim and shrinkers may call into CPA recursively, making deadlocks possible (init_mm -> ... -> fs_reclaim -> init_mm). A page table is allocated when a huge page needs to be split: -> change_page_attr_set_clr() -> __change_page_attr_set_clr() -> __change_page_attr() -> split_large_page() [ pagetable_alloc() ] -> __split_large_page() Avoid deadlocks by dropping the mmap lock across pagetable_alloc() in split_large_page() and track whether this is needed by adding a new 'init_mm_read_locked' flag to struct cpa_data. This is safe as __split_large_page() (called with locks re-established) revalidates that the page table entry is the same as it was prior to the locks being dropped and __change_page_attr() repeats the entire page table walk whenever a split occurs, so concurrent split and collapse are accounted for. Concurrent ptdump is also safe as the lock is only dropped over page table allocation during which time the page table has not yet been modified. The CPA_COLLAPSE flag is only set by set_memory_rox(), which exclusively operates upon vmalloc ranges, and on x86 only within the module mapping space. This is important, because some callers directly invoke __change_page_attr_set_clr(), bypassing this lock. However, none of these operate within the module mapping space. * cpa_process_alias() - a recursive helper called by __change_page_attr_set_clr(). * __set_memory_enc_pgtable() - operates on the direct mapping and (via __vmbus_establish_gpadl()) the vmalloc mapping space. * __set_pages_[n]p() - called by set_direct_map_[invalid, default, valid]_noflush(), __kernel_map_pages() - operates on the direct map. * kernel_[un]map_pages_in_pgd() - operates on EFI ranges. This work is based upon Denis V. Lunev's excellent analysis of the bug with gratitude. [ dhansen: move to imperative voice in changelog ] Fixes: 41d88484c71c ("x86/mm/pat: restore large ROX pages after fragmentation") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Link: https://lore.kernel.org/all/20260626163213.2284080-1-den@openvz.org/ Cc:stable@vger.kernel.org Link: https://patch.msgid.link/20260813-cpa-fixes-v2-2-39b4ff90f91d@kernel.org --- arch/x86/mm/pat/set_memory.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index 4abddd763882..cb5d6d6f71a4 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -50,7 +50,8 @@ struct cpa_data { unsigned int flags; unsigned int force_split : 1, force_static_prot : 1, - force_flush_all : 1; + force_flush_all : 1, + init_mm_read_locked : 1; struct page **pages; }; @@ -1240,7 +1241,11 @@ static int split_large_page(struct cpa_data *cpa, pte_t *kpte, struct ptdesc *ptdesc; spin_unlock(&cpa_lock); + if (cpa->init_mm_read_locked) + mmap_read_unlock(&init_mm); ptdesc = pagetable_alloc(GFP_KERNEL, 0); + if (cpa->init_mm_read_locked) + mmap_read_lock(&init_mm); spin_lock(&cpa_lock); if (!ptdesc) return -ENOMEM; @@ -2134,7 +2139,11 @@ static int change_page_attr_set_clr(unsigned long *addr, int numpages, cpa.curpage = 0; cpa.force_split = force_split; - ret = __change_page_attr_set_clr(&cpa, 1); + /* Avoid race with concurrent CPA collapse. */ + cpa.init_mm_read_locked = true; + scoped_guard(mmap_read_lock, &init_mm) + ret = __change_page_attr_set_clr(&cpa, 1); + cpa.init_mm_read_locked = false; /* * Check whether we really changed something: From 1587d3394e254639cc36516256031334095e6ef3 Mon Sep 17 00:00:00 2001 From: Pedro Falcato Date: Thu, 13 Aug 2026 12:01:26 +0300 Subject: [PATCH 1240/2366] x86/alternatives: Exclude text poking against change_page_attr() From time to time, the following BUG can be observed in the x86 alternatives patching code [0]: > kernel BUG at arch/x86/kernel/alternative.c:2576! > Oops: invalid opcode: 0000 [#1] SMP NOPTI > CPU: 0 UID: 0 PID: 355 Comm: (udev-worker) Not tainted 7.1.3-1-default #1 PREEMPT(full) openSUSE Tumbleweed 8c1795b03ec64f997e57a8ad38b1161e3b98da64 > Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS unknown 02/02/2022 > RIP: 0010:__text_poke+0x2aa/0x450 > Call Trace: > > smp_text_poke_batch_finish+0x2a7/0x320 > __static_call_transform+0xb7/0x220 > arch_static_call_transform+0x5b/0xb0 > __static_call_init+0xe9/0x270 > static_call_module_notify+0x11f/0x150 > notifier_call_chain+0x61/0xe0 > blocking_notifier_call_chain_robust+0x63/0xc0 > load_module+0x1c92/0x20c0 > init_module_from_file+0xd8/0x140 > idempotent_init_module+0x100/0x2f0 > __x64_sys_finit_module+0x71/0xe0 > do_syscall_64+0xe1/0x610 > entry_SYSCALL_64_after_hwframe+0x76/0x7e which matches the following BUG_ON() in alternative.c: /* * If something went wrong, crash and burn since recovery paths are not * implemented. */ BUG_ON(!pages[0] || (cross_page_boundary && !pages[1])); This can happen if vmalloc_to_page() fails, for any reason. Such can happen if text poking races with CPA, which can possibly result in the collapsing of page tables (or breaking of PMD hugepages). It is not a problem for most users of vmalloc_to_page() (they solely own the vmalloc'd range) but, when CONFIG_ARCH_HAS_EXECMEM_ROX=y, various modules own a single execmem vmalloc range, and can call set_memory_*() in parallel on it. This can happen to race against __text_poke and cause havoc in vmalloc_to_page(). Fix it by excluding against CPA using the init_mm mmap read lock. [ dhansen: Fix up SoB ordering. The actual code flow here was: Pedro=>Lorenzo=>Mike=>Me which is reflected in the SoB chain now. I *believe* Mike simply picked up Lorenzo's update to Pedro's post from the Link ] Fixes: 64f6a4e10c05 ("x86: re-enable EXECMEM_ROX support") Reported-by: Jiri Slaby Reported-by: Steffen Dirkwinkel Signed-off-by: Pedro Falcato Signed-off-by: Lorenzo Stoakes (ARM) Co-developed-by: Lorenzo Stoakes (ARM) Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Tested-by: Jiri Slaby Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Cc: stable@vger.kernel.org Link: https://bugzilla.opensuse.org/show_bug.cgi?id=1271202 [0] Link: https://lore.kernel.org/linux-mm/555ea1d43a12c30a8f1eaf10c899b3790d728f33.camel@dirkwinkel.cc/ Link: https://patch.msgid.link/20260813-cpa-fixes-v2-3-39b4ff90f91d@kernel.org --- arch/x86/kernel/alternative.c | 39 ++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index 91b1cdd16569..add62db3e82c 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include #include #include @@ -2372,6 +2375,38 @@ static void text_poke_memset(void *dst, const void *src, size_t len) typedef void text_poke_f(void *dst, const void *src, size_t len); +static void __poke_vmalloc_pages(struct page **pages, void *addr, + bool cross_page_boundary) +{ + pages[0] = vmalloc_to_page(addr); + if (cross_page_boundary) + pages[1] = vmalloc_to_page(addr + PAGE_SIZE); +} + +static void poke_vmalloc_pages(struct page **pages, void *addr, + bool cross_page_boundary) +{ + if (in_dbg_master()) { + /* + * If called from kgdb cannot sleep, but all other CPUs stopped + * anyway so safe to proceed without locks + */ + __poke_vmalloc_pages(pages, addr, cross_page_boundary); + } else { + /* + * execmem ROX ranges are shared between modules and can be + * collapsed to huge PMD entries, and this collapse can happen + * concurrently with a racing set_memory_rox(). + * + * Prevent vmalloc_to_page() from racing by acquiring an + * init_mm read lock which pairs with the init_mm write lock in + * cpa_collapse_large_pages(). + */ + guard(mmap_read_lock)(&init_mm); + __poke_vmalloc_pages(pages, addr, cross_page_boundary); + } +} + static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t len) { bool cross_page_boundary = offset_in_page(addr) + len > PAGE_SIZE; @@ -2389,9 +2424,7 @@ static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t l BUG_ON(!after_bootmem); if (!core_kernel_text((unsigned long)addr)) { - pages[0] = vmalloc_to_page(addr); - if (cross_page_boundary) - pages[1] = vmalloc_to_page(addr + PAGE_SIZE); + poke_vmalloc_pages(pages, addr, cross_page_boundary); } else { pages[0] = virt_to_page(addr); WARN_ON(!PageReserved(pages[0])); From 9e4a3ec3411bb6bb59e3c1f29b75609f1e87aac4 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 13 Aug 2026 12:01:27 +0300 Subject: [PATCH 1241/2366] x86/mm/pat: Allocate split page tables as kernel page tables A PTE is allocated directly without going through the standard page table allocation routines (such as pte_alloc_one_kernel()) when the CPA code splits a large page (__split_large_page()). This means the page table constructor is never called nor is the page table marked as a kernel page table. The former results in the folio associated with the page table not being marked as a page table (__pagetable_ctor() is never called thus neither is __folio_set_pgtable()) nor are statistics updated to reflect it (lruvec_stat_add_folio() is never called). The latter issue of failing to mark the page table as a kernel page table (ptdesc_set_kernel() is never called) is far more problematic. Since commit: 5ba2f0a15564 ("mm: introduce deferred freeing for kernel page tables") kernel page table freeing has been batched and since the subsequent commit: e37d5a2d60a3 ("iommu/sva: invalidate stale IOTLB entries for kernel address space") IOTLB cache entries for kernel page tables have been invalidated upon being freed. Since split page tables are freed without this invalidation, the IOTLB can contain stale entries for them. Resolve the issue by using the ordinary PTE allocation API at split time. This results in these kernel page tables invoking a page table constructor, and thus requires a page table destructor. Destructors are not always present, like for early allocated direct map page tables). Conditionally call pagetable_dtor_free() if the PG_table folio flag for the ptdesc is set, otherwise we free the page table via pagetable_free(). Regardless of which path is taken page tables marked as kernel page tables, which now includes split page tables, take the correct route through pagetable_free_kernel(). There is a user-visible side effect in that split page tables will appear in nr_page_table_pages in /proc/vmstat (as do other kernel page tables allocated after early boot), however this is a positive change. This issue started being markedly problematic after commit: 5ba2f0a15564 ("mm: introduce deferred freeing for kernel page tables") so choose this as the Fixes target. [ dhansen: rephrase in imperative mood ] Fixes: 5ba2f0a15564 ("mm: introduce deferred freeing for kernel page tables") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Acked-by: Vishal Moola Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260813-cpa-fixes-v2-4-39b4ff90f91d@kernel.org --- arch/x86/mm/pat/set_memory.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index cb5d6d6f71a4..4652487b5572 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -441,7 +441,15 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) list_for_each_entry_safe(ptdesc, tmp, &pgtables, pt_list) { list_del(&ptdesc->pt_list); - pagetable_free(ptdesc); + /* + * Only early alloc'd direct map should not be flagged PG_table + * here and those shouldn't be collapsed. However be abundantly + * cautious and handle the !PG_table case too. + */ + if (PageTable((ptdesc_page(ptdesc)))) + pagetable_dtor_free(ptdesc); + else + pagetable_free(ptdesc); } } @@ -1134,11 +1142,10 @@ static void split_set_pte(struct cpa_data *cpa, pte_t *pte, unsigned long pfn, static int __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, - struct ptdesc *ptdesc) + pte_t *pbase) { unsigned long lpaddr, lpinc, ref_pfn, pfn, pfninc = 1; - struct page *base = ptdesc_page(ptdesc); - pte_t *pbase = (pte_t *)page_address(base); + struct page *base = virt_to_page(pbase); unsigned int i, level; pgprot_t ref_prot; bool nx, rw; @@ -1238,20 +1245,20 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, static int split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address) { - struct ptdesc *ptdesc; + pte_t *pte; spin_unlock(&cpa_lock); if (cpa->init_mm_read_locked) mmap_read_unlock(&init_mm); - ptdesc = pagetable_alloc(GFP_KERNEL, 0); + pte = pte_alloc_one_kernel(&init_mm); if (cpa->init_mm_read_locked) mmap_read_lock(&init_mm); spin_lock(&cpa_lock); - if (!ptdesc) + if (!pte) return -ENOMEM; - if (__split_large_page(cpa, kpte, address, ptdesc)) - pagetable_free(ptdesc); + if (__split_large_page(cpa, kpte, address, pte)) + pte_free_kernel(&init_mm, pte); return 0; } From f7491d7c81db0e7c304a7bd757a76d2fbeaff80e Mon Sep 17 00:00:00 2001 From: Vernon Yang Date: Thu, 3 Sep 2026 11:16:08 +0800 Subject: [PATCH 1242/2366] x86/mm: Fix user-space data loss with MADV_FREE and THP Some of users of Polars (a data analytics library) have lost production data from this bug. They seem to have just the right combination of huge pages, MADV_FREE and heavy reclaim pressure. pmd_modify() masks the old value with (_HPAGE_CHG_MASK & ~_PAGE_DIRTY), silently discarding the hardware dirty bit. The subsequent pmd_mksaveddirty() call is supposed to transfer _PAGE_DIRTY into _PAGE_SAVED_DIRTY when write-protecting, but the dirty bit was already stripped from the value, so there is nothing left to transfer. Contrast with pte_modify(), which keeps _PAGE_DIRTY_BITS in its mask, and pud_modify(), which keeps _HPAGE_CHG_MASK untouched: pmd_modify() is the odd one out. Any pmd_modify() on a writable, dirty PMD loses the dirty state. One visible consequence is data loss with MADV_FREE on PMD-mapped THP: memset(buf, 0x5A, size); // PMD-mapped THP, PMD dirty madvise(buf, size, MADV_FREE); // PMD cleaned but left writable, // folio marked lazyfree memset(buf, 0x5A, size); // hardware sets _PAGE_DIRTY again mprotect(buf, size, PROT_READ); // pmd_modify() drops the dirty bit mprotect(buf, size, PROT_READ|PROT_WRITE); // ... memory pressure ... Reclaim (e.g. under memcg pressure) then finds the lazyfree folio with no dirty bit set anywhere and frees it in __discard_anon_folio_pmd_locked(), even though the data was rewritten after MADV_FREE; subsequent reads fault in fresh zero pages. NUMA hinting alone can trigger the same loss, as do_huge_pmd_numa_page() restores the PMD through pmd_modify() as well. PMD-mapped file THPs are affected too: mprotect()/NUMA hinting dropping the dirty bit means rewritten data is never written back. Fix it by keeping _PAGE_DIRTY in the preserved mask, exactly like pte_modify() and pud_modify() do. The existing pmd_mksaveddirty()/pmd_clear_saveddirty() pair then performs the hardware-dirty <-> saved-dirty transition based on the write bit, preserving the shadow-stack encoding rules. Fixes: bb3aadf7d446 ("x86/mm: Start actually marking _PAGE_SAVED_DIRTY") Closes: https://lore.kernel.org/r/CAJxLxMUGu1-L+O_nAONOwOXnS=cNbNApCWqdthRjd76LThtSPg@mail.gmail.com/ Reported-by: Orson Peters Signed-off-by: Vernon Yang Signed-off-by: Dave Hansen Signed-off-by: Ingo Molnar Reviewed-by: Rick Edgecombe Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260903031608.1194238-1-vernon2gm@gmail.com --- arch/x86/include/asm/pgtable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h index d5f4917c1edc..d551120a7c88 100644 --- a/arch/x86/include/asm/pgtable.h +++ b/arch/x86/include/asm/pgtable.h @@ -806,7 +806,7 @@ static inline pmd_t pmd_modify(pmd_t pmd, pgprot_t newprot) pmdval_t val = pmd_val(pmd), oldval = val; pmd_t pmd_result; - val &= (_HPAGE_CHG_MASK & ~_PAGE_DIRTY); + val &= _HPAGE_CHG_MASK; val |= check_pgprot(newprot) & ~_HPAGE_CHG_MASK; val = flip_protnone_guard(oldval, val, PHYSICAL_PMD_PAGE_MASK); From e780259b54e618ceb4763fbc21314acf3565e813 Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Mon, 7 Sep 2026 23:26:32 +0200 Subject: [PATCH 1243/2366] exec: do_close_on_exec() before taking exec_update_lock do_close_on_exec() currently happens while holding the exec_update_lock, which is used in a lot of places that access process state to synchronize access checks. I recently added another such use of exec_update_lock, causing a regression. do_close_on_exec() can block waiting for a reply from a filesystem. That means a hung filesystem can block codepaths that use exec_update_lock; and it also means that a FUSE filesystem which attempts to inspect the calling process can deadlock. To avoid such problems, move do_close_on_exec() before the exec_update_lock is taken, but after the FD table has been copied if necessary. I have looked through all the calls between the old and new position of the do_close_on_exec() call; there seems to be no file descriptor table access in between. Reported-by: Benjamin Peterson Closes: https://lore.kernel.org/r/f5e8166a-88be-46c5-8939-1e5227ffe4c2@app.fastmail.com Fixes: 6650527444da ("proc: protect ptrace_may_access() with exec_update_lock (part 1)") Cc: stable@vger.kernel.org Signed-off-by: Jann Horn Link: https://patch.msgid.link/20260907-cloexec-before-exec-update-lock-v1-1-8018c201a7df@google.com Tested-by: Benjamin Peterson Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/exec.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 263b1f67f1f8..f419a512de63 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1161,6 +1161,20 @@ int begin_new_exec(struct linux_binprm * bprm) if (retval) goto out; + /* + * We have to apply CLOEXEC before we change whether the process is + * dumpable (in setup_new_exec) to avoid a race with a process in userspace + * trying to access the should-be-closed file descriptors of a process + * undergoing exec(2). + * + * This can block on filesystem ->flush() handlers, including waiting + * for FUSE daemons, so do it before exec_mmap takes the + * exec_update_lock. + * This must happen after the point of no return, and after unsharing + * the FD table. + */ + do_close_on_exec(me->files); + /* * Must be called _before_ exec_mmap() as bprm->mm is * not visible until then. Doing it here also ensures @@ -1211,14 +1225,6 @@ int begin_new_exec(struct linux_binprm * bprm) clear_syscall_work_syscall_user_dispatch(me); - /* - * We have to apply CLOEXEC before we change whether the process is - * dumpable (in setup_new_exec) to avoid a race with a process in userspace - * trying to access the should-be-closed file descriptors of a process - * undergoing exec(2). - */ - do_close_on_exec(me->files); - if (bprm->secureexec) { /* Make sure parent cannot signal privileged process. */ me->pdeath_signal = 0; From 56ea4e86832d8abe8930394473566c194d189f85 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Mon, 7 Sep 2026 16:22:17 +0200 Subject: [PATCH 1244/2366] nstree: check listing permission before taking a namespace reference legitimize_ns() takes a reference on the candidate namespace before may_list_ns() has decided whether the caller may see it. The __free(ns_put) cleanup on the denied path can drop the last reference to a mount namespace while we still hold the rcu read lock, and put_mnt_ns() may sleep there. This is the same problem commit 2ec2aff3c8e2 ("ns: make sure reference are dropped outside of rcu lock") fixed for the put_user() path. Neither ns_requested() nor may_list_ns() needs a reference, both only look at the namespace type and at the caller's own namespaces, so do the checks first and take the reference last. Splat: Voluntary context switch within RCU read-side critical section! WARNING: kernel/rcu/tree_plugin.h:332 at rcu_note_context_switch+0x238/0x2a0, CPU#5: a/3442 CPU: 5 UID: 1000 PID: 3442 Comm: a Not tainted 7.0.0-30-generic #30-Ubuntu PREEMPT(lazy) RIP: 0010:rcu_note_context_switch+0x238/0x2a0 Call Trace: __schedule+0xcf/0x650 schedule+0x27/0x90 schedule_preempt_disabled+0x15/0x30 __mutex_lock.constprop.0+0x550/0xaf0 __mutex_lock_slowpath+0x13/0x20 mutex_lock+0x3b/0x50 exp_funnel_lock+0xb2/0x260 synchronize_rcu_expedited+0xe7/0x220 namespace_unlock+0x26a/0x320 put_mnt_ns+0xd3/0x120 mntns_put+0xe/0x20 do_listns+0x13e/0x560 __do_sys_listns+0x126/0x2d0 __x64_sys_listns+0x20/0x30 x64_sys_call+0x2366/0x2390 do_syscall_64+0x105/0x5a0 entry_SYSCALL_64_after_hwframe+0x76/0x7e Fixes: 76b6f5dfb3fd ("nstree: add listns()") Signed-off-by: Norbert Szetei Link: https://patch.msgid.link/ABA32239-733B-438C-B95A-B13ED69FF0F3@doyensec.com Reviewed-by: Bradley Morgan Signed-off-by: Christian Brauner (Amutable) --- kernel/nstree.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/kernel/nstree.c b/kernel/nstree.c index 6d12e5900ac0..831f279d174a 100644 --- a/kernel/nstree.c +++ b/kernel/nstree.c @@ -533,19 +533,13 @@ DEFINE_FREE(ns_put, struct ns_common *, if (!IS_ERR_OR_NULL(_T)) ns_put(_T)) static inline struct ns_common *__must_check legitimize_ns(const struct klistns *kls, struct ns_common *candidate) { - struct ns_common *ns __free(ns_put) = NULL; - if (!ns_requested(kls, candidate)) return NULL; - ns = ns_get_unless_inactive(candidate); - if (!ns) + if (!may_list_ns(kls, candidate)) return NULL; - if (!may_list_ns(kls, ns)) - return NULL; - - return no_free_ptr(ns); + return ns_get_unless_inactive(candidate); } static ssize_t do_listns_userns(struct klistns *kls) From ed761e0693950fcb4f6b0f60387a3961b972adf3 Mon Sep 17 00:00:00 2001 From: Leonardo Costa Date: Mon, 6 Jul 2026 10:24:17 -0300 Subject: [PATCH 1245/2366] drm/bridge: tc358768: Enforce input bus flags via atomic_check The tc358768 declares static bridge timings requiring pixel data to be sampled on the positive clock edge. However, the DRM core default propagation simply copies the output-side bus flags, coming from the next bridge, connector or panel, to the input side. If the propagated flags are incompatible with the bridge ones, the data is wrongly sampled, typically resulting in visual artifacts on the panel. Implement the atomic_check hook, replacing the mutually exclusive mode_fixup, and set the bridge state input bus flags to the ones required by the tc358768. The sync polarity defaulting previously done in mode_fixup is carried over into atomic_check unchanged. Fixes: ff1ca6397b1d ("drm/bridge: Add tc358768 driver") Cc: stable@vger.kernel.org Signed-off-by: Leonardo Costa Reviewed-by: Francesco Dolcini Reviewed-by: Swamil Jain Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260706132440.1594239-1-leoreis.costa@gmail.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/tc358768.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c index d1fc6af37cc5..19b43efcf93f 100644 --- a/drivers/gpu/drm/bridge/tc358768.c +++ b/drivers/gpu/drm/bridge/tc358768.c @@ -1263,10 +1263,13 @@ tc358768_atomic_get_input_bus_fmts(struct drm_bridge *bridge, return input_fmts; } -static bool tc358768_mode_fixup(struct drm_bridge *bridge, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) +static int tc358768_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) { + struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + /* Default to positive sync */ if (!(adjusted_mode->flags & @@ -1277,13 +1280,15 @@ static bool tc358768_mode_fixup(struct drm_bridge *bridge, (DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC))) adjusted_mode->flags |= DRM_MODE_FLAG_PVSYNC; - return true; + bridge_state->input_bus_cfg.flags = bridge->timings->input_bus_flags; + + return 0; } static const struct drm_bridge_funcs tc358768_bridge_funcs = { .attach = tc358768_bridge_attach, .mode_valid = tc358768_bridge_mode_valid, - .mode_fixup = tc358768_mode_fixup, + .atomic_check = tc358768_bridge_atomic_check, .atomic_pre_enable = tc358768_bridge_atomic_pre_enable, .atomic_enable = tc358768_bridge_atomic_enable, .atomic_disable = tc358768_bridge_atomic_disable, From 92c6a8d6470f7e7aa86c1f144818d8fa4fcbd5aa Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 21:01:24 +0900 Subject: [PATCH 1246/2366] dma-mapping: don't trace the DMA address when the allocation fails dma_alloc_attrs() passes *dma_handle to trace_dma_alloc() without checking whether the allocation succeeded. No backend writes it on failure: dma_direct_alloc(), iommu_dma_alloc() and the dma_map_ops instances assign it only on the path that returns a buffer. Callers usually pass an uninitialized automatic variable, so a failed allocation records whatever the stack held, next to the virt_addr=(null) that marks the record as an error: dma_alloc: dmatrace dir=BIDIRECTIONAL dma_addr=deadbeefdeadbeef size=1099511627776 virt_addr=0000000000000000 The device coherent pool path reaches the same call: a non-zero return from dma_alloc_from_dev_coherent() means the request was handled, not that it succeeded, so cpu_addr is NULL and dma_handle is untouched once the pool runs out. For an allocation event a NULL virt_addr already means the request failed, so the address field carries nothing. Report 0 for it in the event class rather than at each call site, which covers dma_alloc_pages() and dma_alloc_sgt_err() as well. Fixes: 038eb433dc14 ("dma-mapping: add tracing for dma-mapping API calls") Fixes: 68b6dbf1f441 ("dma-mapping: trace more error paths") Suggested-by: Marek Szyprowski Signed-off-by: Donggeun Yoo Link: https://lore.kernel.org/r/20260907120124.603373-1-donggeunyoo.kernel@gmail.com Reviewed-by: Sean Anderson Signed-off-by: Marek Szyprowski --- include/trace/events/dma.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/trace/events/dma.h b/include/trace/events/dma.h index 9df02c1511de..b06d8f99922d 100644 --- a/include/trace/events/dma.h +++ b/include/trace/events/dma.h @@ -134,7 +134,7 @@ DECLARE_EVENT_CLASS(dma_alloc_class, TP_fast_assign( __assign_str(device); __entry->virt_addr = virt_addr; - __entry->dma_addr = dma_addr; + __entry->dma_addr = virt_addr ? dma_addr : 0; __entry->size = size; __entry->flags = flags; __entry->dir = dir; From 53823e25793a97d07e6e98e0904bbf74cac8bc76 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sat, 22 Aug 2026 02:58:47 +0800 Subject: [PATCH 1247/2366] mmc: sdio_uart: fix xmit_fifo leak when the port table is full sdio_uart_add_port() allocates the transmit fifo before claiming a slot in sdio_uart_table[]. When all UART_NR slots are taken, it returns -EBUSY with the fifo still allocated, but the probe error path only kfree()s the port, leaking the transmit fifo. Free the fifo in the failure path of sdio_uart_add_port() itself so the function retains nothing on error. Fixes: 8b197a5ce7a7 ("sdio_uart: Use kfifo instead of the messy circ stuff") Signed-off-by: Felix Gu Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/core/sdio_uart.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/core/sdio_uart.c b/drivers/mmc/core/sdio_uart.c index 7fd5dedf3ac0..705163c42972 100644 --- a/drivers/mmc/core/sdio_uart.c +++ b/drivers/mmc/core/sdio_uart.c @@ -104,6 +104,9 @@ static int sdio_uart_add_port(struct sdio_uart_port *port) } spin_unlock(&sdio_uart_table_lock); + if (ret) + kfifo_free(&port->xmit_fifo); + return ret; } From 424103642d009f5a7dc33300a49d5606d1bf4fb5 Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:03 +0000 Subject: [PATCH 1248/2366] dmaengine: add dma_device_get() helper Add dma_device_get() helper to match dma_device_put() to make code symmetric. It wraps open-coded kref_get_unless_zero() and asserts that dma_list_mutex is held, matching its put counterpart. No functional change intended. Suggested-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-1-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index 6ffd8bd82154..6b8af8607e5c 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -433,6 +433,12 @@ static void dma_device_release(struct kref *ref) device->device_release(device); } +static int __must_check dma_device_get(struct dma_device *device) +{ + lockdep_assert_held(&dma_list_mutex); + return kref_get_unless_zero(&device->ref); +} + static void dma_device_put(struct dma_device *device) { lockdep_assert_held(&dma_list_mutex); @@ -460,8 +466,7 @@ static int dma_chan_get(struct dma_chan *chan) if (!try_module_get(owner)) return -ENODEV; - ret = kref_get_unless_zero(&chan->device->ref); - if (!ret) { + if (!dma_device_get(chan->device)) { ret = -ENODEV; goto module_put_out; } From 44dab659064eb5c10adb0306510eebe848ed592d Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:04 +0000 Subject: [PATCH 1249/2366] dmaengine: Fix device kref underflow in dma_chan_put() dma_chan_get() takes chan->device->ref only on the slow path: /* no kref on fast path */ if (chan->client_count) { __module_get(owner); chan->client_count++; return 0; } if (!try_module_get(owner)) return -ENODEV; if (!dma_device_get(chan->device)) { // calls kref_get_unless_zero() dma_chan_put() drops the ref unconditionally, so every fast-path get/put pair drops one extra device reference. The bug fires when two conditions hold together: a non-private provider has a persistent client holding chan->client_count > 0 and another client cycles dmaengine_get()/dmaengine_put(). When the kref hits zero, the subsequent dma_find_channel() returns NULL even though the provider module is still loaded. Fix this by dropping device->ref only on the last put, matching the single slow-path get. Fixes: 8ad342a86359 ("dmaengine: Add reference counting to dma_device struct") Reviewed-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-2-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index 6b8af8607e5c..e380b801df73 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -520,7 +520,9 @@ static void dma_chan_put(struct dma_chan *chan) chan->route_data = NULL; } - dma_device_put(chan->device); + /* This channel is not in use anymore, drop the device ref */ + if (!chan->client_count) + dma_device_put(chan->device); module_put(dma_chan_to_owner(chan)); } From e873c74132f0c5f1452816cd9bb26208f0bba1e1 Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:05 +0000 Subject: [PATCH 1250/2366] dmaengine: fix use-after-free in dma_chan_put() and dma_release_channel() When dma_device_put() drops the last reference on chan->device->ref, dma_device_release() runs and may free the dma_device along with its channels. dma_chan_put() then still reads chan->device->owner via dma_chan_to_owner() for the trailing module_put(). KASAN catches it: slab-use-after-free in dma_chan_put+0x3e6/0x4c0 Read of size 8 by task insmod/6319 Freed by task 6319: kfree+0x225/0x470 dma_chan_put+0x395/0x4c0 dmaengine_put+0xf8/0x160 Cache the module owner in dma_chan_put() before the put so the trailing module_put() does not need chan->device. Fixes: 8ad342a86359 ("dmaengine: Add reference counting to dma_device struct") Suggested-by: Sashiko Link: https://sashiko.dev/#/patchset/20260518-dmaengine-kref-fix-v1-1-4d6125048fb7@amd.com Reviewed-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-3-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index e380b801df73..872ae0d2ed95 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -500,10 +500,13 @@ static int dma_chan_get(struct dma_chan *chan) */ static void dma_chan_put(struct dma_chan *chan) { + struct module *owner; + /* This channel is not in use, bail out */ if (!chan->client_count) return; + owner = dma_chan_to_owner(chan); chan->client_count--; /* This channel is not in use anymore, free it */ @@ -523,7 +526,7 @@ static void dma_chan_put(struct dma_chan *chan) /* This channel is not in use anymore, drop the device ref */ if (!chan->client_count) dma_device_put(chan->device); - module_put(dma_chan_to_owner(chan)); + module_put(owner); } enum dma_status dma_sync_wait(struct dma_chan *chan, dma_cookie_t cookie) From dc750422170a563c7a81f6e49d36bb02c62ae37f Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:06 +0000 Subject: [PATCH 1251/2366] dmaengine: wait for RCU readers before releasing dma_device dma_issue_pending_all() walks the dma_device_list with list_for_each_entry_rcu() under rcu_read_lock(). dma_device_release() unlinks the device with list_del_rcu() and then calls device->device_release() (which in many drivers, such as plx_dma.c, directly calls kfree()). Because there is no grace period between unlinking the device and freeing it, concurrent RCU readers in dma_issue_pending_all() can access the device after it has been freed. The lockless walk originally relied on clients holding a dmaengine reference to pin the provider module, and therefore the device, for as long as they might traverse the list. Commit 8ad342a86359 ("dmaengine: Add reference counting to dma_device struct") decoupled the dma_device lifetime from the module reference, so the device can now be released while a reader is still walking the list. Add synchronize_rcu() before the device is freed, so RCU readers are guaranteed to have finished. Keep it unconditional: providers that do not implement device_release() free the device themselves once dma_async_device_unregister() returns. This call will delay for a grace period with dma_list_mutex held, which is safe and only teardown path is delayed. Fixes: 2ba05622b8b1 ("dmaengine: provide a common 'issue_pending_all' implementation") Suggested-by: Sashiko Link: https://sashiko.dev/#/patchset/20260526-dmaengine-kref-fix-v2-0-3df60afac01d@amd.com Reviewed-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-4-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index 872ae0d2ed95..c71763047126 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c @@ -428,6 +428,7 @@ static void dma_device_release(struct kref *ref) list_del_rcu(&device->global_node); dma_channel_rebalance(); + synchronize_rcu(); if (device->device_release) device->device_release(device); From dab68a74e90b8e07f08ed9deaa5884857a3cfe89 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:03 +0200 Subject: [PATCH 1252/2366] wifi: cfg80211: don't free driver-owned scan requests When an interface goes down while a scan is running, cfg80211 completes the scan towards userspace and frees the scan request. However, the driver can be convinced that it owns the request, since the cancellation is (intended to be) asynchronous. The WARN_ON() in the netdev notifier was meant to catch this, but it's not actually avoidable, so it triggers and we get a UAF in scan_done(). There doesn't seem to be a great way around it, so just track that the driver is still convinced it owns the request, and then just free it on completion if it was already cancelled. Also remove the warnings since they can trigger in the intended architecture. Assisted-by: LLM Fixes: 4a58e7c38443 ("cfg80211: don't "leak" uncompleted scans") Reported-by: syzbot+189dcafc06865d38178d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=189dcafc06865d38178d Link: https://patch.msgid.link/20260904165614.375e543228b1.I03cbb5a54cb02d6bba5034286af1ed73aba134d1@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 11 +++++------ net/wireless/core.h | 10 ++++++++++ net/wireless/rdev-ops.h | 3 +++ net/wireless/scan.c | 31 +++++++++++++++++++++++++++++-- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index d13310fef691..8bb2cbd66b48 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -244,9 +244,8 @@ void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev, rdev->opencount--; if (rdev->scan_req && rdev->scan_req->req.wdev == wdev) { - if (WARN_ON(!rdev->scan_req->notified && - (!rdev->int_scan_req || - !rdev->int_scan_req->notified))) + if (!rdev->scan_req->notified && + (!rdev->int_scan_req || !rdev->int_scan_req->notified)) rdev->scan_req->info.aborted = true; ___cfg80211_scan_done(rdev, false); } @@ -1758,9 +1757,9 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb, wiphy_lock(&rdev->wiphy); cfg80211_update_iface_num(rdev, wdev->iftype, -1); if (rdev->scan_req && rdev->scan_req->req.wdev == wdev) { - if (WARN_ON(!rdev->scan_req->notified && - (!rdev->int_scan_req || - !rdev->int_scan_req->notified))) + if (!rdev->scan_req->notified && + (!rdev->int_scan_req || + !rdev->int_scan_req->notified)) rdev->scan_req->info.aborted = true; ___cfg80211_scan_done(rdev, false); } diff --git a/net/wireless/core.h b/net/wireless/core.h index b4610f6685dc..a0c2b6ebe31f 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -24,6 +24,16 @@ struct cfg80211_scan_request_int { struct cfg80211_scan_info info; bool notified; + /* + * set while the request is handed to the driver, i.e. between + * rdev_scan() and cfg80211_scan_done() + */ + bool driver_owns; + /* + * set when cfg80211 is done with the request but the driver still + * owns it, so that cfg80211_scan_done() knows to just free it + */ + bool stale; /* must be last - variable members */ struct cfg80211_scan_request req; }; diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index 46849fe8d0b3..adcfd0278da3 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -464,7 +464,10 @@ static inline int rdev_scan(struct cfg80211_registered_device *rdev, return -EINVAL; trace_rdev_scan(&rdev->wiphy, request); + request->driver_owns = true; ret = rdev->ops->scan(&rdev->wiphy, &request->req); + if (ret) + request->driver_owns = false; trace_rdev_return_int(&rdev->wiphy, ret); return ret; } diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 9e934b185e34..4fe114f6aee3 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -1114,6 +1114,21 @@ int cfg80211_scan(struct cfg80211_registered_device *rdev) return 0; } +/* + * Release the scan request, but free it only if the driver is also done, + * e.g. mac80211 may cancel it asynchronously and still use it. + */ +static void cfg80211_put_scan_req(struct cfg80211_scan_request_int *req) +{ + if (!req) + return; + + if (req->driver_owns) + req->stale = true; + else + kfree(req); +} + void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool send_message) { @@ -1173,10 +1188,10 @@ void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, dev_put(wdev->netdev); - kfree(rdev->int_scan_req); + cfg80211_put_scan_req(rdev->int_scan_req); rdev->int_scan_req = NULL; - kfree(rdev->scan_req); + cfg80211_put_scan_req(rdev->scan_req); rdev->scan_req = NULL; if (!send_message) @@ -1199,6 +1214,18 @@ void cfg80211_scan_done(struct cfg80211_scan_request *request, struct cfg80211_scan_info old_info = intreq->info; trace_cfg80211_scan_done(intreq, info); + + intreq->driver_owns = false; + + if (intreq->stale) { + /* + * The scan is already completed as far as we're concerned, + * it was just kept around for the driver - done now, free it. + */ + kfree(intreq); + return; + } + WARN_ON(intreq != rdev->scan_req && intreq != rdev->int_scan_req); From 955d86e5f3b95b731991fdb84966c50b16314629 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Sun, 9 Aug 2026 21:36:15 +0000 Subject: [PATCH 1253/2366] arm64: hibernate: pass HVC_SET_VECTORS args to the resume hvc swsusp_arch_suspend_exit() reinstalls the restored kernel's hyp stub vectors with an hvc, but never passes the arguments. x0 is not set to HVC_SET_VECTORS and x1 is not set to the vector address, so the stub dispatch falls through and returns without writing vbar_el2. EL2 is left pointing at the trans_pgd copy of the vectors, a page that swsusp_free() releases right after resume. Set the arguments up the same way __hyp_set_vectors() does. Without this fix, Vladimir was able to trigger a hang when resuming from hibernation with CONFIG_PAGE_POISONING=y and page_poison=on. Fixes: 788bfdd97434 ("arm64: trans_pgd: hibernate: Add trans_pgd_copy_el2_vectors") Cc: stable@vger.kernel.org Signed-off-by: Bradley Morgan Reviewed-by: Vladimir Murzin Tested-by: Vladimir Murzin Acked-by: Mark Rutland Signed-off-by: Will Deacon --- arch/arm64/kernel/hibernate-asm.S | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/kernel/hibernate-asm.S b/arch/arm64/kernel/hibernate-asm.S index 0e1d9c3c6a93..2baefe7a82d3 100644 --- a/arch/arm64/kernel/hibernate-asm.S +++ b/arch/arm64/kernel/hibernate-asm.S @@ -89,6 +89,8 @@ alternative_insn "dc cvau, x4", "dc civac, x4", ARM64_WORKAROUND_CLEAN_CACHE isb cbz x24, 3f /* Do we need to re-initialise EL2? */ + mov x1, x24 + mov x0, #HVC_SET_VECTORS hvc #0 3: ret SYM_CODE_END(swsusp_arch_suspend_exit) From 068843ed0902c552a13860c5ec6b2ca65b57a065 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:04 +0200 Subject: [PATCH 1254/2366] wifi: cfg80211: only group hidden BSSes with beacon entries When a probe response for an unknown BSS comes in, __cfg80211_bss_update() looks for an existing entry with the same BSSID and a hidden (zero-length or NUL-filled) SSID, and if it finds one it groups them, using the beacon IEs from the existing entry. But that could find another entry without a beacon, if it was also from a probe response (with SSID), so there's a group without beacon elements. If a beacon with a hidden SSID for that BSSID arrives later, cfg80211_combine_bsses() goes looking for the probe response entries that belong to it - i.e. entries with the same BSSID and channel that have no beacon IEs - and finds those two. They are already grouped with each other, so it hits its WARN_ON_ONCE(bss->pub.hidden_beacon_bss) WARN_ON_ONCE(!list_empty(&bss->hidden_list)) which are there because an entry without beacon elements is not supposed to be part of a group yet. Only combine entries when a beacon was already received, ones that are kept separate will be combined when a beacon arrives. Assisted-by: LLM Fixes: 4593c4cbe1c9 ("cfg80211: fix BSS list hidden SSID lookup") Reported-by: syzbot+1a797e1c81be78a2ace7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1a797e1c81be78a2ace7 Link: https://patch.msgid.link/20260904165614.bcfa64715745.Iad740347c86de56d4ff4f96a95f3c3afc47c42de@changeid Signed-off-by: Johannes Berg --- net/wireless/scan.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 4fe114f6aee3..604b10ef0f94 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -2077,6 +2077,13 @@ __cfg80211_bss_update(struct cfg80211_registered_device *rdev, if (!hidden) hidden = rb_find_bss(rdev, tmp, BSS_CMP_HIDE_NUL); + /* + * Only group with an entry with beacon data, otherwise + * beacon data can never be filled/updated. + */ + if (hidden && + !rcu_access_pointer(hidden->pub.beacon_ies)) + hidden = NULL; if (hidden) { new->pub.hidden_beacon_bss = &hidden->pub; list_add(&new->hidden_list, From b377e1000d963e7182a987082b4b06580bd7ac84 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:05 +0200 Subject: [PATCH 1255/2366] wifi: cfg80211: don't filter by BSS type when removing stale entries When an assoc AP switches to a channel that already has a BSS entry, cfg80211_update_assoc_bss_entry() removes that entry before rehashing the real one, since the two would otherwise collide in the BSS rbtree. The lookup for that entry also required it to match the connection's BSS type, so an entry advertising e.g. the IBSS capability bit was left in place, and the following cfg80211_rehash_bss() then ran into it: WARN_ON(!cmp) Changing the type shouldn't really happen, but can be triggered by a rogue AP/device, so drop the check and remove any entries matching the comparison. Assisted-by: LLM Fixes: 0afd425b1b64 ("cfg80211: fix duplicated scan entries after channel switch") Reported-by: syzbot+dc6f4dce0d707900cdea@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=dc6f4dce0d707900cdea Link: https://patch.msgid.link/20260904165614.1f05dae1c546.Ib52d57b57caa912efee020f9d4a033a5160617ce@changeid Signed-off-by: Johannes Berg --- net/wireless/scan.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 604b10ef0f94..caa9c6495f20 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -3502,11 +3502,6 @@ void cfg80211_update_assoc_bss_entry(struct wireless_dev *wdev, cbss->pub.channel = chan; list_for_each_entry(bss, &rdev->bss_list, list) { - if (!cfg80211_bss_type_match(bss->pub.capability, - bss->pub.channel->band, - wdev->conn_bss_type)) - continue; - if (bss == cbss) continue; From 708f9d43d6a2eb9c6b83fe62af628de9dffd9314 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:06 +0200 Subject: [PATCH 1256/2366] wifi: cfg80211: ibss: ref BSS entry for joined event When the IBSS is joined, we only record the BSSID/channel in the event and look up the BSS entry when processing it. However, that's racy, e.g. a new scan with NL80211_SCAN_FLAG_FLUSH can remove it, causing a warning in the event work: !bss WARNING: net/wireless/ibss.c:37 at __cfg80211_ibss_joined+0x3d3/0x440 Workqueue: cfg80211 cfg80211_event_work cfg80211_process_wdev_events+0x39f/0x5b0 net/wireless/util.c:1144 cfg80211_process_rdev_events+0xa1/0x110 net/wireless/util.c:1179 cfg80211_event_work+0x2f/0x40 net/wireless/core.c:393 Do the lookup early (the driver is expected to only join an IBSS that has a BSS entry) and keep a reference to it. Assisted-by: LLM Fixes: 667503ddcb96 ("cfg80211: fix locking") Reported-by: syzbot+7f064ba1704c2466e36d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7f064ba1704c2466e36d Link: https://patch.msgid.link/20260904165614.f49a213f0e49.I192bfe738750ebb5f2c4faa3019a428da64cd3ec@changeid Signed-off-by: Johannes Berg --- net/wireless/core.h | 6 ++---- net/wireless/ibss.c | 38 +++++++++++++++++++++----------------- net/wireless/util.c | 3 +-- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/net/wireless/core.h b/net/wireless/core.h index a0c2b6ebe31f..85dfb3ac803b 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -290,8 +290,7 @@ struct cfg80211_event { bool locally_generated; } dc; struct { - u8 bssid[ETH_ALEN]; - struct ieee80211_channel *channel; + struct cfg80211_bss *bss; } ij; struct { u8 peer_addr[ETH_ALEN]; @@ -354,8 +353,7 @@ int __cfg80211_join_ibss(struct cfg80211_registered_device *rdev, void cfg80211_clear_ibss(struct net_device *dev, bool nowext); int cfg80211_leave_ibss(struct cfg80211_registered_device *rdev, struct net_device *dev, bool nowext); -void __cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, - struct ieee80211_channel *channel); +void __cfg80211_ibss_joined(struct net_device *dev, struct cfg80211_bss *bss); int cfg80211_ibss_wext_join(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev); diff --git a/net/wireless/ibss.c b/net/wireless/ibss.c index b1d748bdb504..7f6779d326b8 100644 --- a/net/wireless/ibss.c +++ b/net/wireless/ibss.c @@ -16,26 +16,18 @@ #include "rdev-ops.h" -void __cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, - struct ieee80211_channel *channel) +void __cfg80211_ibss_joined(struct net_device *dev, struct cfg80211_bss *bss) { struct wireless_dev *wdev = dev->ieee80211_ptr; - struct cfg80211_bss *bss; #ifdef CONFIG_CFG80211_WEXT union iwreq_data wrqu; #endif if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC)) - return; + goto put_bss; if (!wdev->u.ibss.ssid_len) - return; - - bss = cfg80211_get_bss(wdev->wiphy, channel, bssid, NULL, 0, - IEEE80211_BSS_TYPE_IBSS, IEEE80211_PRIVACY_ANY); - - if (WARN_ON(!bss)) - return; + goto put_bss; if (wdev->u.ibss.current_bss) { cfg80211_unhold_bss(wdev->u.ibss.current_bss); @@ -43,17 +35,22 @@ void __cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, } cfg80211_hold_bss(bss_from_pub(bss)); + /* the reference from the event is transferred to current_bss */ wdev->u.ibss.current_bss = bss_from_pub(bss); cfg80211_upload_connect_keys(wdev); - nl80211_send_ibss_bssid(wiphy_to_rdev(wdev->wiphy), dev, bssid, + nl80211_send_ibss_bssid(wiphy_to_rdev(wdev->wiphy), dev, bss->bssid, GFP_KERNEL); #ifdef CONFIG_CFG80211_WEXT memset(&wrqu, 0, sizeof(wrqu)); - memcpy(wrqu.ap_addr.sa_data, bssid, ETH_ALEN); + memcpy(wrqu.ap_addr.sa_data, bss->bssid, ETH_ALEN); wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); #endif + return; + +put_bss: + cfg80211_put_bss(wdev->wiphy, bss); } void cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, @@ -62,6 +59,7 @@ void cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, struct wireless_dev *wdev = dev->ieee80211_ptr; struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); struct cfg80211_event *ev; + struct cfg80211_bss *bss; unsigned long flags; trace_cfg80211_ibss_joined(dev, bssid, channel); @@ -69,13 +67,19 @@ void cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, if (WARN_ON(!channel)) return; - ev = kzalloc_obj(*ev, gfp); - if (!ev) + bss = cfg80211_get_bss(wdev->wiphy, channel, bssid, NULL, 0, + IEEE80211_BSS_TYPE_IBSS, IEEE80211_PRIVACY_ANY); + if (WARN_ON(!bss)) return; + ev = kzalloc_obj(*ev, gfp); + if (!ev) { + cfg80211_put_bss(wdev->wiphy, bss); + return; + } + ev->type = EVENT_IBSS_JOINED; - memcpy(ev->ij.bssid, bssid, ETH_ALEN); - ev->ij.channel = channel; + ev->ij.bss = bss; spin_lock_irqsave(&wdev->event_lock, flags); list_add_tail(&ev->list, &wdev->event_list); diff --git a/net/wireless/util.c b/net/wireless/util.c index 5429cf3cfd2f..f2464d2ce0d5 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -1235,8 +1235,7 @@ void cfg80211_process_wdev_events(struct wireless_dev *wdev) !ev->dc.locally_generated); break; case EVENT_IBSS_JOINED: - __cfg80211_ibss_joined(wdev->netdev, ev->ij.bssid, - ev->ij.channel); + __cfg80211_ibss_joined(wdev->netdev, ev->ij.bss); break; case EVENT_STOPPED: /* From 17a5f8571d1d40c88b78cfc154a7da0d60f13f37 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:07 +0200 Subject: [PATCH 1257/2366] wifi: cfg80211: fix NAN regulatory enforcement reg_wdev_chan_valid() returns early for any wdev that has no netdev, which is fine for P2P originally (and later PD still), but NAN has no netdev and yet enforcement code was added and is needed, but is dead code right now. Use wdev_running() instead so that netdev-less wdevs aren't skipped. P2P/PD don't do anything in the later switch, but NAN code can now be reached. Assisted-by: LLM Fixes: 0e8ec738a71e ("wifi: cfg80211: add support for NAN data interface") Link: https://patch.msgid.link/20260904165614.6abc075b5401.Ib90696e3fa49b1698c27d64db5360d51f6f187a9@changeid Signed-off-by: Johannes Berg --- net/wireless/reg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wireless/reg.c b/net/wireless/reg.c index a8336baf85dc..9910b080b402 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -2345,7 +2345,7 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) iftype = wdev->iftype; /* make sure the interface is active */ - if (!wdev->netdev || !netif_running(wdev->netdev)) + if (!wdev_running(wdev)) return true; /* NAN doesn't have links, handle it separately */ From f4e72e3758072d7b063e0d8b93419eb915b69c2c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:55:08 +0200 Subject: [PATCH 1258/2366] wifi: cfg80211: reduce RTNL holding in regulatory enforcement Regulatory enforcement in reg_check_chans_work() does all work with the RTNL held, which can block the RTNL for a long time, which syzbot can hit and report hung tasks. Except for NAN, we don't need the RTNL for the enforcement, and the list iteration can be done with RCU instead. Split the enforcement off into new work structs: for NAN, we have to have the RTNL to close dependent NAN_DATA interfaces, everything else can use cfg80211_leave_locked() in a wiphy work. It'd be doable to use just a single work with RTNL, but then the RTNL would end up being used all the time, and really it only needs to be used for NAN. Assisted-by: LLM Reported-by: syzbot+adeb8550754921fece20@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=adeb8550754921fece20 Reported-by: syzbot+101224300649c3eb8af4@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=101224300649c3eb8af4 Link: https://patch.msgid.link/20260904165614.f65bd4d9fa35.I82dac71371d87f39e459fce931b0e5321e4f9767@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 3 +++ net/wireless/core.h | 2 ++ net/wireless/reg.c | 50 +++++++++++++++++++++++++++++++++++++-------- net/wireless/reg.h | 16 +++++++++++++++ 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 8bb2cbd66b48..668380deec7d 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -645,6 +645,8 @@ struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int sizeof_priv, INIT_WORK(&rdev->destroy_work, cfg80211_destroy_iface_wk); wiphy_work_init(&rdev->sched_scan_stop_wk, cfg80211_sched_scan_stop_wk); INIT_WORK(&rdev->sched_scan_res_wk, cfg80211_sched_scan_results_wk); + wiphy_work_init(&rdev->reg_check_chans_wk, reg_leave_invalid_chans_wk); + INIT_WORK(&rdev->reg_leave_nan_wk, reg_leave_invalid_nan_wk); INIT_WORK(&rdev->propagate_radar_detect_wk, cfg80211_propagate_radar_detect_wk); INIT_WORK(&rdev->propagate_cac_done_wk, cfg80211_propagate_cac_done_wk); @@ -1344,6 +1346,7 @@ void wiphy_unregister(struct wiphy *wiphy) cancel_delayed_work_sync(&rdev->dfs_update_channels_wk); cancel_delayed_work_sync(&rdev->background_cac_done_wk); flush_work(&rdev->destroy_work); + flush_work(&rdev->reg_leave_nan_wk); flush_work(&rdev->propagate_radar_detect_wk); flush_work(&rdev->propagate_cac_done_wk); flush_work(&rdev->mgmt_registrations_update_wk); diff --git a/net/wireless/core.h b/net/wireless/core.h index 85dfb3ac803b..6138d207caf4 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -114,6 +114,8 @@ struct cfg80211_registered_device { struct work_struct destroy_work; struct wiphy_work sched_scan_stop_wk; struct work_struct sched_scan_res_wk; + struct wiphy_work reg_check_chans_wk; + struct work_struct reg_leave_nan_wk; struct cfg80211_chan_def radar_chandef; struct work_struct propagate_radar_detect_wk; diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 9910b080b402..11665e0a7efc 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -2446,19 +2446,52 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) return true; } -static void reg_leave_invalid_chans(struct wiphy *wiphy) +void reg_leave_invalid_nan_wk(struct work_struct *work) { + struct cfg80211_registered_device *rdev; struct wireless_dev *wdev; - struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); + + rdev = container_of(work, struct cfg80211_registered_device, + reg_leave_nan_wk); + + /* stopping NAN closes its data interfaces, which needs the RTNL */ + rtnl_lock(); list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { bool valid; - scoped_guard(wiphy, wiphy) - valid = reg_wdev_chan_valid(wiphy, wdev); + if (wdev->iftype != NL80211_IFTYPE_NAN) + continue; + + scoped_guard(wiphy, &rdev->wiphy) + valid = reg_wdev_chan_valid(&rdev->wiphy, wdev); if (!valid) cfg80211_leave(rdev, wdev, -1); } + + rtnl_unlock(); +} + +void reg_leave_invalid_chans_wk(struct wiphy *wiphy, struct wiphy_work *work) +{ + struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); + struct wireless_dev *wdev; + + lockdep_assert_held(&wiphy->mtx); + + list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { + if (reg_wdev_chan_valid(wiphy, wdev)) + continue; + + /* + * Tearing down NAN needs the RTNL for closing NAN_DATA + * interfaces, handle that separately. + */ + if (wdev->iftype == NL80211_IFTYPE_NAN) + schedule_work(&rdev->reg_leave_nan_wk); + else + cfg80211_leave_locked(rdev, wdev, -1); + } } static void reg_check_chans_work(struct work_struct *work) @@ -2466,12 +2499,13 @@ static void reg_check_chans_work(struct work_struct *work) struct cfg80211_registered_device *rdev; pr_debug("Verifying active interfaces after reg change\n"); - rtnl_lock(); - for_each_rdev(rdev) - reg_leave_invalid_chans(&rdev->wiphy); + rcu_read_lock(); - rtnl_unlock(); + list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) + wiphy_work_queue(&rdev->wiphy, &rdev->reg_check_chans_wk); + + rcu_read_unlock(); } void reg_check_channels(void) diff --git a/net/wireless/reg.h b/net/wireless/reg.h index fc31c5f9a61a..c587079ead8f 100644 --- a/net/wireless/reg.h +++ b/net/wireless/reg.h @@ -178,6 +178,22 @@ int reg_reload_regdb(void); */ void reg_check_channels(void); +/** + * reg_leave_invalid_chans_wk - check if channels are no longer usable and leave + * @wiphy: the wiphy to check + * @work: the work struct + */ +void reg_leave_invalid_chans_wk(struct wiphy *wiphy, struct wiphy_work *work); + +/** + * reg_leave_invalid_nan_wk - check channels and tear down NAN when unusable + * @work: the work struct + * + * Stopping a NAN interface needs the RTNL, so it cannot be done from + * reg_leave_invalid_chans_wk() which runs with the wiphy mutex held. + */ +void reg_leave_invalid_nan_wk(struct work_struct *work); + extern const u8 shipped_regdb_certs[]; extern unsigned int shipped_regdb_certs_len; extern const u8 extra_regdb_certs[]; From f5dd0626d2b07c6f5c8c763d75d13c6307393bca Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:05 +0200 Subject: [PATCH 1259/2366] wifi: mac80211: don't apply peer rates to off-channel frames All the off-channel frames (including scan) aren't really part of the connection, so don't apply the station rates even if they're being sent to the station in question (e.g. by accident). They don't use the rate mask via IEEE80211_TX_CTRL_DONT_USE_RATE_MASK, but the station might not have rates of them either, hitting the warning found by syzbot. Assisted-by: LLM Reported-by: syzbot+34463a129786910405dd@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=34463a129786910405dd Link: https://patch.msgid.link/20260904165722.ade6b07421b8.I59b7ea810eb021a7a68b3090828a757b6dd85e57@changeid Signed-off-by: Johannes Berg --- net/mac80211/rate.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c index 64768abb0a5f..e910f03af777 100644 --- a/net/mac80211/rate.c +++ b/net/mac80211/rate.c @@ -372,6 +372,14 @@ static void __rate_control_send_low(struct ieee80211_hw *hw, u32 rate_flags = 0; int i; + /* + * Frames that shouldn't use the rate mask could be anything, + * even on a different band, so don't take the sta into account + * to avoid ending up without rates. + */ + if (info->control.flags & IEEE80211_TX_CTRL_DONT_USE_RATE_MASK) + sta = NULL; + if (sband->band == NL80211_BAND_S1GHZ) { info->control.rates[0].flags |= IEEE80211_TX_RC_S1G_MCS; info->control.rates[0].idx = 0; From 23c68b4aaf5e18ab95532fc6714b737d5b7c701c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:06 +0200 Subject: [PATCH 1260/2366] wifi: mac80211: don't drop scan probe requests for lack of peer rates While software scanning, ieee80211_tx_h_rate_ctrl() warns and drops the frame if the target station has no usable bitrate on the band that's currently being scanned. But that's really meant for data frames, not if we happen to scan for the BSSID on the wrong band, which can be constructed easily. Skip the check for IEEE80211_TX_CTRL_DONT_USE_RATE_MASK, the previous commit also ignored the station rate mask for such frames as well. Assisted-by: LLM Reported-by: syzbot+0d516b33238bd97ee864@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0d516b33238bd97ee864 Link: https://patch.msgid.link/20260904165722.b57ea4ab82d3.Id6c9c42d5cef5901bfac88853647b03ba4077b3e@changeid Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 3896c7b2c4e5..d155fb319a55 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -744,10 +744,12 @@ ieee80211_tx_h_rate_ctrl(struct ieee80211_tx_data *tx) assoc = test_sta_flag(tx->sta, WLAN_STA_ASSOC); /* - * Lets not bother rate control if we're associated and cannot - * talk to the sta. This should not happen. + * Lets not bother rate control if we're associated and cannot talk to + * the sta. This should not happen - except for frames that aren't + * really for the peer to start with and already ignore rates. */ - if (WARN(test_bit(SCAN_SW_SCANNING, &tx->local->scanning) && assoc && + if (!(info->control.flags & IEEE80211_TX_CTRL_DONT_USE_RATE_MASK) && + WARN(test_bit(SCAN_SW_SCANNING, &tx->local->scanning) && assoc && !rate_usable_index_exists(sband, &tx->sta->sta), "%s: Dropped data frame as no usable bitrate found while " "scanning and associated. Target station: " From 733f0fde95392ed5f61a4e36aee661ea8d0e8581 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:07 +0200 Subject: [PATCH 1261/2366] wifi: mac80211: don't start a ROC while scanning The ROC work can be pending when a scan starts (which requires ROC list to be empty, but that's possible), and then a new ROC can be added to the list and the work will pick it up. Avoid starting that ROC if a scan made it between things, as otherwise we'll hit a warning later: WARNING: net/mac80211/offchannel.c:404 at ieee80211_start_next_roc+0x256/0x2d0 Workqueue: events_unbound cfg80211_wiphy_work Call Trace: __ieee80211_scan_completed+0x4fd/0xe40 net/mac80211/scan.c:537 ieee80211_scan_work+0x472/0x1ff0 net/mac80211/scan.c:1193 cfg80211_wiphy_work+0x410/0x570 net/wireless/core.c:513 Assisted-by: LLM Fixes: aaa016ccd5df ("mac80211: rewrite remain-on-channel logic") Reported-by: syzbot+c3a167b5615df4ccd7fb@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c3a167b5615df4ccd7fb Link: https://patch.msgid.link/20260904165722.f9d5b150edd8.I61bc9de8c8d089096ad695213b9c85c7df38c3bd@changeid Signed-off-by: Johannes Berg --- net/mac80211/offchannel.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 7acef80d5f1f..e30767853c36 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -460,6 +460,13 @@ static void __ieee80211_roc_work(struct ieee80211_local *local) return; if (!roc->started) { + /* + * The work can be started by a previous ROC work, but a scan + * can get between things; scan finish will retrigger us. + */ + if (local->scanning) + return; + WARN_ON(!local->emulate_chanctx); _ieee80211_start_next_roc(local); } else { From a7491b7efbd9136b120a12ed72af9c12121dd134 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:08 +0200 Subject: [PATCH 1262/2366] wifi: mac80211: don't warn when an IBSS has no channel to scan ieee80211_request_ibss_scan() warns when regulatory leaves no allowed channel, but that can happen as the regdomain can change while IBSS is operating, and it can continue to operate briefly during the 60s grace period until it's shut down. Just remove the warning in this case. Assisted-by: LLM Fixes: 34bcf7150241 ("mac80211: fix ibss scanning") Reported-by: syzbot+1634c5399e29d8b66789@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1634c5399e29d8b66789 Link: https://patch.msgid.link/20260904165722.fe380c27fef4.I0e8bee2e12a40d240851a4bc724d47753af46159@changeid Signed-off-by: Johannes Berg --- net/mac80211/scan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index eeff230bd909..8e950ef6d1ea 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -1242,7 +1242,7 @@ int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata, } } - if (WARN_ON_ONCE(n_ch == 0)) + if (n_ch == 0) return -EINVAL; local->int_scan_req->n_channels = n_ch; From 362bd5bce29ed0f6fd3d39a7065567777d70606e Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:09 +0200 Subject: [PATCH 1263/2366] wifi: mac80211: don't offload TC setup on AP_VLAN interfaces AP_VLAN interfaces are purely virtual, so don't try to offload TC setup to drivers. We can't really use the AP interface either since we may not know it all the time, and it could technically even change. Just reject the TC offload so things get done in software. Assisted-by: LLM Fixes: 61587f1556fe ("wifi: mac80211: add support for letting drivers register tc offload support") Reported-by: syzbot+f1ba58d6b55abd13239e@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f1ba58d6b55abd13239e Link: https://patch.msgid.link/20260904165722.726cc076cecb.Iccfd88b13635425e850ce031376eb60a4ce5f4f8@changeid Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 43460a705a6b..8c300e045fdf 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -935,6 +935,9 @@ static int ieee80211_netdev_setup_tc(struct net_device *dev, struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; + if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) + return -EOPNOTSUPP; + return drv_net_setup_tc(local, sdata, dev, type, type_data); } From bf29d085e0eba92388518719d044f4702a8c6644 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:10 +0200 Subject: [PATCH 1264/2366] wifi: mac80211: suppress chanctx warning for debugfs reset Before suspend all the channel contexts should removed, so the warning makes sense and should be there, but during reset the same code is called without first removing. Limit the check to the real suspend case. Assisted-by: LLM Fixes: 12e7f517029d ("mac80211: cleanup generic suspend/resume procedures") Reported-by: syzbot+56a1a45a9a2c04d425ff@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=56a1a45a9a2c04d425ff Link: https://patch.msgid.link/20260904165722.fe46395e310b.Ic4aaa95bd9d0ceb6a3cd7d84c425afee7d7d3dd7@changeid Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 2 +- net/mac80211/debugfs.c | 2 +- net/mac80211/ieee80211_i.h | 2 +- net/mac80211/pm.c | 8 +++++--- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 1f074799f85f..2b13c057312e 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -3475,7 +3475,7 @@ static int ieee80211_set_txq_params(struct wiphy *wiphy, static int ieee80211_suspend(struct wiphy *wiphy, struct cfg80211_wowlan *wowlan) { - return __ieee80211_suspend(wiphy_priv(wiphy), wowlan); + return __ieee80211_suspend(wiphy_priv(wiphy), wowlan, false); } static int ieee80211_resume(struct wiphy *wiphy) diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index 105653a16b68..e38631d7cfb4 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -384,7 +384,7 @@ static ssize_t reset_write(struct file *file, const char __user *user_buf, rtnl_lock(); wiphy_lock(local->hw.wiphy); - __ieee80211_suspend(&local->hw, NULL); + __ieee80211_suspend(&local->hw, NULL, true); ret = __ieee80211_resume(&local->hw); wiphy_unlock(local->hw.wiphy); diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 5761e9621491..d05f59467399 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2432,7 +2432,7 @@ int ieee80211_reconfig(struct ieee80211_local *local); void ieee80211_stop_device(struct ieee80211_local *local, bool suspend); int __ieee80211_suspend(struct ieee80211_hw *hw, - struct cfg80211_wowlan *wowlan); + struct cfg80211_wowlan *wowlan, bool reset); static inline int __ieee80211_resume(struct ieee80211_hw *hw) { diff --git a/net/mac80211/pm.c b/net/mac80211/pm.c index 5a508d99e84f..f63676c44853 100644 --- a/net/mac80211/pm.c +++ b/net/mac80211/pm.c @@ -18,7 +18,8 @@ static void ieee80211_sched_scan_cancel(struct ieee80211_local *local) cfg80211_sched_scan_stopped_locked(local->hw.wiphy, 0); } -int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) +int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan, + bool reset) { struct ieee80211_local *local = hw_to_local(hw); struct ieee80211_sub_if_data *sdata; @@ -166,9 +167,10 @@ int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) /* * We disconnected on all interfaces before suspend, all channel - * contexts should be released. + * contexts should be released, but on 'reset' debugfs that's + * not true so don't check there. */ - WARN_ON(!list_empty(&local->chanctx_list)); + WARN_ON(!reset && !list_empty(&local->chanctx_list)); /* stop hardware - this must stop RX */ ieee80211_stop_device(local, true); From ac7472a24bd433b81c06582835dd1d5547c10da9 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:11 +0200 Subject: [PATCH 1265/2366] wifi: mac80211: abort chanswitch when leaving a mesh The code in ieee80211_stop_mesh() leaves CSA active, but leaving the mesh released the channel context, so the CSA finalize work crashes: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000003 KASAN: null-ptr-deref in range [0x0000000000000018-0x000000000000001f] RIP: 0010:ieee80211_put_srates_elem+0x42/0x640 net/mac80211/util.c:3272 Call Trace: ieee80211_mesh_build_beacon+0xa83/0x1b50 net/mac80211/mesh.c:1093 ieee80211_mesh_rebuild_beacon+0xc7/0x170 net/mac80211/mesh.c:1147 ieee80211_mesh_finish_csa+0x131/0x210 net/mac80211/mesh.c:1542 ieee80211_set_after_csa_beacon net/mac80211/cfg.c:4085 [inline] __ieee80211_csa_finalize net/mac80211/cfg.c:4133 [inline] ieee80211_csa_finalize+0x633/0x1150 net/mac80211/cfg.c:4155 cfg80211_wiphy_work+0x2ab/0x450 net/wireless/core.c:438 Abort the channel switch properly. Assisted-by: LLM Fixes: b8456a14e9d2 ("{nl,cfg,mac}80211: implement mesh channel switch userspace API") Reported-by: syzbot+81cd9dc1596563141d19@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=81cd9dc1596563141d19 Link: https://patch.msgid.link/20260904165722.d0b87eee08aa.I80550d6127e0bb26efb49a5fbe95be1aef1cd0cb@changeid Signed-off-by: Johannes Berg --- net/mac80211/mesh.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index d4507e4e6ec1..bed7ac838250 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -1204,6 +1204,10 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata) netif_carrier_off(sdata->dev); + /* abort any running channel switch */ + sdata->vif.bss_conf.csa_active = false; + ieee80211_vif_unblock_queues_csa(sdata); + /* flush STAs and mpaths on this iface */ sta_info_flush(sdata, -1); ieee80211_free_keys(sdata, true); From 3f28551d0241254a75626d868041c6340285088b Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:12 +0200 Subject: [PATCH 1266/2366] wifi: mac80211: reset state when starting AP fails ieee80211_start_ap() can set enable_beacon (and beacon_int) and fail later, leaving it set forever. Scanning can then attempt to restore beaconing on such an interface, leading to: Oops: divide error: 0000 [#1] SMP KASAN NOPTI RIP: 0010:mac80211_hwsim_link_info_changed+0xca7/0xf00 Call Trace: drv_link_info_changed+0x413/0x860 net/mac80211/driver-ops.c:495 ieee80211_link_info_change_notify+0x24b/0x3c0 net/mac80211/main.c:427 ieee80211_offchannel_return+0x381/0x580 net/mac80211/offchannel.c:160 __ieee80211_scan_completed+0x993/0xe30 net/mac80211/scan.c:519 ieee80211_scan_work+0x472/0x2010 net/mac80211/scan.c:1193 cfg80211_wiphy_work+0x2b7/0x550 net/wireless/core.c:538 in hwsim. Also, cfg80211 then allows changing the interface type, and the off-channel path getgs confused about beaconing as well, leading to another warning: WARNING: net/mac80211/driver-ops.c:468 at drv_link_info_changed+0x583/0x880 ieee80211_link_info_change_notify+0x24b/0x3c0 net/mac80211/main.c:427 ieee80211_offchannel_stop_vifs+0x328/0x5c0 net/mac80211/offchannel.c:122 ieee80211_start_sw_scan net/mac80211/scan.c:583 [inline] __ieee80211_start_scan+0xfb6/0x1af0 net/mac80211/scan.c:882 Reset the state on failures to always have it correct. Assisted-by: LLM Fixes: d6a83228823f ("mac80211: track enable_beacon explicitly") Reported-by: syzbot+ca7a2759caaa6cd4e3db@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ca7a2759caaa6cd4e3db Reported-by: syzbot+c4686c3eb8b64032618f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c4686c3eb8b64032618f Link: https://patch.msgid.link/20260904165722.9629429a5221.I7f599412bfe12a09d41ea4901be9ad165d07d133@changeid Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 2b13c057312e..2d5a0abe35db 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1929,6 +1929,9 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, return 0; error: + link_conf->enable_beacon = false; + link_conf->beacon_int = prev_beacon_int; + sdata->vif.cfg.ssid_len = 0; ieee80211_link_release_channel(link); return err; From 78183e8331958fda11cd2b6850bb424a9747c4b2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:13 +0200 Subject: [PATCH 1267/2366] wifi: mac80211: reset the LED state when ifup fails When the first interface comes up, the radio LED is turned on. This can start the TPT trigger timer, which continues running. But if bringing up the interface fails then the timer keeps running and won't be stopped by anything, eventually it can be freed: ODEBUG: free active (active state 0) object: ffff888127e12130 object type: timer_list hint: tpt_trig_timer+0x0/0x300 net/mac80211/led.c:145 WARNING: CPU: 0 PID: 5923 at lib/debugobjects.c:612 debug_print_object+0x1a2/0x2b0 debug_check_no_obj_freed+0x4b7/0x600 lib/debugobjects.c:1129 kfree+0x436/0x670 mm/slub.c:6818 ieee80211_led_exit+0x162/0x1c0 net/mac80211/led.c:210 ieee80211_unregister_hw+0x27e/0x3a0 net/mac80211/main.c:1706 rt2x00lib_remove_dev+0x55b/0x670 Undo the LED state in the error path. Assisted-by: LLM Fixes: 67408c8c7b9d ("mac80211: selective throughput LED trigger active") Reported-by: syzbot+e84ecca6d1fa09a9b3d9@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e84ecca6d1fa09a9b3d9 Link: https://patch.msgid.link/20260904165722.044aa432f873.I601a67a2cd558b8ef8416a07554ae7efe896e9d8@changeid Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 8c300e045fdf..4c34c3287eb4 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -1606,8 +1606,12 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up) err_del_interface: drv_remove_interface(local, sdata); err_stop: - if (!local->open_count) + if (!local->open_count) { + ieee80211_led_radio(local, false); + ieee80211_mod_tpt_led_trig(local, 0, + IEEE80211_TPT_LEDTRIG_FL_RADIO); drv_stop(local, false); + } if (sdata->vif.type == NL80211_IFTYPE_NAN_DATA) RCU_INIT_POINTER(sdata->u.nan_data.nmi, NULL); if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) From 6f0a100df8539ce90f37c14e1945f396ca2410bc Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 16:57:14 +0200 Subject: [PATCH 1268/2366] wifi: mac80211: only operate on TDLS peers in the TDLS code ieee80211_tdls_oper() can operate on the AP station, which then yields various warnings when the AP station is removed then or at a later point in time after being confused for a TDLS peer. Always check that the station is a TDLS peer. Assisted-by: LLM Fixes: dfe018bf9953 ("mac80211: handle TDLS high-level commands and frames") Fixes: 17e6a59a365a ("mac80211: cleanup TDLS state during failed setup") Reported-by: syzbot+a59b5291776979816910@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a59b5291776979816910 Link: https://patch.msgid.link/20260904165722.3bad8b79679b.I99618745e83cbe9b9804179387be15fcd3505ae3@changeid Signed-off-by: Johannes Berg --- net/mac80211/tdls.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c index dc2f662fe4c4..7f40b1d62938 100644 --- a/net/mac80211/tdls.c +++ b/net/mac80211/tdls.c @@ -1142,6 +1142,7 @@ ieee80211_tdls_mgmt_setup(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_local *local = sdata->local; enum ieee80211_smps_mode smps_mode = sdata->deflink.u.mgd.driver_smps_mode; + struct sta_info *sta; int ret; /* don't support setup with forced SMPS mode that's not off */ @@ -1168,14 +1169,10 @@ ieee80211_tdls_mgmt_setup(struct wiphy *wiphy, struct net_device *dev, * Allow error packets to be sent - sometimes we don't even add a STA * before failing the setup. */ - if (status_code == 0) { - rcu_read_lock(); - if (!sta_info_get(sdata, peer)) { - rcu_read_unlock(); - ret = -ENOLINK; - goto out_unlock; - } - rcu_read_unlock(); + sta = sta_info_get(sdata, peer); + if ((status_code == 0 && !sta) || (sta && !sta->sta.tdls)) { + ret = -ENOLINK; + goto out_unlock; } ieee80211_flush_queues(local, sdata, false); @@ -1442,6 +1439,10 @@ int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, */ tdls_dbg(sdata, "TDLS oper %d peer %pM\n", oper, peer); + sta = sta_info_get(sdata, peer); + if (!sta || !sta->sta.tdls) + return -ENOLINK; + switch (oper) { case NL80211_TDLS_ENABLE_LINK: if (sdata->vif.bss_conf.csa_active) { @@ -1449,10 +1450,6 @@ int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, return -EBUSY; } - sta = sta_info_get(sdata, peer); - if (!sta || !sta->sta.tdls) - return -ENOLINK; - iee80211_tdls_recalc_chanctx(sdata, sta); iee80211_tdls_recalc_ht_protection(sdata, sta); From eeee52cfd1d639774c9812e8890631404a057dd2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:01:59 +0200 Subject: [PATCH 1269/2366] wifi: cfg80211: restore netns_immutable on failures Switching a wiphy's netns has to clear netns_immutable before moving interfaces, but then if any of the interfaces fails to move, it gets netns_immutable cleared forever. Then userspace can move it by itself, breaking the assumption that they all move together. Fix the order here and always reset netns_immutable after attempting the move. Assisted-by: LLM Fixes: 463d018323851 ("cfg80211: make aware of net namespaces") Link: https://patch.msgid.link/20260904170220.7ea88157dcbc.Id868585a790be8b9ece9b39b0db464a5963faaf3@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 668380deec7d..043bb57b0556 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -167,9 +167,9 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, continue; wdev->netdev->netns_immutable = false; err = dev_change_net_namespace(wdev->netdev, net, "wlan%d"); + wdev->netdev->netns_immutable = true; if (err) break; - wdev->netdev->netns_immutable = true; } if (err) { From a41bd1938a9bfe226d444172a7e20e4bd5097960 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:02:00 +0200 Subject: [PATCH 1270/2366] wifi: cfg80211: undo netns switch if renaming the wiphy fails Once all the interfaces have been moved, cfg80211_switch_netns() moves the wiphy itself by setting its network namespace and then renaming it, which makes sysfs move it. The rename can fail (but only on allocation failures), leaving things mixed up and hitting the warning there. Ignoring it isn't great, undo the move and let the change fail in this case. If undo fails then WARN, then things would again be stuck in two different network namespaces. Assisted-by: LLM Reported-by: syzbot+3515319a302224e081b4@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3515319a302224e081b4 Fixes: 463d018323851 ("cfg80211: make aware of net namespaces") Link: https://patch.msgid.link/20260904170220.7966cc705e33.Ib398351113bbd3cab85302467060cab378564421@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 94 +++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 41 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 043bb57b0556..9ee1c36f1262 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -153,9 +153,25 @@ int cfg80211_dev_rename(struct cfg80211_registered_device *rdev, return 0; } +static int cfg80211_switch_wdev_netns(struct wireless_dev *wdev, + struct net *net) +{ + int err; + + if (!wdev->netdev) + return 0; + + wdev->netdev->netns_immutable = false; + err = dev_change_net_namespace(wdev->netdev, net, "wlan%d"); + wdev->netdev->netns_immutable = true; + + return err; +} + int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, struct net *net) { + struct net *old_net = wiphy_net(&rdev->wiphy); struct wireless_dev *wdev; int err = 0; @@ -163,58 +179,54 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, return -EOPNOTSUPP; list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { - if (!wdev->netdev) - continue; - wdev->netdev->netns_immutable = false; - err = dev_change_net_namespace(wdev->netdev, net, "wlan%d"); - wdev->netdev->netns_immutable = true; + err = cfg80211_switch_wdev_netns(wdev, net); if (err) - break; + goto undo; } - if (err) { - /* failed -- clean up to old netns */ - net = wiphy_net(&rdev->wiphy); - - list_for_each_entry_continue_reverse(wdev, - &rdev->wiphy.wdev_list, - list) { + scoped_guard(wiphy, &rdev->wiphy) { + list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { if (!wdev->netdev) continue; - wdev->netdev->netns_immutable = false; - err = dev_change_net_namespace(wdev->netdev, net, - "wlan%d"); - WARN_ON(err); - wdev->netdev->netns_immutable = true; + nl80211_notify_iface(rdev, wdev, + NL80211_CMD_DEL_INTERFACE); } - return err; + nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY); + + wiphy_net_set(&rdev->wiphy, net); + + /* this only fails on allocation failure */ + err = device_rename(&rdev->wiphy.dev, + dev_name(&rdev->wiphy.dev)); + if (err) + wiphy_net_set(&rdev->wiphy, old_net); + + nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY); + + list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { + if (!wdev->netdev) + continue; + nl80211_notify_iface(rdev, wdev, + NL80211_CMD_NEW_INTERFACE); + } } - guard(wiphy)(&rdev->wiphy); + if (!err) + return 0; - list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { - if (!wdev->netdev) - continue; - nl80211_notify_iface(rdev, wdev, NL80211_CMD_DEL_INTERFACE); - } + /* set to the last one to undo all of them */ + wdev = list_entry(&rdev->wiphy.wdev_list, typeof(*wdev), list); +undo: + /* + * Move back everything, if this fails again (allocation failures) + * then things get stuck in different network namespaces. + */ + list_for_each_entry_continue_reverse(wdev, &rdev->wiphy.wdev_list, + list) + WARN_ON(cfg80211_switch_wdev_netns(wdev, old_net)); - nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY); - - wiphy_net_set(&rdev->wiphy, net); - - err = device_rename(&rdev->wiphy.dev, dev_name(&rdev->wiphy.dev)); - WARN_ON(err); - - nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY); - - list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { - if (!wdev->netdev) - continue; - nl80211_notify_iface(rdev, wdev, NL80211_CMD_NEW_INTERFACE); - } - - return 0; + return err; } static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data) From eee2efd82867b623982ac51925b5a1812a74c50d Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:02:01 +0200 Subject: [PATCH 1271/2366] wifi: mac80211: unlist vifs when their netdev is unregistered mac80211 only removes vifs from the local->interfaces list when an interface is removed via ieee80211_if_remove(), before it unregisters the netdev. However, it's possible for a netdev to be unregistered without going through that: When the netns that holds the wiphy is destroyed, the wiphy is supposed to move to the init_ns, but that can run into allocation failures. Then, mac80211 has an interface listed that doesn't exist, and will eventually hit BUG: failure at net/wireless/core.h:141/wiphy_to_rdev()! ... _cfg80211_unregister_wdev+0x24/0x36a [cfg80211] cfg80211_unregister_wdev+0x15/0x1d [cfg80211] ieee80211_remove_interfaces+0x1ff/0x257 [mac80211] ieee80211_unregister_hw+0x73/0x1d1 [mac80211] mac80211_hwsim_del_radio+0x114/0x166 [mac80211_hwsim] Remove the interface from the list in ->ndo_uninit if it's still around to avoid this. Assisted-by: LLM Fixes: 463d018323851 ("cfg80211: make aware of net namespaces") Link: https://patch.msgid.link/20260904170220.038ad73e6c04.I990abca78483e058746b6f42b4796717c3028164@changeid Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 4c34c3287eb4..842bfb4a7cb6 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -924,9 +924,33 @@ static void ieee80211_teardown_sdata(struct ieee80211_sub_if_data *sdata) } } +/* + * The netdev can be unregistered without mac80211 doing it, e.g. by the netdev + * core when cfg80211 couldn't move it out of a network namespace that's being + * destroyed. Drop it from the interface list either way. + */ +static void ieee80211_unlist_sdata(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_sub_if_data *iter; + + ASSERT_RTNL(); + + list_for_each_entry(iter, &local->interfaces, list) { + if (iter != sdata) + continue; + guard(mutex)(&local->iflist_mtx); + list_del_rcu(&sdata->list); + return; + } +} + static void ieee80211_uninit(struct net_device *dev) { - ieee80211_teardown_sdata(IEEE80211_DEV_TO_SUB_IF(dev)); + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + + ieee80211_unlist_sdata(sdata); + ieee80211_teardown_sdata(sdata); } static int ieee80211_netdev_setup_tc(struct net_device *dev, From 4635b1a1c1d693178a537446a6e09963f0fdae52 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:02:02 +0200 Subject: [PATCH 1272/2366] wifi: cfg80211: get the wiphy out of a dying network namespace When a network namespace is destroyed, cfg80211_pernet_exit() moves any wiphy back to the initial namespace, and just warns if that fails. But moving an interface can fail (due to allocation failures), and then the wiphy is left behind with a garbage netns pointer: Kernel mode fault at addr 0x30 genlmsg_multicast_netns.constprop.0+0x46/0xcf [cfg80211] nl80211_notify_wiphy+0xcd/0xe8 [cfg80211] wiphy_unregister+0x169/0x3fc [cfg80211] Note that commit debac3a20dec ("net: Remove conflicting altnames for dying netns in __dev_change_net_namespace().") fixed another path that could reach it without allocation failures. Remove interfaces that cannot be moved instead of failing the switch, so that the wiphy always ends up in the initial namespace. In this case the netdev core will unregister the interfaces anyway. Assisted-by: LLM Reported-by: syzbot+c5f8a81e794d4a4f2014@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c5f8a81e794d4a4f2014 Fixes: 463d018323851 ("cfg80211: make aware of net namespaces") Link: https://patch.msgid.link/20260904170220.7f3edc6d9992.I5e57921011244d3d8ef14d89e738aa19a5d972a0@changeid Signed-off-by: Johannes Berg --- net/wireless/core.c | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index 9ee1c36f1262..25dd1a4d6b4e 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -168,20 +168,24 @@ static int cfg80211_switch_wdev_netns(struct wireless_dev *wdev, return err; } -int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, - struct net *net) +static int __cfg80211_switch_netns(struct cfg80211_registered_device *rdev, + struct net *net, bool force) { struct net *old_net = wiphy_net(&rdev->wiphy); - struct wireless_dev *wdev; + struct wireless_dev *wdev, *tmp; int err = 0; - if (!(rdev->wiphy.flags & WIPHY_FLAG_NETNS_OK)) - return -EOPNOTSUPP; - - list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { + list_for_each_entry_safe(wdev, tmp, &rdev->wiphy.wdev_list, list) { err = cfg80211_switch_wdev_netns(wdev, net); - if (err) + if (!err) + continue; + if (!force) goto undo; + /* remove interfaces that fail to allow wiphy switching */ + dev_close(wdev->netdev); + scoped_guard(wiphy, &rdev->wiphy) + cfg80211_unregister_wdev(wdev); + err = 0; } scoped_guard(wiphy, &rdev->wiphy) { @@ -199,7 +203,7 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, /* this only fails on allocation failure */ err = device_rename(&rdev->wiphy.dev, dev_name(&rdev->wiphy.dev)); - if (err) + if (err && !force) wiphy_net_set(&rdev->wiphy, old_net); nl80211_notify_wiphy(rdev, NL80211_CMD_NEW_WIPHY); @@ -212,8 +216,8 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, } } - if (!err) - return 0; + if (!err || force) + return err; /* set to the last one to undo all of them */ wdev = list_entry(&rdev->wiphy.wdev_list, typeof(*wdev), list); @@ -229,6 +233,15 @@ int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, return err; } +int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, + struct net *net) +{ + if (!(rdev->wiphy.flags & WIPHY_FLAG_NETNS_OK)) + return -EOPNOTSUPP; + + return __cfg80211_switch_netns(rdev, net, false); +} + static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data) { struct cfg80211_registered_device *rdev = data; @@ -1882,7 +1895,7 @@ static void __net_exit cfg80211_pernet_exit(struct net *net) rtnl_lock(); for_each_rdev(rdev) { if (net_eq(wiphy_net(&rdev->wiphy), net)) - WARN_ON(cfg80211_switch_netns(rdev, &init_net)); + WARN_ON(__cfg80211_switch_netns(rdev, &init_net, true)); } rtnl_unlock(); } From 87840d4a3a21b1c19b867a80e16ba69dff284de2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 4 Sep 2026 17:01:35 +0200 Subject: [PATCH 1273/2366] wifi: mac80211_hwsim: don't hand frames to mac80211 while stopping The code checks ->started for frames coming from wmediumd, but the radio can be stopped after the check and before frame delivery, causing mac80211 to hit the WARN_ON(!local->started). Expand the mutex for this case and synchronise against it when the radio is stopped to avoid being able to hit the warning with hwsim. Drop the error print that would've complicated the error path, it only triggers for allocation failures (already noisy) and malformed frames anyway. Assisted-by: LLM Fixes: 7882513bacb1 ("mac80211_hwsim driver support userspace frame tx/rx") Reported-by: syzbot+b4aa2b672b18f1d4dc5f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b4aa2b672b18f1d4dc5f Link: https://patch.msgid.link/20260904170140.5f69a10d606b.I4a7921d00643f69e439c7a3b221d104f66a3dcdc@changeid Signed-off-by: Johannes Berg --- .../wireless/virtual/mac80211_hwsim_main.c | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 02b6d81cccd1..b9446577ff49 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -2327,7 +2327,12 @@ static void mac80211_hwsim_stop(struct ieee80211_hw *hw, bool suspend) struct sk_buff *skb; int i; - data->started = false; + /* + * Serialise against wmediumd userspace, so no more frames + * can be handed to mac80211 after this returns. + */ + scoped_guard(mutex, &data->mutex) + data->started = false; for (i = 0; i < ARRAY_SIZE(data->link_data); i++) hrtimer_cancel(&data->link_data[i].beacon_timer); @@ -6505,12 +6510,12 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, if (frame_data_len < sizeof(struct ieee80211_hdr_3addr) || frame_data_len > IEEE80211_MAX_DATA_LEN) - goto err; + goto out; /* Allocate new skb here */ skb = alloc_skb(frame_data_len, GFP_KERNEL); if (skb == NULL) - goto err; + goto out; /* Copy the data */ skb_put_data(skb, frame_data, frame_data_len); @@ -6535,10 +6540,17 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, goto out; } + /* + * Serialise against mac80211_hwsim_stop() - mac80211 doesn't allow + * frames reported while the HW is down, hence the ->started check + * must be under mutex. + */ + mutex_lock(&data2->mutex); + /* check if radio is configured properly */ if ((data2->idle && !data2->tmp_chan) || !data2->started) - goto out; + goto out_unlock; /* A frame is received from user space */ memset(&rx_status, 0, sizeof(rx_status)); @@ -6557,22 +6569,18 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, iter_data.channel = ieee80211_get_channel(data2->hw->wiphy, rx_status.freq); if (!iter_data.channel) - goto out; + goto out_unlock; rx_status.band = iter_data.channel->band; - mutex_lock(&data2->mutex); if (!hwsim_chans_compat(iter_data.channel, channel)) { ieee80211_iterate_active_interfaces_atomic( data2->hw, IEEE80211_IFACE_ITER_NORMAL, mac80211_hwsim_tx_iter, &iter_data); - if (!iter_data.receive) { - mutex_unlock(&data2->mutex); - goto out; - } + if (!iter_data.receive) + goto out_unlock; } - mutex_unlock(&data2->mutex); } else if (!channel) { - goto out; + goto out_unlock; } else { rx_status.freq = channel->center_freq; rx_status.band = channel->band; @@ -6580,7 +6588,7 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, rx_status.rate_idx = nla_get_u32(info->attrs[HWSIM_ATTR_RX_RATE]); if (rx_status.rate_idx >= data2->hw->wiphy->bands[rx_status.band]->n_bitrates) - goto out; + goto out_unlock; rx_status.signal = nla_get_u32(info->attrs[HWSIM_ATTR_SIGNAL]); hdr = (void *)skb->data; @@ -6590,10 +6598,11 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, rx_status.boottime_ns = ktime_get_boottime_ns(); mac80211_hwsim_rx(data2, &rx_status, skb); + mutex_unlock(&data2->mutex); return 0; -err: - pr_debug("mac80211_hwsim: error occurred in %s\n", __func__); +out_unlock: + mutex_unlock(&data2->mutex); out: dev_kfree_skb(skb); return -EINVAL; From e6c5ed7a98d7bc8b0f7918246f1c90ddb3f79dfa Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Fri, 4 Sep 2026 14:24:45 +0000 Subject: [PATCH 1274/2366] wifi: rsi: fix heap OOB write on key removal When a key is removed (data == NULL), rsi_hal_load_key() runs: memset(&set_key[FRAME_DESC_SZ], 0, frame_len - FRAME_DESC_SZ); set_key is a struct rsi_set_key *, so the subscript is scaled by sizeof(struct rsi_set_key) (160 bytes): &set_key[FRAME_DESC_SZ] is skb->data + 2560, and the memset writes 144 zero bytes starting 2.4KB past the end of the 160-byte skb data buffer, corrupting unrelated heap objects. The intended byte offset would have been (u8 *)set_key + FRAME_DESC_SZ. The write fires on every DISABLE_KEY callback, so plain disconnects, roams and interface teardowns trigger it on real networks. The memset is redundant: the whole buffer is zeroed right after allocation, so the frame sent to the device is byte-identical without it. Drop the else branch; normal operation is unaffected. Discovered by Atuin - Automated Vulnerability Discovery Engine. Fixes: dad0d04fa7ba ("rsi: Add RS9113 wireless driver") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Tianchu Chen Link: https://patch.msgid.link/90bb2b07007942064c04aa3729cedd9eb1e930b1@linux.dev Signed-off-by: Johannes Berg --- drivers/net/wireless/rsi/rsi_91x_mgmt.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/wireless/rsi/rsi_91x_mgmt.c b/drivers/net/wireless/rsi/rsi_91x_mgmt.c index bb167f03367b..d9dcbb255317 100644 --- a/drivers/net/wireless/rsi/rsi_91x_mgmt.c +++ b/drivers/net/wireless/rsi/rsi_91x_mgmt.c @@ -852,8 +852,6 @@ int rsi_hal_load_key(struct rsi_common *common, memcpy(set_key->tx_mic_key, &data[16], 8); memcpy(set_key->rx_mic_key, &data[24], 8); } - } else { - memset(&set_key[FRAME_DESC_SZ], 0, frame_len - FRAME_DESC_SZ); } skb_put(skb, frame_len); From c1ba7f7f18465e259cf1b4d9c73fc73853d7f790 Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Fri, 4 Sep 2026 13:39:34 +0000 Subject: [PATCH 1275/2366] wifi: wilc1000: fix RX buffer OOB-write in wilc_wlan_handle_isr_ext() wilc_wlan_handle_isr_ext() takes the RX transfer size from the device-reported interrupt status register (a 15-bit field shifted left by 2, up to 131068 bytes) and reads that many bytes from the device into rx_buffer, which is only WILC_RX_BUFF_SIZE (96K) large. The wrap check only handles the current offset; the size itself is never compared against the buffer, so a bogus SDIO device can make the driver OOB-write rx_buffer by up to ~32K with data it controls. The oversized transfer also leaves rx_buffer_offset past the end of the buffer, after which the unsigned wrap check stops working and the overflow can repeat. Drop any transfer whose size exceeds the RX buffer, acknowledging the data interrupt and re-arming the RX engine so the bogus frame is discarded and reception can continue. This also restores the rx_buffer_offset <= WILC_RX_BUFF_SIZE invariant the wrap check relies on. This is not expected to change driver behavior in most cases: without this check, an oversized transfer would most likely corrupt neighboring kernel memory instead of completing anyway, and the drop path performs the same interrupt acknowledgment and RX engine re-arming as the normal path, so subsequent transfers are received unaffected. Discovered by Atuin - Automated Vulnerability Discovery Engine. Fixes: c5c77ba18ea6 ("staging: wilc1000: Add SDIO/SPI 802.11 driver") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Tianchu Chen Link: https://patch.msgid.link/7c971924c6bdccf6c2f75704a5a746e9303aaf64@linux.dev Signed-off-by: Johannes Berg --- drivers/net/wireless/microchip/wilc1000/wlan.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/wireless/microchip/wilc1000/wlan.c b/drivers/net/wireless/microchip/wilc1000/wlan.c index 4b116fe6f9ea..55a77a2e3288 100644 --- a/drivers/net/wireless/microchip/wilc1000/wlan.c +++ b/drivers/net/wireless/microchip/wilc1000/wlan.c @@ -1197,6 +1197,15 @@ static void wilc_wlan_handle_isr_ext(struct wilc *wilc, u32 int_status) if (size <= 0) return; + /* A size exceeding the RX buffer is bogus; drop the transfer + * instead of overflowing the buffer. + */ + if (size > WILC_RX_BUFF_SIZE) { + wilc->hif_func->hif_clear_int_ext(wilc, + DATA_INT_CLR | ENABLE_RX_VMM); + return; + } + if (WILC_RX_BUFF_SIZE - offset < size) offset = 0; From e14bf37bb2b3853012ff160131d1c6233f7a9cc9 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:12 +0200 Subject: [PATCH 1276/2366] wifi: mac80211: don't allow injecting frames wider than the chanctx Frames injected on a monitor interface can carry a radiotap field requesting a bandwidth, which mac80211 passes down to the driver regardless of the the actual operational bandwidth. If the bandwidth requested is too wide, that triggers a warning in hwsim: WARN_ON(hwsim_get_chanwidth(bw) > hwsim_get_chanwidth(confbw)) Drop such frames entirely instead since they cannot be sent. Assisted-by: LLM Fixes: 646e76bb5daf ("mac80211: parse VHT info in injected frames") Reported-by: syzbot+435fdb053cf98bfa5778@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=435fdb053cf98bfa5778 Link: https://patch.msgid.link/20260908122838.201719-13-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- include/net/mac80211.h | 5 ++++- net/mac80211/iface.c | 2 +- net/mac80211/tx.c | 28 ++++++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 9d1fac6e8082..ed6a5874ff96 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -7638,11 +7638,14 @@ bool ieee80211_tx_prepare_skb(struct ieee80211_hw *hw, * * @skb: packet injected by userspace * @dev: the &struct device of this 802.11 device + * @chandef: the channel definition the frame will be transmitted on, or + * %NULL to skip the bandwidth checks * * Return: %true if the radiotap header was parsed, %false otherwise */ bool ieee80211_parse_tx_radiotap(struct sk_buff *skb, - struct net_device *dev); + struct net_device *dev, + const struct cfg80211_chan_def *chandef); /** * struct ieee80211_noa_data - holds temporary data for tracking P2P NoA state diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 842bfb4a7cb6..ca66eb493ac7 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -991,7 +991,7 @@ static u16 ieee80211_monitor_select_queue(struct net_device *dev, /* reset flags and info before parsing radiotap header */ memset(info, 0, sizeof(*info)); - if (!ieee80211_parse_tx_radiotap(skb, dev)) + if (!ieee80211_parse_tx_radiotap(skb, dev, NULL)) return 0; /* doesn't matter, frame will be dropped */ len_rthdr = ieee80211_get_radiotap_len(skb->data); diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index d155fb319a55..c343ed56506a 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2105,8 +2105,29 @@ static bool ieee80211_validate_radiotap_len(struct sk_buff *skb) return true; } +static bool ieee80211_rate_bw_usable(u16 rate_flags, + const struct cfg80211_chan_def *chandef) +{ + int width; + + if (!chandef) + return true; + + if (rate_flags & IEEE80211_TX_RC_160_MHZ_WIDTH) + width = 160; + else if (rate_flags & IEEE80211_TX_RC_80_MHZ_WIDTH) + width = 80; + else if (rate_flags & IEEE80211_TX_RC_40_MHZ_WIDTH) + width = 40; + else + return true; + + return width <= cfg80211_chandef_get_width(chandef); +} + bool ieee80211_parse_tx_radiotap(struct sk_buff *skb, - struct net_device *dev) + struct net_device *dev, + const struct cfg80211_chan_def *chandef) { struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); struct ieee80211_radiotap_iterator iterator; @@ -2280,6 +2301,9 @@ bool ieee80211_parse_tx_radiotap(struct sk_buff *skb, struct ieee80211_supported_band *sband = local->hw.wiphy->bands[info->band]; + if (!ieee80211_rate_bw_usable(rate_flags, chandef)) + return false; + info->control.flags |= IEEE80211_TX_CTRL_RATE_INJECT; for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { @@ -2479,7 +2503,7 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb, * selected chandef above to accurately set injection rates and * retransmissions. */ - if (!ieee80211_parse_tx_radiotap(skb, dev)) + if (!ieee80211_parse_tx_radiotap(skb, dev, chandef)) goto fail_rcu; /* remove the injection radiotap header */ From 4504f3960dc4501c73be9f99eabda2e26e9db41e Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:13 +0200 Subject: [PATCH 1277/2366] wifi: mac80211: reset the AP_VLAN tailroom counter on ifdown On ifup, AP_VLAN interfaces get crypto_tx_tailroom_needed_cnt from the AP interface, but it's never decremented again unless the AP is also brought down. Thus, bringing the same AP_VLAN up again will increment the counter again and eventually hit the sanity check: WARN_ON_ONCE(sdata->crypto_tx_tailroom_needed_cnt != master->crypto_tx_tailroom_needed_cnt); Reset it on ifdown to avoid that. Assisted-by: LLM Fixes: f9dca80b98ca ("mac80211: fix AP_VLAN crypto tailroom calculation") Reported-by: syzbot+de3ee5362db09487ea37@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=de3ee5362db09487ea37 Link: https://patch.msgid.link/20260908122838.201719-14-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index ca66eb493ac7..889c32fd8de1 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -616,6 +616,8 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do RCU_INIT_POINTER(sdata->vif.bss_conf.chanctx_conf, NULL); /* see comment in the default case below */ ieee80211_free_keys(sdata, true); + /* increased by AP value on ifup, so reset on ifdown */ + sdata->crypto_tx_tailroom_needed_cnt = 0; /* no need to tell driver */ break; case NL80211_IFTYPE_MONITOR: From 038e1d126304fd25d507fd4e671232df57bd1799 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:14 +0200 Subject: [PATCH 1278/2366] wifi: mac80211: require a peer station for TDLS setup confirm It's nonsense for the setup confirm to go to station that doesn't even exist, and it hits a warning when building the frame: WARN_ON_ONCE(!sta || !ap_sta) Only accept WLAN_TDLS_SETUP_CONFIRM when the station is already there as a TDLS station. Need to copy the call to ieee80211_tdls_prep_mgmt_packet() since the existing WLAN_TDLS_DISCOVERY_REQUEST already falls through to it. Assisted-by: LLM Fixes: 6f7eaa47e1de ("mac80211: add TDLS QoS param IE on setup-confirm") Reported-by: syzbot+e55106f8389651870be0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e55106f8389651870be0 Link: https://patch.msgid.link/20260908122838.201719-15-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/tdls.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c index 7f40b1d62938..f663d28d9209 100644 --- a/net/mac80211/tdls.c +++ b/net/mac80211/tdls.c @@ -1281,6 +1281,24 @@ int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, peer_capability, initiator, extra_ies, extra_ies_len); break; + case WLAN_TDLS_SETUP_CONFIRM: { + struct sta_info *sta; + + sta = sta_info_get(sdata, peer); + if (!sta || !sta->sta.tdls) { + ret = -ENOLINK; + break; + } + + ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer, + link_id, action_code, + dialog_token, + status_code, + peer_capability, + initiator, extra_ies, + extra_ies_len, 0, NULL); + break; + } case WLAN_TDLS_DISCOVERY_REQUEST: /* * Protect the discovery so we can hear the TDLS discovery @@ -1289,7 +1307,6 @@ int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, */ drv_mgd_protect_tdls_discover(sdata->local, sdata, link_id); fallthrough; - case WLAN_TDLS_SETUP_CONFIRM: case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: /* no special handling */ ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer, From 370872d30349d81dec519e15ea2949fd63511cf7 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:15 +0200 Subject: [PATCH 1279/2366] wifi: mac80211: don't allow link changes when iface is down ieee80211_set_active_links() only checks that the interface is running in the inner __ieee80211_set_active_links(), after drv_can_activate_links() was already called, so using active_links on an interface that's down triggers the check-sdata-in-driver warning. Add the missing check in the debugfs file. Assisted-by: LLM Fixes: 3d9011029227 ("wifi: mac80211: implement link switching") Reported-by: syzbot+582469b3a9ef5f13606b@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=582469b3a9ef5f13606b Link: https://patch.msgid.link/20260908122838.201719-16-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/debugfs_netdev.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c index f3c6a41e4911..8346d3eb1143 100644 --- a/net/mac80211/debugfs_netdev.c +++ b/net/mac80211/debugfs_netdev.c @@ -729,6 +729,9 @@ static ssize_t ieee80211_if_parse_active_links(struct ieee80211_sub_if_data *sda if (kstrtou16(buf, 0, &active_links) || !active_links) return -EINVAL; + if (!ieee80211_sdata_running(sdata)) + return -ENETDOWN; + return ieee80211_set_active_links(&sdata->vif, active_links) ?: buflen; } IEEE80211_IF_FILE_RW(active_links); From b481e64e4498e2c053d5954f546ee02338f6ab63 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:16 +0200 Subject: [PATCH 1280/2366] wifi: mac80211: don't RCU-dereference the mesh CSA settings we just set In the error path of ieee80211_mesh_csa_beacon() the settings that were just assigned are read back with rcu_dereference(), which lockdep then complains about. There's no need to read the pointer at all, tmp_csa_settings still is the right value anyway. Assisted-by: LLM Fixes: b8456a14e9d2 ("{nl,cfg,mac}80211: implement mesh channel switch userspace API") Reported-by: syzbot+b59873f5699e941717ca@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b59873f5699e941717ca Link: https://patch.msgid.link/20260908122838.201719-17-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/mesh.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index bed7ac838250..a35e2d5870b6 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -1559,7 +1559,6 @@ int ieee80211_mesh_csa_beacon(struct ieee80211_sub_if_data *sdata, ret = ieee80211_mesh_rebuild_beacon(sdata); if (ret) { - tmp_csa_settings = rcu_dereference(ifmsh->csa); RCU_INIT_POINTER(ifmsh->csa, NULL); kfree_rcu(tmp_csa_settings, rcu_head); return ret; From 0b1de9feeb8651f7a3bb53ed7c9006e3b5298c01 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:17 +0200 Subject: [PATCH 1281/2366] wifi: mac80211: don't access the TSF of a down interface The tsf debugfs files call the driver even if the interface isn't up, tgriggering check-sdata-in-driver warnings. Reject the access in that case. Assisted-by: LLM Fixes: 37a41b4affa3 ("mac80211: add ieee80211_vif param to tsf functions") Reported-by: syzbot+1c8c45017f784e646b47@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1c8c45017f784e646b47 Link: https://patch.msgid.link/20260908122838.201719-18-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/debugfs_netdev.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c index 8346d3eb1143..6aba22493670 100644 --- a/net/mac80211/debugfs_netdev.c +++ b/net/mac80211/debugfs_netdev.c @@ -657,6 +657,9 @@ static ssize_t ieee80211_if_fmt_tsf( struct ieee80211_local *local = sdata->local; u64 tsf; + if (!ieee80211_sdata_running((struct ieee80211_sub_if_data *)sdata)) + return -ENETDOWN; + tsf = drv_get_tsf(local, (struct ieee80211_sub_if_data *)sdata); return scnprintf(buf, buflen, "0x%016llx\n", (unsigned long long) tsf); @@ -670,6 +673,9 @@ static ssize_t ieee80211_if_parse_tsf( int ret; int tsf_is_delta = 0; + if (!ieee80211_sdata_running(sdata)) + return -ENETDOWN; + if (strncmp(buf, "reset", 5) == 0) { if (local->ops->reset_tsf) { drv_reset_tsf(local, sdata); From cd54bf333f5631d3630bab0a832e9ae648f73515 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:18 +0200 Subject: [PATCH 1282/2366] wifi: mac80211: add HE 6 GHz capability in the scan elems len The HE 6 GHz Band Capability element is in the probe request for every band if 6 GHz is supported, so add the size to scan_ies_len. Otherwise, building probe request elements can fail, triggering the WARN_ON in __ieee80211_start_scan(). Assisted-by: LLM Fixes: 2ad2274c58ee ("mac80211: Add HE 6GHz capabilities element to probe request") Reported-by: syzbot+f961b9f94edbc266f1f8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f961b9f94edbc266f1f8 Link: https://patch.msgid.link/20260908122838.201719-19-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/mac80211/main.c b/net/mac80211/main.c index a59837b9f480..6408e8464338 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -1453,6 +1453,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) sizeof(struct ieee80211_he_mcs_nss_supp) + IEEE80211_HE_PPE_THRES_MAX_LEN; + if (local->hw.wiphy->bands[NL80211_BAND_6GHZ]) + local->scan_ies_len += + 3 + sizeof(struct ieee80211_he_6ghz_capa); + if (supp_eht) local->scan_ies_len += 3 + sizeof(struct ieee80211_eht_cap_elem) + From 860134b3af77970e006feab7e5decb8c84771c7f Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:19 +0200 Subject: [PATCH 1283/2366] wifi: mac80211: mesh: reset the CSA state when leaving ifmsh->csa is allocated in ieee80211_mesh_csa_beacon() and only freed in ieee80211_mesh_finish_csa(), i.e. when the channel switch completes. Leaving the mesh while a switch is still pending therefore leaks it. Additionally, ifmsh->csa_role and ifmsh->chsw_ttl have their state leak in this case, so things can get mixed up in addition to the memory leak. Refactor the reset and call it in ieee80211_stop_mesh() to fix it all. Assisted-by: LLM Reported-by: syzbot+f5752cd6b94fe38be666@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f5752cd6b94fe38be666 Fixes: b8456a14e9d2 ("{nl,cfg,mac}80211: implement mesh channel switch userspace API") Link: https://patch.msgid.link/20260908122838.201719-20-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/mesh.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index a35e2d5870b6..8f8814125375 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -1196,6 +1196,21 @@ int ieee80211_start_mesh(struct ieee80211_sub_if_data *sdata) return 0; } +static void ieee80211_mesh_reset_csa(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + struct mesh_csa_settings *csa; + + /* Reset the TTL value and Initiator flag */ + ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE; + ifmsh->chsw_ttl = 0; + + /* Remove the CSA and MCSP elements from the beacon */ + csa = sdata_dereference(ifmsh->csa, sdata); + RCU_INIT_POINTER(ifmsh->csa, NULL); + kfree_rcu(csa, rcu_head); +} + void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata) { struct ieee80211_local *local = sdata->local; @@ -1206,6 +1221,7 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata) /* abort any running channel switch */ sdata->vif.bss_conf.csa_active = false; + ieee80211_mesh_reset_csa(sdata); ieee80211_vif_unblock_queues_csa(sdata); /* flush STAs and mpaths on this iface */ @@ -1514,19 +1530,10 @@ static void ieee80211_mesh_rx_bcn_presp(struct ieee80211_sub_if_data *sdata, int ieee80211_mesh_finish_csa(struct ieee80211_sub_if_data *sdata, u64 *changed) { - struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; - struct mesh_csa_settings *tmp_csa_settings; - int ret = 0; + int ret; - /* Reset the TTL value and Initiator flag */ - ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE; - ifmsh->chsw_ttl = 0; + ieee80211_mesh_reset_csa(sdata); - /* Remove the CSA and MCSP elements from the beacon */ - tmp_csa_settings = sdata_dereference(ifmsh->csa, sdata); - RCU_INIT_POINTER(ifmsh->csa, NULL); - if (tmp_csa_settings) - kfree_rcu(tmp_csa_settings, rcu_head); ret = ieee80211_mesh_rebuild_beacon(sdata); if (ret) return -EINVAL; From ae97fff6495a8764bc0ef281cfe5444f701e527f Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:20 +0200 Subject: [PATCH 1284/2366] wifi: mac80211: mesh: release the channel if start fails ieee80211_join_mesh() acquires a channel context and then calls ieee80211_start_mesh(), which can fail. In that case, the chanctx isn't released then interface removal will attempt to unassign it after it's removed from the driver, hitting: wlan0: Failed check-sdata-in-driver check, flags: 0x0 WARNING: net/mac80211/driver-ops.c:366 at drv_unassign_vif_chanctx ieee80211_assign_link_chanctx __ieee80211_link_release_channel ieee80211_link_release_channel ieee80211_teardown_sdata unregister_netdevice_many_notify _cfg80211_unregister_wdev ieee80211_remove_interfaces ieee80211_unregister_hw mac80211_hwsim_del_radio hwsim_exit_net Correctly release the channel on start failures. Assisted-by: LLM Reported-by: syzbot+63a84ea9c0f57d6133fa@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=63a84ea9c0f57d6133fa Fixes: 2b5e19677592 ("mac80211: cache mesh beacon") Link: https://patch.msgid.link/20260908122838.201719-21-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 2d5a0abe35db..d3558f0c7550 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -3323,7 +3323,11 @@ static int ieee80211_join_mesh(struct wiphy *wiphy, struct net_device *dev, if (err) return err; - return ieee80211_start_mesh(sdata); + err = ieee80211_start_mesh(sdata); + if (err) + ieee80211_link_release_channel(&sdata->deflink); + + return err; } static int ieee80211_leave_mesh(struct wiphy *wiphy, struct net_device *dev) From 50d3d79dc0743b616afb00d01a626c76758721f7 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 8 Sep 2026 14:28:21 +0200 Subject: [PATCH 1285/2366] wifi: mac80211: set up the TX info early to fix failure paths The previous commit 2c51457d930f ("wifi: mac80211: free ack status frame on TX header build failure") cleaned up the leak, but still left the code a bit messy and the failed SKB didn't get reported to userspace. Fix this up by initialising skb->cb[] earlier, which allows using ieee80211_free_txskb() and therefore reports it for the failure in ieee80211_build_hdr(), and unifies the ieee80211_skb_resize() failure path with it. Assisted-by: LLM Fixes: c3e7724b6bc2 ("mac80211: use ieee80211_free_txskb to fix possible skb leaks") Link: https://patch.msgid.link/20260908122838.201719-22-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index c343ed56506a..814399989b5e 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2981,10 +2981,23 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, */ skb = skb_share_check(skb, GFP_ATOMIC); if (unlikely(!skb)) { - ret = -ENOMEM; - goto free; + /* skb_share_check() already freed the skb */ + if (info_id) + ieee80211_remove_ack_skb(local, info_id); + return ERR_PTR(-ENOMEM); } + /* set this up so failure paths can clean up ack skb */ + info = IEEE80211_SKB_CB(skb); + memset(info, 0, sizeof(*info)); + + info->flags = info_flags; + if (info_id) { + info->status_data = info_id; + info->status_data_idr = 1; + } + info->band = band; + hdr.frame_control = fc; hdr.duration_id = 0; hdr.seq_ctrl = 0; @@ -3023,10 +3036,8 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, head_need += local->tx_headroom; head_need = max_t(int, 0, head_need); if (ieee80211_skb_resize(sdata, skb, head_need, ENCRYPT_DATA)) { - ieee80211_free_txskb(&local->hw, skb); - skb = NULL; ret = -ENOMEM; - goto free; + goto free_txskb; } } @@ -3053,16 +3064,6 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, skb_reset_mac_header(skb); - info = IEEE80211_SKB_CB(skb); - memset(info, 0, sizeof(*info)); - - info->flags = info_flags; - if (info_id) { - info->status_data = info_id; - info->status_data_idr = 1; - } - info->band = band; - if (likely(!cookie)) { ctrl_flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); @@ -3086,16 +3087,17 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, pre_conf_link_id, link_id); #endif ret = -EINVAL; - goto free; + goto free_txskb; } } info->control.flags = ctrl_flags; return skb; + free_txskb: + ieee80211_free_txskb(&local->hw, skb); + return ERR_PTR(ret); free: - if (info_id) - ieee80211_remove_ack_skb(local, info_id); kfree_skb(skb); return ERR_PTR(ret); } From d144a494d81fcf2d1c5cf58b01c655bb8bafc701 Mon Sep 17 00:00:00 2001 From: Gabriel Windlin Date: Tue, 8 Sep 2026 22:34:11 +0200 Subject: [PATCH 1286/2366] MAINTAINERS: fix sysfs-platform-ayaneo-ec documentation path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documentation file for the AYANEO platform EC driver was added as Documentation/ABI/testing/sysfs-platform-ayaneo-ec, but MAINTAINERS references it without the '-ec' suffix, causing 'make refcheckdocs' to report a broken reference. Update the file entry to point to the correct file name. Signed-off-by: Gabriel Windlin Link: https://patch.msgid.link/20260908203412.608606-1-gawindlin@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..0a0dbb9cb126 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4512,7 +4512,7 @@ AYANEO PLATFORM EC DRIVER M: Antheas Kapenekakis L: platform-driver-x86@vger.kernel.org S: Maintained -F: Documentation/ABI/testing/sysfs-platform-ayaneo +F: Documentation/ABI/testing/sysfs-platform-ayaneo-ec F: drivers/platform/x86/ayaneo-ec.c AZ6007 DVB DRIVER From 0ee150794c75bcd0be0e24ff3394f433cbae18cc Mon Sep 17 00:00:00 2001 From: Bjoern Doebel Date: Tue, 8 Sep 2026 16:10:00 +0000 Subject: [PATCH 1287/2366] smb: client: fix heap overflow in DACL owner/group rewrite When id_mode_to_cifs_acl rewrites an existing DACL, it allocates a buffer sized according to the on-disk DACL length reported by dacl_ptr->size. However, replace_sids_and_copy_aces may rewrite each ACE with a new owner/group SID obtained from the cifs.idmap upcall. Those SIDs can have up to SID_MAX_SUB_AUTHORITIES (15) sub-authorities, making each ACE up to 76 bytes (sizeof(struct smb_ace)). If the original DACL contains short SIDs (e.g., 1 sub-authority) while the replacement SIDs are long, the rewritten ACEs overflow the allocation. Fix this by always budgeting for worst-case SID expansion: allocate sizeof(struct smb_acl) plus num_aces * sizeof(struct smb_ace), which covers the smb_acl header and room for every ACE at maximum SID size. This replaces the previous split logic that used dacl_ptr->size for cifsacl mounts but num_aces * sizeof(struct smb_ace) for mode_from_sid mounts: both paths can trigger the same rewrite and need the same headroom. KASAN reports this as: BUG: KASAN: slab-out-of-bounds in build_sec_desc+0x1e8a/0x2680 [cifs] Write of size 4 at addr ffff8881a5e25374 by task chown/5298 ... The buggy address is located 0 bytes to the right of allocated 884-byte region [ffff8881a5e25000, ffff8881a5e25374) Cc: stable@vger.kernel.org Fixes: bc3e9dd9d104 ("cifs: Change SIDs in ACEs while transferring file ownership.") Assisted-by: Kiro:claude-opus-4.6 Signed-off-by: Bjoern Doebel Reviewed-by: Namjae Jeon Fixes: 5c3564852c58 ("cifs: Minimize the number of cifs_acl memory allocations") Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsacl.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index def8908dd7e9..3e96e151df35 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -1837,11 +1837,13 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode, cifs_put_tlink(tlink); return rc; } - if (mode_from_sid) - nsecdesclen += - le16_to_cpu(dacl_ptr->num_aces) * sizeof(struct smb_ace); - else /* cifsacl */ - nsecdesclen += le16_to_cpu(dacl_ptr->size); + /* + * Worst case: every ACE is rewritten with a new SID of + * SID_MAX_SUB_AUTHORITIES sub-auths -> sizeof(smb_ace) each, + * plus the smb_acl header replace_sids_and_copy_aces() emits. + */ + nsecdesclen += sizeof(struct smb_acl) + + le16_to_cpu(dacl_ptr->num_aces) * sizeof(struct smb_ace); } } From d05045177a855386bca5e1909e08d06290e6e3b3 Mon Sep 17 00:00:00 2001 From: Bjoern Doebel Date: Tue, 8 Sep 2026 16:10:01 +0000 Subject: [PATCH 1288/2366] smb: client: fail DACL rewrite when the new DACL exceeds 64K replace_sids_and_copy_aces() and set_chmod_dacl() accumulate the size of the DACL they build in a u16. That accumulator can wrap. validate_dacl() caps num_aces at (dacl_size - sizeof(struct smb_acl)) / 20, i.e. 3276 for a maximally sized DACL, while each rewritten ACE can grow to sizeof(struct smb_ace) (76 bytes) once its SID is replaced with one carrying SID_MAX_SUB_AUTHORITIES sub-authorities. The worst case is therefore sizeof(struct smb_acl) + 3276 * 76 = 248984 bytes, far beyond what a u16 can hold. A wraparound is reached with 863 ACEs. After the wraparound, ndacl_ptr->size becomes meaningless and the offset will point anywhere in the ACE array. As a result, we will see corruption of the DACL, which then gets sent to the server. This is not an out-of-bounds write as the allocation now covers the worst-case expansion, so writes will always go into the buffer. Adjust the code to use a u32 internally and return -EOVERFLOW in the overflow case. The operation must be refused, because a DACL can only hold 2^16-1 bytes on the wire and larger DACLs cannot be represented. set_chmod_dacl() carries the same pattern and is fixed the same way. It only wraps once the source DACL comes within roughly 380 bytes of the 64K ceiling, but the failure mode is identical. Suggested-by: Namjae Jeon Cc: stable@vger.kernel.org Fixes: f5065508897a ("cifs: Retain old ACEs when converting between mode bits and ACL.") Assisted-by: Kiro:claude-opus-5 Signed-off-by: Bjoern Doebel Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsacl.c | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index 3e96e151df35..c5e47a835f99 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -1096,13 +1096,13 @@ unsigned int setup_special_user_owner_ACE(struct smb_ace *pntace) static void populate_new_aces(char *nacl_base, struct smb_sid *pownersid, struct smb_sid *pgrpsid, - __u64 *pnmode, u16 *pnum_aces, u16 *pnsize, + __u64 *pnmode, u16 *pnum_aces, u32 *pnsize, bool modefromsid, bool posix) { __u64 nmode; u16 num_aces = 0; - u16 nsize = 0; + u32 nsize = 0; __u64 user_mode; __u64 group_mode; __u64 other_mode; @@ -1201,17 +1201,17 @@ static void populate_new_aces(char *nacl_base, *pnsize = nsize; } -static __u16 replace_sids_and_copy_aces(struct smb_acl *pdacl, struct smb_acl *pndacl, - struct smb_sid *pownersid, struct smb_sid *pgrpsid, - struct smb_sid *pnownersid, struct smb_sid *pngrpsid, - int *aclflag) +static int replace_sids_and_copy_aces(struct smb_acl *pdacl, struct smb_acl *pndacl, + struct smb_sid *pownersid, struct smb_sid *pgrpsid, + struct smb_sid *pnownersid, struct smb_sid *pngrpsid, + int *aclflag, u16 *pnsize) { int i; u16 size = 0; struct smb_ace *pntace = NULL; char *acl_base = NULL; u16 src_num_aces = 0; - u16 nsize = 0; + u32 nsize = 0; struct smb_ace *pnntace = NULL; char *nacl_base = NULL; u16 ace_size = 0; @@ -1240,9 +1240,12 @@ static __u16 replace_sids_and_copy_aces(struct smb_acl *pdacl, struct smb_acl *p size += le16_to_cpu(pntace->size); nsize += ace_size; + if (nsize > U16_MAX) + return -EOVERFLOW; } - return nsize; + *pnsize = nsize; + return 0; } static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl, @@ -1254,7 +1257,7 @@ static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl, struct smb_ace *pntace = NULL; char *acl_base = NULL; u16 src_num_aces = 0; - u16 nsize = 0; + u32 nsize = 0; struct smb_ace *pnntace = NULL; char *nacl_base = NULL; u16 num_aces = 0; @@ -1305,6 +1308,8 @@ static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl, nsize += cifs_copy_ace(pnntace, pntace, NULL); num_aces++; + if (nsize > U16_MAX) + return -EOVERFLOW; next_ace: size += le16_to_cpu(pntace->size); @@ -1321,6 +1326,10 @@ static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl, } finalize_dacl: + /* The DACL size field is 16-bit on the wire, see MS-DTYP 2.4.5 */ + if (nsize > U16_MAX) + return -EOVERFLOW; + pndacl->num_aces = cpu_to_le16(num_aces); pndacl->size = cpu_to_le16(nsize); @@ -1473,6 +1482,8 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd, rc = set_chmod_dacl(dacl_ptr, ndacl_ptr, owner_sid_ptr, group_sid_ptr, pnmode, mode_from_sid, posix); + if (rc) + return rc; sidsoffset = ndacloffset + le16_to_cpu(ndacl_ptr->size); /* copy the non-dacl portion of secdesc */ @@ -1548,10 +1559,12 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd, if (dacloffset) { /* Replace ACEs for old owner with new one */ - size = replace_sids_and_copy_aces(dacl_ptr, ndacl_ptr, - owner_sid_ptr, group_sid_ptr, - nowner_sid_ptr, ngroup_sid_ptr, - aclflag); + rc = replace_sids_and_copy_aces(dacl_ptr, ndacl_ptr, + owner_sid_ptr, group_sid_ptr, + nowner_sid_ptr, ngroup_sid_ptr, + aclflag, &size); + if (rc) + goto chown_chgrp_exit; ndacl_ptr->size = cpu_to_le16(size); } From 4600b4d1a9ee730d03ddac5ce409cd2730ce8c0c Mon Sep 17 00:00:00 2001 From: Esben Haabendal Date: Mon, 31 Aug 2026 14:21:32 +0200 Subject: [PATCH 1289/2366] drm/bridge: ti-sn65dsi83: Fix error handling in sn65dsi83_reset_work() The error handling of sn65dsi83_reset_pipe() in sn65dsi83_reset_work() has seen a couple of changes that seems to cause a bit of confusion. While sn65dsi83_reset_work() has implemented an early exit if sn65dsi83_reset_pipe() fails since it was added, when a commit from Maxime Ripard switched to use drm_bridge_helper_reset_crtc() [1] the sn65dsi83_reset_pipe() function would no longer return an error code, so the early exit was then a no-op, and even on sn65dsi83_reset_pipe() failure, enable_irq() has been called. When drm_bridge_enter()/drm_bridge_exit() resource protection was added, the drm_bridge_exit() incidentally was always called, which is the correct approach. But only because the early exit in sn65dsi83_reset_pipe() was never hit because sn65dsi83_reset_pipe() always returns 0. In order get back to a situation where enable_irq() is not called on sn65dsi83_reset_pipe() failure, which should help protect against irq storms, we need to reintroduce a non-zero return value from sn65dsi83_reset_pipe() on error, and fix sn65dsi83_reset_work() so that we always exit the DRM bridge critical section with drm_bridge_exit(). [1] commit e17fadff7ab9 ("drm/bridge: ti-sn65dsi83: Switch to drm_bridge_helper_reset_crtc") [2] commit d2e8d1bc840b ("drm/bridge: ti-sn65dsi83: protect device resources on unplug") Fixes: e17fadff7ab9 ("drm/bridge: ti-sn65dsi83: Switch to drm_bridge_helper_reset_crtc") Cc: stable@vger.kernel.org Signed-off-by: Esben Haabendal Reviewed-by: Herve Codina Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260831-ti-sn65dsi83-fixes-v5-1-e712765d6c4f@geanix.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index f9fdbf48c6b3..526826ba4524 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -403,7 +403,7 @@ static int sn65dsi83_reset_pipe(struct sn65dsi83 *sn65dsi83) drm_modeset_drop_locks(&ctx); drm_modeset_acquire_fini(&ctx); - return 0; + return err; } static void sn65dsi83_reset_work(struct work_struct *ws) @@ -419,11 +419,13 @@ static void sn65dsi83_reset_work(struct work_struct *ws) ret = sn65dsi83_reset_pipe(ctx); if (ret) { dev_err(ctx->dev, "reset pipe failed %pe\n", ERR_PTR(ret)); - return; + goto bridge_exit; } + if (ctx->irq) enable_irq(ctx->irq); +bridge_exit: drm_bridge_exit(idx); } From 20fce5b34b21a995839743b4917a1edd2fd503ba Mon Sep 17 00:00:00 2001 From: Varun Gupta Date: Mon, 7 Sep 2026 10:30:12 +0530 Subject: [PATCH 1290/2366] drm/xe: Guard page-fault worker with runtime PM check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During VM teardown, the VM's runtime PM reference is dropped asynchronously, allowing the device to autosuspend while stale page faults belonging to the now-dead VM are still queued. When the page-fault worker later tries to ack one of these, it calls into guc_ct_send_locked() on an already-suspended device, tripping:   Assertion `!xe_pm_runtime_suspended(xe)` failed!   WARNING at xe_device.c:1267 xe_device_assert_mem_access+0x11c/0x140 [xe] A live VM/exec queue always holds a PM reference while it has outstanding work, so if the device is suspended at ack time, the owning context is already gone and the fault is stale. Take a runtime PM reference across the entire pagefault queue worker to safely deliver acks for torn-down VMs. v3: - Move PM ref to the generic xe_pagefault_queue_work using guard(xe_pm_runtime)(xe) instead of tracking it in the GuC backend(Matt Brost). v2: - Hold PM ref across the entire batch (begin/end) instead of per-ack. This prevents the device from autosuspending mid-batch, which would leave write_only acks written but the end flush skipped, and skip counter++, desyncing the cadence check.(Himal) - Add a comment explaining stale faults.(Himal) Fixes: f289f7807119 ("drm/xe: Add xe_guc_pagefault layer") Signed-off-by: Varun Gupta Reviewed-by: Matthew Brost Reviewed-by: Tejas Upadhyay Link: https://patch.msgid.link/20260907050011.497181-2-varun.gupta@intel.com Signed-off-by: Tejas Upadhyay (cherry picked from commit fcc2431d2213dc4d04250c4f1ae87d9c3ae0d455) Signed-off-by: Rodrigo Vivi [Rodrigo: Added xe_device struct for compatibility while cherry-picking] --- drivers/gpu/drm/xe/xe_pagefault.c | 10 ++++++++++ drivers/gpu/drm/xe/xe_pagefault_types.h | 3 +++ 2 files changed, 13 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_pagefault.c b/drivers/gpu/drm/xe/xe_pagefault.c index dbf8f71d3328..a4986df8328d 100644 --- a/drivers/gpu/drm/xe/xe_pagefault.c +++ b/drivers/gpu/drm/xe/xe_pagefault.c @@ -16,6 +16,7 @@ #include "xe_hw_engine.h" #include "xe_pagefault.h" #include "xe_pagefault_types.h" +#include "xe_pm.h" #include "xe_svm.h" #include "xe_trace_bo.h" #include "xe_vm.h" @@ -292,9 +293,17 @@ static void xe_pagefault_queue_work(struct work_struct *w) { struct xe_pagefault_queue *pf_queue = container_of(w, typeof(*pf_queue), worker); + struct xe_device *xe = pf_queue->xe; struct xe_pagefault pf; unsigned long threshold; + /* + * A live VM holds a PM reference, but a torn-down VM does not. + * Guard the entire worker loop to safely drain stale faults and + * prevent autosuspends from desyncing batched CT flushes. + */ + guard(xe_pm_runtime)(xe); + #define USM_QUEUE_MAX_RUNTIME_MS 20 threshold = jiffies + msecs_to_jiffies(USM_QUEUE_MAX_RUNTIME_MS); @@ -365,6 +374,7 @@ static int xe_pagefault_queue_init(struct xe_device *xe, drm_dbg(&xe->drm, "xe_pagefault_entry_size=%d, total_num_eus=%d, pf_queue->size=%u", xe_pagefault_entry_size(), total_num_eus, pf_queue->size); + pf_queue->xe = xe; spin_lock_init(&pf_queue->lock); INIT_WORK(&pf_queue->worker, xe_pagefault_queue_work); diff --git a/drivers/gpu/drm/xe/xe_pagefault_types.h b/drivers/gpu/drm/xe/xe_pagefault_types.h index c4ee625b93dd..f63a12aa0d4f 100644 --- a/drivers/gpu/drm/xe/xe_pagefault_types.h +++ b/drivers/gpu/drm/xe/xe_pagefault_types.h @@ -8,6 +8,7 @@ #include +struct xe_device; struct xe_gt; struct xe_pagefault; @@ -118,6 +119,8 @@ struct xe_pagefault { * queue to absorb the device’s worst-case number of outstanding faults. */ struct xe_pagefault_queue { + /** @xe: Back-pointer to the Xe device */ + struct xe_device *xe; /** * @data: Data in queue containing struct xe_pagefault, protected by * @lock From f5fcf7e638b904397ec0f66d3ea6766ef0cfe25b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Thu, 3 Sep 2026 13:45:52 +0200 Subject: [PATCH 1291/2366] drm/xe: Flush LSC untyped L1 dataport cache after rcs/ccs batches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit emit_render_cache_flush() sets PIPE_CONTROL0_HDC_PIPELINE_FLUSH to flush the L2/HDC data cache before fence signalling, but it never requests a flush of the LSC untyped L1 data cache via the 'Untyped Data-Port Cache Flush Enable' bit in PIPE_CONTROL DWord0[11]. Per the Bspec, in 3D pipeline mode HDC Pipeline Flush is documented to also flush/invalidate the untyped L1 cache, but only depending on how HDC_CHICKEN0[13:11] is programmed. Starting with MTL, this coupling between HDC Pipeline Flush and the untyped L1 cache flush no longer holds in practice, regardless of how HDC_CHICKEN0 is programmed, so relying on it is not safe on newer platforms such as BMG. Mesa's Vulkan driver (anv) has been assuming the kernel flushes both caches between submissions, and hit user-visible corruption in apps such as Llama.cpp because of this gap; it now works around it by flushing both caches again from userspace at the end of every command buffer. Correctness between submissions on the same queue is userspace's responsibility and belongs in Mesa, not the kernel. However, for security we must ensure stale data can't leak through the untyped L1 dataport cache once memory is reclaimed or evicted, which requires the KMD to flush it before releasing memory for reuse. Prior to MTL, HDC_CHICKEN0 could be programmed (as already done for DG2 via Wa_22010960976/Wa_14013347512) to reliably keep HDC Pipeline Flush coupled to the untyped L1 cache flush, so those platforms are unaffected. Mesa's own anv driver found that on MTL the HW disconnected the two independently of how HDC_CHICKEN0 is programmed, and could not bring the old behavior back even by writing the register by hand; see Mesa commit 7c2ff46a4fc3 ("anv: don't prevent L1 untyped cache flush in 3D mode"). The kernel can't reliably request the flush from the CS on MTL either, so restrict the new PIPE_CONTROL bit to GRAPHICS_VERx100 >= 2000 (Xe2 and later), where it can be relied on. Explicitly set PIPE_CONTROL0_UNTYPED_DATAPORT_CACHE_FLUSH together with PIPE_CONTROL0_HDC_PIPELINE_FLUSH in emit_render_cache_flush() on Xe2 and later, so the L1 data cache is known clean before memory is released for reuse, without depending on undocumented platform-specific HDC_CHICKEN0 behavior. Bspec: 56551 Link: https://gitlab.freedesktop.org/mesa/mesa/-/commit/7c2ff46a4fc3e537573ac9503057e0cd29b6fff3 Fixes: 9f8f93bee3ef ("drm/xe: Emit a render cache flush after each rcs/ccs batch") Reported-by: Lionel Landwerlin Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/8909 Cc: José Roberto de Souza Cc: intel-xe@lists.freedesktop.org Cc: # v6.8+ Assisted-by: GitHub_Copilot:claude-sonnet-5 Signed-off-by: Thomas Hellström Reviewed-by: Matthew Auld Link: https://patch.msgid.link/20260903114552.48634-1-thomas.hellstrom@linux.intel.com (cherry picked from commit 434514b6fe731e873808297c268fc52cdf4a1ce6) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/instructions/xe_gpu_commands.h | 1 + drivers/gpu/drm/xe/xe_ring_ops.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/gpu/drm/xe/instructions/xe_gpu_commands.h b/drivers/gpu/drm/xe/instructions/xe_gpu_commands.h index 18d0fde8c98f..faf8d7e2c5c1 100644 --- a/drivers/gpu/drm/xe/instructions/xe_gpu_commands.h +++ b/drivers/gpu/drm/xe/instructions/xe_gpu_commands.h @@ -46,6 +46,7 @@ #define GFX_OP_PIPE_CONTROL(len) ((0x3<<29)|(0x3<<27)|(0x2<<24)|((len)-2)) #define PIPE_CONTROL0_QUEUE_DRAIN_MODE BIT(12) +#define PIPE_CONTROL0_UNTYPED_DATAPORT_CACHE_FLUSH BIT(11) /* gen12 */ #define PIPE_CONTROL0_L3_READ_ONLY_CACHE_INVALIDATE BIT(10) /* gen12 */ #define PIPE_CONTROL0_HDC_PIPELINE_FLUSH BIT(9) /* gen12 */ diff --git a/drivers/gpu/drm/xe/xe_ring_ops.c b/drivers/gpu/drm/xe/xe_ring_ops.c index 39a670e91ba7..08b4a4283e96 100644 --- a/drivers/gpu/drm/xe/xe_ring_ops.c +++ b/drivers/gpu/drm/xe/xe_ring_ops.c @@ -212,6 +212,7 @@ static int emit_render_cache_flush(struct xe_sched_job *job, u32 *dw, int i) { struct xe_exec_queue *q = job->q; struct xe_gt *gt = q->gt; + struct xe_device *xe = gt_to_xe(gt); bool lacks_render = !(gt->info.engine_mask & XE_HW_ENGINE_RCS_MASK); u32 flags0, flags1; @@ -220,6 +221,16 @@ static int emit_render_cache_flush(struct xe_sched_job *job, u32 *dw, int i) LRC_PPHWSP_FLUSH_INVAL_SCRATCH_ADDR, 0); flags0 = PIPE_CONTROL0_HDC_PIPELINE_FLUSH; + /* + * Prior to MTL, HDC Pipeline Flush reliably also flushes the LSC + * untyped L1 dataport cache, provided HDC_CHICKEN0 is programmed + * correctly. Starting with MTL that coupling no longer holds + * regardless of how HDC_CHICKEN0 is programmed, but explicitly + * requesting the flush via PIPE_CONTROL is itself only reliable + * from Xe2 onward, so only gate it in on Xe2+. + */ + if (GRAPHICS_VERx100(xe) >= 2000) + flags0 |= PIPE_CONTROL0_UNTYPED_DATAPORT_CACHE_FLUSH; flags1 = (PIPE_CONTROL_TILE_CACHE_FLUSH | PIPE_CONTROL_RENDER_TARGET_CACHE_FLUSH | PIPE_CONTROL_DEPTH_CACHE_FLUSH | From 6028b543884f8735e057ec9eea4908cd61cab230 Mon Sep 17 00:00:00 2001 From: Gabriel Krisman Bertazi Date: Wed, 2 Sep 2026 20:00:41 -0300 Subject: [PATCH 1292/2366] io_uring/net: don't overconsume buffers when using MSG_TRUNC When a recv/recvmsg is issued with MSG_TRUNC and the incoming packet is larger than the provided buffer, the net layer returns the full length of the packet rather than the number of bytes actually copied into the buffer. As a result, io_uring advances more of the provided buffer ring than was actually filled. Use the actual filled region size to consume the buffer, but still return the full size to preserve MSG_TRUNC semantics. Take care with multishot, because that seems to already truncate the consumption based on the available payload size. This was reported in https://github.com/axboe/liburing/issues/1619. Fixes: ae98dbf43d75 ("io_uring/kbuf: add support for incremental buffer consumption") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260728191454.1850326-1-krisman@suse.de Signed-off-by: Gabriel Krisman Bertazi Link: https://patch.msgid.link/20260902230041.1320658-3-krisman@suse.de [axboe: fold in size_t unsigned fix] Signed-off-by: Jens Axboe --- io_uring/net.c | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/io_uring/net.c b/io_uring/net.c index 647156c9331a..050ed274170a 100644 --- a/io_uring/net.c +++ b/io_uring/net.c @@ -853,7 +853,7 @@ int io_recvmsg_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) static inline bool io_recv_finish(struct io_kiocb *req, struct io_async_msghdr *kmsg, struct io_br_sel *sel, bool mshot_finished, - unsigned issue_flags) + unsigned issue_flags, int consumed) { struct io_sr_msg *sr = io_kiocb_to_cmd(req, struct io_sr_msg); unsigned int cflags = 0; @@ -877,7 +877,7 @@ static inline bool io_recv_finish(struct io_kiocb *req, if (sr->flags & IORING_RECVSEND_BUNDLE) { size_t this_ret = sel->val - sr->done_io; - cflags |= io_put_kbufs(req, this_ret, sel->buf_list, io_bundle_nbufs(kmsg, this_ret)); + cflags |= io_put_kbufs(req, consumed, sel->buf_list, io_bundle_nbufs(kmsg, consumed)); if (sr->flags & IORING_RECV_RETRY) cflags = req->cqe.flags | (cflags & CQE_F_MASK); if (sr->mshot_len && sel->val >= sr->mshot_len) @@ -899,7 +899,7 @@ static inline bool io_recv_finish(struct io_kiocb *req, return false; } } else { - cflags |= io_put_kbuf(req, sel->val, sel->buf_list); + cflags |= io_put_kbuf(req, consumed, sel->buf_list); } /* @@ -1027,6 +1027,8 @@ int io_recvmsg(struct io_kiocb *req, unsigned int issue_flags) int ret, min_ret = 0; bool force_nonblock = issue_flags & IO_URING_F_NONBLOCK; bool mshot_finished = true; + int consumed = 0; + size_t len; sock = sock_from_file(req->file); if (unlikely(!sock)) @@ -1042,9 +1044,8 @@ int io_recvmsg(struct io_kiocb *req, unsigned int issue_flags) retry_multishot: sel.buf_list = NULL; + len = sr->len; if (io_do_buffer_select(req)) { - size_t len = sr->len; - sel = io_buffer_select(req, &len, sr->buf_group, issue_flags); if (!sel.addr) return -ENOBUFS; @@ -1065,6 +1066,7 @@ int io_recvmsg(struct io_kiocb *req, unsigned int issue_flags) if (req->flags & REQ_F_APOLL_MULTISHOT) { ret = io_recvmsg_multishot(sock, sr, kmsg, flags, &mshot_finished); + consumed = ret; } else { /* disable partial retry for recvmsg with cmsg attached */ if (flags & MSG_WAITALL && !kmsg->msg.msg_controllen) @@ -1072,6 +1074,15 @@ int io_recvmsg(struct io_kiocb *req, unsigned int issue_flags) ret = __sys_recvmsg_sock(sock, &kmsg->msg, sr->umsg, kmsg->uaddr, flags); + /* + * With MSG_TRUNC, the net layer will return the full size of + * the packet, even if we only filled part of it in the buffers. + * Adjust the returned size to consume only the real part of the + * buffer. + */ + consumed = ret; + if (ret > 0) + consumed = min_t(size_t, ret, len); } if (ret < min_ret) { @@ -1098,7 +1109,7 @@ int io_recvmsg(struct io_kiocb *req, unsigned int issue_flags) io_kbuf_recycle(req, sel.buf_list, issue_flags); sel.val = ret; - if (!io_recv_finish(req, kmsg, &sel, mshot_finished, issue_flags)) + if (!io_recv_finish(req, kmsg, &sel, mshot_finished, issue_flags, consumed)) goto retry_multishot; return sel.val; @@ -1185,9 +1196,10 @@ int io_recv(struct io_kiocb *req, unsigned int issue_flags) struct io_br_sel sel; struct socket *sock; unsigned flags; - int ret, min_ret = 0; + int ret, min_ret = 0, consumed = 0; bool force_nonblock = issue_flags & IO_URING_F_NONBLOCK; bool mshot_finished; + size_t len = 0; sock = sock_from_file(req->file); if (unlikely(!sock)) @@ -1215,6 +1227,7 @@ int io_recv(struct io_kiocb *req, unsigned int issue_flags) retry_multishot: sel.buf_list = NULL; + len = sr->len; if (io_do_buffer_select(req)) { sel.val = sr->len; ret = io_recv_buf_select(req, kmsg, &sel, issue_flags); @@ -1222,6 +1235,7 @@ int io_recv(struct io_kiocb *req, unsigned int issue_flags) kmsg->msg.msg_inq = -1; goto out_free; } + len = ret; sr->buf = NULL; } @@ -1252,6 +1266,17 @@ int io_recv(struct io_kiocb *req, unsigned int issue_flags) } mshot_finished = ret <= 0; + + /* + * With MSG_TRUNC, the net layer will return the full size of + * the packet, even if we only filled part of it in the buffers. + * Adjust the returned size to consume only the real part of the + * buffer. + */ + consumed = ret; + if (ret > 0) + consumed = min_t(size_t, ret, len); + if (ret > 0) ret += sr->done_io; else if (sr->done_io) @@ -1260,7 +1285,7 @@ int io_recv(struct io_kiocb *req, unsigned int issue_flags) io_kbuf_recycle(req, sel.buf_list, issue_flags); sel.val = ret; - if (!io_recv_finish(req, kmsg, &sel, mshot_finished, issue_flags)) + if (!io_recv_finish(req, kmsg, &sel, mshot_finished, issue_flags, consumed)) goto retry_multishot; return sel.val; From 47ccc3f1c615a46c25cbf7f3ae60df30b40eb2e6 Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Wed, 2 Sep 2026 15:01:59 -0600 Subject: [PATCH 1293/2366] io_uring/rw: keep CQE flags on iopoll requests when adding kbuf flags io_do_iopoll() assigns the result of io_put_kbuf() to the request's CQE flags upon completion. This overwrites any CQE flags that may have been set by the opcode-specific layer. (For example, if __io_uring_cmd_done() had set IORING_CQE_F_32, it would be cleared.) Switch the = to an |= so the kbuf flags are added to the existing CQE flags rather than replacing them. io_req_rw_complete() does the same with the io_put_kbuf() result. Fixes: e26dca67fde1 ("io_uring: add support for IORING_SETUP_CQE_MIXED") Reported-by: sashiko-bot@kernel.org Link: https://sashiko.dev/#/message/20260827191705.D53C91F000E9%40smtp.kernel.org Signed-off-by: Caleb Sander Mateos Reviewed-by: Anuj Gupta Link: https://patch.msgid.link/20260902210200.2336720-1-csander@purestorage.com Signed-off-by: Jens Axboe --- io_uring/rw.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/io_uring/rw.c b/io_uring/rw.c index 3e22f294bdf2..432820f86251 100644 --- a/io_uring/rw.c +++ b/io_uring/rw.c @@ -880,6 +880,7 @@ static int io_rw_init_file(struct io_kiocb *req, fmode_t mode, int rw_type) kiocb->private = NULL; kiocb->ki_flags |= IOCB_HIPRI; req->iopoll_completed = 0; + req->cqe.flags = 0; if (ctx->flags & IORING_SETUP_HYBRID_IOPOLL) { /* make sure every req only blocks once*/ req->flags &= ~REQ_F_IOPOLL_STATE; @@ -1382,7 +1383,7 @@ int io_do_iopoll(struct io_ring_ctx *ctx, bool force_nonspin) list_del(&req->iopoll_node); wq_list_add_tail(&req->comp_list, &ctx->submit_state.compl_reqs); nr_events++; - req->cqe.flags = io_put_kbuf(req, max(req->cqe.res, 0), NULL); + req->cqe.flags |= io_put_kbuf(req, max(req->cqe.res, 0), NULL); if (!io_is_uring_cmd(req)) io_req_rw_cleanup(req, 0); } From 3fc9f0558c95152b14d31badd5b27e5978bfb204 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 9 Sep 2026 07:41:54 -0700 Subject: [PATCH 1294/2366] drm/ci: Update xfails for kms_cursor_legacy regression These four started failing with some change not merged thru the msm tree. Update xfails to reflect reality. Signed-off-by: Rob Clark --- drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt b/drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt index 4546363447ff..0f3d85e4845e 100644 --- a/drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt +++ b/drivers/gpu/drm/ci/xfails/msm-apq8016-fails.txt @@ -7,3 +7,10 @@ kms_hdmi_inject@inject-4k,Fail kms_lease@lease-uevent,Fail msm/msm_mapping@memptrs,Fail msm/msm_mapping@ring,Fail + +# Started failing with v7.3-rc2 backmerge +# https://gitlab.freedesktop.org/drm/msm/-/work_items/104 +kms_cursor_legacy@single-move,Fail +kms_cursor_legacy@torture-bo,Fail +kms_cursor_legacy@forked-bo,Fail +kms_cursor_legacy@torture-move,Fail From e2d5b01f878d76bd1142e512a0b979a1d3cd0abf Mon Sep 17 00:00:00 2001 From: Meijing Zhao Date: Wed, 2 Sep 2026 15:59:44 +0800 Subject: [PATCH 1295/2366] mm: memblock: show all region flags in debugfs Commit 493f349e38d0 ("memblock: Add flags and nid info in memblock debugfs") made memblock_debug_show() stop after finding the first set flag. A memblock region can carry multiple flags, so the remaining flags are hidden from debugfs. Walk all bits in the region flags and print every set flag separated by "|". Keep walking beyond flagname[] so that a set flag without a known name is reported as UNKNOWN rather than silently ignored. Fixes: 493f349e38d0 ("memblock: Add flags and nid info in memblock debugfs") Signed-off-by: Meijing Zhao Link: https://patch.msgid.link/20260902075944.3742866-1-zhaomeijing100@gmail.com Signed-off-by: Mike Rapoport (Microsoft) --- mm/memblock.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/mm/memblock.c b/mm/memblock.c index 9ce86349a29f..021db49eb7fc 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -2908,14 +2908,18 @@ static int memblock_debug_show(struct seq_file *m, void *private) else seq_printf(m, "%4c ", 'x'); if (reg->flags) { - for (j = 0; j < count; j++) { - if (reg->flags & (1U << j)) { - seq_printf(m, "%s\n", flagname[j]); - break; - } + unsigned int flags = reg->flags; + bool first = true; + + for (j = 0; flags; j++, flags >>= 1) { + if (!(flags & 1)) + continue; + if (!first) + seq_putc(m, '|'); + seq_puts(m, j < count ? flagname[j] : "UNKNOWN"); + first = false; } - if (j == count) - seq_printf(m, "%s\n", "UNKNOWN"); + seq_putc(m, '\n'); } else { seq_printf(m, "%s\n", "NONE"); } From 6e33dc90df108c76899dc544a42d17adcba61668 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Mon, 31 Aug 2026 13:21:43 +0300 Subject: [PATCH 1296/2366] MAINTAINERS: update memblock tree URLs memblock tree moved into mm/ namespace at git.kernel.org. Update the T: entries for memblock to match it. Link: https://patch.msgid.link/20260831102143.69265-1-rppt@kernel.org Signed-off-by: Mike Rapoport (Microsoft) --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..1731d197c33c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17036,8 +17036,8 @@ MEMBLOCK AND MEMORY MANAGEMENT INITIALIZATION M: Mike Rapoport L: linux-mm@kvack.org S: Maintained -T: git git://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git for-next -T: git git://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git fixes +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mm/memblock.git for-next +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mm/memblock.git fixes F: Documentation/core-api/boot-time-mm.rst F: include/linux/kho/abi/memblock.h F: include/linux/memblock.h From e4a62833adff6ef0fe7c0b90393204fe3c26b5c5 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 9 Sep 2026 12:08:08 +0800 Subject: [PATCH 1297/2366] bpf: Disallow bpf_skb_pull_data() for LWT_SEG6LOCAL An LWT_SEG6LOCAL program can invalidate its cached SRH with bpf_lwt_seg6_adjust_srh() and then call bpf_skb_pull_data(). The latter may reallocate skb->head, leaving the per-CPU SRH pointer dangling. Post-program SRH validation then writes through that pointer. Disallow bpf_skb_pull_data() for LWT_SEG6LOCAL programs so the verifier rejects this unsafe helper combination. Other LWT program types continue to expose the helper through lwt_out_func_proto(). Fixes: 004d4b274e2a ("ipv6: sr: Add seg6local action End.BPF") Reported-by: co+adfca3e91be95776@bugs.sh Suggested-by: Alexei Starovoitov Signed-off-by: Weiming Shi Signed-off-by: Daniel Borkmann Reviewed-by: Emil Tsalapatis Closes: https://lore.kernel.org/all/GCy0KRM2IcQGoJQTjJEU9D0maBxXzEDHuQpq@bugs.sh/ Link: https://lore.kernel.org/bpf/DL9COXZQXX4V.1FN45QO2Q77ZH@gmail.com/ Link: https://lore.kernel.org/bpf/20260909040807.3885815-2-bestswngs@gmail.com --- net/core/filter.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/core/filter.c b/net/core/filter.c index 8513167a858a..2a84f9d01131 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -9044,6 +9044,8 @@ static const struct bpf_func_proto * lwt_seg6local_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) { switch (func_id) { + case BPF_FUNC_skb_pull_data: + return NULL; #if IS_ENABLED(CONFIG_IPV6_SEG6_BPF) case BPF_FUNC_lwt_seg6_store_bytes: return &bpf_lwt_seg6_store_bytes_proto; From 5be081b83abd3f17d908953b4bb77279f5a149e3 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 9 Sep 2026 00:50:47 +0800 Subject: [PATCH 1298/2366] net: dsa: tag_brcm: legacy FCS: request needed tailroom The legacy FCS tagger calculates the CRC over skb->len bytes starting at skb->data. When a nonlinear skb reaches the tagger, this reads past the linear head into unrelated slab memory. The tagger appends an Ethernet FCS but does not declare that tailroom. As a result, DSA leaves NETIF_F_SG and NETIF_F_FRAGLIST enabled on the user port, and nonlinear skbs can reach the CRC calculation. Declare the required tailroom. DSA will then clear those features and the networking core will linearize skbs before the tagger runs. A KASAN-enabled dsa_loop test using this tagger reports: BUG: KASAN: slab-out-of-bounds in crc32_le Read of size 1 at addr ffff8880397086c0 by task exp/135 Call Trace: crc32_le (lib/crc/crc32-main.c:38) brcm_leg_fcs_tag_xmit (net/dsa/tag_brcm.c:343) dsa_user_xmit (net/dsa/user.c:942) dev_hard_start_xmit (net/core/dev.c:3937) __dev_queue_xmit (net/core/dev.c:4926) packet_sendmsg (net/packet/af_packet.c:3110) __sys_sendto (net/socket.c:2281) The buggy address belongs to the object at ffff888039708400 which belongs to the cache skbuff_small_head of size 704 The buggy address is located 0 bytes to the right of allocated 704-byte region [ffff888039708400, ffff8880397086c0) Fixes: ef07df397a62 ("net: dsa: tag_brcm: add support for legacy FCS tags") Cc: stable@vger.kernel.org Reported-by: co+28eef7d8af9428e6@bugs.sh Closes: https://lore.kernel.org/all/jH6u350kaBRuqklDjd3k3BW4nWzp0tYRjq3p%40bugs.sh/ Signed-off-by: Weiming Shi Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260908165047.2786340-1-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- net/dsa/tag_brcm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/dsa/tag_brcm.c b/net/dsa/tag_brcm.c index 411e3b57d16a..b7c49822ca88 100644 --- a/net/dsa/tag_brcm.c +++ b/net/dsa/tag_brcm.c @@ -373,6 +373,7 @@ static const struct dsa_device_ops brcm_legacy_fcs_netdev_ops = { .xmit = brcm_leg_fcs_tag_xmit, .rcv = brcm_leg_tag_rcv, .needed_headroom = BRCM_LEG_TAG_LEN, + .needed_tailroom = ETH_FCS_LEN, }; DSA_TAG_DRIVER(brcm_legacy_fcs_netdev_ops); From e2ab913f68c7d11e2561b8a8ad0b87ffefcad667 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Sep 2026 16:07:06 +0200 Subject: [PATCH 1299/2366] mptcp: do not reschedule the RTX timer for fallback sockets On fallback socket the retrans timer is a quite convoluted no-op, but currently nothing prevents the MPTCP core to keep rescheduling it. Additionally gate RTX timer reset to the msk not being fallen back to TCP yet. To avoid adding multiple tests in fast-path, use a new flags bit for such condition. The RTX enable bit is clear at close time and set before the msk could start retransmitting, with a couple of caveats: - passive sockets inherit the bit from the listener msk; set the bit on such socket to avoid flipping it in the fast-path, even if the listener will obviously never retransmit. - while fastopening (MPTFO), mptcp_sendmsg_fastopen still ends-up calling mptcp_connect via tcp_sendmsg_fastopen -> __inet_stream_connect(ssk->sk_socket), and the first subflow's sk_socket points to the msk one. Fixes: b51f9b80c032 ("mptcp: introduce MPTCP retransmission timer") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-1-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 13 ++++++++++--- net/mptcp/protocol.h | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index e1f08f71cdb1..be59651e708e 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -95,6 +95,7 @@ bool __mptcp_try_fallback(struct mptcp_sock *msk, int fb_mib) msk->allow_subflows = false; set_bit(MPTCP_FALLBACK_DONE, &msk->flags); + clear_bit(MPTCP_RTX_ENABLED, &msk->flags); __MPTCP_INC_STATS(net, fb_mib); spin_unlock_bh(&msk->fallback_lock); return true; @@ -1084,13 +1085,14 @@ static bool mptcp_rtx_timer_pending(struct sock *sk) static void mptcp_reset_rtx_timer(struct sock *sk) { + struct mptcp_sock *msk = mptcp_sk(sk); unsigned long tout; - /* prevent rescheduling on close */ - if (unlikely(inet_sk_state_load(sk) == TCP_CLOSE)) + /* Prevent rescheduling on close and in case of fallback. */ + if (!test_bit(MPTCP_RTX_ENABLED, &msk->flags)) return; - tout = mptcp_sk(sk)->timer_ival; + tout = msk->timer_ival; sk_reset_timer(sk, &sk->mptcp_retransmit_timer, jiffies + tout); } @@ -3323,6 +3325,9 @@ void mptcp_set_state(struct sock *sk, int state) * transition from TCP_SYN_RECV to TCP_CLOSE_WAIT. */ break; + case TCP_CLOSE: + clear_bit(MPTCP_RTX_ENABLED, &mptcp_sk(sk)->flags); + fallthrough; default: if (oldstate == TCP_ESTABLISHED || oldstate == TCP_CLOSE_WAIT) MPTCP_DEC_STATS(sock_net(sk), MPTCP_MIB_CURRESTAB); @@ -4141,6 +4146,7 @@ static int mptcp_connect(struct sock *sk, struct sockaddr_unsized *uaddr, if (IS_ERR(ssk)) return PTR_ERR(ssk); + set_bit(MPTCP_RTX_ENABLED, &msk->flags); mptcp_set_state(sk, TCP_SYN_SENT); subflow = mptcp_subflow_ctx(ssk); #ifdef CONFIG_TCP_MD5SIG @@ -4288,6 +4294,7 @@ static int mptcp_listen(struct socket *sock, int backlog) goto unlock; } + set_bit(MPTCP_RTX_ENABLED, &msk->flags); mptcp_set_state(sk, TCP_LISTEN); sock_set_flag(sk, SOCK_RCU_FREE); diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 87ccb84e9927..2b4c27426477 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -116,6 +116,7 @@ #define MPTCP_WORK_RTX 1 #define MPTCP_FALLBACK_DONE 2 #define MPTCP_WORK_CLOSE_SUBFLOW 3 +#define MPTCP_RTX_ENABLED 4 /* MPTCP socket release cb flags */ #define MPTCP_PUSH_PENDING 1 From 29f641951be0d91036d77edf677807f1447dbe65 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:07 +0200 Subject: [PATCH 1300/2366] mptcp: subflow: no need to copy thmac during ulp_clone 'thmac' is not used after that point. Indeed, subflow_ulp_clone() is called when the request on the passive side is over, so when the truncated HMAC is no longer needed. Note that in case of SYN cookies, thmac will not be initialised. So better to remove it to avoid a warning from debug tools like KMSAN for reading uninitialised data. Fixes: f296234c98a8 ("mptcp: Add handling of incoming MP_JOIN requests") Cc: stable@vger.kernel.org Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-2-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/subflow.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index af81ad5e699d..01db7edce18a 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -2084,7 +2084,6 @@ static void subflow_ulp_clone(const struct request_sock *req, new_ctx->request_bkup = subflow_req->request_bkup; WRITE_ONCE(new_ctx->remote_id, subflow_req->remote_id); new_ctx->token = subflow_req->token; - new_ctx->thmac = subflow_req->thmac; /* the subflow req id is valid, fetched via subflow_check_req() * and subflow_token_join_request() From b76c0e28b392620dfbaf92cdeedbf115820b44cb Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:08 +0200 Subject: [PATCH 1301/2366] mptcp: syncookies: remember the request backup flag Instead of using an uninitialised bit when copying the info in subflow_ulp_clone(). To fix this, no need to extend the join_entry structure: backup is coming from struct mptcp_subflow_request_sock, only one bit. Do the same here by using one bit for both. Fixes: efd340bf3d77 ("mptcp: distinguish rcv vs sent backup flag in requests") Cc: stable@vger.kernel.org Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-3-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/syncookies.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/mptcp/syncookies.c b/net/mptcp/syncookies.c index b5cac5701122..9474706641c1 100644 --- a/net/mptcp/syncookies.c +++ b/net/mptcp/syncookies.c @@ -26,7 +26,8 @@ struct join_entry { u32 local_nonce; u8 join_id; u8 local_id; - u8 backup; + u8 backup:1, + request_bkup:1; u8 valid; }; @@ -63,6 +64,7 @@ static void mptcp_join_store_state(struct join_entry *entry, entry->remote_nonce = subflow_req->remote_nonce; entry->local_nonce = subflow_req->local_nonce; entry->backup = subflow_req->backup; + entry->request_bkup = subflow_req->request_bkup; entry->join_id = subflow_req->remote_id; entry->local_id = subflow_req->local_id; entry->valid = 1; @@ -117,6 +119,7 @@ bool mptcp_token_join_cookie_init_state(struct mptcp_subflow_request_sock *subfl subflow_req->remote_nonce = e->remote_nonce; subflow_req->local_nonce = e->local_nonce; subflow_req->backup = e->backup; + subflow_req->request_bkup = e->request_bkup; subflow_req->remote_id = e->join_id; subflow_req->local_id = e->local_id; subflow_req->token = e->token; From 2ac7d6e620764f1fc79eb4edd3610a7a661981ca Mon Sep 17 00:00:00 2001 From: Kalpan Jani Date: Tue, 8 Sep 2026 16:07:09 +0200 Subject: [PATCH 1302/2366] mptcp: pm: kernel: drop pending ADD_ADDR when removing ID0 The in-kernel MPTCP path manager can leave a stale ADD_ADDR announcement entry alive when removing the id 0 endpoint. This happens because the id 0 removal path does not tear down pending announcements, unlike the non-zero id path. When the PM later reselects id 0 after adding another signal endpoint, it finds the stale anno_list entry and hits WARN_ON_ONCE(mptcp_pm_is_kernel()) in mptcp_pm_announced_alloc(). Root cause: asymmetry between removal paths. - Non-zero id path: mptcp_nl_remove_subflow_and_signal_addr() calls mptcp_pm_remove_announced() to clean up. - Id 0 path: mptcp_nl_remove_id_zero_address() skips cleanup entirely. Fix by making the id 0 path symmetric: call mptcp_pm_announced_remove() and decrement add_addr_signaled before queuing the RM_ADDR. Subtle detail: signal endpoints are stored in anno_list with port 0, but msk_local carries the connection's local port. In other words, entries linked to ID0 paths should have port == 0. A follow-up patch will ensure that. mptcp_pm_announced_remove() uses use_port=true for comparison. So clear the port before the lookup. Fixes: 740d798e8767 ("mptcp: remove id 0 address") Cc: stable@vger.kernel.org Reported-by: syzbot+55c2a5c871441261ed14@syzkaller.appspotmail.com Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/620 Suggested-by: Tao Cui Signed-off-by: Kalpan Jani Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-4-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/pm_kernel.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/mptcp/pm_kernel.c b/net/mptcp/pm_kernel.c index 424f1a7f9248..1a7750813235 100644 --- a/net/mptcp/pm_kernel.c +++ b/net/mptcp/pm_kernel.c @@ -1137,6 +1137,8 @@ static int mptcp_nl_remove_id_zero_address(struct net *net, while ((msk = mptcp_token_iter_next(net, &s_slot, &s_num)) != NULL) { struct sock *sk = (struct sock *)msk; struct mptcp_addr_info msk_local; + struct mptcp_addr_info anno_addr; + bool announced; if (list_empty(&msk->conn_list) || mptcp_pm_is_userspace(msk)) goto next; @@ -1146,7 +1148,13 @@ static int mptcp_nl_remove_id_zero_address(struct net *net, goto next; lock_sock(sk); + /* Drop a possibly pending ADD_ADDR for this address. */ + anno_addr = msk_local; + anno_addr.port = 0; + announced = mptcp_pm_announced_remove(msk, &anno_addr); spin_lock_bh(&msk->pm.lock); + if (announced) + msk->pm.add_addr_signaled--; mptcp_pm_remove_addr(msk, &list); mptcp_pm_rm_subflow(msk, &list); __mark_subflow_endp_available(msk, 0); From ab36b1a80942c78ddb04d006ff38aa7ed3ec0e5e Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:10 +0200 Subject: [PATCH 1303/2366] mptcp: options: handle MPC data + csum reqd + no csum Before this modification, a remote peer could send an MP_CAPABLE with data, with the checksum flag set, but without adding the actual 2 bytes of checksum. As a result, uninitialised bytes could be used for the 'csum' field. That was not a critical issue, because this 'csum' field is only used to compare with the expected one, if previously negotiated in the 3WHS. Worst case, the checksum is likely wrong, a fallback is done without a reject if the negotiation was done earlier. That's OK. Yet, better to take the expected path with this case: only look at the checksum flag for MP_CAPABLEs not carrying a data-len. Such packet can be seen as a 3rd or 4th ACK. The RFC8684 mentions [1] that the 3rd packet should have the checksum flag set. When an MPC + ACK contains data, the checksum flag is redundant with the checksum field. It is not clear what should be done for the 4th ACK, nor if the flag has to be set if the checksum field is set. Therefore, it seems fine to only look at the presence of the checksum field, not to break the interaction with stacks that were not setting both. Note that linked to this checksum flag on the 3rd ACK, with the current implementation, we can have a situation where the SYN packets have no checksum flag, but the 3rd ACK has one, and this is the one that will be taken into account. First, that's clearly not directly linked to this patch, but Clashiko forced us to look at that. At the end, that seems fine to act like that: yes that's not how the negotiation should work, but being flexible without introducing side effects is also fine: fixing this would mean increasing the complexity, and that's not worth it. Fixes: 208e8f66926c ("mptcp: receive checksum for MP_CAPABLE with data") Cc: stable@vger.kernel.org Link: https://datatracker.ietf.org/doc/html/rfc8684#section-3.1-23 [1] Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-0-b8f496d71664%40kernel.org?part=1 Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-5-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/options.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/mptcp/options.c b/net/mptcp/options.c index b8318e030138..92f27b9e087a 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -93,7 +93,8 @@ static void mptcp_parse_option(const struct sk_buff *skb, * In other words, the only way for checksums not to be used * is if both hosts in their SYNs set A=0." */ - if (flags & MPTCP_CAP_CHECKSUM_REQD) + if ((flags & MPTCP_CAP_CHECKSUM_REQD) && + opsize < TCPOLEN_MPTCP_MPC_ACK_DATA) mp_opt->suboptions |= OPTION_MPTCP_CSUMREQD; mp_opt->deny_join_id0 = !!(flags & MPTCP_CAP_DENY_JOIN_ID0); From 85c580b0d8590520ae00a15c29e9fb9c99427a3e Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Sep 2026 16:07:11 +0200 Subject: [PATCH 1304/2366] mptcp: prevent race between disconnect() and rtx Sashiko noted that the two event can race, leading to inconsistent status. Prevent the race using the synchronous timer stop operation. Cc: stable@vger.kernel.org Fixes: b29fcfb54cd7 ("mptcp: full disconnect implementation") Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-6-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index be59651e708e..d611af2eb74f 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -3588,6 +3588,7 @@ static void mptcp_destroy_common(struct mptcp_sock *msk) static int mptcp_disconnect(struct sock *sk, int flags) { + struct inet_connection_sock *icsk = inet_csk(sk); struct mptcp_sock *msk = mptcp_sk(sk); /* We are on the fastopen error path. We can't call straight into the @@ -3600,8 +3601,13 @@ static int mptcp_disconnect(struct sock *sk, int flags) mptcp_check_listen_stop(sk); mptcp_set_state(sk, TCP_CLOSE); - mptcp_stop_rtx_timer(sk); - mptcp_stop_tout_timer(sk); + /* The later subflow close can not kick again the tout timer, + * as the msk is already in closed status. + */ + msk->timer_ival = icsk->icsk_rto_min; + sk_stop_timer_sync(sk, &sk->mptcp_retransmit_timer); + icsk->icsk_mtup.probe_timestamp = 0; + sk_stop_timer_sync(sk, &icsk->mptcp_tout_timer); mptcp_pm_connection_closed(msk); From 730444f094b12052916ebd7e14fe57bc3d47bf38 Mon Sep 17 00:00:00 2001 From: Gang Yan Date: Tue, 8 Sep 2026 16:07:12 +0200 Subject: [PATCH 1305/2366] selftests: mptcp: fix an UAF in mptcp_connect.c At the end of 'sock_connect_mptcp()', it calls 'freeaddrinfo(addr)', the 'peer' pointer (which points into 'addr') remains. Later, the main loop uses this peer pointer for reconnection attempts. If the memory has been freed and reused, the address data could be overwritten, resulting in an invalid remote address. This patch keeps the addrinfo list allocated for the whole process lifetime so "peer" remains valid across reconnects; the memory will be released at exit() time. Fixes: 05be5e273c84 ("selftests: mptcp: add disconnect tests") Cc: stable@vger.kernel.org Suggested-by: Paolo Abeni Signed-off-by: Gang Yan Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-7-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/mptcp_connect.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.c b/tools/testing/selftests/net/mptcp/mptcp_connect.c index ea4cb6c1bd5e..178d98d91fea 100644 --- a/tools/testing/selftests/net/mptcp/mptcp_connect.c +++ b/tools/testing/selftests/net/mptcp/mptcp_connect.c @@ -381,6 +381,9 @@ static int sock_connect_mptcp(const char * const remoteaddr, hints.ai_family = pf; + /* Keep the resolved address alive for the whole execution: it is + * used again when reconnecting, and will be released at exit time. + */ xgetaddrinfo(remoteaddr, port, &hints, &addr); for (a = addr; a; a = a->ai_next) { sock = socket(a->ai_family, a->ai_socktype, proto); @@ -421,7 +424,6 @@ static int sock_connect_mptcp(const char * const remoteaddr, sock = -1; } - freeaddrinfo(addr); if (sock != -1) SOCK_TEST_TCPULP(sock, proto); return sock; From f9f0068e8813d8c10d016b030fc3a320d0b6767c Mon Sep 17 00:00:00 2001 From: Qing Luo Date: Tue, 8 Sep 2026 16:07:13 +0200 Subject: [PATCH 1306/2366] mptcp: pm: userspace: fix address ID overflow When all MPTCP address IDs (1-255) are exhausted in the userspace PM, find_next_zero_bit() returns MPTCP_PM_MAX_ADDR_ID + 1 (256). This value overflows when stored in the u8 field e->addr.id, resulting in ID 0 being stored and the entry being incorrectly added to the list. ID 0 is reserved for the initial connection in MPTCP, so this overflow can cause address conflicts. Note: the in-kernel PM already has an 'endpoints == MPTCP_PM_MAX_ADDR_ID' check in mptcp_pm_nl_append_new_local_addr() that returns -ERANGE before reaching find_next_zero_bit(), preventing this overflow. So this fix only addresses the userspace PM path. Check the find_next_zero_bit() result against MPTCP_PM_MAX_ADDR_ID and return -ENOSPC if all IDs are truly exhausted. Move the ID allocation check before the memory allocation so that the error path does not need to free the allocated entry. Fixes: 4638de5aefe5 ("mptcp: handle local addrs announced by userspace PMs") Cc: stable@vger.kernel.org Signed-off-by: Qing Luo Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-8-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/pm_userspace.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/net/mptcp/pm_userspace.c b/net/mptcp/pm_userspace.c index b94fbb483bf9..fab16d953dbf 100644 --- a/net/mptcp/pm_userspace.c +++ b/net/mptcp/pm_userspace.c @@ -69,6 +69,19 @@ static int mptcp_userspace_pm_append_new_local_addr(struct mptcp_sock *msk, } if (!addr_match && !id_match) { + unsigned int id; + + if (!entry->addr.id && needs_id) { + id = find_next_zero_bit(id_bitmap, + MPTCP_PM_MAX_ADDR_ID + 1, 1); + if (id > MPTCP_PM_MAX_ADDR_ID) { + ret = -ENOSPC; + goto append_err; + } + } else { + id = entry->addr.id; + } + /* Memory for the entry is allocated from the * sock option buffer. */ @@ -78,10 +91,7 @@ static int mptcp_userspace_pm_append_new_local_addr(struct mptcp_sock *msk, goto append_err; } - if (!e->addr.id && needs_id) - e->addr.id = find_next_zero_bit(id_bitmap, - MPTCP_PM_MAX_ADDR_ID + 1, - 1); + e->addr.id = id; list_add_tail_rcu(&e->list, &msk->pm.userspace_pm_local_addr_list); msk->pm.local_addr_used++; ret = e->addr.id; From f968190c0b42ea2004dc1426359a53ec365a7a37 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:14 +0200 Subject: [PATCH 1307/2366] mptcp: pm: reset retrans_time when ADD_ADDR entry is reused When an ADD_ADDR entry is reused, the timer is re-armed, because the goal is to re-announce an ADD_ADDR, and eventually retransmit it if needed. In this case, the retransmission counter should be reset as well, so the re-announced address gets its retransmissions back instead of relying on what was left before, and possibly not being able to retransmit it. Fixes: 304ab97f4c7c ("mptcp: allow ADD_ADDR reissuance by userspace PMs") Cc: stable@vger.kernel.org Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-0-b8f496d71664%40kernel.org?part=4 Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-9-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/pm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c index 8b68868255c5..b0b71adefb8f 100644 --- a/net/mptcp/pm.c +++ b/net/mptcp/pm.c @@ -462,10 +462,10 @@ bool mptcp_pm_announced_alloc(struct mptcp_sock *msk, add_entry->addr = *addr; add_entry->sock = msk; - add_entry->retrans_times = 0; timer_setup(&add_entry->timer, mptcp_pm_add_addr_timer, 0); reset_timer: + add_entry->retrans_times = 0; add_entry->timer_done = false; timeout = mptcp_adjust_add_addr_timeout(msk); if (timeout) From caa4a79f74f32084ce28aee8653bc04df745970d Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Sep 2026 16:07:15 +0200 Subject: [PATCH 1308/2366] mptcp: remove unneeded READ_ONCE() annotation The subflow->fully_established flag is always written under the subflow socket lock. Reading such value under the same lock does not require any ONCE annotation. Fixes: 581c8cbfa934 ("mptcp: annotate data-races around subflow->fully_established") Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-10-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/options.c | 4 ++-- net/mptcp/protocol.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/net/mptcp/options.c b/net/mptcp/options.c index 92f27b9e087a..196a46e7467d 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -530,7 +530,7 @@ static bool mptcp_established_options_mp(struct sock *sk, struct sk_buff *skb, return false; /* MPC/MPJ needed only on 3rd ack packet, DATA_FIN and TCP shutdown take precedence */ - if (READ_ONCE(subflow->fully_established) || snd_data_fin_enable || + if (subflow->fully_established || snd_data_fin_enable || subflow->snd_isn != TCP_SKB_CB(skb)->seq || sk->sk_state != TCP_ESTABLISHED) return false; @@ -981,7 +981,7 @@ static bool check_fully_established(struct mptcp_sock *msk, struct sock *ssk, /* here we can process OoO, in-window pkts, only in-sequence 4th ack * will make the subflow fully established */ - if (likely(READ_ONCE(subflow->fully_established))) { + if (likely(subflow->fully_established)) { /* on passive sockets, check for 3rd ack retransmission * note that msk is always set by subflow_syn_recv_sock() * for mp_join subflows diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index d611af2eb74f..302936ff456a 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -3886,7 +3886,7 @@ static void schedule_3rdack_retransmission(struct sock *ssk) struct tcp_sock *tp = tcp_sk(ssk); unsigned long timeout; - if (READ_ONCE(mptcp_subflow_ctx(ssk)->fully_established)) + if (mptcp_subflow_ctx(ssk)->fully_established) return; /* reschedule with a timeout above RTT, as we must look only for drop */ From e1a56368eac18b3b4b956b794526e8713c48a0ec Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:16 +0200 Subject: [PATCH 1309/2366] selftests: mptcp: lib: dump nstat for the right test In case of errors, mptcp_lib_pr_nstat is called to dump the nstat counters, but for some tests, it was dumping the counters for all subtests, not just the current one. That's an issue for tests that don't recreate the netns for each subtest, e.g. mptcp_connect.sh. In this case, 'nstat -a' will look at the absolute counters since the creation of the netns, making debugging harder. Instead, it should dump the counters for the current test, by using the history recorded in /tmp/.nstat if available, and not using '-a' which was dumping the absolute values instead of calculating increments. While at it, rename the previous 'hist' variable to 'cache' as it was used to look at the cache, not the nstat history. Fixes: 658e53141780 ("selftests: mptcp: join: dump stats from history") Cc: stable@vger.kernel.org Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-11-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/mptcp_lib.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_lib.sh b/tools/testing/selftests/net/mptcp/mptcp_lib.sh index 5ef6033775c8..da1da414c30f 100644 --- a/tools/testing/selftests/net/mptcp/mptcp_lib.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_lib.sh @@ -108,12 +108,14 @@ mptcp_lib_pr_info() { mptcp_lib_pr_nstat() { local ns="${1}" - local hist="/tmp/${ns}.out" + local cache="/tmp/${ns}.out" + local hist="/tmp/${ns}.nstat" - if [ -f "${hist}" ]; then - awk '$2 != 0 { print " "$0 }' "${hist}" + if [ -f "${cache}" ]; then + awk '$2 != 0 { print " "$0 }' "${cache}" else - ip netns exec "${ns}" nstat -as | grep Tcp + NSTAT_HISTORY="${hist}" ip netns exec "${ns}" nstat -s | + grep Tcp fi } From d23c41366e85f149b48323d66adc36c4a9f18cbd Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:17 +0200 Subject: [PATCH 1310/2366] selftests: mptcp: lib: get counters for the right test When the value for a MIB counter is required, mptcp_lib_get_counter is called. It tries to use the cache, if available. If not it falls back to calling 'nstat' directly by looking at the absolute counters. That's an issue for tests that don't recreate the netns for each subtest. In this case, 'nstat -a' will look at the counters for the netns. Instead, it should look at the increment for the current test, by using the history recorded in /tmp/.nstat, if available, and not using '-a' which was dumping the absolute values. While at it, rename the previous 'hist' variable to 'cache' as it was used to look at the cache, not the nstat history. Fixes: 71388a9f331d ("selftests: mptcp: lib: get counters from nstat history") Cc: stable@vger.kernel.org Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-12-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/mptcp/mptcp_lib.sh | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/net/mptcp/mptcp_lib.sh b/tools/testing/selftests/net/mptcp/mptcp_lib.sh index da1da414c30f..b9d14647f401 100644 --- a/tools/testing/selftests/net/mptcp/mptcp_lib.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_lib.sh @@ -416,19 +416,21 @@ mptcp_lib_nstat_get() { } # $1: ns, $2: MIB counter -# Get the counter from the history (mptcp_lib_nstat_{init,get}()) if available. -# If not, get the counter from nstat ignoring any history. +# Get the counter from the cache (mptcp_lib_nstat_{init,get}()) if available. +# If not, get the counter from nstat ignoring any cache, but using the history. mptcp_lib_get_counter() { local ns="${1}" local counter="${2}" - local hist="/tmp/${ns}.out" + local cache="/tmp/${ns}.out" + local hist="/tmp/${ns}.nstat" local count - if [[ -s "${hist}" && "${counter}" == *"Tcp"* ]]; then - count=$(awk "/^${counter} / {print \$2; exit}" "${hist}") + if [[ -s "${cache}" && "${counter}" == *"Tcp"* ]]; then + count=$(awk "/^${counter} / {print \$2; exit}" "${cache}") else - count=$(ip netns exec "${ns}" nstat -asz "${counter}" | - awk 'NR==1 {next} {print $2}') + count=$(NSTAT_HISTORY="${hist}" ip netns exec "${ns}" \ + nstat -sz "${counter}" | + awk 'NR==1 {next} {print $2}') fi if [ -z "${count}" ]; then mptcp_lib_fail_if_expected_feature "${counter} counter" From b110f1dd6cb6a9930503354a01a315e0a821eaa7 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Tue, 8 Sep 2026 16:07:18 +0200 Subject: [PATCH 1311/2366] mptcp: options: fix uninit-value in mptcp_write_data_fin When sending a DATA_FIN without data, and because the DATA_FIN occupies 1 octet of the connection-level sequence space [1], it is then required to add a DSS mapping with specific values. If the checksum has been negotiated, it also needs to be computed, and included in the outgoing packet, and thus the initial csum data needs to be reset to 0 as well. This is no longer the case since commit cfcceb7a39fc ("tcp: shrink per-packet memset in __tcp_transmit_skb()"), because the whole ext_copy structure is no longer zeroed by default. This seems to be the only case where use_map is changed and set afterwards, so initialising the csum field only in this case, along with other fields for this specific case. Fixes: cfcceb7a39fc ("tcp: shrink per-packet memset in __tcp_transmit_skb()") Cc: stable@vger.kernel.org Link: https://datatracker.ietf.org/doc/html/rfc8684#section-3.3.3 [1] Link: https://sashiko.dev/#/patchset/20260812-net-next-mptcp-misc-feat-7-3-v1-0-1905a818f6cb%40kernel.org?part=2 Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-13-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/options.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/mptcp/options.c b/net/mptcp/options.c index 196a46e7467d..ce0de02f5a3a 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -612,6 +612,7 @@ static void mptcp_write_data_fin(struct mptcp_subflow_context *subflow, ext->data_seq = data_fin_tx_seq; ext->subflow_seq = 0; ext->data_len = 1; + ext->csum = 0; } else if (ext->data_seq + ext->data_len == data_fin_tx_seq) { /* If there's an existing DSS mapping and it is the * final mapping, DATA_FIN consumes 1 additional byte of From a4257a91af7a77a8347d33413ec9e54106f7ff48 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Sep 2026 16:07:19 +0200 Subject: [PATCH 1312/2366] mptcp: being below memory limit is a likely() condition The current compiler hint annotation is wrong, due to inverted logic in the previous revision of the relevant code. Fixes: e468d371180d ("mptcp: implemented OoO queue pruning") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-14-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 302936ff456a..4309fca6b119 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -289,8 +289,8 @@ static void mptcp_prune_ofo_queue(struct sock *sk, */ static bool mptcp_can_ingest(const struct sock *sk) { - return unlikely(sk_rmem_alloc_get(sk) <= READ_ONCE(sk->sk_rcvbuf)) || - __mptcp_check_fallback(mptcp_sk(sk)); + return likely(sk_rmem_alloc_get(sk) <= READ_ONCE(sk->sk_rcvbuf)) || + __mptcp_check_fallback(mptcp_sk(sk)); } static bool mptcp_try_rmem_schedule(struct sock *sk, const struct sk_buff *skb) From f01b8275745efe611284f6c3628099a81a421f0d Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Tue, 8 Sep 2026 16:07:20 +0200 Subject: [PATCH 1313/2366] mptcp: avoid pruning for OoW data Pruning is expansive and destructive, do it only when we expect to accept the skb triggering the cleanup. Fixes: e468d371180d ("mptcp: implemented OoO queue pruning") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260908-net-mptcp-misc-fixes-7-3-rc1-v2-15-df1de70348b6@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 4309fca6b119..0098e2830931 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -313,12 +313,6 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb) u64 seq, end_seq, max_seq; struct sk_buff *skb1; - if (!mptcp_try_rmem_schedule(sk, skb)) { - MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED); - mptcp_drop(sk, skb); - return; - } - seq = MPTCP_SKB_CB(skb)->map_seq; end_seq = MPTCP_SKB_CB(skb)->end_seq; max_seq = atomic64_read(&msk->rcv_wnd_sent); @@ -335,6 +329,12 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb) return; } + if (!mptcp_try_rmem_schedule(sk, skb)) { + MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED); + mptcp_drop(sk, skb); + return; + } + p = &msk->out_of_order_queue.rb_node; MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOQUEUE); if (RB_EMPTY_ROOT(&msk->out_of_order_queue)) { From 0fa37512eb747e4ffdcf367274f9e72845f1bca4 Mon Sep 17 00:00:00 2001 From: David Arcari Date: Thu, 3 Sep 2026 14:20:29 -0400 Subject: [PATCH 1314/2366] watchdog: fix hrtimer start when pretimeout is zero Per the watchdog API, a pretimeout value of 0 disables the feature. However, watchdog_hrtimer_pretimeout_start() fails to verify if the pretimeout is non-zero before arming the timer. This omission inadvertently starts the software pretimeout timer, which could result in the pretimeout handler executing incorrectly when the watchdog timeout is reached. Fix this by adding a check for wdd->pretimeout before calling hrtimer_start(), ensuring the disabled state is respected. Fixes: 7b7d2fdc8c3e ("watchdog: Add hrtimer-based pretimeout feature") Signed-off-by: David Arcari Link: https://patch.msgid.link/20260903182029.936030-1-darcari@redhat.com Signed-off-by: Guenter Roeck --- drivers/watchdog/watchdog_hrtimer_pretimeout.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/watchdog/watchdog_hrtimer_pretimeout.c b/drivers/watchdog/watchdog_hrtimer_pretimeout.c index fbc7eecd8b20..49a05ea60c97 100644 --- a/drivers/watchdog/watchdog_hrtimer_pretimeout.c +++ b/drivers/watchdog/watchdog_hrtimer_pretimeout.c @@ -30,6 +30,7 @@ void watchdog_hrtimer_pretimeout_init(struct watchdog_device *wdd) void watchdog_hrtimer_pretimeout_start(struct watchdog_device *wdd) { if (!(wdd->info->options & WDIOF_PRETIMEOUT) && + wdd->pretimeout && !watchdog_pretimeout_invalid(wdd, wdd->pretimeout)) hrtimer_start(&wdd->wd_data->pretimeout_timer, ktime_set(wdd->timeout - wdd->pretimeout, 0), From 3c73a37f5e40972ce26d8eeb98e8b938d719b069 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 29 Aug 2026 00:13:41 +0800 Subject: [PATCH 1315/2366] watchdog: msc313e: Avoid division by zero clk_get_rate() could return 0. Avoid a division by zero panic. Fixes: e9800b799464 ("watchdog: Add Mstar MSC313e WDT driver") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260828161348.13212-3-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index f69d66971c41..c3018b970164 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -97,6 +97,7 @@ static int msc313e_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct msc313e_wdt_priv *priv; + unsigned long rate; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -116,7 +117,10 @@ static int msc313e_wdt_probe(struct platform_device *pdev) priv->wdev.ops = &msc313e_wdt_ops, priv->wdev.parent = dev; priv->wdev.min_timeout = MSC313E_WDT_MIN_TIMEOUT; - priv->wdev.max_timeout = U32_MAX / clk_get_rate(priv->clk); + rate = clk_get_rate(priv->clk); + if (!rate) + return -EINVAL; + priv->wdev.max_timeout = U32_MAX / rate; priv->wdev.timeout = MSC313E_WDT_DEFAULT_TIMEOUT; /* If the period is non-zero the WDT is running */ From 3db30f315935c2fb0d95f46b7a593b5b4d3ec3d0 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 29 Aug 2026 00:13:42 +0800 Subject: [PATCH 1316/2366] watchdog: msc313e: Fix clock leak and spurious timer in settimeout() msc313e_wdt_settimeout() unconditionally calls msc313e_wdt_start() which introduces two severe bugs: 1. If the watchdog is already active, calling start() again will increase the reference count of the clock again. However stop() is only called once, the reference count is unbalance. 2. If the watchdog is stopped, calling settimeout() will start the hardware timer accidentally. Factor out the register-writing logic into a helper function. Only call it in settimeout() if the watchdog is running. Otherwise, simply update `wdev->timeout`. Fixes: e9800b799464 ("watchdog: Add Mstar MSC313e WDT driver") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260828161348.13212-4-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index c3018b970164..8ce24df8e338 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -31,20 +31,26 @@ struct msc313e_wdt_priv { struct clk *clk; }; +static void msc313e_wdt_set_hw_timeout(struct msc313e_wdt_priv *priv, + unsigned int timeout) +{ + u32 t = timeout * clk_get_rate(priv->clk); + + writew(t & 0xffff, priv->base + REG_WDT_MAX_PRD_L); + writew((t >> 16) & 0xffff, priv->base + REG_WDT_MAX_PRD_H); + writew(1, priv->base + REG_WDT_CLR); +} + static int msc313e_wdt_start(struct watchdog_device *wdev) { struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev); - u32 timeout; int err; err = clk_prepare_enable(priv->clk); if (err) return err; - timeout = wdev->timeout * clk_get_rate(priv->clk); - writew(timeout & 0xffff, priv->base + REG_WDT_MAX_PRD_L); - writew((timeout >> 16) & 0xffff, priv->base + REG_WDT_MAX_PRD_H); - writew(1, priv->base + REG_WDT_CLR); + msc313e_wdt_set_hw_timeout(priv, wdev->timeout); return 0; } @@ -69,9 +75,13 @@ static int msc313e_wdt_stop(struct watchdog_device *wdev) static int msc313e_wdt_settimeout(struct watchdog_device *wdev, unsigned int new_time) { + struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev); + wdev->timeout = new_time; - return msc313e_wdt_start(wdev); + if (watchdog_hw_running(wdev) || watchdog_active(wdev)) + msc313e_wdt_set_hw_timeout(priv, wdev->timeout); + return 0; } static const struct watchdog_info msc313e_wdt_ident = { From 3db2df24e7f11fb117718f6abe326628d91bc500 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 29 Aug 2026 00:13:43 +0800 Subject: [PATCH 1317/2366] watchdog: msc313e: Enable clock before accessing hardware registers msc313e_wdt_probe() reads from hardware registers without ensuring the required clock is enabled. Furthermore, if the bootloader leaves the watchdog running, msc313e_wdt_probe() sets WDOG_HW_RUNNING without increasing the clock's reference count. While the clock is currently supplied as a fixed clock by the device tree (`xtal_div2` in arch/arm/boot/dts/sigmastar/mstar-v7.dtsi) which masks the physical issue, this still violates the API usage. Call clk_prepare_enable() before reading WDT registers. If the WDT is running, leave the clock enabled so the CCF reference counter is balanced. Fixes: ffd264bd152c ("watchdog: msc313e: Check if the WDT was running at boot") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260828161348.13212-5-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index 8ce24df8e338..7c4593566781 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -108,6 +108,7 @@ static int msc313e_wdt_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct msc313e_wdt_priv *priv; unsigned long rate; + int ret; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -133,9 +134,21 @@ static int msc313e_wdt_probe(struct platform_device *pdev) priv->wdev.max_timeout = U32_MAX / rate; priv->wdev.timeout = MSC313E_WDT_DEFAULT_TIMEOUT; + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + /* If the period is non-zero the WDT is running */ - if (readw(priv->base + REG_WDT_MAX_PRD_L) | (readw(priv->base + REG_WDT_MAX_PRD_H) << 16)) + if (readw(priv->base + REG_WDT_MAX_PRD_L) | (readw(priv->base + REG_WDT_MAX_PRD_H) << 16)) { set_bit(WDOG_HW_RUNNING, &priv->wdev.status); + /* + * Keep the clock enabled. The watchdog core will skip the next + * start() and a future stop() will balance the CCF reference + * count. + */ + } else { + clk_disable_unprepare(priv->clk); + } watchdog_set_drvdata(&priv->wdev, priv); platform_set_drvdata(pdev, priv); @@ -144,7 +157,13 @@ static int msc313e_wdt_probe(struct platform_device *pdev) watchdog_stop_on_reboot(&priv->wdev); watchdog_stop_on_unregister(&priv->wdev); - return devm_watchdog_register_device(dev, &priv->wdev); + ret = devm_watchdog_register_device(dev, &priv->wdev); + + /* If the WDT is running and anything goes wrong, disable the clock. */ + if (ret && test_bit(WDOG_HW_RUNNING, &priv->wdev.status)) + clk_disable_unprepare(priv->clk); + + return ret; } static int __maybe_unused msc313e_wdt_suspend(struct device *dev) From 4f6817c9eff4aa1078e16652d82e4b7ef4ffae3e Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 29 Aug 2026 00:13:44 +0800 Subject: [PATCH 1318/2366] watchdog: msc313e: Fix spurious reset on suspend If the hardware watchdog was started by the bootloader and the device is suspended before userspace opens it, the ping worker (from watchdog core) is frozen and the active hardware timer continues running. This leads to a spurious system reset. Check both watchdog_active() and watchdog_hw_running() when deciding whether to start or stop the watchdog during suspend and resume. Additionally, call watchdog_stop_ping_on_suspend() to ensure the ping worker be correctly paused and restarted during suspend and resume. Fixes: ffd264bd152c ("watchdog: msc313e: Check if the WDT was running at boot") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260828161348.13212-6-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index 7c4593566781..c7d558fefc86 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -156,6 +156,7 @@ static int msc313e_wdt_probe(struct platform_device *pdev) watchdog_init_timeout(&priv->wdev, timeout, dev); watchdog_stop_on_reboot(&priv->wdev); watchdog_stop_on_unregister(&priv->wdev); + watchdog_stop_ping_on_suspend(&priv->wdev); ret = devm_watchdog_register_device(dev, &priv->wdev); @@ -170,7 +171,7 @@ static int __maybe_unused msc313e_wdt_suspend(struct device *dev) { struct msc313e_wdt_priv *priv = dev_get_drvdata(dev); - if (watchdog_active(&priv->wdev)) + if (watchdog_active(&priv->wdev) || watchdog_hw_running(&priv->wdev)) msc313e_wdt_stop(&priv->wdev); return 0; @@ -180,7 +181,7 @@ static int __maybe_unused msc313e_wdt_resume(struct device *dev) { struct msc313e_wdt_priv *priv = dev_get_drvdata(dev); - if (watchdog_active(&priv->wdev)) + if (watchdog_active(&priv->wdev) || watchdog_hw_running(&priv->wdev)) msc313e_wdt_start(&priv->wdev); return 0; From ab390021b2a3bb4cc875f28a6f76d13de90d7457 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 29 Aug 2026 00:13:45 +0800 Subject: [PATCH 1319/2366] watchdog: msc313e: Fix undefined behavior readw() returns a u16. Left shifting a u16 by 16 bits yields undefined behavior. Cast to u32 explicitly before the shift. Fixes: ffd264bd152c ("watchdog: msc313e: Check if the WDT was running at boot") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260828161348.13212-7-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index c7d558fefc86..e28261c7a8d4 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -31,6 +31,16 @@ struct msc313e_wdt_priv { struct clk *clk; }; +static u32 msc313e_wdt_get_hw_timeout(struct msc313e_wdt_priv *priv) +{ + u16 low, high; + + low = readw(priv->base + REG_WDT_MAX_PRD_L); + high = readw(priv->base + REG_WDT_MAX_PRD_H); + + return ((u32)high << 16) | low; +} + static void msc313e_wdt_set_hw_timeout(struct msc313e_wdt_priv *priv, unsigned int timeout) { @@ -139,7 +149,7 @@ static int msc313e_wdt_probe(struct platform_device *pdev) return ret; /* If the period is non-zero the WDT is running */ - if (readw(priv->base + REG_WDT_MAX_PRD_L) | (readw(priv->base + REG_WDT_MAX_PRD_H) << 16)) { + if (msc313e_wdt_get_hw_timeout(priv)) { set_bit(WDOG_HW_RUNNING, &priv->wdev.status); /* * Keep the clock enabled. The watchdog core will skip the next From 01504d14e47b34779911250dd308a03f6ef681c2 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sat, 29 Aug 2026 00:13:46 +0800 Subject: [PATCH 1320/2366] watchdog: msc313e: Sync timeout value if WDT was running at boot If WDT was running at boot, the hardware timeout might be set to values other than the final software timeout. To be consistent, set the hardware timeout to match the final software timeout (i.e., after watchdog_init_timeout()) if WDT was running. Fixes: ffd264bd152c ("watchdog: msc313e: Check if the WDT was running at boot") Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260828161348.13212-8-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index e28261c7a8d4..4a5cce2a16b1 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -144,12 +144,21 @@ static int msc313e_wdt_probe(struct platform_device *pdev) priv->wdev.max_timeout = U32_MAX / rate; priv->wdev.timeout = MSC313E_WDT_DEFAULT_TIMEOUT; + watchdog_set_drvdata(&priv->wdev, priv); + platform_set_drvdata(pdev, priv); + + watchdog_init_timeout(&priv->wdev, timeout, dev); + watchdog_stop_on_reboot(&priv->wdev); + watchdog_stop_on_unregister(&priv->wdev); + watchdog_stop_ping_on_suspend(&priv->wdev); + ret = clk_prepare_enable(priv->clk); if (ret) return ret; /* If the period is non-zero the WDT is running */ if (msc313e_wdt_get_hw_timeout(priv)) { + msc313e_wdt_set_hw_timeout(priv, priv->wdev.timeout); set_bit(WDOG_HW_RUNNING, &priv->wdev.status); /* * Keep the clock enabled. The watchdog core will skip the next @@ -160,14 +169,6 @@ static int msc313e_wdt_probe(struct platform_device *pdev) clk_disable_unprepare(priv->clk); } - watchdog_set_drvdata(&priv->wdev, priv); - platform_set_drvdata(pdev, priv); - - watchdog_init_timeout(&priv->wdev, timeout, dev); - watchdog_stop_on_reboot(&priv->wdev); - watchdog_stop_on_unregister(&priv->wdev); - watchdog_stop_ping_on_suspend(&priv->wdev); - ret = devm_watchdog_register_device(dev, &priv->wdev); /* If the WDT is running and anything goes wrong, disable the clock. */ From d22c3e0088e85be8131f7a9283f759cdbb20726d Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Wed, 9 Sep 2026 11:29:53 +0200 Subject: [PATCH 1321/2366] selftests/ftrace: Fix unique symbol check in kprobe_non_uniq_symbol.tc The current regex also matches symbols in modules, which makes the test fail on s390 where name_show is present only once in the kernel, but also multiple times in modules: 000001b1401cdc20 t name_show 000001b0c05e6c40 t name_show [mdev] 000001b0c0495f30 t name_show [i2c_core] Fix this by changing the regular expression to only match the function name. Link: https://lore.kernel.org/all/20260909092954.2200558-1-svens@linux.ibm.com/ Fixes: 03b80ff8023a ("selftests/ftrace: Add new test case which checks non unique symbol") Signed-off-by: Sven Schnelle Reviewed-by: Steven Rostedt Signed-off-by: Masami Hiramatsu (Google) --- .../selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc index bc9514428dba..07b1177c1634 100644 --- a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc +++ b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc @@ -6,7 +6,7 @@ SYMBOL='name_show' # We skip this test on kernel where SYMBOL is unique or does not exist. -if [ "$(grep -c -E "[[:alnum:]]+ t ${SYMBOL}" /proc/kallsyms)" -le '1' ]; then +if [ "$(grep -c -E "[[:alnum:]]+ t ${SYMBOL}$" /proc/kallsyms)" -le '1' ]; then exit_unsupported fi From b824476c56a153934c67c9e0f873e1fd967743d6 Mon Sep 17 00:00:00 2001 From: Yilin Zhang Date: Sat, 5 Sep 2026 00:28:00 +0800 Subject: [PATCH 1322/2366] inet: frags: invalidate queues before flushing them fqdir_pre_exit() flushes the skbs from incomplete queues without changing their completion state. A fragment which found a queue before high_thresh was cleared can then acquire the queue lock and reuse stale reassembly metadata. A queue concurrently killed after fqdir->dead is set can instead become INET_FRAG_COMPLETE|INET_FRAG_HASH_DEAD while still holding its old skbs; skipping it because it is complete leaves those references behind until asynchronous fqdir teardown. For IPv6, stale metadata can make ip6_frag_reasm() use the old nhoffset with a new skb and access memory out of bounds. The resulting heap corruption can be leveraged for local privilege escalation when unprivileged network namespaces are available. Unflushed fragments can also keep conntrack references alive after the conntrack per-net cleanup point. Kill each incomplete queue, then flush every queue still owned by the dying rhashtable. HASH_DEAD identifies that ownership, while complete queues without it are already owned by another destroy path and must be left alone. Releasing a timer reference removed by inet_frag_kill() is deferred to inet_frag_putn(), after the queue lock is dropped. KASAN report: BUG: KASAN: slab-out-of-bounds in ipv6_frag_rcv (net/ipv6/reassembly.c:289 (discriminator 2) net/ipv6/reassembly.c:229 (discriminator 2) net/ipv6/reassembly.c:391 (discriminator 2)) Write of size 1 at addr ff110001039c6e00 by task poc/771 Call Trace: ? ipv6_frag_rcv (net/ipv6/reassembly.c:289 (discriminator 2) net/ipv6/reassembly.c:229 (discriminator 2) net/ipv6/reassembly.c:391 (discriminator 2)) ipv6_frag_rcv (net/ipv6/reassembly.c:289 (discriminator 2) net/ipv6/reassembly.c:229 (discriminator 2) net/ipv6/reassembly.c:391 (discriminator 2)) ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:479 (discriminator 5)) ip6_input_finish (net/ipv6/ip6_input.c:534) ipv6_rcv (include/net/dst.h:480 (discriminator 3) net/ipv6/ip6_input.c:119 (discriminator 3) net/ipv6/ip6_input.c:109 (discriminator 3) include/linux/netfilter.h:325 (discriminator 3) include/linux/netfilter.h:319 (discriminator 3) net/ipv6/ip6_input.c:351 (discriminator 3)) packet_sendmsg (net/packet/af_packet.c:3110 net/packet/af_packet.c:3142) __x64_sys_sendmmsg (net/socket.c:2883 net/socket.c:2880 net/socket.c:2880) The buggy address belongs to the object at ff110001039c6b40 which belongs to the cache skbuff_small_head of size 704 The buggy address is located 0 bytes to the right of allocated 704-byte region [ff110001039c6b40, ff110001039c6e00) BUG: KASAN: slab-out-of-bounds in ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:423 (discriminator 1)) Read of size 1 at addr ff110001039c6e08 by task poc/771 Call Trace: ? ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:423 (discriminator 1)) ip6_protocol_deliver_rcu (net/ipv6/ip6_input.c:423 (discriminator 1)) ip6_input_finish (net/ipv6/ip6_input.c:534) ipv6_rcv (include/net/dst.h:480 (discriminator 3) net/ipv6/ip6_input.c:119 (discriminator 3) net/ipv6/ip6_input.c:109 (discriminator 3) include/linux/netfilter.h:325 (discriminator 3) include/linux/netfilter.h:319 (discriminator 3) net/ipv6/ip6_input.c:351 (discriminator 3)) packet_sendmsg (net/packet/af_packet.c:3110 net/packet/af_packet.c:3142) __x64_sys_sendmmsg (net/socket.c:2883 net/socket.c:2880 net/socket.c:2880) packet_sendmsg (net/packet/af_packet.c:2959 net/packet/af_packet.c:3053 net/packet/af_packet.c:3142) __x64_sys_sendmmsg (net/socket.c:2883 net/socket.c:2880 net/socket.c:2880) The buggy address belongs to the object at ff110001039c6b40 which belongs to the cache skbuff_small_head of size 704 The buggy address is located 8 bytes to the right of allocated 704-byte region [ff110001039c6b40, ff110001039c6e00) Fixes: 006a5035b495 ("inet: frags: flush pending skbs in fqdir_pre_exit()") Cc: stable@vger.kernel.org Reported-by: Kimi Security Team Tested-by: Weiming Shi Reviewed-by: Eric Dumazet Signed-off-by: Yilin Zhang Link: https://patch.msgid.link/20260904162800.1095662-1-yilinzhang@moonshot.ai Signed-off-by: Jakub Kicinski --- net/ipv4/inet_fragment.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c index c17e57ec7d5c..b286ee429da8 100644 --- a/net/ipv4/inet_fragment.c +++ b/net/ipv4/inet_fragment.c @@ -235,6 +235,8 @@ void fqdir_pre_exit(struct fqdir *fqdir) rhashtable_walk_start(&hti); while ((fq = rhashtable_walk_next(&hti))) { + int refs = 0; + if (IS_ERR(fq)) { if (PTR_ERR(fq) != -EAGAIN) break; @@ -242,8 +244,12 @@ void fqdir_pre_exit(struct fqdir *fqdir) } spin_lock_bh(&fq->lock); if (!(fq->flags & INET_FRAG_COMPLETE)) + inet_frag_kill(fq, &refs); + + if (fq->flags & INET_FRAG_HASH_DEAD) inet_frag_queue_flush(fq, 0); spin_unlock_bh(&fq->lock); + inet_frag_putn(fq, refs); } rhashtable_walk_stop(&hti); From 59fb389ad6bf50916189e56dafcd225ab977f874 Mon Sep 17 00:00:00 2001 From: "Jan Havran (Advantech Czech)" Date: Mon, 7 Sep 2026 15:48:18 +0200 Subject: [PATCH 1323/2366] net: dsa: lantiq_gswip: fix GSWIP_MDIO_PHY_FCONTX_EN value Per the GSW145 data sheet, the FCONTX (bits 8:7) and FCONRX (bits 6:5) flow-control fields of the PHY_ADDR_n register both encode 00 = AUTO, 01 = EN, 10 = reserved, 11 = DIS. GSWIP_MDIO_PHY_FCONTX_EN was 0x0100, i.e. field value 10 (the reserved encoding), instead of 0x0080 (01 = EN); FCONRX_EN is already 0x0020 (01). Enabling tx flow control therefore wrote the reserved value. Set FCONTX_EN to 0x0080. The register is shared by all supported parts. Fixes: 14fceff4771e ("net: dsa: Add Lantiq / Intel DSA driver for vrx200") Signed-off-by: Jan Havran (Advantech Czech) Reviewed-by: Daniel Golle Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260907134818.16670-4-havran.jan@email.cz Signed-off-by: Jakub Kicinski --- drivers/net/dsa/lantiq/lantiq_gswip.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.h b/drivers/net/dsa/lantiq/lantiq_gswip.h index bc3686faad0d..0b75be14dc10 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip.h +++ b/drivers/net/dsa/lantiq/lantiq_gswip.h @@ -42,7 +42,7 @@ #define GSWIP_MDIO_PHY_FDUP_DIS 0x0600 #define GSWIP_MDIO_PHY_FCONTX_MASK 0x0180 #define GSWIP_MDIO_PHY_FCONTX_AUTO 0x0000 -#define GSWIP_MDIO_PHY_FCONTX_EN 0x0100 +#define GSWIP_MDIO_PHY_FCONTX_EN 0x0080 #define GSWIP_MDIO_PHY_FCONTX_DIS 0x0180 #define GSWIP_MDIO_PHY_FCONRX_MASK 0x0060 #define GSWIP_MDIO_PHY_FCONRX_AUTO 0x0000 From 66ef5adb75446627f8b6c26cd04f2adc86d4de56 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Sat, 5 Sep 2026 15:02:32 +0200 Subject: [PATCH 1324/2366] net: ks8851: Fix receiver error in 100BASE-TX mode following software power-down KSZ8851 errata sheet DS80000716D-page 4 Module 3 [1] states that, when issuing a software power-down (PMECR[1:0] = 10) followed by a power-on (PMECR[1:0] = 00), the receiver circuit can fail to start properly preventing communication. The Transmitter will still send data, but no data will be received. The errata sheet also includes a workaround, which states that, it is recommended that the software power-down feature not be used. Implement that workaround and drop the entry into software power-down mode. The ks8851_write_mac_addr() calls entry into normal power-on mode at the very beginning of the function, therefore dropping the second call to enter software power-down mode is sufficient here. The ks8851_net_stop() can only be called after ks8851_net_start() was already called, and ks8851_net_start() also makes the MAC enter normal power-on mode, therefore it is also fine to drop the call to enter software power-down mode from ks8851_net_stop(). This will lead to a slight increase in power consumption, but it also fixes a sporadic reliability problem on at least KSZ8851-16MLL, which is where the problem was reported and this fix was tested. [1] https://ww1.microchip.com/downloads/en/DeviceDoc/80000716D.pdf Fixes: 3ba81f3ece3c ("net: Micrel KS8851 SPI network driver") Reviewed-by: Sebastian Andrzej Siewior Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260905130327.203851-1-marex@nabladev.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/micrel/ks8851_common.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/net/ethernet/micrel/ks8851_common.c b/drivers/net/ethernet/micrel/ks8851_common.c index 4afbb40bc0e4..d49f281c7867 100644 --- a/drivers/net/ethernet/micrel/ks8851_common.c +++ b/drivers/net/ethernet/micrel/ks8851_common.c @@ -143,9 +143,6 @@ static int ks8851_write_mac_addr(struct net_device *dev) ks8851_wrreg16(ks, KS_MAR(i), val); } - if (!netif_running(dev)) - ks8851_set_powermode(ks, PMECR_PM_SOFTDOWN); - ks8851_unlock(ks); return 0; @@ -478,8 +475,7 @@ static int ks8851_net_open(struct net_device *dev) * @dev: The device being closed. * * Called to close down a network device which has been active. Cancel any - * work, shutdown the RX and TX process and then place the chip into a low - * power state whilst it is not being used. + * work and shutdown the RX and TX process. */ static int ks8851_net_stop(struct net_device *dev) { @@ -506,8 +502,6 @@ static int ks8851_net_stop(struct net_device *dev) /* shutdown TX process */ ks8851_wrreg16(ks, KS_TXCR, 0x0000); - /* set powermode to soft power down to save power */ - ks8851_set_powermode(ks, PMECR_PM_SOFTDOWN); ks8851_unlock(ks); /* ensure any queued tx buffers are dumped */ From cb26524ef4ac28fcfa554c0656e8dc412c38a8ff Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 9 Sep 2026 17:02:40 -0300 Subject: [PATCH 1325/2366] smb: client: fix one-byte OOB read in smb2_parse_native_symlink() When parsing a share-root relative native symlink, memcpy copies smb_target+1 (skipping the leading separator) but uses strlen(smb_target)+1 as the length, reading one byte past the allocated buffer. This fixes the following KASAN splat when accessing an SMB symlink with a target of '\a\b': BUG: KASAN: slab-out-of-bounds in smb2_parse_native_symlink+0x4f5/0xca0 Read of size 5 at addr ffff88800878fe21 by task netfsfuzz-execu/1 CPU: 1 UID: 0 PID: 1 Comm: netfsfuzz-execu Tainted: G N 7.2.0-11943-g2709dd5ae32f-dirty #1 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996) Call Trace: dump_stack_lvl+0x7b/0xa0 print_report+0xd0/0x630 kasan_report+0xe5/0x120 kasan_check_range+0x105/0x1b0 __asan_memcpy+0x23/0x60 smb2_parse_native_symlink+0x4f5/0xca0 parse_reparse_point+0x68a/0x1530 reparse_info_to_fattr+0x752/0xa20 cifs_get_fattr+0x873/0x15b0 cifs_get_inode_info+0xc0/0x310 cifs_lookup+0x308/0xa70 __lookup_slow+0x122/0x2b0 lookup_slow+0x50/0x70 path_lookupat+0x525/0xaf0 filename_lookup+0x1f2/0x550 vfs_statx+0xd1/0x1a0 vfs_fstatat+0x65/0xc0 __do_sys_newfstatat+0x9a/0x120 do_syscall_64+0xdd/0x4a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Reported-by: Yuanfu Xie Fixes: 723f4ef90452 ("cifs: Fix parsing native symlinks relative to the export") Suggested-by: Pali Rohar Reviewed-by: Pali Rohar Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index b6bded042e78..8a1b9e8be5ba 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -971,7 +971,8 @@ int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, linux_target[i*3 + 1] = '.'; linux_target[i*3 + 2] = sep; } - memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */ + /* +1 to skip leading sep */ + memcpy(linux_target + levels*3, smb_target+1, smb_target_len-1); } else { /* * This is either an absolute symlink in POSIX-style format From 286b175bb03893949f24621f356abd9d20368b0a Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 23 Aug 2026 19:59:01 +0200 Subject: [PATCH 1326/2366] hwmon: (chipcap2) fix channels in humidity alarm notifications hwmon_notify_event() expects the channel number as its last argument, taken into account with the type parameter that it is a humidity sensor type. Given that this device only provides one humidity channel, 0 must be passed. The custom construct to enumerate the channels makes wrong assumptions by listing all types together (temperature and humidity). Remove the custom channel enumeration and pass the right channel to hwmon_notify_event() for hwmon_humidity_min_alarm and hwmon_humidity_max_alarm. Fixes: 3af350929e75 ("hwmon: Add support for Amphenol ChipCap 2") Cc: stable@vger.kernel.org Signed-off-by: Javier Carrasco Link: https://patch.msgid.link/20260823-chipcap2_locks-v2-1-6a26c8e9e2fc@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 086571d556b7..9bef767b589e 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -92,11 +92,6 @@ struct cc2_data { bool process_irqs; }; -enum cc2_chan_addr { - CC2_CHAN_TEMP = 0, - CC2_CHAN_HUMIDITY, -}; - /* %RH as a per cent mille from a register value */ static long cc2_rh_convert(u16 data) { @@ -499,7 +494,7 @@ static irqreturn_t cc2_low_interrupt(int irq, void *data) if (cc2->process_irqs) { hwmon_notify_event(cc2->hwmon, hwmon_humidity, - hwmon_humidity_min_alarm, CC2_CHAN_HUMIDITY); + hwmon_humidity_min_alarm, 0); cc2->rh_alarm.low_alarm = true; } @@ -512,7 +507,7 @@ static irqreturn_t cc2_high_interrupt(int irq, void *data) if (cc2->process_irqs) { hwmon_notify_event(cc2->hwmon, hwmon_humidity, - hwmon_humidity_max_alarm, CC2_CHAN_HUMIDITY); + hwmon_humidity_max_alarm, 0); cc2->rh_alarm.high_alarm = true; } From 6d760f8b41aed74de4402440e4db663d261478bd Mon Sep 17 00:00:00 2001 From: Vishnu Razdan Date: Mon, 24 Aug 2026 23:58:00 -0700 Subject: [PATCH 1327/2366] hwmon: (pmbus) Clear generic status alarms with CLEAR_FAULTS Some hwmon alarms fall back to STATUS_WORD summary bits when no individual limit alarm is available. On PMBus 1.2 and newer devices, pmbus_get_boolean() acknowledges these alarms with the same byte-data write used for detailed status registers. For example, PB_STATUS_INPUT is 0x2000, so it is truncated to zero when passed to _pmbus_write_byte_data(). The resulting write cannot acknowledge the input alarm. PMBus 1.3 Part II, sections 10.2.4 and 10.2.5, excludes ordinary STATUS_BYTE and STATUS_WORD summary bits from individual clearing. Their summary bits clear when the underlying status bits clear, so changing this to a word-data write would not fix the generic input alarm either. Use the existing page CLEAR_FAULTS path for generic STATUS_WORD alarms, including devices whose status accessor uses STATUS_BYTE. Keep individual byte writes for detailed status registers on PMBus 1.2 and newer devices. As with the existing older-device fallback, CLEAR_FAULTS can clear other latched status; an active condition can reassert its status. Fixes: 35f165f08950 ("hwmon: (pmbus) Clear pmbus fault/warning bits after read") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Vishnu Razdan Link: https://patch.msgid.link/20260824-vrazdan-pmbus-status-word-b4-v1-1-2606ecd0c029@openai.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 806c9a4913bb..5f69c1420b4e 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -1275,7 +1275,9 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, regval = status & mask; if (regval) { - if (data->revision >= PMBUS_REV_12) { + /* Generic STATUS_WORD alarms are not individually clearable. */ + if (data->revision >= PMBUS_REV_12 && + reg != PMBUS_STATUS_WORD) { ret = _pmbus_write_byte_data(client, page, reg, regval); if (ret) return ret; From 508baf1713f32f287bfb4f85d759403ec8ba35a3 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 31 Aug 2026 09:45:09 +0800 Subject: [PATCH 1328/2366] hwmon: (corsair-cpro) Create debugfs entries after hwmon registration ccp_debugfs_init() registers debugfs files whose private data is the devm allocated ccp. It runs before hwmon_device_register_with_info(), so when that registration fails, ccp_probe() returns with the files still in place. The HID core then frees ccp, and ccp_remove() is not called for a failed probe, so nothing removes them later either. Reading one of the files dereferences the freed pointer. Create the debugfs entries only after the hwmon device has been registered, so no failing path can leave them behind. The two version queries stay where they are. They send USB commands without holding ccp->mutex, which is only safe as long as nothing else can call send_usb_cmd(); once the hwmon device is registered its callbacks can do so concurrently. Only the debugfs creation moves, and it is told which queries succeeded. Reported-by: Sashiko Closes: https://lore.kernel.org/linux-hwmon/20260708031612.BD7E61F000E9@smtp.kernel.org/ Suggested-by: Guenter Roeck Fixes: 5997eb60f896 ("hwmon: (corsair-cpro) Add firmware and bootloader information") Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260831014509.3352442-1-lilinmao@kylinos.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/corsair-cpro.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/corsair-cpro.c b/drivers/hwmon/corsair-cpro.c index 8354a002f4c5..56de0fe0f544 100644 --- a/drivers/hwmon/corsair-cpro.c +++ b/drivers/hwmon/corsair-cpro.c @@ -566,21 +566,18 @@ static int bootloader_show(struct seq_file *seqf, void *unused) } DEFINE_SHOW_ATTRIBUTE(bootloader); -static void ccp_debugfs_init(struct ccp_device *ccp) +static void ccp_debugfs_init(struct ccp_device *ccp, bool fw_valid, bool bl_valid) { char name[32]; - int ret; scnprintf(name, sizeof(name), "corsaircpro-%s", dev_name(&ccp->hdev->dev)); ccp->debugfs = debugfs_create_dir(name, NULL); - ret = get_fw_version(ccp); - if (!ret) + if (fw_valid) debugfs_create_file("firmware_version", 0444, ccp->debugfs, ccp, &firmware_fops); - ret = get_bl_version(ccp); - if (!ret) + if (bl_valid) debugfs_create_file("bootloader_version", 0444, ccp->debugfs, ccp, &bootloader_fops); } @@ -588,6 +585,7 @@ static void ccp_debugfs_init(struct ccp_device *ccp) static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct ccp_device *ccp; + bool fw_valid, bl_valid; int ret; ccp = devm_kzalloc(&hdev->dev, sizeof(*ccp), GFP_KERNEL); @@ -632,7 +630,13 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret) goto out_hw_close; - ccp_debugfs_init(ccp); + /* + * Query the versions before registering the hwmon device: they send + * USB commands without holding ccp->mutex, which is only safe while + * nothing else can call send_usb_cmd(). + */ + fw_valid = !get_fw_version(ccp); + bl_valid = !get_bl_version(ccp); ccp->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsaircpro", ccp, &ccp_chip_info, NULL); @@ -641,6 +645,8 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id) goto out_hw_close; } + ccp_debugfs_init(ccp, fw_valid, bl_valid); + return 0; out_hw_close: From bb2424c3502cc72292eedade46960c331d5f28fb Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Tue, 1 Sep 2026 22:54:04 +0700 Subject: [PATCH 1329/2366] hwmon: (gpio-fan) take fan_data->lock in gpio_fan_shutdown() set_fan_speed() writes the control GPIOs one bit at a time. Every other caller locks around it; gpio_fan_shutdown() doesn't. If it races a locked caller, the GPIO writes can interleave and leave the fan at a speed neither caller asked for. Fixes: b95579cd8795 ("hwmon: (gpio-fan) Add a shutdown handler to poweroff the fans") Reported-by: Sashiko AI review Link: https://lore.kernel.org/r/20260830152150.27F5F1F000E9@smtp.kernel.org Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Link: https://patch.msgid.link/20260901155404.1532092-1-congnt264@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/gpio-fan.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index 7f36e5f6f223..df8bd9707605 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -612,8 +612,11 @@ static void gpio_fan_shutdown(struct platform_device *pdev) { struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); - if (fan_data->gpios) + if (fan_data->gpios) { + mutex_lock(&fan_data->lock); set_fan_speed(fan_data, 0); + mutex_unlock(&fan_data->lock); + } } static int gpio_fan_runtime_suspend(struct device *dev) From 09a9e1746a87845d7d8e2b4e23bb613306effdff Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 20:50:44 +0800 Subject: [PATCH 1330/2366] hwmon: (aspeed-pwm-tacho) Propagate reset deassert errors aspeed_pwm_tacho_probe() installs its reset cleanup action and configures the controller after an unchecked reset deassertion. Stop probing when the reset controller rejects the transition, before the hwmon device becomes visible. Fixes: 18c514cc0e02 ("hwmon: (aspeed-pwm-tacho) Deassert reset in probe") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830125044.97718-1-pengpeng@iscas.ac.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/aspeed-pwm-tacho.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/aspeed-pwm-tacho.c b/drivers/hwmon/aspeed-pwm-tacho.c index 1c5945d4ba37..bfce589c3fb1 100644 --- a/drivers/hwmon/aspeed-pwm-tacho.c +++ b/drivers/hwmon/aspeed-pwm-tacho.c @@ -934,7 +934,9 @@ static int aspeed_pwm_tacho_probe(struct platform_device *pdev) "missing or invalid reset controller device tree entry"); return PTR_ERR(priv->rst); } - reset_control_deassert(priv->rst); + ret = reset_control_deassert(priv->rst); + if (ret) + return ret; ret = devm_add_action_or_reset(dev, aspeed_pwm_tacho_remove, priv); if (ret) From 4ee875c423c66c45d7ef7bbff403cd0e3971e0a2 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 28 Aug 2026 14:19:49 +0800 Subject: [PATCH 1331/2366] hwmon: (corsair-cpro) Remove debugfs entries when probe fails ccp_debugfs_init() registers debugfs files whose private data is the devm allocated ccp. If hwmon_device_register_with_info() fails right after it, ccp_probe() returns without removing them: the HID core then frees ccp, and ccp_remove() is not called for a failed probe, so the files stay behind. Reading one of them dereferences the freed pointer. Remove the debugfs entries on that error path. debugfs_remove_recursive() waits for readers already inside the show callbacks, so ccp is no longer reachable through debugfs by the time probe returns. Reported-by: Sashiko Closes: https://lore.kernel.org/linux-hwmon/20260708031612.BD7E61F000E9@smtp.kernel.org/ Fixes: 5997eb60f896 ("hwmon: (corsair-cpro) Add firmware and bootloader information") Signed-off-by: Linmao Li Link: https://patch.msgid.link/20260828061949.3151191-1-lilinmao@kylinos.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/corsair-cpro.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/corsair-cpro.c b/drivers/hwmon/corsair-cpro.c index 56de0fe0f544..c09645152613 100644 --- a/drivers/hwmon/corsair-cpro.c +++ b/drivers/hwmon/corsair-cpro.c @@ -642,13 +642,15 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id) ccp, &ccp_chip_info, NULL); if (IS_ERR(ccp->hwmon_dev)) { ret = PTR_ERR(ccp->hwmon_dev); - goto out_hw_close; + goto out_debugfs_remove; } ccp_debugfs_init(ccp, fw_valid, bl_valid); return 0; +out_debugfs_remove: + debugfs_remove_recursive(ccp->debugfs); out_hw_close: hid_hw_close(hdev); hid_device_io_stop(hdev); From 8042312e73c50de82634ce63eae7cf219464b481 Mon Sep 17 00:00:00 2001 From: Arie Miller Date: Thu, 3 Sep 2026 22:21:28 -0400 Subject: [PATCH 1332/2366] hwmon: (asus_rog_ryujin) Validate HID report lengths rog_ryujin_raw_event() parses response headers and payload fields without first checking that they are present in the received report. A short report can therefore make the driver consume uninitialized bytes from the HID transport buffer and expose them as sensor values through sysfs. Validate the response header and the fields used by each response type before parsing them. Fixes: ed3e03790c5c ("hwmon: Add driver for ASUS ROG RYUJIN II 360 AIO cooler") Reported-by: Sashiko Closes: https://lore.kernel.org/linux-hwmon/20260812104617.858D01F000E9@smtp.kernel.org/ Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol sparse Signed-off-by: Arie Miller Link: https://patch.msgid.link/20260904022129.97896-2-renari@arimil.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus_rog_ryujin.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c index 702edb831394..f4d99c510369 100644 --- a/drivers/hwmon/asus_rog_ryujin.c +++ b/drivers/hwmon/asus_rog_ryujin.c @@ -422,10 +422,15 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo { struct rog_ryujin_data *priv = hid_get_drvdata(hdev); - if (data[0] != RYUJIN_CMD_PREFIX) + if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) return 0; if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { + if (size <= priv->info->temp_offset + 1 || + size <= priv->info->pump_speed_offset + 1 || + size <= priv->info->fan_speed_offset + 1) + return 0; + /* Received coolant temp and speeds of pump and internal fan */ priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + data[priv->info->temp_offset + 1] * 100; @@ -437,6 +442,9 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo if (!completion_done(&priv->cooler_status_received)) complete_all(&priv->cooler_status_received); } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) + return 0; + /* Received speeds of four fans attached to the controller */ priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); priv->speed_input[3] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_2); @@ -446,6 +454,9 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo if (!completion_done(&priv->controller_status_received)) complete_all(&priv->controller_status_received); } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_INTERNAL_FAN_DUTY) + return 0; + /* Received report for pump and internal fan duties (in %) */ if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { /* @@ -472,6 +483,9 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo if (!completion_done(&priv->cooler_duty_received)) complete_all(&priv->cooler_duty_received); } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_DUTY) + return 0; + /* Received report for controller duty for fans (in PWM) */ if (data[RYUJIN_CONTROLLER_DUTY] == 0) { /* From 06d48355bf41028c1321acda6a4391cd70098be8 Mon Sep 17 00:00:00 2001 From: Arie Miller Date: Thu, 3 Sep 2026 22:21:29 -0400 Subject: [PATCH 1333/2366] hwmon: (asus_rog_ryujin) Synchronize HID command and report handling rog_ryujin_execute_cmd() holds status_report_request_lock while reinitializing a completion, intending to exclude raw-event handling. However, rog_ryujin_raw_event() does not acquire the lock when it updates the completion. A response can therefore race with reinit_completion() and be lost, leaving the command to time out. Hold the lock while parsing reports and updating their completions. Use the irqsave variants in both paths because raw-event handling may run in interrupt context. Fixes: ed3e03790c5c ("hwmon: Add driver for ASUS ROG RYUJIN II 360 AIO cooler") Reported-by: Sashiko Closes: https://lore.kernel.org/linux-hwmon/20260812104617.858D01F000E9@smtp.kernel.org/ Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol sparse Signed-off-by: Arie Miller Link: https://patch.msgid.link/20260904022129.97896-3-renari@arimil.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus_rog_ryujin.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c index f4d99c510369..e297557ca346 100644 --- a/drivers/hwmon/asus_rog_ryujin.c +++ b/drivers/hwmon/asus_rog_ryujin.c @@ -184,6 +184,7 @@ static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, struct completion *status_completion) { + unsigned long flags; int ret; /* @@ -191,9 +192,9 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i * completion. Reinit is done because hidraw could have triggered * the raw event parsing and marked the passed in completion as done. */ - spin_lock_bh(&priv->status_report_request_lock); + spin_lock_irqsave(&priv->status_report_request_lock, flags); reinit_completion(status_completion); - spin_unlock_bh(&priv->status_report_request_lock); + spin_unlock_irqrestore(&priv->status_report_request_lock, flags); /* Send command for getting data */ ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); @@ -421,15 +422,18 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo int size) { struct rog_ryujin_data *priv = hid_get_drvdata(hdev); + unsigned long flags; if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) return 0; + spin_lock_irqsave(&priv->status_report_request_lock, flags); + if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { if (size <= priv->info->temp_offset + 1 || size <= priv->info->pump_speed_offset + 1 || size <= priv->info->fan_speed_offset + 1) - return 0; + goto unlock; /* Received coolant temp and speeds of pump and internal fan */ priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + @@ -443,7 +447,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo complete_all(&priv->cooler_status_received); } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) - return 0; + goto unlock; /* Received speeds of four fans attached to the controller */ priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); @@ -455,7 +459,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo complete_all(&priv->controller_status_received); } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { if (size <= RYUJIN_INTERNAL_FAN_DUTY) - return 0; + goto unlock; /* Received report for pump and internal fan duties (in %) */ if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { @@ -474,7 +478,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo * We're expecting a report, so parse it. */ goto read_cooler_duty; - return 0; + goto unlock; } read_cooler_duty: priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); @@ -484,7 +488,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo complete_all(&priv->cooler_duty_received); } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { if (size <= RYUJIN_CONTROLLER_DUTY) - return 0; + goto unlock; /* Received report for controller duty for fans (in PWM) */ if (data[RYUJIN_CONTROLLER_DUTY] == 0) { @@ -503,7 +507,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo * We're expecting a report, so parse it. */ goto read_controller_duty; - return 0; + goto unlock; } read_controller_duty: priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; @@ -512,6 +516,8 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo complete_all(&priv->controller_duty_received); } +unlock: + spin_unlock_irqrestore(&priv->status_report_request_lock, flags); return 0; } From c88a6338ae485e4d6210cc74cdb7664d6476c925 Mon Sep 17 00:00:00 2001 From: Ali Ahmet Memis Date: Mon, 3 Aug 2026 10:21:48 +0000 Subject: [PATCH 1334/2366] hwmon: (nct6694) do not expose enable on DTIN temperature channels The driver registers 26 temperature channels, all advertising HWMON_T_ENABLE, and indexes the enable bitmap with the raw channel: data->hwmon_en.tin_en[channel / 8] |= BIT(channel % 8); tin_en is two bytes and only covers the 5 THR and 5 TDP channels (index 0-9). The 16 DTIN channels (index 10-25) are enabled by the firmware and were never meant to carry an enable bit. Because the control structure is packed, writing temp17_enable and above indexes past tin_en into the fin_en bytes that follow it, so it toggles fan enable state instead; nct6694_hwmon_init() then sends the whole structure back to the device, and reads report fan state as temperature state. It stays within the structure, so this is not a memory safety problem, but on a board that uses the fan channels it is not harmless. Give the DTIN channels a temperature config without HWMON_T_ENABLE so the core never creates their enable attribute. The enable path is then reachable only for the first 10 channels, which stay within tin_en, and fin_en is left alone. The DTIN input and limit attributes are unchanged. Fixes: 197e779d29d8 ("hwmon: Add Nuvoton NCT6694 HWMON support") Suggested-by: Ming Yu Link: https://lore.kernel.org/all/20260802124730.20387-1-ali@iusegentoo.com/ Signed-off-by: Ali Ahmet Memis Link: https://patch.msgid.link/20260803102148.14196-1-ali@iusegentoo.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6694-hwmon.c | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c index 6dcf22ca5018..9a9a4db434c4 100644 --- a/drivers/hwmon/nct6694-hwmon.c +++ b/drivers/hwmon/nct6694-hwmon.c @@ -159,6 +159,9 @@ static inline s8 temp_to_reg(long val) #define NCT6694_HWMON_TEMP_CONFIG (HWMON_T_INPUT | HWMON_T_ENABLE | \ HWMON_T_MAX | HWMON_T_MAX_HYST | \ HWMON_T_MAX_ALARM) +#define NCT6694_HWMON_DTIN_CONFIG (HWMON_T_INPUT | \ + HWMON_T_MAX | HWMON_T_MAX_HYST | \ + HWMON_T_MAX_ALARM) #define NCT6694_HWMON_FAN_CONFIG (HWMON_F_INPUT | HWMON_F_ENABLE | \ HWMON_F_MIN | HWMON_F_MIN_ALARM) #define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_ENABLE | \ @@ -193,22 +196,22 @@ static const struct hwmon_channel_info *nct6694_info[] = { NCT6694_HWMON_TEMP_CONFIG, /* TDP2 */ NCT6694_HWMON_TEMP_CONFIG, /* TDP3 */ NCT6694_HWMON_TEMP_CONFIG, /* TDP4 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN0 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN1 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN2 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN3 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN4 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN5 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN6 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN7 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN8 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN9 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN10 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN11 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN12 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN13 */ - NCT6694_HWMON_TEMP_CONFIG, /* DTIN14 */ - NCT6694_HWMON_TEMP_CONFIG), /* DTIN15 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN0 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN1 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN2 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN3 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN4 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN5 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN6 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN7 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN8 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN9 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN10 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN11 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN12 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN13 */ + NCT6694_HWMON_DTIN_CONFIG, /* DTIN14 */ + NCT6694_HWMON_DTIN_CONFIG), /* DTIN15 */ HWMON_CHANNEL_INFO(fan, NCT6694_HWMON_FAN_CONFIG, /* FIN0 */ From ef39fca8508597fa565cf2be72a884a712fb98af Mon Sep 17 00:00:00 2001 From: Ratheesh Kannoth Date: Thu, 3 Sep 2026 07:35:33 +0530 Subject: [PATCH 1335/2366] octeontx2-pf: reset HTB scheduler topology before freeing queues HTB offload programs NIX_AF_TLxX_TOPOLOGY on QoS-allocated scheduler queues via otx2_qos_txschq_set_parent_topology(), but teardown freed those queues without clearing TOPOLOGY. The AF only restores PARENT and SCHEDULE on free, so PRIO_ANCHOR/RR_PRIO settings can survive in the shared scheduler pool and affect later allocations. Add otx2_qos_reset_schq_topology() and otx2_qos_free_hw_schq() to zero TL4 through TL2 TOPOLOGY before each schq is returned to the AF during hierarchy teardown and cfg rollback. Skip the aggregation level (TL1): it is a per-tx-link queue shared by the PF, default Tx hierarchy and VFs, and is not freed back to the AF by nix_txschq_free_one(). Fixes: 5e6808b4c68d ("octeontx2-pf: Add support for HTB offload") Signed-off-by: Ratheesh Kannoth Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260903020533.3068041-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/marvell/octeontx2/nic/qos.c | 60 +++++++++++++++++-- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/qos.c b/drivers/net/ethernet/marvell/octeontx2/nic/qos.c index 69c0911e28e9..f160b1618efa 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/qos.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/qos.c @@ -235,13 +235,63 @@ static int otx2_qos_txschq_set_parent_topology(struct otx2_nic *pfvf, return rc; } +static int otx2_qos_reset_schq_topology(struct otx2_nic *pfvf, u16 lvl, + u16 schq) +{ + struct mbox *mbox = &pfvf->mbox; + struct nix_txschq_config *cfg; + int rc; + + if (lvl < NIX_TXSCH_LVL_TL4 || lvl >= NIX_TXSCH_LVL_TL1) + return 0; + + mutex_lock(&mbox->lock); + + cfg = otx2_mbox_alloc_msg_nix_txschq_cfg(mbox); + if (!cfg) { + mutex_unlock(&mbox->lock); + return -ENOMEM; + } + + cfg->lvl = lvl; + cfg->num_regs = 1; + + if (lvl == NIX_TXSCH_LVL_TL4) + cfg->reg[0] = NIX_AF_TL4X_TOPOLOGY(schq); + else if (lvl == NIX_TXSCH_LVL_TL3) + cfg->reg[0] = NIX_AF_TL3X_TOPOLOGY(schq); + else if (lvl == NIX_TXSCH_LVL_TL2) + cfg->reg[0] = NIX_AF_TL2X_TOPOLOGY(schq); + + cfg->regval[0] = 0; + + rc = otx2_sync_mbox_msg(mbox); + + mutex_unlock(&mbox->lock); + + return rc; +} + +static void otx2_qos_free_hw_schq(struct otx2_nic *pfvf, u16 lvl, u16 schq) +{ + int err; + + err = otx2_qos_reset_schq_topology(pfvf, lvl, schq); + if (err) + netdev_warn(pfvf->netdev, + "QoS: failed to reset topology for schq %u at level %u: %d\n", + schq, lvl, err); + + otx2_txschq_free_one(pfvf, lvl, schq); +} + static void otx2_qos_free_hw_node_schq(struct otx2_nic *pfvf, struct otx2_qos_node *parent) { struct otx2_qos_node *node; list_for_each_entry_reverse(node, &parent->child_schq_list, list) - otx2_txschq_free_one(pfvf, node->level, node->schq); + otx2_qos_free_hw_schq(pfvf, node->level, node->schq); } static void otx2_qos_free_hw_node(struct otx2_nic *pfvf, @@ -252,7 +302,7 @@ static void otx2_qos_free_hw_node(struct otx2_nic *pfvf, list_for_each_entry_safe(node, tmp, &parent->child_list, list) { otx2_qos_free_hw_node(pfvf, node); otx2_qos_free_hw_node_schq(pfvf, node); - otx2_txschq_free_one(pfvf, node->level, node->schq); + otx2_qos_free_hw_schq(pfvf, node->level, node->schq); } } @@ -266,7 +316,7 @@ static void otx2_qos_free_hw_cfg(struct otx2_nic *pfvf, otx2_qos_free_hw_node_schq(pfvf, node); /* free node hw mappings */ - otx2_txschq_free_one(pfvf, node->level, node->schq); + otx2_qos_free_hw_schq(pfvf, node->level, node->schq); mutex_unlock(&pfvf->qos.qos_lock); } @@ -913,7 +963,7 @@ static void otx2_qos_free_cfg(struct otx2_nic *pfvf, struct otx2_qos_cfg *cfg) for (lvl = 0; lvl < NIX_TXSCH_LVL_CNT; lvl++) { for (idx = 0; idx < cfg->schq[lvl]; idx++) { schq = cfg->schq_list[lvl][idx]; - otx2_txschq_free_one(pfvf, lvl, schq); + otx2_qos_free_hw_schq(pfvf, lvl, schq); } } @@ -921,7 +971,7 @@ static void otx2_qos_free_cfg(struct otx2_nic *pfvf, struct otx2_qos_cfg *cfg) for (idx = 0; idx < cfg->schq_contig[lvl]; idx++) { if (cfg->schq_index_used[lvl][idx]) { schq = cfg->schq_contig_list[lvl][idx]; - otx2_txschq_free_one(pfvf, lvl, schq); + otx2_qos_free_hw_schq(pfvf, lvl, schq); } } } From 264bf9655c3d067d775a46f05eb8c871c488a864 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 6 Sep 2026 19:09:25 +0200 Subject: [PATCH 1336/2366] scsi: pm80xx: Fix the use_msix, use_tasklet and read_wwn parameter descriptions The MODULE_PARM_DESC() lines of use_msix, use_tasklet and read_wwn all name a parameter zoned, which does not exist, and the use_tasklet one repeats the use_msix text. modinfo shows three "zoned" entries and no description for the real parameters. Name the right parameters and describe use_tasklet. Fixes: efa1fca45082 ("scsi: pm8001: Remove PM8001_USE_MSIX") Fixes: 205430290ad0 ("scsi: pm8001: Remove PM8001_USE_TASKLET") Fixes: 80975adc79dd ("scsi: pm8001: Remove PM8001_READ_VPD") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/20260906170925.2524-1-kmehltretter@gmail.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/pm8001/pm8001_init.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/scsi/pm8001/pm8001_init.c b/drivers/scsi/pm8001/pm8001_init.c index 54b35893261a..5af81c73a8f8 100644 --- a/drivers/scsi/pm8001/pm8001_init.c +++ b/drivers/scsi/pm8001/pm8001_init.c @@ -58,15 +58,15 @@ MODULE_PARM_DESC(link_rate, "Enable link rate.\n" bool pm8001_use_msix = true; module_param_named(use_msix, pm8001_use_msix, bool, 0444); -MODULE_PARM_DESC(zoned, "Use MSIX interrupts. Default: true"); +MODULE_PARM_DESC(use_msix, "Use MSIX interrupts. Default: true"); static bool pm8001_use_tasklet = true; module_param_named(use_tasklet, pm8001_use_tasklet, bool, 0444); -MODULE_PARM_DESC(zoned, "Use MSIX interrupts. Default: true"); +MODULE_PARM_DESC(use_tasklet, "Use tasklets for interrupt handling. Default: true"); static bool pm8001_read_wwn = true; module_param_named(read_wwn, pm8001_read_wwn, bool, 0444); -MODULE_PARM_DESC(zoned, "Get WWN from the controller. Default: true"); +MODULE_PARM_DESC(read_wwn, "Get WWN from the controller. Default: true"); uint pcs_event_log_severity = 0x03; module_param(pcs_event_log_severity, int, 0644); From 779f202a92ef10a426efc07d0f4267918cb07ca3 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 6 Sep 2026 19:10:09 +0200 Subject: [PATCH 1337/2366] scsi: qla2xxx: Fix the ql2xfc2target parameter description The module parameter is ql2xfc2target, but its MODULE_PARM_DESC() names qla2xfc2target, so modinfo describes a parameter that does not exist and shows no description for the real one. Use the parameter name in the description. Fixes: 877b03795fcf ("scsi: qla2xxx: Add option to disable FC2 Target support") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260906171009.2560-1-kmehltretter@gmail.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/qla2xxx/qla_os.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index c0efdbff5da7..3c412c7fb6fe 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -352,7 +352,7 @@ MODULE_PARM_DESC(ql2xnvme_queues, int ql2xfc2target = 1; module_param(ql2xfc2target, int, 0444); -MODULE_PARM_DESC(qla2xfc2target, +MODULE_PARM_DESC(ql2xfc2target, "Enables FC2 Target support. " "0 - FC2 Target support is disabled. " "1 - FC2 Target support is enabled (default)."); From be83178bfc44588f6e3adb827ed874c683193466 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 6 Sep 2026 18:01:04 +0000 Subject: [PATCH 1338/2366] vxlan: initialize _md in vxlan_xmit_one() If a VXLAN device is configured with both VXLAN_F_COLLECT_METADATA and VXLAN_F_GBP, and a packet is transmitted through it using an external ip_tunnel_info that lacks the IP_TUNNEL_VXLAN_OPT_BIT flag, md is left pointing to the uninitialized _md stack variable: if (test_bit(IP_TUNNEL_VXLAN_OPT_BIT, info->key.tun_flags)) { if (info->options_len < sizeof(*md)) goto drop; md = ip_tunnel_info_opts(info); } Because IP_TUNNEL_VXLAN_OPT_BIT is not set, md is not updated and remains pointing to _md. Later, vxlan_build_skb() is called with md, which eventually calls vxlan_build_gbp_hdr(): if (vxflags & VXLAN_F_GBP) vxlan_build_gbp_hdr(vxh, md); Inside vxlan_build_gbp_hdr(), md->gbp is read: if (!md->gbp) return; gbp = (struct vxlanhdr_gbp *)vxh; ... if (md->gbp & VXLAN_GBP_DONT_LEARN) gbp->dont_learn = 1; If the stack contains garbage, this causes: 1) VXLAN_HF_GBP flag to be spuriously set in the VXLAN header. 2) gbp->dont_learn and gbp->policy_applied to be set from stack bits. 3) gbp->policy_id to receive 16 bits of uninitialized kernel stack data, leaking it onto the wire. Fix this by zero-initializing _md. If IP_TUNNEL_VXLAN_OPT_BIT is not present, md->gbp remains 0, and vxlan_build_gbp_hdr() returns early without modifying the VXLAN header. Fixes: ee122c79d422 ("vxlan: Flow based tunneling") Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260906180111.1973188-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index be95af64a1f5..c1d54339fa2b 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -2373,7 +2373,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, struct ip_tunnel_key key; struct vxlan_dev *vxlan = netdev_priv(dev); const struct iphdr *old_iph; - struct vxlan_metadata _md; + struct vxlan_metadata _md = {}; struct vxlan_metadata *md = &_md; unsigned int pkt_len = skb->len; __be16 src_port = 0, dst_port; From 1274045b0eda1df5a5cdd3e63ed48cf013b3b4ea Mon Sep 17 00:00:00 2001 From: Venkat Rao Bagalkote Date: Thu, 3 Sep 2026 13:13:29 +0530 Subject: [PATCH 1339/2366] scsi: ibmvfc: Add Kconfig dependency to fix link failure when NVME_FC=m Building with CONFIG_SCSI_IBMVFC=y and CONFIG_NVME_FC=m results in a link failure: ibmvfc-nvme.o: undefined reference to `nvme_fc_register_localport' ibmvfc-nvme.o: undefined reference to `nvme_fc_register_remoteport' ibmvfc-nvme.o: undefined reference to `nvme_fc_unregister_localport' ibmvfc-nvme.o: undefined reference to `nvme_fc_unregister_remoteport' ibmvfc-core.o: undefined reference to `nvme_fc_rescan_remoteport' IS_ENABLED() evaluates to 1 for both =y and =m, so the nvme_fc_* call sites are kept in the object file. When SCSI_IBMVFC=y (built-in) but NVME_FC=m (loadable module), the linker cannot resolve these symbols at vmlinux link time. Add the same "depends on NVME_FC || NVME_FC=n" constraint already used by SCSI_LPFC to prevent this configuration. Reported-by: Pavithra Closes: https://lore.kernel.org/all/327877a29337aa526cc50ac88fbddb86@linux.ibm.com/ Signed-off-by: Venkat Rao Bagalkote Tested-by: Pavithra Acked-by: Tyrel Datwyler Link: https://patch.msgid.link/20260903074329.6705-1-venkat88@linux.ibm.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index 4a2af0f702e1..1eec66195cf4 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -753,6 +753,7 @@ config SCSI_IBMVFC tristate "IBM Virtual FC support" depends on PPC_PSERIES && SCSI depends on SCSI_FC_ATTRS + depends on NVME_FC || NVME_FC=n help This is the IBM POWER Virtual FC Client From 8aaeb56aff2a557a88f83ae866da2c91ad247e59 Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Tue, 8 Sep 2026 15:21:31 +0800 Subject: [PATCH 1340/2366] ppp_synctty: ensure a writeable skb header ppp_sync_txmunge() checks headroom before prepending the address and control bytes, but does not ensure that the skb header is writable. A received skb can reach this function through PPP channel bridging without passing through ppp_start_xmit(), which calls skb_cow_head(). For example, a PPPoE frame may share its buffer with a clone queued to an AF_PACKET socket. If it is bridged to a synchronous tty channel, the address/control bytes can overwrite data still visible to that socket. Use skb_cow_head() to ensure both sufficient headroom and a writable header. Fixes: 4cf476ced45d ("ppp: add PPPIOCBRIDGECHAN and PPPIOCUNBRIDGECHAN ioctls") Signed-off-by: Qingfang Deng Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260908072135.877364-1-qingfang.deng@linux.dev Signed-off-by: Jakub Kicinski --- drivers/net/ppp/ppp_synctty.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/drivers/net/ppp/ppp_synctty.c b/drivers/net/ppp/ppp_synctty.c index f87d43faeeab..ebd62a7ab54b 100644 --- a/drivers/net/ppp/ppp_synctty.c +++ b/drivers/net/ppp/ppp_synctty.c @@ -455,17 +455,9 @@ ppp_sync_txmunge(struct syncppp *ap, struct sk_buff *skb) /* prepend address/control fields if necessary */ if ((ap->flags & SC_COMP_AC) == 0 || islcp) { - if (skb_headroom(skb) < 2) { - struct sk_buff *npkt = dev_alloc_skb(skb->len + 2); - if (npkt == NULL) { - kfree_skb(skb); - return NULL; - } - skb_reserve(npkt,2); - skb_copy_from_linear_data(skb, - skb_put(npkt, skb->len), skb->len); - consume_skb(skb); - skb = npkt; + if (skb_cow_head(skb, 2)) { + kfree_skb(skb); + return NULL; } skb_push(skb,2); skb->data[0] = PPP_ALLSTATIONS; From 0cb1fd924126f1f581621a5e804df98a02be9dff Mon Sep 17 00:00:00 2001 From: Arun Easi Date: Thu, 3 Sep 2026 10:55:47 -0700 Subject: [PATCH 1341/2366] scsi: fnic: Fix missed link-up when critical IRQ targets offline CPU When CPU Hyper Threading is disabled, sibling CPUs remain present but are reported offline. Managed MSI-X IRQs can still receive affinity masks that include those offline CPUs. If a driver-critical vector is managed, it can be parked on an offline CPU and the driver may miss critical events such as link-up. Keep driver-critical vectors unmanaged so they can be migrated by the IRQ core when their target CPU is offlined. Since HWQ-0 is unmanaged now, in some queue combinations there can be no mappings to it in mq_map. So without the blk-mq fix mentioned below, system may crash during cpu offline/online tests. Fixes: 8a8449ca5e33 ("scsi: fnic: Modify ISRs to support multiqueue (MQ)") Cc: stable@vger.kernel.org Depends-on: commit 10845a105bbc ("blk-mq: skip CPU offline notify on unmapped hctx") Reviewed-by: Sesidhar Baddela Reviewed-by: Arulprabhu Ponnusamy Reviewed-by: Gian Carlo Boffa Reviewed-by: Karan Tilak Kumar Signed-off-by: Arun Easi Reviewed-by: Laurence Oberman Link: https://patch.msgid.link/20260903175547.57971-1-aeasi@cisco.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/fnic/fnic.h | 2 +- drivers/scsi/fnic/fnic_isr.c | 13 ++++++++--- drivers/scsi/fnic/fnic_main.c | 41 ++++++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/drivers/scsi/fnic/fnic.h b/drivers/scsi/fnic/fnic.h index c576a7f5083e..3ba1592940ca 100644 --- a/drivers/scsi/fnic/fnic.h +++ b/drivers/scsi/fnic/fnic.h @@ -31,7 +31,7 @@ #define DRV_NAME "fnic" #define DRV_DESCRIPTION "Cisco FCoE HBA Driver" -#define DRV_VERSION "1.9.0.0" +#define DRV_VERSION "1.9.0.1" #define PFX DRV_NAME ": " #define DFX DRV_NAME "%d: " diff --git a/drivers/scsi/fnic/fnic_isr.c b/drivers/scsi/fnic/fnic_isr.c index 02856745580f..43149d8312ec 100644 --- a/drivers/scsi/fnic/fnic_isr.c +++ b/drivers/scsi/fnic/fnic_isr.c @@ -245,7 +245,14 @@ int fnic_set_intr_mode_msix(struct fnic *fnic) unsigned int m = ARRAY_SIZE(fnic->wq); unsigned int o = ARRAY_SIZE(fnic->hw_copy_wq); unsigned int min_irqs = n + m + 1 + 1; /*rq, raw wq, wq, err*/ - + /* + * Make driver critical vectors unmanaged, or else it can get tied + * to an offline CPU. This can happen when hyper-threading is off. + */ + struct irq_affinity affd = { + .pre_vectors = n + m + 1, /* rq, raw wq, 1 ioq */ + .post_vectors = 1, /* err */ + }; /* * We need n RQs, m WQs, o Copy WQs, n+m+o CQs, and n+m+o+1 INTRs * (last INTR is used for WQ/RQ errors and notification area) @@ -263,8 +270,8 @@ int fnic_set_intr_mode_msix(struct fnic *fnic) int vec_count = 0; int vecs = fnic->rq_count + fnic->raw_wq_count + fnic->wq_copy_count + 1; - vec_count = pci_alloc_irq_vectors(fnic->pdev, min_irqs, vecs, - PCI_IRQ_MSIX | PCI_IRQ_AFFINITY); + vec_count = pci_alloc_irq_vectors_affinity(fnic->pdev, min_irqs, + vecs, PCI_IRQ_MSIX|PCI_IRQ_AFFINITY, &affd); FNIC_ISR_DBG(KERN_INFO, fnic, "allocated %d MSI-X vectors\n", vec_count); diff --git a/drivers/scsi/fnic/fnic_main.c b/drivers/scsi/fnic/fnic_main.c index 9b3025007075..f13c381a66d7 100644 --- a/drivers/scsi/fnic/fnic_main.c +++ b/drivers/scsi/fnic/fnic_main.c @@ -744,8 +744,19 @@ static int fnic_nvme_drv_init(struct fnic *fnic) return ret; } +static void fnic_mq_init_queue_map(struct fnic *fnic, + struct blk_mq_queue_map *qmap) +{ + unsigned int cpu; + + for_each_possible_cpu(cpu) + qmap->mq_map[cpu] = 0; +} + void fnic_mq_map_queues_cpus(struct Scsi_Host *host) { + const struct cpumask *mask; + unsigned int queue, cpu; struct fnic *fnic = *((struct fnic **) shost_priv(host)); struct pci_dev *l_pdev = fnic->pdev; int intr_mode = fnic->config.intr_mode; @@ -766,7 +777,35 @@ void fnic_mq_map_queues_cpus(struct Scsi_Host *host) return; } - blk_mq_map_hw_queues(qmap, &l_pdev->dev, FNIC_PCI_OFFSET); + fnic_mq_init_queue_map(fnic, qmap); + + /* + * Setup CPU to Queue mapping for all managed MSI-X IRQs. + * Q0 is driver critical and non-managed, hence start from Q1. + */ + for (queue = 1; queue < qmap->nr_queues; queue++) { + int irq_num = pci_irq_vector(fnic->pdev, + queue + FNIC_PCI_OFFSET); + + if (irq_num < 0) + continue; + + mask = pci_irq_get_affinity(fnic->pdev, + queue + FNIC_PCI_OFFSET); + if (!mask) { + shost_printk(KERN_ERR, host, + "failed to get irq_affinity map for queue:%d\n", irq_num); + continue; + } + FNIC_MAIN_DBG(KERN_INFO, fnic, + "got irq_affinity map for %d:\n", irq_num); + for_each_cpu(cpu, mask) { + qmap->mq_map[cpu] = qmap->queue_offset + queue; + FNIC_MAIN_DBG(KERN_INFO, fnic, + "[Q%d] cpu:%d <=> irq:%d\n", + queue, cpu, irq_num); + } + } } static int fnic_probe(struct pci_dev *pdev, const struct pci_device_id *ent) From 113998aa372f4869bf62cfc75c28a2849e8487be Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 4 Sep 2026 18:55:40 +0000 Subject: [PATCH 1342/2366] net: phylink: initialise link_state before a forced major config phylink_resolve() leaves link_state on the stack unpopulated on its disable and link-failed branches, which set only link_state.link. phylink_apply_manual_flow() then reads the struct's advertising on every mode but MLO_AN_FIXED, and has done so since long before force_major_config existed. force_major_config turns that into a write to the hardware. It is the only trigger for the major-config block that does not require mac_config, so phylink_major_config() programs the MAC for whatever the stack held, a zeroed interface is PHY_INTERFACE_MODE_NA, and the write-back stores it in pl->link_config.interface. phylink_replay_link_end() is the only in-tree setter, and sja1105_static_config_reload() calls it for every port that has a phylink instance, regardless of admin state. On a stopped port phylink_run_resolve() no-ops, so the flag outlives the call. The next resolve consumes it whatever branch it takes; an unpopulated branch is where that does damage. Found while developing a series that attaches a late PHY from a delayed work item and sets this flag there, so the PHY attached after its port was already up. The link stayed down until the port was cycled 29 minutes later. With this patch on the same board the same attach programs the MAC for 2500base-x rather than unknown, and the PHY's interrupt fires without a port bounce where it had stayed at zero throughout the failure. Fixes: 96969b132bf1 ("net: phylink: introduce helpers for replaying link callbacks") Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260904185540.2844261-1-f@lex.la Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 3ec3bb439109..a1458da8111b 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -1630,8 +1630,10 @@ static void phylink_resolve(struct work_struct *w) if (pl->phylink_disable_state) { pl->link_failed = false; + link_state = pl->link_config; link_state.link = false; } else if (pl->link_failed) { + link_state = pl->link_config; link_state.link = false; retrigger = true; } else if (pl->act_link_an_mode == MLO_AN_FIXED) { From 0338c68e22abd2ee509ec2e32508a50896618c32 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 4 Sep 2026 12:32:55 +0200 Subject: [PATCH 1343/2366] net: stmmac: initialize ptp_lock at probe time priv->ptp_lock is only initialized in stmmac_ptp_register(), which runs during __stmmac_open(). However, the lock is also used while the interface is down and has never been opened: tc_taprio_configure() invokes the PTP gettime64() callback to compute the EST base time when offloading a TAPRIO schedule, and stmmac_get_time() takes priv->ptp_lock. Using an uninitialized rwlock is undefined behaviour. Move the rwlock_init() to __stmmac_dvr_probe(), together with the other private locks, so that ptp_lock is always valid regardless of the interface state. Fixes: b60189e0392f ("net: stmmac: Integrate EST with TAPRIO scheduler API") Signed-off-by: Lorenzo Bianconi Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260904-stmmac-fix-ptp-clock-init-v1-1-df70eb1eb04d@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 1 + drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 24656b35350b..5fe7e95fdd34 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -8025,6 +8025,7 @@ static int __stmmac_dvr_probe(struct device *device, stmmac_napi_add(ndev); mutex_init(&priv->lock); + rwlock_init(&priv->ptp_lock); stmmac_fpe_init(priv); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c index 960249960004..3bfcc9760dce 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c @@ -365,7 +365,6 @@ void stmmac_ptp_register(struct stmmac_priv *priv) if (priv->plat->crosststamp) priv->ptp_clock_ops.getcrosststamp = stmmac_getcrosststamp; - rwlock_init(&priv->ptp_lock); mutex_init(&priv->aux_ts_lock); priv->ptp_clock = ptp_clock_register(&priv->ptp_clock_ops, From 3d676e458fe0c566f5a62753dc696b6a862fc412 Mon Sep 17 00:00:00 2001 From: Alberto Carboneri Date: Fri, 4 Sep 2026 13:54:37 +0000 Subject: [PATCH 1344/2366] scsi: core: Validate MODE SENSE lengths in scsi_cdl_enable() scsi_cdl_enable() uses length fields returned by MODE SENSE to locate the ATA feature mode page in a 64-byte stack buffer. A target can report a total length shorter than its mode header and block descriptors. The unsigned subtraction used for the MODE SELECT length can wrap, and the separately computed buf_data can point beyond buf. During automatic scan, enable is false, so the read-modify-write of buf_data[4] can clear the low two bits of a target-selected out-of-bounds stack byte. scsi_mode_select() can then copy up to 64 bytes from outside the buffer into the outgoing MODE SELECT payload, disclosing stack contents to the target. This is reachable while scanning a USB storage device that identifies as an ATA device and advertises CDL support. No filesystem mount or userspace access to the block device is required. On upstream commit cee9395acd80 ("Linux 7.3-rc1"), a build-specific, one-vCPU QEMU/Raw Gadget proof using QEMU-only multi-UDC allocator sampling executed a fixed proof command inside the guest and created a UID-0-owned marker during automatic enumeration, with KASLR and NX enabled. The issue was independently found during security research at Drivesec S.r.l. Cap the available length to the buffer size. Validate and consume the mode header and block descriptor lengths before using the page, and require the five bytes needed to access the CDL field. Fixes: 1b22cfb14142 ("scsi: core: Allow enabling and disabling command duration limits") Reported-by: Sashiko AI Review Closes: https://lore.kernel.org/linux-scsi/20260717192313.93D791F000E9@smtp.kernel.org/ Link: https://lore.kernel.org/linux-scsi/20260717222931.AC4EE1F000E9@smtp.kernel.org/ Link: https://lore.kernel.org/linux-scsi/df13ec87ac9b28e3b0a2d9eb26477e276ff0278a.camel@HansenPartnership.com/ Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Pimen Flavian Dei (Drivesec S.r.l.) Signed-off-by: Pimen Flavian Dei (Drivesec S.r.l.) Signed-off-by: Alberto Carboneri (Drivesec S.r.l.) Link: https://lore.kernel.org/linux-scsi/20260717192313.93D791F000E9@smtp.kernel.org/ Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/20260904135410.360314-1-acarboneri@drivesec.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/scsi.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c index 76cdad063f7b..f285521d9de6 100644 --- a/drivers/scsi/scsi.c +++ b/drivers/scsi/scsi.c @@ -727,6 +727,7 @@ int scsi_cdl_enable(struct scsi_device *sdev, bool enable) struct scsi_mode_data data; struct scsi_sense_hdr sshdr; char *buf_data; + size_t avail, offset; int len; ret = scsi_mode_sense(sdev, 0x08, 0x0a, 0xf2, buf, sizeof(buf), @@ -735,11 +736,24 @@ int scsi_cdl_enable(struct scsi_device *sdev, bool enable) return -EINVAL; /* Enable or disable CDL using the ATA feature page */ - len = min_t(size_t, sizeof(buf), - data.length - data.header_length - - data.block_descriptor_length); - buf_data = buf + data.header_length + - data.block_descriptor_length; + avail = min_t(size_t, data.length, sizeof(buf)); + if (data.header_length > avail) + return -EINVAL; + + offset = data.header_length; + avail -= data.header_length; + + if (data.block_descriptor_length > avail) + return -EINVAL; + + offset += data.block_descriptor_length; + avail -= data.block_descriptor_length; + + if (avail < 5) + return -EINVAL; + + buf_data = buf + offset; + len = avail; /* * If we want to enable CDL and CDL is already enabled on the From 7f26a5e8040b4957ef4dbdfcde6cc7ba2db53937 Mon Sep 17 00:00:00 2001 From: Carolina Jubran Date: Sun, 6 Sep 2026 12:07:00 +0300 Subject: [PATCH 1345/2366] net/mlx5e: Move representor vnic reporter to eswitch devlink port The representor vnic devlink health reporter is created and destroyed along the representor netdev (un)load path, which is not serialized by the devlink instance lock. Destroying the reporter from there triggers a devl_assert_locked() splat on driver unbind: WARNING: net/devlink/core.c:259 at devl_assert_locked+0x54/0x70, CPU#2: bash/3758 Modules linked in: mlx5_vdpa vringh vdpa mlx5_ib mlx5_fwctl mlx5_core ... CPU: 2 UID: 0 PID: 3758 Comm: bash Tainted: G W 6.19.0+ #1 PREEMPT Tainted: [W]=WARN Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), ... RIP: 0010:devl_assert_locked+0x54/0x70 Call Trace: devl_health_reporter_destroy+0x3a/0x1b0 mlx5e_vport_rep_unload+0x12d/0x2b0 [mlx5_core] mlx5_eswitch_unregister_vport_reps+0x1b8/0x220 [mlx5_core] ? __esw_offloads_unload_rep+0x190/0x190 [mlx5_core] ? kernfs_remove_by_name_ns+0xc3/0xf0 device_release_driver_internal+0x3b2/0x560 unbind_store+0xce/0xf0 Move the reporter's lifecycle to the eswitch devlink port (un)register paths, which are already serialized by the devlink instance lock, and store the handle on mlx5_devlink_port. Use the port's mlx5_vport as the reporter priv since the diagnose callback only needs a device handle and a vport number, and mlx5_vport carries both and is initialized before any representor driver probes. Fixes: cf14af140a5a ("net/mlx5e: Add vnic devlink health reporter to representors") Signed-off-by: Carolina Jubran Reviewed-by: Cosmin Ratiu Signed-off-by: Tariq Toukan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260906090700.3761260-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/en_rep.c | 52 +------------------ .../net/ethernet/mellanox/mlx5/core/en_rep.h | 1 - .../mellanox/mlx5/core/esw/devlink_port.c | 37 +++++++++++++ .../net/ethernet/mellanox/mlx5/core/eswitch.h | 1 + 4 files changed, 39 insertions(+), 52 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c index ce765692fd19..88a170e40bd9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c @@ -56,7 +56,6 @@ #include "lib/vxlan.h" #define CREATE_TRACE_POINTS #include "diag/en_rep_tracepoint.h" -#include "diag/reporter_vnic.h" #include "en_accel/ipsec.h" #include "en/tc/int_port.h" #include "en/ptp.h" @@ -1439,51 +1438,6 @@ static unsigned int mlx5e_ul_rep_stats_grps_num(struct mlx5e_priv *priv) return ARRAY_SIZE(mlx5e_ul_rep_stats_grps); } -static int -mlx5e_rep_vnic_reporter_diagnose(struct devlink_health_reporter *reporter, - struct devlink_fmsg *fmsg, - struct netlink_ext_ack *extack) -{ - struct mlx5e_rep_priv *rpriv = devlink_health_reporter_priv(reporter); - struct mlx5_eswitch_rep *rep = rpriv->rep; - - mlx5_reporter_vnic_diagnose_counters(rep->esw->dev, fmsg, rep->vport, - true); - return 0; -} - -static const struct devlink_health_reporter_ops mlx5_rep_vnic_reporter_ops = { - .name = "vnic", - .diagnose = mlx5e_rep_vnic_reporter_diagnose, -}; - -static void mlx5e_rep_vnic_reporter_create(struct mlx5e_priv *priv, - struct devlink_port *dl_port) -{ - struct mlx5e_rep_priv *rpriv = priv->ppriv; - struct devlink_health_reporter *reporter; - - reporter = devl_port_health_reporter_create(dl_port, - &mlx5_rep_vnic_reporter_ops, - rpriv); - if (IS_ERR(reporter)) { - mlx5_core_err(priv->mdev, - "Failed to create representor vnic reporter, err = %pe\n", - reporter); - return; - } - - rpriv->rep_vnic_reporter = reporter; -} - -static void mlx5e_rep_vnic_reporter_destroy(struct mlx5e_priv *priv) -{ - struct mlx5e_rep_priv *rpriv = priv->ppriv; - - if (!IS_ERR_OR_NULL(rpriv->rep_vnic_reporter)) - devl_health_reporter_destroy(rpriv->rep_vnic_reporter); -} - static const struct mlx5e_profile mlx5e_rep_profile = { .init = mlx5e_init_rep, .cleanup = mlx5e_cleanup_rep, @@ -1607,10 +1561,8 @@ mlx5e_vport_vf_rep_load(struct mlx5_core_dev *dev, struct mlx5_eswitch_rep *rep) dl_port = mlx5_esw_offloads_devlink_port(dev->priv.eswitch, rpriv->rep->vport); - if (!IS_ERR(dl_port)) { + if (!IS_ERR(dl_port)) SET_NETDEV_DEVLINK_PORT(netdev, dl_port); - mlx5e_rep_vnic_reporter_create(priv, dl_port); - } err = register_netdev(netdev); if (err) { @@ -1623,7 +1575,6 @@ mlx5e_vport_vf_rep_load(struct mlx5_core_dev *dev, struct mlx5_eswitch_rep *rep) return 0; err_detach_netdev: - mlx5e_rep_vnic_reporter_destroy(priv); mlx5e_detach_netdev(netdev_priv(netdev)); err_cleanup_profile: priv->profile->cleanup(priv); @@ -1681,7 +1632,6 @@ mlx5e_vport_rep_unload(struct mlx5_eswitch_rep *rep) } unregister_netdev(netdev); - mlx5e_rep_vnic_reporter_destroy(priv); mlx5e_detach_netdev(priv); priv->profile->cleanup(priv); mlx5e_destroy_netdev(netdev); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.h b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.h index 70640fa1ad7b..bcd7b4e814d0 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.h @@ -118,7 +118,6 @@ struct mlx5e_rep_priv { struct rtnl_link_stats64 prev_vf_vport_stats; struct mlx5_flow_handle *send_to_vport_meta_rule; struct rhashtable tc_ht; - struct devlink_health_reporter *rep_vnic_reporter; }; static inline diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c index 36b00a856bc2..fdc960ea5331 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c @@ -4,6 +4,26 @@ #include #include "eswitch.h" #include "devlink.h" +#include "diag/reporter_vnic.h" + +static int +mlx5_esw_rep_vnic_reporter_diagnose(struct devlink_health_reporter *reporter, + struct devlink_fmsg *fmsg, + struct netlink_ext_ack *extack) +{ + struct mlx5_vport *vport = devlink_health_reporter_priv(reporter); + + mlx5_reporter_vnic_diagnose_counters(vport->dev, fmsg, vport->vport, + true); + + return 0; +} + +static const +struct devlink_health_reporter_ops mlx5_esw_rep_vnic_reporter_ops = { + .name = "vnic", + .diagnose = mlx5_esw_rep_vnic_reporter_diagnose, +}; static void mlx5_esw_get_port_parent_id(struct mlx5_core_dev *dev, struct netdev_phys_item_id *ppid) @@ -220,6 +240,7 @@ static void mlx5_esw_devlink_port_res_unregister(struct devlink_port *dl_port) int mlx5_esw_offloads_devlink_port_register(struct mlx5_eswitch *esw, struct mlx5_vport *vport) { + struct devlink_health_reporter *reporter; struct mlx5_core_dev *dev = esw->dev; const struct devlink_port_ops *ops; struct mlx5_devlink_port *dl_port; @@ -255,6 +276,16 @@ int mlx5_esw_offloads_devlink_port_register(struct mlx5_eswitch *esw, struct mlx mlx5_core_dbg(dev, "Failed to register port resources: %d\n", err); + reporter = devl_port_health_reporter_create( + &dl_port->dl_port, &mlx5_esw_rep_vnic_reporter_ops, + vport); + if (IS_ERR(reporter)) + mlx5_core_err(dev, + "Failed to create vnic health reporter for vport %d: %pe\n", + vport_num, reporter); + else + dl_port->vnic_reporter = reporter; + return 0; rate_err: @@ -269,6 +300,12 @@ void mlx5_esw_offloads_devlink_port_unregister(struct mlx5_vport *vport) if (!vport->dl_port) return; dl_port = vport->dl_port; + + if (dl_port->vnic_reporter) { + devl_health_reporter_destroy(dl_port->vnic_reporter); + dl_port->vnic_reporter = NULL; + } + mlx5_esw_devlink_port_res_unregister(&dl_port->dl_port); devl_rate_leaf_destroy(&dl_port->dl_port); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h index c655f6e8da1c..8b1f93b13ea9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h @@ -189,6 +189,7 @@ struct mlx5_vport; struct mlx5_devlink_port { struct devlink_port dl_port; struct mlx5_vport *vport; + struct devlink_health_reporter *vnic_reporter; }; static inline void mlx5_devlink_port_init(struct mlx5_devlink_port *dl_port, From 11ae2e1dc58304a48816fc8ca4afa8f2ef9d1bdf Mon Sep 17 00:00:00 2001 From: Aboorva Devarajan Date: Fri, 4 Sep 2026 08:28:30 +0530 Subject: [PATCH 1346/2366] powerpc/entry: Fix double accounting of user time on interrupt entry Since the switch to generic entry, an interrupt from user mode accounts user time twice: once in arch_interrupt_enter_prepare() and again in arch_enter_from_user_mode(), which irqentry_enter() invokes for the same interrupt: arch_interrupt_enter_prepare() account_cpu_user_entry() /* first */ irqentry_enter() arch_enter_from_user_mode() account_cpu_user_entry() /* second */ The second call charges the same interval again, because account_cpu_user_entry() accumulates the time spent in user mode since the last return to user space. The two calls come from the GENERIC_ENTRY preparation series, where each step was a no-op on its own. Commit 09a9d3a8499d ("powerpc: introduce arch_enter_from_user_mode") added the hook with the user-time accounting in it, but nothing called it yet. Commit 893082ac769b ("powerpc: Prepare for IRQ entry exit") copied interrupt_enter_prepare() verbatim into entry-common.h as arch_interrupt_enter_prepare(); that copy was equally unused, as handlers still called interrupt_enter_prepare(). Commit bee25f97ad24 ("powerpc: Enable GENERIC_ENTRY feature") made both live. On the syscall side it did the full conversion: system_call_exception() now accounts once through the hook via syscall_enter_from_user_mode(), rather than calling account_cpu_user_entry() directly. On the interrupt side it switched the handler macros to arch_interrupt_enter_prepare() followed by irqentry_enter(), which also runs the hook, but the accounting in arch_interrupt_enter_prepare() was not removed to match. The double accounting starts with that commit. With CONFIG_VIRT_CPU_ACCOUNTING_NATIVE=y this roughly doubles the reported user time of any workload that takes interrupts. The other accounting modes compile account_cpu_user_entry() to an empty stub, so they are not affected. Remove the accounting from arch_interrupt_enter_prepare() and rely on arch_enter_from_user_mode(), which already runs for both syscalls and interrupts. The duplicate account_stolen_time() call is removed the same way. On a pseries LPAR a busy loop reports 6s user time in 3s elapsed (~210% CPU) before the fix, and 3s (~105% CPU) after it: $ python3 -c 'while True: pass' & $ sleep 3; ps -p $! -o etime,time,pcpu ELAPSED TIME %CPU Before 00:03 00:00:06 210 After 00:03 00:00:03 105 A 50% load reports ~70% usr / 30% idle before the fix, and ~49% usr / 51% idle after it: $ taskset -c 6 stress-ng --cpu 1 --cpu-load 50 & $ mpstat -P 6 1 CPU %usr %idle Before 6 69.74 30.26 After 6 48.51 50.50 Fixes: bee25f97ad24 ("powerpc: Enable GENERIC_ENTRY feature") Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Signed-off-by: Aboorva Devarajan Tested-by: Venkat Rao Bagalkote Reviewed-by: Amit Machhiwal Reviewed-by: Ritesh Harjani (IBM) Reviewed-by: Christophe Leroy (CS GROUP) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260904025831.3439809-1-aboorvad@linux.ibm.com --- arch/powerpc/include/asm/entry-common.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/powerpc/include/asm/entry-common.h b/arch/powerpc/include/asm/entry-common.h index 80b07750b531..8e91489fdf2b 100644 --- a/arch/powerpc/include/asm/entry-common.h +++ b/arch/powerpc/include/asm/entry-common.h @@ -222,8 +222,6 @@ static inline void arch_interrupt_enter_prepare(struct pt_regs *regs) if (user_mode(regs)) { kuap_lock(); - account_cpu_user_entry(); - account_stolen_time(); } else { kuap_save_and_lock(regs); /* From ef17515a8ef88c246c342a6c532aa10f9ecdcfaf Mon Sep 17 00:00:00 2001 From: Athira Rajeev Date: Thu, 3 Sep 2026 13:10:36 +0530 Subject: [PATCH 1347/2366] selftests/powerpc/pmu/ebb: fix lost_exception_test hang with sched yield change commit 79104becf42b ("sched/fair: Forfeit vruntime on yield") changed yield_task_fair() to only bump the deadline when the entity is eligible (vruntime <= avg_vruntime). When the entity is ineligible the yield becomes a complete no-op from scheduling perspective. lost_exception_test calls sched_yield() 100,000 times per iteration to race the EBB exception delivery with a context switch to the eat_cpu companion process. After enough iterations the test process's vruntime races ahead of avg_vruntime (each eligible yield bumps vruntime to deadline, then advances deadline by one slice). Once ineligible, yield_task_fair() does nothing: so the scheduler won't pick the eat_cpu child. No context switch occurs, the PMAO race is never triggered, and ebb_count stays at 0 forever causing the test to hang until timeout. Fix by replacing sched_yield() with nanosleep(0, 1ns). nanosleep() goes through hrtimer_nanosleep() -> do_nanosleep(), which puts the task into TASK_INTERRUPTIBLE and removes it from the run queue entirely. This guarantees the scheduler picks the eat_cpu child, restoring the context-switch guarantee the test requires. The 1ns duration is enough to engage the hrtimer path while keeping the sleep effectively instantaneous; the same race window between PMU overflow and context switch is preserved. Reported-by: Venkat Rao Bagalkote Signed-off-by: Athira Rajeev Tested-by: Venkat Rao Bagalkote Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260903074036.63309-1-atrajeev@linux.ibm.com --- .../selftests/powerpc/pmu/ebb/lost_exception_test.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c b/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c index ba2681a12cc7..9be5945f3b1f 100644 --- a/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c +++ b/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "ebb.h" @@ -22,6 +23,7 @@ static int test_body(void) { int i, orig_period, max_period; struct event event; + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1 }; SKIP_IF(!ebb_is_supported()); @@ -57,10 +59,15 @@ static int test_body(void) * kernel to decide our timeslice is up and context switch to * the other thread. When we come back our EBB will have been * lost and we'll spin in this while loop forever. + * + * Use nanosleep(0) instead of sched_yield() to guarantee a + * context switch to the eat_cpu child regardless of the + * eligibility state. sched_yield() via yield_task_fair() may + * become a no-op when the task is ineligible (vruntime ahead + * of avg_vruntime), preventing the required context switch. */ - for (i = 0; i < 100000; i++) - sched_yield(); + nanosleep(&ts, NULL); /* Change the sample period slightly to try and hit the race */ if (sample_period >= (orig_period + 200)) From 57a78ad2305f299bc3f933ed36b3d402df04784a Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 9 Sep 2026 09:06:31 +0100 Subject: [PATCH 1348/2366] block: Fix start and length check added to iov_iter_extract_bvecs() Commit 14b007e17881 added an address check using iter_iov_addr() and a length check using iter_iov_len() to iov_iter_extract_bvecs(), but these cannot be used so and are unsafe in this circumstance as the functions have hardwired assumptions about the iterator type. They should only be used with ITER_UBUF or ITER_IOVEC-type iterators; they shouldn't be used with ITER_BVEC, ITER_KVEC, ITER_FOLIOQ, ITER_XARRAY or ITER_DISCARD iterators. This proves to be a problem for cachefiles as an iterator of type ITER_FOLIOQ is passed and iter_iov_addr() and iter_iov_len() both malfunction because iter->__iov in iter_iov() is not pointing to an iovec array. Fix this by using iov_iter_alignment() instead. Fixes: 14b007e17881 ("block: validate user space vectors during extraction") Signed-off-by: David Howells Link: https://patch.msgid.link/1667275.1788941191@warthog.procyon.org.uk Reviewed-by: Keith Busch Reviewed-by: Christoph Hellwig cc: Hannes Reinecke cc: Christoph Hellwig cc: Jens Axboe cc: Alexander Viro cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-block@vger.kernel.org cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- lib/iov_iter.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/iov_iter.c b/lib/iov_iter.c index 6665372ecf71..2072c04e99d0 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -1921,15 +1921,29 @@ ssize_t iov_iter_extract_bvecs(struct iov_iter *iter, struct bio_vec *bv, unsigned short max_vecs, unsigned mem_align_mask, iov_iter_extraction_t extraction_flags) { - unsigned long start = (unsigned long)iter_iov_addr(iter); unsigned short entries_left = max_vecs - *nr_vecs; unsigned short nr_pages, i = 0; size_t left, offset, len; struct page **pages; ssize_t size; - if ((start | iter_iov_len(iter)) & mem_align_mask) + /* + * DMA engines typically have both memory address and length alignment + * requirements, so check these against the alignment mask. For UBUF, + * IOVEC and KVEC, only the current segment will be extracted from; for + * everything else we might extract from multiple segments, so we need + * to check those too. + */ + if (likely(iter_is_ubuf(iter) || + iter_is_iovec(iter) || + iov_iter_is_kvec(iter))) { + unsigned long start = (unsigned long)iter_iov_addr(iter); + + if ((start | iter_iov_len(iter)) & mem_align_mask) + return -EINVAL; + } else if (iov_iter_alignment(iter) & mem_align_mask) { return -EINVAL; + } /* * Move page array up in the allocated memory for the bio vecs as far as From d59ac79915daa567c69aa93d458d41844676e92a Mon Sep 17 00:00:00 2001 From: Disha Goel Date: Fri, 3 Jul 2026 20:37:42 +0530 Subject: [PATCH 1349/2366] selftests/filesystems: fix missing and stale TARGETS entries filesystems/eventfd, filesystems/open_tree_ns and filesystems/xattr were never added to TARGETS when introduced. filesystems/openat2 was moved from selftests/openat2/ but the TARGETS entry was never updated, leaving a stale entry pointing at a directory that no longer exists. Fix this by adding the four missing subdirectories to TARGETS and removing the stale openat2 entry. Link: https://lore.kernel.org/20260703150742.58991-1-disgoel@linux.ibm.com Fixes: 7c37857fc23a ("selftests: add eventfd selftests") Fixes: b8f7622aa6e3 ("selftests/open_tree: add OPEN_TREE_NAMESPACE tests") Fixes: 7e28fef5d4db ("selftests/xattr: path-based AF_UNIX socket xattr tests") Fixes: fe087927046c ("selftests: move openat2 tests to selftests/filesystems/") Signed-off-by: Disha Goel Reviewed-by: Christian Brauner (Amutable) Cc: "Darrick J. Wong" Cc: Jan Kara Cc: Jeff Layton Cc: Shuah Khan Cc: Wen Yang Signed-off-by: Andrew Morton Link: https://patch.msgid.link/20260904183659.B81CD1F00A3D@smtp.kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 2d960626750e..330d061f6366 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -35,8 +35,11 @@ TARGETS += fchmodat2 TARGETS += filesystems TARGETS += filesystems/binderfs TARGETS += filesystems/epoll +TARGETS += filesystems/eventfd TARGETS += filesystems/failfs TARGETS += filesystems/fat +TARGETS += filesystems/openat2 +TARGETS += filesystems/open_tree_ns TARGETS += filesystems/overlayfs TARGETS += filesystems/statmount TARGETS += filesystems/mount-notify @@ -47,6 +50,7 @@ TARGETS += filesystems/empty_mntns TARGETS += filesystems/fsmount_ns TARGETS += filesystems/fscontext_ns TARGETS += filesystems/mntns_cleanup +TARGETS += filesystems/xattr TARGETS += firmware TARGETS += fpu TARGETS += ftrace @@ -103,7 +107,6 @@ TARGETS += prctl TARGETS += proc TARGETS += pstore TARGETS += ptrace -TARGETS += openat2 TARGETS += rdma TARGETS += resctrl TARGETS += riscv From 1abd643f3783ea8f8e273c18697ff0413aa92dc7 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 9 Sep 2026 11:03:18 +0200 Subject: [PATCH 1350/2366] fs/ntfs3: use d_instantiate_new() in ntfs_create_inode() and murder syzbot's "WARNING in do_new_mount" saga ntfs_create_inode() creates a new inode via ntfs_new_inode(). It hashes it with insert_inode_locked() and so it's marked as I_NEW until unlock_new_inode(). ntfs 3 calls d_instantiate() in between though... Since the dentry was already hashed by the lookup before the create any path walk finds it without touching the parent's i_rwsem and so can lock the inode. If the inode is a directory unlock_new_inode() calls lockdep_annotate_inode_mutex_key() and marks i_rwsem with the i_mutex_dir_key class. That resets the count and the owner of a lock somebody else may already hold by now... syzbot has been spamming us with the same godforsaken bug "WARNING in do_new_mount" since 2023. I can't take it anymore so I went looking. Afaict, syzbot's executor chdirs into a freshly mounted ntfs3 image, creates a directory and then mounts some pseudofs on it. Everytime the mkdir() takes longer than syzbot waits mount() runs concurrently: mkdir("./sys") mount(NULL, "./sys", "sysfs") ntfs_create_inode() d_instantiate() user_path_at() finds the dentry do_lock_mount() inode_lock(inode) namespace_lock() unlock_new_inode() lockdep_annotate_inode_mutex_key() init_rwsem(&inode->i_rwsem) unlock_mount() inode_unlock(inode) The mount side then releases a lock that according to the rwsem nobody holds: DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && ...): count = 0x0, magic = 0xffff888043a854e8, owner = 0x0, curr 0xffff888000244880, list empty WARNING: CPU: 0 PID: 5346 at kernel/locking/rwsem.c:1368 __up_write Call Trace: inode_unlock include/linux/fs.h:877 [inline] unlock_mount fs/namespace.c:2892 [inline] do_new_mount_fc fs/namespace.c:3828 [inline] do_new_mount+0x777/0xa40 fs/namespace.c:3887 On PREEMPT_RT the same thing shows up as DEBUG_LOCKS_WARN_ON(rt_mutex_owner(lock) != current) WARNING: kernel/locking/rtmutex_common.h:193 at rt_mutex_slowunlock The up_write() underflows the reset count. A following inode_lock() on that directory then never returns. A path walk into the new directory racing with the mkdir() corrupts the lock the same way via inode_lock_shared() in lookup_slow(). Switch to d_instantiate_new() and drop the trailing unlock_new_inode(). All error paths bail out before that point with I_NEW still set and keep using discard_new_inode(). May we never see this fscking bug report again. Link: https://patch.msgid.link/20260909-work-ntfs3-d_instantiate_new-v1-1-2db697162ce8@kernel.org Fixes: 82cae269cfa9 ("fs/ntfs3: Add initialization of super block") Reviewed-by: Jan Kara Cc: stable@vger.kernel.org # v5.15+ Reported-by: syzbot+2a13ad6914e6fcec716c@syzkaller.appspotmail.com Closes: https://lore.kernel.org/6a9beced.a5e650b3.26d8a.000b.GAE@google.com Signed-off-by: Christian Brauner (Amutable) --- fs/ntfs3/inode.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 56b4f6469a28..4ac26c80bd34 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -1866,10 +1866,10 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, goto out6; /* - * Call 'd_instantiate' after inode->i_op is set + * Call 'd_instantiate_new' after inode->i_op is set * but before finish_open. */ - d_instantiate(dentry, inode); + d_instantiate_new(dentry, inode); /* Set original time. inode times (i_ctime) may be changed in ntfs_init_acl. */ inode_set_atime_to_ts(inode, ni->i_crtime); @@ -1917,9 +1917,6 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, if (!fnd) ni_unlock(dir_ni); - if (!err) - unlock_new_inode(inode); - return err; } From ed28b16eab705071d28edaace47189c2eb3aa108 Mon Sep 17 00:00:00 2001 From: Thibault Ferrante Date: Mon, 7 Sep 2026 23:54:20 +0200 Subject: [PATCH 1351/2366] selftests/powerpc/tm: Fix tcheck() reading uninitialised CR value tcheck() is used to check the current transaction state (active, suspended, doomed) via the "tcheck" instruction, which writes its result into CR field 0. The inline asm declared a GPR output operand for this result but never actually moved the CR into it. Every caller (tcheck_doomed(), tcheck_active(), tcheck_suspended(), tcheck_transactional()) has effectively been testing bits of an unrelated, arbitrary register value since this helper was introduced. The "& 4" mask discards the TDOOMED and TS_lsb (suspended) bits before they ever reach the callers, so tcheck_doomed() and tcheck_suspended() can never return true, and tcheck_transactional() degrades to being equivalent to tcheck_active(). Fix tcheck() to actually move CR into the output register with mfcr, and widen the mask from "& 4" to "& 0xf" so the full CR0 nibble (TDOOMED | TS_msb | TS_lsb | reserved) is preserved for the callers. This bug has been present since tcheck() was introduced. Link: https://bugs.launchpad.net/bugs/2107442 Fixes: 8e03bd4e70b6 ("selftests/powerpc: Add TM tcheck helpers in C") Signed-off-by: Thibault Ferrante Reported-by: Venkat Rao Bagalkote Tested-by: Venkat Rao Bagalkote Closes: https://lore.kernel.org/all/364996ce-aba2-4213-8d20-7dd481b43fe6@linux.ibm.com/ Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260907215420.1258678-1-thibault.ferrante@canonical.com --- tools/testing/selftests/powerpc/tm/tm.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/powerpc/tm/tm.h b/tools/testing/selftests/powerpc/tm/tm.h index c03c6e778876..6024ce4ba6ff 100644 --- a/tools/testing/selftests/powerpc/tm/tm.h +++ b/tools/testing/selftests/powerpc/tm/tm.h @@ -105,8 +105,12 @@ static inline bool failure_is_nesting(void) static inline int tcheck(void) { long cr; - asm volatile ("tcheck 0" : "=r"(cr) : : "cr0"); - return (cr >> 28) & 4; + asm volatile("tcheck 0;" + "mfcr %0;" + : "=r"(cr) + : + : "cr0"); + return (cr >> 28) & 0xf; } static inline bool tcheck_doomed(void) From 10557fe7fc9e09d273f8575274be2bbbe255dbf0 Mon Sep 17 00:00:00 2001 From: Michail Tatas Date: Tue, 4 Aug 2026 22:55:23 +0300 Subject: [PATCH 1352/2366] powerpc/pseries/htmdump: Fix leak in htmdump_init_debugfs If any allocation fails during init all previous allocations are leaked and the debugfs directory is left. Fix by freeing the allocations that have already happened and also remove the directory that has been created. Signed-off-by: Michail Tatas Reviewed-by: Athira Rajeev Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/anJDq-JckR6j-6EJ@michalis-linux --- arch/powerpc/platforms/pseries/htmdump.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/arch/powerpc/platforms/pseries/htmdump.c b/arch/powerpc/platforms/pseries/htmdump.c index 489a80e87082..f33941b80ada 100644 --- a/arch/powerpc/platforms/pseries/htmdump.c +++ b/arch/powerpc/platforms/pseries/htmdump.c @@ -527,28 +527,28 @@ static int htmdump_init_debugfs(void) htm_status_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!htm_status_buf) { pr_err("Failed to allocate htmstatus buf\n"); - return -ENOMEM; + goto htm_status_buf_err; } /* Debugfs interface file to present System Processor Configuration */ htm_info_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!htm_info_buf) { pr_err("Failed to allocate htm info buf\n"); - return -ENOMEM; + goto htm_info_buf_err; } /* Debugfs interface file to present HTM capabilities */ htm_caps_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!htm_caps_buf) { pr_err("Failed to allocate htm caps buf\n"); - return -ENOMEM; + goto htm_caps_buf_err; } /* Memory to present HTM system memory configuration */ htm_mem_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!htm_mem_buf) { pr_err("Failed to allocate htm mem buf\n"); - return -ENOMEM; + goto htm_mem_buf_err; } debugfs_create_file("htmstatus", 0400, htmdump_debugfs_dir, htm_status_buf, &htmstatus_fops); @@ -557,6 +557,17 @@ static int htmdump_init_debugfs(void) debugfs_create_file("htmsystem_mem", 0400, htmdump_debugfs_dir, htm_mem_buf, &htmsystem_mem_fops); return 0; + +htm_mem_buf_err: + kfree(htm_caps_buf); +htm_caps_buf_err: + kfree(htm_info_buf); +htm_info_buf_err: + kfree(htm_status_buf); +htm_status_buf_err: + debugfs_remove_recursive(htmdump_debugfs_dir); + kfree(htm_buf); + return -ENOMEM; } static int __init htmdump_init(void) From 1144454ea22290d7c6998a2af6239e5995476afc Mon Sep 17 00:00:00 2001 From: leixiang Date: Thu, 9 Jul 2026 13:57:52 +0800 Subject: [PATCH 1353/2366] KVM: PPC: Book3S HV: Set irqfd->producer only on success Set irqfd->producer only after kvmppc_set_passthru_irq() succeeds to avoid leaving a dangling pointer on failure. The bypass manager does not register a failed producer, so the pointer is never cleared. Fixes: c57875f5f9be ("KVM: PPC: Book3S HV: Enable IRQ bypass") Suggested-by: Sean Christopherson Cc: stable@vger.kernel.org Signed-off-by: leixiang Reviewed-by: Amit Machhiwal Reviewed-by: Vaibhav Jain Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260709055755.31297-1-leixiang@kylinos.cn --- arch/powerpc/kvm/book3s_hv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/powerpc/kvm/book3s_hv.c b/arch/powerpc/kvm/book3s_hv.c index 0409ac9e7b31..dbac3573b2c8 100644 --- a/arch/powerpc/kvm/book3s_hv.c +++ b/arch/powerpc/kvm/book3s_hv.c @@ -6140,12 +6140,12 @@ static int kvmppc_irq_bypass_add_producer_hv(struct irq_bypass_consumer *cons, struct kvm_kernel_irqfd *irqfd = container_of(cons, struct kvm_kernel_irqfd, consumer); - irqfd->producer = prod; - ret = kvmppc_set_passthru_irq(irqfd->kvm, prod->irq, irqfd->gsi); if (ret) pr_info("kvmppc_set_passthru_irq (irq %d, gsi %d) fails: %d\n", prod->irq, irqfd->gsi, ret); + else + irqfd->producer = prod; return ret; } From e58b9d90973e09f1908d2cd00ca2336a8232b9f8 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 25 Jul 2025 18:14:38 +0530 Subject: [PATCH 1354/2366] powerpc/kexec_file: print configured kernel command line Kexec with the -d option prints extra logs about the kexec/kdump kernel that help debug kexec and kdump. For example, it shows what kexec segments are loaded, their locations, and sizes. One key piece of information still missing is the kernel command line configured for the kexec/kdump kernel. With this patch included, the kernel will print the kernel command line configured for the kexec/kdump kernel as shown below: kexec --initrd=./initrd ./kernel -lspd --command-line="test1 test2" Loaded elf core header at 0x22e30000, bufsz=0x2000 memsz=0x80000 kexec_elf: Command line: elfcorehdr=0x22e30000 test1 test2 <--- New kexec_elf: Loaded initrd at 0x22eb0000 Signed-off-by: Sourabh Jain Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20250725124438.327593-1-sourabhjain@linux.ibm.com --- arch/powerpc/kexec/elf_64.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/powerpc/kexec/elf_64.c b/arch/powerpc/kexec/elf_64.c index ea50a072debf..d9a466cd602e 100644 --- a/arch/powerpc/kexec/elf_64.c +++ b/arch/powerpc/kexec/elf_64.c @@ -94,6 +94,8 @@ static void *elf64_load(struct kimage *image, char *kernel_buf, cmdline = modified_cmdline; } + kexec_dprintk("Command line: %s", cmdline ? cmdline : ""); + if (initrd != NULL) { kbuf.buffer = initrd; kbuf.bufsz = kbuf.memsz = initrd_len; From bf1d8287816194457c1a936056ad2d1e1e478944 Mon Sep 17 00:00:00 2001 From: longlong yan Date: Wed, 22 Jul 2026 10:34:28 +0800 Subject: [PATCH 1355/2366] selftests/powerpc: use MAP_FAILED instead of (void *)-1 in tm-signal-context-force-tm mmap() is documented to return MAP_FAILED on error, but tm-signal-context-force-tm.c compares the return value against (void *)-1. Replace these with the standard MAP_FAILED macro for better readability and type safety. Signed-off-by: longlong yan Tested-by: Venkat Rao Bagalkote Reviewed-by: Amit Machhiwal Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260722023428.932-1-yanlonglong@kylinos.cn --- .../testing/selftests/powerpc/tm/tm-signal-context-force-tm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/powerpc/tm/tm-signal-context-force-tm.c b/tools/testing/selftests/powerpc/tm/tm-signal-context-force-tm.c index 0a4bc479ae39..5dc0f12f467d 100644 --- a/tools/testing/selftests/powerpc/tm/tm-signal-context-force-tm.c +++ b/tools/testing/selftests/powerpc/tm/tm-signal-context-force-tm.c @@ -60,7 +60,7 @@ void usr_signal_handler(int signo, siginfo_t *si, void *uc) ucp->uc_link = mmap(NULL, sizeof(ucontext_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); - if (ucp->uc_link == (void *)-1) { + if (ucp->uc_link == MAP_FAILED) { perror("Mmap failed"); exit(-1); } @@ -129,7 +129,7 @@ void tm_trap_test(void) ss.ss_size = SIGSTKSZ; ss.ss_flags = 0; - if (ss.ss_sp == (void *)-1) { + if (ss.ss_sp == MAP_FAILED) { perror("mmap error\n"); exit(-1); } From 15f3ce3aa218f6d4cece9101f1af4332e8712e39 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Sat, 18 Apr 2026 14:42:50 +0530 Subject: [PATCH 1356/2366] powerpc/prom: Remove redundant early_init_dt_scan_root() call Commit 554b66233623 ("of/fdt: Scan the root node properties earlier") moved the invocation of early_init_dt_scan_root() into early_init_dt_verify(). early_init_devtree() already calls early_init_dt_verify(), so the root node properties are parsed before reaching the explicit call in this function. Keeping the call here results in scanning the root node twice. Remove the redundant call and rely on the invocation from early_init_dt_verify(). This change keeps the behavior the same and removes an unnecessary duplicate call. Signed-off-by: Sourabh Jain Tested-by: Shivang Upadhyay Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260418091250.134111-1-sourabhjain@linux.ibm.com --- arch/powerpc/kernel/prom.c | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/powerpc/kernel/prom.c b/arch/powerpc/kernel/prom.c index 9ed9dde7d231..d218c8cc1f73 100644 --- a/arch/powerpc/kernel/prom.c +++ b/arch/powerpc/kernel/prom.c @@ -824,7 +824,6 @@ void __init early_init_devtree(void *params) fadump_append_bootargs(); /* Scan memory nodes and rebuild MEMBLOCKs */ - early_init_dt_scan_root(); early_init_dt_scan_memory_ppc(); /* From 9e5c53d75c560a058abef0e4338c5e3e52cb432a Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Wed, 17 Jan 2024 17:17:06 +0800 Subject: [PATCH 1357/2366] powerpc/pasemi: Add a null pointer check to the pas_setup_mce_regs kasprintf() returns a pointer to dynamically allocated memory which can be NULL upon failure. Ensure the allocation was successful by checking the pointer validity. Signed-off-by: Kunwu Chan Reviewed-by: Christophe Leroy Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20240117091706.153431-1-chentao@kylinos.cn --- arch/powerpc/platforms/pasemi/setup.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/powerpc/platforms/pasemi/setup.c b/arch/powerpc/platforms/pasemi/setup.c index d03b41336901..45792ecd5dfd 100644 --- a/arch/powerpc/platforms/pasemi/setup.c +++ b/arch/powerpc/platforms/pasemi/setup.c @@ -165,6 +165,8 @@ static int __init pas_setup_mce_regs(void) while (dev && reg < MAX_MCE_REGS) { mce_regs[reg].name = kasprintf(GFP_KERNEL, "mc%d_mcdebug_errsta", reg); + if (!mce_regs[reg].name) + return -ENOMEM; mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0x730); dev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa00a, dev); reg++; From a894f97318366d12102c15937aa6b63c21aa82b5 Mon Sep 17 00:00:00 2001 From: Mitul Golani Date: Tue, 25 Aug 2026 13:02:04 +0530 Subject: [PATCH 1358/2366] drm/i915/dp: Gate UHBR SST SDP splitting on sink capability SDP splitting for 128b/132b (UHBR) SST audio must only be enabled when the sink advertises support for it. Previously sdp_split_enable was set for every UHBR SST stream carrying audio, regardless of sink capability. In MST mode SDP splitting is inherently supported, so the sink capability check (DP_SST_SPLIT_SDP_CAP) is applied only to the SST path. Fixes: 8853750dbad8 ("drm/i915: Enable SDP split for DP2.0") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Mitul Golani Reviewed-by: Suraj Kandpal Signed-off-by: Suraj Kandpal Link: https://patch.msgid.link/20260825073204.872441-1-mitulkumar.ajitkumar.golani@intel.com (cherry picked from commit b37921c9f533ca936c5b5a484c1299680c570a7e) Signed-off-by: Jani Nikula --- .../drm/i915/display/intel_display_types.h | 2 + drivers/gpu/drm/i915/display/intel_dp.c | 44 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index b7cc361fd955..43d53a98dae7 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -1946,6 +1946,8 @@ struct intel_dp { bool colorimetry_support; + bool sst_split_sdp_support; + struct { enum transcoder transcoder; struct mutex lock; diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 113d767e62e9..3152122e6aef 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -3409,12 +3409,22 @@ intel_dp_audio_compute_config(struct intel_encoder *encoder, struct intel_crtc_state *pipe_config, struct drm_connector_state *conn_state) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + pipe_config->has_audio = intel_dp_has_audio(encoder, conn_state) && intel_audio_compute_config(encoder, pipe_config, conn_state); pipe_config->sdp_split_enable = pipe_config->has_audio && intel_dp_is_uhbr(pipe_config); + + /* + * SDP splitting for UHBR audio requires explicit sink capability in + * SST mode, whereas in MST mode it is inherently supported. + */ + if (pipe_config->sdp_split_enable && + !intel_crtc_has_type(pipe_config, INTEL_OUTPUT_DP_MST)) + pipe_config->sdp_split_enable = intel_dp->sst_split_sdp_support; } void @@ -4462,16 +4472,25 @@ void intel_dp_configure_protocol_converter(struct intel_dp *intel_dp, str_enable_disable(tmp)); } -static bool intel_dp_get_colorimetry_status(struct intel_dp *intel_dp) +static u8 intel_dp_read_dprx_feature_enum(struct intel_dp *intel_dp) { u8 dprx = 0; - if (drm_dp_dpcd_readb(&intel_dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, - &dprx) != 1) - return false; + drm_dp_dpcd_read_data(&intel_dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, + &dprx, sizeof(dprx)); + return dprx; +} + +static bool intel_dp_get_colorimetry_status(u8 dprx) +{ return dprx & DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED; } +static bool intel_dp_get_sst_split_sdp_status(u8 dprx) +{ + return dprx & DP_SST_SPLIT_SDP_CAP; +} + static int intel_dp_read_dsc_dpcd(struct drm_dp_aux *aux, u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]) { @@ -4771,6 +4790,7 @@ intel_edp_init_dpcd(struct intel_dp *intel_dp, struct intel_connector *connector { struct intel_display *display = to_intel_display(intel_dp); int ret; + u8 dprx; /* this function is meant to be called only once */ drm_WARN_ON(display->drm, intel_dp->dpcd[DP_DPCD_REV] != 0); @@ -4782,8 +4802,13 @@ intel_edp_init_dpcd(struct intel_dp *intel_dp, struct intel_connector *connector drm_dp_is_branch(intel_dp->dpcd)); intel_init_dpcd_quirks(intel_dp, &intel_dp->desc.ident); + dprx = intel_dp_read_dprx_feature_enum(intel_dp); + intel_dp->colorimetry_support = - intel_dp_get_colorimetry_status(intel_dp); + intel_dp_get_colorimetry_status(dprx); + + intel_dp->sst_split_sdp_support = + intel_dp_get_sst_split_sdp_status(dprx); /* * Read the eDP display control registers. @@ -4874,13 +4899,20 @@ intel_dp_get_dpcd(struct intel_dp *intel_dp) * the OUI/ID since we know it won't change. */ if (!intel_dp_is_edp(intel_dp)) { + u8 dprx; + drm_dp_read_desc(&intel_dp->aux, &intel_dp->desc, drm_dp_is_branch(intel_dp->dpcd)); intel_init_dpcd_quirks(intel_dp, &intel_dp->desc.ident); + dprx = intel_dp_read_dprx_feature_enum(intel_dp); + intel_dp->colorimetry_support = - intel_dp_get_colorimetry_status(intel_dp); + intel_dp_get_colorimetry_status(dprx); + + intel_dp->sst_split_sdp_support = + intel_dp_get_sst_split_sdp_status(dprx); intel_dp_update_sink_caps(intel_dp); } From cbd3dafc2003db679ccd2f6c6a2551db79657049 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 23 Aug 2026 22:50:28 +0200 Subject: [PATCH 1359/2366] drm/i915: Fix memory leak in query_perf_config_list() When krealloc() fails, free the original oa_config_ids before returning to avoid a memory leak. Fixes: 4f6ccc74a85c ("drm/i915: add support for perf configuration queries") Signed-off-by: Thorsten Blum Cc: # v5.5+ Reviewed-by: Andi Shyti Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260823205028.178597-2-thorsten.blum@linux.dev (cherry picked from commit 9977e9d84f46d4f12ad35fbbc0ec4638554bce87) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/i915_query.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/i915_query.c b/drivers/gpu/drm/i915/i915_query.c index 0c55fb6e9727..11157fb14db3 100644 --- a/drivers/gpu/drm/i915/i915_query.c +++ b/drivers/gpu/drm/i915/i915_query.c @@ -403,8 +403,10 @@ static int query_perf_config_list(struct drm_i915_private *i915, ids = krealloc(oa_config_ids, n_configs * sizeof(*oa_config_ids), GFP_KERNEL); - if (!ids) + if (!ids) { + kfree(oa_config_ids); return -ENOMEM; + } alloc = fetch_and_zero(&n_configs); From 59e63416f5153e7d58652c616fbdcb7d5e01fff7 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Tue, 8 Sep 2026 12:56:37 +0200 Subject: [PATCH 1360/2366] perf/core: Allow list_del during perf_event_overflow() A PMU might use perf_sched_cb_inc() and perf_sched_cb_dec() interface to get the PMU call back function pmu::sched_task invoked at schedule in and schedule out. This is achieved by walking along the list anchored by sched_cb_list. The following scenario might lead to a list corruption. perf_pmu_sched_task() for_each_list_entry(..., &sched_cb_list) +--> __perf_pmu_sched_task() +--> event->pmu->sched_task()) +--> PMU_push_sample() +--> perf_event_overflow() +--> __perf_event_overflow() +--> pmu->stop() +--> perf_sched_cb_dec() remove entry from sched_cb_list while list node in use. This happens when ioctl(fd, PERF_EVENT_IOC_REFRESH, xxx) has been invoked and perf_event::event_limit hits zero. Prevent the list corruption and convert for_each_list_entry() to for_each_list_entry_safe(). Fixes: bd2756811766 ("perf: Rewrite core context handling") Signed-off-by: Thomas Richter Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260908105637.627004-1-tmricht@linux.ibm.com --- kernel/events/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index 33210aff3ee6..fe33fe15689d 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -3925,13 +3925,13 @@ static void perf_pmu_sched_task(struct task_struct *prev, bool sched_in) { struct perf_cpu_context *cpuctx = this_cpu_ptr(&perf_cpu_context); - struct perf_cpu_pmu_context *cpc; + struct perf_cpu_pmu_context *cpc, *cpc2; /* cpuctx->task_ctx will be handled in perf_event_context_sched_in/out */ if (prev == next || cpuctx->task_ctx) return; - list_for_each_entry(cpc, this_cpu_ptr(&sched_cb_list), sched_cb_entry) + list_for_each_entry_safe(cpc, cpc2, this_cpu_ptr(&sched_cb_list), sched_cb_entry) __perf_pmu_sched_task(cpc, sched_in ? next : prev, sched_in); } From 88aa1223bfffb1a0a98c639e9e1f71058f0d9178 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Tue, 8 Sep 2026 15:51:01 +0800 Subject: [PATCH 1361/2366] perf/x86/intel: Correct pt_regs->flags update for PEBS path pt_regs->flags holds the saved CPU FLAGS register. In the PEBS path, it was incorrectly set to PERF_EFLAGS_EXACT instead of being populated from the PEBS flags snapshot. Update pt_regs->flags from PEBS GPR flags if GPRs group is present. Fixes: c22497f5838c ("perf/x86/intel: Support adaptive PEBS v4") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260908075102.540715-1-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 8940f0292229..d0bb767a0fef 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -2432,7 +2432,7 @@ static inline void __setup_pebs_basic_group(struct perf_event *event, { /* The ip in basic is EventingIP */ set_linear_ip(regs, ip); - regs->flags = PERF_EFLAGS_EXACT; + regs->flags |= PERF_EFLAGS_EXACT; setup_pebs_time(event, data, tsc); if (sample_type & PERF_SAMPLE_WEIGHT_STRUCT) @@ -2444,9 +2444,17 @@ static inline void __setup_pebs_gpr_group(struct perf_event *event, struct pebs_gprs *gprs, u64 sample_type) { + /* + * Update flags with PEBS data. PERF_EFLAGS_EXACT must be set + * in previous basic group handling. + */ + regs->flags = gprs->flags | PERF_EFLAGS_EXACT; + if (event->attr.precise_ip < 2) { set_linear_ip(regs, gprs->ip); regs->flags &= ~PERF_EFLAGS_EXACT; + } else if (regs->flags & X86_VM_MASK) { + regs->flags ^= (PERF_EFLAGS_VM | X86_VM_MASK); } if (sample_type & (PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER)) From a56c03a397e2cd0c4cf8da96dcd6214f7d0e7d8c Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 13 Aug 2026 14:43:46 +0800 Subject: [PATCH 1362/2366] perf/x86/intel: Prevent drain_pebs() reentry The PEBS buffer is shared by all events on a CPU, so drain_pebs() must not be reentered. If so, one instance may observe stale buffer state and potentially access out-of-bound memory. Most invocations happen in NMI context, which naturally prevents reentry. However, drain_pebs() is also reachable from process context via intel_pmu_drain_pebs_buffer(). In those paths, the PMU is often already disabled, but not guaranteed. For example, __intel_pmu_pebs_disable() only disables the target counter, so other active counters can still raise a PMI and interrupt an in-flight drain_pebs(). Here is an example, __perf_addr_filters_adjust() perf_event_stop() __perf_event_stop() x86_pmu_stop() (event->pmu->stop) intel_pmu_disable_event() intel_pmu_pebs_disable() __intel_pmu_pebs_disable() intel_pmu_drain_large_pebs() intel_pmu_drain_pebs_buffer() Introduce __intel_pmu_quiesce() and __intel_pmu_resume() helpers and use them in intel_pmu_drain_large_pebs() to disable the full PMU around the intel_pmu_drain_pebs_buffer() call, preventing reentry. Also add a warning in intel_pmu_drain_pebs_buffer() when the full PMU is not disabled. Fixes: b752ea0c28e3 ("perf/x86/intel/ds: Flush PEBS DS when changing PEBS_DATA_CFG") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260813064346.335458-1-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 33 ++++++++++++++++++++++++--------- arch/x86/events/intel/ds.c | 8 +++++++- arch/x86/events/perf_event.h | 3 +++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index cc13164d948f..1ac2ca35db53 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -3125,6 +3125,27 @@ static void intel_pmu_del_event(struct perf_event *event) this_cpu_ptr(&cpu_hw_events)->n_late_setup--; } +int __intel_pmu_quiesce(void) +{ + struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); + int pmu_enabled = cpuc->enabled; + + cpuc->enabled = 0; + if (pmu_enabled) + intel_pmu_disable_all(); + + return pmu_enabled; +} + +void __intel_pmu_resume(int pmu_enabled) +{ + struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); + + cpuc->enabled = pmu_enabled; + if (pmu_enabled) + intel_pmu_enable_all(0); +} + static int icl_set_topdown_event_period(struct perf_event *event) { struct hw_perf_event *hwc = &event->hw; @@ -3316,16 +3337,13 @@ static void intel_pmu_read_event(struct perf_event *event) if (event->hw.flags & (PERF_X86_EVENT_AUTO_RELOAD | PERF_X86_EVENT_TOPDOWN) || is_pebs_counter_event_group(event)) { struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); - bool pmu_enabled = cpuc->enabled; + int pmu_enabled; /* Only need to call update_topdown_event() once for group read. */ if (is_metric_event(event) && (cpuc->txn_flags & PERF_PMU_TXN_READ)) return; - cpuc->enabled = 0; - if (pmu_enabled) - intel_pmu_disable_all(); - + pmu_enabled = __intel_pmu_quiesce(); /* * If the PEBS counters snapshotting is enabled, * the topdown event is available in PEBS records. @@ -3334,10 +3352,7 @@ static void intel_pmu_read_event(struct perf_event *event) static_call(intel_pmu_update_topdown_event)(event, NULL); else intel_pmu_drain_pebs_buffer(); - - cpuc->enabled = pmu_enabled; - if (pmu_enabled) - intel_pmu_enable_all(0); + __intel_pmu_resume(pmu_enabled); return; } diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index d0bb767a0fef..b98029b44052 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1242,8 +1242,11 @@ int intel_pmu_drain_bts_buffer(void) void intel_pmu_drain_pebs_buffer(void) { + struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); struct perf_sample_data data; + WARN_ON_ONCE(cpuc->enabled); + static_call(x86_pmu_drain_pebs)(NULL, &data); } @@ -1864,8 +1867,11 @@ static void intel_pmu_pebs_via_pt_enable(struct perf_event *event) static inline void intel_pmu_drain_large_pebs(struct cpu_hw_events *cpuc) { if (cpuc->n_pebs == cpuc->n_large_pebs && - cpuc->n_pebs != cpuc->n_pebs_via_pt) + cpuc->n_pebs != cpuc->n_pebs_via_pt) { + int enabled = __intel_pmu_quiesce(); intel_pmu_drain_pebs_buffer(); + __intel_pmu_resume(enabled); + } } static void __intel_pmu_pebs_enable(struct perf_event *event) diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 71ed5b2acea2..4680cba91340 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1638,6 +1638,9 @@ static __always_inline void __intel_pmu_lbr_disable(void) wrmsrq(MSR_IA32_DEBUGCTLMSR, debugctl); } +extern int __intel_pmu_quiesce(void); +extern void __intel_pmu_resume(int pmu_enabled); + int intel_pmu_save_and_restart(struct perf_event *event); struct event_constraint * From 9a8bc9bb4c3fb3218b4f151f98a722fbeb5b5c34 Mon Sep 17 00:00:00 2001 From: Vincent Guittot Date: Mon, 7 Sep 2026 14:38:55 +0200 Subject: [PATCH 1363/2366] sched/eevdf: Fix augmented max_slice Similarly to se->min_slice, init se->max_slice with se->slice before enqueueing the entity so the augmented callback computes it correctly at parent level. Fixes: 6e3c0a4e1ad1 ("sched/fair: Fix lag clamp") Signed-off-by: Vincent Guittot Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: K Prateek Nayak Link: https://patch.msgid.link/20260907123855.1297976-1-vincent.guittot@linaro.org --- kernel/sched/fair.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index ade1eceb39b8..5b944f9a8a00 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1073,6 +1073,8 @@ static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) sum_w_vruntime_add(cfs_rq, se); se->min_vruntime = se->vruntime; se->min_slice = se->slice; + se->max_slice = se->slice; + rb_add_augmented_cached(&se->run_node, &cfs_rq->tasks_timeline, __entity_less, &min_vruntime_cb); } From 51b0e68cfa0ac69e3c3ea9d6753af7e15dfaab22 Mon Sep 17 00:00:00 2001 From: Vincent Guittot Date: Wed, 9 Sep 2026 17:05:22 +0200 Subject: [PATCH 1364/2366] sched/eevdf: Fix rb augmented with multi fields The eevdf rb tree maintains 3 augmented fields but only one is currently copied when balancing the tree. Add a more generic define that can be used when there are several augmented fields. In this case, we provide a function that takes care of copying all fields. Fixes: aef6987d8954 ("sched/eevdf: Propagate min_slice up the cgroup hierarchy") Signed-off-by: Vincent Guittot Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Link: https://patch.msgid.link/20260909150522.858312-1-vincent.guittot@linaro.org --- include/linux/rbtree_augmented.h | 35 +++++++++++++++++++++++++------- kernel/sched/fair.c | 12 +++++++++-- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/include/linux/rbtree_augmented.h b/include/linux/rbtree_augmented.h index 6dbc5a1bf6a8..d2fa1c41bfd2 100644 --- a/include/linux/rbtree_augmented.h +++ b/include/linux/rbtree_augmented.h @@ -87,18 +87,18 @@ rb_add_augmented_cached(struct rb_node *node, struct rb_root_cached *tree, } /* - * Template for declaring augmented rbtree callbacks (generic case) + * Template for declaring augmented rbtree callbacks (generic multi fields) * * RBSTATIC: 'static' or empty * RBNAME: name of the rb_augment_callbacks structure * RBSTRUCT: struct type of the tree nodes * RBFIELD: name of struct rb_node field within RBSTRUCT - * RBAUGMENTED: name of field within RBSTRUCT holding data for subtree - * RBCOMPUTE: name of function that recomputes the RBAUGMENTED data + * RBCOPY: name of function that copies the RBAUGMENTED datas + * RBCOMPUTE: name of function that recomputes the RBAUGMENTED datas */ -#define RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ - RBSTRUCT, RBFIELD, RBAUGMENTED, RBCOMPUTE) \ +#define RB_DECLARE_CALLBACKS_MULTI(RBSTATIC, RBNAME, \ + RBSTRUCT, RBFIELD, RBCOPY, RBCOMPUTE) \ static inline void \ RBNAME ## _propagate(struct rb_node *rb, struct rb_node *stop) \ { \ @@ -114,14 +114,14 @@ RBNAME ## _copy(struct rb_node *rb_old, struct rb_node *rb_new) \ { \ RBSTRUCT *old = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ RBSTRUCT *new = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ - new->RBAUGMENTED = old->RBAUGMENTED; \ + RBCOPY(new, old); \ } \ static void \ RBNAME ## _rotate(struct rb_node *rb_old, struct rb_node *rb_new) \ { \ RBSTRUCT *old = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ RBSTRUCT *new = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ - new->RBAUGMENTED = old->RBAUGMENTED; \ + RBCOPY(new, old); \ RBCOMPUTE(old, false); \ } \ RBSTATIC const struct rb_augment_callbacks RBNAME = { \ @@ -130,6 +130,27 @@ RBSTATIC const struct rb_augment_callbacks RBNAME = { \ .rotate = RBNAME ## _rotate \ }; +/* + * Template for declaring augmented rbtree callbacks (generic single field) + * + * RBSTATIC: 'static' or empty + * RBNAME: name of the rb_augment_callbacks structure + * RBSTRUCT: struct type of the tree nodes + * RBFIELD: name of struct rb_node field within RBSTRUCT + * RBAUGMENTED: name of field within RBSTRUCT holding data for subtree + * RBCOMPUTE: name of function that recomputes the RBAUGMENTED data + */ + +#define RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ + RBSTRUCT, RBFIELD, RBAUGMENTED, RBCOMPUTE) \ +static inline void \ +RBNAME ## _copy_single(RBSTRUCT *new, RBSTRUCT *old) \ +{ \ + new->RBAUGMENTED = old->RBAUGMENTED; \ +} \ +RB_DECLARE_CALLBACKS_MULTI(RBSTATIC, RBNAME, \ + RBSTRUCT, RBFIELD, RBNAME ## _copy_single, RBCOMPUTE) + /* * Template for declaring augmented rbtree callbacks, * computing RBAUGMENTED scalar as max(RBCOMPUTE(node)) for all subtree nodes. diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 5b944f9a8a00..944833e8056f 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1032,6 +1032,13 @@ static inline void __max_slice_update(struct sched_entity *se, struct rb_node *n } } +static inline void min_vruntime_copy(struct sched_entity *new, struct sched_entity *old) +{ + new->min_vruntime = old->min_vruntime; + new->min_slice = old->min_slice; + new->max_slice = old->max_slice; +} + /* * se->min_vruntime = min(se->vruntime, {left,right}->min_vruntime) */ @@ -1059,8 +1066,9 @@ static inline bool min_vruntime_update(struct sched_entity *se, bool exit) se->max_slice == old_max_slice; } -RB_DECLARE_CALLBACKS(static, min_vruntime_cb, struct sched_entity, - run_node, min_vruntime, min_vruntime_update); + +RB_DECLARE_CALLBACKS_MULTI(static, min_vruntime_cb, struct sched_entity, + run_node, min_vruntime_copy, min_vruntime_update); /* * Enqueue an entity into the rb-tree: From c23810313bdf6b02f39a1f2a1464c4b18bd39e31 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Fri, 4 Sep 2026 11:47:07 +0800 Subject: [PATCH 1365/2366] sched: Account cgroup CPU time to the execution context Proxy execution separates the scheduling context from the execution context. Commit aa4f74dfd42b ("sched: Fix runtime accounting w/ split exec & sched contexts") made per-task and thread-group runtime accounting follow the task that actually executes, while cgroup CPU usage is charged to the donor. When the donor and execution task belong to different cgroups, this makes a task's execution time count against a different cgroup from the one the task belongs to. Cgroup CPU usage should follow the execution context, matching the per-task, thread-group, and cgroup user/system accounting. Keep scheduling state associated with the donor, but charge cgroup CPU usage to rq->curr. A reproducer with the donor and execution task in separate cgroups showed the execution task accumulating runtime while cgroup CPU usage was charged to the donor's cgroup. With this change, the execution task's cgroup accumulates the CPU usage instead. The same behavior was verified with an RT donor and with legacy cpuacct accounting. Fixes: aa4f74dfd42b ("sched: Fix runtime accounting w/ split exec & sched contexts") Suggested-by: Tejun Heo Signed-off-by: Hui Su Signed-off-by: Peter Zijlstra (Intel) Acked-by: Tejun Heo Acked-by: John Stultz Link: https://patch.msgid.link/20260904034707.268416-1-sh_def@163.com --- kernel/sched/fair.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 944833e8056f..7455a83a6a99 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1414,7 +1414,6 @@ static s64 update_se(struct rq *rq, struct sched_entity *se) se->exec_start = now; if (entity_is_task(se)) { - struct task_struct *donor = task_of(se); struct task_struct *running = rq->curr; /* * If se is a task, we account the time against the running @@ -1427,8 +1426,7 @@ static s64 update_se(struct rq *rq, struct sched_entity *se) account_group_exec_runtime(running, delta_exec); account_mm_sched(rq, running, delta_exec); - /* cgroup time is always accounted against the donor */ - cgroup_account_cputime(donor, delta_exec); + cgroup_account_cputime(running, delta_exec); } else { /* If not task, account the time against donor se */ se->sum_exec_runtime += delta_exec; From f5741d2b34519d387edf6e9798fc7030c20a35f3 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Wed, 2 Sep 2026 23:02:09 +0800 Subject: [PATCH 1366/2366] sched/core: Call wq_worker_tick() for the execution context wq_worker_tick() accounts CPU time and detects CPU-intensive work for the kworker that is actually running. With proxy execution, rq->donor is the scheduling context while rq->curr is the execution context. Calling the hook with rq->donor can skip workqueue accounting when a kworker is executing on behalf of a donor task. It can also account a blocked kworker when the donor is a worker but rq->curr is the task actually executing. The former can delay WORKER_CPU_INTENSIVE handling and pool concurrency management, which can delay pending kernel work and userspace operations depending on it. Use rq->curr for the workqueue tick hook while retaining rq->donor for scheduler accounting. Fixes: af0c8b2bf67b ("sched: Split scheduler and execution contexts") Signed-off-by: Hui Su Signed-off-by: Peter Zijlstra (Intel) Acked-by: Tejun Heo Link: https://patch.msgid.link/20260902150208.1209922-2-sh_def@163.com --- kernel/sched/core.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index b998ef6b87af..7885ff76e69f 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -5776,8 +5776,8 @@ void sched_tick(void) { int cpu = smp_processor_id(); struct rq *rq = cpu_rq(cpu); - /* accounting goes to the donor task */ - struct task_struct *donor; + /* scheduler accounting goes to the donor task */ + struct task_struct *curr, *donor; struct rq_flags rf; unsigned long hw_pressure; u64 resched_latency; @@ -5788,6 +5788,7 @@ void sched_tick(void) sched_clock_tick(); rq_lock(rq, &rf); + curr = rq->curr; donor = rq->donor; psi_account_irqtime(rq, donor, NULL); @@ -5813,8 +5814,8 @@ void sched_tick(void) perf_event_task_tick(); - if (donor->flags & PF_WQ_WORKER) - wq_worker_tick(donor); + if (curr->flags & PF_WQ_WORKER) + wq_worker_tick(curr); if (!scx_switched_all()) { rq->idle_balance = idle_cpu(cpu); From 5a5d26f2cfe13467166219f6bf58099326912ddb Mon Sep 17 00:00:00 2001 From: Soheil Hassas Yeganeh Date: Mon, 31 Aug 2026 14:48:44 +0000 Subject: [PATCH 1367/2366] x86/cfi: Fix FineIBT hash offset in cfi_get_func_hash() The switch of the FineIBT preamble from "subl $hash, %r10d" to the shorter "subl $hash, %eax" moved the hash immediate from offset 7 to offset 5 of the preamble. fineibt_preamble_hash was updated to match, but the open-coded offset in cfi_get_func_hash() was missed and it still reads the hash at offset 7. cfi_get_func_hash() is used by the BPF JIT to give a struct_ops trampoline the CFI hash of the stub function it stands in for. With FineIBT the trampoline now gets the upper half of the real hash followed by the first two bytes of the next instruction, so the first indirect call from the kernel into a struct_ops program, tcp_init_congestion_control() calling ->init() of a BPF congestion control for example, fails the FineIBT check and the kernel dies with a CFI failure. Move the FineIBT preamble template and its offset defines above cfi_get_func_hash() and use fineibt_preamble_hash there, so every reader of the preamble shares one definition of its layout. The CFI_FINEIBT arm is only built with CONFIG_FINEIBT, the only configuration in which cfi_mode can take that value. cfi_get_func_arity() does not need the same treatment: the __bhi_args call whose displacement it reads still ends at the function address. Fixes: 85a2d4a890dc ("x86,ibt: Use UDB instead of 0xEA") Assisted-by: LLM Signed-off-by: Soheil Hassas Yeganeh Signed-off-by: Peter Zijlstra (Intel) Cc: stable@vger.kernel.org # 6.18+ Link: https://patch.msgid.link/20260831-b4-x86-cfi-fineibt-func-hash-v1-1-6ffc0af5c4ec@gmail.com --- arch/x86/kernel/alternative.c | 72 ++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index add62db3e82c..741d8767ddf8 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c @@ -1201,6 +1201,41 @@ static bool cfi_debug __ro_after_init; bool cfi_bhi __ro_after_init = false; #endif +#ifdef CONFIG_FINEIBT +/* + * : + * 0: f3 0f 1e fa endbr64 + * 4: 2d 78 56 34 12 sub $0x12345678, %eax + * 9: 2e 0f 85 03 00 00 00 jne,pn 13 + * 10: 0f 1f 40 d6 nopl -0x2a(%rax) + * + * Note that the JNE target is the 0xD6 byte inside the NOPL, this decodes as + * UDB on x86_64 and raises #UD. + */ +asm( ".pushsection .rodata \n" + "fineibt_preamble_start: \n" + " endbr64 \n" + " subl $0x12345678, %eax \n" + "fineibt_preamble_bhi: \n" + " cs jne.d32 fineibt_preamble_start+0x13 \n" + "#fineibt_func: \n" + " nopl -42(%rax) \n" + "fineibt_preamble_end: \n" + ".popsection\n" +); + +extern u8 fineibt_preamble_start[]; +extern u8 fineibt_preamble_bhi[]; +extern u8 fineibt_preamble_end[]; + +#define fineibt_preamble_size (fineibt_preamble_end - fineibt_preamble_start) +#define fineibt_preamble_bhi (fineibt_preamble_bhi - fineibt_preamble_start) +#define fineibt_preamble_ud 0x13 +#define fineibt_preamble_hash 5 + +#define fineibt_prefix_size (fineibt_preamble_size - ENDBR_INSN_SIZE) +#endif /* CONFIG_FINEIBT */ + #ifdef CONFIG_CFI u32 cfi_get_func_hash(void *func) { @@ -1208,9 +1243,11 @@ u32 cfi_get_func_hash(void *func) func -= cfi_get_offset(); switch (cfi_mode) { +#ifdef CONFIG_FINEIBT case CFI_FINEIBT: - func += 7; + func += fineibt_preamble_hash; break; +#endif case CFI_KCFI: func += 1; break; @@ -1366,39 +1403,6 @@ early_param("cfi", cfi_parse_cmdline); * anyway. */ -/* - * : - * 0: f3 0f 1e fa endbr64 - * 4: 2d 78 56 34 12 sub $0x12345678, %eax - * 9: 2e 0f 85 03 00 00 00 jne,pn 13 - * 10: 0f 1f 40 d6 nopl -0x2a(%rax) - * - * Note that the JNE target is the 0xD6 byte inside the NOPL, this decodes as - * UDB on x86_64 and raises #UD. - */ -asm( ".pushsection .rodata \n" - "fineibt_preamble_start: \n" - " endbr64 \n" - " subl $0x12345678, %eax \n" - "fineibt_preamble_bhi: \n" - " cs jne.d32 fineibt_preamble_start+0x13 \n" - "#fineibt_func: \n" - " nopl -42(%rax) \n" - "fineibt_preamble_end: \n" - ".popsection\n" -); - -extern u8 fineibt_preamble_start[]; -extern u8 fineibt_preamble_bhi[]; -extern u8 fineibt_preamble_end[]; - -#define fineibt_preamble_size (fineibt_preamble_end - fineibt_preamble_start) -#define fineibt_preamble_bhi (fineibt_preamble_bhi - fineibt_preamble_start) -#define fineibt_preamble_ud 0x13 -#define fineibt_preamble_hash 5 - -#define fineibt_prefix_size (fineibt_preamble_size - ENDBR_INSN_SIZE) - /* * : * 0: b8 78 56 34 12 mov $0x12345678, %eax From 00f9fbc12320253bfc576fb7539d860029c82d0f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 7 Sep 2026 08:52:35 +0200 Subject: [PATCH 1368/2366] net: hso: fix TIOCMIWAIT race The task state must be updated before checking the wakeup condition to avoid missing a racing modem status update. Fixes: 542f54823614 ("tty: Modem functions for the HSO driver") Cc: stable@vger.kernel.org # 2.6.29 Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260907065235.100848-1-johan@kernel.org Signed-off-by: Paolo Abeni --- drivers/net/usb/hso.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c index c1aec67688ae..71caa3764b23 100644 --- a/drivers/net/usb/hso.c +++ b/drivers/net/usb/hso.c @@ -1547,10 +1547,10 @@ hso_wait_modem_status(struct hso_serial *serial, unsigned long arg) spin_unlock_irq(&serial->serial_lock); add_wait_queue(&tiocmget->waitq, &wait); for (;;) { + set_current_state(TASK_INTERRUPTIBLE); spin_lock_irq(&serial->serial_lock); memcpy(&cnow, &tiocmget->icount, sizeof(struct uart_icount)); spin_unlock_irq(&serial->serial_lock); - set_current_state(TASK_INTERRUPTIBLE); if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd))) { From 1853f30cf5c84971f99788a76207c6f745380896 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 7 Sep 2026 16:21:30 -0300 Subject: [PATCH 1369/2366] net/sched: cls_route: free emptied bucket on filter move route4_change can move an existing filter to a different top-level bucket: route4_set_parms recomputes the handle from TCA_ROUTE4_TO/ FROM/IIF, and the handle-mismatch check is gated on the 'new' flag, so for an existing filter the new handle may differ from the old one and land in a different bucket. When this happens, the filter is unlinked from the old bucket, but the bucket itself is never freed once it goes empty. The stale empty bucket remains in head->table[], causing route4_delete to report *last=false even after the last live filter is gone. That pins the empty tcf_proto and causes a leak. Fix this by refcounting the filters linked to a bucket and freeing the bucket when the count drops to zero. The existing scan in route4_delete goes away with it. The count is updated at all sites that link or unlink a filter during add, change and delete, and the bucket is dropped from head->table[] as soon as it reaches zero. Conditions to recreate the bug: CONFIG_NET_CLS_ROUTE4=y, CONFIG_NET_SCH_INGRESS=y, CONFIG_NET_CLS_ACT=y. tc qdisc replace dev lo clsact tc filter add dev lo ingress protocol ip pref 100 route from 1 to 1 tc filter change dev lo ingress protocol ip pref 100 handle 0x10001 \ route from 1 to 2 tc filter del dev lo ingress protocol ip pref 100 handle 0x10002 \ route from 1 to 2 tc filter show dev lo ingress | grep -c 'pref 100 route chain 0 ' Fixes: 1e052be69d04 ("net_sched: destroy proto tp when all filters are gone") Reported-by: Vega Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260907192133.2639067-2-victor@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/cls_route.c | 45 +++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index 0d1324c90583..17b0ebb76662 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ struct route4_head { struct route4_bucket { /* 16 FROM buckets + 16 IIF buckets + 1 wildcard bucket */ struct route4_filter __rcu *ht[16 + 16 + 1]; + refcount_t filters_ref; struct rcu_head rcu; }; @@ -336,7 +338,7 @@ static int route4_delete(struct tcf_proto *tp, void *arg, bool *last, struct route4_filter *nf; struct route4_bucket *b; unsigned int h = 0; - int i, h1; + int h1; if (!head || !f) return -EINVAL; @@ -362,23 +364,14 @@ static int route4_delete(struct tcf_proto *tp, void *arg, bool *last, tcf_exts_get_net(&f->exts); tcf_queue_work(&f->rwork, route4_delete_filter_work); - /* Strip RTNL protected tree */ - for (i = 0; i <= 32; i++) { - struct route4_filter *rt; - - rt = rtnl_dereference(b->ht[i]); - if (rt) - goto out; + if (refcount_dec_and_test(&b->filters_ref)) { + RCU_INIT_POINTER(head->table[to_hash(h)], NULL); + kfree_rcu(b, rcu); } - - /* OK, session has no flows */ - RCU_INIT_POINTER(head->table[to_hash(h)], NULL); - kfree_rcu(b, rcu); break; } } -out: *last = true; for (h1 = 0; h1 <= 256; h1++) { if (rcu_access_pointer(head->table[h1])) { @@ -459,6 +452,7 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, if (b == NULL) return -ENOBUFS; + refcount_set(&b->filters_ref, 1); rcu_assign_pointer(head->table[h1], b); } else { unsigned int h2 = from_hash(nhandle >> 16); @@ -468,6 +462,8 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, fp = rtnl_dereference(fp->next)) if (fp->handle == f->handle) return -EEXIST; + + refcount_inc(&b->filters_ref); } if (tb[TCA_ROUTE4_TO]) @@ -500,7 +496,7 @@ static int route4_change(struct net *net, struct sk_buff *in_skb, struct route4_filter *fold, *f1, *pfp, *f = NULL; struct route4_bucket *b; struct nlattr *tb[TCA_ROUTE4_MAX + 1]; - unsigned int h, th; + unsigned int h; int err; bool new = true; @@ -560,17 +556,20 @@ static int route4_change(struct net *net, struct sk_buff *in_skb, rcu_assign_pointer(*fp, f); if (fold) { - th = to_hash(fold->handle); + b = fold->bkt; h = from_hash(fold->handle >> 16); - b = rtnl_dereference(head->table[th]); - if (b) { - fp = &b->ht[h]; - for (pfp = rtnl_dereference(*fp); pfp; - fp = &pfp->next, pfp = rtnl_dereference(*fp)) { - if (pfp == fold) { - rcu_assign_pointer(*fp, fold->next); - break; + fp = &b->ht[h]; + for (pfp = rtnl_dereference(*fp); pfp; + fp = &pfp->next, pfp = rtnl_dereference(*fp)) { + if (pfp == fold) { + rcu_assign_pointer(*fp, fold->next); + if (refcount_dec_and_test(&b->filters_ref)) { + unsigned int th = to_hash(fold->handle); + + RCU_INIT_POINTER(head->table[th], NULL); + kfree_rcu(b, rcu); } + break; } } } From b74a8455a2f271f54695b6a8ec1f113824a46c0e Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 7 Sep 2026 16:21:31 -0300 Subject: [PATCH 1370/2366] net/sched: cls_route: Reject handle aliasing route4_set_parms() rejects a duplicate by scanning the destination chain for f->handle, but f->handle is the handle the filter has before the update, not the one it is about to be linked under. The comparison and the insertion therefore use different handles, which causes breakage. When a change moves the filter to a chain that already holds nhandle, the scan looks for the old handle instead, misses the collision and links a second filter with the same handle: tc filter add dev lo ingress protocol ip pref 100 \ route from 1 to 1 classid 1:1 action ok tc filter add dev lo ingress protocol ip pref 100 \ route from 2 to 2 classid 1:2 action drop tc filter change dev lo ingress protocol ip pref 100 handle 0x10001 \ route from 2 to 2 classid 1:1 action ok tc filter show dev lo ingress ... fh 0x00020002 flowid 1:2 to 2 from 2 ... fh 0x00020002 flowid 1:1 to 2 from 2 The newcomer is appended after the incumbent, and both end up with the same f->id. route4_get() returns the first match, so the second filter can no longer be addressed by handle, and route4_classify() stops at the first filter whose f->id matches. The second filter is dumped but is effectively dead. Fix this by comparing against nhandle. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260829205422.854785-1-victor%40mojatatu.com Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260907192133.2639067-3-victor@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/cls_route.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index 17b0ebb76662..9710b77d379c 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -460,8 +460,12 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, for (fp = rtnl_dereference(b->ht[h2]); fp; fp = rtnl_dereference(fp->next)) - if (fp->handle == f->handle) + if (fp->handle == nhandle) { + NL_SET_ERR_MSG_FMT(extack, + "Handle %x is already in use", + nhandle); return -EEXIST; + } refcount_inc(&b->filters_ref); } From 41e85e54e5649a1617698438b0ce64c6f9d83d69 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 7 Sep 2026 16:21:32 -0300 Subject: [PATCH 1371/2366] net/sched: cls_route: Fix in-place replace Building on the previous patch, route4_set_parms rejects a duplicate by scanning the destination chain for nhandle, but the scan doesn't exclude the older version it is replacing, so an in-place replace will match the older version's handle and fail. Fix this by passing the older filter as a parameter to route4_set_parms (replacing "new") and skipping it in the scan. Excluding the older version is not enough on its own. nhandle is built out of TCA_ROUTE4_TO, TCA_ROUTE4_FROM and TCA_ROUTE4_IIF alone, while the 0x7F00 bits, which only tell apart filters sharing one key, are folded in on the create path. Letting the replace through would therefore rename the filter it replaces: replacing handle 0x10101 stored it back as 0x10001, and a sibling at 0x10201 could then no longer be replaced at all, since its own nhandle collided with the renamed filter. tc filter add ... handle 0x10101 route from 1 to 1 classid 1:1 tc filter add ... handle 0x10201 route from 1 to 1 classid 1:2 tc filter replace ... handle 0x10101 route from 1 to 1 classid 1:9 ... fh 0x00010001 flowid 1:9 to 1 from 1 ... fh 0x00010201 flowid 1:2 to 1 from 1 tc filter replace ... handle 0x10201 route from 1 to 1 classid 1:8 Error: Handle 10001 is already in use. So carry those bits over when the key the request builds is the key the older filter already has. An in-place replace then keeps the handle userspace named the filter by, while a request that does change the key still renames it, as it did before. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260829205422.854785-1-victor%40mojatatu.com Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260907192133.2639067-4-victor@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/cls_route.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index 9710b77d379c..0f211f030fd9 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -393,8 +393,9 @@ static const struct nla_policy route4_policy[TCA_ROUTE4_MAX + 1] = { static int route4_set_parms(struct net *net, struct tcf_proto *tp, unsigned long base, struct route4_filter *f, u32 handle, struct route4_head *head, - struct nlattr **tb, struct nlattr *est, int new, - u32 flags, struct netlink_ext_ack *extack) + struct nlattr **tb, struct nlattr *est, + struct route4_filter *fold, u32 flags, + struct netlink_ext_ack *extack) { u32 id = 0, to = 0, nhandle = 0x8000; struct route4_filter *fp; @@ -407,7 +408,7 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, return err; if (tb[TCA_ROUTE4_TO]) { - if (new && handle & 0x8000) { + if (!fold && handle & 0x8000) { NL_SET_ERR_MSG(extack, "Invalid handle"); return -EINVAL; } @@ -430,14 +431,14 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, } else nhandle |= 0xFFFF << 16; - if (handle && new) { + if (handle && (!fold || nhandle == (handle & ~0x7F00))) nhandle |= handle & 0x7F00; - if (nhandle != handle) { - NL_SET_ERR_MSG_FMT(extack, - "Handle mismatch constructed: %x (expected: %x)", - handle, nhandle); - return -EINVAL; - } + + if (handle && !fold && nhandle != handle) { + NL_SET_ERR_MSG_FMT(extack, + "Handle mismatch constructed: %x (expected: %x)", + handle, nhandle); + return -EINVAL; } if (!nhandle) { @@ -460,7 +461,7 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, for (fp = rtnl_dereference(b->ht[h2]); fp; fp = rtnl_dereference(fp->next)) - if (fp->handle == nhandle) { + if (fp != fold && fp->handle == nhandle) { NL_SET_ERR_MSG_FMT(extack, "Handle %x is already in use", nhandle); @@ -502,7 +503,6 @@ static int route4_change(struct net *net, struct sk_buff *in_skb, struct nlattr *tb[TCA_ROUTE4_MAX + 1]; unsigned int h; int err; - bool new = true; if (!handle) { NL_SET_ERR_MSG(extack, "Creating with handle of 0 is invalid"); @@ -539,11 +539,10 @@ static int route4_change(struct net *net, struct sk_buff *in_skb, f->tp = fold->tp; f->bkt = fold->bkt; - new = false; } err = route4_set_parms(net, tp, base, f, handle, head, tb, - tca[TCA_RATE], new, flags, extack); + tca[TCA_RATE], fold, flags, extack); if (err < 0) goto errout; From e190a7aabbea4fbfec0e74de134144cb4d040738 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Mon, 7 Sep 2026 16:21:33 -0300 Subject: [PATCH 1372/2366] selftests/tc-testing: Add cls_route bucket move and change tests Add 4 tdc tests for the cls_route bugs fixed earlier in this series: - Delete a route filter that was moved to another bucket (a7d2): Validates that deleting a filter, and making a bucket empty, does not leave a dangling empty bucket - Try to change a route filter onto an already used handle (c05a): Validates that attempting to change an existing filter's handle to an already taken one fails - Replace a route filter that shares its key with another filter (3f21): Validates that an in-place replace keeps the handle userspace named the filter by, rather than dropping the 0x7F00 bits from it - Replace both route filters sharing a key (9d0e): Validates that replacing one of the two does not make the other one unreplaceable Acked-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260907192133.2639067-5-victor@mojatatu.com Signed-off-by: Paolo Abeni --- .../tc-testing/tc-tests/filters/route.json | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/filters/route.json b/tools/testing/selftests/tc-testing/tc-tests/filters/route.json index 05cedca67cca..2d5843aebd72 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/filters/route.json +++ b/tools/testing/selftests/tc-testing/tc-tests/filters/route.json @@ -202,5 +202,215 @@ "teardown": [ "$TC qdisc del dev $DEV1 parent root drr" ] + }, + { + "id": "a7d2", + "name": "Delete a route filter that was moved to another bucket", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route from 1 to 1 classid 1:1", + "$TC filter change dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10001 route from 1 to 2 classid 1:1", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 200 route from 5 to 5 classid 1:5" + ], + "cmdUnderTest": "$TC filter del dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10002 route from 1 to 2", + "expExitCode": "0", + "verifyCmd": "$TC -j filter show dev $DEV1 parent ffff:", + "matchJSON": [ + { + "protocol": "ip", + "pref": 200, + "kind": "route", + "chain": 0 + }, + { + "protocol": "ip", + "pref": 200, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x50005", + "flowid": "1:5" + } + } + ], + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "c05a", + "name": "Try to change a route filter onto an already used handle", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route from 1 to 1 classid 1:1 action ok", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route from 2 to 2 classid 1:2 action drop" + ], + "cmdUnderTest": "$TC filter change dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10001 route from 2 to 2 classid 1:1 action ok", + "expExitCode": "2", + "verifyCmd": "$TC -j filter show dev $DEV1 parent ffff:", + "matchJSON": [ + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0 + }, + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x10001", + "flowid": "1:1", + "actions": [ + { + "order": 1, + "kind": "gact", + "control_action": { + "type": "pass" + } + } + ] + } + }, + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x20002", + "flowid": "1:2", + "actions": [ + { + "order": 1, + "kind": "gact", + "control_action": { + "type": "drop" + } + } + ] + } + } + ], + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "3f21", + "name": "Replace a route filter that shares its key with another filter", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10101 route from 1 to 1 classid 1:1", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10201 route from 1 to 1 classid 1:2" + ], + "cmdUnderTest": "$TC filter replace dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10101 route from 1 to 1 classid 1:9", + "expExitCode": "0", + "verifyCmd": "$TC -j filter show dev $DEV1 parent ffff:", + "matchJSON": [ + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0 + }, + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x10101", + "flowid": "1:9" + } + }, + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x10201", + "flowid": "1:2" + } + } + ], + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "9d0e", + "name": "Replace both route filters sharing a key", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10101 route from 1 to 1 classid 1:1", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10201 route from 1 to 1 classid 1:2", + "$TC filter replace dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10101 route from 1 to 1 classid 1:9" + ], + "cmdUnderTest": "$TC filter replace dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10201 route from 1 to 1 classid 1:8", + "expExitCode": "0", + "verifyCmd": "$TC -j filter show dev $DEV1 parent ffff:", + "matchJSON": [ + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0 + }, + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x10101", + "flowid": "1:9" + } + }, + { + "protocol": "ip", + "pref": 100, + "kind": "route", + "chain": 0, + "options": { + "fh": "0x10201", + "flowid": "1:8" + } + } + ], + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] } ] From dff39930ad5e53d202bfdfb14687d1d2fd753b4d Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Tue, 8 Sep 2026 20:55:25 +0000 Subject: [PATCH 1373/2366] net/sched: cls_api: Don't replay RTM_GETCHAIN in tc_ctl_chain(). If a netlink socket sends RTM_GETCHAIN requests repeatedly without recv()ing the responses, tc_ctl_chain() hogs CPU and triggers Hung Task splat. [0] As caught in the stack trace, netlink_attachskb() could confuse tc_ctl_chain() by returning -EAGAIN when the userspace netlink socket's receive buffer is full. The replay: label exists since commit 32a4f5ecd738 ("net: sched: introduce chain object to uapi") but was not used initially. Since commit 9f407f1768d3 ("net: sched: introduce chain templates"), the label is needed for RTM_NEWCHAIN because tcf_proto_lookup_ops() may release RTNL to call request_module(). However, the replay logic is unnecessary for RTM_GETCHAIN. Let's apply the replay logic only for RTM_NEWCHAIN. [0]: INFO: task repro:1018 is blocked on a mutex likely owned by task repro:1022. task:repro state:R running task stack:14096 pid:1022 tgid:1014 ppid:961 task_flags:0x400040 flags:0x00080000 Call Trace: ? clockevents_program_event (kernel/time/clockevents.c:372) ? pskb_expand_head (net/core/skbuff.c:615) ? skb_release_data (net/core/skbuff.c:1122) ? netlink_attachskb (./include/linux/skbuff.h:1323 ./include/linux/skbuff.h:1332 net/netlink/af_netlink.c:1232) ? __netlink_lookup (./include/linux/rcupdate.h:882 ./include/linux/rhashtable.h:711 net/netlink/af_netlink.c:499) ? tc_chain_notify (net/sched/cls_api.c:3045) ? tc_chain_notify (./include/linux/skbuff.h:1384 net/sched/cls_api.c:3041) ? netlink_unicast (net/netlink/af_netlink.c:1335) ? rtnl_unicast (./include/net/netlink.h:1198 net/core/rtnetlink.c:985) ? tc_ctl_chain (net/sched/cls_api.c:3242) ? rtnetlink_rcv_msg (net/core/rtnetlink.c:7146) ? netlink_unicast (net/netlink/af_netlink.c:1354) ? __pfx_rtnetlink_rcv_msg (net/core/rtnetlink.c:7177) ? netlink_rcv_skb (net/netlink/af_netlink.c:2556) ? netlink_unicast (net/netlink/af_netlink.c:1319) ? netlink_sendmsg (net/netlink/af_netlink.c:1900) ? __sock_sendmsg (net/socket.c:800) ? __sys_sendto (net/socket.c:2281) ? __x64_sys_sendto (net/socket.c:2288 net/socket.c:2284 net/socket.c:2284) ? do_syscall_64 (arch/x86/entry/syscall_64.c:61 arch/x86/entry/syscall_64.c:84) ? entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Fixes: 2ed9db3074fc ("net: sched: cls_api: fix dead code in switch") Reported-by: Taras Madan Signed-off-by: Kuniyuki Iwashima Reviewed-by: Jamal Hadi Salim Tested-by: hybris@mojatatu.ai Link: https://patch.msgid.link/20260908205537.863484-1-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/sched/cls_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index 9966766661d5..c47d2ee13641 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -3254,7 +3254,7 @@ static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n, tcf_chain_put(chain); errout_block: tcf_block_release(q, block, true); - if (err == -EAGAIN) + if (err == -EAGAIN && n->nlmsg_type == RTM_NEWCHAIN) /* Replay the request. */ goto replay; return err; From af406abfecad2f48d8f1fc646d3994f0982bac62 Mon Sep 17 00:00:00 2001 From: Li Youhong Date: Fri, 4 Sep 2026 16:07:58 +0800 Subject: [PATCH 1374/2366] net: sun4i-emac: fix missing of_node_put() for phy_node of_parse_phandle() returns a node pointer with an elevated refcount. Add the missing of_node_put() on the probe error path after register_netdev() fails and in emac_remove(). Fixes: 492205050d77 ("net: Add EMAC ethernet driver found on Allwinner A10 SoC's") Signed-off-by: Li Youhong Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260904080758.2432748-1-dayou5941@163.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/allwinner/sun4i-emac.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/allwinner/sun4i-emac.c b/drivers/net/ethernet/allwinner/sun4i-emac.c index 942454e29488..0ba67a55705d 100644 --- a/drivers/net/ethernet/allwinner/sun4i-emac.c +++ b/drivers/net/ethernet/allwinner/sun4i-emac.c @@ -1067,6 +1067,7 @@ static int emac_probe(struct platform_device *pdev) return 0; out_release_sram: + of_node_put(db->phy_node); sunxi_sram_release(&pdev->dev); out_clk_disable_unprepare: clk_disable_unprepare(db->clk); @@ -1094,6 +1095,7 @@ static void emac_remove(struct platform_device *pdev) } unregister_netdev(ndev); + of_node_put(db->phy_node); sunxi_sram_release(&pdev->dev); clk_disable_unprepare(db->clk); irq_dispose_mapping(ndev->irq); From 5d4d985957434867bbe85e4fa5e638f3e48ad522 Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Mon, 7 Sep 2026 02:42:34 +0000 Subject: [PATCH 1375/2366] net: hinic: fix mailbox segment buffer overflow check_mbox_seq_id_and_seg_len() validates that seq_id does not exceed SEQ_ID_MAX_VAL (42) and seg_len does not exceed MBOX_SEG_LEN (48). However, this allows the last segment (seq_id=42) to carry a full 48-byte payload, writing to offset 42*48=2016 for 48 bytes (ending at byte 2064). The receive buffer is only MBOX_MAX_BUF_SZ (2048) bytes, resulting in a 16-byte heap buffer overflow. The hinic3 driver already handles this correctly by defining MBOX_LAST_SEG_MAX_LEN and rejecting the last segment when it exceeds the remaining buffer space. Apply the same fix to the hinic driver. Fixes: a425b6e1c69b ("hinic: add mailbox function support") Signed-off-by: Aamir Ahmed Link: https://patch.msgid.link/AS8P251MB0001AE870B09020B46B5D7DBC8B22@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM Signed-off-by: Paolo Abeni --- drivers/net/ethernet/huawei/hinic/hinic_hw_mbox.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/huawei/hinic/hinic_hw_mbox.c b/drivers/net/ethernet/huawei/hinic/hinic_hw_mbox.c index 2784127327e6..6e67a6c9578e 100644 --- a/drivers/net/ethernet/huawei/hinic/hinic_hw_mbox.c +++ b/drivers/net/ethernet/huawei/hinic/hinic_hw_mbox.c @@ -128,6 +128,7 @@ enum hinic_mbox_tx_status { #define SEQ_ID_START_VAL 0 #define SEQ_ID_MAX_VAL 42 +#define MBOX_LAST_SEG_MAX_LEN (MBOX_MAX_BUF_SZ - SEQ_ID_MAX_VAL * MBOX_SEG_LEN) #define NO_DMA_ATTRIBUTE_VAL 0 @@ -372,7 +373,8 @@ recv_pf_from_vf_mbox_handler(struct hinic_mbox_func_to_func *func_to_func, static bool check_mbox_seq_id_and_seg_len(struct hinic_recv_mbox *recv_mbox, u8 seq_id, u8 seg_len) { - if (seq_id > SEQ_ID_MAX_VAL || seg_len > MBOX_SEG_LEN) + if (seq_id > SEQ_ID_MAX_VAL || seg_len > MBOX_SEG_LEN || + (seq_id == SEQ_ID_MAX_VAL && seg_len > MBOX_LAST_SEG_MAX_LEN)) return false; if (seq_id == 0) { From 3c18e3c9a54e1239b72849502ca4737604bfbb46 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 3 Sep 2026 12:36:43 +0000 Subject: [PATCH 1376/2366] net: dsa: mt7530: populate lpi_interfaces to fix EEE support phylink_create() decides once and for all that a MAC supports managed EEE, and it requires the tx_lpi ops plus non-empty lpi_capabilities and lpi_interfaces. mt753x_phylink_get_caps() leaves lpi_interfaces empty. So ever since the conversion to phylink managed EEE, ethtool has answered "Not supported" on every mt753x port, and phy_disable_eee() has locked userspace out of turning EEE on. That undoes what commit 06dfcd4098cf ("net: dsa: mt7530: fix enabling EEE on MT7531 switch on all boards") arranged: EEE off by default, but reachable with ethtool. Leave the speeds above 1 Gbps out of both bitmaps. PMCR folds SPEED_2500 and SPEED_10000 onto PMCR_FORCE_SPEED_1000, so PMCR_FORCE_EEE1G would govern LPI on such a link, and that is unvalidated rather than known unsupported: MediaTek's SDK driver sets the EEE force bits for 100 Mbps and 1 Gbps only, and the unit of the wakeup timers is undocumented with the port clock at 2.5 times the rate. LPI stays off until userspace enables it, but the EEE advertisement of a PHY that advertises it out of reset comes back, since phylink stops force-clearing it. Fixes: 9cf21773f535 ("net: dsa: mt7530: convert to phylink managed EEE") Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260903123644.23800-2-f@lex.la Signed-off-by: Paolo Abeni --- drivers/net/dsa/mt7530.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 2b7be091c056..3e61eb3c2b1e 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -3172,23 +3172,31 @@ static void mt753x_phylink_get_caps(struct dsa_switch *ds, int port, config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE; + priv->info->mac_port_get_caps(ds, port, config); + /* The EN7528 GPHYs report EEE capability, but negotiating EEE with * common link partners (e.g. Realtek GbE NICs) results in an unstable * link with dropped frames. Leave the LPI capabilities empty so that * phylink disables EEE on these PHYs and refuses to enable it from * userspace. */ - if (priv->id != ID_EN7528) { + if (priv->id != ID_EN7528 && + config->mac_capabilities & (MAC_100FD | MAC_1000FD)) { u32 eeecr = mt7530_read(priv, MT753X_PMEEECR_P(port)); - config->lpi_capabilities = MAC_100FD | MAC_1000FD | MAC_2500FD; + /* LPI above 1 Gbps is not supported */ + config->lpi_capabilities = config->mac_capabilities & + (MAC_100FD | MAC_1000FD); + phy_interface_copy(config->lpi_interfaces, + config->supported_interfaces); + __clear_bit(PHY_INTERFACE_MODE_2500BASEX, + config->lpi_interfaces); + /* tx_lpi_timer should be in microseconds. The time units for * LPI threshold are unspecified. */ config->lpi_timer_default = FIELD_GET(LPI_THRESH_MASK, eeecr); } - - priv->info->mac_port_get_caps(ds, port, config); } static int mt753x_pcs_validate(struct phylink_pcs *pcs, From d876c9cb2d16ed259449fe9da08c37a5cb81d724 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 3 Sep 2026 12:36:44 +0000 Subject: [PATCH 1377/2366] net: ethernet: mtk_eth_soc: populate lpi_interfaces to fix EEE support phylink_create() decides once and for all that a MAC supports managed EEE, and it requires the tx_lpi ops plus non-empty lpi_capabilities and lpi_interfaces. mtk_add_mac() leaves lpi_interfaces empty. So ever since EEE support was added, ethtool has answered "Not supported" on every MAC that uses mtk_phylink_ops, and phy_disable_eee() has locked userspace out of turning EEE on. MT7628 is unaffected, as rt5350_phylink_ops has no tx_lpi methods. Leave 2.5 Gbps out of both bitmaps, and the xGMII modes that mtk_mac_enable_tx_lpi() already refuses. MAC_MCR folds SPEED_2500 onto MAC_MCR_SPEED_1000, so MAC_MCR_EEE1G would govern LPI on such a link, and that is unvalidated rather than known unsupported: MediaTek's SDK driver sets the EEE force bits for 100 Mbps and 1 Gbps only, and the unit of the wakeup timers is undocumented with the port clock at 2.5 times the rate. mtk_mac_enable_tx_lpi() programs wake-up times taken from MT7531's reset values, and the SoC's own field has no reset value to fall back on. Only MT7981 has been seen to exit LPI cleanly with them, so the LPI interfaces sit behind a new MTK_GMAC_EEE capability that only MT7981 sets; every other SoC keeps the current behaviour until it has been confirmed. LPI stays off until userspace enables it, but the EEE advertisement of a PHY that advertises it out of reset comes back, since phylink stops force-clearing it. Fixes: 952d7325362f ("net: ethernet: mediatek: add EEE support") Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260903123644.23800-3-f@lex.la Signed-off-by: Paolo Abeni --- drivers/net/ethernet/mediatek/mtk_eth_soc.c | 18 +++++++++++++++--- drivers/net/ethernet/mediatek/mtk_eth_soc.h | 4 +++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c index be3bd025c41a..fd7a49ae88d0 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -4828,7 +4828,7 @@ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np) phy_interface_t phy_mode; struct phylink *phylink; struct mtk_mac *mac; - int id, err; + int id, err, i; int txqs = 1; u32 val; @@ -4907,8 +4907,8 @@ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np) mac->phylink_config.type = PHYLINK_NETDEV; mac->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD; - mac->phylink_config.lpi_capabilities = MAC_100FD | MAC_1000FD | - MAC_2500FD; + /* LPI above 1 Gbps is not supported */ + mac->phylink_config.lpi_capabilities = MAC_100FD | MAC_1000FD; mac->phylink_config.lpi_timer_default = 1000; /* MT7623 gmac0 is now missing its speed-specific PLL configuration @@ -4966,6 +4966,18 @@ static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np) __set_bit(PHY_INTERFACE_MODE_INTERNAL, mac->phylink_config.supported_interfaces); + /* LPI wake-up timing is only verified on MTK_GMAC_EEE SoCs */ + if (MTK_HAS_CAPS(eth->soc->caps, MTK_GMAC_EEE)) { + phy_interface_copy(mac->phylink_config.lpi_interfaces, + mac->phylink_config.supported_interfaces); + __clear_bit(PHY_INTERFACE_MODE_2500BASEX, + mac->phylink_config.lpi_interfaces); + for (i = 0; i < PHY_INTERFACE_MODE_MAX; i++) + if (mtk_interface_mode_is_xgmii(eth, i)) + __clear_bit(i, + mac->phylink_config.lpi_interfaces); + } + phylink = phylink_create(&mac->phylink_config, of_fwnode_handle(mac->of_node), phy_mode, mac_ops); diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/drivers/net/ethernet/mediatek/mtk_eth_soc.h index 0168e2fbc619..88a9b3b23bea 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h @@ -994,6 +994,7 @@ enum mkt_eth_capabilities { MTK_U3_COPHY_V2_BIT, MTK_SRAM_BIT, MTK_36BIT_DMA_BIT, + MTK_GMAC_EEE_BIT, /* MUX BITS*/ MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT, @@ -1034,6 +1035,7 @@ enum mkt_eth_capabilities { #define MTK_U3_COPHY_V2 BIT_ULL(MTK_U3_COPHY_V2_BIT) #define MTK_SRAM BIT_ULL(MTK_SRAM_BIT) #define MTK_36BIT_DMA BIT_ULL(MTK_36BIT_DMA_BIT) +#define MTK_GMAC_EEE BIT_ULL(MTK_GMAC_EEE_BIT) #define MTK_ETH_MUX_GDM1_TO_GMAC1_ESW \ BIT_ULL(MTK_ETH_MUX_GDM1_TO_GMAC1_ESW_BIT) @@ -1117,7 +1119,7 @@ enum mkt_eth_capabilities { #define MT7981_CAPS (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | MTK_GMAC2_GEPHY | \ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \ MTK_MUX_U3_GMAC2_TO_QPHY | MTK_U3_COPHY_V2 | \ - MTK_RSTCTRL_PPE1 | MTK_SRAM) + MTK_RSTCTRL_PPE1 | MTK_SRAM | MTK_GMAC_EEE) #define MT7986_CAPS (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | \ MTK_MUX_GMAC12_TO_GEPHY_SGMII | MTK_QDMA | \ From 9c182bc5d7817437a7d04ab96133f9191846d93d Mon Sep 17 00:00:00 2001 From: Florian Maillard Date: Mon, 24 Aug 2026 08:57:55 +0200 Subject: [PATCH 1378/2366] mmc: rtsx_pci_sdmmc: ignore broken write-protect on ThinkPad X260 The Realtek RTS522A card reader in the Lenovo ThinkPad X260 (subsystem 17aa:504a) incorrectly reports inserted SD cards as write-protected. This causes the MMC core to expose the card as read-only: mmcblk0: mmc0:aaaa SN256 238 GiB (ro) and /sys/block/mmcblk0/ro reports 1. Setting MMC_CAP2_NO_WRITE_PROTECT makes the card writable again. Limit the quirk to the affected Lenovo subsystem. Assisted-by: ChatGPT:GPT-5.6 Sol Signed-off-by: Florian Maillard Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/rtsx_pci_sdmmc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index 8dfbc62f165b..d25d4cb59bc9 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -1425,6 +1425,11 @@ static void realtek_init_host(struct realtek_pci_sdmmc *host) mmc->caps = mmc->caps | MMC_CAP_AGGRESSIVE_PM; mmc->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE | MMC_CAP2_NO_SDIO; + + if (pcr->pci->device == 0x522a && + pcr->pci->subsystem_vendor == PCI_VENDOR_ID_LENOVO && + pcr->pci->subsystem_device == 0x504a) + mmc->caps2 |= MMC_CAP2_NO_WRITE_PROTECT; mmc->max_current_330 = 400; mmc->max_current_180 = 800; mmc->ops = &realtek_pci_sdmmc_ops; From 08b54e16d547d5c1aa61bf7a3595bb1620975eeb Mon Sep 17 00:00:00 2001 From: Zhu Ling Date: Fri, 4 Sep 2026 17:07:46 +0800 Subject: [PATCH 1379/2366] mmc: core: Fix OF node reference leak on card add failure mmc_of_find_child_device() returns a device node with its reference count incremented. mmc_add_card() stores the reference before calling device_add(), while the card is marked present only after device_add() succeeds. If device_add() fails, the callers release the card through mmc_remove_card(). However, mmc_remove_card() only drops the OF node reference for a present card, leaking the reference on this error path. Move of_node_put() outside the present-card conditional so the reference is released for both registered cards and card-add failures. Fixes: 25185f3f31c9 ("mmc: Add SDIO function devicetree subnode parsing") Cc: stable@vger.kernel.org Signed-off-by: Zhu Ling Reviewed-by: Shawn Lin Signed-off-by: Ulf Hansson --- drivers/mmc/core/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index be5cf338bdeb..92ad39c0cabc 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -417,8 +417,8 @@ void mmc_remove_card(struct mmc_card *card) mmc_hostname(card->host), card->rca); } device_del(&card->dev); - of_node_put(card->dev.of_node); } + of_node_put(card->dev.of_node); if (host->cqe_enabled) { host->cqe_ops->cqe_disable(host); From cee9c863ee68cb27d66745eb03f60e357f4f8ad2 Mon Sep 17 00:00:00 2001 From: Alex Bereza Date: Mon, 17 Aug 2026 11:23:55 +0200 Subject: [PATCH 1380/2366] dmaengine: xilinx_dma: Fix hardware buffer descriptor reuse order xilinx_dma_alloc_chan_resources() builds a static ring of hardware buffer descriptors once and the driver uses this ring throughout the lifetime of a channel. This requires the allocation order of hardware buffer descriptors from chan->free_seg_list to stay in sync with the hardware buffer descriptor ring built at channel allocation time by returning oldest descriptors to chan->free_seg_list first. When chan->pending_list is not empty e.g. during xilinx_dma_terminate_all() the chan->free_seg_list and the order of the static hardware buffer descriptor ring get out of sync. Descriptors age in this order: pending -> active -> done. So freeing pending_list first returns the newest buffer descriptors to the chan->free_seg_list first and thus breaks the order required by the static hardware buffer descriptor ring. Then when the channel is reused, after a wrap around of the free_seg_list the DMA will find a hardware buffer descriptor with a length field that is still zeroed and stop with something like this: xilinx-vdma 86000000.dma: Channel 000000003a21d7b8 has errors 10, cdr 6de4c000 tdr 6de4c000 After this no more descriptors are completed and a consumer potentially blocks and waits forever. The only way to get out of this error state is to rebuild the static hardware buffer descriptor ring and the free_seg_list by releasing and re-acquiring the channel. Fix the order in which hardware buffer descriptors are returned to free_seg_list to ensure the mentioned requirement holds. Fixes: 23059408b6a3 ("dmaengine: xilinx_dma: Fix race condition in the driver for multiple descriptor scenario") Signed-off-by: Alex Bereza Reviewed-by: Frank Li Reviewed-by: Suraj Gupta Link: https://patch.msgid.link/20260817-fix-hw-buf-desc-reuse-v1-1-d79827a844c7@bereza.email Signed-off-by: Vinod Koul --- drivers/dma/xilinx/xilinx_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c index bef2b031dba1..0817b74f7450 100644 --- a/drivers/dma/xilinx/xilinx_dma.c +++ b/drivers/dma/xilinx/xilinx_dma.c @@ -920,9 +920,9 @@ static void xilinx_dma_free_descriptors(struct xilinx_dma_chan *chan) spin_lock_irqsave(&chan->lock, flags); - xilinx_dma_free_desc_list(chan, &chan->pending_list); xilinx_dma_free_desc_list(chan, &chan->done_list); xilinx_dma_free_desc_list(chan, &chan->active_list); + xilinx_dma_free_desc_list(chan, &chan->pending_list); spin_unlock_irqrestore(&chan->lock, flags); } From c90b6973daa37f4c283342dff881ae001dea4fe6 Mon Sep 17 00:00:00 2001 From: Christian Lugnberg Date: Mon, 17 Aug 2026 15:51:22 +0200 Subject: [PATCH 1381/2366] dmaengine: sun6i: fix non-atomic read of DMA position registers sun6i_get_chan_size() reads DMA_CHAN_LLI_ADDR and DMA_CHAN_CUR_CNT in two separate readl() calls with no synchronisation between them: pos = readl(pchan->base + DMA_CHAN_LLI_ADDR); bytes = readl(pchan->base + DMA_CHAN_CUR_CNT); DMA_CHAN_LLI_ADDR holds the physical address of the *next* descriptor the engine will load once the current one completes. DMA_CHAN_CUR_CNT holds the remaining byte count for the *current* descriptor. If the DMA engine advances to the next LLI entry between the two reads, pos becomes stale: it still points to what was the next descriptor at the time of the first read, but that descriptor is now the current one and CUR_CNT reflects its initial (full) byte count. The subsequent virtual-chain walk starts one entry too early and accumulates an extra full period's worth of bytes into the residue estimate. Fix this by re-reading DMA_CHAN_LLI_ADDR after DMA_CHAN_CUR_CNT and retrying if the value changed. This double-read pattern guarantees that both registers were sampled during the same descriptor interval. The cost is at most one extra readl() pair per call in the racy case, which occurs only at descriptor boundaries (~every 2 ms) and is negligible. Fixes: a90e173f3faf ("dmaengine: sun6i: Add cyclic capability") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Christian Lugnberg Reviewed-by: Frank Li Link: https://patch.msgid.link/20260817135723.12807-2-christian.lugnberg@soundtrack.io Signed-off-by: Vinod Koul --- drivers/dma/sun6i-dma.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c index f47a326dd7ff..04fe1f5042e9 100644 --- a/drivers/dma/sun6i-dma.c +++ b/drivers/dma/sun6i-dma.c @@ -354,8 +354,10 @@ static size_t sun6i_get_chan_size(struct sun6i_pchan *pchan) size_t bytes; dma_addr_t pos; - pos = readl(pchan->base + DMA_CHAN_LLI_ADDR); - bytes = readl(pchan->base + DMA_CHAN_CUR_CNT); + do { + pos = readl(pchan->base + DMA_CHAN_LLI_ADDR); + bytes = readl(pchan->base + DMA_CHAN_CUR_CNT); + } while (pos != readl(pchan->base + DMA_CHAN_LLI_ADDR)); if (pos == LLI_LAST_ITEM) return bytes; From 9096bdc8d930147f7c39a493a859acbd3a8485d8 Mon Sep 17 00:00:00 2001 From: Christian Lugnberg Date: Mon, 17 Aug 2026 15:51:23 +0200 Subject: [PATCH 1382/2366] dmaengine: sun6i: fix undefined behaviour in sun6i_dma_tx_status sun6i_dma_tx_status() calls vchan_find_desc() to look up the virtual descriptor for a given cookie, before checking whether the pointer vd is NULL: vd = vchan_find_desc(&vchan->vc, cookie); txd = to_sun6i_desc(&vd->tx); /* vd may be NULL here */ if (vd) { for (lli = txd->v_lli; ...) vchan_find_desc() returns NULL when the descriptor has already been completed or is in-flight on a physical channel and no longer present in the virtual channel's descriptor list. When vd is NULL, to_sun6i_desc() is called unconditionally on &vd->tx before the NULL check, which is undefined behaviour. Move the call inside the if (vd) guard to ensure it is only reached with a valid pointer. vd = vchan_find_desc(&vchan->vc, cookie); if (vd) { struct sun6i_desc *txd = to_sun6i_desc(&vd->tx); for (lli = txd->v_lli; ...) Fixes: 555859308723 ("dmaengine: sun6i: Add driver for the Allwinner A31 DMA controller") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Christian Lugnberg Reviewed-by: Frank Li Link: https://patch.msgid.link/20260817135723.12807-3-christian.lugnberg@soundtrack.io Signed-off-by: Vinod Koul --- drivers/dma/sun6i-dma.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c index 04fe1f5042e9..7704b016aed8 100644 --- a/drivers/dma/sun6i-dma.c +++ b/drivers/dma/sun6i-dma.c @@ -981,7 +981,6 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan, struct sun6i_pchan *pchan = vchan->phy; struct sun6i_dma_lli *lli; struct virt_dma_desc *vd; - struct sun6i_desc *txd; enum dma_status ret; unsigned long flags; size_t bytes = 0; @@ -993,9 +992,9 @@ static enum dma_status sun6i_dma_tx_status(struct dma_chan *chan, spin_lock_irqsave(&vchan->vc.lock, flags); vd = vchan_find_desc(&vchan->vc, cookie); - txd = to_sun6i_desc(&vd->tx); if (vd) { + struct sun6i_desc *txd = to_sun6i_desc(&vd->tx); for (lli = txd->v_lli; lli != NULL; lli = lli->v_lli_next) bytes += lli->len; } else if (!pchan || !pchan->desc) { From f6504be006aa4bb4bd26285f410a885c17920d65 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Mon, 17 Aug 2026 22:44:33 +0200 Subject: [PATCH 1383/2366] dmaengine: pxa: fix double counting of the hw descriptors pxad_alloc_desc() was converted from kzalloc(struct_size(sw_desc, hw_desc, nb_hw_desc), GFP_NOWAIT) to kzalloc_flex(), which sets the __counted_by() counter sw_desc->nb_desc itself - but only where the compiler has __builtin_counted_by_ref(), so from gcc 15.1 or clang 22.1 on. The loop below it still increments nb_desc, which makes it come out doubled there and correct elsewhere. nb_desc is what pxad_free_desc() iterates over and what set_updater_desc() indexes from, so set it explicitly and drop the increment. The error path has to lower it to the number of descriptors allocated so far, otherwise pxad_free_desc() would free entries that were never allocated. Fixes: 69050f8d6d075 ("treewide: Replace kmalloc with kmalloc_obj for non-scalar types") Assisted-by: Claude:claude-opus-5 Signed-off-by: Sascha Hauer Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20260817-dmaengine-pxa-v1-1-850c215c1196@pengutronix.de Link: https://patch.msgid.link/20260817-dmaengine-pxa-v2-1-f42ab0569a48@pengutronix.de Signed-off-by: Vinod Koul --- drivers/dma/pxa_dma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/dma/pxa_dma.c b/drivers/dma/pxa_dma.c index fa2ee0b3e09f..fc43124fefa8 100644 --- a/drivers/dma/pxa_dma.c +++ b/drivers/dma/pxa_dma.c @@ -744,6 +744,7 @@ pxad_alloc_desc(struct pxad_chan *chan, unsigned int nb_hw_desc) sw_desc = kzalloc_flex(*sw_desc, hw_desc, nb_hw_desc, GFP_NOWAIT); if (!sw_desc) return NULL; + sw_desc->nb_desc = nb_hw_desc; sw_desc->desc_pool = chan->desc_pool; for (i = 0; i < nb_hw_desc; i++) { @@ -752,10 +753,10 @@ pxad_alloc_desc(struct pxad_chan *chan, unsigned int nb_hw_desc) dev_err(&chan->vc.chan.dev->device, "%s(): Couldn't allocate the %dth hw_desc from dma_pool %p\n", __func__, i, sw_desc->desc_pool); + sw_desc->nb_desc = i; goto err; } - sw_desc->nb_desc++; sw_desc->hw_desc[i] = desc; if (i == 0) From 125755776bc6d4dd53eaf551c87e3d460625d638 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Fri, 4 Sep 2026 14:43:07 +0100 Subject: [PATCH 1384/2366] tcp: reject non zerocopy devmem tx Devmem tcp tx doesn't work without zero-copy, however it's not currently enforced if NETIF_F_SG isn't present. In this case, tcp_sendmsg_locked() will try the copy path and try to copy data from an iovec which consists of offsets into the dma-buf and would normally fail. Moreover, d9c56501c72fd ("net: tcp: block mixing readable and unreadable frags") relies on that and assumes that the devmem binding is present IFF we're using the zero-copy path, which can be used to mix net-iov and pages in a single skb, and break invariants. Let's reject devmem tx without zero-copy. Note, the parameter check the patch is modifying is too loose, we can create an io_uring request with dmabuf_id and all ZC flags, but which won't have the binding. We replace it with stricter validation. Fixes: bd61848900bff ("net: devmem: Implement TX path") Fixes: d9c56501c72fd ("net: tcp: block mixing readable and unreadable frags") Signed-off-by: Pavel Begunkov Reviewed-by: Mina Almasry Link: https://patch.msgid.link/fdc2478d8f21268d7078556409887d8e6ba0ad32.1788529053.git.asml.silence@gmail.com Signed-off-by: Paolo Abeni --- net/ipv4/tcp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 1c867a302444..562752352afe 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -1169,8 +1169,7 @@ int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size) zc = MSG_SPLICE_PAGES; } - if (!sockc_err && sockc.dmabuf_id && - (!(flags & MSG_ZEROCOPY) || !sock_flag(sk, SOCK_ZEROCOPY))) { + if (!sockc_err && sockc.dmabuf_id && (zc != MSG_ZEROCOPY || !binding)) { err = -EINVAL; goto out_err; } From ccbe7540e4aad0d1c3acc249697350b93ccb8025 Mon Sep 17 00:00:00 2001 From: Vladislav Karmanov Date: Tue, 8 Sep 2026 17:52:13 +0300 Subject: [PATCH 1385/2366] net: phy: mediatek-ge: disable EEE on the MT7530 PHY The MT7530 internal GE PHY advertises EEE by hardware default, but its EEE support is defective: with EEE advertised, some link partners fail to establish a stable link. On a 2-pair (4-wire) cable where both ends advertise gigabit, 1000BASE-T training cannot succeed, and instead of falling back to 100 Mbps the port loops, so no link or DHCP lease is ever obtained. MediaTek confirms the hardware is the root cause (Landen Chao, 2021): "EEE of the 10-year-old MT7530 internal gephy has many IOT problems, so it is recommended to disable its EEE." mtk_gephy_config_init() used to clear the EEE advertisement early, but commit af3b4b0e59de ("net: phy: mediatek-ge: do not disable EEE advertisement") removed that on the rationale that the DSA subdriver already performs an early disable. That holds for MT7531, whose mt7531_setup() clears MDIO_AN_EEE_ADV on each switch PHY, but not for the MT7530 PHY: neither the MT7621 integrated switch nor the dedicated MT7530 IC ever had such a loop, so removing it left those boards without any working early EEE disable and the link flapping came back. Since the broken hardware is the PHY, fix it in the PHY driver so it covers all users of this PHY, integrated in a switch or standalone: - clear MDIO_AN_EEE_ADV in probe(), as early as possible, before anything can negotiate EEE with the link partner; - clear it again in config_init() and call phy_disable_eee() there. config_init() is what phy_init_hw() replays after a PHY reset, when the register is back at its EEE-advertising hardware default, and it runs after of_set_phy_eee_broken() in phy_probe(), so the eee_disabled_modes mask survives and neither phylib nor userspace can re-enable EEE. dp83867 disables broken EEE from config_init() the same way. Auto-negotiation then falls back to a stable 100 Mbps link instead of looping at gigabit. Tested on ASUS RT-AX53U (MT7621): with a 2-pair cable on the WAN port, a single clean 100 Mbps link comes up and a DHCP lease is obtained, where the unpatched driver loops. Fixes: af3b4b0e59de ("net: phy: mediatek-ge: do not disable EEE advertisement") Suggested-by: Andrew Lunn Signed-off-by: Vladislav Karmanov Link: https://patch.msgid.link/20260908145213.3976508-1-vladislav.karmanov.dev@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/phy/mediatek/mtk-ge.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/net/phy/mediatek/mtk-ge.c b/drivers/net/phy/mediatek/mtk-ge.c index 73d9b72f9d9e..96d8ac5154e5 100644 --- a/drivers/net/phy/mediatek/mtk-ge.c +++ b/drivers/net/phy/mediatek/mtk-ge.c @@ -62,10 +62,38 @@ static void mtk_gephy_config_init(struct phy_device *phydev) FIELD_PREP(MTK_MCC_NEARECHO_OFFSET_MASK, 0x3)); } +static int mt7530_phy_probe(struct phy_device *phydev) +{ + /* The MT7530 internal GE PHY has broken EEE: with EEE advertised, + * some link partners fail to establish a stable link (on a 2-pair + * cable, 1000BASE-T training fails and the port loops instead of + * falling back). MediaTek recommends disabling EEE on this PHY. + * Clear the advertisement as early as possible, before anything + * can negotiate EEE with the link partner. + */ + return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0); +} + static int mt7530_phy_config_init(struct phy_device *phydev) { + int ret; + mtk_gephy_config_init(phydev); + /* The probe() clear alone is not durable: phy_init_hw() replays only + * ->config_init after a PHY reset, with the register back at its + * EEE-advertising hardware default, and phy_probe() zeroes + * eee_disabled_modes (of_set_phy_eee_broken()) after ->probe already + * ran. Clear the advertisement again and mark EEE disabled, so that + * neither phylib nor userspace can re-enable it; dp83867 disables + * broken EEE from config_init() the same way. + */ + ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0); + if (ret) + return ret; + + phy_disable_eee(phydev); + /* Increase post_update_timer */ phy_write_paged(phydev, MTK_PHY_PAGE_EXTENDED_3, MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG11, 0x4b); @@ -100,6 +128,7 @@ static struct phy_driver mtk_gephy_driver[] = { { PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7530), .name = "MediaTek MT7530 PHY", + .probe = mt7530_phy_probe, .config_init = mt7530_phy_config_init, /* Interrupts are handled by the switch, not the PHY * itself. From 7ed1e3070c9b4bbd67d5519e14711038dd53ab13 Mon Sep 17 00:00:00 2001 From: Alex Bereza Date: Tue, 18 Aug 2026 09:36:29 +0200 Subject: [PATCH 1386/2366] dmaengine: xilinx_dma: Fix hardware buffer descriptor chain after cyclic DMA Using the DMA in cyclic mode modifies the hardware buffer descriptor chain in xilinx_dma_prep_dma_cyclic so that the last descriptor used by the cyclic transfer points back to the first descriptor, but it never restores the original descriptor ring. This breaks using non-cyclic mode after cyclic mode with an error like: xilinx-vdma 86000000.dma: Channel 00000000354d5c8d has errors 100, cdr 6de40000 tdr 6de40400 The only way to get out of this error state is to rebuild the hardware buffer descriptor ring by releasing and re-acquiring the channel. Fix using non-cyclic mode after cyclic mode by always restoring the original buffer descriptor ring in the same manner as it is set up by xilinx_dma_alloc_chan_resources(). Fixes: 23059408b6a3 ("dmaengine: xilinx_dma: Fix race condition in the driver for multiple descriptor scenario") Signed-off-by: Alex Bereza Reviewed-by: Frank Li Reviewed-by: Suraj Gupta Link: https://patch.msgid.link/20260817-fix-hw-buf-desc-after-cyclic-mode-v1-1-1fe47e701d6c@bereza.email Link: https://patch.msgid.link/20260818-fix-hw-buf-desc-after-cyclic-mode-v2-1-530ff44c6a81@bereza.email Signed-off-by: Vinod Koul --- drivers/dma/xilinx/xilinx_dma.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c index 0817b74f7450..cffe7c6fa640 100644 --- a/drivers/dma/xilinx/xilinx_dma.c +++ b/drivers/dma/xilinx/xilinx_dma.c @@ -756,15 +756,25 @@ xilinx_aximcdma_alloc_tx_segment(struct xilinx_dma_chan *chan) return segment; } -static void xilinx_dma_clean_hw_desc(struct xilinx_axidma_desc_hw *hw) +static void xilinx_dma_clean_hw_desc(struct xilinx_dma_chan *chan, + struct xilinx_axidma_tx_segment *segment) { - u32 next_desc = hw->next_desc; - u32 next_desc_msb = hw->next_desc_msb; + dma_addr_t next; + u32 i; - memset(hw, 0, sizeof(struct xilinx_axidma_desc_hw)); + /* + * Restore the buffer descriptor's next descriptor pointer to the value + * set up in xilinx_dma_alloc_chan_resources(). Otherwise using the DMA + * in cyclic mode leaves the next descriptor pointer altered and + * prevents subsequent non-cyclic transfers. + */ + i = segment - chan->seg_v; + next = chan->seg_p + + sizeof(*chan->seg_v) * ((i + 1) % XILINX_DMA_NUM_DESCS); - hw->next_desc = next_desc; - hw->next_desc_msb = next_desc_msb; + memset(&segment->hw, 0, sizeof(segment->hw)); + segment->hw.next_desc = lower_32_bits(next); + segment->hw.next_desc_msb = upper_32_bits(next); } static void xilinx_mcdma_clean_hw_desc(struct xilinx_aximcdma_desc_hw *hw) @@ -786,7 +796,7 @@ static void xilinx_mcdma_clean_hw_desc(struct xilinx_aximcdma_desc_hw *hw) static void xilinx_dma_free_tx_segment(struct xilinx_dma_chan *chan, struct xilinx_axidma_tx_segment *segment) { - xilinx_dma_clean_hw_desc(&segment->hw); + xilinx_dma_clean_hw_desc(chan, segment); list_add_tail(&segment->node, &chan->free_seg_list); } From 985a663bf00799c1daf1c5789efa6406958780c8 Mon Sep 17 00:00:00 2001 From: Faicker Mo Date: Tue, 8 Sep 2026 12:06:29 +0800 Subject: [PATCH 1387/2366] net: net_failover: Fix the deadlock in net_failover_slave_name_change() This is a sibling fix of commit b84c5632c7b3 ("net: net_failover: Fix the deadlock in slave register"). There is netdev_lock_ops() in the upper callers, so using netif_open() instead of dev_open(). Call Trace: __schedule+0x2bb/0x650 schedule+0x27/0xb0 schedule_preempt_disabled+0x15/0x30 __mutex_lock.constprop.0+0x550/0xaf0 __mutex_lock_slowpath+0x13/0x20 mutex_lock+0x3b/0x50 dev_open+0x3b/0xe0 net_failover_slave_name_change+0x22/0x40 failover_event+0xd4/0x1e0 notifier_call_chain+0x62/0xf0 raw_notifier_call_chain+0x16/0x30 call_netdevice_notifiers_info+0x50/0x80 netif_change_name+0x200/0x330 do_setlink.isra.0+0xb12/0xdf0 ? security_capable+0x9a/0x1e0 ? ns_capable+0x31/0x60 rtnl_setlink+0x302/0x670 ? netlink_recvmsg+0x296/0x340 ? security_capable+0x9a/0x1e0 ? __pfx_rtnl_setlink+0x10/0x10 rtnetlink_rcv_msg+0x384/0x460 ? __pfx_rtnetlink_rcv_msg+0x10/0x10 netlink_rcv_skb+0x61/0x120 rtnetlink_rcv+0x15/0x30 netlink_unicast+0x28f/0x3c0 netlink_sendmsg+0x216/0x450 __sys_sendto+0x222/0x230 __x64_sys_sendto+0x24/0x40 x64_sys_call+0x1d5d/0x2390 do_syscall_64+0x105/0x5a0 ? do_syscall_64+0x140/0x5a0 ? exc_page_fault+0x94/0x1e0 entry_SYSCALL_64_after_hwframe+0x76/0x7e Fixes: 7e4d784f5810 ("net: hold netdev instance lock during rtnetlink operations") Signed-off-by: Faicker Mo Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260908040708.3972058-1-faicker.mo@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/net_failover.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/net_failover.c b/drivers/net/net_failover.c index 3f7d31033bae..1b5213e87070 100644 --- a/drivers/net/net_failover.c +++ b/drivers/net/net_failover.c @@ -675,7 +675,7 @@ static int net_failover_slave_name_change(struct net_device *slave_dev, /* We need to bring up the slave after the rename by udev in case * open failed with EBUSY when it was registered. */ - dev_open(slave_dev, NULL); + netif_open(slave_dev, NULL); return 0; } From e08aca85c02ff290f785f07acae758f0daf5f49e Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Thu, 10 Sep 2026 09:35:13 -0400 Subject: [PATCH 1388/2366] RDMA/efa: Keep admin queues alive while IRQ is registered The management IRQ handler accesses both the admin completion queue and the async event queue. The driver registered the IRQ before constructing these queues and destroyed them before freeing the IRQ, so the handler's lifetime was not contained by the resources it accesses. Initialize the queues with interrupts masked, request the IRQ, and then switch to interrupt mode. On removal, reset the device and free the IRQ before destroying the queues. Also reset the device before destroying the queues if IRQ registration fails, because the device already has their DMA addresses. Fixes: b7f5e880f377 ("RDMA/efa: Add the efa module") Link: https://patch.msgid.link/20260907-use-after-free-of-admin-queue-struct-v1-1-dd9d9267fbf4@nvidia.com Reviewed-by: Michael Margolin Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/efa/efa_com.c | 4 +--- drivers/infiniband/hw/efa/efa_main.c | 15 +++++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/infiniband/hw/efa/efa_com.c b/drivers/infiniband/hw/efa/efa_com.c index 583b1cf0d721..cb2d448db2d7 100644 --- a/drivers/infiniband/hw/efa/efa_com.c +++ b/drivers/infiniband/hw/efa/efa_com.c @@ -850,7 +850,7 @@ int efa_com_admin_init(struct efa_com_dev *edev, aq->dmadev = edev->dmadev; aq->efa_dev = edev->efa_dev; - set_bit(EFA_AQ_STATE_POLLING_BIT, &aq->state); + efa_com_set_admin_polling_mode(edev, true); sema_init(&aq->avail_cmds, aq->depth); @@ -868,8 +868,6 @@ int efa_com_admin_init(struct efa_com_dev *edev, if (err) goto err_destroy_sq; - efa_com_set_admin_polling_mode(edev, false); - err = efa_com_admin_init_aenq(edev, aenq_handlers); if (err) goto err_destroy_cq; diff --git a/drivers/infiniband/hw/efa/efa_main.c b/drivers/infiniband/hw/efa/efa_main.c index 4cd80727dcd2..b1df9bb8fac2 100644 --- a/drivers/infiniband/hw/efa/efa_main.c +++ b/drivers/infiniband/hw/efa/efa_main.c @@ -619,18 +619,21 @@ static struct efa_dev *efa_probe_device(struct pci_dev *pdev) edev->aq.msix_vector_idx = dev->admin_msix_vector_idx; edev->aenq.msix_vector_idx = dev->admin_msix_vector_idx; - err = efa_set_mgmnt_irq(dev); + err = efa_com_admin_init(edev, &aenq_handlers); if (err) goto err_disable_msix; - err = efa_com_admin_init(edev, &aenq_handlers); + err = efa_set_mgmnt_irq(dev); if (err) - goto err_free_mgmnt_irq; + goto err_destroy_admin; + + efa_com_set_admin_polling_mode(edev, false); return dev; -err_free_mgmnt_irq: - efa_free_irq(dev, &dev->admin_irq); +err_destroy_admin: + efa_com_dev_reset(edev, EFA_REGS_RESET_INIT_ERR); + efa_com_admin_destroy(edev); err_disable_msix: efa_disable_msix(dev); err_reg_read_destroy: @@ -654,8 +657,8 @@ static void efa_remove_device(struct pci_dev *pdev, edev = &dev->edev; efa_com_dev_reset(edev, reset_reason); - efa_com_admin_destroy(edev); efa_free_irq(dev, &dev->admin_irq); + efa_com_admin_destroy(edev); efa_disable_msix(dev); efa_com_mmio_reg_read_destroy(edev); devm_iounmap(&pdev->dev, edev->reg_bar); From e22a3627b7151754f07f90ea3d1ab6e85f5d93f4 Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Thu, 10 Sep 2026 09:35:13 -0400 Subject: [PATCH 1389/2366] RDMA/efa: Keep EQ resources alive while IRQ is registered The completion IRQ handler accesses the EQ state and DMA buffer. Its IRQ was registered before that state was initialized, while teardown released the buffer before free_irq() synchronized the handler. Initialize the EQ without arming it, register the IRQ, and then arm it. Reverse the resource order during teardown by freeing the IRQ before destroying the EQ. Fixes: 2a152512a155 ("RDMA/efa: CQ notifications") Link: https://patch.msgid.link/20260907-use-after-free-of-admin-queue-struct-v1-2-dd9d9267fbf4@nvidia.com Reviewed-by: Michael Margolin Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/efa/efa_com.c | 3 +-- drivers/infiniband/hw/efa/efa_com.h | 1 + drivers/infiniband/hw/efa/efa_main.c | 20 +++++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/infiniband/hw/efa/efa_com.c b/drivers/infiniband/hw/efa/efa_com.c index cb2d448db2d7..04c6d63c449e 100644 --- a/drivers/infiniband/hw/efa/efa_com.c +++ b/drivers/infiniband/hw/efa/efa_com.c @@ -1252,7 +1252,7 @@ static void efa_com_destroy_eq(struct efa_com_dev *edev, err); } -static void efa_com_arm_eq(struct efa_com_dev *edev, struct efa_com_eq *eeq) +void efa_com_arm_eq(struct efa_com_dev *edev, struct efa_com_eq *eeq) { u32 val = 0; @@ -1341,7 +1341,6 @@ int efa_com_eq_init(struct efa_com_dev *edev, struct efa_com_eq *eeq, eeq->phase = 1; eeq->depth = params.depth; eeq->cb = cb; - efa_com_arm_eq(edev, eeq); return 0; diff --git a/drivers/infiniband/hw/efa/efa_com.h b/drivers/infiniband/hw/efa/efa_com.h index 0341704d0921..98fb6a42a6cb 100644 --- a/drivers/infiniband/hw/efa/efa_com.h +++ b/drivers/infiniband/hw/efa/efa_com.h @@ -169,6 +169,7 @@ int efa_com_admin_init(struct efa_com_dev *edev, void efa_com_admin_destroy(struct efa_com_dev *edev); int efa_com_eq_init(struct efa_com_dev *edev, struct efa_com_eq *eeq, efa_eqe_handler cb, u16 depth, u8 msix_vec); +void efa_com_arm_eq(struct efa_com_dev *edev, struct efa_com_eq *eeq); void efa_com_eq_destroy(struct efa_com_dev *edev, struct efa_com_eq *eeq); int efa_com_dev_reset(struct efa_com_dev *edev, enum efa_regs_reset_reason_types reset_reason); diff --git a/drivers/infiniband/hw/efa/efa_main.c b/drivers/infiniband/hw/efa/efa_main.c index b1df9bb8fac2..753ed8430b69 100644 --- a/drivers/infiniband/hw/efa/efa_main.c +++ b/drivers/infiniband/hw/efa/efa_main.c @@ -302,28 +302,30 @@ static void efa_set_host_info(struct efa_dev *dev) static void efa_destroy_eq(struct efa_dev *dev, struct efa_eq *eq) { - efa_com_eq_destroy(&dev->edev, &eq->eeq); efa_free_irq(dev, &eq->irq); + efa_com_eq_destroy(&dev->edev, &eq->eeq); } static int efa_create_eq(struct efa_dev *dev, struct efa_eq *eq, u32 msix_vec) { int err; - efa_setup_comp_irq(dev, eq, msix_vec); - err = efa_request_irq(dev, &eq->irq); - if (err) - return err; - err = efa_com_eq_init(&dev->edev, &eq->eeq, efa_process_eqe, dev->dev_attr.max_eq_depth, msix_vec); if (err) - goto err_free_comp_irq; + return err; + + efa_setup_comp_irq(dev, eq, msix_vec); + err = efa_request_irq(dev, &eq->irq); + if (err) + goto err_destroy_eq; + + efa_com_arm_eq(&dev->edev, &eq->eeq); return 0; -err_free_comp_irq: - efa_free_irq(dev, &eq->irq); +err_destroy_eq: + efa_com_eq_destroy(&dev->edev, &eq->eeq); return err; } From 3db7d7d583419f7b1f2e141e36418802dbb25cf8 Mon Sep 17 00:00:00 2001 From: Vadim Nikitushkin Date: Wed, 9 Sep 2026 23:50:28 +0300 Subject: [PATCH 1390/2366] drm/ttm: fix swapped-out resources never leaving their bulk_move range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ttm_tt_swapout() returns the number of pages swapped out on success and a negative error code on failure; for a populated ttm it never returns zero. Commit b2ed01e7ad3d ("drm/ttm: Fix ttm_bo_swapout() infinite LRU walk on swapout failure") moved the bulk_move bookkeeping in ttm_bo_swapout_cb() under "if (!ret)", so the ttm_resource_del_bulk_move_unevictable() / ttm_resource_move_to_lru_tail() pair is now skipped on every successful swapout. The equivalent change for the shrinker in commit 1d59f36e95f7 ("drm/ttm: Fix ttm_bo_shrink() infinite LRU walk on backup failure") tests "lret > 0", which is what was intended here as well. Before b2ed01e7ad3d the resource was taken off the bulk_move before the swapout; since then a swapped-out resource stays inside its BO's bulk_move range (and on the manager LRU) although it is unevictable. When it is later freed or the BO leaves the bulk_move (ttm_resource_free(), ttm_bo_set_bulk_move() via amdgpu_vm_bo_del()), ttm_resource_del_bulk_move() skips it because of its !ttm_resource_unevictable() guard, so a range endpoint in pos->first / pos->last is left pointing at freed memory. The next ttm_lru_bulk_move_tail() or ttm_resource_add_bulk_move() on that cursor is a use-after-free, seen as the resv WARN in ttm_lru_bulk_move_add(), "list_del corruption" in ttm_resource_move_to_lru_tail() or a NULL dereference in ttm_resource_manager_next() -- minutes to hours after a hibernation, or at process exit / reboot following one. Samuel Ainsworth's analysis of drm/amd issue 5387 (see Link) identified the dangling cursor; the missing removal at swapout time is the reason it dangles. Testing the condition for success restores the removal. On an AMD Phoenix APU (ASUS UM3406GA, gfx1103) running suspend-then-hibernate on a 7.0.y stable kernel carrying the backport (Ubuntu 7.0.0-31) the bug crashed 5 of 18 hibernation cycles; a function profile of one hibernation showed 336 ttm_tt_swapout() calls and zero ttm_resource_del_bulk_move_unevictable() calls. With this change the removal happens for every swapped-out resource and 12 further cycles were clean. Fixes: b2ed01e7ad3d ("drm/ttm: Fix ttm_bo_swapout() infinite LRU walk on swapout failure") Cc: stable@vger.kernel.org # v7.1+ Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5387 Link: https://lore.kernel.org/dri-devel/CAHYiNPa6aVacJoLOje-qZ1GyYx-9p0tN4NuP8D_eSL+UJeevXw@mail.gmail.com/ Signed-off-by: Vadim Nikitushkin Reviewed-by: Thomas Hellström Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260909205028.13799-1-bub4z0r@gmail.com --- drivers/gpu/drm/ttm/ttm_bo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index ef56c18ded1b..a12af5b38a31 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -532,7 +532,7 @@ static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo, ret = ttm_resource_try_charge(bo, place, &alloc_state->charge_pool, force_space ? &alloc_state->limit_pool : NULL); - if (ret) { + if (ret > 0) { /* * -EAGAIN means the charge failed, which we treat * like an allocation failure. Therefore, return an From 568a1588b906780dc3e9be56a61217afb4f7800e Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:03:02 -0700 Subject: [PATCH 1391/2366] xfs: snapshot scrub stats when rendering them LOLLM complains about concurrency problems in the scrub stats code because xchk_stats_format doesn't synchronize in any way with updates. These stats are only reported through debugfs so I don't think it really matters, but I guess I exist to make bots happy now. Note: We snapshot the entire stats object with a spinlock so that we don't have to worry about users seeing slightly weird numbers (e.g. invocations has incremented but none of the outcomes have been yet) if we race with xchk_stats_merge_one. This isn't a hot path. Cc: stable@vger.kernel.org # v6.6 Fixes: d7a74cad8f4513 ("xfs: track usage statistics of online fsck") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/stats.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/fs/xfs/scrub/stats.c b/fs/xfs/scrub/stats.c index 76f2515188d1..f3f1fbfb6d99 100644 --- a/fs/xfs/scrub/stats.c +++ b/fs/xfs/scrub/stats.c @@ -99,25 +99,31 @@ xchk_stats_format( int ret = 0; for (i = 0; i < XFS_SCRUB_TYPE_NR; i++, css++) { + struct xchk_scrub_stats fss; + if (!name_map[i]) continue; + spin_lock(&css->css_lock); + memcpy(&fss, css, offsetof(struct xchk_scrub_stats, css_lock)); + spin_unlock(&css->css_lock); + ret = scnprintf(buf, remaining, "%s %u %u %u %u %u %u %u %u %u %llu %u %u %llu\n", name_map[i], - (unsigned int)css->invocations, - (unsigned int)css->clean, - (unsigned int)css->corrupt, - (unsigned int)css->preen, - (unsigned int)css->xfail, - (unsigned int)css->xcorrupt, - (unsigned int)css->incomplete, - (unsigned int)css->warning, - (unsigned int)css->retries, - (unsigned long long)css->checktime_us, - (unsigned int)css->repair_invocations, - (unsigned int)css->repair_success, - (unsigned long long)css->repairtime_us); + (unsigned int)fss.invocations, + (unsigned int)fss.clean, + (unsigned int)fss.corrupt, + (unsigned int)fss.preen, + (unsigned int)fss.xfail, + (unsigned int)fss.xcorrupt, + (unsigned int)fss.incomplete, + (unsigned int)fss.warning, + (unsigned int)fss.retries, + (unsigned long long)fss.checktime_us, + (unsigned int)fss.repair_invocations, + (unsigned int)fss.repair_success, + (unsigned long long)fss.repairtime_us); if (ret <= 0) break; From 0ae61c331ec552ad0c278c5c48a1c4ccb90b4bab Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:03:17 -0700 Subject: [PATCH 1392/2366] xfs: report healthy filesystem events in scrub stats LOLLM also notices that I forgot to expose the "clean bill of health" scrub stats. Fix that. Cc: stable@vger.kernel.org # v6.9 Fixes: a1f3e0cca41036 ("xfs: update health status if we get a clean bill of health") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/stats.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/scrub/stats.c b/fs/xfs/scrub/stats.c index f3f1fbfb6d99..da0c05ffe5cd 100644 --- a/fs/xfs/scrub/stats.c +++ b/fs/xfs/scrub/stats.c @@ -84,6 +84,7 @@ static const char *name_map[XFS_SCRUB_TYPE_NR] = { [XFS_SCRUB_TYPE_RGSUPER] = "rgsuper", [XFS_SCRUB_TYPE_RTRMAPBT] = "rtrmapbt", [XFS_SCRUB_TYPE_RTREFCBT] = "rtrefcountbt", + [XFS_SCRUB_TYPE_HEALTHY] = "healthy", }; /* Format the scrub stats into a text buffer, similar to pcp style. */ From d3dc979a49df6d48f8e137034d19d9b35afd07d8 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:03:33 -0700 Subject: [PATCH 1393/2366] xfs: report runtime failures in scrub Add a new counter so that we can track the number of runtime failures encountered during scrubs. Signed-off-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/stats.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/xfs/scrub/stats.c b/fs/xfs/scrub/stats.c index da0c05ffe5cd..3339cae4b39d 100644 --- a/fs/xfs/scrub/stats.c +++ b/fs/xfs/scrub/stats.c @@ -29,6 +29,7 @@ struct xchk_scrub_stats { uint32_t incomplete; uint32_t warning; uint32_t retries; + uint32_t runtime_errors; /* repair stats */ uint32_t repair_invocations; @@ -110,7 +111,7 @@ xchk_stats_format( spin_unlock(&css->css_lock); ret = scnprintf(buf, remaining, - "%s %u %u %u %u %u %u %u %u %u %llu %u %u %llu\n", + "%s %u %u %u %u %u %u %u %u %u %llu %u %u %llu %u\n", name_map[i], (unsigned int)fss.invocations, (unsigned int)fss.clean, @@ -124,7 +125,8 @@ xchk_stats_format( (unsigned long long)fss.checktime_us, (unsigned int)fss.repair_invocations, (unsigned int)fss.repair_success, - (unsigned long long)fss.repairtime_us); + (unsigned long long)fss.repairtime_us, + (unsigned int)fss.runtime_errors); if (ret <= 0) break; @@ -207,13 +209,17 @@ xchk_stats_merge_one( } /* caller applies this same transformation after we return */ - if (error == -EFSCORRUPTED || error == -EFSBADCRC) + if (error == -EFSCORRUPTED || error == -EFSBADCRC) { sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; + error = 0; + } css = &cs->cs_stats[sm->sm_type]; spin_lock(&css->css_lock); css->invocations++; - if (!(sm_flags & XFS_SCRUB_OFLAG_UNCLEAN)) + if (error) + css->runtime_errors++; + else if (!(sm_flags & XFS_SCRUB_OFLAG_UNCLEAN)) css->clean++; if (sm_flags & XFS_SCRUB_OFLAG_CORRUPT) css->corrupt++; From 341f03865d0793e7df22c4661e04047c092e5ac2 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:03:48 -0700 Subject: [PATCH 1394/2366] xfs: remove redundant function declaration Remove this useless code. Signed-off-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dabtree.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/xfs/scrub/dabtree.h b/fs/xfs/scrub/dabtree.h index de291e3b77dd..d654c125feb4 100644 --- a/fs/xfs/scrub/dabtree.h +++ b/fs/xfs/scrub/dabtree.h @@ -37,8 +37,6 @@ bool xchk_da_process_error(struct xchk_da_btree *ds, int level, int *error); void xchk_da_set_corrupt(struct xchk_da_btree *ds, int level); void xchk_da_set_preen(struct xchk_da_btree *ds, int level); -void xchk_da_set_preen(struct xchk_da_btree *ds, int level); - int xchk_da_btree_hash(struct xchk_da_btree *ds, int level, __be32 *hashp); int xchk_da_btree(struct xfs_scrub *sc, int whichfork, xchk_da_btree_rec_fn scrub_fn, void *private); From 3466dfef0a20f842363958deea55be9f1d26818a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:04:04 -0700 Subject: [PATCH 1395/2366] xfs: snapshot old AGFL before rewriting it LOLLM complains that we can't undo an attempt at fixing the AGFL if anything goes wrong during the rewrite, so take a snapshot of the whole buffer so that we can restore it. Move the xrep_agfl_update_agf call so that we only update the AGF if the AGFL update is 100% successful. While we're at it, fix leaking the used_extents bitmap if the disunion operation fails. Cc: stable@vger.kernel.org # v4.19 Fixes: 0e93d3f43ec7d3 ("xfs: repair the AGFL") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 35 ++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 2104512f1ee1..46c95354ca64 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -668,14 +668,16 @@ xrep_agfl_init_header( struct xfs_scrub *sc, struct xfs_buf *agfl_bp, struct xagb_bitmap *agfl_extents, - xfs_agblock_t flcount) + xfs_agblock_t flcount, + struct xfs_agfl *old_agfl) { struct xrep_agfl_fill af = { .sc = sc, .flcount = flcount, }; struct xfs_mount *mp = sc->mp; - struct xfs_agfl *agfl; + struct xfs_agfl *agfl = XFS_BUF_TO_AGFL(agfl_bp); + const size_t agfl_sz = BBTOB(agfl_bp->b_length); int error; ASSERT(flcount <= xfs_agfl_size(mp)); @@ -684,8 +686,8 @@ xrep_agfl_init_header( * Start rewriting the header by setting the bno[] array to * NULLAGBLOCK, then setting AGFL header fields. */ - agfl = XFS_BUF_TO_AGFL(agfl_bp); - memset(agfl, 0xFF, BBTOB(agfl_bp->b_length)); + memcpy(old_agfl, agfl, agfl_sz); + memset(agfl, 0xFF, agfl_sz); agfl->agfl_magicnum = cpu_to_be32(XFS_AGFL_MAGIC); agfl->agfl_seqno = cpu_to_be32(pag_agno(sc->sa.pag)); uuid_copy(&agfl->agfl_uuid, &mp->m_sb.sb_meta_uuid); @@ -700,13 +702,18 @@ xrep_agfl_init_header( xagb_bitmap_walk(agfl_extents, xrep_agfl_fill, &af); error = xagb_bitmap_disunion(agfl_extents, &af.used_extents); if (error) - return error; + goto err_undo; /* Write new AGFL to disk. */ xfs_trans_buf_set_type(sc->tp, agfl_bp, XFS_BLFT_AGFL_BUF); - xfs_trans_log_buf(sc->tp, agfl_bp, 0, BBTOB(agfl_bp->b_length) - 1); + xfs_trans_log_buf(sc->tp, agfl_bp, 0, agfl_sz - 1); xagb_bitmap_destroy(&af.used_extents); return 0; + +err_undo: + xagb_bitmap_destroy(&af.used_extents); + memcpy(agfl, old_agfl, agfl_sz); + return error; } /* Repair the AGFL. */ @@ -718,6 +725,7 @@ xrep_agfl( struct xfs_mount *mp = sc->mp; struct xfs_buf *agf_bp; struct xfs_buf *agfl_bp; + struct xfs_agfl *old_agfl; xfs_agblock_t flcount; int error; @@ -725,6 +733,10 @@ xrep_agfl( if (!xfs_has_rmapbt(mp)) return -EOPNOTSUPP; + old_agfl = kzalloc(BBTOB(XFS_FSS_TO_BB(mp, 1)), XCHK_GFP_FLAGS); + if (!old_agfl) + return -ENOMEM; + xagb_bitmap_init(&agfl_extents); /* @@ -734,7 +746,7 @@ xrep_agfl( */ error = xfs_alloc_read_agf(sc->sa.pag, sc->tp, 0, &agf_bp); if (error) - return error; + goto err_old_agfl; /* * Make sure we have the AGFL buffer, as scrub might have decided it @@ -745,7 +757,7 @@ xrep_agfl( XFS_AGFL_DADDR(mp)), XFS_FSS_TO_BB(mp, 1), 0, &agfl_bp, NULL); if (error) - return error; + goto err_old_agfl; agfl_bp->b_ops = &xfs_agfl_buf_ops; /* Gather all the extents we're going to put on the new AGFL. */ @@ -762,10 +774,11 @@ xrep_agfl( * we adjust the AGF flcount (which can fail) so avoid updating any * buffers until we know that part works. */ - xrep_agfl_update_agf(sc, agf_bp, flcount); - error = xrep_agfl_init_header(sc, agfl_bp, &agfl_extents, flcount); + error = xrep_agfl_init_header(sc, agfl_bp, &agfl_extents, flcount, + old_agfl); if (error) goto err; + xrep_agfl_update_agf(sc, agf_bp, flcount); /* * Ok, the AGFL should be ready to go now. Roll the transaction to @@ -785,6 +798,8 @@ xrep_agfl( err: xagb_bitmap_destroy(&agfl_extents); +err_old_agfl: + kfree(old_agfl); return error; } From eaf580538eb1be3d162400d04c4b7dc4c627296b Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:04:19 -0700 Subject: [PATCH 1396/2366] xfs: bail out on bitmap errors in xrep_agfl_fill LOLLM also points out that the xagb_bitmap_set call in xrep_agfl_fill can fail, but we don't check the result of xagb_bitmap_walk, so we silently drop the error and proceed with inconsistent incore data. That shouldn't be allowed. Cc: stable@vger.kernel.org # v6.6 Fixes: 014ad53732d2ba ("xfs: use per-AG bitmaps to reap unused AG metadata blocks during repair") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 46c95354ca64..a66b611588c4 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -699,7 +699,9 @@ xrep_agfl_init_header( */ xagb_bitmap_init(&af.used_extents); af.agfl_bno = xfs_buf_to_agfl_bno(agfl_bp); - xagb_bitmap_walk(agfl_extents, xrep_agfl_fill, &af); + error = xagb_bitmap_walk(agfl_extents, xrep_agfl_fill, &af); + if (error && error != -ECANCELED) + goto err_undo; error = xagb_bitmap_disunion(agfl_extents, &af.used_extents); if (error) goto err_undo; From ad0033e2dbd3ecc063dfe613060da5cbab9a4970 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 7 Sep 2026 10:33:07 +0300 Subject: [PATCH 1397/2366] xfs: also flush the RT device cache in xlog_write_iclog The cache flush before writing the CIL start record no only needs to ensure any metadata covered by the overwritten part of the log is on stable storage, but also that any data pointed to by metadata logged is on stable storage, as otherwise log recovery could created allocated blocks that point to stale data. Fortunately the code already handles this right for the data device, but it also needs to flush the RT device for this to work for data on the RT device. Also update the comments to explicitly mention this case. This omission goes back to the first days of cache control in XFS. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log.c | 45 ++++++++++++++++++++++++++++++-------------- fs/xfs/xfs_log_cil.c | 7 ++++--- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/fs/xfs/xfs_log.c b/fs/xfs/xfs_log.c index 2a34611d81f6..f4f81d893e8c 100644 --- a/fs/xfs/xfs_log.c +++ b/fs/xfs/xfs_log.c @@ -1544,6 +1544,35 @@ xlog_bio_end_io( &iclog->ic_end_io_work); } +/* + * When using multiple devices, we also need to flush the data and RT device + * caches first to ensure that all metadata writeback covered by the LSN in + * this iclog is on stable storage. This is slow, but it *must* complete + * before we issue the external log IO. + * + * If the flush fails, we cannot conclude that past metadata writeback from + * the log succeeded. Repeating the flush is not possible, hence we must + * shut down with log IO error to avoid shutdown re-entering this path and + * erroring out again. + */ +static int +xlog_flush_data_caches( + struct xlog *log) +{ + struct xfs_mount *mp = log->l_mp; + + if (log->l_targ != mp->m_ddev_targp) { + if (blkdev_issue_flush(mp->m_ddev_targp->bt_bdev)) + return -EIO; + } + if (mp->m_rtdev_targp && mp->m_rtdev_targp != mp->m_ddev_targp) { + if (blkdev_issue_flush(mp->m_rtdev_targp->bt_bdev)) + return -EIO; + } + + return 0; +} + STATIC void xlog_write_iclog( struct xlog *log, @@ -1588,21 +1617,9 @@ xlog_write_iclog( iclog->ic_bio.bi_private = iclog; if (iclog->ic_flags & XLOG_ICL_NEED_FLUSH) { - iclog->ic_bio.bi_opf |= REQ_PREFLUSH; - /* - * For external log devices, we also need to flush the data - * device cache first to ensure all metadata writeback covered - * by the LSN in this iclog is on stable storage. This is slow, - * but it *must* complete before we issue the external log IO. - * - * If the flush fails, we cannot conclude that past metadata - * writeback from the log succeeded. Repeating the flush is - * not possible, hence we must shut down with log IO error to - * avoid shutdown re-entering this path and erroring out again. - */ - if (log->l_targ != log->l_mp->m_ddev_targp && - blkdev_issue_flush(log->l_mp->m_ddev_targp->bt_bdev)) + if (xlog_flush_data_caches(log)) goto shutdown; + iclog->ic_bio.bi_opf |= REQ_PREFLUSH; } if (iclog->ic_flags & XLOG_ICL_NEED_FUA) iclog->ic_bio.bi_opf |= REQ_FUA; diff --git a/fs/xfs/xfs_log_cil.c b/fs/xfs/xfs_log_cil.c index 166531018ce4..f9e07a32f60f 100644 --- a/fs/xfs/xfs_log_cil.c +++ b/fs/xfs/xfs_log_cil.c @@ -1055,9 +1055,10 @@ xlog_cil_set_ctx_write_state( spin_unlock(&cil->xc_push_lock); /* - * Make sure the metadata we are about to overwrite in the log - * has been flushed to stable storage before this iclog is - * issued. + * Flush the write cache before writing the start record so that + * the metadata we are about to overwrite in the log and the + * data that new allocations in this context refer to are + * persisted to stable storage before this iclog is written. */ spin_lock(&cil->xc_log->l_icloglock); iclog->ic_flags |= XLOG_ICL_NEED_FLUSH; From c84455c683eb0b0397b0f5c5f5ce5cd82572a23f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 7 Sep 2026 10:33:08 +0300 Subject: [PATCH 1398/2366] xfs: don't continue on error in xfs_fsync As soon as we get an error from cache flushing or log forcing, there is no point in continuing as the data integrity is already impacted. Return the error instead of continuing to do more work. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_file.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 426a67b813a7..0d31fea67a2c 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -130,8 +130,8 @@ xfs_file_fsync( { struct xfs_inode *ip = XFS_I(file->f_mapping->host); struct xfs_mount *mp = ip->i_mount; - int error, err2; int log_flushed = 0; + int error; trace_xfs_file_fsync(ip); @@ -154,15 +154,17 @@ xfs_file_fsync( error = blkdev_issue_flush(mp->m_rtdev_targp->bt_bdev); else if (mp->m_logdev_targp != mp->m_ddev_targp) error = blkdev_issue_flush(mp->m_ddev_targp->bt_bdev); + if (error) + return error; /* * If the inode has a inode log item attached, it may need the journal * flushed to persist any changes the log item might be tracking. */ if (ip->i_itemp) { - err2 = xfs_fsync_flush_log(ip, datasync, &log_flushed); - if (err2 && !error) - error = err2; + error = xfs_fsync_flush_log(ip, datasync, &log_flushed); + if (error) + return error; } /* @@ -178,14 +180,11 @@ xfs_file_fsync( if (!log_flushed) { struct xfs_buftarg *file_targp = xfs_inode_buftarg(ip); - if (mp->m_logdev_targp == file_targp) { - err2 = blkdev_issue_flush(file_targp->bt_bdev); - if (err2 && !error) - error = err2; - } + if (mp->m_logdev_targp == file_targp) + return blkdev_issue_flush(file_targp->bt_bdev); } - return error; + return 0; } static int From 761e015e5a54851043c3b5bb7cfb6f539b01a35e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 7 Sep 2026 10:33:09 +0300 Subject: [PATCH 1399/2366] xfs: avoid extra cache flushes for multi-device file systems in xfs_fsync When xlog_force_lsn sets log_flushed, it has just called xlog_force_iclog through xlog_force_and_check_iclog, which sets XLOG_ICL_NEED_FLUSH before writing out the head iclog. This means that we already flushed the log, data, and (with the recent fix) RT devices before writing out the iclog start record and no extra cache flushed is required. This optimizes the external log case, and fixes a performance regression due to double RT dev flushes with "xfs: also flush the RT device cache in xlog_write_iclog". The explicit flush of the data that the device resides on when no iclog was written out is still required. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_file.c | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 0d31fea67a2c..d8202da15aca 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -129,7 +129,6 @@ xfs_file_fsync( int datasync) { struct xfs_inode *ip = XFS_I(file->f_mapping->host); - struct xfs_mount *mp = ip->i_mount; int log_flushed = 0; int error; @@ -139,27 +138,17 @@ xfs_file_fsync( if (error) return error; - if (xfs_is_shutdown(mp)) + if (xfs_is_shutdown(ip->i_mount)) return -EIO; xfs_iflags_clear(ip, XFS_ITRUNCATED); /* - * If we have an RT and/or log subvolume we need to make sure to flush - * the write cache the device used for file data first. This is to - * ensure newly written file data make it to disk before logging the new - * inode size in case of an extending write. - */ - if (XFS_IS_REALTIME_INODE(ip) && mp->m_rtdev_targp != mp->m_ddev_targp) - error = blkdev_issue_flush(mp->m_rtdev_targp->bt_bdev); - else if (mp->m_logdev_targp != mp->m_ddev_targp) - error = blkdev_issue_flush(mp->m_ddev_targp->bt_bdev); - if (error) - return error; - - /* - * If the inode has a inode log item attached, it may need the journal - * flushed to persist any changes the log item might be tracking. + * If the inode has a log item attached, we must force the log up to the + * last LSN in which the inode was modified to ensure all metadata is + * persisted. The log force will flush the caches for all devices + * before writing the log records unless it is a no-op because there are + * no modifications to this inode that need to be pushed out. */ if (ip->i_itemp) { error = xfs_fsync_flush_log(ip, datasync, &log_flushed); @@ -173,17 +162,10 @@ xfs_file_fsync( * when no metadata needed to be committed. * * Use the inode's actual file data target rather than assuming the - * main data device. Realtime inodes with a separate realtime device - * are flushed before the log force, so this fallback only applies - * when the file data target is the same as the log target. + * main data device. */ - if (!log_flushed) { - struct xfs_buftarg *file_targp = xfs_inode_buftarg(ip); - - if (mp->m_logdev_targp == file_targp) - return blkdev_issue_flush(file_targp->bt_bdev); - } - + if (!log_flushed) + return blkdev_issue_flush(xfs_inode_buftarg(ip)->bt_bdev); return 0; } From 50ba24ccb9a94f61c707209442ca23d98c815052 Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Mon, 7 Sep 2026 10:27:39 +0300 Subject: [PATCH 1400/2366] xfs: set IOMAP_F_INTEGRITY for zoned writes on integrity devices xfs_iomap_set_anon_write does not set IOMAP_F_INTEGRITY based on bdev_has_integrity_csum(), so file system PI generation is silently skipped for zoned writes on integrity-enabled devices, and left to the block layer PI generation. Fixes: 6bbb4d96f797 ("xfs: support T10 protection information") Signed-off-by: Anuj Gupta [hch: ported to the recently introduced xfs_iomap_set_anon_write()] Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_iomap.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/xfs_iomap.h b/fs/xfs/xfs_iomap.h index c906c62d46f3..f2520a9b3a13 100644 --- a/fs/xfs/xfs_iomap.h +++ b/fs/xfs/xfs_iomap.h @@ -41,6 +41,8 @@ xfs_iomap_set_anon_write( iomap->offset = offset; iomap->length = length; iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY; + if (bdev_has_integrity_csum(iomap->bdev)) + iomap->flags |= IOMAP_F_INTEGRITY; } static inline xfs_filblks_t From e240919ca727776f16a468d3d90686dc82cfe9cb Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Sun, 6 Sep 2026 20:16:29 +0530 Subject: [PATCH 1401/2366] xfs: take hm->lock in xfs_ioc_health_monitor() before insert __xfs_healthmon_insert() asserts that hm->lock is held (lockdep_assert_held), but xfs_ioc_health_monitor() called it right after allocating hm, before ever taking the lock, triggering a lockdep warning. Take hm->lock around the call. Fixes: b3a289a2a9397 ("xfs: create event queuing, formatting, and discovery infrastructure") Reported-by: syzbot+ccdf3469f5f653bff7ac@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ccdf3469f5f653bff7ac Signed-off-by: Deepanshu Kartikey Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index b57fa033cec4..c3749675ef19 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -1223,7 +1223,9 @@ xfs_ioc_health_monitor( } running_event->type = XFS_HEALTHMON_RUNNING; running_event->domain = XFS_HEALTHMON_MOUNT; + mutex_lock(&hm->lock); __xfs_healthmon_insert(hm, INSERT_HEAD, running_event); + mutex_unlock(&hm->lock); /* * Preallocate the unmount event so that we can't fail to notify the From 4f4b743c2d2bbc336cb164d9d3d2ed6956ad8437 Mon Sep 17 00:00:00 2001 From: Ratheesh Kannoth Date: Fri, 4 Sep 2026 14:21:13 +0530 Subject: [PATCH 1402/2366] octeontx2-af: fix PF/CGX debugfs PCI bus lookup rvu_dbg_rvu_pf_cgx_map_display() locates each RVU PF PCI device via pci_get_domain_bus_and_slot() when printing the PF-to-CGX map. It assumed PF0 always sits on PCI bus 1 and derived other PF bus numbers as pf + 1, but the AF device can be enumerated on a different bus. Use rvu->pdev->bus->number as the base bus instead, so each PF lookup uses pf + start on systems where RVU functions are on contiguous buses but do not start at bus 1. Fixes: e2fb373038654 ("octeontx2-af: Display CGX, NIX and PF map in debugfs.") Signed-off-by: Subbaraya Sundeep Signed-off-by: Ratheesh Kannoth Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260904085114.3385530-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/marvell/octeontx2/af/rvu_debugfs.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c index 22ee99676879..904374baae6f 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c @@ -829,19 +829,25 @@ static int rvu_dbg_rvu_pf_cgx_map_display(struct seq_file *filp, void *unused) int pf, domain, blkid; u8 cgx_id, lmac_id; u16 pcifunc; + u8 start; - domain = 2; + domain = pci_domain_nr(rvu->pdev->bus); mac_ops = get_mac_ops(rvu_first_cgx_pdata(rvu)); /* There can be no CGX devices at all */ if (!mac_ops) return 0; seq_printf(filp, "PCI dev\t\tRVU PF Func\tNIX block\t%s\tLMAC\tCHAN\n", mac_ops->name); + + /* All the PF devices are on contiguous PCI bus numbers, but the PF0(AF) + * may not start from 1 always. Hence get domain and bus from PCI device. + */ + start = rvu->pdev->bus->number; for (pf = 0; pf < rvu->hw->total_pfs; pf++) { if (!is_pf_cgxmapped(rvu, pf)) continue; - pdev = pci_get_domain_bus_and_slot(domain, pf + 1, 0); + pdev = pci_get_domain_bus_and_slot(domain, pf + start, 0); if (!pdev) continue; From 36a45facedd5c8e73bfb2403f8b0dbff05124c9c Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Thu, 3 Sep 2026 11:28:38 +0900 Subject: [PATCH 1403/2366] net: phy: dp83867: handle the active-high LED polarity mode Commit a274465cc3be ("net: phy: support 'active-high' property for PHY LEDs") added PHY_LED_ACTIVE_HIGH and made of_phy_led() set the matching bit in the modes mask when a LED node carries the 'active-high' property. dp83867 was not part of that series. dp83867_led_polarity_set() only recognizes PHY_LED_ACTIVE_LOW, so PHY_LED_ACTIVE_HIGH falls through to the default case and returns -EINVAL. of_phy_led() propagates the error, of_phy_leds() drops the LEDs registered so far and passes it on, and phy_probe() fails. A device tree marking a DP83867 LED as 'active-high', which leds/common.yaml allows and ethernet-phy.yaml references for led@N nodes, thus stops the PHY from probing. Active high is what the function programs when no polarity mode is requested at all, so the initial value of polarity already satisfies the request and only the case label is missing. The same series updated mxl-gpy in commit eb89c79c1b8f ("net: phy: mxl-gpy: correctly describe LED polarity") and aquantia in commit 9d55e68b19f2 ("net: phy: aquantia: correctly describe LED polarity override"). Fixes: a274465cc3be ("net: phy: support 'active-high' property for PHY LEDs") Signed-off-by: Donggeun Yoo Link: https://patch.msgid.link/20260903022839.4006614-1-donggeunyoo.kernel@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/dp83867.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c index 88255e92b4cd..61a941aa02d9 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -1150,6 +1150,9 @@ static int dp83867_led_polarity_set(struct phy_device *phydev, int index, case PHY_LED_ACTIVE_LOW: polarity = 0; break; + case PHY_LED_ACTIVE_HIGH: + polarity = DP83867_LED_POLARITY(index); + break; default: return -EINVAL; } From 4c46beb807efcc93f5899ebe1f5958248eb296c6 Mon Sep 17 00:00:00 2001 From: Long Li Date: Fri, 4 Sep 2026 13:26:40 -0700 Subject: [PATCH 1404/2366] net: mana: restore the XDP program pointer when pre-allocation fails mana_xdp_set() publishes the new program into apc->bpf_prog before it allocates anything, because mana_pre_alloc_rxbufs() sizes the buffers from it via mana_get_rxbuf_cfg(). When that allocation fails the function returns the error directly, skipping the err_dealloc_rxbuffs label which is the only place that restores the previous pointer. The attach is reported as failed, so the BPF core drops the reference it held for the caller and the program can be freed, while apc->bpf_prog still points at it. The next consumer of mana_xdp_get() - typically mana_chn_setxdp() from mana_alloc_queues() on the following ifup, or after a TX timeout reset - then calls bpf_prog_add() on freed memory. This is reachable from an ordinary "ip link set dev ethX xdp obj ..." whenever the per-queue RX buffer pre-allocation cannot be satisfied. Restore the previous program on that error path. Fixes: 730ff06d3f5c ("net: mana: Use page pool fragments for RX buffers instead of full pages to improve memory efficiency.") Signed-off-by: Long Li Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260904202640.3900685-1-longli@microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/mana_bpf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c index 53308e139cbe..5c9961ee9747 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c +++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c @@ -208,6 +208,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog, if (err) { NL_SET_ERR_MSG_MOD(extack, "XDP: Insufficient memory for tx/rx re-config"); + apc->bpf_prog = old_prog; return err; } From 9ff797e516dbc1ecb73701ec4c24055712d44411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Tue, 8 Sep 2026 08:55:20 +0000 Subject: [PATCH 1405/2366] RDMA/siw: Bound fragmented header copies by the remaining length MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit siw_get_hdr() can receive an extended DDP/RDMAP header across more than one TCP callback. The first callback may receive most of the header, while the next one still limits the copy to hdrlen - MIN_DDP_HDR instead of the number of missing bytes. This makes the destination move past the end of the header and overwrite the receive state, including fpdu_part_rcvd. A later callback can then use a negative fpdu_part_rcvd value as a copy offset, which creates an OOB write. Use the number of header bytes already received when calculating the next copy length. Fixes: 754209850df8 ("RDMA/siw: Always consume all skbuf data in sk_data_ready() upcall.") Signed-off-by: Jérémy Jean Link: https://patch.msgid.link/20260908085520.1746329-1-Jeremy.Jean@oss.cyber.gouv.fr Assisted-by: Codex:gpt-6 Acked-by: Bernard Metzler Signed-off-by: Leon Romanovsky --- drivers/infiniband/sw/siw/siw_qp_rx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/infiniband/sw/siw/siw_qp_rx.c b/drivers/infiniband/sw/siw/siw_qp_rx.c index b566d163c5aa..e5b641c8d694 100644 --- a/drivers/infiniband/sw/siw/siw_qp_rx.c +++ b/drivers/infiniband/sw/siw/siw_qp_rx.c @@ -1079,7 +1079,7 @@ static int siw_get_hdr(struct siw_rx_stream *srx) if (iwarp_pktinfo[opcode].hdr_len > sizeof(struct iwarp_ctrl_tagged)) { int hdrlen = iwarp_pktinfo[opcode].hdr_len; - bytes = min_t(int, hdrlen - MIN_DDP_HDR, srx->skb_new); + bytes = min_t(int, hdrlen - srx->fpdu_part_rcvd, srx->skb_new); skb_copy_bits(skb, srx->skb_offset, (char *)c_hdr + srx->fpdu_part_rcvd, bytes); From 2ac09b5353fe6858411fdc8c6efa60d832e20f13 Mon Sep 17 00:00:00 2001 From: Greg Marsden Date: Sat, 5 Sep 2026 10:00:41 -0700 Subject: [PATCH 1406/2366] net/rds: fix tcp stream corruption with large pages rds_message_map_pages() assigns PAGE_SIZE bytes to every scatterlist entry, even when total_len ends in a partial page. The RDS congestion map is defined as 8192 bytes, so on systems with PAGE_SIZE greater than 8192 the scatterlist maps bytes beyond the end of the congestion map. RDS-TCP transmits the SG contents according to those lengths, so the extra bytes become part of the TCP RDS stream and are interpreted as subsequent RDS message headers, corrupting the stream. Limit the final scatterlist mapping to the number of bytes remaining. This has no effect on systems with a 4K page size and allows RDS-TCP to be used on systems with 16K and larger page sizes. The RDS selftest, which previously hung on 16K pages, now passes. Fixes: 7875e18e0996 ("RDS: Message parsing") Signed-off-by: Greg Marsden Reviewed-by: Allison Henderson Link: https://patch.msgid.link/apxJjxvStibPI0AS@oracle.com Signed-off-by: Jakub Kicinski --- net/rds/message.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/rds/message.c b/net/rds/message.c index f25f2592586f..47d5e9ab9b10 100644 --- a/net/rds/message.c +++ b/net/rds/message.c @@ -431,7 +431,9 @@ struct rds_message *rds_message_map_pages(unsigned long *page_addrs, unsigned in for (i = 0; i < rm->data.op_nents; ++i) { sg_set_page(&rm->data.op_sg[i], virt_to_page((void *)page_addrs[i]), - PAGE_SIZE, 0); + i == rm->data.op_nents - 1 + ? total_len - (i * PAGE_SIZE) + : PAGE_SIZE, 0); } return rm; From 5e38d732ec67a5b1f9a56e6c73add480c4b6030a Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 7 Sep 2026 23:46:45 +0200 Subject: [PATCH 1407/2366] net: stmmac: fix TX descriptor availability check for TSO traffic stmmac_tso_xmit() estimates the number of free TX descriptors required by a TSO skb as: (skb->len - proto_hdr_len) / TSO_MAX_BUFF_SIZE + 1 which assumes the payload is split into TSO_MAX_BUFF_SIZE chunks. This underestimates the descriptors actually consumed by stmmac_tso_allocator(), since each fragment is mapped individually and so it needs at least one descriptor regardless of its size. Moreover, one descriptor is used for the L2/L3/L4 headers and, when the MSS changes, one more is consumed for the MSS context descriptor. For a highly fragmented TSO skb the check can therefore pass even when the ring has too few free slots. stmmac_tso_allocator() then writes past the available descriptors, overwriting descriptors still owned by the DMA engine, corrupting the TX ring. Add stmmac_tso_get_num_desc() to compute the exact number of descriptors needed for the header, the linear payload and each fragment, plus the MSS context descriptor when required, and use it in the availability check. Fixes: f748be531d70 ("stmmac: support new GMAC4") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260907-stmmac-fix-tso-nfrags-check-v1-1-328459906cdb@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 5fe7e95fdd34..62c3441911e7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4454,6 +4454,26 @@ static bool stmmac_tso_valid_packet(struct sk_buff *skb) header_len + gso_size < 16383; } +static int stmmac_tso_get_num_desc(struct stmmac_tx_queue *tx_q, + struct sk_buff *skb, u32 pay_len) +{ + int i, ndesc = 1; + + /* head payload */ + ndesc += DIV_ROUND_UP(pay_len, TSO_MAX_BUFF_SIZE); + /* frag payload */ + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + + ndesc += DIV_ROUND_UP(skb_frag_size(frag), + TSO_MAX_BUFF_SIZE); + } + /* MSS update requires a new descriptor */ + ndesc += !!(skb_shinfo(skb)->gso_size != tx_q->mss); + + return ndesc; +} + /** * stmmac_tso_xmit - Tx entry point of the driver for oversized frames (TSO) * @skb : the socket buffer @@ -4497,10 +4517,10 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) struct stmmac_priv *priv = netdev_priv(dev); unsigned int first_entry, entry, tx_packets; struct stmmac_txq_stats *txq_stats; + int i, first_tx, nfrags, ndesc; struct stmmac_tx_queue *tx_q; bool set_ic, is_last_segment; u32 pay_len, mss, queue; - int i, first_tx, nfrags; u8 proto_hdr_len, hdr; dma_addr_t des; @@ -4513,14 +4533,15 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) /* Compute header lengths */ proto_hdr_len = stmmac_tso_header_size(skb); + pay_len = skb_headlen(skb) - proto_hdr_len; /* no frags */ + if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4) hdr = sizeof(struct udphdr); else hdr = tcp_hdrlen(skb); - /* Desc availability based on threshold should be enough safe */ - if (unlikely(stmmac_tx_avail(priv, queue) < - (((skb->len - proto_hdr_len) / TSO_MAX_BUFF_SIZE + 1)))) { + ndesc = stmmac_tso_get_num_desc(tx_q, skb, pay_len); + if (unlikely(stmmac_tx_avail(priv, queue) < ndesc)) { if (!netif_tx_queue_stopped(netdev_get_tx_queue(dev, queue))) { netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, queue)); @@ -4532,8 +4553,6 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) return NETDEV_TX_BUSY; } - pay_len = skb_headlen(skb) - proto_hdr_len; /* no frags */ - mss = skb_shinfo(skb)->gso_size; /* set new MSS value if needed */ From 19b4ed644d68098cc62ab612727f40d30f43476c Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Tue, 8 Sep 2026 07:42:56 +0000 Subject: [PATCH 1408/2366] ipv6: fix fib6 walker UAF on seq stop ipv6_route_iter_active() treats a walker in FWS_U at the table root as already unlinked. fib6_del_route() can move a still-linked walker into that same state when the current leaf is the last route at the root, so ipv6_route_native_seq_stop() skips fib6_walker_unlink(). The seq private object can then be freed while it remains on net->ipv6.fib6_walkers. A later route deletion walks the dangling list and uses the freed walker. Use the list head as membership state and reinitialize it when unlinking. Keep the existing w->node check so a never-started iterator with a zeroed private object is not treated as linked. The same stop helper is used by /proc/net/ipv6_route and by the BPF ipv6_route iterator. The BPF show path only widens the race. Fixes: 8d2ca1d7b5c3 ("ipv6: avoid high order memory allocations for /proc/net/ipv6_route") Cc: stable@vger.kernel.org Reported-by: Vega Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/89699735763f6c297584d7c2ff106239cc1e8ce0.1788837093.git.zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_fib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 3e382ba1573e..9ea75703b38d 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -85,7 +85,7 @@ static void fib6_walker_link(struct net *net, struct fib6_walker *w) static void fib6_walker_unlink(struct net *net, struct fib6_walker *w) { write_lock_bh(&net->ipv6.fib6_walker_lock); - list_del(&w->lh); + list_del_init(&w->lh); write_unlock_bh(&net->ipv6.fib6_walker_lock); } @@ -2760,7 +2760,7 @@ static void *ipv6_route_seq_start(struct seq_file *seq, loff_t *pos) static bool ipv6_route_iter_active(struct ipv6_route_iter *iter) { struct fib6_walker *w = &iter->w; - return w->node && !(w->state == FWS_U && w->node == w->root); + return w->node && !list_empty(&w->lh); } static void ipv6_route_native_seq_stop(struct seq_file *seq, void *v) From a2dc179481d18f6df7274522571b64dd50f31e81 Mon Sep 17 00:00:00 2001 From: MD Danish Anwar Date: Tue, 8 Sep 2026 14:38:56 +0530 Subject: [PATCH 1409/2366] net: hsr: enable promiscuous mode on interlink port with fwd offload hsr_portdev_setup() skips promiscuous mode on non-master ports when hsr->fwd_offloaded is set. fwd_offloaded is derived only from the ring slaves' NETIF_F_HW_HSR_FWD bit, so this also skips it for the interlink port, which never gets forwarding offload. Without promiscuous mode, the interlink NIC drops unicast frames addressed to hsr_dev's MAC (e.g. SAN traffic to the RedBox), breaking RedBox whenever the ring is HW-offloaded. Fixes: 5055cccfc2d1 ("net: hsr: Provide RedBox support (HSR-SAN)") Signed-off-by: MD Danish Anwar Reviewed-by: Simon Horman Reviewed-by: Fernando Fernandez Mancera Link: https://patch.msgid.link/20260908090856.2876114-1-danishanwar@ti.com Signed-off-by: Jakub Kicinski --- net/hsr/hsr_slave.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/net/hsr/hsr_slave.c b/net/hsr/hsr_slave.c index 01c73b4b50dd..a546f70f9cc8 100644 --- a/net/hsr/hsr_slave.c +++ b/net/hsr/hsr_slave.c @@ -149,9 +149,12 @@ static int hsr_portdev_setup(struct hsr_priv *hsr, struct net_device *dev, int res; /* Don't use promiscuous mode for offload since L2 frame forward - * happens at the offloaded hardware. + * happens at the offloaded hardware. The interlink port never + * gets forwarding offload (RedBox forwarding to/from it is done + * by this driver), so it still needs promiscuous mode to receive + * frames addressed to hsr_dev's MAC rather than its own. */ - if (!port->hsr->fwd_offloaded) { + if (!port->hsr->fwd_offloaded || port->type == HSR_PT_INTERLINK) { res = dev_set_promiscuity(dev, 1); if (res) return res; @@ -176,7 +179,7 @@ static int hsr_portdev_setup(struct hsr_priv *hsr, struct net_device *dev, fail_rx_handler: netdev_upper_dev_unlink(dev, hsr_dev); fail_upper_dev_link: - if (!port->hsr->fwd_offloaded) + if (!port->hsr->fwd_offloaded || port->type == HSR_PT_INTERLINK) dev_set_promiscuity(dev, -1); return res; @@ -240,7 +243,7 @@ void hsr_del_port(struct hsr_port *port) netdev_update_features(master->dev); dev_set_mtu(master->dev, hsr_get_max_mtu(hsr)); netdev_rx_handler_unregister(port->dev); - if (!port->hsr->fwd_offloaded) + if (!port->hsr->fwd_offloaded || port->type == HSR_PT_INTERLINK) dev_set_promiscuity(port->dev, -1); if (port->type == HSR_PT_SLAVE_A || port->type == HSR_PT_SLAVE_B) vlan_vids_del_by_dev(port->dev, master->dev); From e1406330d70e56dd44fa6fbafc86e77e5c80c122 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 8 Sep 2026 18:39:24 +0800 Subject: [PATCH 1410/2366] net: macb: initialize PTP state before registering clock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gem_ptp_init() registers the PTP clock before initializing bp->tsu_clk_lock and the TSU hardware. Since ptp_clock_register() publishes the PTP character device, userspace may invoke PTP callbacks before the lock and hardware are ready. In addition, gem_ptp_init() is called from both the interface open and resume paths. Reinitializing tsu_clk_lock there can reset the lock while timestamp processing is using it. This race is theoretical and has not been observed in practice. Initialize tsu_clk_lock once during probe and initialize the TSU before registering the PTP clock. Fixes: ab91f0a9b5f4 ("net: macb: Add hardware PTP support") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/netdev/20260904030439.3994047-1-runyu.xiao@seu.edu.cn/ Reviewed-by: Théo Lebrun Reviewed-by: Vadim Fedorenko Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260908103924.607033-1-runyu.xiao@seu.edu.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 1 + drivers/net/ethernet/cadence/macb_ptp.c | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 77dec2d6e3fb..4cb5d7088d43 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -5883,6 +5883,7 @@ static int macb_probe(struct platform_device *pdev) } spin_lock_init(&bp->lock); spin_lock_init(&bp->stats_lock); + spin_lock_init(&bp->tsu_clk_lock); /* setup capabilities */ macb_configure_caps(bp, macb_config); diff --git a/drivers/net/ethernet/cadence/macb_ptp.c b/drivers/net/ethernet/cadence/macb_ptp.c index e5195d7dac1d..6d9166389988 100644 --- a/drivers/net/ethernet/cadence/macb_ptp.c +++ b/drivers/net/ethernet/cadence/macb_ptp.c @@ -334,6 +334,7 @@ void gem_ptp_init(struct net_device *netdev) bp->tsu_rate = bp->ptp_info->get_tsu_rate(bp); bp->ptp_clock_info.max_adj = bp->ptp_info->get_ptp_max_adj(); gem_ptp_init_timer(bp); + gem_ptp_init_tsu(bp); bp->ptp_clock = ptp_clock_register(&bp->ptp_clock_info, &netdev->dev); if (IS_ERR(bp->ptp_clock)) { pr_err("ptp clock register failed: %ld\n", @@ -345,10 +346,6 @@ void gem_ptp_init(struct net_device *netdev) return; } - spin_lock_init(&bp->tsu_clk_lock); - - gem_ptp_init_tsu(bp); - dev_info(&bp->pdev->dev, "%s ptp clock registered.\n", GEM_PTP_TIMER_NAME); } From 6ca81bbc31cdc964e4b74d17b86215d4a810a56f Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Tue, 8 Sep 2026 19:59:58 +0900 Subject: [PATCH 1411/2366] net: phy: dp83td510: handle the active-high LED polarity mode dp83td510_led_polarity_set() only recognizes PHY_LED_ACTIVE_LOW, so PHY_LED_ACTIVE_HIGH falls through to the default case and returns -EINVAL. of_phy_led() propagates the error, of_phy_leds() drops the LEDs registered so far and passes it on, and phy_probe() returns it. A device tree marking a DP83TD510 LED as 'active-high', which leds/common.yaml allows and ethernet-phy.yaml references for led@N nodes, thus leaves the mdio device unbound, so phy_attach_direct() falls back to the genphy driver, which cannot drive this 10BASE-T1L single-mode PHY, so the interface has no usable link. The callback initializes polarity to DP83TD510E_LED_POLARITY(index), which is the active-high setting, so the request is already satisfied and only the case label is missing. Cc: stable@vger.kernel.org Fixes: 5b281fe7e396 ("net: phy: dp83td510: introduce LED framework support") Signed-off-by: Donggeun Yoo Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260908105959.70453-3-donggeunyoo.kernel@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/dp83td510.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/phy/dp83td510.c b/drivers/net/phy/dp83td510.c index d75dae6071ad..9e9a41bf6457 100644 --- a/drivers/net/phy/dp83td510.c +++ b/drivers/net/phy/dp83td510.c @@ -439,6 +439,9 @@ static int dp83td510_led_polarity_set(struct phy_device *phydev, int index, case PHY_LED_ACTIVE_LOW: polarity = 0; break; + case PHY_LED_ACTIVE_HIGH: + polarity = DP83TD510E_LED_POLARITY(index); + break; default: return -EINVAL; } From b7ee18725f2292ab554aa96a101ae42d45f008bd Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Tue, 8 Sep 2026 11:58:39 +0000 Subject: [PATCH 1412/2366] ipmr: account multicast table and route memory A netadmin in a user+net namespace can create many IPv4 and IPv6 multicast routing tables with MRT_TABLE and MRT6_TABLE. Each unseen id allocates an mr_table via the shared mr_table_alloc(), links it into the per-net list, and leaves it until netns teardown. Those objects were not charged to memcg, so the host unreclaimable slab grows with the table count. Account mr_table allocations with GFP_KERNEL_ACCOUNT and mark the IPv4/IPv6 MFC caches SLAB_ACCOUNT. This matches the established handling of IP addresses, routes and alternate interface names. Unresolved MFC entries are still allocated from softIRQ with GFP_ATOMIC and are not charged. They expire after 10 seconds and are bounded by the socket receive queue; see commit 0079ad8e8dc3 ("ipmr: remove hard code cache_resolve_queue_len limit"). Fixes: f0ad0860d01e ("ipv4: ipmr: support multiple tables") Fixes: d1db275dd3f6 ("ipv6: ip6mr: support multiple tables") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zihan Xi Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/050b58f7fc6b45da0fb12768ebb62d18fa46133d.1788784801.git.zihanx@nebusec.ai Signed-off-by: Jakub Kicinski --- net/ipv4/ipmr.c | 3 ++- net/ipv4/ipmr_base.c | 2 +- net/ipv6/ip6mr.c | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index e5f2b1c6150d..b9c544d48c45 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -3376,7 +3376,8 @@ int __init ip_mr_init(void) { int err; - mrt_cachep = KMEM_CACHE(mfc_cache, SLAB_HWCACHE_ALIGN | SLAB_PANIC); + mrt_cachep = KMEM_CACHE(mfc_cache, + SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT); err = register_pernet_subsys(&ipmr_net_ops); if (err) diff --git a/net/ipv4/ipmr_base.c b/net/ipv4/ipmr_base.c index 867b24beded1..a0ec6d19a237 100644 --- a/net/ipv4/ipmr_base.c +++ b/net/ipv4/ipmr_base.c @@ -52,7 +52,7 @@ mr_table_alloc(struct net *net, u32 id, struct mr_table *mrt; int err; - mrt = kzalloc_obj(*mrt); + mrt = kzalloc_obj(*mrt, GFP_KERNEL_ACCOUNT); if (!mrt) return ERR_PTR(-ENOMEM); mrt->id = id; diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index 3f2ed9b77deb..9d8116b5edb1 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c @@ -1427,7 +1427,7 @@ int __init ip6_mr_init(void) { int err; - mrt_cachep = KMEM_CACHE(mfc6_cache, SLAB_HWCACHE_ALIGN); + mrt_cachep = KMEM_CACHE(mfc6_cache, SLAB_HWCACHE_ALIGN | SLAB_ACCOUNT); if (!mrt_cachep) return -ENOMEM; From e184a4a6f423550a25adce867036cdb1ff471745 Mon Sep 17 00:00:00 2001 From: Eelco Chaudron Date: Tue, 8 Sep 2026 16:15:17 +0200 Subject: [PATCH 1413/2366] openvswitch: fix wrong flag value in get_ipv6_ext_hdrs() The ESP and AH cases in get_ipv6_ext_hdrs() used IPPROTO_FRAGMENT instead of OFPIEH12_FRAG when checking for out-of-order extension headers, causing the fragment header to not be recognised as a valid predecessor. The original code used IPPROTO_FRAGMENT (44) as a bitmask constant where OFPIEH12_FRAG (1 << 4 = 16) was intended. IPPROTO_FRAGMENT encodes bits 2, 3 and 5 (OFPIEH12_AUTH | OFPIEH12_DEST | OFPIEH12_ROUTER), but not bit 4 (OFPIEH12_FRAG). This caused incorrect OFPIEH12_UNSEQ verdicts in both the ESP and AH arms: the ESP arm failed to whitelist OFPIEH12_FRAG, while the AH arm accidentally whitelisted OFPIEH12_AUTH. With the fix, a packet with two AH headers now also gets OFPIEH12_UNSEQ in addition to OFPIEH12_UNREP, matching the ESP arm which already sets UNSEQ on a repeat, which is the intended behavior. Fixes: 28a3f0601727 ("net: openvswitch: IPv6: Add IPv6 extension header support") Reported-by: Paolo Abeni Reviewed-by: Aaron Conole Reviewed-by: Ilya Maximets Signed-off-by: Eelco Chaudron Link: https://patch.msgid.link/1b1582eb07550d71f3cbe210e5cb31eeb8d0ad86.1788876917.git.echaudro@redhat.com Signed-off-by: Jakub Kicinski --- net/openvswitch/flow.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/openvswitch/flow.c b/net/openvswitch/flow.c index 46c1d66aad8c..868d9fdf3afd 100644 --- a/net/openvswitch/flow.c +++ b/net/openvswitch/flow.c @@ -288,7 +288,7 @@ static void get_ipv6_ext_hdrs(struct sk_buff *skb, struct ipv6hdr *nh, if (*ext_hdrs & OFPIEH12_ESP) *ext_hdrs |= OFPIEH12_UNREP; if ((*ext_hdrs & ~(OFPIEH12_HOP | OFPIEH12_DEST | - OFPIEH12_ROUTER | IPPROTO_FRAGMENT | + OFPIEH12_ROUTER | OFPIEH12_FRAG | OFPIEH12_AUTH | OFPIEH12_UNREP)) || dest_options_header_count >= 2) { *ext_hdrs |= OFPIEH12_UNSEQ; @@ -301,7 +301,7 @@ static void get_ipv6_ext_hdrs(struct sk_buff *skb, struct ipv6hdr *nh, *ext_hdrs |= OFPIEH12_UNREP; if ((*ext_hdrs & ~(OFPIEH12_HOP | OFPIEH12_DEST | OFPIEH12_ROUTER | - IPPROTO_FRAGMENT | OFPIEH12_UNREP)) || + OFPIEH12_FRAG | OFPIEH12_UNREP)) || dest_options_header_count >= 2) { *ext_hdrs |= OFPIEH12_UNSEQ; } From e10f2b7e28be3e1ce42a4be8fa9b0684d1d354ac Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 10 Sep 2026 16:41:50 +0200 Subject: [PATCH 1414/2366] ALSA: hdspm: Add a new PCI device ID (1d18:3fc6) for RME HDSPe AIO PCI express audio The RME HDSPe AIO PCI express audio card has a new PCI vendor ID (1d18) while keeping the same device ID (3fc6). The card seems working fine by just adding the new ID. While we're at it, use the standard macro to cleann up the existing PCI ID entry, too. Reported-by: AtmanActive Closes: https://lore.kernel.org/178674392532.7.10140952469564861620.1550442282@slmail.me Tested-by: AtmanActive Link: https://patch.msgid.link/20260910144204.973359-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/pci/rme9652/hdspm.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c index d8bbedbc8ff6..95fd82bb1c6d 100644 --- a/sound/pci/rme9652/hdspm.c +++ b/sound/pci/rme9652/hdspm.c @@ -1071,14 +1071,8 @@ struct hdspm { static const struct pci_device_id snd_hdspm_ids[] = { - { - .vendor = PCI_VENDOR_ID_XILINX, - .device = PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI, - .subvendor = PCI_ANY_ID, - .subdevice = PCI_ANY_ID, - .class = 0, - .class_mask = 0, - .driver_data = 0}, + { PCI_DEVICE(PCI_VENDOR_ID_XILINX, PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI) }, + { PCI_DEVICE(0x1d18, 0x3fc6) }, /* RME HDSPe AIO PCI express audio */ {0,} }; From fcfe64715b425262af1b36f498f9197f3537ceed Mon Sep 17 00:00:00 2001 From: Vadim Nikitushkin Date: Thu, 10 Sep 2026 17:34:51 +0300 Subject: [PATCH 1415/2366] drm/ttm: apply the swapout bulk_move fix to the intended condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 3db7d7d58341 ("drm/ttm: fix swapped-out resources never leaving their bulk_move range") landed in drm-misc-fixes with its one-line change applied to the wrong "if": the "if (ret)" after ttm_resource_try_charge() in ttm_bo_alloc_at_place() became "if (ret > 0)", while the "if (!ret)" after ttm_tt_swapout() in ttm_bo_swapout_cb() that the patch targeted was left untouched. ttm_resource_try_charge() returns 0 or a negative error code, so with "ret > 0" a failed dmem cgroup charge no longer fails the allocation. Restore that check and apply the intended change: ttm_tt_swapout() returns the number of pages swapped out on success, so the bulk_move removal must run for ret > 0. Fixes: 3db7d7d58341 ("drm/ttm: fix swapped-out resources never leaving their bulk_move range") Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Vadim Nikitushkin Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260910143451.65853-1-bub4z0r@gmail.com --- drivers/gpu/drm/ttm/ttm_bo.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index a12af5b38a31..9b85b5f388d4 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -532,7 +532,7 @@ static int ttm_bo_alloc_at_place(struct ttm_buffer_object *bo, ret = ttm_resource_try_charge(bo, place, &alloc_state->charge_pool, force_space ? &alloc_state->limit_pool : NULL); - if (ret > 0) { + if (ret) { /* * -EAGAIN means the charge failed, which we treat * like an allocation failure. Therefore, return an @@ -1434,7 +1434,7 @@ ttm_bo_swapout_cb(struct ttm_lru_walk *walk, struct ttm_buffer_object *bo) if (ttm_tt_is_populated(tt)) { ret = ttm_tt_swapout(bdev, tt, swapout_walk->gfp_flags); - if (!ret) { + if (ret > 0) { spin_lock(&bdev->lru_lock); ttm_resource_del_bulk_move_unevictable(bo->resource, bo); ttm_resource_move_to_lru_tail(bo->resource); From 478eb5abb51931a152abab068f8a717b7ff480fd Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Wed, 9 Sep 2026 15:03:35 +0800 Subject: [PATCH 1416/2366] net/sched: act_api: release all action references on NEWACTION failure When a batched RTM_NEWACTION request replaces an existing action, tcf_idr_check_alloc() takes a temporary reference on it. If a later action fails to initialize, tcf_action_destroy() uses strict release semantics to clean up the actions initialized so far. For an action bound to a filter, the strict check returns -EPERM without dropping the temporary reference. This error also makes tcf_action_destroy() return before releasing subsequent entries. Any new action initialized between the bound action and the failing entry is leaked together with its reserved IDR slot, preventing reuse of its index. Use tcf_idr_release() to drop each reference held by the batch without rejecting bound actions. This allows cleanup to continue through all initialized entries and preserves the module reference release when an action is destroyed. Explicit action deletion and flushing retain their separate bind-count checks. Fixes: 55334a5db5cd ("net_sched: act: refuse to remove bound action outside") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260909070336.32979-2-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 37eced84dfa5..19501dc99464 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -1200,18 +1200,13 @@ EXPORT_SYMBOL(tcf_action_exec); int tcf_action_destroy(struct tc_action *actions[], int bind) { - const struct tc_action_ops *ops; struct tc_action *a; int ret = 0, i; tcf_act_for_each_action(i, a, actions) { actions[i] = NULL; - ops = a->ops; - ret = __tcf_idr_release(a, bind, true); - if (ret == ACT_P_DELETED) - module_put(ops->owner); - else if (ret < 0) - return ret; + /* Drop our reference even if the action is still bound to a filter. */ + ret = tcf_idr_release(a, bind); } return ret; } From 2a86bbed9f60702e97a8194e40f90f4db22d7795 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Wed, 9 Sep 2026 15:03:36 +0800 Subject: [PATCH 1417/2366] selftests: tc-testing: test action batch failure cleanup Add tests for cleanup after a batched RTM_NEWACTION request fails. Replace an existing gact action bound to a filter, then fail a later entry by requesting goto chain without a classifier context. Check that the bound action's reference count returns to its original value. Also cover a successfully initialized new action between the bound action and the failing entry, verifying that its reserved index can be reused. Repeat the bound action in another batch to check that each temporary reference to the same action is released. Signed-off-by: Xuanqiang Luo Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260909070336.32979-3-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- .../tc-tests/actions/gact-rollback.json | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tools/testing/selftests/tc-testing/tc-tests/actions/gact-rollback.json diff --git a/tools/testing/selftests/tc-testing/tc-tests/actions/gact-rollback.json b/tools/testing/selftests/tc-testing/tc-tests/actions/gact-rollback.json new file mode 100644 index 000000000000..e92a4180db68 --- /dev/null +++ b/tools/testing/selftests/tc-testing/tc-tests/actions/gact-rollback.json @@ -0,0 +1,78 @@ +[ + { + "id": "e3b1", + "name": "Failed action batch releases a bound action reference", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC filter add dev $DEV1 protocol all ingress prio 1 matchall action gact index 1" + ], + "cmdUnderTest": "$TC actions replace action pass index 1 action goto chain 42 index 3", + "expExitCode": "255", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 1.*index 1 ref 2 bind 1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "e3b2", + "name": "Failed action batch releases entries after a bound action", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC filter add dev $DEV1 protocol all ingress prio 1 matchall action gact index 1", + [ + "$TC actions replace action pass index 1 action pass index 2 action goto chain 42 index 3", + 255 + ] + ], + "cmdUnderTest": "$TC actions add action pass index 2", + "expExitCode": "0", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 2.*index 1 ref 2 bind 1.*index 2 ref 1 bind 0", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "e3b3", + "name": "Failed action batch releases repeated references to a bound action", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC filter add dev $DEV1 protocol all ingress prio 1 matchall action gact index 1" + ], + "cmdUnderTest": "$TC actions replace action pass index 1 action pass index 1 action goto chain 42 index 3", + "expExitCode": "255", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 1.*index 1 ref 2 bind 1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + } +] From cfdcf5571c3107bf636002fc0c16ce93c19bd671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michel=20D=C3=A4nzer?= Date: Mon, 18 May 2026 17:48:09 +0200 Subject: [PATCH 1418/2366] drm/amd/display: Consult MCCS FreeSync cap only if requested & supported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the do_mccs parameter is false, we don't call dm_helpers_read_mccs_caps, so sink->mccs_caps.freesync_supported is unlikely to be true. Fixes: 6f71d5dd3206 ("drm/amd/display: Read sink freesync support via mccs") Bug: https://gitlab.freedesktop.org/drm/amd/-/work_items/5286 Signed-off-by: Michel Dänzer Reviewed-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit ac3aea794fb4156467b4b3b92c3155d95bf435c9) Cc: stable@vger.kernel.org --- .../amd/display/amdgpu_dm/amdgpu_dm_connector.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index c8a1ab8c3b16..d03773887214 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -3937,17 +3937,15 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, } /* Handle MCCS */ - if (do_mccs) + if (do_mccs) { dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); - if ((sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || - as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) && - (!sink->edid_caps.freesync_vcp_code || - (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported))) - freesync_capable = false; + if (sink->edid_caps.freesync_vcp_code && !sink->mccs_caps.freesync_supported) + freesync_capable = false; - if (do_mccs && sink->mccs_caps.freesync_supported && freesync_capable) - dm_helpers_mccs_vcp_set(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); + if (sink->mccs_caps.freesync_supported && freesync_capable) + dm_helpers_mccs_vcp_set(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); + } update: if (dm_con_state) From 87ceb8cba73d0b3c4025ff42495bccd8164acaed Mon Sep 17 00:00:00 2001 From: Arunpravin Paneer Selvam Date: Wed, 2 Sep 2026 18:33:48 +0530 Subject: [PATCH 1419/2366] drm/amdgpu: skip the VMID 0 flush for VRAM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clear-on-release only runs on VRAM, which amdgpu_ttm_map_buffer() reaches via its direct MC address without programming a GART window, yet the wipe still forces a VMID 0 flush. On GFX11 (e.g. Navi33) that spurious SDMA flush can wedge the engine; only flush when a GART window is actually used. v2: Let amdgpu_ttm_map_buffer() return whether the VMID 0 flush is needed, and drive the clear and copy paths from that. (Christian) v3: Make the vm_needs_flush output parameter mandatory instead of allowing NULL. (Christian) Fixes: a68c7eaa7a8f ("drm/amdgpu: Enable clear page functionality") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5413 Cc: Christian König Signed-off-by: Arunpravin Paneer Selvam Reviewed-by: Christian König Reviewed-by: Timur Kristóf Signed-off-by: Alex Deucher (cherry picked from commit a306e406e570b74318ff7d80e5b07b540ca1d3a9) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 05abf4c31dce..016957cac1f2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -191,6 +191,8 @@ amdgpu_ttm_job_submit(struct amdgpu_device *adev, struct amdgpu_ttm_buffer_entit * @tmz: if we should setup a TMZ enabled mapping * @size: in number of bytes to map, out number of bytes mapped * @addr: resulting address inside the MC address space + * @vm_needs_flush: out, set true if a GART window was programmed (VMID 0 flush + * needed) or false for a direct address * * Setup one of the GART windows to access a specific piece of memory or return * the physical address for local memory. @@ -200,7 +202,8 @@ static int amdgpu_ttm_map_buffer(struct amdgpu_ttm_buffer_entity *entity, struct ttm_resource *mem, struct amdgpu_res_cursor *mm_cur, unsigned int window, - bool tmz, uint64_t *size, uint64_t *addr) + bool tmz, uint64_t *size, uint64_t *addr, + bool *vm_needs_flush) { struct amdgpu_device *adev = amdgpu_ttm_adev(bo->bdev); unsigned int offset, num_pages, num_dw, num_bytes; @@ -221,9 +224,12 @@ static int amdgpu_ttm_map_buffer(struct amdgpu_ttm_buffer_entity *entity, if (!tmz && mem->start != AMDGPU_BO_INVALID_OFFSET) { *addr = amdgpu_ttm_domain_start(adev, mem->mem_type) + mm_cur->start; + *vm_needs_flush = false; return 0; } + /* A GART window is programmed below, so its VMID 0 TLB needs a flush */ + *vm_needs_flush = true; /* * If start begins at an offset inside the page, then adjust the size @@ -324,6 +330,7 @@ static int amdgpu_ttm_copy_mem_to_mem(struct amdgpu_device *adev, while (src_mm.remaining) { uint64_t from, to, cur_size, tiling_flags; uint32_t num_type, data_format, max_com, write_compress_disable; + bool src_vm_flush, dst_vm_flush; struct dma_fence *next; /* Never copy more than 256MiB at once to avoid a timeout */ @@ -331,12 +338,12 @@ static int amdgpu_ttm_copy_mem_to_mem(struct amdgpu_device *adev, /* Map src to window 0 and dst to window 1. */ r = amdgpu_ttm_map_buffer(entity, src->bo, src->mem, &src_mm, - 0, tmz, &cur_size, &from); + 0, tmz, &cur_size, &from, &src_vm_flush); if (r) goto error; r = amdgpu_ttm_map_buffer(entity, dst->bo, dst->mem, &dst_mm, - 1, tmz, &cur_size, &to); + 1, tmz, &cur_size, &to, &dst_vm_flush); if (r) goto error; @@ -364,7 +371,7 @@ static int amdgpu_ttm_copy_mem_to_mem(struct amdgpu_device *adev, } r = amdgpu_copy_buffer(adev, entity, from, to, cur_size, resv, - &next, true, copy_flags); + &next, src_vm_flush || dst_vm_flush, copy_flags); if (r) goto error; @@ -2624,6 +2631,7 @@ int amdgpu_ttm_clear_buffer(struct amdgpu_ttm_buffer_entity *entity, struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev); struct dma_fence *fence = NULL; struct amdgpu_res_cursor dst; + bool vm_needs_flush = false; int r; if (!entity) @@ -2645,13 +2653,13 @@ int amdgpu_ttm_clear_buffer(struct amdgpu_ttm_buffer_entity *entity, cur_size = min(dst.size, 256ULL << 20); r = amdgpu_ttm_map_buffer(entity, &bo->tbo, bo->tbo.resource, &dst, - 0, false, &cur_size, &to); + 0, false, &cur_size, &to, &vm_needs_flush); if (r) goto error; r = amdgpu_ttm_fill_mem(adev, entity, 0, to, cur_size, resv, - &next, true, k_job_id); + &next, vm_needs_flush, k_job_id); if (r) goto error; From 622b4e8505aa7453a53d17fa3a288871f270fc8b Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Tue, 16 Jun 2026 13:39:21 -0400 Subject: [PATCH 1420/2366] dm/amdgpu: fix malformed link_settings debugfs output [Why] dp_link_settings_read() passed strlen() of each format string as the size argument to snprintf() and then advanced rd_buf_ptr by that same fixed amount. The format-string length has no relation to the formatted output length, so snprintf() truncated each field at a NUL it wrote inside the buffer while the pointer was advanced past it. The result is a buffer peppered with embedded NUL bytes and fields that are silently cut short, so the data read back from the debugfs node does not reflect the actual link settings. [How] Use scnprintf() with the real remaining buffer size (rd_buf_size - (rd_buf_ptr - rd_buf)) and advance rd_buf_ptr by its return value, which is the number of characters actually written. This both bounds each write to the space left in rd_buf and keeps the output a single, properly terminated string. The now-unused str_len local is removed. Fixes: 41db5f1931ec ("drm/amd/display: set-read link rate and lane count through debugfs") Assisted-by: Copilot:claude-opus-4.8 Signed-off-by: Harry Wentland Reviewed-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit 43b9f0f18693c7f7b75613f3aeae25fa2b4e2f76) Cc: stable@vger.kernel.org --- .../amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c index c4b2fc690fd7..2a6b48e24869 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c @@ -196,7 +196,6 @@ static ssize_t dp_link_settings_read(struct file *f, char __user *buf, char *rd_buf_ptr = NULL; const uint32_t rd_buf_size = 100; uint32_t result = 0; - uint8_t str_len = 0; int r; if (*pos & 3 || size & 3) @@ -208,29 +207,26 @@ static ssize_t dp_link_settings_read(struct file *f, char __user *buf, rd_buf_ptr = rd_buf; - str_len = strlen("Current: %d 0x%x %d "); - snprintf(rd_buf_ptr, str_len, "Current: %d 0x%x %d ", + rd_buf_ptr += scnprintf(rd_buf_ptr, rd_buf_size - (rd_buf_ptr - rd_buf), + "Current: %d 0x%x %d ", link->cur_link_settings.lane_count, link->cur_link_settings.link_rate, link->cur_link_settings.link_spread); - rd_buf_ptr += str_len; - str_len = strlen("Verified: %d 0x%x %d "); - snprintf(rd_buf_ptr, str_len, "Verified: %d 0x%x %d ", + rd_buf_ptr += scnprintf(rd_buf_ptr, rd_buf_size - (rd_buf_ptr - rd_buf), + "Verified: %d 0x%x %d ", link->verified_link_cap.lane_count, link->verified_link_cap.link_rate, link->verified_link_cap.link_spread); - rd_buf_ptr += str_len; - str_len = strlen("Reported: %d 0x%x %d "); - snprintf(rd_buf_ptr, str_len, "Reported: %d 0x%x %d ", + rd_buf_ptr += scnprintf(rd_buf_ptr, rd_buf_size - (rd_buf_ptr - rd_buf), + "Reported: %d 0x%x %d ", link->reported_link_cap.lane_count, link->reported_link_cap.link_rate, link->reported_link_cap.link_spread); - rd_buf_ptr += str_len; - str_len = strlen("Preferred: %d 0x%x %d "); - snprintf(rd_buf_ptr, str_len, "Preferred: %d 0x%x %d\n", + rd_buf_ptr += scnprintf(rd_buf_ptr, rd_buf_size - (rd_buf_ptr - rd_buf), + "Preferred: %d 0x%x %d\n", link->preferred_link_setting.lane_count, link->preferred_link_setting.link_rate, link->preferred_link_setting.link_spread); From 2e8ff3ac79eb09dccbd8f00ee5da1b61e53246be Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Thu, 18 Jun 2026 09:52:14 -0700 Subject: [PATCH 1421/2366] drm/amd/display: Shorten hdmi_frl_status_polling_workqueue There is a warning when creating the hdmi_frl_status_polling_wq workqueue because "hdmi_frl_status_polling_workqueue" excceds WQ_NAME_LEN: workqueue: name exceeds WQ_NAME_LEN. Truncating to: hdmi_frl_status_polling_workque Shorten the workqueue name to "hdmi_frl_status_polling_wq" like the structure member to avoid the warning. Fixes: 5c9b8b27a883 ("drm/amd/display: Tie FRL support into amdgpu_dm") Reviewed-by: Alex Hung Signed-off-by: Nathan Chancellor Reviewed-by: Tvrtko Ursulin Link: https://patch.msgid.link/20260618-amdgpu-fix-wq_name_len-warning-v2-1-ef0e2e6f5be7@kernel.org Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 767ae341b68193fda5fdbc510b2d77e3e8938039) --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 2fe934036e36..a95243656f54 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -747,9 +747,9 @@ static int amdgpu_dm_init(struct amdgpu_device *adev) } if (adev->dm.dc->caps.max_links > 0) { adev->dm.hdmi_frl_status_polling_wq = - create_singlethread_workqueue("hdmi_frl_status_polling_workqueue"); + create_singlethread_workqueue("hdmi_frl_status_polling_wq"); if (!adev->dm.hdmi_frl_status_polling_wq) - drm_err(adev_to_drm(adev), "failed to initialize hdmi_frl_status_polling_workqueue\n"); + drm_err(adev_to_drm(adev), "failed to initialize hdmi_frl_status_polling_wq\n"); } if (dc_is_dmub_outbox_supported(adev->dm.dc)) { init_completion(&adev->dm.dmub_aux_transfer_done); From 3001d2073d6542a9e51fa5bca3a39a078094d3c6 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Thu, 27 Aug 2026 13:12:46 -0400 Subject: [PATCH 1422/2366] drm/amd/display: Exit IPS before connector detection on resume [Why & How] On resume, dm_resume() walks the connector list and, for each connector, calls dc_link_detect_connection_type() at the top of the loop iteration before the per-connector dc_exit_ips_for_hw_access() that sits in the detection branch. There is no dc_exit_ips_for_hw_access() before the loop, so the very first HW access relies on an earlier connector having already taken the display out of IPS. Commit d1d51519bc3b ("drm/amd/display: Skip eDP detection when no sink") skips the eDP connector when no panel is present. On a DCN3.5 APU whose eDP link has no sink, the eDP iteration - which used to bring the HW out of IPS first - is now skipped, so a downstream DP connector becomes the first one processed. Its initial DDC/AUX access then runs while the HW is still idle, the AUX transfers time out (-ETIMEDOUT), and the EDID read fails: [drm:dm_helpers_read_local_edid [amdgpu]] *ERROR* EDID err: 2, on connector: DP-1 amdgpu: [drm] *ERROR* No EDID read. Take the display out of IPS once before the detection loop so the first connector processed no longer touches the AUX/DDC engine while the HW is still in idle power state. This keeps the eDP-skip boot/resume optimization while fixing the DP EDID read failure. Fixes: d1d51519bc3b ("drm/amd/display: Skip eDP detection when no sink") Reviewed-by: Roman Li Signed-off-by: Fangzhi Zuo Signed-off-by: Ray Wu Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 86420fe3093161971b4064e05be11ffff1df76aa) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index a95243656f54..08b8605029ab 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1972,6 +1972,10 @@ static int dm_resume(struct amdgpu_ip_block *ip_block) /* On resume we need to rewrite the MSTM control bits to enable MST*/ s3_handle_mst(ddev, false); + /* Exit IPS before the detection loop's first AUX/DDC access. */ + scoped_guard(mutex, &dm->dc_lock) + dc_exit_ips_for_hw_access(dm->dc); + /* Do detection*/ drm_connector_list_iter_begin(ddev, &iter); drm_for_each_connector_iter(connector, &iter) { From 5d7e0cc4afda0cb25c0829dc7c1bb4bedd7886a5 Mon Sep 17 00:00:00 2001 From: Fangzhi Zuo Date: Wed, 26 Aug 2026 17:47:41 -0400 Subject: [PATCH 1423/2366] drm/amd/display: Fix HF-VSDB DSC bpc detection to be cumulative [Why & How] The HDMI Forum VSDB reports the maximum DSC color depth a sink supports. This maximum is cumulative: a sink that reports 12 bpc also supports 10 and 8 bpc. The previous code used exact "== 10" and "== 12" comparisons chained with else-if, so a 12 bpc sink only set frl_dsc_12bpc and never set frl_dsc_10bpc, incorrectly narrowing the DSC bpc range usable with that sink. Use ">= 10" and a separate ">= 12" check so a sink advertising a higher maximum also enables the lower DSC bit depths it supports. Reviewed-by: Alex Hung Signed-off-by: Fangzhi Zuo Signed-off-by: Ray Wu Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 4523adbf4dca157aea96a6f28b4e7b7ebd4d5eda) --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 5 +++-- .../drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index d451082552e8..249e0cd995c6 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -1202,9 +1202,10 @@ void populate_hdmi_info_from_connector(bool enable_frl, struct drm_hdmi_info *hd edid_caps->max_frl_rate = get_max_frl_rate(hdmi->max_lanes, hdmi->max_frl_rate_per_lane); edid_caps->frl_dsc_support = hdmi->dsc_cap.v_1p2; if (edid_caps->frl_dsc_support) { - if (hdmi->dsc_cap.bpc_supported == 10) + /* HF-VSDB DSC max bpc is cumulative: >=12 implies 10 and 8. */ + if (hdmi->dsc_cap.bpc_supported >= 10) edid_caps->frl_dsc_10bpc = true; - else if (hdmi->dsc_cap.bpc_supported == 12) + if (hdmi->dsc_cap.bpc_supported >= 12) edid_caps->frl_dsc_12bpc = true; edid_caps->frl_dsc_all_bpp = hdmi->dsc_cap.all_bpp; edid_caps->frl_dsc_native_420 = hdmi->dsc_cap.native_420; diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 058e1ad15dfe..0c4517fb9d75 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -909,7 +909,7 @@ static void dm_test_populate_hdmi_frl_dsc_12bpc(struct kunit *test) KUNIT_EXPECT_EQ(test, caps->max_frl_rate, 2); KUNIT_EXPECT_TRUE(test, caps->frl_dsc_support); - KUNIT_EXPECT_FALSE(test, caps->frl_dsc_10bpc); + KUNIT_EXPECT_TRUE(test, caps->frl_dsc_10bpc); KUNIT_EXPECT_TRUE(test, caps->frl_dsc_12bpc); KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_slices, 7); KUNIT_EXPECT_EQ(test, caps->frl_dsc_max_frl_rate, 1); From 1f1d43418d61c8511779e0f63a954a78b9433b43 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Mon, 31 Aug 2026 10:51:04 +0800 Subject: [PATCH 1424/2366] drm/amdgpu: skip gfx switch_power_profile during GPU reset During resume from GPU reset, the gfx idle work may invoke switch_power_profile before the reset completes. This causes the following assert error because the register access occurs without first releasing the GPU reset semaphore: [ 1576.768935] CR2: 0000559ea133ead0 CR3: 00000002e6c42000 CR4: 0000000000350ef0 [ 1576.768940] Call Trace: [ 1576.768944] [ 1576.768953] amdgpu_device_rreg+0x21/0x50 [amdgpu] [ 1576.769158] smu_msg_v1_send_msg+0x1a4/0x6e0 [amdgpu] [ 1576.769437] smu_cmn_send_smc_msg_with_params_ext+0xba/0x120 [amdgpu] [ 1576.769721] smu_cmn_send_smc_msg_with_param+0x33/0x40 [amdgpu] [ 1576.769993] smu_v13_0_0_set_power_profile_mode+0x192/0x2b0 [amdgpu] [ 1576.770267] smu_bump_power_profile_mode+0x5d/0x80 [amdgpu] [ 1576.770538] smu_switch_power_profile+0xa4/0xf0 [amdgpu] [ 1576.770839] amdgpu_dpm_switch_power_profile+0x6f/0x90 [amdgpu] [ 1576.771210] amdgpu_gfx_profile_idle_work_handler+0xe9/0x130 [amdgpu] [ 1576.771460] process_one_work+0x23e/0x6f0 [ 1576.771491] worker_thread+0x1c4/0x380 [ 1576.771506] kthread+0x10c/0x150 [ 1576.771512] ? __pfx_worker_thread+0x10/0x10 [ 1576.771518] ? __pfx_kthread+0x10/0x10 [ 1576.771530] ret_from_fork+0x314/0x390 [ 1576.771537] ? __pfx_kthread+0x10/0x10 [ 1576.771546] ret_from_fork_asm+0x1a/0x30 Signed-off-by: Prike Liang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit d93b1ff538ce9750c01e0dd0aa62575579c0fc08) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/pm/amdgpu_dpm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c index ce526db4d24a..808be6c425bf 100644 --- a/drivers/gpu/drm/amd/pm/amdgpu_dpm.c +++ b/drivers/gpu/drm/amd/pm/amdgpu_dpm.c @@ -348,7 +348,8 @@ int amdgpu_dpm_switch_power_profile(struct amdgpu_device *adev, const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; int ret = 0; - if (amdgpu_sriov_vf(adev)) + if (amdgpu_sriov_vf(adev) || + amdgpu_in_reset(adev)) return 0; if (pp_funcs && pp_funcs->switch_power_profile) { @@ -367,7 +368,8 @@ int amdgpu_dpm_pause_power_profile(struct amdgpu_device *adev, const struct amd_pm_funcs *pp_funcs = adev->powerplay.pp_funcs; int ret = 0; - if (amdgpu_sriov_vf(adev)) + if (amdgpu_sriov_vf(adev) || + amdgpu_in_reset(adev)) return 0; if (pp_funcs && pp_funcs->pause_power_profile) { From 829157e762ed043e28ecb2e9f3c7d22b54e5989e Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Tue, 8 Sep 2026 16:59:51 -0300 Subject: [PATCH 1425/2366] Revert "drm/amdgpu: debugfs: avoid extra EOLs in amdgpu_gem_info" This reverts commit c119d05a36a884482decc67e55944648f8cba97e. It removes the newline even when there are no fences attached to a struct dma_resv, leading to multiple BOs being output on the same line, making the debug file less readable, not more as the commit intended. Signed-off-by: Thadeu Lima de Souza Cascardo Signed-off-by: Alex Deucher (cherry picked from commit a2aafaeb2be13ed3c893e6a44a3a5d26b251ae6a) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_object.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c index 5d9d137209b6..1b6c32a177fb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.c @@ -1701,9 +1701,8 @@ u64 amdgpu_bo_print_info(int id, struct amdgpu_bo *bo, struct seq_file *m) if (dma_resv_trylock(bo->tbo.base.resv)) { dma_resv_describe(bo->tbo.base.resv, m); dma_resv_unlock(bo->tbo.base.resv); - } else { - seq_puts(m, "\n"); } + seq_puts(m, "\n"); return size; } From bdcd0411d7d186225a52458fd42bb70d54ca917a Mon Sep 17 00:00:00 2001 From: Satyajit Roy Date: Sun, 30 Aug 2026 03:51:58 +0000 Subject: [PATCH 1426/2366] drm/amd/display: Propagate HDMI RGB quantization selectability DC uses dc_edid_caps.qs_bit when constructing the HDMI AVI InfoFrame quantization-range field. Although DRM parses the sink capability into drm_display_info, DM never copies it into the DC EDID capabilities. The field therefore remains zero and the AVI quantization range stays at its default value. Copy rgb_quant_range_selectable for HDMI sinks and extend the existing EDID-capability KUnit test to cover it. Fixes: 6eb4c13a3845 ("drm/amd/display: Support "Broadcast RGB" drm property") Signed-off-by: Satyajit Roy Reviewed-by: Alex Hung Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 892659399f64642e33072562a11ec1b2e7bd2263) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 1 + .../drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 249e0cd995c6..298de7b75ca8 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -200,6 +200,7 @@ enum dc_edid_status dm_helpers_parse_edid_caps( edid_caps->edid_hdmi = connector->display_info.is_hdmi; if (edid_caps->edid_hdmi) { + edid_caps->qs_bit = connector->display_info.rgb_quant_range_selectable; populate_hdmi_info_from_connector(link->dc->config.enable_frl, &connector->display_info.hdmi, edid_caps); drm_dbg_driver(connector->dev, "%s: HDMI_FRL [%s] max_frl_rate %d\n", __func__, connector->name, edid_caps->max_frl_rate); if (edid_caps->frl_dsc_support) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 0c4517fb9d75..82e0c984693c 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -358,12 +358,14 @@ static void dm_test_parse_edid_caps_hdmi_frl(struct kunit *test) /* Drive the HDMI/FRL branch */ connector->display_info.is_hdmi = true; + connector->display_info.rgb_quant_range_selectable = true; connector->display_info.hdmi.scdc.supported = true; connector->display_info.hdmi.max_lanes = 4; connector->display_info.hdmi.max_frl_rate_per_lane = 12; KUNIT_EXPECT_EQ(test, dm_helpers_parse_edid_caps(link, dc_edid, edid_caps), EDID_OK); KUNIT_EXPECT_TRUE(test, edid_caps->edid_hdmi); + KUNIT_EXPECT_EQ(test, edid_caps->qs_bit, 1); KUNIT_EXPECT_TRUE(test, edid_caps->scdc_present); /* max_lanes 4 + max_frl_rate_per_lane 12 -> rate index 6 */ KUNIT_EXPECT_EQ(test, edid_caps->max_frl_rate, 6); From 7fca7acd60a228b62b4e9efa5f184738041e9564 Mon Sep 17 00:00:00 2001 From: Satyajit Roy Date: Sun, 30 Aug 2026 03:52:06 +0000 Subject: [PATCH 1427/2366] drm/amd/display: Honor Broadcast RGB for BT.2020 RGB output amdgpu_dm_get_output_color_space() applies the Broadcast RGB connector property to default RGB output, but always selects full-range output for BT.2020 RGB. Consequently, explicitly selecting Limited has no effect on the output CSC or AVI InfoFrame when HDR uses BT.2020 RGB. Select COLOR_SPACE_2020_RGB_LIMITEDRANGE when the output encoding is RGB and Broadcast RGB is Limited. Keep Automatic and Full at full range, and leave YCbCr output unchanged. Add KUnit coverage for limited-range RGB output through both BT.2020 connector colorspace values. Fixes: 6eb4c13a3845 ("drm/amd/display: Support "Broadcast RGB" drm property") Signed-off-by: Satyajit Roy Reviewed-by: Alex Hung Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 022236eaa63bbf65761aa8aec43f661451a94654) Cc: stable@vger.kernel.org --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 10 +++-- .../tests/amdgpu_dm_connector_test.c | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index d03773887214..154e1f35dcb1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -756,10 +756,14 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, break; case DRM_MODE_COLORIMETRY_BT2020_RGB: case DRM_MODE_COLORIMETRY_BT2020_YCC: - if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) - color_space = COLOR_SPACE_2020_RGB_FULLRANGE; - else + if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) { + if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) + color_space = COLOR_SPACE_2020_RGB_LIMITEDRANGE; + else + color_space = COLOR_SPACE_2020_RGB_FULLRANGE; + } else { color_space = COLOR_SPACE_2020_YCBCR_LIMITED; + } break; case DRM_MODE_COLORIMETRY_DEFAULT: /* ITU601 */ default: diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 0d2f9dbce0a9..212a7536e65b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -567,6 +567,23 @@ static void dm_test_output_color_space_bt2020_rgb(struct kunit *test) (int)COLOR_SPACE_2020_RGB_FULLRANGE); } +/** + * dm_test_output_color_space_bt2020_rgb_limited - Test limited BT.2020 RGB + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_rgb_limited(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_LIMITED; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_RGB_LIMITEDRANGE); +} + /** * dm_test_output_color_space_bt2020_ycc - Test Output color space bt2020 ycc * @test: The KUnit test context @@ -638,6 +655,24 @@ static void dm_test_output_color_space_bt2020_ycc_rgb_encoding(struct kunit *tes (int)COLOR_SPACE_2020_RGB_FULLRANGE); } +/** + * dm_test_output_color_space_bt2020_ycc_rgb_encoding_limited - Test limited + * BT.2020 RGB output selected through the BT.2020 YCC connector colorspace + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_ycc_rgb_encoding_limited(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_YCC; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_LIMITED; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_RGB_LIMITEDRANGE); +} + /** * dm_test_output_color_space_bt2020_rgb_ycc_encoding - Test Output color space * bt2020 rgb with non-rgb pixel encoding falls back to limited ycbcr @@ -5422,10 +5457,12 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_output_color_space_bt709_y_only), KUNIT_CASE(dm_test_output_color_space_oprgb), KUNIT_CASE(dm_test_output_color_space_bt2020_rgb), + KUNIT_CASE(dm_test_output_color_space_bt2020_rgb_limited), KUNIT_CASE(dm_test_output_color_space_bt2020_ycc), KUNIT_CASE(dm_test_output_color_space_default_ycbcr709_y_only), KUNIT_CASE(dm_test_output_color_space_default_ycbcr601), KUNIT_CASE(dm_test_output_color_space_bt2020_ycc_rgb_encoding), + KUNIT_CASE(dm_test_output_color_space_bt2020_ycc_rgb_encoding_limited), KUNIT_CASE(dm_test_output_color_space_bt2020_rgb_ycc_encoding), /* Tests for amdgpu_dm_convert_dc_color_depth_into_bpc */ KUNIT_CASE(dm_test_convert_color_depth_bpc_mappings), From 8cfd9e22eb5c04b15b82985ff913944f84673d4f Mon Sep 17 00:00:00 2001 From: Satyajit Roy Date: Sun, 30 Aug 2026 03:52:13 +0000 Subject: [PATCH 1428/2366] drm/amd/display: Rebuild InfoFrames on output color space changes resource_build_info_frame() derives colorimetry and RGB quantization from stream->output_color_space. A Broadcast RGB-only atomic commit updates that field and reprograms the output CSC, but none of the InfoFrame update predicates include output_color_space. The sink can therefore retain the previous AVI InfoFrame range while the source starts transmitting a different pixel range. Treat an output color space change as an InfoFrame change in update classification and in both stream programming paths. Hardware testing on an HDMI 2.1 television confirmed that its automatic black-level selection follows Full to Limited and Limited to Full transitions in SDR, HDR, and HDR with VRR active, without a modeset or visible link blank. Fixes: 6eb4c13a3845 ("drm/amd/display: Support "Broadcast RGB" drm property") Signed-off-by: Satyajit Roy Reviewed-by: Alex Hung Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit d6faca79f5720893843e649e70aeb19147ee0578) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/core/dc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index a98ed4617a03..519ac878ada1 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -3198,6 +3198,7 @@ static struct dc_update_descriptor check_update_surfaces_for_stream( } if ((stream_update->hdr_static_metadata && !stream_update->stream->use_dynamic_meta) || + stream_update->output_color_space || stream_update->vrr_infopacket || stream_update->vsc_infopacket || stream_update->vsp_infopacket || @@ -4188,6 +4189,7 @@ static void commit_planes_do_stream_update_sequence(struct dc *dc, hwss_add_setup_periodic_interrupt(&seq_state, dc, pipe_ctx); if ((stream_update->hdr_static_metadata && !stream->use_dynamic_meta) || + stream_update->output_color_space || stream_update->vrr_infopacket || stream_update->vsc_infopacket || stream_update->vsp_infopacket || @@ -4370,6 +4372,7 @@ static void commit_planes_do_stream_update(struct dc *dc, dc->hwss.setup_periodic_interrupt(dc, pipe_ctx); if ((stream_update->hdr_static_metadata && !stream->use_dynamic_meta) || + stream_update->output_color_space || stream_update->vrr_infopacket || stream_update->vsc_infopacket || stream_update->vsp_infopacket || From 13ddcc7acb9adbed7627e952a61d1d62cd9546fc Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Wed, 26 Aug 2026 09:51:55 +0800 Subject: [PATCH 1429/2366] drm/amd/pm: fix gpu metrics energy accumulator for smu 13.0.0/13.0.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GPU metrics v1.3 defines energy_accumulator as a 64‑bit field. The unsupported‑firmware code path assigns UINT_MAX, which is neither the full‑width invalid value for this field nor its default value. Fixes: 8de9edb35976 ("drm/amd/pm: remove invalid gpu_metrics.energy_accumulator on smu v13.0.x") Signed-off-by: Kevin Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher (cherry picked from commit c2b948c4fe16eb13d98ff5d1371956cb2f55cdc6) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c | 2 -- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c index 6e741ec4a71e..87b80b6f83b5 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c @@ -2095,8 +2095,6 @@ static ssize_t smu_v13_0_0_get_gpu_metrics(struct smu_context *smu, if ((mp1_ver == IP_VERSION(13, 0, 0) && smu->smc_fw_version <= 0x004e1e00) || (mp1_ver == IP_VERSION(13, 0, 10) && smu->smc_fw_version <= 0x00500800)) gpu_metrics->energy_accumulator = metrics->EnergyAccumulator; - else - gpu_metrics->energy_accumulator = UINT_MAX; if (metrics->AverageGfxActivity <= SMU_13_0_0_BUSY_THRESHOLD) gpu_metrics->average_gfxclk_frequency = metrics->AverageGfxclkFrequencyPostDs; diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c index b94ae43586df..5fe409a23772 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_7_ppt.c @@ -2097,8 +2097,8 @@ static ssize_t smu_v13_0_7_get_gpu_metrics(struct smu_context *smu, metrics->Vcn1ActivityPercentage); gpu_metrics->average_socket_power = metrics->AverageSocketPower; - gpu_metrics->energy_accumulator = smu->smc_fw_version <= 0x00521400 ? - metrics->EnergyAccumulator : UINT_MAX; + if (smu->smc_fw_version <= 0x00521400) + gpu_metrics->energy_accumulator = metrics->EnergyAccumulator; if (metrics->AverageGfxActivity <= SMU_13_0_7_BUSY_THRESHOLD) gpu_metrics->average_gfxclk_frequency = metrics->AverageGfxclkFrequencyPostDs; From f3c6a8ae601abf2d8476d3f899283cc9a1001f7d Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 8 Sep 2026 18:15:41 +0800 Subject: [PATCH 1430/2366] drm/amd/pm: report energy accumulator for smu 13.0.0 add energy accumulator on pmfw 0x004e8600 and above version. Signed-off-by: Kevin Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher (cherry picked from commit 3a804a5b15c22e4d7a3906ff09035e539785813e) --- drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c index 87b80b6f83b5..a29e76b03476 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu13/smu_v13_0_0_ppt.c @@ -2092,8 +2092,10 @@ static ssize_t smu_v13_0_0_get_gpu_metrics(struct smu_context *smu, gpu_metrics->average_socket_power = metrics->AverageSocketPower; - if ((mp1_ver == IP_VERSION(13, 0, 0) && smu->smc_fw_version <= 0x004e1e00) || - (mp1_ver == IP_VERSION(13, 0, 10) && smu->smc_fw_version <= 0x00500800)) + if ((mp1_ver == IP_VERSION(13, 0, 0) && + (smu->smc_fw_version <= 0x004e1e00 || smu->smc_fw_version >= 0x004e8600)) || + (mp1_ver == IP_VERSION(13, 0, 10) && + smu->smc_fw_version <= 0x00500800)) gpu_metrics->energy_accumulator = metrics->EnergyAccumulator; if (metrics->AverageGfxActivity <= SMU_13_0_0_BUSY_THRESHOLD) From a19d4f9b8befdcfcd5a87bab91312fe64af3bbb8 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Sat, 6 Jun 2026 23:40:49 -0700 Subject: [PATCH 1431/2366] ata: pata_legacy: remove documentation for removed module parameters Commit 3c4d783f6922 ("ata: pata_legacy: remove VLB support") removed several module parameters from the pata_legacy driver, but neglected to remove their documentation. Remove it. Fixes: 3c4d783f6922 ("ata: pata_legacy: remove VLB support") Cc: stable@vger.kernel.org # 7.0+ Signed-off-by: Ethan Nelson-Moore Reviewed-by: Karl Mehltretter Reviewed-by: Damien Le Moal Reviewed-by: Randy Dunlap Link: https://lore.kernel.org/r/20260607064053.195166-1-enelsonmoore@gmail.com Signed-off-by: Niklas Cassel --- .../admin-guide/kernel-parameters.txt | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 68647ff4bdd2..33cd30996e47 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4987,18 +4987,6 @@ Kernel parameters Set to non-zero if a chip is present that snoops speed changes. Disabled by default. - pata_legacy.ht6560a= [HW,LIBATA] - Format: - Set to 1, 2, or 3 for HT 6560A on the primary channel, - the secondary channel, or both channels respectively. - Disabled by default. - - pata_legacy.ht6560b= [HW,LIBATA] - Format: - Set to 1, 2, or 3 for HT 6560B on the primary channel, - the secondary channel, or both channels respectively. - Disabled by default. - pata_legacy.iordy_mask= [HW,LIBATA] Format: IORDY enable mask. Set individual bits to allow IORDY @@ -5011,18 +4999,6 @@ Kernel parameters with the sequence. By default IORDY is allowed across all channels. - pata_legacy.opti82c46x= [HW,LIBATA] - Format: - Set to 1, 2, or 3 for Opti 82c611A on the primary - channel, the secondary channel, or both channels - respectively. Disabled by default. - - pata_legacy.opti82c611a= [HW,LIBATA] - Format: - Set to 1, 2, or 3 for Opti 82c465MV on the primary - channel, the secondary channel, or both channels - respectively. Disabled by default. - pata_legacy.pio_mask= [HW,LIBATA] Format: PIO mode mask for autospeed devices. Set individual @@ -5046,19 +5022,6 @@ Kernel parameters the first port in the list above (0x1f0), and so on. By default all supported ports are probed. - pata_legacy.qdi= [HW,LIBATA] - Format: - Set to non-zero to probe QDI controllers. By default - set to 1 if CONFIG_PATA_QDI_MODULE, 0 otherwise. - - pata_legacy.winbond= [HW,LIBATA] - Format: - Set to non-zero to probe Winbond controllers. Use - the standard I/O port (0x130) if 1, otherwise the - value given is the I/O port to use (typically 0x1b0). - By default set to 1 if CONFIG_PATA_WINBOND_VLB_MODULE, - 0 otherwise. - pata_platform.pio_mask= [HW,LIBATA] Format: Supported PIO mode mask. Set individual bits to allow From f65d38155aef069c897a64643a03db237dd1e0c8 Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 3 Aug 2026 10:47:01 +0100 Subject: [PATCH 1432/2366] x86/div64: Fix addition of large constants in mul_u64_add_u64_div_u64() Adding constants over 2^31 fails to compile because the ADD instruction only supports 32bit signed immediates. Replace the "irm" constraint with "erm" so that the compiler loads large constants into a register. Found by a patch to drivers/iio/frequency/ad9910.c [ bp: Massage commit message. ] Fixes: 6480241f31f5 ("lib: add mul_u64_add_u64_div_u64() and mul_u64_u64_div_u64_roundup()") Signed-off-by: David Laight Signed-off-by: Borislav Petkov (AMD) Reviewed-by: H. Peter Anvin Link: https://patch.msgid.link/20260803094702.3852-2-david.laight.linux@gmail.com --- arch/x86/include/asm/div64.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/include/asm/div64.h b/arch/x86/include/asm/div64.h index 30fd06ede751..8a2d343f977e 100644 --- a/arch/x86/include/asm/div64.h +++ b/arch/x86/include/asm/div64.h @@ -111,7 +111,7 @@ static inline u64 mul_u64_add_u64_div_u64(u64 rax, u64 mul, u64 add, u64 div) if (!statically_true(!add)) asm ("addq %[add], %[lo]; adcq $0, %[hi]" : - [lo] "+r" (rax), [hi] "+r" (rdx) : [add] "irm" (add)); + [lo] "+r" (rax), [hi] "+r" (rdx) : [add] "erm" (add)); asm ("divq %[div]" : "+a" (rax), "+d" (rdx) : [div] "rm" (div)); From adf5967331318bcb436fc80069915231ec039352 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 6 Sep 2026 19:03:47 +0200 Subject: [PATCH 1433/2366] drm/msm: Fix the separate_gpu_kms parameter description The module parameter is separate_gpu_kms, but its MODULE_PARM_DESC() names separate_gpu_drm, so modinfo describes a parameter that does not exist and shows no description for the real one. Use the parameter name in the description. Fixes: 217ed15bd399 ("drm/msm: enable separate binding of GPU and display devices") Assisted-by: LLM Signed-off-by: Karl Mehltretter Patchwork: https://patchwork.freedesktop.org/patch/751407/ Message-ID: <20260906170347.2427-1-kmehltretter@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index db1b655dd055..f3d2eaa04f14 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -55,7 +55,7 @@ MODULE_PARM_DESC(modeset, "Use kernel modesetting [KMS] (1=on (default), 0=disab module_param(modeset, bool, 0600); static bool separate_gpu_kms; -MODULE_PARM_DESC(separate_gpu_drm, "Use separate DRM device for the GPU (0=single DRM device for both GPU and display (default), 1=two DRM devices)"); +MODULE_PARM_DESC(separate_gpu_kms, "Use separate DRM device for the GPU (0=single DRM device for both GPU and display (default), 1=two DRM devices)"); module_param(separate_gpu_kms, bool, 0400); DECLARE_FAULT_ATTR(fail_gem_alloc); From 8061ee61b9426fe38350fa9eead2d9c50b03deb6 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 6 Sep 2026 19:03:01 +0200 Subject: [PATCH 1434/2366] drm/msm/adreno: Fix the skip_gpu parameter description The module parameter is skip_gpu, but its MODULE_PARM_DESC() names no_gpu, so modinfo describes a parameter that does not exist and shows no description for the real one. Use the parameter name in the description. Fixes: 3f17991488af ("drm/msm/adreno: Add a modparam to skip GPU") Assisted-by: LLM Signed-off-by: Karl Mehltretter Patchwork: https://patchwork.freedesktop.org/patch/751406/ Message-ID: <20260906170301.2393-1-kmehltretter@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/adreno_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/adreno_device.c b/drivers/gpu/drm/msm/adreno/adreno_device.c index 7f20320ef66a..05c77fe27e62 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_device.c +++ b/drivers/gpu/drm/msm/adreno/adreno_device.c @@ -25,7 +25,7 @@ MODULE_PARM_DESC(disable_acd, "Forcefully disable GPU ACD"); module_param_unsafe(disable_acd, bool, 0400); static bool skip_gpu; -MODULE_PARM_DESC(no_gpu, "Disable GPU driver register (0=enable GPU driver register (default), 1=skip GPU driver register"); +MODULE_PARM_DESC(skip_gpu, "Disable GPU driver register (0=enable GPU driver register (default), 1=skip GPU driver register"); module_param(skip_gpu, bool, 0400); extern const struct adreno_gpulist a2xx_gpulist; From 01c8d1f385f788f1bbbbb7687c4386d614281218 Mon Sep 17 00:00:00 2001 From: "Jonghyuk Kim(MalHyuk)" Date: Wed, 2 Sep 2026 10:27:20 +0900 Subject: [PATCH 1435/2366] drm/msm: RCU-free the scheduler-containing ring and VM objects Both struct msm_ringbuffer and struct msm_gem_vm embed a struct drm_gpu_scheduler. msm_ringbuffer_destroy() and the VM free callback msm_gem_vm_free() call drm_sched_fini() on the embedded scheduler and then free the containing object with plain kfree(). drm_sched_fence_get_timeline_name() returns fence->sched->name, and the scheduler fence keeps a .release callback so it is not ops-detached on signalling. A finished fence exported to userspace (the submit out-fence, or a VM_BIND fence, via sync_file / drm_syncobj) keeps pointing at the embedded scheduler after the ring/VM is freed, so a later get_timeline_name() -- reachable unprivileged through SYNC_IOC_FILE_INFO -- dereferences freed slab memory (KASAN slab-use-after-free read). Per the dma-fence lifetime contract the exporter must keep the data backing a signalled fence alive for an RCU grace period. Free the scheduler-containing objects with kfree_rcu() instead of kfree(). Fixes: 1d8a5ca436ee ("drm/msm: Conversion to drm scheduler") Fixes: 92395af63a99 ("drm/msm: Add VM_BIND submitqueue") Cc: stable@vger.kernel.org Signed-off-by: Jonghyuk Kim(MalHyuk) Patchwork: https://patchwork.freedesktop.org/patch/750234/ Message-ID: <20260902012720.880783-1-malhyuk97@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_gem.h | 3 +++ drivers/gpu/drm/msm/msm_gem_vma.c | 2 +- drivers/gpu/drm/msm/msm_ringbuffer.c | 2 +- drivers/gpu/drm/msm/msm_ringbuffer.h | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/msm_gem.h b/drivers/gpu/drm/msm/msm_gem.h index dff60cbc9d95..7c6a8c01f910 100644 --- a/drivers/gpu/drm/msm/msm_gem.h +++ b/drivers/gpu/drm/msm/msm_gem.h @@ -68,6 +68,9 @@ struct msm_gem_vm { /** @base: Inherit from drm_gpuvm. */ struct drm_gpuvm base; + /** @rcu: RCU-delayed free so an exported sched fence->sched stays valid. */ + struct rcu_head rcu; + /** * @sched: Scheduler used for asynchronous VM_BIND request. * diff --git a/drivers/gpu/drm/msm/msm_gem_vma.c b/drivers/gpu/drm/msm/msm_gem_vma.c index c11d021581e0..1badec3caa7b 100644 --- a/drivers/gpu/drm/msm/msm_gem_vma.c +++ b/drivers/gpu/drm/msm/msm_gem_vma.c @@ -166,7 +166,7 @@ msm_gem_vm_free(struct drm_gpuvm *gpuvm) dma_fence_put(vm->last_fence); put_pid(vm->pid); kfree(vm->log); - kfree(vm); + kfree_rcu(vm, rcu); } /** diff --git a/drivers/gpu/drm/msm/msm_ringbuffer.c b/drivers/gpu/drm/msm/msm_ringbuffer.c index 59c69aa75649..38e1e6866301 100644 --- a/drivers/gpu/drm/msm/msm_ringbuffer.c +++ b/drivers/gpu/drm/msm/msm_ringbuffer.c @@ -140,5 +140,5 @@ void msm_ringbuffer_destroy(struct msm_ringbuffer *ring) msm_gem_kernel_put(ring->bo, ring->gpu->vm); - kfree(ring); + kfree_rcu(ring, rcu); } diff --git a/drivers/gpu/drm/msm/msm_ringbuffer.h b/drivers/gpu/drm/msm/msm_ringbuffer.h index 3631ec283c6e..0bfb6b0f42fa 100644 --- a/drivers/gpu/drm/msm/msm_ringbuffer.h +++ b/drivers/gpu/drm/msm/msm_ringbuffer.h @@ -55,6 +55,7 @@ struct msm_ringbuffer { /* * The job scheduler for this ring. */ + struct rcu_head rcu; struct drm_gpu_scheduler sched; bool sched_initialized; From 3fb13d29cf8eb4502837bff06b2873c5435f6ffd Mon Sep 17 00:00:00 2001 From: Yang Zi <2959243019@qq.com> Date: Tue, 25 Aug 2026 16:58:15 +0800 Subject: [PATCH 1436/2366] fbdev: ssd1307fb: fix NULL pointer dereference on missing match data device_get_match_data() can return NULL, e.g. when the device is matched through the I2C device ID table rather than the OF match table. The returned value is stored in par->device_info and later dereferenced when initializing par->vcomh, causing a NULL pointer dereference. Check the return value right after the assignment and bail out with -ENODEV (releasing the already allocated framebuffer) before any dereference. Signed-off-by: Yang Zi <2959243019@qq.com> Signed-off-by: Helge Deller --- drivers/video/fbdev/ssd1307fb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index c4fdecafd856..4d185c754284 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -665,6 +665,10 @@ static int ssd1307fb_probe(struct i2c_client *client) spin_lock_init(&par->damage_lock); par->device_info = device_get_match_data(dev); + if (!par->device_info) { + ret = -ENODEV; + goto fb_alloc_error; + } par->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(par->reset)) { From 3934185ba63feca6e80cc8b90f7d7f01cde78223 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 27 Aug 2026 17:39:48 +0800 Subject: [PATCH 1437/2366] fbdev: atafb: Restrict SuperBlitter to supported formats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SuperBlitter operations derive an integer byte count per pixel. The accelerated fill path handles only one-, two- and four-byte pixels. However, the operations are currently installed for every external framebuffer in SuperVidel RAM, including planar 1/2/4/8-bpp and 24-bpp truecolor modes accepted by the external video parser. For 1/2/4-bpp modes, the byte count becomes zero, so accelerated copies do nothing and fills fall through to 32-bit stores. Planar 8-bpp uses an incompatible memory layout. For 24-bpp modes, fills also use 32-bit stores despite advancing addresses by three bytes per pixel. These cases can corrupt the framebuffer beyond the requested rectangle. Enable the SuperBlitter operations only for the layouts they implement: 8-bpp packed pixels and 16/32-bpp truecolor. Keep the existing software operations for all other external formats. Fixes: d463633d63e6 ("fbdev: atafb: Add support for SuperVidel's SuperBlitter") Signed-off-by: Linmao Li Tested-by: Miro Kropáček Reviewed-by: Michael Schmitz Signed-off-by: Helge Deller --- drivers/video/fbdev/atafb.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/video/fbdev/atafb.c b/drivers/video/fbdev/atafb.c index 5bca34c45cef..c3011b61a94b 100644 --- a/drivers/video/fbdev/atafb.c +++ b/drivers/video/fbdev/atafb.c @@ -3360,7 +3360,11 @@ static int __init atafb_probe(struct platform_device *pdev) memset (screen_base, 0, external_len); /* framebuffer in SV RAM: enable the SuperBlitter */ - if (external_addr >= 0xa0000000) { + if (external_addr >= 0xa0000000 && + ((external_pmode == FB_TYPE_PACKED_PIXELS && + external_depth == 8) || + (external_pmode == -1 && + (external_depth == 16 || external_depth == 32)))) { svblit_regs = ioremap(SVBLIT_REGS_PHYS, 0x100); if (svblit_regs) { svblit_fw = svblit_rd(SVBLIT_VERSION) & 0x1ff; From c4fa55f85c47cd5d54d717fb8170746edb10292e Mon Sep 17 00:00:00 2001 From: Mahmoud Nagy Adam Date: Wed, 9 Sep 2026 15:26:03 +0200 Subject: [PATCH 1438/2366] selftests: ublk: install test_common.sh and trace/ scripts Every ublk test script sources test_common.sh from its own directory: . "$(cd "$(dirname "$0")" && pwd)"/test_common.sh and test_generic_02/12 additionally run bpftrace against the scripts in trace/. Neither test_common.sh nor trace/ is listed in TEST_FILES, so "make install" does not copy them into the install directory and every ublk test fails when run from there: ./test_generic_02.sh: line 4: .../kselftest_install/ublk/test_common.sh: No such file or directory ./test_generic_02.sh: line 8: _have_program: command not found The bpftrace tests are affected even when bpftrace is installed: the missing trace/*.bt makes bpftrace exit immediately, and the tests then report a skip rather than a failure, which hides the problem. Add both to TEST_FILES, matching how other selftests ship their sourced helpers (see kexec/kexec_common_lib.sh and zram/zram_lib.sh). Fixes: 6aecda00b7d1e1 ("selftests: ublk: add kernel selftests for ublk") Fixes: 723977cab4c0fd ("selftests: ublk: add generic_01 for verifying sequential IO order") Cc: stable@vger.kernel.org # v6.15+ Assisted-by: Kiro:claude-opus-5 Signed-off-by: Mahmoud Nagy Adam Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260909132602.68852-2-mngyadam@amazon.de Signed-off-by: Jens Axboe --- tools/testing/selftests/ublk/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/selftests/ublk/Makefile b/tools/testing/selftests/ublk/Makefile index 5daf36c6c36c..37883e9d50ec 100644 --- a/tools/testing/selftests/ublk/Makefile +++ b/tools/testing/selftests/ublk/Makefile @@ -73,6 +73,8 @@ TEST_PROGS += test_stress_08.sh TEST_PROGS += test_stress_09.sh TEST_FILES := settings +TEST_FILES += test_common.sh +TEST_FILES += trace TEST_GEN_PROGS_EXTENDED = kublk metadata_size STANDALONE_UTILS := metadata_size.c From b0d8d56b7c93ed767eb4f2be9988e7b9dc023566 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 9 Sep 2026 09:06:31 +0100 Subject: [PATCH 1439/2366] block: Fix start and length check added to iov_iter_extract_bvecs() Commit 14b007e17881 added an address check using iter_iov_addr() and a length check using iter_iov_len() to iov_iter_extract_bvecs(), but these cannot be used so and are unsafe in this circumstance as the functions have hardwired assumptions about the iterator type. They should only be used with ITER_UBUF or ITER_IOVEC-type iterators; they shouldn't be used with ITER_BVEC, ITER_KVEC, ITER_FOLIOQ, ITER_XARRAY or ITER_DISCARD iterators. This proves to be a problem for cachefiles as an iterator of type ITER_FOLIOQ is passed and iter_iov_addr() and iter_iov_len() both malfunction because iter->__iov in iter_iov() is not pointing to an iovec array. Fix this by using iov_iter_alignment() instead. Fixes: 14b007e17881 ("block: validate user space vectors during extraction") Signed-off-by: David Howells Reviewed-by: Keith Busch cc: Hannes Reinecke cc: Christoph Hellwig cc: Jens Axboe cc: Alexander Viro cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-block@vger.kernel.org cc: linux-fsdevel@vger.kernel.org Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/1667275.1788941191@warthog.procyon.org.uk Signed-off-by: Jens Axboe --- lib/iov_iter.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/iov_iter.c b/lib/iov_iter.c index 6665372ecf71..2072c04e99d0 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -1921,15 +1921,29 @@ ssize_t iov_iter_extract_bvecs(struct iov_iter *iter, struct bio_vec *bv, unsigned short max_vecs, unsigned mem_align_mask, iov_iter_extraction_t extraction_flags) { - unsigned long start = (unsigned long)iter_iov_addr(iter); unsigned short entries_left = max_vecs - *nr_vecs; unsigned short nr_pages, i = 0; size_t left, offset, len; struct page **pages; ssize_t size; - if ((start | iter_iov_len(iter)) & mem_align_mask) + /* + * DMA engines typically have both memory address and length alignment + * requirements, so check these against the alignment mask. For UBUF, + * IOVEC and KVEC, only the current segment will be extracted from; for + * everything else we might extract from multiple segments, so we need + * to check those too. + */ + if (likely(iter_is_ubuf(iter) || + iter_is_iovec(iter) || + iov_iter_is_kvec(iter))) { + unsigned long start = (unsigned long)iter_iov_addr(iter); + + if ((start | iter_iov_len(iter)) & mem_align_mask) + return -EINVAL; + } else if (iov_iter_alignment(iter) & mem_align_mask) { return -EINVAL; + } /* * Move page array up in the allocated memory for the bio vecs as far as From 0c6da21fa35e03fc74f09895433ccd6d4a9c3530 Mon Sep 17 00:00:00 2001 From: Stian Halseth Date: Tue, 1 Sep 2026 19:39:45 +0200 Subject: [PATCH 1440/2366] sunvdc: unmap LDC cookies when the descriptor send fails __send_request() maps the request's pages into the LDC channel's map table (ldc_map_sg()), fills in the descriptor and marks it VIO_DESC_READY before ringing the doorbell via __vdc_tx_trigger(). When the trigger fails, the error path only prints a message: the descriptor stays READY and the cookies are never unmapped. The mapping is normally released in vdc_end_one() when the peer completes the descriptor - but a descriptor whose doorbell was never sent will never complete, and since dr->prod is not advanced on failure, the reset path (vdc_requeue_inflight(), which walks [cons, prod)) never visits it either. The map table entries are leaked permanently. Since commit a11f6ca9aef9 ("sunvdc: Do not spin in an infinite loop when vio_ldc_send() returns EAGAIN") trigger failures occur in practice under load, so every resulting I/O error also leaks one request's worth of entries from the fixed-size (8192 entries per channel) map table. Because the allocator hands out contiguous ranges, fragmentation makes large multi-segment requests fail first as the table drains, until ldc_map_sg() fails permanently and the disk is dead until reboot. It also makes any retry-based recovery unusable: requeuing the request on -EAGAIN remaps the pages on every attempt, overwriting desc->cookies and orphaning the previous mapping, so the table drains at the retry rate. This is the memory exhaustion observed when the requeue approach was first tested in October 2025. Roll back on failure: unmap the cookies, mark the descriptor FREE again and clear the request entry. If the trigger failed with -ENOTCONN, __vdc_tx_trigger() has already reset the port, which tears down and reallocates both the dring and the LDC channel including its map table - nothing to roll back, and the stale descriptor must not be touched. Fixes: a11f6ca9aef9 ("sunvdc: Do not spin in an infinite loop when vio_ldc_send() returns EAGAIN") Reported-by: John Paul Adrian Glaubitz Link: https://github.com/sparclinux/issues/issues/2 Signed-off-by: Stian Halseth Link: https://patch.msgid.link/20260901173947.3292110-2-stian@itx.no Signed-off-by: Jens Axboe --- drivers/block/sunvdc.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/block/sunvdc.c b/drivers/block/sunvdc.c index 020bd9f1a7b6..24ad56536ed6 100644 --- a/drivers/block/sunvdc.c +++ b/drivers/block/sunvdc.c @@ -525,6 +525,23 @@ static int __send_request(struct request *req) err = __vdc_tx_trigger(port); if (err < 0) { printk(KERN_ERR PFX "vdc_tx_trigger() failure, err=%d\n", err); + /* + * If the port was reset (-ENOTCONN), the dring and the + * LDC channel including all of its mappings are already + * torn down and reallocated - there is nothing to undo + * and @desc must not be touched. + * + * For any other failure the descriptor was never handed + * to the peer: unmap the cookies and free the descriptor + * again, so that a later retry of the request does not + * leak LDC map table entries. + */ + if (err != -ENOTCONN) { + ldc_unmap(port->vio.lp, desc->cookies, + desc->ncookies); + desc->hdr.state = VIO_DESC_FREE; + rqe->req = NULL; + } } else { port->req_id++; dr->prod = vio_dring_next(dr, dr->prod); From 5067d4ba713961d8ccea1e06cd4c453793f3121e Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 1 Sep 2026 19:39:46 +0200 Subject: [PATCH 1441/2366] sunvdc: fix -EIO issue due to lack of retries John reports that since commit: a11f6ca9aef9 ("sunvdc: Do not spin in an infinite loop when vio_ldc_send() returns EAGAIN") users of Linux inside Solaris ldom see occasional -EIO errors because the request send loop now times out. The current loop does 10 retries, and inside vio_ldc_send() a further 1000 1usec retries are done as well. Even with 10.5 msec of busy loop retries that's apparently not enough to always succeed. Rather than introduce continued busy looping, requeue the request and have the delayed queue kicking retry the request after another 10ms. This obviously isn't ideal, but there's seemingly no way to wait for this type of event. And if 10ms of busy looping was not enough to make progress, then presumably this is an edge condition and we just need to guarantee to make forward progress at some later point in time. That's more suitably done through letting the CPU tend to other work, rather than sitting in a tight loop retrying. [stian: rebased on top of the cookie-unmap fix, without which every requeued attempt leaks LDC map table entries; tested on an UltraSPARC T4 LDOM where the vdc_tx_trigger failure condition was reproduced and absorbed by the requeue with no I/O error] Reported-by: John Paul Adrian Glaubitz Link: https://lore.kernel.org/all/20251006100226.4246-2-glaubitz@physik.fu-berlin.de/ Link: https://lore.kernel.org/all/418310b3-2b77-4534-b2fd-27dcc11e333c@kernel.dk/ Signed-off-by: Stian Halseth Link: https://patch.msgid.link/20260901173947.3292110-3-stian@itx.no Signed-off-by: Jens Axboe --- drivers/block/sunvdc.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/block/sunvdc.c b/drivers/block/sunvdc.c index 24ad56536ed6..2be8231dcd5b 100644 --- a/drivers/block/sunvdc.c +++ b/drivers/block/sunvdc.c @@ -556,6 +556,7 @@ static blk_status_t vdc_queue_rq(struct blk_mq_hw_ctx *hctx, struct vdc_port *port = hctx->queue->queuedata; struct vio_dring_state *dr; unsigned long flags; + int ret; dr = &port->vio.drings[VIO_DRIVER_TX_RING]; @@ -577,7 +578,13 @@ static blk_status_t vdc_queue_rq(struct blk_mq_hw_ctx *hctx, return BLK_STS_DEV_RESOURCE; } - if (__send_request(bd->rq) < 0) { + ret = __send_request(bd->rq); + if (ret == -EAGAIN) { + spin_unlock_irqrestore(&port->vio.lock, flags); + /* already spun for 10msec, defer 10msec and retry */ + blk_mq_delay_kick_requeue_list(hctx->queue, 10); + return BLK_STS_DEV_RESOURCE; + } else if (ret < 0) { spin_unlock_irqrestore(&port->vio.lock, flags); return BLK_STS_IOERR; } From a0a34a40ed299c9c7cff6af163a5b883ee9d6d73 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Thu, 10 Sep 2026 03:10:23 +0800 Subject: [PATCH 1442/2366] fbdev: vfb: defer cleanup until the last reference FBIOGETCMAP takes a shallow snapshot of info->cmap and performs the usercopy after dropping info->lock. vfb_remove() frees the colormap immediately after unregistering the framebuffer, even when an open file still holds a reference to fb_info. A concurrent driver unbind can therefore free the colormap while the ioctl copies it to userspace. KASAN reports: BUG: KASAN: slab-use-after-free in _copy_to_user Read of size 512 by task poc/125 _copy_to_user (./include/linux/instrumented.h:129 ./include/linux/uaccess.h:201 lib/usercopy.c:24) fb_cmap_to_user (./include/linux/uaccess.h:230 drivers/video/fbdev/core/fbcmap.c:211) do_fb_ioctl (drivers/video/fbdev/core/fb_chrdev.c:114) Allocated by task 1: fb_alloc_cmap_gfp (./include/linux/slab.h:973 ./include/linux/slab.h:1290 drivers/video/fbdev/core/fbcmap.c:108) vfb_probe (drivers/video/fbdev/vfb.c:459) Freed by task 124: fb_dealloc_cmap (drivers/video/fbdev/core/fbcmap.c:151) vfb_remove (drivers/video/fbdev/vfb.c:489) unregister_framebuffer() drops the registration reference, and fbdev calls fb_destroy after the last put_fb_info(). Move the registered framebuffer's cleanup into an fb_destroy callback so its colormap and screen buffer stay alive until all file references have been released. Fixes: 5e266e2e0e19 ("vfb: fix memory leaks in removal path") Reported-by: co+c25629c98ba36ebe@bugs.sh Cc: stable@kernel.org Closes: https://lore.kernel.org/linux-fbdev/f2Kf9GYn1lKR5S1dbvGVtykMxK1RlgP5z8sW@bugs.sh/ Assisted-by: Codex:gpt-5 Signed-off-by: Weiming Shi Link: https://lore.kernel.org/linux-fbdev/f2Kf9GYn1lKR5S1dbvGVtykMxK1RlgP5z8sW@bugs.sh/ Signed-off-by: Helge Deller --- drivers/video/fbdev/vfb.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/video/fbdev/vfb.c b/drivers/video/fbdev/vfb.c index 5b7965f36c5e..de137b2bdaed 100644 --- a/drivers/video/fbdev/vfb.c +++ b/drivers/video/fbdev/vfb.c @@ -78,6 +78,13 @@ static int vfb_pan_display(struct fb_var_screeninfo *var, static int vfb_mmap(struct fb_info *info, struct vm_area_struct *vma); +static void vfb_destroy(struct fb_info *info) +{ + vfree(info->screen_buffer); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); +} + static const struct fb_ops vfb_ops = { .owner = THIS_MODULE, __FB_DEFAULT_SYSMEM_OPS_RDWR, @@ -87,6 +94,7 @@ static const struct fb_ops vfb_ops = { .fb_pan_display = vfb_pan_display, __FB_DEFAULT_SYSMEM_OPS_DRAW, .fb_mmap = vfb_mmap, + .fb_destroy = vfb_destroy, }; /* @@ -485,9 +493,6 @@ static void vfb_remove(struct platform_device *dev) if (info) { unregister_framebuffer(info); - vfree(videomemory); - fb_dealloc_cmap(&info->cmap); - framebuffer_release(info); } } From 88ce88e933e40f4ab4b81a5e9d0a10328583f593 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 23:35:07 +0200 Subject: [PATCH 1443/2366] bpf: Add KF_PERFMON kfunc flag Tracing related BPF helpers e.g. under bpf_base_func_proto() are gated behind CAP_PERFMON. However, the same is currently not true for kfuncs and they are accessible via plain CAP_BPF. Add a new KF_PERFMON flag which can be used such that check_kfunc_call() ensures env->allow_ptr_leaks is permitted. This follows similar pattern to existing KF_DESTRUCTIVE flag. The rejection returns -EPERM to match the other CAP_PERFMON gates in the verifier, that is, check_ptr_to_btf_access() and check_ptr_to_map_access(), which report the very same policy to user space. Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260910213510.49358-1-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- Documentation/bpf/kfuncs.rst | 10 ++++++++++ include/linux/btf.h | 1 + kernel/bpf/verifier.c | 14 ++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst index 85f73e0bbd0f..6691fe8a32c3 100644 --- a/Documentation/bpf/kfuncs.rst +++ b/Documentation/bpf/kfuncs.rst @@ -486,6 +486,16 @@ Example usage in BPF program: /* note that the last argument is omitted */ bpf_task_work_schedule_signal(task, &work->tw, &arrmap, task_work_callback); +2.5.10 KF_PERFMON flag +---------------------- + +The KF_PERFMON flag is used for kfuncs that can expose kernel memory or kernel +addresses to the BPF program, for example by reading through a pointer that the +verifier does not check. Calling such a kfunc requires CAP_PERFMON, or +CAP_SYS_ADMIN, in the same way that the equivalent BPF helpers are gated in +bpf_base_func_proto(). A program loaded with CAP_BPF alone is rejected at load +time. + 2.6 Registering the kfuncs -------------------------- diff --git a/include/linux/btf.h b/include/linux/btf.h index 89d5a5c4f117..7c62ea17b116 100644 --- a/include/linux/btf.h +++ b/include/linux/btf.h @@ -80,6 +80,7 @@ #define KF_ARENA_ARG2 (1 << 15) /* kfunc takes an arena pointer as its second argument */ #define KF_IMPLICIT_ARGS (1 << 16) /* kfunc has implicit arguments supplied by the verifier */ #define KF_SPINLOCK_SAFE (1 << 17) /* kfunc is allowed inside bpf_spin_lock-ed region */ +#define KF_PERFMON (1 << 18) /* kfunc requires CAP_PERFMON */ /* * Tag marking a kernel function as a kfunc. This is meant to minimize the diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 72a3f5998dd2..939e535a3442 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -11356,6 +11356,11 @@ static bool is_kfunc_destructive(struct bpf_call_arg_meta *meta) return meta->kfunc_flags & KF_DESTRUCTIVE; } +static bool is_kfunc_perfmon(struct bpf_call_arg_meta *meta) +{ + return meta->kfunc_flags & KF_PERFMON; +} + static bool is_kfunc_rcu(struct bpf_call_arg_meta *meta) { return meta->kfunc_flags & KF_RCU; @@ -13834,6 +13839,15 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn, return -EACCES; } + if (is_kfunc_perfmon(&meta) && !env->allow_ptr_leaks) { + verbose(env, "%s is allowed only to CAP_PERFMON and CAP_SYS_ADMIN\n", + func_name); + operation = bpf_diag_fmt(env, "kfunc %s", func_name); + bpf_diag_policy(env, insn_idx, operation, "the kfunc requires CAP_PERFMON", + "Load the program with CAP_PERFMON, or avoid the kfunc."); + return -EPERM; + } + sleepable = bpf_is_kfunc_sleepable(&meta); if (sleepable && !in_sleepable(env)) { verbose(env, "program must be sleepable to call sleepable kfunc %s\n", func_name); From 81c975aae375d2053fa2926ee3730f02e713140c Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 23:35:08 +0200 Subject: [PATCH 1444/2366] bpf: Require CAP_PERFMON for kfuncs reading memory Mark fault-safe probe reading kfuncs as KF_PERFMON, similarly as we do for the old-style BPF helper equivalents. bpf_rdonly_cast() is included in this list as well as it returns PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED for an unchecked object and is using fault-safe BPF_PROBE_MEM. Note that only the void form of bpf_rdonly_cast() produced a register that was readable without CAP_PERFMON. For a struct type id the kfunc returns PTR_TO_BTF_ID | PTR_UNTRUSTED, whose dereference has always been gated in check_ptr_to_btf_access(). The flag is not conditional on the type id, so for the latter it only moves the rejection from the dereference to the call itself, which is the better place to report it anyway. The bpf_stream_vprintk() and bpf_stream_print_stack() kfuncs are marked as well. The former ends up in the same bpf_bprintf_prepare() as the bpf_snprintf() helper, where %pks, %pus and %pI4 read through a program- supplied address and %pB resolves one into a symbol. The latter walks the stack and prints each instruction pointer via %pS. Lastly, bpf_get_kmem_cache() takes a raw scalar address that the verifier does not constrain and dereferences the page/slab metadata for it. Field reads on the returned pointer are still blocked (PTR_TO_BTF_ID | PTR_UNTRUSTED -> check_ptr_to_btf_access() results in -EPERM without the CAP), but the NULL/non-NULL result is observable. Reported-by: STAR Labs SG Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260910213510.49358-2-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- kernel/bpf/helpers.c | 60 ++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index b3cc5c8fc875..712dca5a2c5b 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -4883,7 +4883,7 @@ BTF_ID(func, bpf_cgroup_release_dtor) BTF_KFUNCS_START(common_btf_ids) BTF_ID_FLAGS(func, bpf_cast_to_kern_ctx, KF_FASTCALL) -BTF_ID_FLAGS(func, bpf_rdonly_cast, KF_FASTCALL) +BTF_ID_FLAGS(func, bpf_rdonly_cast, KF_FASTCALL | KF_PERFMON) BTF_ID_FLAGS(func, bpf_rcu_read_lock) BTF_ID_FLAGS(func, bpf_rcu_read_unlock) BTF_ID_FLAGS(func, bpf_dynptr_slice, KF_RET_NULL) @@ -4920,26 +4920,26 @@ BTF_ID_FLAGS(func, bpf_wq_set_callback, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_wq_start) BTF_ID_FLAGS(func, bpf_preempt_disable) BTF_ID_FLAGS(func, bpf_preempt_enable) -BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW) +BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW | KF_PERFMON) BTF_ID_FLAGS(func, bpf_iter_bits_next, KF_ITER_NEXT | KF_RET_NULL) BTF_ID_FLAGS(func, bpf_iter_bits_destroy, KF_ITER_DESTROY) -BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_get_kmem_cache) +BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_get_kmem_cache, KF_PERFMON) BTF_ID_FLAGS(func, bpf_iter_kmem_cache_new, KF_ITER_NEW | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_local_irq_save) BTF_ID_FLAGS(func, bpf_local_irq_restore) #ifdef CONFIG_BPF_EVENTS -BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr) -BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr) -BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr) -BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr) -BTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE) +BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE | KF_PERFMON) #endif #ifdef CONFIG_DMA_SHARED_BUFFER BTF_ID_FLAGS(func, bpf_iter_dmabuf_new, KF_ITER_NEW | KF_SLEEPABLE) @@ -4947,26 +4947,26 @@ BTF_ID_FLAGS(func, bpf_iter_dmabuf_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPAB BTF_ID_FLAGS(func, bpf_iter_dmabuf_destroy, KF_ITER_DESTROY | KF_SLEEPABLE) #endif BTF_ID_FLAGS(func, __bpf_trap) -BTF_ID_FLAGS(func, bpf_strcmp); -BTF_ID_FLAGS(func, bpf_strcasecmp); -BTF_ID_FLAGS(func, bpf_strncasecmp); -BTF_ID_FLAGS(func, bpf_strchr); -BTF_ID_FLAGS(func, bpf_strchrnul); -BTF_ID_FLAGS(func, bpf_strnchr); -BTF_ID_FLAGS(func, bpf_strrchr); -BTF_ID_FLAGS(func, bpf_strlen); -BTF_ID_FLAGS(func, bpf_strnlen); -BTF_ID_FLAGS(func, bpf_strspn); -BTF_ID_FLAGS(func, bpf_strcspn); -BTF_ID_FLAGS(func, bpf_strstr); -BTF_ID_FLAGS(func, bpf_strcasestr); -BTF_ID_FLAGS(func, bpf_strnstr); -BTF_ID_FLAGS(func, bpf_strncasestr); +BTF_ID_FLAGS(func, bpf_strcmp, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strcasecmp, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strncasecmp, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strchr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strchrnul, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strnchr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strrchr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strlen, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strnlen, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strspn, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strcspn, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strstr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strcasestr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strnstr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strncasestr, KF_PERFMON); #if defined(CONFIG_BPF_LSM) && defined(CONFIG_CGROUPS) BTF_ID_FLAGS(func, bpf_cgroup_read_xattr, KF_RCU) #endif -BTF_ID_FLAGS(func, bpf_stream_vprintk, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE) -BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE) +BTF_ID_FLAGS(func, bpf_stream_vprintk, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE | KF_PERFMON) BTF_ID_FLAGS(func, bpf_task_work_schedule_signal, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_task_work_schedule_resume, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_dynptr_from_file) From f9191460cd805e84eace0884c75c67ec719fa1d8 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 23:35:09 +0200 Subject: [PATCH 1445/2366] bpf: Require CAP_PERFMON for untrusted read-only memory reads Marking bpf_rdonly_cast() KF_PERFMON CAP-limits one producer of PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED, but not the type itself. A global subprogram argument tagged __arg_untrusted results in the same register with no kfunc call. Reported-by: STAR Labs SG Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260910213510.49358-3-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 939e535a3442..8058f684a9ea 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -6453,6 +6453,15 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b return -EACCES; } + if (rdonly_untrusted && !env->allow_ptr_leaks) { + verbose(env, "%s access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN\n", + reg_type_str(env, reg->type)); + bpf_diag_policy(env, insn_idx, "read from untrusted read-only memory", + "the access requires CAP_PERFMON", + "Load the program with CAP_PERFMON, or avoid dereferencing untrusted pointers."); + return -EPERM; + } + /* * Accesses to untrusted PTR_TO_MEM are done through probe * instructions, hence no need to check bounds in that case. From a903f145a8914d9ecba009b398d135154e4a5d94 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 23:35:10 +0200 Subject: [PATCH 1446/2366] selftests/bpf: Add tests for the KF_PERFMON gates Add test cases where each one loads with CAP_BPF alone and checks that the program is correctly rejected. # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t verifier_kfunc_perfmon [...] #627/1 verifier_kfunc_perfmon/rdonly_cast_noperfmon:OK #627/2 verifier_kfunc_perfmon/rdonly_cast_noperfmon @unpriv:OK #627/3 verifier_kfunc_perfmon/probe_read_kernel_dynptr_noperfmon:OK #627/4 verifier_kfunc_perfmon/probe_read_kernel_dynptr_noperfmon @unpriv:OK #627/5 verifier_kfunc_perfmon/stream_vprintk_noperfmon:OK #627/6 verifier_kfunc_perfmon/stream_vprintk_noperfmon @unpriv:OK #627/7 verifier_kfunc_perfmon/get_kmem_cache_noperfmon:OK #627/8 verifier_kfunc_perfmon/get_kmem_cache_noperfmon @unpriv:OK #627/9 verifier_kfunc_perfmon/arg_untrusted_read_noperfmon:OK #627/10 verifier_kfunc_perfmon/arg_untrusted_read_noperfmon @unpriv:OK #627 verifier_kfunc_perfmon:OK Summary: 1/10 PASSED, 0 SKIPPED, 0/0 FAILED Signed-off-by: Daniel Borkmann Link: https://lore.kernel.org/r/20260910213510.49358-4-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/verifier.c | 2 + .../bpf/progs/verifier_kfunc_perfmon.c | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c index 64ac49ad67e6..ec9e3907c75d 100644 --- a/tools/testing/selftests/bpf/prog_tests/verifier.c +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c @@ -53,6 +53,7 @@ #include "verifier_iterating_callbacks.skel.h" #include "verifier_jeq_infer_not_null.skel.h" #include "verifier_jit_convergence.skel.h" +#include "verifier_kfunc_perfmon.skel.h" #include "verifier_ld_ind.skel.h" #include "verifier_ldsx.skel.h" #include "verifier_leak_ptr.skel.h" @@ -216,6 +217,7 @@ void test_verifier_int_ptr(void) { RUN(verifier_int_ptr); } void test_verifier_iterating_callbacks(void) { RUN(verifier_iterating_callbacks); } void test_verifier_jeq_infer_not_null(void) { RUN(verifier_jeq_infer_not_null); } void test_verifier_jit_convergence(void) { RUN(verifier_jit_convergence); } +void test_verifier_kfunc_perfmon(void) { RUN(verifier_kfunc_perfmon); } void test_verifier_load_acquire(void) { RUN(verifier_load_acquire); } void test_verifier_ld_ind(void) { RUN(verifier_ld_ind); } void test_verifier_ldsx(void) { RUN(verifier_ldsx); } diff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c new file mode 100644 index 000000000000..76c39ef30e96 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include "bpf_misc.h" + +void *user_ptr; +char dynptr_buf[8]; +u64 kaddr; + +extern struct kmem_cache *bpf_get_kmem_cache(u64 addr) __ksym; + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_rdonly_cast is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int rdonly_cast_noperfmon(void *ctx) +{ + char *p = bpf_rdonly_cast(0, 0); + + return p[0x7fff]; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_probe_read_kernel_dynptr is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int probe_read_kernel_dynptr_noperfmon(void *ctx) +{ + struct bpf_dynptr dptr; + + bpf_dynptr_from_mem(dynptr_buf, sizeof(dynptr_buf), 0, &dptr); + bpf_probe_read_kernel_dynptr(&dptr, 0, sizeof(dynptr_buf), user_ptr); + return 0; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_stream_vprintk is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int stream_vprintk_noperfmon(void *ctx) +{ + bpf_stream_printk(BPF_STDOUT, "%pB", (void *)kaddr); + return 0; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_get_kmem_cache is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int get_kmem_cache_noperfmon(void *ctx) +{ + return !!bpf_get_kmem_cache(kaddr); +} + +__weak int subprog_untrusted_read(void *p __arg_untrusted) +{ + return *(char *)p; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("rdonly_untrusted_mem access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int arg_untrusted_read_noperfmon(void *ctx) +{ + return subprog_untrusted_read(0); +} + +char _license[] SEC("license") = "GPL"; From 2936aed9b02162df7fbe08fcd6bfbb3270ad9f95 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 10 Sep 2026 20:56:04 +0200 Subject: [PATCH 1447/2366] bpf: Clear scalar delta on narrowing stack spill check_stack_write_fixed_off() breaks the scalar link on a narrowing spill by zeroing the id of the destination stack slot, but leaves the delta in place. save_register_state() has just copied the source register verbatim, so the slot keeps the BPF_ADD_CONST delta of a register it is no longer linked to. This is the same shape as the case addressed in 1b327732c846 ("bpf: Clear delta when clearing reg id for non-{add,sub} ops"). Unlike the latter, no miscomputation seems reachable, so mainly consistency. Signed-off-by: Daniel Borkmann Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260910185604.44949-1-daniel@iogearbox.net Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 8058f684a9ea..1b0b1fb62878 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -3582,7 +3582,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env, save_register_state(env, state, spi, reg, size); /* Break the relation on a narrowing spill. */ if (!reg_value_fits) - state->stack[spi].spilled_ptr.id = 0; + clear_scalar_id(&state->stack[spi].spilled_ptr); } else if (!reg && !(off % BPF_REG_SIZE) && is_bpf_st_mem(insn) && env->bpf_capable) { struct bpf_reg_state *tmp_reg = &env->fake_reg[0]; From b0b3dc66529676228cb938cbcad66920f735c223 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 20:22:55 +0800 Subject: [PATCH 1448/2366] bpf: Fix divide-by-zero in btf_struct_walk() When an access goes past the struct and the last member is a flexible array, btf_struct_walk() folds the offset back into a single element with (off - moff) % t->size, but never checks that the element type has a size. BTF takes an empty struct, so this in program BTF /* event could be empty */ struct event { #ifdef HAVE_TIMESTAMP __u64 ts; #endif }; struct batch { int nr; struct event events[]; }; divides by zero at prog load time. Getting there needs a PTR_TO_BTF_ID that is not MEM_ALLOC, e.g. a plain read of a local kptr stashed in a map from a sleepable program. Oops: divide error: 0000 [#1] SMP KASAN PTI RIP: 0010:btf_struct_walk+0x53f/0x1570 Call Trace: btf_struct_access+0x42a/0xcd0 check_ptr_to_btf_access+0x4dc/0x1160 check_mem_access+0x3a45/0x8740 check_load_mem+0x36a/0xd10 do_check_common+0x3ef0/0xb210 bpf_check+0x6d3b/0x8580 bpf_prog_load+0xf7c/0x2720 __sys_bpf+0xa83/0x3690 __x64_sys_bpf+0xc7/0x150 x64_sys_call+0x1f3f/0x27e0 do_syscall_64+0xe5/0x610 entry_SYSCALL_64_after_hwframe+0x76/0x7e Reject a zero-sized element type. The fixed array path in the same function already bails out on the same thing: btf_struct_walk() ... /* skip empty array */ if (moff == mtrue_end) continue; msize /= total_nelems; Fixes: 9c5f8a1008a1 ("bpf: Support variable length array in tracing programs") Signed-off-by: Jiayuan Chen Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20260910122316.186384-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- kernel/bpf/btf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 9f33e95d5741..d67a169ba2bb 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -7187,7 +7187,7 @@ static int btf_struct_walk(struct bpf_verifier_log *log, const struct btf *btf, if (btf_type_is_int(t)) return WALK_SCALAR; - if (!btf_type_is_struct(t)) + if (!btf_type_is_struct(t) || !t->size) goto error; off = (off - moff) % t->size; From f77d21245710690f3fb02d19d8dd4dc17ee58c2c Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 20:22:56 +0800 Subject: [PATCH 1449/2366] selftests/bpf: Test BTF walk into a flexible array of zero-sized elements The program stashes a bpf_obj_new() object whose type ends with a flexible array of empty structs, then reads it back as an untrusted kptr. Without the previous patch this divides by zero in btf_struct_walk() instead of being rejected. # ./test_progs -t verifier_btf_flex_array ... #602 verifier_btf_flex_array:OK Summary: 1/1 PASSED, 0 SKIPPED, 0/0 FAILED Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260910122316.186384-2-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/verifier.c | 2 + .../bpf/progs/verifier_btf_flex_array.c | 56 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c index ec9e3907c75d..8b439e194bcc 100644 --- a/tools/testing/selftests/bpf/prog_tests/verifier.c +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c @@ -23,6 +23,7 @@ #include "verifier_bpf_trap.skel.h" #include "verifier_bswap.skel.h" #include "verifier_btf_ctx_access.skel.h" +#include "verifier_btf_flex_array.skel.h" #include "verifier_btf_unreliable_prog.skel.h" #include "verifier_call_large_imm.skel.h" #include "verifier_cfg.skel.h" @@ -187,6 +188,7 @@ void test_verifier_bpf_get_stack(void) { RUN(verifier_bpf_get_stack); } void test_verifier_bpf_trap(void) { RUN(verifier_bpf_trap); } void test_verifier_bswap(void) { RUN(verifier_bswap); } void test_verifier_btf_ctx_access(void) { RUN(verifier_btf_ctx_access); } +void test_verifier_btf_flex_array(void) { RUN(verifier_btf_flex_array); } void test_verifier_btf_unreliable_prog(void) { RUN(verifier_btf_unreliable_prog); } void test_verifier_call_large_imm(void) { RUN(verifier_call_large_imm); } void test_verifier_cfg(void) { RUN(verifier_cfg); } diff --git a/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c b/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c new file mode 100644 index 000000000000..59b84261f622 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#include "bpf_experimental.h" +#include "bpf_misc.h" + +struct test_empty_event {}; + +struct test_flex_batch { + int nr; + struct test_empty_event events[]; +}; + +struct map_value { + struct test_flex_batch __kptr *batch; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, int); + __type(value, struct map_value); + __uint(max_entries, 1); +} batches SEC(".maps"); + +SEC("syscall") +__description("btf walk into flexible array of zero-sized elements") +__failure __msg("access beyond struct test_flex_batch at off 4 size 1") +int stash_and_peek(void *ctx) +{ + struct test_flex_batch *b, *old; + struct map_value *v; + int key = 0; + + v = bpf_map_lookup_elem(&batches, &key); + if (!v) + return 0; + + b = bpf_obj_new(struct test_flex_batch); + if (!b) + return 0; + b->nr = 1; + + old = bpf_kptr_xchg(&v->batch, b); + if (old) + bpf_obj_drop(old); + + b = v->batch; + if (!b) + return 0; + + return b->nr + *(char *)&b->events[0]; +} + +char _license[] SEC("license") = "GPL"; From 01b245ba016d44861690594e10f67e026ce8552f Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 19:26:26 +0800 Subject: [PATCH 1450/2366] bpf: Fix out-of-bounds read of sk_protocol in bpf_sock_destroy() sk_protocol lives in struct sock, not in struct sock_common. A timewait or request sock handed to bpf_sock_destroy() by the tcp iterator is neither, so reading sk->sk_protocol runs past the object: ================================================================== BUG: KASAN: slab-out-of-bounds in bpf_sock_destroy+0xc7/0xe0 Read of size 2 at addr ffff8881047d11b4 by task test_progs/428 Tainted: [W]=WARN Call Trace: dump_stack_lvl+0x91/0xf0 print_report+0xd1/0x630 kasan_report+0xf3/0x130 __asan_report_load2_noabort+0x14/0x30 bpf_sock_destroy+0xc7/0xe0 bpf_prog_c3dd61f9d9cd9f37_iter_tcp6_timewait+0x9f/0xb7 bpf_iter_run_prog+0x538/0xde0 bpf_iter_tcp_seq_show+0x26b/0x4b0 bpf_seq_read+0x424/0x1210 vfs_read+0x197/0xe40 ksys_read+0x119/0x240 __x64_sys_read+0x72/0xc0 x64_sys_call+0x647/0x27e0 do_syscall_64+0xe5/0x610 entry_SYSCALL_64_after_hwframe+0x76/0x7e Only check sk_protocol on full socks. tcp_abort() already knows how to deal with TIME_WAIT and NEW_SYN_RECV socks. Also fix the comment, it never matched the code. Fixes: 4ddbcb886268 ("bpf: Add bpf_sock_destroy kfunc") Reported-by: Xiang Mei (Microsoft) Closes: https://lore.kernel.org/bpf/20260702224519.800135-1-xmei5@asu.edu/ Signed-off-by: Jiayuan Chen Reviewed-by: Kuniyuki Iwashima Link: https://lore.kernel.org/r/20260910112634.152195-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- net/core/filter.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 2a84f9d01131..cae43b999162 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -12913,8 +12913,9 @@ __bpf_kfunc_start_defs(); * @sock: Pointer to socket to be destroyed * * Return: - * On error, may return EPROTONOSUPPORT, EINVAL. - * EPROTONOSUPPORT if protocol specific destroy handler is not supported. + * On error, may return EOPNOTSUPP, or whatever the protocol specific + * destroy handler returns. + * EOPNOTSUPP if protocol specific destroy handler is not supported. * 0 otherwise */ __bpf_kfunc int bpf_sock_destroy(struct sock_common *sock) @@ -12926,8 +12927,12 @@ __bpf_kfunc int bpf_sock_destroy(struct sock_common *sock) * Supporting protocols will need to acquire sock lock in the BPF context * prior to invoking this kfunc. */ - if (!sk->sk_prot->diag_destroy || (sk->sk_protocol != IPPROTO_TCP && - sk->sk_protocol != IPPROTO_UDP)) + if (!sk->sk_prot->diag_destroy) + return -EOPNOTSUPP; + + if (sk_fullsock(sk) && + sk->sk_protocol != IPPROTO_TCP && + sk->sk_protocol != IPPROTO_UDP) return -EOPNOTSUPP; return sk->sk_prot->diag_destroy(sk, ECONNABORTED); From eaab8cab451b9502ce224cd202550375b894a467 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 19:27:28 +0800 Subject: [PATCH 1451/2366] tcp: Skip cond_resched() in inet_csk_listen_stop() under BPF context bpf_sock_destroy() runs from the tcp iterator, under rcu_read_lock(). If the sock is a listener that still has children in its accept queue, tcp_abort() ends up in inet_csk_listen_stop() and the cond_resched() there trips the debug check: BUG: sleeping function called from invalid context at net/ipv4/inet_connection_sock.c:1523 in_atomic(): 0, irqs_disabled(): 0, non_block: 0, pid: 628, name: test_progs preempt_count: 0, expected: 0 RCU nest depth: 1, expected: 0 locks held by test_progs/628: 3, last CPU#3: #0: ffff8881158cee18 (&p->lock){+.+.}-{4:4}, at: bpf_seq_read+0x56/0x1210 #1: ffff8881106bb858 (sk_lock-AF_INET6){+.+.}-{0:0}, at: bpf_iter_tcp_seq_show+0x32b/0x4b0 #2: ffffffffb435af20 (rcu_read_lock){....}-{1:3}, at: bpf_iter_run_prog+0x46b/0xde0 CPU: 3 UID: 0 PID: 628 Comm: test_progs Tainted: G W 7.2.0+ #65 PREEMPT Tainted: [W]=WARN Call Trace: dump_stack_lvl+0xc1/0xf0 dump_stack+0x10/0x20 __might_resched+0x3d2/0x610 inet_csk_listen_stop+0x7b/0xbf0 tcp_abort+0x23b/0x3b0 bpf_sock_destroy+0xfc/0x140 bpf_prog_448133d24601754f_iter_tcp6_server+0x81/0x8a bpf_iter_run_prog+0x538/0xde0 bpf_iter_tcp_seq_show+0x26b/0x4b0 bpf_seq_read+0x424/0x1210 vfs_read+0x197/0xe40 ksys_read+0x119/0x240 __x64_sys_read+0x72/0xc0 x64_sys_call+0x647/0x27e0 do_syscall_64+0xe5/0x610 entry_SYSCALL_64_after_hwframe+0x76/0x7e RIP: 0033:0x7fad39b28aca RSP: 002b:00007ffc381c61c0 EFLAGS: 00000246 ORIG_RAX: 0000000000000000 RAX: ffffffffffffffda RBX: 00007ffc381c6a88 RCX: 00007fad39b28aca RDX: 0000000000000032 RSI: 00007ffc381c6250 RDI: 0000000000000014 RBP: 00007ffc381c61e0 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000003 R13: 0000000000000000 R14: 000055f077c1bbb0 R15: 00007fad3a0f3000 The commit that added the kfunc already guards lock_sock() in tcp_abort() and udp_abort() with has_current_bpf_ctx(), but missed the listener path. Do the same for the cond_resched(). The loop runs inside the iterator's rcu_read_lock(), it must not reschedule or report a quiescent state there. Fixes: 4ddbcb886268 ("bpf: Add bpf_sock_destroy kfunc") Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260910112736.153710-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- net/ipv4/inet_connection_sock.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 6257459bcee2..6a30f1138454 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -1520,7 +1520,8 @@ void inet_csk_listen_stop(struct sock *sk) local_bh_enable(); sock_put(child); - cond_resched(); + if (!has_current_bpf_ctx()) + cond_resched(); } if (queue->fastopenq.rskq_rst_head) { /* Free all the reqs queued in rskq_rst_head. */ From 8036d3a5a6589ef0721d15e7c03976bc3996f7a8 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 10 Sep 2026 19:28:02 +0800 Subject: [PATCH 1452/2366] selftests/bpf: Test bpf_sock_destroy() on TIME_WAIT and listener socks Add two subtests. tcp_timewait: the client shuts down first and the server closes after it, so the client sock ends up in TIME_WAIT. A tcp iterator then finds the timewait sock by the cookie it inherited from the client sock and destroys it. Iterate once more to make sure it is gone. Without the first fix bpf_sock_destroy() reads past the timewait sock and KASAN complains. tcp_listen_pending: connect to a listener but never accept, so the child sits in the accept queue, then destroy the listener. Without the second fix the cond_resched() in inet_csk_listen_stop() trips the might_sleep check under rcu_read_lock(). ./test_progs -a sock_destroy #444/1 sock_destroy/tcp_client:OK #444/2 sock_destroy/tcp_server:OK #444/3 sock_destroy/tcp_listen_pending:OK #444/4 sock_destroy/tcp_timewait:OK #444/5 sock_destroy/udp_client:OK #444/6 sock_destroy/udp_server:OK #444/7 sock_destroy/trace_tcp_destroy_sock:OK #444 sock_destroy:OK Summary: 1/7 PASSED, 0 SKIPPED, 0/0 FAILED Signed-off-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260910112810.153801-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/sock_destroy.c | 121 ++++++++++++++++++ .../selftests/bpf/progs/sock_destroy_prog.c | 30 +++++ 2 files changed, 151 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/sock_destroy.c b/tools/testing/selftests/bpf/prog_tests/sock_destroy.c index 9c11938fe597..78d642a02bdb 100644 --- a/tools/testing/selftests/bpf/prog_tests/sock_destroy.c +++ b/tools/testing/selftests/bpf/prog_tests/sock_destroy.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include #include @@ -110,6 +111,122 @@ static void test_tcp_server(struct sock_destroy_prog *skel) close(serv); } +static void test_tcp_listen_pending(struct sock_destroy_prog *skel) +{ + int serv = -1, clien = -1, accept_serv = -1, n, serv_port; + struct pollfd pfd = { .events = POLLIN }; + char buf[1]; + + serv = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); + if (!ASSERT_GE(serv, 0, "start_server")) + goto cleanup; + serv_port = get_socket_local_port(serv); + if (!ASSERT_GE(serv_port, 0, "get_sock_local_port")) + goto cleanup; + skel->bss->serv_port = (__be16)serv_port; + + /* + * Connect but never accept, so the child sits in the accept queue + * of the listener. Wait until it's actually there. + */ + clien = connect_to_fd(serv, 0); + if (!ASSERT_GE(clien, 0, "connect_to_fd")) + goto cleanup; + pfd.fd = serv; + if (!ASSERT_EQ(poll(&pfd, 1, -1), 1, "poll listener")) + goto cleanup; + + /* Run iterator program that destroys server sockets. */ + start_iter_sockets(skel->progs.iter_tcp6_server); + + accept_serv = accept(serv, NULL, NULL); + if (!ASSERT_LT(accept_serv, 0, "accept on destroyed listener")) + goto cleanup; + ASSERT_EQ(errno, EINVAL, "error code on destroyed listener"); + + /* The unaccepted child was reset along with the listener. */ + n = recv(clien, buf, sizeof(buf), 0); + if (!ASSERT_LT(n, 0, "client recv on reset child")) + goto cleanup; + ASSERT_EQ(errno, ECONNRESET, "error code on reset child"); + +cleanup: + if (clien != -1) + close(clien); + if (accept_serv != -1) + close(accept_serv); + if (serv != -1) + close(serv); +} + +static void test_tcp_timewait(struct sock_destroy_prog *skel) +{ + int serv = -1, clien = -1, accept_serv = -1, n; + struct timeval tv = {}; + char buf[1]; + + serv = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); + if (!ASSERT_GE(serv, 0, "start_server")) + goto cleanup; + + clien = connect_to_fd(serv, 0); + if (!ASSERT_GE(clien, 0, "connect_to_fd")) + goto cleanup; + + accept_serv = accept(serv, NULL, NULL); + if (!ASSERT_GE(accept_serv, 0, "serv accept")) + goto cleanup; + + /* + * Active close from the client, then close the server side. Once + * recv() sees EOF the server FIN has been processed and the client + * sock is in TIME_WAIT. Block without timeout so a loaded CI box + * can't race us. + */ + if (!ASSERT_OK(setsockopt(clien, SOL_SOCKET, SO_RCVTIMEO, &tv, + sizeof(tv)), "clear rcvtimeo")) + goto cleanup; + if (!ASSERT_OK(shutdown(clien, SHUT_WR), "client shutdown")) + goto cleanup; + + /* + * Make sure the server has seen the client FIN before it closes, + * so the two FINs never cross. + */ + n = recv(accept_serv, buf, sizeof(buf), 0); + if (!ASSERT_EQ(n, 0, "server recv EOF")) + goto cleanup; + + close(accept_serv); + accept_serv = -1; + + /* block until return EOF */ + n = recv(clien, buf, sizeof(buf), 0); + if (!ASSERT_EQ(n, 0, "client recv EOF")) + goto cleanup; + + /* Run iterator program that destroys the timewait client sock. */ + skel->bss->tw_found = 0; + start_iter_sockets(skel->progs.iter_tcp6_timewait); + if (!ASSERT_EQ(skel->bss->tw_found, 1, "timewait sock found")) + goto cleanup; + + ASSERT_OK(skel->bss->tw_destroy_err, "destroy timewait sock"); + + /* The destroyed timewait sock must be gone. */ + skel->bss->tw_found = 0; + start_iter_sockets(skel->progs.iter_tcp6_timewait); + ASSERT_EQ(skel->bss->tw_found, 0, "timewait sock destroyed"); + +cleanup: + if (clien != -1) + close(clien); + if (accept_serv != -1) + close(accept_serv); + if (serv != -1) + close(serv); +} + static void test_udp_client(struct sock_destroy_prog *skel) { int serv = -1, clien = -1, n = 0; @@ -204,6 +321,10 @@ void test_sock_destroy(void) test_tcp_client(skel); if (test__start_subtest("tcp_server")) test_tcp_server(skel); + if (test__start_subtest("tcp_listen_pending")) + test_tcp_listen_pending(skel); + if (test__start_subtest("tcp_timewait")) + test_tcp_timewait(skel); if (test__start_subtest("udp_client")) test_udp_client(skel); if (test__start_subtest("udp_server")) diff --git a/tools/testing/selftests/bpf/progs/sock_destroy_prog.c b/tools/testing/selftests/bpf/progs/sock_destroy_prog.c index 9e0bf7a54cec..0a8887543218 100644 --- a/tools/testing/selftests/bpf/progs/sock_destroy_prog.c +++ b/tools/testing/selftests/bpf/progs/sock_destroy_prog.c @@ -7,6 +7,8 @@ #include "bpf_tracing_net.h" __be16 serv_port = 0; +int tw_found = 0; +int tw_destroy_err = 0; int bpf_sock_destroy(struct sock_common *sk) __ksym; @@ -100,6 +102,34 @@ int iter_tcp6_server(struct bpf_iter__tcp *ctx) return 0; } +SEC("iter/tcp") +int iter_tcp6_timewait(struct bpf_iter__tcp *ctx) +{ + struct sock_common *sk_common = ctx->sk_common; + __u64 *val; + int key = 0; + + if (!sk_common) + return 0; + + if (sk_common->skc_family != AF_INET6) + return 0; + + if (!bpf_skc_to_tcp_timewait_sock(sk_common)) + return 0; + + val = bpf_map_lookup_elem(&tcp_conn_sockets, &key); + if (!val) + return 0; + /* The timewait sock inherits the cookie of the closed client sock. */ + if (bpf_get_socket_cookie(sk_common) != *val) + return 0; + + tw_found++; + tw_destroy_err = bpf_sock_destroy(sk_common); + + return 0; +} SEC("iter/udp") int iter_udp6_client(struct bpf_iter__udp *ctx) From 27600805e62f800bacf990354632eae4e487d34c Mon Sep 17 00:00:00 2001 From: Yazen Ghannam Date: Thu, 3 Sep 2026 10:43:25 -0500 Subject: [PATCH 1453/2366] x86/amd_node: Fix PCI device reference counting in amd_smn_init() The local "root" pointer is a temporary variable used during the device search. Therefore, refcount related to the search iterators should be cleaned up after the search is complete. Use the __free() cleanup macro to ensure the refcount is decremented when the temporary pointer goes out of scope. Additionally, increment the refcount when caching a root pointer. This ensures the in-use refcount is separate from the temporary search refcounting. Finally, drop the redundant "root = NULL" before the second search loop. The pci_get_class() iterator always decrements the refcount of its "from" argument, so the first loop can only fall through with "root" already NULL. Fixes: 0a4b61d9c2e4 ("x86/amd_node: Fix AMD root device caching") Closes: https://sashiko.dev/#/patchset/20260806160159.230453-1-jason.andryuk%40amd.com Reported-by: Sashiko Assisted-by: LLM Signed-off-by: Yazen Ghannam Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Mario Limonciello (AMD) Cc: Link: https://patch.msgid.link/20260903154325.74343-1-yazen.ghannam@amd.com --- arch/x86/kernel/amd_node.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/arch/x86/kernel/amd_node.c b/arch/x86/kernel/amd_node.c index 762585775b5a..b7926ba3610a 100644 --- a/arch/x86/kernel/amd_node.c +++ b/arch/x86/kernel/amd_node.c @@ -251,7 +251,7 @@ __setup("amd_smn_debugfs_enable", amd_smn_enable_dfs); static int __init amd_smn_init(void) { u16 count, num_roots, roots_per_node, node, num_nodes; - struct pci_dev *root; + struct pci_dev *root __free(pci_dev_put) = NULL; if (!cpu_feature_enabled(X86_FEATURE_ZEN)) return 0; @@ -262,7 +262,6 @@ static int __init amd_smn_init(void) return 0; num_roots = 0; - root = NULL; while ((root = get_next_root(root))) { pci_dbg(root, "Reserving PCI config space\n"); @@ -299,14 +298,13 @@ static int __init amd_smn_init(void) count = 0; node = 0; - root = NULL; while (node < num_nodes && (root = get_next_root(root))) { /* Use one root for each node and skip the rest. */ if (count++ % roots_per_node) continue; pci_dbg(root, "is root for AMD node %u\n", node); - amd_roots[node++] = root; + amd_roots[node++] = pci_dev_get(root); } if (enable_dfs) { From 8a14be55bdc6d5a25cd7b0ac5d4d884fcc727b49 Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Fri, 21 Aug 2026 18:30:46 +0800 Subject: [PATCH 1454/2366] ublk: clear force_abort in ublk_queue_reset_io_flags() Quiesce sets ubq->force_abort for batch I/O. Recovery never clears it, so batch fetch keeps failing with -ENODEV and the device stays QUIESCED. Fixes: a4d883755399 ("ublk: add UBLK_U_IO_FETCH_IO_CMDS for batch I/O processing") Signed-off-by: Yang Xiuwei Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260821103047.369522-2-yangxiuwei@kylinos.cn Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index e5ba07d8d281..44d10cb36bf1 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -3030,6 +3030,7 @@ static void ublk_queue_reset_io_flags(struct ublk_queue *ubq) ubq->canceling = false; spin_unlock(&ubq->cancel_lock); ubq->fail_io = false; + ubq->force_abort = false; } /* device can only be started after all IOs are ready */ From 94b1a3ca9b8db3151f1416263704c159a9470da5 Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Fri, 21 Aug 2026 18:30:47 +0800 Subject: [PATCH 1455/2366] selftests: ublk: add batch IO cases to recover_03 Add -b coverage for quiesce recover. Signed-off-by: Yang Xiuwei Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260821103047.369522-3-yangxiuwei@kylinos.cn Signed-off-by: Jens Axboe --- tools/testing/selftests/ublk/test_recover_03.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/testing/selftests/ublk/test_recover_03.sh b/tools/testing/selftests/ublk/test_recover_03.sh index 2554805e5b02..92f4012178f0 100755 --- a/tools/testing/selftests/ublk/test_recover_03.sh +++ b/tools/testing/selftests/ublk/test_recover_03.sh @@ -29,6 +29,11 @@ _create_backfile 0 256M _create_backfile 1 128M _create_backfile 2 128M +ublk_run_quiesce_recover -t null -q 2 -r 1 -b & +ublk_run_quiesce_recover -t loop -q 2 -r 1 -b "${UBLK_BACKFILES[0]}" & +ublk_run_quiesce_recover -t stripe -q 2 -r 1 -b "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" & +wait + ublk_run_quiesce_recover -t null -q 2 -r 1 & ublk_run_quiesce_recover -t loop -q 2 -r 1 "${UBLK_BACKFILES[0]}" & ublk_run_quiesce_recover -t stripe -q 2 -r 1 "${UBLK_BACKFILES[1]}" "${UBLK_BACKFILES[2]}" & From 135d84c66f85426299db01a09d93a79a87af18ba Mon Sep 17 00:00:00 2001 From: Binglei Wang Date: Fri, 11 Sep 2026 12:11:33 +0800 Subject: [PATCH 1456/2366] erofs: add missing buf->off in erofs_bread() erofs_bread() locates the target folio with index = (buf->off + offset) >> PAGE_SHIFT; but computes the in-folio offset without taking buf->off into account: return buf->base + (offset & ~PAGE_MASK); If buf->off is not page-aligned, the returned pointer misses the in-page component of buf->off, so callers end up fetching data from a wrong offset. buf->off is set to sbi->dif0.fsoff in erofs_init_metabuf(), and fsoff can be specified via the "fsoffset=" mount option, which only requires block-size alignment. Therefore, on an image with a sub-page block size (e.g. 512 bytes), a non-page-aligned fsoff (e.g. 512) triggers the issue, since 512 is a multiple of the block size but not of PAGE_SIZE. It can be reproduced by mounting an image that is placed at a non-page-aligned offset: mkfs.erofs -b512 -zlz4hc sub.erofs src/ # prepend 512 bytes of padding to the image mount -t erofs -o loop,fsoffset=512 padded.erofs /mnt which fails with erofs (device loop0): cannot find valid erofs superblock because the on-disk superblock (at offset 1024 within the image, i.e. 1536 within the padded file) is read from a wrong in-folio offset. With this fixed, the very same image mounts successfully and its file contents match those read from the unpadded image. Fix it by including buf->off in the in-folio offset calculation, so that it is consistent with the folio index calculation. Fixes: c36ec00d7f67 ("erofs: add 'fsoffset' mount option to specify filesystem offset") Signed-off-by: Binglei Wang Reviewed-by: Gao Xiang Signed-off-by: Gao Xiang --- fs/erofs/data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/erofs/data.c b/fs/erofs/data.c index 0885b1f2fc92..be63b89f0862 100644 --- a/fs/erofs/data.c +++ b/fs/erofs/data.c @@ -48,7 +48,7 @@ void *erofs_bread(struct erofs_buf *buf, erofs_off_t offset, bool need_kmap) return NULL; if (!buf->base) buf->base = kmap_local_page(buf->page); - return buf->base + (offset & ~PAGE_MASK); + return buf->base + ((buf->off + offset) & ~PAGE_MASK); } int erofs_init_metabuf(struct erofs_buf *buf, struct super_block *sb, From 4d3c07591534517c633945c8d8e6526f10e3fabc Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 21:42:28 -0700 Subject: [PATCH 1457/2366] xfs: fix under-reservation of blocks when repairing sf directories Whilst running QA on XFS for-next as of 7.3-rc2 with MKFS_OPTIONS="-n size=8192", I observed the following (trimmed) dmesg splat: XFS: Assertion failed: args->total >= dp->i_nblocks - nblks, file: fs/xfs/libxfs/xfs_da_btree.c, line: 2387 WARNING: fs/xfs/xfs_message.c:104 at assfail+0x46/0x4a [xfs], CPU#0: xfs_scrub/1426511 CPU: 0 UID: 0 PID: 1426511 Comm: xfs_scrub Tainted: G W 7.3.0-rc2-djwx #rc2 PREEMPT(lazy) 6e418570b606a39783b0e7e7b30dc407b965f9e8 Tainted: [W]=WARN RIP: 0010:assfail+0x46/0x4a [xfs] RSP: 0018:ffffc900010d7890 EFLAGS: 00010246 RAX: 0000000000000000 RBX: 0000000000000000 RCX: 00000000ffffffd1 RDX: 0000000000000000 RSI: 0000000000000021 RDI: ffffffffa059fd38 RBP: 0000000000000002 R08: 0000000000000000 R09: 0000000000000000 R10: 000000000000000a R11: 000000007fffffff R12: ffffc900010d7940 R13: ffff888368d8f980 R14: ffffc900010d7a48 R15: ffffc900010d78d0 FS: 00007f445c5ce680(0000) GS:ffff8884a97ea000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f443803b9a8 CR3: 0000000107a4b000 CR4: 00000000003506f0 Call Trace: xfs_da_grow_inode_int+0x2e0/0x300 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xfs_dir2_grow_inode+0x6e/0x150 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xfs_dir2_sf_to_block+0x149/0x870 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xrep_dir_swap_prep+0xe2/0x110 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xrep_dir_swap+0xfb/0x2f0 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xrep_dir_rebuild_tree+0x99/0x100 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xrep_directory+0x83/0x1c0 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xrep_attempt+0x4f/0x1e0 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xfs_scrub_metadata+0x393/0x5b0 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xfs_ioc_scrubv_metadata+0x306/0x570 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] xfs_file_ioctl+0xa4f/0x1150 [xfs 5de2257e14108c136f11317e6bbb8ac77efd392c] __x64_sys_ioctl+0x76/0xc0 do_syscall_64+0x7a/0x3b0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 This is a consequence of commit 0fe77e57588b98, which added the following assertion to xfs_da_grow_inode_int: ASSERT(args->total >= dp->i_nblocks - nblks); Tracing this back to xrep_dir_swap_prep, I noticed that the xfs_da_args object that's passed to xfs_dir2_sf_to_block sets args->total to 1. This is incorrect because mkfs set the directory block size to 8k and the filesystem block size to 4k. In other words, args->total should be 2 here, not 1. Dave Chinner tripped over the same problem with the same branch through a different channel -- his test setup set the fs block size to 1k, in which case the directory block size is still set to 4k. Here, args->total should be 4. Changing the assignment of args->total to sc->mp->m_dir_geo->fsbcount makes the assertion go away, but that isn't a complete fix. In xrep_tempexch_estimate, we also incorrectly assume that a shortform conversion requires 1 fsblock when it should be m_dir_geo->fsbcount. Without that, we can under-reserve space in the transaction and cause a filesystem shutdown. Note that the xfs_dabuf_nfsb helper will compute the correct value for directories and xattr, so we use that instead of open-coding the logic. Also fix xrep_xattr_swap_prep to assign args->total via xfs_dabuf_nfsb to avoid one logic bomb if we ever support multi-fsblock attrs. Cc: stable@vger.kernel.org # v6.10 Cc: floss@jetm.me Reported-by: dgc@kernel.org Fixes: 629fdaf5f5b1b7 ("xfs: use atomic extent swapping to fix user file fork data") Tripped-by: 0fe77e57588b98 ("xfs: assert the reservation covers each da fork growth") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_da_btree.c | 2 +- fs/xfs/libxfs/xfs_da_btree.h | 2 ++ fs/xfs/scrub/attr_repair.c | 2 +- fs/xfs/scrub/dir_repair.c | 2 +- fs/xfs/scrub/tempfile.c | 29 ++++++++++++++++++++++------- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/fs/xfs/libxfs/xfs_da_btree.c b/fs/xfs/libxfs/xfs_da_btree.c index 8cbdd6574755..3d02a0d7ba44 100644 --- a/fs/xfs/libxfs/xfs_da_btree.c +++ b/fs/xfs/libxfs/xfs_da_btree.c @@ -130,7 +130,7 @@ xfs_da_state_reset( state->mp = state->args->dp->i_mount; } -static inline int xfs_dabuf_nfsb(struct xfs_mount *mp, int whichfork) +inline int xfs_dabuf_nfsb(struct xfs_mount *mp, int whichfork) { if (whichfork == XFS_DATA_FORK) return mp->m_dir_geo->fsbcount; diff --git a/fs/xfs/libxfs/xfs_da_btree.h b/fs/xfs/libxfs/xfs_da_btree.h index afcf2d3c7a21..a718b1ceb0aa 100644 --- a/fs/xfs/libxfs/xfs_da_btree.h +++ b/fs/xfs/libxfs/xfs_da_btree.h @@ -244,4 +244,6 @@ xfs_failaddr_t xfs_da3_node_header_check(struct xfs_buf *bp, xfs_ino_t owner); extern struct kmem_cache *xfs_da_state_cache; +int xfs_dabuf_nfsb(struct xfs_mount *mp, int whichfork); + #endif /* __XFS_DA_BTREE_H__ */ diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c index 6e6af142f1fb..28f92e9ba72b 100644 --- a/fs/xfs/scrub/attr_repair.c +++ b/fs/xfs/scrub/attr_repair.c @@ -1294,7 +1294,7 @@ xrep_xattr_swap_prep( .geo = sc->mp->m_attr_geo, .whichfork = XFS_ATTR_FORK, .trans = sc->tp, - .total = 1, + .total = xfs_dabuf_nfsb(sc->mp, XFS_ATTR_FORK), .owner = I_INO(sc->ip), }; diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index 31a23c5f386a..d9d6b7e2abda 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -1488,7 +1488,7 @@ xrep_dir_swap_prep( .geo = sc->mp->m_dir_geo, .whichfork = XFS_DATA_FORK, .trans = sc->tp, - .total = 1, + .total = xfs_dabuf_nfsb(sc->mp, XFS_DATA_FORK), .owner = I_INO(sc->ip), }; diff --git a/fs/xfs/scrub/tempfile.c b/fs/xfs/scrub/tempfile.c index 98820003b929..59a9213a3c7d 100644 --- a/fs/xfs/scrub/tempfile.c +++ b/fs/xfs/scrub/tempfile.c @@ -649,6 +649,19 @@ xrep_tempexch_prep_request( return 0; } +static inline unsigned int +xrep_tempexch_estimate_sf_resblks( + struct xfs_scrub *sc, + int whichfork) +{ + /* repairing a symlink target */ + if (S_ISLNK(VFS_I(sc->ip)->i_mode) && whichfork == XFS_DATA_FORK) + return 1; + + /* everything else is a directory or an xattr structure */ + return xfs_dabuf_nfsb(sc->mp, whichfork); +} + /* * Fill out the mapping exchange resource estimation structures in preparation * for exchanging the contents of a metadata file that we've rebuilt in the @@ -663,6 +676,8 @@ xrep_tempexch_estimate( struct xfs_ifork *ifp; struct xfs_ifork *tifp; int whichfork = xfs_exchmaps_reqfork(req); + unsigned int sf_resblks = + xrep_tempexch_estimate_sf_resblks(sc, whichfork); int state = 0; /* @@ -693,9 +708,9 @@ xrep_tempexch_estimate( * plus the block we converted. */ req->ip1_bcount = sc->tempip->i_nblocks; - req->ip2_bcount = 1; + req->ip2_bcount = sf_resblks; req->nr_exchanges = 1 + tifp->if_nextents; - req->resblks = 1; + req->resblks = sf_resblks; break; case 2: /* @@ -707,10 +722,10 @@ xrep_tempexch_estimate( * is (worst case) the extent count of the file being repaired * plus the block we converted. */ - req->ip1_bcount = 1; + req->ip1_bcount = sf_resblks; req->ip2_bcount = sc->ip->i_nblocks; req->nr_exchanges = 1 + ifp->if_nextents; - req->resblks = 1; + req->resblks = sf_resblks; break; case 3: /* @@ -722,10 +737,10 @@ xrep_tempexch_estimate( * fileoff 0. Presumably, the caller could not exchange the * two inode fork areas directly. */ - req->ip1_bcount = 1; - req->ip2_bcount = 1; + req->ip1_bcount = sf_resblks; + req->ip2_bcount = sf_resblks; req->nr_exchanges = 1; - req->resblks = 2; + req->resblks = 2 * sf_resblks; break; } From 1ee2ce797c360785a3813fef62c90f427f3aed34 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:04:50 -0700 Subject: [PATCH 1458/2366] xfs: actually check internal-rtdev fields in the superblock LOLLM points out that the superblock scrubber doesn't check the new fields that were added for internal realtime volumes when we added zoned device support. Cc: stable@vger.kernel.org # v6.15 Fixes: 2167eaabe2fadd ("xfs: define the zoned on-disk format") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c index 1fa66aa68e16..fa5d32ec020a 100644 --- a/fs/xfs/scrub/agheader.c +++ b/fs/xfs/scrub/agheader.c @@ -418,6 +418,13 @@ xchk_superblock( xchk_block_set_corrupt(sc, bp); } + if (xfs_has_zoned(mp)) { + if (sb->sb_rtstart != cpu_to_be64(mp->m_sb.sb_rtstart)) + xchk_block_set_corrupt(sc, bp); + if (sb->sb_rtreserved != cpu_to_be64(mp->m_sb.sb_rtreserved)) + xchk_block_set_corrupt(sc, bp); + } + /* Everything else must be zero. */ sblen = xchk_superblock_ondisk_size(mp); if (memchr_inv((char *)sb + sblen, 0, BBTOB(bp->b_length) - sblen)) From 3bdbf472a608aeb7e8e4dc70ee86738ad5256356 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:05:06 -0700 Subject: [PATCH 1459/2366] xfs: fix rtrmap cross-referencing elision logic LOLLM points out that xchk_bmap_xref_rmap_cow skips the cross-reference if the data-section rmapbt cursor is not present. However, this is broken for realtime file data fork scanning, because they will have an rtrmapbt cursor and not an rmapbt cursor. Fix the behavior by removing the cursor checks because xchk_bmap_get_rmap already accounts for that. Cc: stable@vger.kernel.org # v6.14 Fixes: 037a44d8277adf ("xfs: cross-reference the realtime rmapbt") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/bmap.c b/fs/xfs/scrub/bmap.c index 401c278725d2..3b0f1dbd9147 100644 --- a/fs/xfs/scrub/bmap.c +++ b/fs/xfs/scrub/bmap.c @@ -274,7 +274,7 @@ xchk_bmap_xref_rmap_cow( unsigned long long rmap_end; uint64_t owner = XFS_RMAP_OWN_COW; - if (!info->sc->sa.rmap_cur || xchk_skip_xref(info->sc->sm)) + if (xchk_skip_xref(info->sc->sm)) return; /* Find the rmap record for this irec. */ From d3a6a35a220615c4f4578aedf3b1626b91d3acae Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:05:21 -0700 Subject: [PATCH 1460/2366] xfs: fix termination logic in xchk_bmap xchk_should_terminate can turn its @error argument into -EINTR if the user is sitting on ^C. Unfortunately, this code here turns that into a 0 return, which isn't quite correct. LOLLM complains about this, though I think it's a very minor matter because the only way -EINTR happens is if there's a fatal signal. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bmap.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/bmap.c b/fs/xfs/scrub/bmap.c index 3b0f1dbd9147..4f3c7f681bd9 100644 --- a/fs/xfs/scrub/bmap.c +++ b/fs/xfs/scrub/bmap.c @@ -1103,8 +1103,9 @@ xchk_bmap( * the rmap must match the combined mapping exactly. */ while (xchk_bmap_iext_iter(&info, &irec)) { - if (xchk_should_terminate(sc, &error) || - (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) + if (xchk_should_terminate(sc, &error)) + return error; + if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT) return 0; if (irec.br_startoff >= endoff) { From e854f9a28b1fa08dfa5bf18ee4184fae90106180 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:05:37 -0700 Subject: [PATCH 1461/2366] xfs: fix replaying dirent removals into the temporary directory xrep_dir_replay_removename is the function that replays a directory entry removal from sc->ip into the temporary directory so that when we swap the contents of sc->tempip and sc->ip, the directory is correct. LOLLM noticed that we were passing the wrong inode pointer into xrep_dir_init_args. It doesn't make sense to set rd->args.dp to rd->args.dp so let's fix this. Cc: stable@vger.kernel.org # v6.10 Fixes: 8559b21a64d983 ("xfs: implement live updates for directory repairs") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index d9d6b7e2abda..2cfcf1c35679 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -727,7 +727,7 @@ xrep_dir_replay_removename( const struct xfs_name *name, xfs_extlen_t total) { - struct xfs_inode *dp = rd->args.dp; + struct xfs_inode *dp = rd->sc->tempip; ASSERT(S_ISDIR(VFS_I(dp)->i_mode)); From 69e10c2b4a51b4ff3c88a70e90f5180ad58c758f Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:05:52 -0700 Subject: [PATCH 1462/2366] xfs: reset parent pointer args before each dir tree unlink repair LOLLM noticed that xfs_parent_removename only partially initializes the passed-in parent pointer arguments object. In the directory tree repair code, we could decide to remove multiple links to a file, so we don't want state from one call to bleed into the next one. Zero the whole thing explicitly. Cc: stable@vger.kernel.org # v6.10 Fixes: 3f31406aef493b ("xfs: fix corruptions in the directory tree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree_repair.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/scrub/dirtree_repair.c b/fs/xfs/scrub/dirtree_repair.c index 8acd55b8c769..1d1eafcf6eb5 100644 --- a/fs/xfs/scrub/dirtree_repair.c +++ b/fs/xfs/scrub/dirtree_repair.c @@ -479,6 +479,7 @@ xrep_dirtree_unlink( } if (xfs_has_parent(sc->mp)) { + memset(&dl->ppargs, 0, sizeof(dl->ppargs)); error = xfs_parent_removename(sc->tp, &dl->ppargs, dp, &dl->xname, sc->ip); if (error) From ad4497a92caba4630f75c80d49cb947026213280 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 8 Sep 2026 23:06:08 -0700 Subject: [PATCH 1463/2366] xfs: advance the findparent inode scan cursor while holding ILOCK LOLLM pointed out a race condition in xrep_findparent_scan -- the directory live update hook holds the directory ILOCK when it calls the xchk_iscan_want_live_update predicate to figure out if it needs to remember the live update, but xrep_findparent_scan drops the directory ILOCK before advancing the cursor. Therefore, it's possible for a live update to check the scan cursor after the scan drops the ILOCK but before the scan updates its cursor. If this happens, we'll fail to record the live update. Fix this by moving the cursor update logic inside xrep_findparent_walk_directory. Note that for non-directories it's ok to advance the cursor without holding any ILOCK because the findparent scan only cares about directory parents, not the children. Cc: stable@vger.kernel.org # v6.10 Fixes: a07b45576264e7 ("xfs: scan the filesystem to repair a directory dotdot entry") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/findparent.c | 56 ++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/fs/xfs/scrub/findparent.c b/fs/xfs/scrub/findparent.c index 04b6b96b0a30..eab3ac2704be 100644 --- a/fs/xfs/scrub/findparent.c +++ b/fs/xfs/scrub/findparent.c @@ -139,12 +139,40 @@ xrep_findparent_dirent( return 0; } +static inline bool +xrep_findparent_want_scan_file( + const struct xrep_findparent_info *fpi) +{ + const struct xfs_scrub *sc = fpi->sc; + const struct xfs_inode *dp = fpi->dp; + + /* Only directories can be parents */ + if (!S_ISDIR(VFS_IC(dp)->i_mode)) + return false; + + /* + * The inode being scanned cannot be its own parent, nor can any + * temporary directory we created to stage this repair. + */ + if (dp == sc->ip || dp == sc->tempip) + return false; + + /* + * Similarly, temporary files created to stage a repair cannot be the + * parent of this inode. + */ + if (xrep_is_tempfile(dp)) + return false; + + return true; +} + /* * If this is a directory, walk the dirents looking for any that point to the * scrub target inode. */ STATIC int -xrep_findparent_walk_directory( +xrep_findparent_walk_file( struct xrep_findparent_info *fpi) { struct xfs_scrub *sc = fpi->sc; @@ -152,19 +180,11 @@ xrep_findparent_walk_directory( unsigned int lock_mode; int error = 0; - /* - * The inode being scanned cannot be its own parent, nor can any - * temporary directory we created to stage this repair. - */ - if (dp == sc->ip || dp == sc->tempip) - return 0; - - /* - * Similarly, temporary files created to stage a repair cannot be the - * parent of this inode. - */ - if (xrep_is_tempfile(dp)) + if (!xrep_findparent_want_scan_file(fpi)) { + if (fpi->parent_scan) + xchk_iscan_mark_visited(&fpi->parent_scan->iscan, dp); return 0; + } /* * Scan the directory to see if there it contains an entry pointing to @@ -201,6 +221,8 @@ xrep_findparent_walk_directory( goto out_unlock; out_unlock: + if (fpi->parent_scan) + xchk_iscan_mark_visited(&fpi->parent_scan->iscan, dp); xfs_iunlock(dp, lock_mode); return error; } @@ -308,11 +330,7 @@ xrep_findparent_scan( ASSERT(S_ISDIR(VFS_IC(sc->ip)->i_mode)); while ((ret = xchk_iscan_iter(&pscan->iscan, &fpi.dp)) == 1) { - if (S_ISDIR(VFS_I(fpi.dp)->i_mode)) - ret = xrep_findparent_walk_directory(&fpi); - else - ret = 0; - xchk_iscan_mark_visited(&pscan->iscan, fpi.dp); + ret = xrep_findparent_walk_file(&fpi); xchk_irele(sc, fpi.dp); if (ret) break; @@ -401,7 +419,7 @@ xrep_findparent_confirm( goto out_rele; } - error = xrep_findparent_walk_directory(&fpi); + error = xrep_findparent_walk_file(&fpi); if (error) goto out_rele; From c5dcb3aadc18d7b82ba64790721b005d18193d35 Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Thu, 10 Sep 2026 16:34:42 +0200 Subject: [PATCH 1464/2366] hrtimer: Use hard expiry when updating timers on the same base Rearming a queued timer with nonzero slack can leave the timerqueue out of order. remove_and_enqueue_same_base() checks the new soft expiry against its neighbours' hard expiries, then stores the new hard expiry in the node without requeueing it. For example, with A at 10 and B at 20, rearming A at 11 with slack 30 passes the neighbour check but leaves A's hard expiry of 41 before B's 20. The same function also caches the soft expiry in base->expires_next when updating or inserting the first timer, giving next-event selection an earlier deadline than the queue head's hard expiry. Set the timer expiry before handling the queue. Use its stored hard expiry for the in-place ordering check and both updates to base->expires_next. The early update is safe because remove_and_enqueue_same_base() runs with base->cpu_base->lock held. The lock keeps the queue stable while hrtimer_can_update_in_place() checks the new expiry against both neighbours. If the check fails, timerqueue_linked_del() removes the node without comparing expiry values before it is reinserted. Fixes: eddffab8282e3 ("hrtimer: Keep track of first expiring timer per clock base") Fixes: 343f2f4dc5425 ("hrtimer: Try to modify timers in place") Signed-off-by: Andrea Parri Signed-off-by: Thomas Gleixner Assisted-by: LLM Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260910143442.2018-1-parri.andrea@gmail.com --- kernel/time/hrtimer.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 530d61257b9a..cbf1693c86b3 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -1263,13 +1263,23 @@ remove_and_enqueue_same_base(struct hrtimer *timer, struct hrtimer_clock_base *b { bool was_first = false; + /* + * Updating the sort key while @timer is queued can temporarily + * make the tree inconsistent. This is safe under cpu_base->lock: + * no other queue operation can observe that state. + * hrtimer_can_update_in_place() either confirms that the new expiry + * fits between the neighbours or timerqueue_linked_del() removes the + * timer without consulting the expiry. + */ + hrtimer_set_expires_range_ns(timer, expires, delta_ns); + expires = hrtimer_get_expires(timer); + /* Remove it from the timer queue if active */ if (timer->is_queued) { was_first = !timerqueue_linked_prev(&timer->node); /* Try to update in place to avoid the de/enqueue dance */ if (hrtimer_can_update_in_place(timer, base, expires)) { - hrtimer_set_expires_range_ns(timer, expires, delta_ns); trace_hrtimer_start(timer, mode, true); if (was_first) base->expires_next = expires; @@ -1280,9 +1290,6 @@ remove_and_enqueue_same_base(struct hrtimer *timer, struct hrtimer_clock_base *b timerqueue_linked_del(&base->active, &timer->node); } - /* Set the new expiry time */ - hrtimer_set_expires_range_ns(timer, expires, delta_ns); - debug_activate(timer, mode, timer->is_queued); base->cpu_base->active_bases |= 1 << base->index; From 55a8e1451869233db837a97e8f3cbf9983bc8678 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V (Arm)" Date: Tue, 8 Sep 2026 17:02:32 +0530 Subject: [PATCH 1465/2366] x86/mm: Don't force unencrypted DMA for IOMMU-backed devices Commit 8277a12d0d60 ("dma-pool: track decrypted atomic pools and select them via attrs") exposed an issue with force_dma_unencrypted() on systems using host memory encryption. force_dma_unencrypted() checks whether the device DMA mask can address the encryption bit and, if not, requires DMA allocations to use unencrypted memory. However, this check is not applicable when the device is using the IOMMU. In that case, the device DMA mask constrains the IOVA seen by the device, not the backing physical address, so it does not need to cover the C-bit. This currently causes dma_alloc_attrs() to set __DMA_ATTR_ALLOC_CC_SHARED for such devices. iommu_dma_alloc() does not support that attribute and rejects the allocation, causing DMA allocations to fail. Do not force DMA allocations to be unencrypted when the device is using the IOMMU. This allows the IOMMU to map the encrypted physical pages as before and avoids incorrectly requesting CC_SHARED allocations. Fixes: 8277a12d0d60 ("dma-pool: track decrypted atomic pools and select them via attrs") Reported-by: Timo Witte Closes: https://lore.kernel.org/all/CANB4YXS=Nf-co3t8eMHtqrW4sn=1BDcP6o=EoTrgZm0WYMYTyw@mail.gmail.com/ Signed-off-by: Aneesh Kumar K.V (Arm) Link: https://lore.kernel.org/r/20260908113232.247457-1-aneesh.kumar@kernel.org [mszyprow: adjusted url and changed 'link' tag to the 'closes' one] Signed-off-by: Marek Szyprowski --- arch/x86/mm/mem_encrypt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/x86/mm/mem_encrypt.c b/arch/x86/mm/mem_encrypt.c index 95bae74fdab2..3aefdef5bcb0 100644 --- a/arch/x86/mm/mem_encrypt.c +++ b/arch/x86/mm/mem_encrypt.c @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -30,7 +31,7 @@ bool force_dma_unencrypted(struct device *dev) * device does not support DMA to addresses that include the * encryption mask. */ - if (cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT)) { + if (cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT) && !use_dma_iommu(dev)) { u64 dma_enc_mask = DMA_BIT_MASK(__ffs64(sme_me_mask)); u64 dma_dev_mask = min_not_zero(dev->coherent_dma_mask, dev->bus_dma_limit); From 143755bdabaa96776c24f878014608e9cb44f930 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 7 Sep 2026 04:00:15 +0200 Subject: [PATCH 1466/2366] dma-buf: Make DMABUF_DEBUG default to y on DEBUG_KERNEL kernels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 646013f513f3 ("dma-buf: enable DMABUF_DEBUG by default on DEBUG kernels") changed the default of DMABUF_DEBUG to "y if DEBUG", but no Kconfig symbol DEBUG exists, so the option has had no default since. Use DEBUG_KERNEL, the Kconfig symbol for a debug kernel. Fixes: 646013f513f3 ("dma-buf: enable DMABUF_DEBUG by default on DEBUG kernels") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260907020015.24719-1-kmehltretter@gmail.com --- drivers/dma-buf/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma-buf/Kconfig b/drivers/dma-buf/Kconfig index 7efc0f0d0712..e4f078a326a4 100644 --- a/drivers/dma-buf/Kconfig +++ b/drivers/dma-buf/Kconfig @@ -43,7 +43,7 @@ config UDMABUF config DMABUF_DEBUG bool "DMA-BUF debug checks" depends on DMA_SHARED_BUFFER - default y if DEBUG + default y if DEBUG_KERNEL help This option enables additional checks for DMA-BUF importers and exporters. Specifically it validates that importers do not peek at the From b344ca94e8cc85796f16ea25e2e5a8e0303fe813 Mon Sep 17 00:00:00 2001 From: David Hu Date: Tue, 1 Sep 2026 17:08:48 +0000 Subject: [PATCH 1467/2366] dma-buf: Fix silent overflow for phys vec to sgt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In case MMIO size is bigger than 4G and peer2peer DMA goes through host bridge, we trigger a code path that assigns the total linked IOVA (which is greater than 4G) to mapped_len. Previously, `mapped_len` was declared as 32-bit `unsigned int`. When accumulating `size_t` lengths, this leads to a silent wrap-around. This truncation causes truncated lengths to be passed to functions like `fill_sg_entry()`. Fix this by changing `mapped_len` to `size_t` (64-bit). While at it, fix similar potential overflow issues in `calc_sg_nents` by using `check_add_overflow()` for `nents` and using `unsigned int` for the loop iterator in `fill_sg_entry` to match. Fixes: 3aa31a8bb11e ("dma-buf: provide phys_vec to scatter-gather mapping routine") Cc: stable@vger.kernel.org Cc: iommu@lists.linux.dev Reviewed-by: Pranjal Shrivastava Reviewed-by: Kevin Tian Reviewed-by: Leon Romanovsky Signed-off-by: David Hu Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260901170849.4052816-2-dhu@x6u.co --- drivers/dma-buf/dma-buf-mapping.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/dma-buf/dma-buf-mapping.c b/drivers/dma-buf/dma-buf-mapping.c index 794acff2546a..80f6ab2f4809 100644 --- a/drivers/dma-buf/dma-buf-mapping.c +++ b/drivers/dma-buf/dma-buf-mapping.c @@ -5,12 +5,13 @@ */ #include #include +#include static struct scatterlist *fill_sg_entry(struct scatterlist *sgl, size_t length, dma_addr_t addr) { unsigned int len, nents; - int i; + unsigned int i; nents = DIV_ROUND_UP(length, UINT_MAX); for (i = 0; i < nents; i++) { @@ -40,8 +41,12 @@ static unsigned int calc_sg_nents(struct dma_iova_state *state, size_t i; if (!state || !dma_use_iova(state)) { - for (i = 0; i < nr_ranges; i++) - nents += DIV_ROUND_UP(phys_vec[i].len, UINT_MAX); + for (i = 0; i < nr_ranges; i++) { + unsigned int added = DIV_ROUND_UP(phys_vec[i].len, UINT_MAX); + + if (check_add_overflow(nents, added, &nents)) + return 0; + } } else { /* * In IOVA case, there is only one SG entry which spans @@ -95,9 +100,10 @@ struct sg_table *dma_buf_phys_vec_to_sgt(struct dma_buf_attachment *attach, size_t nr_ranges, size_t size, enum dma_data_direction dir) { - unsigned int nents, mapped_len = 0; struct dma_buf_dma *dma; struct scatterlist *sgl; + size_t mapped_len = 0; + unsigned int nents; dma_addr_t addr; size_t i; int ret; @@ -133,6 +139,8 @@ struct sg_table *dma_buf_phys_vec_to_sgt(struct dma_buf_attachment *attach, } nents = calc_sg_nents(dma->state, phys_vec, nr_ranges, size); + + /* sg_alloc_table will cleanly fail and return -EINVAL if nents == 0 */ ret = sg_alloc_table(&dma->sgt, nents, GFP_KERNEL | __GFP_ZERO); if (ret) goto err_free_state; From 06dd5e1ae8ce4e129791087c8c66594950f0ba03 Mon Sep 17 00:00:00 2001 From: David Hu Date: Tue, 1 Sep 2026 17:08:49 +0000 Subject: [PATCH 1468/2366] dma-buf: Split sgl by largest page-aligned chunk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, `fill_sg_entry()` splits the scatterlist using `UINT_MAX`. This creates a non-page-aligned DMA length (`0xFFFFFFFF`) for the first entry, resulting in non-page-aligned DMA addresses for all subsequent entries. While the underlying IOMMU mapping may be contiguous, hardware DMA engines often require explicit address alignment (e.g., page, cacheline, or storage sector boundaries). Passing unaligned addresses and lengths can cause explicit failures in DMA descriptor creation or silent data corruption if lower unaligned bits are truncated. In addition, a non-page-aligned sgl length will trigger an edge case in `ib_umem_find_best_pgsz()`. In case of a discontinuity in later buffers, we will have a `va` with lowest bit set to 1. That will lead to `ib_umem_find_best_pgsz()` always return 0, and break the promise to find best page size for the mapping on the NIC side. Fix this by splitting the scatterlist by the largest possible page aligned chunk within `UINT_MAX` (`ALIGN_DOWN(UINT_MAX, PAGE_SIZE)`). This ensures all scatterlist DMA addresses and lengths remain page aligned, while minimizing the total number of sgl entries. Page-aligned entries allow the system to cleanly chunk payloads into PCIe MaxPayloadSize (MPS) (e.g., 128 bytes, 256 bytes, 512 bytes). As a result, this may help reduce TLP fragmentation in P2P transfers and alleviate potential congestion within a logical PCIe switch partition, especially when Relaxed Ordering is not possible due to hardware constraints. Reported-by: sashiko-bot Closes: https://lore.kernel.org/all/20260609165431.778061F00893@smtp.kernel.org/ Fixes: 3aa31a8bb11e ("dma-buf: provide phys_vec to scatter-gather mapping routine") Cc: stable@vger.kernel.org Reviewed-by: Leon Romanovsky Signed-off-by: David Hu Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260901170849.4052816-3-dhu@x6u.co --- drivers/dma-buf/dma-buf-mapping.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/dma-buf/dma-buf-mapping.c b/drivers/dma-buf/dma-buf-mapping.c index 80f6ab2f4809..833be519e1e6 100644 --- a/drivers/dma-buf/dma-buf-mapping.c +++ b/drivers/dma-buf/dma-buf-mapping.c @@ -6,16 +6,17 @@ #include #include #include +#include + +#define MAX_SG_ENT_SZ ALIGN_DOWN(UINT_MAX, PAGE_SIZE) static struct scatterlist *fill_sg_entry(struct scatterlist *sgl, size_t length, dma_addr_t addr) { - unsigned int len, nents; - unsigned int i; + size_t len; - nents = DIV_ROUND_UP(length, UINT_MAX); - for (i = 0; i < nents; i++) { - len = min_t(size_t, length, UINT_MAX); + while (length) { + len = min(length, MAX_SG_ENT_SZ); length -= len; /* * DMABUF abuses scatterlist to create a scatterlist @@ -25,8 +26,10 @@ static struct scatterlist *fill_sg_entry(struct scatterlist *sgl, size_t length, * does not require the CPU list for mapping or unmapping. */ sg_set_page(sgl, NULL, 0, 0); - sg_dma_address(sgl) = addr + (dma_addr_t)i * UINT_MAX; + sg_dma_address(sgl) = addr; sg_dma_len(sgl) = len; + addr += len; + /* Unconditionally advance. On last segment, this becomes NULL */ sgl = sg_next(sgl); } @@ -42,7 +45,7 @@ static unsigned int calc_sg_nents(struct dma_iova_state *state, if (!state || !dma_use_iova(state)) { for (i = 0; i < nr_ranges; i++) { - unsigned int added = DIV_ROUND_UP(phys_vec[i].len, UINT_MAX); + unsigned int added = DIV_ROUND_UP(phys_vec[i].len, MAX_SG_ENT_SZ); if (check_add_overflow(nents, added, &nents)) return 0; @@ -53,7 +56,7 @@ static unsigned int calc_sg_nents(struct dma_iova_state *state, * for whole IOVA address space, but we need to make sure * that it fits sg->length, maybe we need more. */ - nents = DIV_ROUND_UP(size, UINT_MAX); + nents = DIV_ROUND_UP(size, MAX_SG_ENT_SZ); } return nents; From d313499df66159b4b7971d760d16729598ab7e5a Mon Sep 17 00:00:00 2001 From: Theodor Arsenij Larionov Trichkine Date: Tue, 25 Aug 2026 12:10:56 +0300 Subject: [PATCH 1469/2366] netfilter: nft_nat: fully initialise new_addr in netmap setup nft_nat_setup_netmap() builds the mapped address in an on-stack union nf_inet_addr. For an IPv4 mapping it writes only the 4-byte .ip member and the loop runs a single 32-bit iteration, but it then copies the whole 16-byte union into range->min_addr and range->max_addr, so the upper 12 bytes reach nf_nat_setup_info() uninitialised. KMSAN reports an uninit-value in nf_nat_setup_info() reached from nft_nat_eval(). The IPv6 path fills all 16 bytes and is not affected. Zero-initialise new_addr. Fixes: 3ff7ddb1353d ("netfilter: nft_nat: add netmap support") Signed-off-by: Theodor Arsenij Larionov Trichkine Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nft_nat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/nft_nat.c b/net/netfilter/nft_nat.c index e32cd9fbc7c2..cdbd800cac96 100644 --- a/net/netfilter/nft_nat.c +++ b/net/netfilter/nft_nat.c @@ -64,8 +64,8 @@ static void nft_nat_setup_netmap(struct nf_nat_range2 *range, const struct nft_pktinfo *pkt, const struct nft_nat *priv) { + union nf_inet_addr new_addr = {}; struct sk_buff *skb = pkt->skb; - union nf_inet_addr new_addr; __be32 netmask; int i, len = 0; From 444e4c88c9c62a3d823069006563513fe7d5aa66 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Thu, 27 Aug 2026 12:32:56 +0200 Subject: [PATCH 1470/2366] netfilter: nf_tables: fix device name and prefix match in hook lookup Currently, a netdev chain or flowtable hooked to a device prefix can be unintentionally deleted by a control-plane request targeting an exact device name or even a shorter one due to the usage of min() to calculate the length to match. Fix this by making sure an exact device match never matches a prefix and that both the target and the candidate have the same length during delete operation. The add and update paths retain the existing overlap matching to prevent a single device from matching multiple hooks. Reported-by: Wei Fang Closes: https://lore.kernel.org/netfilter-devel/CANE+tVrDeNCHQVmsqkV2ozeBqyE3GtRDMhZgsg1bhw10yGNTRQ@mail.gmail.com/ Fixes: 6d07a289504a ("netfilter: nf_tables: Support wildcard netdev hook specs") Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_tables_api.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 31fbd5a28937..c0b754a2d45b 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -2440,11 +2440,14 @@ static struct nft_hook *nft_netdev_hook_alloc(struct net *net, } static struct nft_hook *nft_hook_list_find(struct list_head *hook_list, - const struct nft_hook *this) + const struct nft_hook *this, + bool strict) { struct nft_hook *hook; list_for_each_entry(hook, hook_list, list) { + if (strict && hook->ifnamelen != this->ifnamelen) + continue; if (!strncmp(hook->ifname, this->ifname, min(hook->ifnamelen, this->ifnamelen))) { if (hook->flags & NFT_HOOK_REMOVE) @@ -2486,7 +2489,7 @@ static int nf_tables_parse_netdev_hooks(struct net *net, err = PTR_ERR(hook); goto err_hook; } - if (nft_hook_list_find(hook_list, hook)) { + if (nft_hook_list_find(hook_list, hook, false)) { NL_SET_BAD_ATTR(extack, tmp); nft_netdev_hook_free(hook); err = -EEXIST; @@ -2943,7 +2946,7 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy, ops->hook = basechain->ops.hook; } - if (nft_hook_list_find(&basechain->hook_list, h)) { + if (nft_hook_list_find(&basechain->hook_list, h, false)) { list_del(&h->list); nft_netdev_hook_free(h); continue; @@ -2956,7 +2959,8 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy, !nft_trans_chain_update(trans)) continue; - if (nft_hook_list_find(&nft_trans_chain_hooks(trans), h)) { + if (nft_hook_list_find(&nft_trans_chain_hooks(trans), + h, false)) { nft_chain_release_hook(&hook); return -EEXIST; } @@ -3257,7 +3261,7 @@ static int nft_delchain_hook(struct nft_ctx *ctx, return err; list_for_each_entry(this, &chain_hook.list, list) { - hook = nft_hook_list_find(&basechain->hook_list, this); + hook = nft_hook_list_find(&basechain->hook_list, this, true); if (!hook) { err = -ENOENT; goto err_chain_del_hook; @@ -9053,7 +9057,7 @@ static int nft_register_flowtable_net_hooks(struct net *net, if (!nft_is_active_next(net, ft)) continue; - if (nft_hook_list_find(&ft->hook_list, hook)) { + if (nft_hook_list_find(&ft->hook_list, hook, false)) { err = -EEXIST; goto err_unregister_net_hooks; } @@ -9130,7 +9134,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh, return err; list_for_each_entry_safe(hook, next, &flowtable_hook.list, list) { - if (nft_hook_list_find(&flowtable->hook_list, hook)) { + if (nft_hook_list_find(&flowtable->hook_list, hook, false)) { list_del(&hook->list); nft_netdev_hook_free(hook); continue; @@ -9143,7 +9147,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh, !nft_trans_flowtable_update(trans)) continue; - if (nft_hook_list_find(&nft_trans_flowtable_hooks(trans), hook)) { + if (nft_hook_list_find(&nft_trans_flowtable_hooks(trans), hook, false)) { err = -EEXIST; goto err_flowtable_update_hook; } @@ -9363,7 +9367,7 @@ static int nft_delflowtable_hook(struct nft_ctx *ctx, return err; list_for_each_entry(this, &flowtable_hook.list, list) { - hook = nft_hook_list_find(&flowtable->hook_list, this); + hook = nft_hook_list_find(&flowtable->hook_list, this, true); if (!hook) { err = -ENOENT; goto err_flowtable_del_hook; From cbdd39ce42530a193c56beb206a3356cb6d01016 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 3 Sep 2026 01:28:56 +0200 Subject: [PATCH 1471/2366] netfilter: nf_nat: unregister and release hooks on error If nf_hook_entries_insert_raw() fails, the NAT hooks get never released, resulting in a memleak. Postpone setting nat_proto_net->nat_hook_ops when the hooks are registered to simplify the error path to decide whether the nat hooks need unwinding. Fixes: 1cd472bf036c ("netfilter: nf_nat: add nat hook register functions to nf_nat") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_nat_core.c | 46 ++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c index 8ac326e1eb5b..a4858c2b2d65 100644 --- a/net/netfilter/nf_nat_core.c +++ b/net/netfilter/nf_nat_core.c @@ -1224,31 +1224,45 @@ int nf_nat_register_fn(struct net *net, u8 pf, const struct nf_hook_ops *ops, } ret = nf_register_net_hooks(net, nat_ops, ops_count); - if (ret < 0) { - mutex_unlock(&nf_nat_proto_mutex); - for (i = 0; i < ops_count; i++) { - priv = nat_ops[i].priv; - kfree_rcu(priv, rcu_head); - } - kfree_rcu(nat_ops, rcu); - return ret; - } - - nat_proto_net->nat_hook_ops = nat_ops; + if (ret < 0) + goto err_free_hooks; + } else { + nat_ops = nat_proto_net->nat_hook_ops; } - nat_ops = nat_proto_net->nat_hook_ops; priv = nat_ops[hooknum].priv; if (WARN_ON_ONCE(!priv)) { - mutex_unlock(&nf_nat_proto_mutex); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto err_unregister_hooks; } ret = nf_hook_entries_insert_raw(&priv->entries, ops); - if (ret == 0) - nat_proto_net->users++; + if (ret) + goto err_unregister_hooks; + + if (!nat_proto_net->nat_hook_ops) + nat_proto_net->nat_hook_ops = nat_ops; + + nat_proto_net->users++; mutex_unlock(&nf_nat_proto_mutex); + + return 0; + +err_unregister_hooks: + if (nat_proto_net->nat_hook_ops) { + mutex_unlock(&nf_nat_proto_mutex); + return ret; + } + nf_unregister_net_hooks(net, nat_ops, ops_count); +err_free_hooks: + mutex_unlock(&nf_nat_proto_mutex); + for (i = 0; i < ops_count; i++) { + priv = nat_ops[i].priv; + kfree_rcu(priv, rcu_head); + } + kfree_rcu(nat_ops, rcu); + return ret; } From e75a9fa1d44bcbd66ea02e8781bcca6ea4076e0d Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 7 Sep 2026 21:04:05 +0200 Subject: [PATCH 1472/2366] netfilter: flowtable: hold reference on ct until flow is released nf_ct_put() releases the ct->ext area inmediately, the rcu typesafe semantics also allow to refer to the wrong conntrack from the flowtable datapath. Hold reference on ct until flow is released after rcu grace period. Add rcu_barrier() on module exit path, to ensure pending flow entries are release before module goes away. Fixes: 0ff90b6c2034 ("netfilter: nf_flow_offload: fix use-after-free and a resource leak") Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_core.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c index 03241d4bfd5e..934c6151f558 100644 --- a/net/netfilter/nf_flow_table_core.c +++ b/net/netfilter/nf_flow_table_core.c @@ -258,6 +258,14 @@ static void flow_offload_route_release(struct flow_offload *flow) nft_flow_dst_release(flow, FLOW_OFFLOAD_DIR_REPLY); } +static void flow_offload_free_rcu(struct rcu_head *rcu_head) +{ + struct flow_offload *flow = container_of(rcu_head, struct flow_offload, rcu_head); + + nf_ct_put(flow->ct); + kfree(flow); +} + void flow_offload_free(struct flow_offload *flow) { switch (flow->type) { @@ -267,8 +275,7 @@ void flow_offload_free(struct flow_offload *flow) default: break; } - nf_ct_put(flow->ct); - kfree_rcu(flow, rcu_head); + call_rcu(&flow->rcu_head, flow_offload_free_rcu); } EXPORT_SYMBOL_GPL(flow_offload_free); @@ -854,6 +861,7 @@ static int __init nf_flow_table_module_init(void) static void __exit nf_flow_table_module_exit(void) { + rcu_barrier(); nf_flow_table_offload_exit(); unregister_pernet_subsys(&nf_flow_table_net_ops); kmem_cache_destroy(flow_offload_cachep); From 49daa3d668b69a5454b5aba0078848a479f79f1c Mon Sep 17 00:00:00 2001 From: Shouping Wang Date: Thu, 10 Sep 2026 19:46:01 +0800 Subject: [PATCH 1473/2366] perf/arm-cmn: Fix wp_dev_sel2 setting for multi-DTM configurations When MXP_MULTIPLE_DTM_EN is TRUE, each DTM will monitor at most two device ports. In this case, {wp_dev_sel2, wp_dev_sel} will only use values 2'b00 and 2'b01 per DTM. Previously the setting allowed values beyond the supported range per DTM, which could cause each DTM to select invalid ports when MXP_MULTIPLE_DTM_EN is TRUE. Fix this by only setting CMN_DTM_WPn_CONFIG_WP_DEV_SEL2 when !multi_dtm. Fixes: 60d1504070c2 ("perf/arm-cmn: Support new IP features") Signed-off-by: Shouping Wang Reviewed-by: Robin Murphy Signed-off-by: Will Deacon --- drivers/perf/arm-cmn.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c index b162de3d9d16..33ee2be9b386 100644 --- a/drivers/perf/arm-cmn.c +++ b/drivers/perf/arm-cmn.c @@ -1582,13 +1582,14 @@ static void arm_cmn_claim_wp_idx(struct arm_cmn_dtm *dtm, static u32 arm_cmn_wp_config(struct perf_event *event, int wp_idx) { + struct arm_cmn *cmn = to_cmn(event->pmu); u32 config; u32 dev = CMN_EVENT_WP_DEV_SEL(event); u32 chn = CMN_EVENT_WP_CHN_SEL(event); u32 grp = CMN_EVENT_WP_GRP(event); u32 exc = CMN_EVENT_WP_EXCLUSIVE(event); u32 combine = CMN_EVENT_WP_COMBINE(event); - bool is_cmn600 = to_cmn(event->pmu)->part == PART_CMN600; + bool is_cmn600 = cmn->part == PART_CMN600; /* CMN-600 supports only primary and secondary matching groups */ if (is_cmn600) @@ -1596,8 +1597,11 @@ static u32 arm_cmn_wp_config(struct perf_event *event, int wp_idx) config = FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_DEV_SEL, dev) | FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_CHN_SEL, chn) | - FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_GRP, grp) | - FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_DEV_SEL2, dev >> 1); + FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_GRP, grp); + + if (!cmn->multi_dtm) + config |= FIELD_PREP(CMN_DTM_WPn_CONFIG_WP_DEV_SEL2, dev >> 1); + if (exc) config |= is_cmn600 ? CMN600_WPn_CONFIG_WP_EXCLUSIVE : CMN_DTM_WPn_CONFIG_WP_EXCLUSIVE; From e4a6f57d22e079e23fafac51057fad534160b269 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 10 Sep 2026 06:53:27 -0700 Subject: [PATCH 1474/2366] arm64: hibernate: clone only the linear map that exists at runtime This is similar to commit 1537e55728ec2 ("arm64: trans_pgd: clone only the linear map that exists at runtime"), but in a different place. swsusp_arch_resume() clones the kernel linear map with trans_pgd_create_copy(..., PAGE_OFFSET, PAGE_END). PAGE_OFFSET comes from the compile-time VA_BITS, so a CONFIG_ARM64_VA_BITS_52 kernel booting on hardware without LPA2 -- vabits_actual is 48 and the fifth level is folded -- hands the walk a 3.9PB window while its linear map only spans the top 128TB. On a VA_BITS_52 4k kernel with CONFIG_KASAN_GENERIC in a 4GB VM, I see: swapper/0: page allocation failure: order:0, mode:0x920(GFP_ATOMIC|__GFP_ZERO) hibernate_page_alloc+0x10/0x1c swsusp_arch_resume+0x70/0x320 hibernation_restore+0xa4/0x138 software_resume+0x15c/0x270 PM: hibernation: Failed to load image, recovering. PM: hibernation: resume failed (-12) Fix it by copying the linear map that is the actual one, not the compiled one. Fixes: a6bbf5d4d9d1 ("arm64: mm: Add definitions to support 5 levels of paging") Signed-off-by: Breno Leitao Reviewed-by: Ard Biesheuvel Signed-off-by: Will Deacon --- arch/arm64/kernel/hibernate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kernel/hibernate.c b/arch/arm64/kernel/hibernate.c index 7bf117427777..424291c547f0 100644 --- a/arch/arm64/kernel/hibernate.c +++ b/arch/arm64/kernel/hibernate.c @@ -423,8 +423,8 @@ int __nocfi swsusp_arch_resume(void) * Create a second copy of just the linear map, and use this when * restoring. */ - rc = trans_pgd_create_copy(&trans_info, &tmp_pg_dir, PAGE_OFFSET, - PAGE_END); + rc = trans_pgd_create_copy(&trans_info, &tmp_pg_dir, + _PAGE_OFFSET(vabits_actual), PAGE_END); if (rc) return rc; From 885bff055a0f251a51a0d4fd4f0a7b525582a3de Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:21 +0100 Subject: [PATCH 1475/2366] arm64: percpu: Fix this_cpu_write() casting The arm64 implementation of this_cpu_write() casts 'val' to unsigned long. This is necessary to handle cases where 'val' is a pointer type, and to avoid spurious compiler warnings for the (unreachable!) cases where the pointer type would be cast to a smaller integer type. Unfortunately, the cast is applied to 'val' rather than '(val)', which won't always generate the expected value when 'val' is an expression. For example, for this_cpu_write(pcp, zero - 1), where 'pcp' is a u64 and 'zero' is a u32: * 'zero' ===> (u32) 0x00000000 * 'zero - 1' ===> (u32) 0xffffffff * '(unsigned long)zero - 1' ===> (u64) 0xffffffffffffffff * '(unsigned long)(zero - 1)' ===> (u64) 0x00000000ffffffff Fix this by adding brackets around 'val'. Fixes: 959bf2fd03b5 ("arm64: percpu: Rewrite per-cpu ops to allow use of LSE atomics") Reported-by: David Laight Signed-off-by: Mark Rutland Reviewed-by: David Laight Reviewed-by: Jinjie Ruan Tested-by: Muhammad Usama Anjum Acked-by: Christopher Lameter (Ampere) Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: Catalin Marinas Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Cc: stable@vger.kernel.org Reviewed-by: Lorenzo Stoakes (ARM) Signed-off-by: Will Deacon --- arch/arm64/include/asm/percpu.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index b57b2bb00967..63bbfd4944a3 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -179,13 +179,13 @@ PERCPU_RET_OP(add, add, ldadd) _pcp_protect_return(__percpu_read_64, pcp) #define this_cpu_write_1(pcp, val) \ - _pcp_protect(__percpu_write_8, pcp, (unsigned long)val) + _pcp_protect(__percpu_write_8, pcp, (unsigned long)(val)) #define this_cpu_write_2(pcp, val) \ - _pcp_protect(__percpu_write_16, pcp, (unsigned long)val) + _pcp_protect(__percpu_write_16, pcp, (unsigned long)(val)) #define this_cpu_write_4(pcp, val) \ - _pcp_protect(__percpu_write_32, pcp, (unsigned long)val) + _pcp_protect(__percpu_write_32, pcp, (unsigned long)(val)) #define this_cpu_write_8(pcp, val) \ - _pcp_protect(__percpu_write_64, pcp, (unsigned long)val) + _pcp_protect(__percpu_write_64, pcp, (unsigned long)(val)) #define this_cpu_add_1(pcp, val) \ _pcp_protect(__percpu_add_case_8, pcp, val) From 44274c657256b4911de82f8104e9e22f054cf742 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:22 +0100 Subject: [PATCH 1476/2366] arm64: percpu: Fix this_cpu_and() mask generation The arm64 implementation of this_cpu_and(pcp, val) is built in terms of ANDNOT operations, which requires the 'val' argument to be bitwise negated. The bitwise negation is not implemented correctly, with two bugs described below. (1) The bitwise negation is performed as '~val' rather than '~(val)'. This won't always generate the expected value when 'val' is an expression. For example, for this_cpu_and(pcp, 1 - 1): * 'val' is '1 - 1' ===> (int) 0x00000000 * '~val' is '~1 - 1' ===> (int) 0xfffffffd * '~(val)' is '~(1 - 1)' ===> (int) 0xffffffff ... and thus bit[1] of 'pcp' would be preserved unexpectedly by the ANDNOT operation. (2) The bitwise negation is performed on 'val' before it has been cast to (at least) the width of 'pcp'. This won't always generate the expected value for the upper bits. For example, for this_cpu_and(pcp, zero), where 'pcp' is a u64 and 'zero' is a u32: * 'zero' ===> (u32) 0x00000000 * '~(zero)' ===> (u32) 0xffffffff * '(u64)~(zero)' ===> (u64) 0x00000000ffffffff * '~((u64)(zero))' ===> (u64) 0xffffffffffffffff ... and thus bits[63:32] of 'pcp' would be preserved unexpectedly by the ANDNOT operation. Fix these issues by adding brackets around 'val', and by casting 'val' to an appropriately-sized type before bitwise negation. Fixes: 959bf2fd03b5 ("arm64: percpu: Rewrite per-cpu ops to allow use of LSE atomics") Signed-off-by: Mark Rutland Reviewed-by: Jinjie Ruan Tested-by: Muhammad Usama Anjum Acked-by: Christopher Lameter (Ampere) Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: Catalin Marinas Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Cc: stable@vger.kernel.org Signed-off-by: Will Deacon --- arch/arm64/include/asm/percpu.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index 63bbfd4944a3..31193bcf89a2 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -206,13 +206,13 @@ PERCPU_RET_OP(add, add, ldadd) _pcp_protect_return(__percpu_add_return_case_64, pcp, val) #define this_cpu_and_1(pcp, val) \ - _pcp_protect(__percpu_andnot_case_8, pcp, ~val) + _pcp_protect(__percpu_andnot_case_8, pcp, ~(u8)(val)) #define this_cpu_and_2(pcp, val) \ - _pcp_protect(__percpu_andnot_case_16, pcp, ~val) + _pcp_protect(__percpu_andnot_case_16, pcp, ~(u16)(val)) #define this_cpu_and_4(pcp, val) \ - _pcp_protect(__percpu_andnot_case_32, pcp, ~val) + _pcp_protect(__percpu_andnot_case_32, pcp, ~(u32)(val)) #define this_cpu_and_8(pcp, val) \ - _pcp_protect(__percpu_andnot_case_64, pcp, ~val) + _pcp_protect(__percpu_andnot_case_64, pcp, ~(u64)(val)) #define this_cpu_or_1(pcp, val) \ _pcp_protect(__percpu_or_case_8, pcp, val) From 8cf2093f5372952a9ebc805c418d45df7112cd14 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:23 +0100 Subject: [PATCH 1477/2366] arm64: percpu: Fix LSE operations on {8,16}-bit types The assembly for __percpu_##name##_case_##sz() and __percpu_##name##_return_case_##sz() doesn't use the 'sfx' macro argument to form the LSE instruction. Without 'sfx', a W register argument will imply a 32-bit memory location, and consequently {8,16}-bit ops will erroneously read and write 32 bits of memory when the LSE instruction is used. Fix this by appending 'sfx' to 'op_lse' to LSE instruction. It is not necessary (and not valid) to append 'sfx' to 'op_llsc', as 'op_llsc' is a register-register operation which does not access memory (and does not take a size suffix). Fixes: 959bf2fd03b5 ("arm64: percpu: Rewrite per-cpu ops to allow use of LSE atomics") Signed-off-by: Mark Rutland Reviewed-by: Jinjie Ruan Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: Catalin Marinas Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Cc: stable@vger.kernel.org Reviewed-by: Vladimir Murzin Signed-off-by: Will Deacon --- arch/arm64/include/asm/percpu.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index 31193bcf89a2..8cf4068ce1b5 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -77,7 +77,7 @@ __percpu_##name##_case_##sz(void *ptr, unsigned long val) \ " stxr" #sfx "\t%w[loop], %" #w "[tmp], %[ptr]\n" \ " cbnz %w[loop], 1b", \ /* LSE atomics */ \ - #op_lse "\t%" #w "[val], %" #w "[tmp], %[ptr]\n" \ + #op_lse #sfx "\t%" #w "[val], %" #w "[tmp], %[ptr]\n" \ __nops(3)) \ : [loop] "=&r" (loop), [tmp] "=&r" (tmp), \ [ptr] "+Q"(*(u##sz *)ptr) \ @@ -98,7 +98,7 @@ __percpu_##name##_return_case_##sz(void *ptr, unsigned long val) \ " stxr" #sfx "\t%w[loop], %" #w "[ret], %[ptr]\n" \ " cbnz %w[loop], 1b", \ /* LSE atomics */ \ - #op_lse "\t%" #w "[val], %" #w "[ret], %[ptr]\n" \ + #op_lse #sfx "\t%" #w "[val], %" #w "[ret], %[ptr]\n" \ #op_llsc "\t%" #w "[ret], %" #w "[ret], %" #w "[val]\n" \ __nops(2)) \ : [loop] "=&r" (loop), [ret] "=&r" (ret), \ From 3d1ba5cbfb622025690c218d8f20da92a9ecb383 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 9 Sep 2026 17:57:07 +0200 Subject: [PATCH 1478/2366] kselftest/arm64: Fix size of thread_data values for pthread_join() pthread_join() stores the thread's return value (a "void *", i.e. 8 bytes on 64 bit computers) into the address that is passed as second parameter. However, the entries of thread_data are only normal "int"s, i.e. only 4 bytes. The additional 4 bytes of the return value clobber whatever is adjacent on the stack, i.e. other members of the thread_data array (which will be re-written in the next iteration of the for-loop, so that nobody noticed this problem), or another other local variable on the stack for the last iteration. Use "intptr_t" to declare the thread_data array entries with the correct size. Fixes: 29f080881601c ("kselftest/arm64: check GCR_EL1 after context switch") Cc: stable@vger.kernel.org Signed-off-by: Thomas Huth Signed-off-by: Will Deacon --- tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c b/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c index d23f154d3288..5d9dc8bcfbf5 100644 --- a/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c +++ b/tools/testing/selftests/arm64/mte/check_gcr_el1_cswitch.c @@ -69,7 +69,7 @@ void *execute_thread(void *x) int execute_test(pid_t pid) { pthread_t thread_id[MAX_THREADS]; - int thread_data[MAX_THREADS]; + intptr_t thread_data[MAX_THREADS]; for (int i = 0; i < MAX_THREADS; i++) pthread_create(&thread_id[i], NULL, From fdb9ebc7fb7788b8371fbef6c17dc5c8291e1429 Mon Sep 17 00:00:00 2001 From: Catalin Marinas Date: Mon, 7 Sep 2026 12:30:25 +0100 Subject: [PATCH 1479/2366] arm64: mte: Fix PTRACE_{PEEK,POKE}MTETAGS error documentation PTRACE_{PEEK,POKE}MTETAGS return -EIO rather than -EOPNOTSUPP (as documented) when no tags are copied from/to a mapping without PROT_MTE. This has been the behaviour since the interface was introduced, though the original intent was to distinguish between address not being accessible and mapped as untagged. Update the documentation to match the implementation (de-facto ABI). Since -EOPNOTSUPP was never returned, change the error assignment to -EIO as well to avoid confusion. Fixes: df9d7a22dd21 ("arm64: mte: Add Memory Tagging Extension documentation") Fixes: 18ddbaa02b7a ("arm64: mte: ptrace: Add PTRACE_{PEEK,POKE}MTETAGS support") Reported-by: Yury Khrustalev Cc: Will Deacon Cc: Mark Rutland Signed-off-by: Catalin Marinas Signed-off-by: Will Deacon --- Documentation/arch/arm64/memory-tagging-extension.rst | 5 ++--- arch/arm64/kernel/mte.c | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Documentation/arch/arm64/memory-tagging-extension.rst b/Documentation/arch/arm64/memory-tagging-extension.rst index e6fe428f0e2a..1d32fc5df183 100644 --- a/Documentation/arch/arm64/memory-tagging-extension.rst +++ b/Documentation/arch/arm64/memory-tagging-extension.rst @@ -208,11 +208,10 @@ will use the corresponding aligned address. tracer's space cannot be accessed or does not have valid tags. - ``-EPERM`` - the specified process cannot be traced. - ``-EIO`` - the tracee's address range cannot be accessed (e.g. invalid - address) and no tags copied. ``iov_len`` not updated. + address) or does not have valid tags (not mapped with the ``PROT_MTE`` + flag) and no tags copied. ``iov_len`` not updated. - ``-EFAULT`` - fault on accessing the tracer's memory (``struct iovec`` or ``iov_base`` buffer) and no tags copied. ``iov_len`` not updated. -- ``-EOPNOTSUPP`` - the tracee's address does not have valid tags (never - mapped with the ``PROT_MTE`` flag). ``iov_len`` not updated. **Note**: There are no transient errors for the requests above, so user programs should not retry in case of a non-zero system call return. diff --git a/arch/arm64/kernel/mte.c b/arch/arm64/kernel/mte.c index 1a9aad6ef22a..31f5c6b0510e 100644 --- a/arch/arm64/kernel/mte.c +++ b/arch/arm64/kernel/mte.c @@ -476,7 +476,7 @@ static int __access_remote_tags(struct mm_struct *mm, unsigned long addr, * was never mapped with PROT_MTE. */ if (!(vma->vm_flags & VM_MTE)) { - err = -EOPNOTSUPP; + err = -EIO; put_page(page); break; } From 9aa237cf66495b2426ddde8532e9b08a0ed83aaa Mon Sep 17 00:00:00 2001 From: Wei Jie LAW <98lawweijie@gmail.com> Date: Wed, 9 Sep 2026 09:15:13 +0800 Subject: [PATCH 1480/2366] HID: wacom: fix OOB read in wacom_wac_pen_serial_enforce() The 'wacom_wac_pen_serial_enforce()' function may calculate and pass an invalid offset to hid_field_extract(), resulting in memory reads at incorrect addresses -- possibly beyond the end of the report. If a field in the HID descriptor lists more usages than its Report Count actually reserves space for, the function's inner 'j' will walk past the end of the field: for (i = 0; i < report->maxfield; i++) { for (j = 0; j < report->field[i]->maxusage; j++) { ... value = hid_field_extract(hdev, raw_data + 1, offset + j * size, size); A descriptor listing 12288 usages against Report Count 1 has the loop extract the usage at index 12287 from bit offset 98296 -- about 12 KB past a 2-byte received report. The value is stored in wacom_wac->serial[0] and can reach userspace as an MSC_SERIAL event, making this an information disclosure. Clamp the loop to field->report_count, the number of value slots the report holds. Value slots past the last declared usage are still scanned; they reuse that usage (HID 1.11, 6.2.2.8). Verified on v6.12.105 with a UHID reproducer: a 2-byte report from such a descriptor trips KASAN before the patch and not after it. Fixes: 83417206427b ("HID: wacom: Queue events with missing type/serial data for later processing") Suggested-by: Jason Gerecke Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Assisted-by: GLM:glm-5.3 Signed-off-by: Wei Jie Law <98lawweijie@gmail.com> Reviewed-by: Jason Gerecke Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 0eafa483b7f7..40770affdbde 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -113,8 +113,9 @@ static int wacom_wac_pen_serial_enforce(struct hid_device *hdev, /* Queue events which have invalid tool type or serial number */ for (i = 0; i < report->maxfield; i++) { - for (j = 0; j < report->field[i]->maxusage; j++) { - struct hid_field *field = report->field[i]; + struct hid_field *field = report->field[i]; + + for (j = 0; j < field->report_count; j++) { struct hid_usage *usage = &field->usage[j]; unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid); unsigned int offset; From 462d0b066b613103f579793031429db2ca23abc0 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Fri, 11 Sep 2026 00:15:24 +0900 Subject: [PATCH 1481/2366] tools/bootconfig: Fix integer overflow and truncation in size checks Sashiko reported that on 32-bit systems, if an attacker crafts size in the bootconfig footer such that adding BOOTCONFIG_FOOTER_SIZE wraps around (for instance, if size is 0xFFFFFFFF), the size check in load_xbc_from_initrd() can be bypassed: if (stat.st_size < size + BOOTCONFIG_FOOTER_SIZE) { pr_err("bootconfig size is too big\n"); return -E2BIG; } Furthermore, on 64-bit systems with an initrd > 4.29 GB, comparing a corrupted 32-bit size (e.g. 0xFFFFFFFF) against stat.st_size - BOOTCONFIG_FOOTER_SIZE can also bypass the check if size is not bounded. Similarly, load_xbc_file() passes 64-bit stat.st_size directly into the 32-bit int size parameter of load_xbc_fd(), truncating large standalone files (>= 2GB). In both cases, passing 0xFFFFFFFF to load_xbc_fd() truncates to -1, resulting in malloc(0), an integer overflow in read(), and an out-of-bounds null-byte write. Fix this by: 1. Rejecting size > XBC_DATA_MAX or size > stat.st_size - BOOTCONFIG_FOOTER_SIZE in load_xbc_from_initrd(). 2. Rejecting stat.st_size > XBC_DATA_MAX in load_xbc_file() before passing it to load_xbc_fd(). 3. Checking size < 0 || size > XBC_DATA_MAX defensively in load_xbc_fd(). Link: https://lore.kernel.org/all/178905332413.213925.3179977110281463499.stgit@devnote2/ Fixes: 950313ebf79c ("tools: bootconfig: Add bootconfig command") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260909161113.16C691F00A3A@smtp.kernel.org/ Closes: https://lore.kernel.org/all/20260910010137.EE0431F000FF@smtp.kernel.org/ Assisted-by: Antigravity:gemini-3.8-flash Signed-off-by: Masami Hiramatsu (Google) Reviewed-by: Sang-Heon Jeon --- tools/bootconfig/main.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tools/bootconfig/main.c b/tools/bootconfig/main.c index 7dc9fff9b637..17d971d47f87 100644 --- a/tools/bootconfig/main.c +++ b/tools/bootconfig/main.c @@ -140,6 +140,9 @@ static int load_xbc_fd(int fd, char **buf, int size) { int ret; + if (size < 0 || size > XBC_DATA_MAX) + return -EINVAL; + *buf = malloc(size + 1); if (!*buf) return -ENOMEM; @@ -168,6 +171,13 @@ static int load_xbc_file(const char *path, char **buf) return ret; } + if (stat.st_size > XBC_DATA_MAX) { + pr_err("%s size is too big\n", path); + ret = -E2BIG; + close(fd); + return ret; + } + ret = load_xbc_fd(fd, buf, stat.st_size); close(fd); @@ -218,7 +228,8 @@ static int load_xbc_from_initrd(int fd, char **buf) csum = le32toh(csum); /* Wrong size error */ - if (stat.st_size < size + BOOTCONFIG_FOOTER_SIZE) { + if (size > XBC_DATA_MAX || + size > stat.st_size - BOOTCONFIG_FOOTER_SIZE) { pr_err("bootconfig size is too big\n"); return -E2BIG; } From 7812d6dab0698001e50e8c2f901e17da3eb6f429 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Fri, 11 Sep 2026 00:15:34 +0900 Subject: [PATCH 1482/2366] bootconfig: Fix integer overflow in initrd size check Sashiko reported that in get_boot_config_from_initrd(), a crafted initrd with a huge bootconfig size (such as 0xFFFFFFFF) can cause the pointer arithmetic: data = ((void *)hdr) - size; to wrap around on 32-bit systems (or when pointer subtraction overflows). Because data wraps around, the subsequent bounds check: if ((unsigned long)data < initrd_start) evaluates to false, bypassing the check. The kernel then calls xbc_calc_checksum(data, size), which attempts to read 4GB of memory, hitting unmapped pages and triggering a fatal kernel page fault during early boot. Furthermore, on 64-bit systems with an initrd > 4.29 GB, an unbounded 32-bit size can similarly bypass the initrd_start check. Fix this by: 1. Ensuring the initrd is at least large enough to contain the bootconfig footer and verifying hdr is within the initrd bounds. 2. Checking that size does not exceed XBC_DATA_MAX and does not exceed the available space between initrd_start and hdr before performing pointer subtraction. Link: https://lore.kernel.org/all/178905333479.213925.1358412668943562406.stgit@devnote2/ Fixes: de462e5f1071 ("bootconfig: Fix to remove bootconfig data from initrd while boot") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260910010137.EE0431F000FF@smtp.kernel.org/ Assisted-by: Antigravity:gemini-3.8-flash Signed-off-by: Masami Hiramatsu (Google) Reviewed-by: Sang-Heon Jeon --- init/main.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/init/main.c b/init/main.c index 2613d3f9b3ce..16749bb7a219 100644 --- a/init/main.c +++ b/init/main.c @@ -277,7 +277,8 @@ static void * __init get_boot_config_from_initrd(size_t *_size) u8 *hdr; int i; - if (!initrd_end) + if (!initrd_end || initrd_end < initrd_start || + initrd_end - initrd_start < BOOTCONFIG_MAGIC_LEN + 8) return NULL; data = (char *)initrd_end - BOOTCONFIG_MAGIC_LEN; @@ -294,16 +295,26 @@ static void * __init get_boot_config_from_initrd(size_t *_size) found: hdr = (u8 *)(data - 8); + if ((unsigned long)hdr < initrd_start) + return NULL; + size = get_unaligned_le32(hdr); csum = get_unaligned_le32(hdr + 4); - data = ((void *)hdr) - size; - if ((unsigned long)data < initrd_start) { - pr_err("bootconfig size %d is greater than initrd size %ld\n", + if (size > XBC_DATA_MAX) { + pr_err("bootconfig size %u is greater than max size %d\n", + size, XBC_DATA_MAX); + return NULL; + } + + if (size > ((unsigned long)hdr - initrd_start)) { + pr_err("bootconfig size %u is greater than initrd size %lu\n", size, initrd_end - initrd_start); return NULL; } + data = ((void *)hdr) - size; + if (xbc_calc_checksum(data, size) != csum) { pr_err("bootconfig checksum failed\n"); return NULL; @@ -394,12 +405,6 @@ static void __init setup_boot_config(void) return; } - if (size >= XBC_DATA_MAX) { - pr_err("bootconfig size %ld greater than max size %d\n", - (long)size, XBC_DATA_MAX); - return; - } - ret = xbc_init(data, size, &msg, &pos); if (ret < 0) { if (pos < 0) From aaaea79efba5a27cb9e0a5a628046d829d3f2cbb Mon Sep 17 00:00:00 2001 From: Lovekesh Solanki Date: Wed, 5 Aug 2026 01:50:31 +0530 Subject: [PATCH 1483/2366] HID: multitouch: Add report ID mismatch quirk for ASUS ROG Z13 Folio Commit e716edafedad ("HID: multitouch: Check to ensure report responses match the request") introduced validating GET_FEATURE responses return the requested report ID. ASUS ROG Z13 Flow (2025) GZ302EA touchpad (USB 0b05:1a30) returns a different report ID for Win8 feature request. Before this check, the response was still processed and allowed device to switch into its full Touchpad Precision mode. After the validation, the response is discarded before hid_report_raw_event() processes it and device remains in fallback mode and no longer exposes ABS_MT_SLOT, ABS_MT_TOOL_TYPE or the multi-finger BTN_TOOL_* capabilities for palm rejection. Add a device quirk to allow the known firmware behavior for this device while preserving report ID validation for all other devices. The device previously matched the generic MT_CLS_WIN_8 entry, so base the new class on MT_CLS_WIN_8 to keep it on the same quirk set as before the regression. MT_QUIRK_CONFIDENCE must be set explicitly: it is normally enabled by the class name check in mt_touch_input_mapping(), which only matches the MT_CLS_WIN_8* names, and it is what makes ABS_MT_TOOL_TYPE available for touchpads. Fixes: e716edafedad ("HID: multitouch: Check to ensure report responses match the request") Signed-off-by: Lovekesh Solanki Reported-by: mayhemandcoffee Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221774 Tested-by: mayhemandcoffee Link: https://bugzilla.kernel.org/show_bug.cgi?id=221774 Signed-off-by: Jiri Kosina --- drivers/hid/hid-multitouch.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 451c7324e6a0..ab4ac261fe76 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -79,6 +79,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) #define MT_QUIRK_YOGABOOK9I BIT(24) #define MT_QUIRK_KEEP_LATENCY_ON_CLOSE BIT(25) +#define MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH BIT(26) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -235,6 +236,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_APPLE_TOUCHBAR 0x0114 #define MT_CLS_YOGABOOK9I 0x0115 #define MT_CLS_EGALAX_P80H84 0x0116 +#define MT_CLS_ASUS_ROG_Z13_FOLIO 0x0117 #define MT_CLS_SIS 0x0457 #define MT_DEFAULT_MAXCONTACT 10 @@ -405,6 +407,16 @@ static const struct mt_class mt_classes[] = { .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE | MT_QUIRK_ASUS_CUSTOM_UP }, + { .name = MT_CLS_ASUS_ROG_Z13_FOLIO, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | + MT_QUIRK_WIN8_PTP_BUTTONS | + MT_QUIRK_CONFIDENCE | + MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH, + .export_all_inputs = true }, { .name = MT_CLS_VTL, .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE | @@ -507,6 +519,7 @@ static const struct attribute_group mt_attribute_group = { static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) { + struct mt_device *td = hid_get_drvdata(hdev); int ret; u32 size = hid_report_len(report); u8 *buf; @@ -528,8 +541,14 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) dev_warn(&hdev->dev, "failed to fetch feature %d\n", report->id); } else { - /* The report ID in the request and the response should match */ - if (report->id != buf[0]) { + /* + * The report ID in the request and the response should match. + * Some firmware (e.g. the ASUS ROG Z13 Folio + * touchpad) returns a mismatched ID on this specific fetch; + * tolerate it only for devices explicitly flagged as such. + */ + if (report->id != buf[0] && + !(td->mtclass.quirks & MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH)) { hid_err(hdev, "Returned feature report did not match the request\n"); goto free; } @@ -2715,6 +2734,12 @@ static const struct hid_device_id mt_devices[] = { HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288) }, + /* Asus ROG Flow Z13 (2025) GZ302EA keyboard-cover touchpad */ + { .driver_data = MT_CLS_ASUS_ROG_Z13_FOLIO, + HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, + USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO) }, + /* Generic MT device */ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH, HID_ANY_ID, HID_ANY_ID) }, From 9eb1a393c89a79c4210230d23e7d88d239c61d7b Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Wed, 26 Aug 2026 08:46:58 -0300 Subject: [PATCH 1484/2366] drm: Fix drm_pending_vblank_event leak in error path for out_fence_ptr When an out_fence_ptr is provided but DRM_MODE_PAGE_FLIP_EVENT is not set, a drm_pending_vblank_event will be allocated. If later, there is an allocation failure or another failure at setup_out_fence(), that event will not have base.fence set and it will not be released at complete_signaling(). Release the event and set crtc_state->event to NULL just like in the DRM_MODE_PAGE_FLIP_EVENT case when there is a failure at drm_event_reserve_init(). That is, prepare_signaling() releases the event and there is nothing to be done at complete_signaling(). Use drm_event_cancel_free() as that will also undo drm_event_reserve_init() in case it has been called. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260727-drm_crtc_atomic_commit_leak-v1-1-23d9948a9d7c@igalia.com?part=1 Fixes: 92c715fca907 ("drm/atomic: Fix double free in drm_atomic_state_default_clear") Signed-off-by: Thadeu Lima de Souza Cascardo Reviewed-by: Melissa Wen Signed-off-by: Melissa Wen Link: https://patch.msgid.link/20260826-drm_pending_vblank_event_leak-v4-1-f8de8b996b9d@igalia.com --- drivers/gpu/drm/drm_atomic_uapi.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index 5ea593b3a98e..68d59e17ffc2 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -1462,10 +1462,12 @@ static int prepare_signaling(struct drm_device *dev, struct dma_fence *fence; struct drm_out_fence_state *f; + ret = -ENOMEM; + f = krealloc(*fence_state, sizeof(**fence_state) * (*num_fences + 1), GFP_KERNEL); if (!f) - return -ENOMEM; + goto err_free_event; memset(&f[*num_fences], 0, sizeof(*f)); @@ -1474,12 +1476,12 @@ static int prepare_signaling(struct drm_device *dev, fence = drm_crtc_create_fence(crtc); if (!fence) - return -ENOMEM; + goto err_free_event; ret = setup_out_fence(&f[(*num_fences)++], fence); if (ret) { dma_fence_put(fence); - return ret; + goto err_free_event; } crtc_state->event->base.fence = fence; @@ -1535,6 +1537,11 @@ static int prepare_signaling(struct drm_device *dev, } return 0; + +err_free_event: + drm_event_cancel_free(dev, &crtc_state->event->base); + crtc_state->event = NULL; + return ret; } static void complete_signaling(struct drm_device *dev, From a1a5ad37e50ceb192c07ac7e2d7143638cd4d110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Onier?= Date: Wed, 12 Aug 2026 13:13:59 -0400 Subject: [PATCH 1485/2366] HID: winwing: fix use-after-free in force feedback teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit winwing_init_ff() passes the driver's private data, allocated with devm_kzalloc() in winwing_probe(), as the effect context to input_ff_create_memless(). The memoryless force-feedback core takes ownership of that pointer and frees it with kfree() from input_ff_destroy() (ml_ff_destroy()) when the input device is destroyed. Freeing a devm-managed allocation with kfree() is an invalid free, and the same object is then released again by devres when the HID device is torn down, a double free. As the allocation also embeds the LED class devices, their timers and work item live on freed memory and the slab gets corrupted. This triggers on unbind, rmmod, hot-unplug and on system suspend, where the firmware cache walks the now-corrupt devres list. KASAN reports: BUG: KASAN: invalid-free in input_ff_destroy Allocated by task N: winwing_probe Pass NULL as the memless context instead and fetch the driver data from the input device in winwing_play_effect(): the HID core already stores the hid_device as the input device's drvdata. The force-feedback core then owns nothing that it must not free. Fixes: 42d020b54edc ("HID: winwing: Enable rumble effects") Signed-off-by: René Onier Signed-off-by: Jiri Kosina --- drivers/hid/hid-winwing.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c index 9cd25a77999e..19b92c2c6579 100644 --- a/drivers/hid/hid-winwing.c +++ b/drivers/hid/hid-winwing.c @@ -315,7 +315,8 @@ static void winwing_haptic_rumble_cb(struct work_struct *work) static int winwing_play_effect(struct input_dev *dev, void *context, struct ff_effect *effect) { - struct winwing_drv_data *data = (struct winwing_drv_data *) context; + struct hid_device *hdev = input_get_drvdata(dev); + struct winwing_drv_data *data = hid_get_drvdata(hdev); if (effect->type != FF_RUMBLE) return 0; @@ -342,7 +343,12 @@ static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput) input_set_capability(hidinput->input, EV_FF, FF_RUMBLE); - return input_ff_create_memless(hidinput->input, data, + /* + * input_ff_create_memless() takes ownership of the context pointer + * and frees it on teardown; do not hand it the devm-managed drvdata. + * winwing_play_effect() fetches it from the input device instead. + */ + return input_ff_create_memless(hidinput->input, NULL, winwing_play_effect); } From aa9dde93e05a837645fdfd577eea71f0733f0694 Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Fri, 14 Aug 2026 15:06:20 +0800 Subject: [PATCH 1486/2366] HID: alps: unregister DualPoint Stick input device on remove alps_input_configured() allocates a second input device ("DualPoint Stick") with input_allocate_device() and registers it, but the alps_driver struct has no .remove handler and input2 is not tracked in hdev->inputs. The default remove path (hid_hw_stop -> hidinput_disconnect) only iterates hdev->inputs, so input2 is never unregistered and leaks on every device removal. Add a .remove handler that stops the device first (preventing URB callbacks from touching input2 during teardown) and then unregisters input2. Fixes: 2562756dde55 ("HID: add Alps I2C HID Touchpad-Stick support") Cc: stable@vger.kernel.org Signed-off-by: Chen Changcheng Signed-off-by: Jiri Kosina --- drivers/hid/hid-alps.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 67179e3fe39b..370635f5b704 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -823,6 +823,24 @@ static int alps_probe(struct hid_device *hdev, const struct hid_device_id *id) return 0; } +static void alps_remove(struct hid_device *hdev) +{ + struct alps_dev *data = hid_get_drvdata(hdev); + + /* + * input2 ("DualPoint Stick") is allocated separately and is not + * tracked in hdev->inputs, so the default remove path + * (hid_hw_stop -> hidinput_disconnect) does not unregister it. + * + * Stop the device first so that no URB callback can touch input2 + * while it is being unregistered, then drop it explicitly. + */ + hid_hw_stop(hdev); + + if (data->input2) + input_unregister_device(data->input2); +} + static const struct hid_device_id alps_id[] = { { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) }, @@ -845,6 +863,7 @@ static struct hid_driver alps_driver = { .input_configured = alps_input_configured, .resume = pm_ptr(alps_post_resume), .reset_resume = pm_ptr(alps_post_reset), + .remove = alps_remove, }; module_hid_driver(alps_driver); From d3aba3442798ce4a4c8ce3104d7b286d61e605f9 Mon Sep 17 00:00:00 2001 From: Chen Changcheng Date: Fri, 14 Aug 2026 15:06:21 +0800 Subject: [PATCH 1487/2366] HID: alps: fix use-after-free on input2 registration failure alps_input_configured() stores data->input2 before calling input_register_device(). If registration fails, input_free_device() frees the input device but data->input2 still points to the freed memory. alps_input_configured() calls hid_hw_open() before allocating input2, so URBs are already active and raw_event can fire during the failure window. A U1_SP_ABSOLUTE_REPORT_ID report arriving then causes u1_raw_event() to dereference the freed data->input2 -> use-after-free. Fix by only storing input2 into drvdata after successful registration and adding a NULL guard in the raw_event path. Fixes: 2562756dde55 ("HID: add Alps I2C HID Touchpad-Stick support") Cc: stable@vger.kernel.org Signed-off-by: Chen Changcheng Signed-off-by: Jiri Kosina --- drivers/hid/hid-alps.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 370635f5b704..0556cb5645eb 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -407,6 +407,8 @@ static int u1_raw_event(struct alps_dev *hdata, u8 *data, int size) return 1; case U1_SP_ABSOLUTE_REPORT_ID: + if (!hdata->input2) + return 0; sp_x = get_unaligned_le16(data+2); sp_y = get_unaligned_le16(data+4); @@ -738,7 +740,6 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) goto exit; } - data->input2 = input2; input2->phys = input->phys; input2->name = "DualPoint Stick"; input2->id.bustype = BUS_I2C; @@ -762,11 +763,12 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) __set_bit(INPUT_PROP_POINTER, input2->propbit); __set_bit(INPUT_PROP_POINTING_STICK, input2->propbit); - if (input_register_device(data->input2)) { + if (input_register_device(input2)) { input_free_device(input2); ret = -ENOENT; goto exit; } + data->input2 = input2; } exit: From 31fe2cb51133f00e1138c617fef8ee808a37714d Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Thu, 27 Aug 2026 13:29:49 +0200 Subject: [PATCH 1488/2366] HID: fix semantic patch and improve its performance Replace "expression" with "identifier" in the declaration of hdev. This is necessary because hdev is used as the name of a function parameter. Move the two uses of @p2 to the relevant function names. Convert <... ...>, meaning that the contained pattern is optional, to use ..., when any, and exists. This requires that the function contain calls to hid_hw_start, etc, which reduces the set of files that are considered for matching against this pattern. Reported-by: Ricardo Ribalda Signed-off-by: Julia Lawall Signed-off-by: Jiri Kosina --- scripts/coccinelle/hid/ff_race.cocci | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/coccinelle/hid/ff_race.cocci b/scripts/coccinelle/hid/ff_race.cocci index 479f5d1e3184..e861de00cb10 100644 --- a/scripts/coccinelle/hid/ff_race.cocci +++ b/scripts/coccinelle/hid/ff_race.cocci @@ -7,18 +7,19 @@ virtual report -@r@ +@r exists@ identifier probe_fn; -expression hdev, flags; +identifier hdev; +expression flags; position p1, p2; @@ probe_fn(struct hid_device *hdev, ...) { - <... + ... when any hid_hw_start@p1(hdev, flags) ... - \(input_ff_create\|input_ff_create_memless\)@p2(...) - ...> + \(input_ff_create@p2\|input_ff_create_memless@p2\)(...) + ... when any } @script:python depends on report@ From d5ea0d226e8f0801d78702142a124d78c317d822 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 2 Sep 2026 22:09:26 +0800 Subject: [PATCH 1489/2366] mmc: sh_mmcif: initialize IRQ-thread mutex before requesting interrupt The threaded IRQ handler can run before devm_request_threaded_irq() returns, but thread_lock was initialized afterwards. Initialize it before requesting either interrupt. Fixes: 8047310ee984 ("mmc: sh_mmcif: fix a race, causing an Oops on SMP") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Signed-off-by: Ulf Hansson --- drivers/mmc/host/sh_mmcif.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 7706d01b149e..8bd9cdedafa4 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -1461,6 +1461,7 @@ static int sh_mmcif_probe(struct platform_device *pdev) host->pd = pdev; spin_lock_init(&host->lock); + mutex_init(&host->thread_lock); mmc->ops = &sh_mmcif_ops; sh_mmcif_init_ocr(host); @@ -1515,8 +1516,6 @@ static int sh_mmcif_probe(struct platform_device *pdev) goto err_clk; } - mutex_init(&host->thread_lock); - ret = mmc_add_host(mmc); if (ret < 0) goto err_clk; From fce551616dfa4523c15fe50c27aa3baf8afda955 Mon Sep 17 00:00:00 2001 From: Andres Diaz Date: Mon, 31 Aug 2026 23:01:47 -0600 Subject: [PATCH 1490/2366] HID: logitech-hidpp: Add support for G502 X Lightspeed USB mouse The G502 X Lightspeed enumerates as 046d:c098 when connected with its cable. Add the id so the driver handles the wired device. Signed-off-by: Andres Diaz Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 1504de32b1c8..493763a12518 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4924,6 +4924,8 @@ static const struct hid_device_id hidpp_devices[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) }, { /* Logitech G502 X Plus Wireless Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC095) }, + { /* Logitech G502 X Lightspeed Wireless Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC098) }, { /* Logitech G703 Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) }, { /* Logitech G703 Hero Gaming Mouse over USB */ From 7e749a7972829a53fd1e41568cf2018c180627d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20H=C3=A5kansson?= Date: Tue, 1 Sep 2026 22:30:44 +0200 Subject: [PATCH 1491/2366] HID: steelseries: Add support for Arctis 7 (2018) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The headset reports connection and battery status on HID interface 5. When the device is disconnected, ignore incoming battery reports as they will incorrectly report battery level 0. Clamp overreported battery values to 100% and add USB ID 1038:12ad to the driver's device tables. Signed-off-by: Erik Håkansson Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 3 +- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-quirks.c | 1 + drivers/hid/hid-steelseries-arctis.c | 44 ++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index a81bf51cbcf1..c43e824428f0 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1210,7 +1210,8 @@ config HID_STEELSERIES depends on USB_HID help Support for Steelseries SRW-S1 steering wheel, and the Steelseries - Arctis 1 Wireless for XBox headset. + Arctis 1 Wireless for XBox, Arctis 7 (2018), Arctis 9, and Arctis + Nova headsets. config HID_SUNPLUS tristate "Sunplus wireless desktop" diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 8b4f4b02aec0..874ed8e49302 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1398,6 +1398,7 @@ #define USB_VENDOR_ID_STEELSERIES 0x1038 #define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X 0x12b6 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_2018 0x12ad #define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X 0x2253 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7 0x2202 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index c8c6b29fc04d..9ac90b7efcb1 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -752,6 +752,7 @@ static const struct hid_device_id hid_have_special_driver[] = { #if IS_ENABLED(CONFIG_HID_STEELSERIES) { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_2018) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7) }, diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c index 23fb0cebd72a..fa33fbd94889 100644 --- a/drivers/hid/hid-steelseries-arctis.c +++ b/drivers/hid/hid-steelseries-arctis.c @@ -101,6 +101,19 @@ static int steelseries_arctis_1_request_status(struct hid_device *hdev) return steelseries_send_output_report(hdev, data, sizeof(data)); } +static int steelseries_arctis_7_2018_request_status(struct hid_device *hdev) +{ + const u8 connection[] = { 0x06, 0x14 }; + const u8 battery[] = { 0x06, 0x18 }; + int ret; + + ret = steelseries_send_output_report(hdev, connection, sizeof(connection)); + if (ret) + return ret; + + return steelseries_send_output_report(hdev, battery, sizeof(battery)); +} + static int steelseries_arctis_9_request_status(struct hid_device *hdev) { const u8 data[] = { 0x00, 0x20 }; @@ -152,6 +165,27 @@ static void steelseries_arctis_1_parse_status(struct steelseries_device *sd, sd->battery_capacity = data[3]; } +static void steelseries_arctis_7_2018_parse_status(struct steelseries_device *sd, + u8 *data, int size) +{ + if (size < 3 || data[0] != 0x06) + return; + + switch (data[1]) { + case 0x14: + /* 0x03 means that the headset is connected to the transmitter. */ + sd->headset_connected = data[2] == 0x03; + break; + case 0x18: + if (!sd->headset_connected) + break; + + /* The Arctis 7 sometimes overreports battery. Cap to 100. */ + sd->battery_capacity = steelseries_map_capacity(data[2], 0, 100); + break; + } +} + static void steelseries_arctis_9_parse_status(struct steelseries_device *sd, u8 *data, int size) { @@ -232,6 +266,13 @@ static const struct steelseries_device_info arctis_1_info = { .parse_status = steelseries_arctis_1_parse_status, }; +static const struct steelseries_device_info arctis_7_2018_info = { + .sync_interface = 5, + .capabilities = SS_CAP_BATTERY, + .request_status = steelseries_arctis_7_2018_request_status, + .parse_status = steelseries_arctis_7_2018_parse_status, +}; + static const struct steelseries_device_info arctis_9_info = { .sync_interface = 0, .capabilities = SS_CAP_BATTERY, @@ -653,6 +694,9 @@ static const struct hid_device_id steelseries_arctis_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X), .driver_data = (unsigned long)&arctis_1_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_7_2018), + .driver_data = (unsigned long)&arctis_7_2018_info }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9), .driver_data = (unsigned long)&arctis_9_info }, From 798514a25544d6978d0bd7fe7071c9bdb5503076 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Fri, 11 Sep 2026 08:33:50 +0000 Subject: [PATCH 1492/2366] iommu/amd: Make iommu_sva_set_dev_pasid as static Its used inside pasid.c only. No functional changes. Signed-off-by: Vasant Hegde Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu.h | 3 --- drivers/iommu/amd/pasid.c | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h index a2fe804b038b..71113e860859 100644 --- a/drivers/iommu/amd/amd_iommu.h +++ b/drivers/iommu/amd/amd_iommu.h @@ -54,9 +54,6 @@ struct protection_domain *protection_domain_alloc(void); struct iommu_domain *amd_iommu_domain_alloc_sva(struct device *dev, struct mm_struct *mm); void amd_iommu_domain_free(struct iommu_domain *dom); -int iommu_sva_set_dev_pasid(struct iommu_domain *domain, - struct device *dev, ioasid_t pasid, - struct iommu_domain *old); void amd_iommu_remove_dev_pasid(struct device *dev, ioasid_t pasid, struct iommu_domain *domain); diff --git a/drivers/iommu/amd/pasid.c b/drivers/iommu/amd/pasid.c index d708c6532480..40be5902087c 100644 --- a/drivers/iommu/amd/pasid.c +++ b/drivers/iommu/amd/pasid.c @@ -99,9 +99,9 @@ static const struct mmu_notifier_ops sva_mn = { .release = sva_mn_release, }; -int iommu_sva_set_dev_pasid(struct iommu_domain *domain, - struct device *dev, ioasid_t pasid, - struct iommu_domain *old) +static int iommu_sva_set_dev_pasid(struct iommu_domain *domain, + struct device *dev, ioasid_t pasid, + struct iommu_domain *old) { struct pdom_dev_data *pdom_dev_data; struct protection_domain *sva_pdom = to_pdomain(domain); From 5e1afd4ea1d6a9bbaecf3e28707dac9c8b56bd45 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Fri, 11 Sep 2026 08:33:51 +0000 Subject: [PATCH 1493/2366] iommu/amd: Remove redundant check in irq_remapping_select() The amd_iommu_irq_remap flag is already validated during irq remapping domain creation (before calling amd_iommu_create_irq_domain()). The duplicate check in irq_remapping_select() is unnecessary and can be removed. Additionally, mark amd_iommu_irq_remap as static. No functional changes. Signed-off-by: Vasant Hegde Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu_types.h | 3 --- drivers/iommu/amd/init.c | 2 +- drivers/iommu/amd/iommu.c | 3 --- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h index 3dbe20023456..bce5027388b9 100644 --- a/drivers/iommu/amd/amd_iommu_types.h +++ b/drivers/iommu/amd/amd_iommu_types.h @@ -434,9 +434,6 @@ struct irq_remap_table { u32 *table; }; -/* Interrupt remapping feature used? */ -extern bool amd_iommu_irq_remap; - extern const struct iommu_ops amd_iommu_ops; /* IVRS indicates that pre-boot remapping was enabled */ diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c index edcc187b8f14..8a410d4aa370 100644 --- a/drivers/iommu/amd/init.c +++ b/drivers/iommu/amd/init.c @@ -152,7 +152,7 @@ struct ivmd_header { } __attribute__((packed)); bool amd_iommu_dump; -bool amd_iommu_irq_remap __read_mostly; +static bool amd_iommu_irq_remap __read_mostly; enum protection_domain_mode amd_iommu_pgtable = PD_MODE_V1; /* Virtual address size */ diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 4dc306a4b5c6..67a86b9e1eca 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -3976,9 +3976,6 @@ static int irq_remapping_select(struct irq_domain *d, struct irq_fwspec *fwspec, struct amd_iommu *iommu; int devid = -1; - if (!amd_iommu_irq_remap) - return 0; - if (x86_fwspec_is_ioapic(fwspec)) devid = get_ioapic_devid(fwspec->param[0]); else if (x86_fwspec_is_hpet(fwspec)) From 80a4e3ad8daba66915a9bdf0fcae5831cc8dbd5f Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Fri, 11 Sep 2026 08:33:52 +0000 Subject: [PATCH 1494/2366] iommu/amd: Remove redundant checks from interrupt handler path PPR and GAlog interrupt is enabled only if buffer is allocated. (See amd_iommu_enable_ppr_log() and iommu_ga_log_enable()). The duplicate check in interrupt hanlder path is unnecessary and can be removed. No functional changes. Signed-off-by: Vasant Hegde Signed-off-by: Joerg Roedel --- drivers/iommu/amd/iommu.c | 3 --- drivers/iommu/amd/ppr.c | 3 --- 2 files changed, 6 deletions(-) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 67a86b9e1eca..56262f6b1f70 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -1076,9 +1076,6 @@ static void iommu_poll_ga_log(struct amd_iommu *iommu) { u32 head, tail; - if (iommu->ga_log == NULL) - return; - head = readl(iommu->mmio_base + MMIO_GA_HEAD_OFFSET); tail = readl(iommu->mmio_base + MMIO_GA_TAIL_OFFSET); diff --git a/drivers/iommu/amd/ppr.c b/drivers/iommu/amd/ppr.c index 76296079bb8b..2039a9dd71ac 100644 --- a/drivers/iommu/amd/ppr.c +++ b/drivers/iommu/amd/ppr.c @@ -165,9 +165,6 @@ void amd_iommu_poll_ppr_log(struct amd_iommu *iommu) { u32 head, tail; - if (iommu->ppr_log == NULL) - return; - head = readl(iommu->mmio_base + MMIO_PPR_HEAD_OFFSET); tail = readl(iommu->mmio_base + MMIO_PPR_TAIL_OFFSET); From b63c3c26726576e2a87baeee80bc202a5a43c9e5 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Fri, 11 Sep 2026 08:33:53 +0000 Subject: [PATCH 1495/2366] iommu/amd: Remove unused macro Remove unsed device range capability related macros. No functional changes. Signed-off-by: Vasant Hegde Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu_types.h | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h index bce5027388b9..8241ef922519 100644 --- a/drivers/iommu/amd/amd_iommu_types.h +++ b/drivers/iommu/amd/amd_iommu_types.h @@ -39,18 +39,6 @@ #define MMIO_RANGE_OFFSET 0x0c #define MMIO_MISC_OFFSET 0x10 -/* Masks, shifts and macros to parse the device range capability */ -#define MMIO_RANGE_LD_MASK 0xff000000 -#define MMIO_RANGE_FD_MASK 0x00ff0000 -#define MMIO_RANGE_BUS_MASK 0x0000ff00 -#define MMIO_RANGE_LD_SHIFT 24 -#define MMIO_RANGE_FD_SHIFT 16 -#define MMIO_RANGE_BUS_SHIFT 8 -#define MMIO_GET_LD(x) (((x) & MMIO_RANGE_LD_MASK) >> MMIO_RANGE_LD_SHIFT) -#define MMIO_GET_FD(x) (((x) & MMIO_RANGE_FD_MASK) >> MMIO_RANGE_FD_SHIFT) -#define MMIO_GET_BUS(x) (((x) & MMIO_RANGE_BUS_MASK) >> MMIO_RANGE_BUS_SHIFT) -#define MMIO_MSI_NUM(x) ((x) & 0x1f) - /* Used offsets into the MMIO space */ #define MMIO_DEV_TABLE_OFFSET 0x0000 #define MMIO_CMD_BUF_OFFSET 0x0008 @@ -247,7 +235,6 @@ /* constants to configure the command buffer */ #define CMD_BUFFER_SIZE 8192 -#define CMD_BUFFER_UNINITIALIZED 1 #define CMD_BUFFER_ENTRIES 512 #define MMIO_CMD_SIZE_SHIFT 56 #define MMIO_CMD_SIZE_512 (0x9ULL << MMIO_CMD_SIZE_SHIFT) From d76994443eed0af297cb82bd038ae9d76327ef90 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Wed, 2 Sep 2026 12:45:49 +0300 Subject: [PATCH 1496/2366] HID: roccat: fix locking in roccat_connect() and roccat_disconnect() Extend critical section in roccat_connect() to ensure that partially initialized 'struct roccat_device' is never exposed in 'devices' list, and do the same in roccat_disconnect() to avoid racy 'devices' access against roccat_release(). Signed-off-by: Dmitry Antipov Signed-off-by: Jiri Kosina --- drivers/hid/hid-roccat.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c index 4f15eb951039..5deb6da8d4f7 100644 --- a/drivers/hid/hid-roccat.c +++ b/drivers/hid/hid-roccat.c @@ -344,8 +344,6 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report return temp; } - mutex_unlock(&devices_lock); - init_waitqueue_head(&device->wait); INIT_LIST_HEAD(&device->readers); mutex_init(&device->readers_lock); @@ -356,6 +354,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report device->cbuf_end = 0; device->report_size = report_size; + mutex_unlock(&devices_lock); return minor; } EXPORT_SYMBOL_GPL(roccat_connect); @@ -369,15 +368,12 @@ void roccat_disconnect(int minor) mutex_lock(&devices_lock); device = devices[minor]; - mutex_unlock(&devices_lock); device->exist = 0; /* TODO exist maybe not needed */ device_destroy(device->dev->class, MKDEV(roccat_major, minor)); - mutex_lock(&devices_lock); devices[minor] = NULL; - mutex_unlock(&devices_lock); if (device->open) { hid_hw_close(device->hid); @@ -385,6 +381,8 @@ void roccat_disconnect(int minor) } else { roccat_free_device(device); } + + mutex_unlock(&devices_lock); } EXPORT_SYMBOL_GPL(roccat_disconnect); From 39e8e085710a2d36d9ceade11739fb7b87f5aeb0 Mon Sep 17 00:00:00 2001 From: Oleg Keri Date: Thu, 10 Sep 2026 08:30:50 +0200 Subject: [PATCH 1497/2366] HID: i2c-hid: add reset quirk for Lenovo Yoga Slim 7x Gen 11 keyboard The ITE controller behind the keyboard of the Lenovo Yoga Slim 7x Gen 11 (048d:83db) carries out a reset but never raises the interrupt that acknowledges it. i2c_hid_finish_hwreset() therefore waits out its full one second timeout and logs "device did not ack reset within 1000 ms" on every probe and every resume, before the keyboard comes up regardless. Set I2C_HID_QUIRK_NO_IRQ_AFTER_RESET for it, as is already done for several other ITE parts, so the reset is followed by a fixed 100 ms sleep instead. Signed-off-by: Oleg Keri Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/i2c-hid/i2c-hid-core.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 874ed8e49302..06a1309589e3 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -765,6 +765,7 @@ #define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a #define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396 #define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987 +#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD 0x83db #define USB_DEVICE_ID_ITE8595 0x8595 #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 0ff07fdab442..ad8d9f329404 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -126,6 +126,8 @@ static const struct i2c_hid_quirks { I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { USB_VENDOR_ID_ALPS_JP, HID_ANY_ID, From abd24922c2a9797d6184be0561dd85e7bcdfd091 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Fri, 4 Sep 2026 10:58:00 +0000 Subject: [PATCH 1498/2366] HID: hid-oxp: use cancel_delayed_work_sync() in remove oxp_hid_remove() uses cancel_delayed_work() for all three delayed work items. cancel_delayed_work() only dequeues a pending work item without waiting for a currently executing callback to finish. If any of the work callbacks (oxp_rgb_queue_fn, oxp_btn_queue_fn, oxp_mcu_init_fn) is running at the time of removal, the callback continues executing concurrently with hid_hw_close() and hid_hw_stop(), accessing the HID device after it has been closed and stopped. Use cancel_delayed_work_sync() instead to ensure that any in-progress work callback completes before device teardown proceeds. Fixes: 84910c459d65 ("HID: hid-oxp: Add OneXPlayer configuration driver") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani Reviewed-by: Derek J. Clark Link: https://lore.kernel.org/r/20260804-oxp-fix-v2-1-b2d56e4c8a2c@cherr.cc Link: https://lore.kernel.org/r/20260804-oxp-fix-v1-1-51a4fe787167@cherr.cc Signed-off-by: Jiri Kosina --- drivers/hid/hid-oxp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c index d2ded6b08ce9..1e691ebc1199 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -1552,9 +1552,9 @@ static int oxp_hid_probe(struct hid_device *hdev, static void oxp_hid_remove(struct hid_device *hdev) { - cancel_delayed_work(&drvdata.oxp_rgb_queue); - cancel_delayed_work(&drvdata.oxp_btn_queue); - cancel_delayed_work(&drvdata.oxp_mcu_init); + cancel_delayed_work_sync(&drvdata.oxp_rgb_queue); + cancel_delayed_work_sync(&drvdata.oxp_btn_queue); + cancel_delayed_work_sync(&drvdata.oxp_mcu_init); hid_hw_close(hdev); hid_hw_stop(hdev); } From 58d97b45f3f43af0afd9c74e8480d22d36fd3f72 Mon Sep 17 00:00:00 2001 From: Youth Cao Date: Fri, 4 Sep 2026 00:14:44 +0800 Subject: [PATCH 1499/2366] HID: i2c-hid: Add i2c-hid-quirk-bad-input-size quirk for 0911:5288 device I have recently acquired a cheap Apollo Lake-based laptop that uses a Hynitron CST128-A touchpad controller. While booting from a Debian LiveCD, the kernel log is flooded with the following error (though the touchpad works well): i2c_hid_acpi i2c-ALPS0001:00: i2c_hid_get_input: incomplete report (27/42405) The CST128-A was identified via ACPI as ALPS0001:00, and the I2C HID device ID (0911:5288) was shared with the Hantick 5288. Add the I2C_HID_QUIRK_BAD_INPUT_SIZE quirk option to the existing Hantick 5288 quirk entry to suppress the kernel log flood. Signed-off-by: Youth Cao Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index ad8d9f329404..a61fd1de5203 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -123,7 +123,7 @@ static const struct i2c_hid_quirks { __u32 quirks; } i2c_hid_quirks[] = { { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288, - I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET | I2C_HID_QUIRK_BAD_INPUT_SIZE }, { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD, From 1d00442cc4699accfaa2317fb18259ddd7556f9a Mon Sep 17 00:00:00 2001 From: Stuart Hayhurst Date: Tue, 18 Aug 2026 14:00:53 +0100 Subject: [PATCH 1500/2366] HID: corsair-void: Fix firmware event packet description The size was incorrectly stated as 4 bytes since the ID was missed out. Add the ID in and correct the indices for the firmware versions. Signed-off-by: Stuart Hayhurst Signed-off-by: Jiri Kosina --- drivers/hid/hid-corsair-void.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-corsair-void.c b/drivers/hid/hid-corsair-void.c index 071a663a6c26..7be8c974d11e 100644 --- a/drivers/hid/hid-corsair-void.c +++ b/drivers/hid/hid-corsair-void.c @@ -51,20 +51,23 @@ /* Receiver report information: (ID 102) */ /* -------------------------------------------------------------------------- */ /* - * When queried, the recevier responds with 4 bytes to describe the firmware - * The first 2 bytes are for the receiver, the second 2 are the headset + * When queried, the receiver responds with 5 bytes to describe the firmware + * The first byte is the ID, then 2 bytes for the receiver, then 2 for the headset * The headset firmware version will be 0 if no headset is connected * - * 0: Recevier firmware major version + * 0: Report ID + * 102 for the firmware packet + * + * 1: Receiver firmware major version * Major version of the receiver's firmware * - * 1: Recevier firmware minor version + * 2: Receiver firmware minor version * Minor version of the receiver's firmware * - * 2: Headset firmware major version + * 3: Headset firmware major version * Major version of the headset's firmware * - * 3: Headset firmware minor version + * 4: Headset firmware minor version * Minor version of the headset's firmware */ /* -------------------------------------------------------------------------- */ From f3c2b266b8fd7cf816d36168d19f67e716f2c080 Mon Sep 17 00:00:00 2001 From: Berke Durak Date: Mon, 7 Sep 2026 11:47:19 -0700 Subject: [PATCH 1501/2366] HID: elecom: Add support for ELECOM M-XT4DRBK (018E) The 2025 revision of the left-handed EX-G wireless trackball (M-XT4DRBK-G) reports USB ID 056e:018e instead of 056e:00fd. Its report descriptor declares an 8-bit button field (Report Count 8) but only five usages (Usage Maximum 5), so the sixth (Fn) button ends up as a duplicate of button 5 and is unusable. The report descriptor has the same layout as the M-XT3DRBK 018C (button usage maximum at offset 16, button report count at 22, button report size at 24, padding report size at 30), so reuse that fixup. Rename the existing M_XT4DRBK define to M_XT4DRBK_00FD to match the convention used for the other EX-G revisions. Tested on a Raspberry Pi 5 with the same fixup in an out-of-tree module on 6.12 (6.12.96+rpt-rpi-2712); all six buttons are reported after the fix, and they work (tested with xev as well). Report descriptor as sent by the device, pre-fix (056e:018e, 215 bytes): 05 01 09 02 A1 01 09 01 A1 00 85 01 05 09 19 01 29 05 15 00 25 01 95 08 75 01 81 02 95 01 75 00 81 01 05 01 09 30 09 31 16 00 80 26 FF 7F 75 10 95 02 81 06 C0 A1 00 05 01 09 38 15 81 25 7F 75 08 95 01 81 06 C0 A1 00 05 0C 0A 38 02 95 01 75 08 15 81 25 7F 81 06 C0 C0 06 01 FF 09 00 A1 01 85 02 09 00 15 00 26 FF 00 75 08 95 07 81 02 C0 05 0C 09 01 A1 01 85 05 15 00 26 3C 02 19 00 2A 3C 02 75 10 95 01 81 00 C0 05 01 09 80 A1 01 85 03 19 81 29 83 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 C0 06 BC FF 09 88 A1 01 85 04 95 01 75 08 15 00 26 FF 00 19 00 2A FF 00 81 00 C0 06 02 FF 09 02 A1 01 85 06 09 02 15 00 26 FF 00 75 08 95 07 B1 02 C0 Assisted-by: LLM Signed-off-by: Berke Durak Signed-off-by: Jiri Kosina --- drivers/hid/hid-elecom.c | 6 ++++-- drivers/hid/hid-ids.h | 3 ++- drivers/hid/hid-quirks.c | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c index 37d88ce57f67..d21ead40bb07 100644 --- a/drivers/hid/hid-elecom.c +++ b/drivers/hid/hid-elecom.c @@ -79,7 +79,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB: case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F: case USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC: - case USB_DEVICE_ID_ELECOM_M_XT4DRBK: + case USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD: /* * Report descriptor format: * 12: button bit count @@ -104,6 +104,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8); break; case USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C: + case USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E: /* * Report descriptor format: * 22: button bit count @@ -148,7 +149,8 @@ static const struct hid_device_id elecom_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 06a1309589e3..9a473f72c8cb 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -477,7 +477,8 @@ #define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c -#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd +#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD 0x00fd +#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E 0x018e #define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe #define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff #define USB_DEVICE_ID_ELECOM_M_DT2DRBK 0x018d diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 9ac90b7efcb1..8c4b7580a6ee 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -432,7 +432,8 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, From 8e2a4b458ad25e13422bb059758c30a6562aa9cf Mon Sep 17 00:00:00 2001 From: Oscar Priego Verdugo Date: Mon, 17 Aug 2026 06:05:46 -0600 Subject: [PATCH 1502/2366] HID: elecom: fix bus type for M-XGL20DLBK The M-XGL20DLBK is matched as a USB device by hid-elecom, but its entry in hid_have_special_driver[] uses HID_BLUETOOTH_DEVICE. This prevents the special-driver quirk entry from matching the USB device handled by hid-elecom. Use HID_USB_DEVICE there as well. Fixes: 55633e681afb ("HID: elecom: add support for EX-G M-XGL20DLBK wireless mouse") Signed-off-by: Oscar Priego Verdugo Signed-off-by: Jiri Kosina --- drivers/hid/hid-quirks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 8c4b7580a6ee..96a36c5ba04f 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -426,7 +426,7 @@ static const struct hid_device_id hid_have_special_driver[] = { #endif #if IS_ENABLED(CONFIG_HID_ELECOM) { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, - { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, From a5c41fa7f925fda2db394329fa0b26243fa63a81 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:48 -0400 Subject: [PATCH 1503/2366] Revert "nouveau/gsp: fix suspend/resume regression on r570 firmware" This reverts commit 8302d0afeaec0bc57d951dd085e0cffe997d4d18. It turns out this looked like the right fix on some systems, but it's not - as this causes runtime PM to actually fail on many a laptop. Fixes: 8302d0afeaec ("nouveau/gsp: fix suspend/resume regression on r570 firmware") Cc: # v6.19+ Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-2-lyude@redhat.com (cherry picked from commit 94097122bfd701976bc1a62ccd434c13f3f67cde) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c | 2 +- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 2 +- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 8 ++++---- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c index f128330f30d7..40bf83ea33ac 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/fbsr.c @@ -208,7 +208,7 @@ r535_fbsr_resume(struct nvkm_gsp *gsp) } static int -r535_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) +r535_fbsr_suspend(struct nvkm_gsp *gsp) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvkm_device *device = subdev->device; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index f544afa12b6b..4a3b771ded25 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1749,7 +1749,7 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) sr->sysmemAddrOfSuspendResumeData = gsp->sr.radix3.lvl0.addr; sr->sizeOfSuspendResumeData = len; - ret = rm->api->fbsr->suspend(gsp, suspend == NVKM_RUNTIME_SUSPEND); + ret = rm->api->fbsr->suspend(gsp); if (ret) { nvkm_gsp_mem_dtor(&gsp->sr.meta); nvkm_gsp_radix3_dtor(gsp, &gsp->sr.radix3); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index 8ef8b4f65588..2945d5b4e570 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -62,7 +62,7 @@ r570_fbsr_resume(struct nvkm_gsp *gsp) } static int -r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtime) +r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) { NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS *ctrl; struct nvkm_gsp_object memlist; @@ -81,7 +81,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtim ctrl->hClient = gsp->internal.client.object.handle; ctrl->hSysMem = memlist.handle; ctrl->sysmemAddrOfSuspendResumeData = gsp->sr.meta.addr; - ctrl->bEnteringGcoffState = runtime ? 1 : 0; + ctrl->bEnteringGcoffState = 1; ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); if (ret) @@ -92,7 +92,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size, bool runtim } static int -r570_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) +r570_fbsr_suspend(struct nvkm_gsp *gsp) { struct nvkm_subdev *subdev = &gsp->subdev; struct nvkm_device *device = subdev->device; @@ -133,7 +133,7 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp, bool runtime) return ret; /* Initialise FBSR on RM. */ - ret = r570_fbsr_init(gsp, &gsp->sr.fbsr, size, runtime); + ret = r570_fbsr_init(gsp, &gsp->sr.fbsr, size); if (ret) { nvkm_gsp_sg_free(device, &gsp->sr.fbsr); return ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h index fcd0221dcea1..e9ac47d86b69 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h @@ -79,7 +79,7 @@ struct nvkm_rm_api { } *device; const struct nvkm_rm_api_fbsr { - int (*suspend)(struct nvkm_gsp *, bool runtime); + int (*suspend)(struct nvkm_gsp *); void (*resume)(struct nvkm_gsp *); } *fbsr; From 12f6eff11ccf9cad3b2dfcdd94184fdb9ffface2 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:49 -0400 Subject: [PATCH 1504/2366] drm/nouveau/gsp/r570: Set GcOff = 0 in fbsr Previously, it looked as if we were able to fix suspend/resume on some desktops by setting Gcoff based on whether or not we were entering runtime PM. This was a mistake though - the only time suspend/resume would end up actually working was if Gcoff = 0. It seems like it's likely the main reason for this is the FBSR GcOff argument actually controls GSP's behavior with regards to which buffers it decides to save across suspend/resume. When GcOff = 1, RM reserved regions are saved unless they are marked as LOST_ON_SUSPEND, and RM channel-context and kernel-client buffers are also saved -including- when they are LOST_ON_SUSPEND. This means with GcOff = 1, we end up having GSP save and restore buffers that actually need to be reinitialized on resume - causing the failures we're setting. Thanks to John Hubbard from Nvidia for providing some background on what these options do in the GSP firmware do! Signed-off-by: Lyude Paul Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: # v6.16+ Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-3-lyude@redhat.com (cherry picked from commit c7abe771e013848970421e5ca29c6b2f05c31965) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index 2945d5b4e570..af5aa5065c3d 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -81,7 +81,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) ctrl->hClient = gsp->internal.client.object.handle; ctrl->hSysMem = memlist.handle; ctrl->sysmemAddrOfSuspendResumeData = gsp->sr.meta.addr; - ctrl->bEnteringGcoffState = 1; + ctrl->bEnteringGcoffState = 0; ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); if (ret) From 24fbd6d4bcf3363ef13ebe0d36dea93f30396c6d Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:50 -0400 Subject: [PATCH 1505/2366] drm/nouveau/gsp/r570: Enable S/R Display workaround in GSP There's two flags that we've never been setting when asking GSP to suspend the GPU, which OpenRM does set: GPU_STATE_FLAGS_PRESERVING GPU_STATE_FLAGS_PM_TRANSITION These flags aren't -supposed- to do much in GSP, they're mostly used by OpenRM itself for state tracking. The only thing they do from GSP's side is control whether or not a single display related workaround is applied during suspend. But as it turns out, that single workaround is actually quite crucial for getting runtime PM working with nouveau - and without it set we end up seeing a lot more failures with runtime PM resume. So, let's start setting it. Signed-off-by: Lyude Paul Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: # v6.16+ Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-4-lyude@redhat.com (cherry picked from commit ca57629b3eb912c77bc4357178a2130ea6c2d6df) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c | 3 ++- .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c index 1488771c63fc..b45781cd0dfd 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c @@ -207,7 +207,8 @@ r570_gsp_set_rmargs(struct nvkm_gsp *gsp, bool resume) args->srInitArguments.bInPMTransition = 0; } else { args->srInitArguments.oldLevel = NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3; - args->srInitArguments.flags = 0; + args->srInitArguments.flags = + GPU_STATE_FLAGS_PRESERVING | GPU_STATE_FLAGS_PM_TRANSITION; args->srInitArguments.bInPMTransition = 1; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h index b6075021e74f..c458569af9d7 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h @@ -523,6 +523,14 @@ typedef struct #define NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3 (0x00000003U) +#define GPU_STATE_FLAGS_PRESERVING BIT(0) // GPU state is preserved +#define GPU_STATE_FLAGS_VGA_TRANSITION BIT(1) // To be used with GPU_STATE_FLAGS_PRESERVING. +#define GPU_STATE_FLAGS_PM_TRANSITION BIT(2) // To be used with GPU_STATE_FLAGS_PRESERVING. +#define GPU_STATE_FLAGS_PM_SUSPEND BIT(3) +#define GPU_STATE_FLAGS_PM_HIBERNATE BIT(4) +#define GPU_STATE_FLAGS_GC6_TRANSITION BIT(5) // To be used with GPU_STATE_FLAGS_PRESERVING. +#define GPU_STATE_FLAGS_FAST_UNLOAD BIT(6) // Used during windows restart, skips stateDestroy steps + typedef struct { // Magic for verification by secure ucode From bbb9293c9bb792f3f16c842f223b8c97bdbaf227 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 14 Aug 2026 15:43:51 -0400 Subject: [PATCH 1506/2366] drm/nouveau/gsp: Increase delay for magic sleep in r535_gsp_fini() As it turns out, Turing isn't the only architecture that needs this. On this Dell Precision 7780 with an AD103 GPU, along with pretty much every other laptop I tested, runtime PM is still somewhat unreliable. At first glance it seems as if it's fixed, but lowering the autosuspend delay to 500ms and then doing a stress test of suspend/resume cycles on the GPU ends up causing everything to start timing out. After quite a lot of digging, I eventually landed back on this magic timeout in r535_gsp_fini(). As it turns out, increasing the timeout ends up fixing the runtime PM issues as far as I can tell, even during intense stress testing. Unfortunately after spending quite a bit of time trying to dig through OpenRM to figure out what this magic sleep is actually doing, I've also come up short with any reasonable explanation. In lieu of that, I'm going to include the observations I did make while trying to figure this out in hopes someone eventually does figure this out: * The magic sleep has to occur after fbsr is initialized. Performing it at any time before that doesn't appear to work. * In situations where runtime PM starts getting flaky, some rather interesting visual effects end up happening on occasion before the GPU fully falls over. In particular, squares that look like the result of an incomplete blitting operation to a tiled buffer end up showing up on applications like vkcube. Interestingly enough, they remain in precisely the same place between runtime PM cycles until the GPU falls over - even when restarting vkcube multiple times, and even when vkcube is actively updating the screen. Even more interestingly, they're not limited to a specific framebuffer - you can see the squares changing as the cube rotates around. We cannot however, say that this is likely to be a incomplete fbsr operation. The magic sleep happens before fbsr is actually saved (which happens on the GSP unload), so it's something else. * During a short bit of testing with a desktop that I have, the magic sleep seemed to make no difference to whether or not suspend/resume works. It seems to generally work almost always. So we can assume this is likely exclusive to runtime PM, not S3. As well, here's a list of the things I tried before settling on the magic sleep: * Hooking up NV2080_CTRL_CMD_INTERNAL_GCX_ENTRY_PREREQUISITE and then blocking runtime PM until OpenRM signals that GC6/GCOFF is ready appears to make no difference. * Hooking up some (maybe not all, unsure about that part) bits of comptag saving including: * Fetching static memsys information from GSP * Adding the size of the comptag storage to the fbsr data * Adding a GA103+ workaround for disabling raw compression mode during fbsr (it doesn't seem like it applies for any systems I tried it on anyhow) * Setting bPreserveVideoMemoryAllocations=1 in GspSystemInfo So, until we can figure this out properly - just sleep for longer. Signed-off-by: Lyude Paul Fixes: 53dac0623853 ("drm/nouveau/gsp: add support for 570.144") Cc: # v6.16+ Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260814194542.781955-5-lyude@redhat.com (cherry picked from commit 09b47186a4164f3aaa3591313f80794443117342) Signed-off-by: Lyude Paul --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index 4a3b771ded25..94925f1590ea 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1761,8 +1761,12 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) * TODO: Debug the GSP firmware / RPC handling to find out why * without this Turing (but none of the other architectures) * ends up resetting all channels after resume. + * Additionally, runtime suspend on other architectures quickly + * becomes unreliable without this sleep. If you're experiencing + * issues with runtime suspend, try bumping this delay up and + * sending a patch if it fixes your GPU. */ - msleep(50); + msleep(200); } ret = r535_gsp_rpc_unloading_guest_driver(gsp, suspend); From 2deb753127d7b7035e893955c5e91875e767d1f8 Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Fri, 4 Sep 2026 19:52:23 +0800 Subject: [PATCH 1507/2366] tracing/user_events: Don't destroy fields when event removal fails destroy_user_event() destroys the event's fields before attempting to remove the trace event call. If user_event_set_call_visible() fails, e.g. because the event is still enabled and trace_remove_event_call() returns -EBUSY, the event is left registered with an irreversibly destroyed field list. Any subsequent interaction with the event then operates on an empty field list while it is still fully visible in tracefs. Move the field destruction after the call removal, and splice the field list back onto the event when the removal fails so the event remains in a consistent state. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260904115223.2976446-1-bsdhenrymartin@gmail.com Fixes: 7f5a08c79df35 ("user_events: Add minimal support for trace_event into ftrace") Signed-off-by: Henry Martin Reviewed-by: Beau Belgrave Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_user.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index 93cda2f6f269..f658c3a77aa7 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -1122,10 +1122,9 @@ static void user_event_destroy_validators(struct user_event *user) } } -static void user_event_destroy_fields(struct user_event *user) +static void user_event_destroy_fields(struct list_head *head) { struct ftrace_event_field *field, *next; - struct list_head *head = &user->fields; list_for_each_entry_safe(field, next, head, link) { list_del(&field->link); @@ -1502,17 +1501,32 @@ static int user_event_set_call_visible(struct user_event *user, bool visible) static int destroy_user_event(struct user_event *user) { + LIST_HEAD(fields); int ret = 0; lockdep_assert_held(&event_mutex); - /* Must destroy fields before call removal */ - user_event_destroy_fields(user); + /* + * Detach the fields before removing the call. Removing the event + * frees the field list memory (trace_destroy_fields() is run on + * successful removal and kmem_cache_free()s the fields), but the + * fields here are allocated and owned by user_events. Destroy + * them separately once removal has succeeded. + */ + list_splice_init(&user->fields, &fields); ret = user_event_set_call_visible(user, false); - if (ret) + if (ret) { + /* + * Removal failed and the event stays registered, recover + * the fields so it is left in a consistent state. + */ + list_splice(&fields, &user->fields); return ret; + } + + user_event_destroy_fields(&fields); dyn_event_remove(&user->devent); hash_del(&user->node); @@ -2212,7 +2226,7 @@ static int user_event_parse(struct user_event_group *group, char *name, put_user_lock: mutex_unlock(&event_mutex); put_user: - user_event_destroy_fields(user); + user_event_destroy_fields(&user->fields); user_event_destroy_validators(user); kfree(user->call.print_fmt); From 08cacffeef8f64f1a222c93467ca84f24a46c953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Sat, 22 Aug 2026 19:53:22 +0000 Subject: [PATCH 1508/2366] ftrace: fork: Initialize function graph state before copy_exec_state() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dup_task_struct() copies the parent's task_struct, including ret_stack. ftrace_graph_init_task() clears the copied function graph state, but it currently runs after copy_exec_state(). For non-CLONE_VM forks, copy_exec_state() allocates a new task_exec_state. If that allocation fails, copy_process() reaches bad_fork_free and free_task() calls ftrace_graph_exit_task(). Since the child still carries the parent's ret_stack pointer, the unwind frees the parent's active function graph return stack. The parent subsequently accesses freed memory from function_graph_enter_regs(). KASAN reports: [ 22.190920] ================================================================== [ 22.195899] BUG: KASAN: slab-use-after-free in function_graph_enter_regs+0xa76/0xb90 [ 22.200747] Write of size 8 at addr ff110000054dc0a8 by task repro/1 [ 22.205134] [ 22.210770] CPU: 0 UID: 0 PID: 1 Comm: repro Not tainted 7.2.0-07732-g9328b3b03bdc-dirty #3 PREEMPT(lazy) [ 22.212576] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 22.213750] Call Trace: [ 22.215271] [ 22.216242] ? ftrace_stub_direct_tramp+0x10/0x10 [ 22.217774] dump_stack_lvl+0x4e/0x70 [ 22.220531] print_report+0x157/0x4b4 [ 22.223202] ? fixup_red_left+0x9/0x30 [ 22.224407] ? complete_report_info+0x83/0x110 [ 22.226679] ? function_graph_enter_regs+0xa76/0xb90 [ 22.228084] kasan_report+0xce/0x100 [ 22.230109] ? function_graph_enter_regs+0xa76/0xb90 [ 22.232860] ? stack_trace_save+0x4/0xd0 [ 22.234156] function_graph_enter_regs+0xa76/0xb90 [ 22.236090] ? kasan_save_stack+0x30/0x50 [ 22.237752] ? __pfx_function_graph_enter_regs+0x10/0x10 [ 22.238694] ? ring_buffer_lock_reserve+0x345/0xf80 [ 22.239628] ? stack_trace_save+0x4/0xd0 [ 22.242121] ? stack_trace_save+0x4/0xd0 [ 22.243588] ftrace_graph_func+0xda/0x160 [ 22.245362] ? ftrace_stub_direct_tramp+0x10/0x10 [ 22.246520] 0xffffffffa0000095 [ 22.250528] ? stack_trace_save+0x9/0xd0 [ 22.251757] ? ring_buffer_unlock_commit+0x11d/0x5c0 [ 22.253152] stack_trace_save+0x9/0xd0 [ 22.254264] kasan_save_stack+0x30/0x50 [ 22.273631] kasan_save_track+0x14/0x30 [ 22.276763] kasan_save_free_info+0x3b/0x70 [ 22.278296] __kasan_slab_free+0x43/0x70 [ 22.280157] kmem_cache_free+0xbf/0x3b0 [ 22.282963] ? ftrace_stub_direct_tramp+0x10/0x10 [ 22.284001] free_task+0xa2/0x160 [ 22.285699] ? ftrace_stub_direct_tramp+0x10/0x10 [ 22.286752] copy_process+0x2aae/0x7bc0 Initialize the child function graph state immediately after dup_task_struct(), before the first fallible operation. Cc: stable@vger.kernel.org Fixes: 6b1c66c9cca9 ("exec_state: relocate dumpable information") Reviewed-by: Bradley Morgan Link: https://patch.msgid.link/20260822195321.962383-2-Jeremy.Jean@oss.cyber.gouv.fr Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Signed-off-by: Steven Rostedt --- kernel/fork.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kernel/fork.c b/kernel/fork.c index 416758c8a3d4..a5934a317634 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -2133,6 +2133,11 @@ __latent_entropy struct task_struct *copy_process( p = dup_task_struct(current, node); if (!p) goto fork_out; + /* + * Must run before the first fallible op, so error paths never + * free the parent's ret_stack. + */ + ftrace_graph_init_task(p); retval = copy_exec_state(clone_flags, p); if (retval) goto bad_fork_free; @@ -2159,8 +2164,6 @@ __latent_entropy struct task_struct *copy_process( */ p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? args->child_tid : NULL; - ftrace_graph_init_task(p); - rt_mutex_init_task(p); raw_spin_lock_init(&p->blocked_lock); From b22845487096247f5370c412376a472304627847 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sun, 6 Sep 2026 06:19:22 +0900 Subject: [PATCH 1509/2366] fgraph: Remove unused FGRAPH_MAX_INDEX FGRAPH_MAX_INDEX has no user, and it expands to FGRAPH_INDEX_SIZE and FGRAPH_RET_INDEX, neither of which is defined anywhere in the tree. It was added in that form by commit 91c46b0aa917 ("function_graph: Implement fgraph_reserve_data() and fgraph_retrieve_data()"), which introduced the current data word layout under new names, so anything referencing it would have failed to build ever since. Remove it. Link: https://patch.msgid.link/20260905211922.1196366-1-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Acked-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- kernel/trace/fgraph.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/kernel/trace/fgraph.c b/kernel/trace/fgraph.c index 40d373d65f9b..ed455b53513b 100644 --- a/kernel/trace/fgraph.c +++ b/kernel/trace/fgraph.c @@ -143,9 +143,6 @@ enum { #define FGRAPH_DATA_INDEX_MASK GENMASK(FGRAPH_DATA_INDEX_BITS - 1, 0) #define FGRAPH_DATA_INDEX_SHIFT (FGRAPH_DATA_SHIFT + FGRAPH_DATA_BITS) -#define FGRAPH_MAX_INDEX \ - ((FGRAPH_INDEX_SIZE << FGRAPH_DATA_BITS) + FGRAPH_RET_INDEX) - #define FGRAPH_ARRAY_SIZE FGRAPH_INDEX_BITS /* From 0701995aaf8fc2281154db829ca85e231951e51d Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sun, 6 Sep 2026 12:44:06 +0900 Subject: [PATCH 1510/2366] function_graph: Use the saved entry's size when reprinting it When a graph entry does not fit in the trace_seq, print_graph_entry() saves it in the iterator's fgraph_data and reprints it on the next read. The entry has already been consumed from the ring buffer by then, so the copy is all that is left of it. The copy is sized with iter->ent_size, which no longer describes the saved entry but whatever entry the iterator has moved on to. The argument count is derived from the same field, so a 72 byte entry saved and then reprinted ahead of a 48 byte return entry loses its arguments. Record the size next to the failure flag, so that the two are always set together, and restore it before reprinting. Cc: stable@vger.kernel.org Fixes: ff5c9c576e75 ("ftrace: Add support for function argument to graph tracer") Link: https://patch.msgid.link/20260906034406.1335316-1-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_functions_graph.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/trace/trace_functions_graph.c b/kernel/trace/trace_functions_graph.c index ff7cb1a76b95..c5befd0c5b9a 100644 --- a/kernel/trace/trace_functions_graph.c +++ b/kernel/trace/trace_functions_graph.c @@ -52,6 +52,7 @@ struct fgraph_data { }; struct ftrace_graph_ret_entry ret; int failed; + int ent_size; int cpu; }; @@ -1274,6 +1275,7 @@ print_graph_entry(struct ftrace_graph_ent_entry *field, struct trace_seq *s, if (s->full) { data->failed = 1; data->cpu = cpu; + data->ent_size = iter->ent_size; } else data->failed = 0; } @@ -1457,6 +1459,7 @@ print_graph_function_flags(struct trace_iterator *iter, u32 flags) if (data && data->failed) { field = &data->ent.ent; iter->cpu = data->cpu; + iter->ent_size = data->ent_size; ret = print_graph_entry(field, s, iter, flags); if (ret == TRACE_TYPE_HANDLED && iter->cpu != cpu) { per_cpu_ptr(data->cpu_data, iter->cpu)->ignore = 1; From 4bddcb346a6cf4615ca77f69a589623b877ca267 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sun, 6 Sep 2026 21:40:25 +0900 Subject: [PATCH 1511/2366] tracing: Free histogram var refs regardless of how often they are referenced Using the same variable three or more times in one hist trigger leaks the variable reference and its strings when the trigger is removed. commit 656fe2ba85e8 ("tracing: Use hist trigger's var_ref array to destroy var_refs") made a trigger's var_refs[] array the only owner of a var ref: destroy_hist_field() returns early for HIST_FIELD_FL_VAR_REF, so the field expressions never destroy one. One entry, freed once, no count needed. commit 8bcebc77e85f ("tracing: Fix histogram code when expression has same var as value") then made repeated references share one object and added a count of them. Only the increment side exists, since those expressions still return early and never drop a reference, so __destroy_hist_field() sees how many references were created rather than how many are left. It frees when the decremented count is 0 or 1, so two references work and three or more leak. Sharing kept one array entry per object, and create_var_ref() searches and appends within a single trigger, so nothing outside it holds the object. Removing a trigger whose variables are still referenced is already refused by check_var_refs() with -EBUSY. Drop the count and free unconditionally. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260906124025.3550596-1-donggeunyoo.kernel@gmail.com Fixes: 8bcebc77e85f ("tracing: Fix histogram code when expression has same var as value") Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 963e0d6b61fd..f90680b33a37 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -169,7 +169,6 @@ struct hist_field { struct hist_field *operands[HIST_FIELD_OPERANDS_MAX]; struct hist_trigger_data *hist_data; enum hist_field_fn fn_num; - unsigned int ref; unsigned int size; unsigned int offset; unsigned int is_signed; @@ -1913,16 +1912,8 @@ static int contains_operator(char *str, char **sep) return field_op; } -static void get_hist_field(struct hist_field *hist_field) -{ - hist_field->ref++; -} - static void __destroy_hist_field(struct hist_field *hist_field) { - if (--hist_field->ref > 1) - return; - kfree(hist_field->var.name); kfree(hist_field->name); @@ -1969,8 +1960,6 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data, if (!hist_field) return NULL; - hist_field->ref = 1; - hist_field->hist_data = hist_data; if (flags & HIST_FIELD_FL_EXPR || flags & HIST_FIELD_FL_ALIAS) @@ -2223,10 +2212,8 @@ static struct hist_field *create_var_ref(struct hist_trigger_data *hist_data, for (i = 0; i < hist_data->n_var_refs; i++) { ref_field = hist_data->var_refs[i]; if (ref_field->var.idx == var_field->var.idx && - ref_field->var.hist_data == var_field->hist_data) { - get_hist_field(ref_field); + ref_field->var.hist_data == var_field->hist_data) return ref_field; - } } /* Sanity check to avoid out-of-bound write on 'hist_data->var_refs' */ if (hist_data->n_var_refs >= TRACING_MAP_VARS_MAX) @@ -3276,7 +3263,6 @@ static struct hist_field *create_var(struct hist_trigger_data *hist_data, goto out; } - var->ref = 1; var->flags = HIST_FIELD_FL_VAR; var->var.idx = idx; var->var.hist_data = var->hist_data = hist_data; From 516001d53e6b2ea95a251ee2ef54a1a689a3fd58 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Sun, 6 Sep 2026 22:33:52 +0900 Subject: [PATCH 1512/2366] tracing: Free histogram the var ref when its initialization fails create_var_ref() allocates a VAR_REF hist_field and then calls init_var_ref() to fill it in. When that fails the field is leaked. commit 656fe2ba85e8 ("tracing: Use hist trigger's var_ref array to destroy var_refs") made destroy_hist_field() return early for HIST_FIELD_FL_VAR_REF, since var refs are freed by walking the trigger's var_refs[] array instead. create_var_ref() adds the field to that array only after init_var_ref() has succeeded, so on this path the field is in neither place and nothing frees it. The call was correct when it was written, before var refs were taken out of destroy_hist_field(). init_var_ref() cannot free it either. The caller owns the field, so init_var_ref() undoes only its own string allocations and leaves the field alone. Freeing it there would leave create_var_ref() passing freed memory to destroy_hist_field(), which reads its flags. Call __destroy_hist_field(), which frees the field without consulting the flag. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260906133352.3815019-1-donggeunyoo.kernel@gmail.com Fixes: 656fe2ba85e8 ("tracing: Use hist trigger's var_ref array to destroy var_refs") Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index f90680b33a37..bbdd56208eff 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -2221,7 +2221,7 @@ static struct hist_field *create_var_ref(struct hist_trigger_data *hist_data, ref_field = create_hist_field(var_field->hist_data, NULL, flags, NULL); if (ref_field) { if (init_var_ref(ref_field, var_field, system, event_name)) { - destroy_hist_field(ref_field, 0); + __destroy_hist_field(ref_field); return NULL; } From 230234d12ce42ab04132a32c3a848f07a5d27a71 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 12:49:48 +0900 Subject: [PATCH 1513/2366] tracing: Free histogram the field rejected for a bad modifier Writing a hist trigger whose value or variable carries a modifier that is not allowed there leaks the fields that were built for it. __create_val_field() takes the field from parse_expr() and stores it in hist_data->fields[] only after the modifier checks have run: hist_field = parse_expr(hist_data, file, field_str, flags, var_name, &n_subexprs); ... if (hist_field->flags & HIST_FIELD_FL_VAR) { if (hist_field->flags & (...)) goto err; } else { if (hist_field->flags & (...)) goto err; } hist_data->fields[val_idx] = hist_field; Both checks jump past that store, and the err label returns without freeing anything. The error unwinds to create_hist_data(), which calls destroy_hist_data() -> destroy_hist_fields(), and that reaches a field only by walking fields[]. A field that never got there is unreachable. commit e0213434fe3e ("tracing: Do not let histogram values have some modifiers") set ret to -EINVAL and fell through to the store, which left the field owned by fields[] and freed along with the rest of hist_data. Splitting the check into a value case and a variable case replaced that fall-through with a goto that skips it. With CONFIG_DEBUG_KMEMLEAK, 200 writes of # echo 'hist:keys=prev_pid:vals=next_pid.log2' > \ events/sched/sched_switch/trigger each correctly rejected with -EINVAL, leave 332 unreferenced objects (63744 bytes) reported at create_hist_field(); 200 install and remove cycles of a valid trigger leave none. A '.log2' field is two allocations, since create_hist_field() puts the plain field in operands[0] of the log2 field, and both are reported. Use destroy_hist_field() rather than __destroy_hist_field() so that operands[0] is freed as well. It returns early for HIST_FIELD_FL_VAR_REF, which is what an operand owned by hist_data->var_refs[] needs; the rejected field itself is never a var ref, because a var ref never carries a modifier flag. Cc: stable@vger.kernel.org Fixes: e30fbc618e97 ("tracing/histograms: Allow variables to have some modifiers") Link: https://patch.msgid.link/20260907034948.240387-1-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index bbdd56208eff..8cad99a8d01e 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -4317,6 +4317,7 @@ static int __create_val_field(struct hist_trigger_data *hist_data, return ret; err: hist_err(file->tr, HIST_ERR_BAD_FIELD_MODIFIER, errpos(field_str)); + destroy_hist_field(hist_field, 0); return -EINVAL; } From 06f5634ec5584954177f9a22e36b3bfb398a971b Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 15:03:23 +0900 Subject: [PATCH 1514/2366] tracing: Keep the entry count when the histogram stats allocation fails print_entries() uses n_entries both as the number of sort entries and as its own return value, so the -ENOMEM it stores when the stats allocation fails overwrites the count that the cleanup still needs: n_entries = tracing_map_sort_entries(map, ...); if (n_entries < 0) return n_entries; ... if (!stats) { n_entries = -ENOMEM; goto out; } ... out: tracing_map_destroy_sort_entries(sort_entries, n_entries); tracing_map_destroy_sort_entries() takes an unsigned int and loops up to it, so -ENOMEM arrives as 4294967284. It walks an array of at most map->max_elts pointers and calls destroy_sort_entry(), which dereferences and frees, on whatever lies past the end. Reading the hist file of a trigger with a .percent value, with that allocation forced to fail: BUG: KASAN: vmalloc-out-of-bounds in tracing_map_destroy_sort_entries+0xa0/0xb0 Read of size 8 at addr ffffc90000045000 by task init/1 tracing_map_destroy_sort_entries+0xa0/0xb0 hist_show+0x6f7/0x1df0 seq_read_iter+0x2b8/0x1190 vfs_read+0x176/0xa40 The buggy address belongs to a 4-page vmalloc region starting at ffffc90000041000 allocated at tracing_map_sort_entries+0x5c/0xd50 A few pages further the fault is fatal. The registers at the oops confirm the bound: the loop's end pointer less the array start, over the pointer size, is 4294967284. Return the error in a separate variable and leave n_entries holding the count, the way tracing_map_sort_entries() does on its own error path. The stats block is only entered for a value carrying .percent or .graph, which __create_val_field() has rejected since v6.3, so this cannot be reached in mainline as it stands. It becomes reachable again with "tracing: hist: let values keep the percent and graph modifiers", so it should be applied first. Cc: stable@vger.kernel.org Fixes: abaa5258ce5e ("tracing: Add .percent suffix option to histogram values") Link: https://patch.msgid.link/20260907060323.480728-1-donggeunyoo.kernel@gmail.com Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/all/20260907053113.1CED91F00A3A@smtp.kernel.org/ Signed-off-by: Donggeun Yoo Acked-by: Masami Hiramatsu (Google) Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 8cad99a8d01e..8d80562fb502 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -5677,7 +5677,7 @@ static int print_entries(struct seq_file *m, { struct tracing_map_sort_entry **sort_entries = NULL; struct tracing_map *map = hist_data->map; - int i, j, n_entries; + int i, j, n_entries, ret; struct hist_val_stat *stats = NULL; u64 val; @@ -5687,6 +5687,8 @@ static int print_entries(struct seq_file *m, if (n_entries < 0) return n_entries; + ret = n_entries; + /* Calculate the max and the total for each field if needed. */ for (j = 0; j < hist_data->n_vals; j++) { if (!(hist_data->fields[j]->flags & @@ -5695,7 +5697,7 @@ static int print_entries(struct seq_file *m, if (!stats) { stats = kzalloc_objs(*stats, hist_data->n_vals); if (!stats) { - n_entries = -ENOMEM; + ret = -ENOMEM; goto out; } } @@ -5716,7 +5718,7 @@ static int print_entries(struct seq_file *m, out: tracing_map_destroy_sort_entries(sort_entries, n_entries); - return n_entries; + return ret; } static void hist_trigger_show(struct seq_file *m, From 3d617bfd79330ae3acf94862c18bb3ccf5f5a0f9 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 14:21:13 +0900 Subject: [PATCH 1515/2366] tracing: Let histogram values keep the percent and graph modifiers The .percent and .graph modifiers exist only for histogram values, but a value carrying either of them has been rejected since v6.3. The example in Documentation/trace/histogram.rst, # echo 'hist:keys=prev_comm:vals=hitcount.percent:nohitcount' > \ events/sched/sched_switch/trigger returns -EINVAL. parse_field() sets the two flags only when the field is neither a key nor a variable, that is, only on a value: } else if (strncmp(modifier, "percent", 7) == 0) { if (*flags & (HIST_FIELD_FL_VAR | HIST_FIELD_FL_KEY)) goto error; *flags |= HIST_FIELD_FL_PERCENT; __create_val_field() then rejects a value for carrying them, so no field can reach hist_trigger_print_val(), where both are implemented. commit e0213434fe3e ("tracing: Do not let histogram values have some modifiers") added the check after a value with .buckets oopsed in hist_field_name(). That happens because .buckets and .log2 make create_hist_field() build a nested field in operands[0] which hist_field_name() then walks into. The percent and graph flags do not create an operand and are not read by hist_field_name(); they are only used when printing a value. Stop rejecting the two flags on a value. The check for variables is left alone, where they are unreachable anyway because parse_field() rejects a variable carrying them first. With the two flags removed, the trigger above installs and prints as documented: { prev_comm: rcu_preempt } hitcount (%): 0.00 { prev_comm: init } hitcount (%): 99.98 Totals: Hits: 237896 Cc: stable@vger.kernel.org Fixes: e0213434fe3e ("tracing: Do not let histogram values have some modifiers") Link: https://patch.msgid.link/20260907052113.430818-1-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 8d80562fb502..5e00da2d5b1a 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -4299,8 +4299,7 @@ static int __create_val_field(struct hist_trigger_data *hist_data, goto err; } else { /* Value */ - if (hist_field->flags & (HIST_FIELD_FL_GRAPH | HIST_FIELD_FL_PERCENT | - HIST_FIELD_FL_BUCKET | HIST_FIELD_FL_LOG2 | + if (hist_field->flags & (HIST_FIELD_FL_BUCKET | HIST_FIELD_FL_LOG2 | HIST_FIELD_FL_SYM | HIST_FIELD_FL_SYM_OFFSET | HIST_FIELD_FL_SYSCALL | HIST_FIELD_FL_STACKTRACE)) goto err; From 89b000ba0796593aa61f6eec24d369337594588b Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 11:56:08 +0530 Subject: [PATCH 1516/2366] tracing: Fix typo "availabe" in comment Correct "availabe" to "available", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Link: https://patch.msgid.link/20260907062608.13924-1-hemanth.selam@gmail.com Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Steven Rostedt --- kernel/trace/rethook.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/rethook.c b/kernel/trace/rethook.c index 5a8bdf88999a..87a27f3aa4a6 100644 --- a/kernel/trace/rethook.c +++ b/kernel/trace/rethook.c @@ -171,7 +171,7 @@ struct rethook_node *rethook_try_get(struct rethook *rh) * This expects the caller will set up a rethook on a function entry. * When the function returns, the rethook will eventually be reclaimed * or released in the rethook_recycle() with call_rcu(). - * This means the caller must be run in the RCU-availabe context. + * This means the caller must be run in the RCU-available context. */ if (unlikely(!rcu_is_watching())) return NULL; From 0999d3e16d13b6299fd7cc7a7fb2825c18e90dd0 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:26:07 +0530 Subject: [PATCH 1517/2366] tracing: Fix typo "preceeded" in comment Correct "preceeded" to "Preceded", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Link: https://patch.msgid.link/20260907065607.36615-1-hemanth.selam@gmail.com Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Steven Rostedt --- include/trace/events/timer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/trace/events/timer.h b/include/trace/events/timer.h index ca82fd62dc30..3aa0608c6361 100644 --- a/include/trace/events/timer.h +++ b/include/trace/events/timer.h @@ -302,7 +302,7 @@ DECLARE_EVENT_CLASS(hrtimer_class, * hrtimer_start_expired - Invoked when a expired timer was started * @hrtimer: pointer to struct hrtimer * - * Preceeded by a hrtimer_start tracepoint. + * Preceded by a hrtimer_start tracepoint. */ DEFINE_EVENT(hrtimer_class, hrtimer_start_expired, From 6ede78d0563a2a3ae3e46f9c07cedb5d79645429 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 18:14:15 +0900 Subject: [PATCH 1518/2366] tracing: Set the trace clock before registering the histogram trigger hist_register_trigger() puts the trigger on the global named_triggers list in cmd_ops->init(), and only then sets the trace clock: if (data->cmd_ops->init) { ret = data->cmd_ops->init(data); if (ret < 0) goto out; } if (hist_data->enable_timestamps) { ret = tracing_set_clock(file->tr, hist_data->attrs->clock); if (ret) { hist_err(tr, HIST_ERR_SET_CLOCK_FAIL, errpos(clock)); goto out; } The clock string is not checked anywhere before that call, so a named trigger using common_timestamp with an unknown clock fails after it has already become findable. event_hist_trigger_parse() then frees it without taking it off the list, and the next lookup by name reads the freed object: ~# cd /sys/kernel/tracing/events/sched/sched_switch ~# echo 'hist:name=foo:keys=common_pid:ts=common_timestamp:clock=bogus' > trigger bash: echo: write error: Invalid argument ~# echo 'hist:name=foo:keys=common_pid' > trigger BUG: KASAN: slab-use-after-free in find_named_trigger+0xac/0xc0 Read of size 8 at addr ffff88800915d760 by task init/1 find_named_trigger+0xac/0xc0 hist_register_trigger+0xc1/0x900 event_hist_trigger_parse+0x3146/0x6af0 event_trigger_write+0xce/0x160 Freed by task 63: kfree+0x154/0x420 trigger_kthread_fn+0xfd/0x160 Set the clock before the trigger is registered, so that nothing which can fail runs after it is published, the way commit 6f86bdeab633 ("tracing: Fix bad hist from corrupting named_triggers list") moved the registration below the rest of the setup. tracing_set_filter_buffering() is reference counted, so the init failure path has to drop the reference that the clock block now takes first. Cc: stable@vger.kernel.org Fixes: a4072fe85ba3 ("tracing: Add a clock attribute for hist triggers") Link: https://patch.msgid.link/20260907091415.554535-1-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 5e00da2d5b1a..1889e310b73c 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6631,12 +6631,6 @@ static int hist_register_trigger(char *glob, data->cmd_ops = cmd_ops; } - if (data->cmd_ops->init) { - ret = data->cmd_ops->init(data); - if (ret < 0) - goto out; - } - if (hist_data->enable_timestamps) { char *clock = hist_data->attrs->clock; @@ -6649,6 +6643,15 @@ static int hist_register_trigger(char *glob, tracing_set_filter_buffering(file->tr, true); } + if (data->cmd_ops->init) { + ret = data->cmd_ops->init(data); + if (ret < 0) { + if (hist_data->enable_timestamps) + tracing_set_filter_buffering(file->tr, false); + goto out; + } + } + if (named_data) { remove_hist_vars(hist_data); destroy_hist_data(hist_data); From 0fe23b8eaba0d3372c66b7b31204408da0715edc Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 21:44:19 +0900 Subject: [PATCH 1519/2366] tracing: Take the reference before publishing the named histogram trigger event_hist_trigger_named_init() puts the trigger on the global named_triggers list and only then takes the reference on the trigger it shares its histogram with: data->ref++; save_named_trigger(data->named_data->name, data); ret = event_hist_trigger_init(data->named_data); if (ret < 0) { kfree(data->cmd_ops); data->cmd_ops = &trigger_hist_cmd; } return ret; event_hist_trigger_init() fails when alloc_hist_pad() cannot allocate, and nothing takes the trigger back off the list on the way out. event_hist_trigger_parse() frees it, and the next lookup by name reads the freed object: BUG: KASAN: slab-use-after-free in find_named_trigger+0xac/0xc0 Read of size 8 at addr ffff888009346860 by task init/1 find_named_trigger+0xac/0xc0 hist_register_trigger+0xc1/0xa00 event_hist_trigger_parse+0x3146/0x6af0 event_trigger_write+0xce/0x160 Freed by task 67: kfree+0x154/0x420 trigger_kthread_fn+0xfd/0x160 Do the reference first and publish once it has succeeded, so that nothing which can fail runs after the trigger becomes findable. Cc: stable@vger.kernel.org Fixes: 7ab0fc61ce73 ("tracing: Move histogram trigger variables from stack to per CPU structure") Reported-by: Sashiko AI Closes: https://lore.kernel.org/linux-trace-kernel/20260907092944.3950E1F00A3D@smtp.kernel.org/ Link: https://patch.msgid.link/20260907124420.607097-2-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Acked-by: Tom Zanussi Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 1889e310b73c..54c95f9bd0a3 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6371,17 +6371,18 @@ static int event_hist_trigger_named_init(struct event_trigger_data *data) { int ret; - data->ref++; - - save_named_trigger(data->named_data->name, data); - ret = event_hist_trigger_init(data->named_data); if (ret < 0) { kfree(data->cmd_ops); data->cmd_ops = &trigger_hist_cmd; + return ret; } - return ret; + data->ref++; + + save_named_trigger(data->named_data->name, data); + + return 0; } static void event_hist_trigger_named_free(struct event_trigger_data *data) From 92383cef66791a0c63a2f27755cadbdb2fbf270b Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 21:44:20 +0900 Subject: [PATCH 1520/2366] tracing: Undo the registration when enabling the histogram trigger fails Commit 6f86bdeab633 ("tracing: Fix bad hist from corrupting named_triggers list") described how a trigger that is registered but not on file->triggers ends up freed while still on the global named_triggers list, and moved the registration down so that hist_trigger_enable() follows it immediately. One path still gets there. hist_trigger_enable() adds the trigger and takes it straight back out when the event cannot be enabled: list_add_tail_rcu(&data->list, &file->triggers); update_cond_flag(file); if (trace_event_trigger_enable_disable(file, 1) < 0) { list_del_rcu(&data->list); update_cond_flag(file); ret--; } so the list walk in hist_unregister_trigger() matches nothing, test stays NULL, and the ->free() that would call del_named_trigger() is skipped. out_unreg falls through to out_free, which frees the trigger anyway: BUG: KASAN: slab-use-after-free in find_named_trigger+0xac/0xc0 Read of size 8 at addr ffff8880091d3160 by task init/1 find_named_trigger+0xac/0xc0 hist_register_trigger+0xc1/0xa00 event_hist_trigger_parse+0x3146/0x6af0 event_trigger_write+0xce/0x160 Freed by task 69: kfree+0x154/0x420 trigger_kthread_fn+0xfd/0x160 Leave the trigger where hist_unregister_trigger() can find it and let that undo the registration, which is the only code that knows all of what cmd_ops->init() took: the named list entry, the hist_pad reference, the reference on the trigger a named histogram is shared with, and the copied cmd_ops. It also pairs the failed trace_event_trigger_enable_disable(), whose sm_ref and buffered event reference are otherwise left behind. Since ->free() releases trigger_data and, for a trigger that does not share its histogram, hist_data with it, out_unreg can no longer fall through to out_free. For a trigger that does share, hist_register_trigger() has already destroyed the caller's hist_data, so the fall-through was reading freed memory there as well. Move the enable_timestamps check in hist_unregister_trigger() above the ->free() call for the same reason: hist_data does not outlive it once the trigger being removed is the one that owns it. Cc: stable@vger.kernel.org Fixes: 067fe038e70f ("tracing: Add variable reference handling to hist triggers") Reported-by: Sashiko AI Closes: https://lore.kernel.org/linux-trace-kernel/20260907092944.3950E1F00A3D@smtp.kernel.org/ Link: https://patch.msgid.link/20260907124420.607097-3-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 54c95f9bd0a3..53100287466f 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6670,11 +6670,12 @@ static int hist_trigger_enable(struct event_trigger_data *data, update_cond_flag(file); - if (trace_event_trigger_enable_disable(file, 1) < 0) { - list_del_rcu(&data->list); - update_cond_flag(file); + /* + * On failure the caller undoes the registration, and + * hist_unregister_trigger() can only find the trigger here. + */ + if (trace_event_trigger_enable_disable(file, 1) < 0) ret--; - } return ret; } @@ -6752,13 +6753,13 @@ static void hist_unregister_trigger(char *glob, } } - if (test && test->cmd_ops->free) - test->cmd_ops->free(test); - if (hist_data->enable_timestamps) { if (!hist_data->remove || test) tracing_set_filter_buffering(file->tr, false); } + + if (test && test->cmd_ops->free) + test->cmd_ops->free(test); } static bool hist_file_check_refs(struct trace_event_file *file) @@ -6963,6 +6964,8 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops, return ret; out_unreg: event_trigger_unregister(cmd_ops, file, glob+1, trigger_data); + /* The unregister frees trigger_data, skip out_free */ + goto out; out_free: remove_hist_vars(hist_data); From a5e70ba87ca8ebc79b4e63de302d03b0625fe153 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Tue, 8 Sep 2026 00:50:44 +0900 Subject: [PATCH 1521/2366] tracing: Fix memory corruption from the histogram stacktrace modifier parse_field() sets HIST_FIELD_FL_STACKTRACE from the ".stacktrace" modifier before it looks the field name up, and nothing afterwards checks that the name resolved to a field which holds a stacktrace. create_hist_field() picks HIST_FIELD_FN_STACK on the strength of the field pointer alone, which reads a __data_loc word from the record and follows its low 16 bits as an offset into the same record. event_hist_trigger() takes the first word there as an entry count and copies that many longs into a 31 entry array: n_entries = *stack; memcpy(entries, ++stack, n_entries * sizeof(unsigned long)); Neither end of that copy is bounded, and the count is whatever the event holds at the offset, so any field will do: # cd /sys/kernel/tracing/events/sched/sched_process_fork # echo 'hist:keys=parent_pid.stacktrace' > trigger # (true) BUG: kernel NULL pointer dereference, address: 0000000000000008 RIP: 0010:rb_insert_color+0x18/0x130 timerqueue_linked_add+0x7e/0xd0 enqueue_hrtimer+0x39/0xb0 __hrtimer_run_queues+0x10f/0x1f0 RIP: 0010:memcpy+0xc/0x30 event_hist_trigger+0x165/0x690 The timer interrupt landed on the rbtree the copy had already run over. No debug options are needed for this; KASAN reports the same write as an out-of-bounds read of 13835058055416381440 bytes. Documentation/trace/histogram.rst already states the rule, "must be a long[] type", so enforce it once the name has been resolved. Names which resolve to no field at all, "hitcount.stacktrace" and the common_* pseudo-fields, are refused for the same reason: they hold no stacktrace to read. Cc: stable@vger.kernel.org Fixes: cc5fc8bfc961 ("tracing/histogram: Add stacktrace type") Link: https://patch.msgid.link/20260907155045.692664-2-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 53100287466f..9bc829c1e876 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -2317,6 +2317,7 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file, struct ftrace_event_field *field = NULL; char *field_name, *modifier, *str; struct trace_array *tr = file->tr; + bool stack_modifier = false; modifier = str = kstrdup(field_str, GFP_KERNEL); if (!modifier) @@ -2339,9 +2340,10 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file, *flags |= HIST_FIELD_FL_EXECNAME; else if (strcmp(modifier, "syscall") == 0) *flags |= HIST_FIELD_FL_SYSCALL; - else if (strcmp(modifier, "stacktrace") == 0) + else if (strcmp(modifier, "stacktrace") == 0) { *flags |= HIST_FIELD_FL_STACKTRACE; - else if (strcmp(modifier, "log2") == 0) + stack_modifier = true; + } else if (strcmp(modifier, "log2") == 0) *flags |= HIST_FIELD_FL_LOG2; else if (strcmp(modifier, "usecs") == 0) *flags |= HIST_FIELD_FL_TIMESTAMP_USECS; @@ -2412,6 +2414,12 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file, } } } + + if (stack_modifier && + (!field || field->filter_type != FILTER_STACKTRACE)) { + hist_err(tr, HIST_ERR_BAD_FIELD_MODIFIER, errpos(field_str)); + field = ERR_PTR(-EINVAL); + } out: kfree(str); From 7f711e62355bb3123a2ca2f97a2facbfebc678c6 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Tue, 8 Sep 2026 00:50:45 +0900 Subject: [PATCH 1522/2366] tracing: Fix memory corruption from a "STACKTRACE" histogram key "cpu", "CPU", "stacktrace" and "STACKTRACE" are generic fields, defined with an offset and a size of zero so that the filter code can match them by name. parse_field() maps them onto their common_* equivalents for backward compatibility, but unlike the common_* names it hands the placeholder back to the caller instead of NULL. create_hist_field() takes a non-NULL field as a promise that the record carries a stacktrace and picks HIST_FIELD_FN_STACK, so the __data_loc word is read from offset 0, that is from common_type, and its low 16 bits are followed as an offset into the record. What is found there becomes the length of an unbounded memcpy. Pick an event whose id is small enough that the offset stays inside its own record and the length is a kernel text address: # cd /sys/kernel/tracing # echo 'hist:keys=STACKTRACE' > events/ftrace/print/trigger # echo hello > trace_marker Oops: general protection fault, probably for non-canonical address RIP: 0010:rb_next+0x23/0x60 RIP: 0010:memcpy+0xc/0x30 event_hist_trigger+0x2e7/0x12c0 Kernel panic - not syncing: Fatal exception in interrupt Leave the field NULL, which is what the comment above the branch says the code does and what common_stacktrace already does. FILTER_CPU and FILTER_COMM are left alone, their create_hist_field() branches never look at the field. Cc: stable@vger.kernel.org Fixes: 4b512860bdbd ("tracing: Rename stacktrace field to common_stacktrace") Link: https://patch.msgid.link/20260907155045.692664-3-donggeunyoo.kernel@gmail.com Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 9bc829c1e876..8af97fd4ee2d 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -2404,6 +2404,7 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file, *flags |= HIST_FIELD_FL_CPU; } else if (field && field->filter_type == FILTER_STACKTRACE) { *flags |= HIST_FIELD_FL_STACKTRACE; + field = NULL; } else if (field && field->filter_type == FILTER_COMM) { *flags |= HIST_FIELD_FL_COMM | HIST_FIELD_FL_STRING; } else { From 911002e99e15f640f1fdc6d276206beaef59e790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 8 Sep 2026 08:22:15 +0200 Subject: [PATCH 1523/2366] tracing: Restore :mod: trailer after parsing in ftrace_set_clr_event() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While ftrace_set_clr_event() modifies its input buffer during parsing, before returning to the caller the buffer is supposed to be restored to its original state. This works correctly for the colon between the subsystem and event but not the colon at the beginning of :mod:. Restore the colon, so the :mod: trailer is not stripped after ftrace_set_clr_event(). Cc: stable@vger.kernel.org Fixes: 4c86bc531e60 ("tracing: Add :mod: command to enabled module events") Link: https://patch.msgid.link/20260908-tracing-cli-event-filter-v2-1-05396a3fb663@linutronix.de Signed-off-by: Thomas Weißschuh Signed-off-by: Steven Rostedt --- kernel/trace/trace_events.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 9dbc2441763b..30c0ddf90887 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -1489,6 +1489,8 @@ int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set) /* Put back the colon to allow this to be called again */ if (buf) *(buf - 1) = ':'; + if (mod) + *(mod - 5) = ':'; return ret; } From 7e645147dfba67edb3ed3090a1ed1d89df77fc27 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 9 Sep 2026 08:29:17 +0200 Subject: [PATCH 1524/2366] tracing: Fix ring_buffer_read_page_size() kernel-doc ring_buffer_read_page_size() takes a parameter named rpage, but its kernel-doc describes page. As a result, kernel-doc reports rpage as undescribed and page as an excess parameter description. Rename the documentation entry to match the function. Link: https://patch.msgid.link/20260909062917.89482-1-kmehltretter@gmail.com Fixes: dae8dda341d2 ("tracing: Fix subbuf resize races with trace_pipe_raw readers") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 9c03a555a6ba..0e30c7bd6045 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -7384,7 +7384,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_read_page_data); /** * ring_buffer_read_page_size - get size of the read page. - * @page: the page to get the size from + * @rpage: the page to get the size from * * Returns size of the page in bytes. */ From ed0aff60f83a9bdc2f6556376ac79c96b3ce7e80 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Thu, 10 Sep 2026 22:12:09 -0400 Subject: [PATCH 1525/2366] tracing: Take trace_array reference when opening a tracer options file When a tracer option file is opened, it is passed a descriptor that points to an element on the trace_array's topts array. This element has information to find the trace array and other information. It uses this element to take a reference of the trace_array so that the trace_array does not get removed while this file is opened. Unfortunately, there's a race condition where the element itself could be freed by the removal of the instance the trace_array represents causing a use-after-free as this element that is used to find the trace_array to increment its reference counter is also freed when the instance is removed. To solve this, add a trace_array_tracer_options_get() helper function that will take the address of the element that is passed to the open function by the inode->i_private pointer and search all the trace_arrays under a lock to find the one that the element's address is in the range of the trace_arrays topts array elements. When a match happens, that trace_array's reference would be increased. Note, there's a race where if an admin was deleting and creating trace instances at the same time and the memory of the old trace_array's array matched the memory of the new trace_array that it could in theory open the option from the wrong trace array. But we do not care because it would be stupid to perform that kind of action. As long as the only thing that can happen is that the option from the wrong trace array is used and doesn't crash the kernel it will only make the user confused. But if they are doing something stupid like this, they are already confused, so no harm done. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260910221209.62dad8d3@robin Fixes: 7e2cfbd2d3c86 ("tracing: Have option files inc the trace array ref count") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-trace-kernel/20260902121918.5a9e9d1b@gandalf.local.home/ Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 46 +++++++++++++++++++++++++++++++++++++++++++- kernel/trace/trace.h | 1 + 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 8658cad53cb5..e4a490d3d08c 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -7717,12 +7717,55 @@ trace_options_write(struct file *filp, const char __user *ubuf, size_t cnt, return cnt; } +static bool tr_option_match(struct trace_array *tr, void *topt) +{ + for (int i = 0; i < tr->nr_topts; i++) { + struct trace_options *tr_topts = &tr->topts[i]; + + if (topt >= (void *)&tr_topts->topts[0] && + topt < (void *)&tr_topts->topts[tr_topts->nr_topts]) + return true; + } + return false; +} + +/* + * The topt is the address of a trace_array->topts[] element that holds the + * the tracer options descriptor. But since the trace_array reference has not + * been taken yet, it cannot be dereferenced as it could have been freed by + * a rmdir of the instance the trace_array represents. + * + * Search the list of trace_arrays and compare the topt to the address of + * the entire trace_array topts array for each trace_array in the list. + * If one is matched, then take the reference and return it. If not, the + * trace_array no longer exits. + */ +static int trace_array_tracer_options_get(void *topt) +{ + struct trace_array *tr; + int ret; + + ret = security_locked_down(LOCKDOWN_TRACEFS); + if (ret) + return ret; + + if (tracing_disabled) + return -ENODEV; + + guard(mutex)(&trace_types_lock); + list_for_each_entry(tr, &ftrace_trace_arrays, list) { + if (tr_option_match(tr, topt)) + return __trace_array_get(tr); + } + return -ENODEV; +} + static int tracing_open_options(struct inode *inode, struct file *filp) { struct trace_option_dentry *topt = inode->i_private; int ret; - ret = tracing_check_open_get_tr(topt->tr); + ret = trace_array_tracer_options_get(topt); if (ret) return ret; @@ -7984,6 +8027,7 @@ create_trace_option_files(struct trace_array *tr, struct tracer *tracer, tr->topts = tr_topts; tr->topts[tr->nr_topts].tracer = tracer; tr->topts[tr->nr_topts].topts = topts; + tr->topts[tr->nr_topts].nr_topts = cnt; tr->nr_topts++; for (cnt = 0; opts[cnt].name; cnt++) { diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 5e76f94e7a80..bd3c8f80300f 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -227,6 +227,7 @@ struct array_buffer { struct trace_options { struct tracer *tracer; struct trace_option_dentry *topts; + int nr_topts; }; struct trace_pid_list *trace_pid_list_alloc(void); From 23c240d9509e15f72e4112fc95f0160ab32ec430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Thu, 10 Sep 2026 18:37:43 +0000 Subject: [PATCH 1526/2366] smb: client: validate absolute native symlink targets before NT fixups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With symlinkroot unset, an absolute target is copied without conversion to an NT drive path. Later code still assumes an NT prefix is present when modifying the target and calculating the print name length. For "/ab", this causes two failures: sym[5] and path[5] are written past their allocations, and plen -= 2 * poff subtracts an assumed 8-byte prefix from a 6-byte UTF-16 target, wrapping u16 plen to 65534. That underflow causes another overflow: memcpy() copies 65534 bytes into a 24-byte buffer. A user with write access to a mounted share can trigger these bugs with default settings. Validate the NT drive prefix, including an ASCII drive letter, before accessing fixed offsets or subtracting the prefix length. Fixes: 3363da82e02f ("smb: client: fix native SMB symlink traversal") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/reparse.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 8a1b9e8be5ba..9e31fce7e0a5 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -3,6 +3,7 @@ * Copyright (c) 2024 Paulo Alcantara */ +#include #include #include #include @@ -159,15 +160,24 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode, convert_delimiter(sym, sep); /* - * For absolute NT symlinks it is required to pass also leading - * backslash and to not mangle NT object prefix "\\??\\" and not to - * mangle colon in drive letter. But cifs_convert_path_to_utf16() - * removes leading backslash and replaces '?' and ':'. So temporary - * mask these characters in NT object prefix by '_' and then change - * them back. + * Absolute NT symlinks must retain the leading backslash, "\\??\\" + * prefix and drive-letter colon. cifs_convert_path_to_utf16() strips + * the leading backslash and maps '?' and ':', so temporarily mask + * these characters with '_' and restore them after conversion. + * + * When symlinkroot is unset, sym comes directly from the caller. + * Validate the complete "\\??\\X:" prefix before using fixed offsets + * or subtracting the NT prefix length below. Require an ASCII drive + * letter so the prefix occupies six characters in UTF-16 too. */ - if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') + if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') { + if (!strstarts(sym, "\\??\\") || !isascii(sym[4]) || + !isalpha(sym[4]) || sym[5] != ':') { + rc = -EINVAL; + goto out; + } sym[0] = sym[1] = sym[2] = sym[5] = '_'; + } /* * On a POSIX paths mount the symlink target is stored verbatim, so From 815e07c8fe885a87751c2496a30ae0dcd4118210 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 11 Sep 2026 12:21:52 +0200 Subject: [PATCH 1527/2366] ring-buffer: Acquire the lock with irqsave in rb_wake_up_waiters() rb_wake_up_waiters() is a irq_work callback which is initialized with init_irq_work(). As such it will be invoked in thread context on PREEMPT_RT. Invoking the callback in IRQ context on PREEMPT_RT is not an option due its usage of wake_up_all(). Since this callback may run in thread context, it needs to acquire ring_buffer_per_cpu::reader_lock with disabling interrupts and may not assume that they are disabled. Use raw_spinlock_irqsave() to acquire ring_buffer_per_cpu::reader_lock. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260911102152.YEtwkBj9@linutronix.de Fixes: 68282dd930ea3 ("ring-buffer: Fix resetting of shortest_full") Reviewed-by: Vincent Donnefort Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 0e30c7bd6045..9bc8ce8c5676 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -904,14 +904,13 @@ static void rb_wake_up_waiters(struct irq_work *work) struct ring_buffer_per_cpu *cpu_buffer = container_of(rbwork, struct ring_buffer_per_cpu, irq_work); - /* Called from interrupt context */ - raw_spin_lock(&cpu_buffer->reader_lock); - rbwork->wakeup_full = false; - rbwork->full_waiters_pending = false; + scoped_guard(raw_spinlock_irqsave, &cpu_buffer->reader_lock) { + rbwork->wakeup_full = false; + rbwork->full_waiters_pending = false; - /* Waking up all waiters, they will reset the shortest full */ - cpu_buffer->shortest_full = 0; - raw_spin_unlock(&cpu_buffer->reader_lock); + /* Waking up all waiters, they will reset the shortest full */ + cpu_buffer->shortest_full = 0; + } wake_up_all(&rbwork->full_waiters); } From 3163cd7253432f262c2fa22edb51474d7afc76a0 Mon Sep 17 00:00:00 2001 From: George Emmanuel Thomas Date: Sat, 15 Aug 2026 22:13:35 +0530 Subject: [PATCH 1528/2366] drm/msm: remove stale perf counter XML TODO The adreno_perfcntrs macro already takes the XML file stem as its second argument, allowing each perf counter JSON file to select the appropriate register XML. The a2xx and a5xx entries already pass their respective XML file stems. Remove the stale TODO comment. Signed-off-by: George Emmanuel Thomas Patchwork: https://patchwork.freedesktop.org/patch/746745/ Message-ID: <20260815164335.158958-1-georgeemmanuelthomas@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index d0c3a4c6703b..0b8f2cafed19 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -177,7 +177,6 @@ quiet_cmd_headergen = GENHDR $@ cmd_headergen = mkdir -p $(obj)/generated && $(PYTHON3) $(src)/registers/gen_header.py \ $(headergen-opts) --rnn $(src)/registers --xml $< c-defines > $@ -# TODO how to do this for a2xx/a5xx which have different .xml arg? quiet_cmd_headergen_json = GENHDRJSN $@ cmd_headergen_json = mkdir -p $(obj)/generated && $(PYTHON3) $(src)/registers/gen_header.py \ $(headergen-opts) --rnn $(src)/registers --xml $(filter %.xml,$^) perfcntrs --json $< > $@ From ed9ad3d4183053d4a8c43960a3d23ea8db71c30d Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Wed, 9 Sep 2026 15:07:37 +0200 Subject: [PATCH 1529/2366] drm/msm/a6xx: Add CX AO Counter registers used for a750 GPUs The a750 uses the CX AO Counters instead of the GMU_ALWAYS_ON_COUNTER register on A6xx and other A7xx GPUs. Signed-off-by: Neil Armstrong Patchwork: https://patchwork.freedesktop.org/patch/752234/ Message-ID: <20260909-topic-sm8650-gmu-a750-timestamp-reg-v2-1-091d74958951@linaro.org> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml b/drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml index 33404eb18fd0..b3082738c99d 100644 --- a/drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml +++ b/drivers/gpu/drm/msm/registers/adreno/a6xx_gmu.xml @@ -141,6 +141,8 @@ xsi:schemaLocation="https://gitlab.freedesktop.org/freedreno/ rules-fd.xsd"> + + From a2b65837980a513cf24825bc4a50b0430e3391e7 Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Wed, 9 Sep 2026 15:07:38 +0200 Subject: [PATCH 1530/2366] drm/msm/a6xx: Use CX AO Counter register for timestamp on a750 GPUs The a750 uses the GMU CX AO Counters instead of the GMU_ALWAYS_ON_COUNTER register on A6xx and other A7xx GPUs, use it when running a A750 GPU. The GMU_ALWAYS_ON_COUNTER at offset 0x1f888 doesn't seem to exist on the SM8650 A750 GMU and returns 0, but the CX AO counter at offset 0x1f880 returns some proper timestamp data. Signed-off-by: Neil Armstrong Patchwork: https://patchwork.freedesktop.org/patch/752235/ Message-ID: <20260909-topic-sm8650-gmu-a750-timestamp-reg-v2-2-091d74958951@linaro.org> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index f9de9329dee3..081e79ea4652 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -23,9 +23,15 @@ static u64 a6xx_gmu_get_timestamp(struct msm_gpu *gpu) u64 count_hi, count_lo, temp; do { - count_hi = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_H); - count_lo = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_L); - temp = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_H); + if (adreno_is_a750_family(adreno_gpu)) { + count_hi = gmu_read(&a6xx_gpu->gmu, REG_A7XX_GMU_CX_AO_COUNTER_H); + count_lo = gmu_read(&a6xx_gpu->gmu, REG_A7XX_GMU_CX_AO_COUNTER_L); + temp = gmu_read(&a6xx_gpu->gmu, REG_A7XX_GMU_CX_AO_COUNTER_H); + } else { + count_hi = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_H); + count_lo = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_L); + temp = gmu_read(&a6xx_gpu->gmu, REG_A6XX_GMU_ALWAYS_ON_COUNTER_H); + } } while (unlikely(count_hi != temp)); return (count_hi << 32) | count_lo; From 79a71cc2568f4b5d42284da2aa26f3b4f47ce01b Mon Sep 17 00:00:00 2001 From: Jim Mattson Date: Wed, 2 Sep 2026 11:47:11 -0700 Subject: [PATCH 1531/2366] KVM: x86/pmu: Move Intel PMU global MSRs to intel_is_valid_msr() Commit c85cdc1cc1ea ("KVM: x86/pmu: Move handling PERF_GLOBAL_CTRL and friends to common x86") moved the existence check for the following Intel PMU MSRs to kvm_pmu_is_valid_msr(): - MSR_CORE_PERF_GLOBAL_STATUS - MSR_CORE_PERF_GLOBAL_CTRL - MSR_CORE_PERF_GLOBAL_OVF_CTRL That commit deemed these MSRs valid whenever pmu->version > 1. It intended to share the check with AMD PerfMonV2 because both vendor implementations require version 2 or greater for global PMU controls. However, as noted in the commit message, AMD uses different MSR indices for its global PMU registers. Commit 4a2771895ca6 ("KVM: x86/svm/pmu: Add AMD PerfMonV2 support") subsequently added AMD PerfMonV2 support and set pmu->version = 2. Because kvm_pmu_is_valid_msr() validated the Intel MSRs whenever pmu->version > 1, KVM incorrectly permitted AMD guests with PerfMonV2 to access these Intel MSRs without a #GP. Move the validation of these Intel MSRs to intel_is_valid_msr() and remove the common switch statement from kvm_pmu_is_valid_msr(). AMD already validates its own global PMU MSRs in amd_is_valid_msr(). Fixes: 4a2771895ca6 ("KVM: x86/svm/pmu: Add AMD PerfMonV2 support") Signed-off-by: Jim Mattson Reviewed-by: Like Xu Reviewed-by: Sandipan Das Link: https://patch.msgid.link/20260902184711.138538-1-jmattson@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/pmu.c | 8 -------- arch/x86/kvm/vmx/pmu_intel.c | 3 +++ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/arch/x86/kvm/pmu.c b/arch/x86/kvm/pmu.c index a7d60c8785cd..d2fd47ee5ec8 100644 --- a/arch/x86/kvm/pmu.c +++ b/arch/x86/kvm/pmu.c @@ -823,14 +823,6 @@ void kvm_pmu_deliver_pmi(struct kvm_vcpu *vcpu) bool kvm_pmu_is_valid_msr(struct kvm_vcpu *vcpu, u32 msr) { - switch (msr) { - case MSR_CORE_PERF_GLOBAL_STATUS: - case MSR_CORE_PERF_GLOBAL_CTRL: - case MSR_CORE_PERF_GLOBAL_OVF_CTRL: - return kvm_pmu_has_perf_global_ctrl(vcpu_to_pmu(vcpu)); - default: - break; - } return kvm_pmu_call(msr_idx_to_pmc)(vcpu, msr) || kvm_pmu_call(is_valid_msr)(vcpu, msr); } diff --git a/arch/x86/kvm/vmx/pmu_intel.c b/arch/x86/kvm/vmx/pmu_intel.c index bfa8612fb450..70a8c4816135 100644 --- a/arch/x86/kvm/vmx/pmu_intel.c +++ b/arch/x86/kvm/vmx/pmu_intel.c @@ -187,6 +187,9 @@ static bool intel_is_valid_msr(struct kvm_vcpu *vcpu, u32 msr) int ret; switch (msr) { + case MSR_CORE_PERF_GLOBAL_STATUS: + case MSR_CORE_PERF_GLOBAL_CTRL: + case MSR_CORE_PERF_GLOBAL_OVF_CTRL: case MSR_CORE_PERF_FIXED_CTR_CTRL: return kvm_pmu_has_perf_global_ctrl(pmu); case MSR_IA32_PEBS_ENABLE: From f13368e0acffd6d6289629705cb821d921104725 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Wed, 26 Aug 2026 09:14:30 -0700 Subject: [PATCH 1532/2366] KVM: selftests: Use __GLIBC__, not _GNU_SOURCE, to detect actual glibc Use __GLIBC__ in the hardware disable test to detect when selftests are being built/linked against glibc and thus pthread_attr_setaffinity_np() is (hopefully) available. As pointed out by Sashiko and Hisam, _GNU_SOURCE is effectively a "request" macro to enable functionality, whereas __GLIBC__ is an announcement of support and selftests' idiomatic way of guarding code that's specific to glibc. Fixes: 496779b54943 ("KVM: selftests: Pre-set threads affinity in hardware disable test when possible") Reported-by: Sashiko Bot Closes: https://lore.kernel.org/all/20260731201140.5AF0C1F00AC4@smtp.kernel.org Suggested-by: Hisam Mehboob Link: https://patch.msgid.link/20260826161430.714316-1-seanjc@google.com Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/hardware_disable_test.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/kvm/hardware_disable_test.c b/tools/testing/selftests/kvm/hardware_disable_test.c index 43a36ef3ead8..1c20892d6782 100644 --- a/tools/testing/selftests/kvm/hardware_disable_test.c +++ b/tools/testing/selftests/kvm/hardware_disable_test.c @@ -37,7 +37,7 @@ static void *run_vcpu(void *arg) struct kvm_vcpu *vcpu = arg; struct kvm_run *run = vcpu->run; -#ifndef _GNU_SOURCE +#ifndef __GLIBC__ kvm_sched_setaffinity(0, sizeof(cpu_set_t), &threads_cpu_set); #endif @@ -51,7 +51,7 @@ static void *sleeping_thread(void *arg) { int fd; -#ifndef _GNU_SOURCE +#ifndef __GLIBC__ kvm_sched_setaffinity(0, sizeof(cpu_set_t), &threads_cpu_set); #endif @@ -71,7 +71,7 @@ static void run_test(u32 run) u32 i, j; TEST_ASSERT_EQ(pthread_attr_init(&attr), 0); -#ifdef _GNU_SOURCE +#ifdef __GLIBC__ TEST_ASSERT_EQ(pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &threads_cpu_set), 0); #endif From 8cd280282d1239bca68f8c3632ef1ac8556a396b Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 6 Aug 2026 14:46:18 -0700 Subject: [PATCH 1533/2366] KVM: Never clear KVM_REQ_VM_DEAD from a vCPU's requests Use kvm_test_request() instead of kvm_check_request() when querying KVM_REQ_VM_DEAD, i.e. don't clear KVM_REQ_VM_DEAD, as the entire purpose of KVM_REQ_VM_DEAD is to prevent the vCPU from enterring the guest ever again, even if userspace insists on redoing KVM_RUN. Ensuring KVM_REQ_VM_DEAD is never cleared will allow relaxing KVM's rule that ioctls can't be invoked on dead VMs, to only disallow ioctls if the VM is bugged, i.e. if KVM hit a KVM_BUG_ON(). Opportunistically add compile-time assertions to guard against clearing KVM_REQ_VM_DEAD through the standard APIs. Reviewed-by: Kai Huang Acked-by: Marc Zyngier Link: https://patch.msgid.link/20260806214618.82180-1-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/arm64/kvm/arm.c | 2 +- arch/x86/kvm/mmu/mmu.c | 2 +- arch/x86/kvm/vmx/tdx.c | 2 +- arch/x86/kvm/x86.c | 2 +- include/linux/kvm_host.h | 9 +++++++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 8b080804bc90..62c81d6ae8ee 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -1129,7 +1129,7 @@ static int kvm_vcpu_suspend(struct kvm_vcpu *vcpu) static int check_vcpu_requests(struct kvm_vcpu *vcpu) { if (kvm_request_pending(vcpu)) { - if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) return -EIO; if (kvm_check_request(KVM_REQ_SLEEP, vcpu)) diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index 064ecc33b926..8e62476e477b 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -5058,7 +5058,7 @@ static int kvm_tdp_page_prefault(struct kvm_vcpu *vcpu, gpa_t gpa, if (signal_pending(current)) return -EINTR; - if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) return -EIO; cond_resched(); diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index b272c20586a7..6c842e9191a5 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -1995,7 +1995,7 @@ static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu) if (kvm_vcpu_has_events(vcpu) || signal_pending(current)) break; - if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) { + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) { ret = -EIO; break; } diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 79468ddfe473..a137dc6dd8c6 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -8060,7 +8060,7 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) bool req_immediate_exit = false; if (kvm_request_pending(vcpu)) { - if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) { + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) { r = -EIO; goto out; } diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 03bfc92864b6..cf7fe835c4ad 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -2324,13 +2324,18 @@ static inline bool kvm_test_request(int req, struct kvm_vcpu *vcpu) return test_bit(req & KVM_REQUEST_MASK, (void *)&vcpu->requests); } -static inline void kvm_clear_request(int req, struct kvm_vcpu *vcpu) +static __always_inline void kvm_clear_request(int req, struct kvm_vcpu *vcpu) { + BUILD_BUG_ON(req == KVM_REQ_VM_DEAD); + clear_bit(req & KVM_REQUEST_MASK, (void *)&vcpu->requests); } -static inline bool kvm_check_request(int req, struct kvm_vcpu *vcpu) +static __always_inline bool kvm_check_request(int req, struct kvm_vcpu *vcpu) { + /* Once a VM is dead, it needs to stay dead. */ + BUILD_BUG_ON(req == KVM_REQ_VM_DEAD); + if (kvm_test_request(req, vcpu)) { kvm_clear_request(req, vcpu); From ebc5660132ddd244b57f03ed324922013a3d7363 Mon Sep 17 00:00:00 2001 From: April Cardenas Date: Thu, 10 Sep 2026 00:16:49 -0500 Subject: [PATCH 1534/2366] smb/client: send lease break ACKs thru correct session for multiuser mounts Currently, when cifs_oplock_break handles a break request from the server it searches for the appropriate tlink to handle the request but incorrectly uses the current fsuid as the search key, eventually causing read errors for users with multiuser mounts on NetApp. Fix this by using the tlink from the cfile struct instead to respond through the correct session. As breaks are handled in a worker thread, the current fsuid isn't guaranteed to match the session that the break is intended for. This means that cifs_sb_tlink may search the rbtree using the wrong fsuid, and return a tlink with an incorrect session than the lease break was intended for. As a result, the breaks may be ACKed through an incorrect session. While it seems that Samba/Windows Servers 2016-2025 ignore this as long as the lease key is correct, we ran into a case where if you're using NetApp ONTAP or Azure NetApp Files they will reject the ACK and return `STATUS_LOCK_NOT_GRANTED` errors on any future read requests a user may initiate through their still held open file handle, and the server will eventually close the file. In the dmesg logs, the user may see errors like these: CIFS: Status code returned 0xc0000128 STATUS_FILE_CLOSED CIFS: VFS: Send error in read = -9 With a multiuser mount using NetApp, this issue is really easy for users to hit on a wide variety of kernel versions by attempting to copy a file from the share to the local machine through GNOME Files/Nautilus. This copy will always result in Nautilus throwing a `Bad File Descriptor` error to the user and fail. With this fix, you can copy files through Nautilus without issue. >From looking at the traces, it seems that glib will open the file first, and call listxattr before actually attempting to copy the file data. The listxattr call always triggers a break, causing the copy to fail. The proposed fix returns to the way the client grabbed the tlink before commit e8f5f849ffce2 ("cifs: fix potential oops in cifs_oplock_break"). The bulk of that commit (checking for list empty) remains untouched, and I think the change to using cifs_sb_tlink was intended to avoid a NULL/ERR deference on the tlink as well as update the reference count. I believe this fix should preserve those safety properties, but of course I'd appreciate any corrections here. Fixes: e8f5f849ffce2 ("cifs: fix potential oops in cifs_oplock_break") Cc: stable@vger.kernel.org Signed-off-by: April Cardenas Reviewed-by: Namjae Jeon Reviewed-by: Bharath S M Signed-off-by: Paulo Alcantara --- fs/smb/client/file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 1aa4844f8b8a..0d428517f454 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -3354,8 +3354,8 @@ void cifs_oplock_break(struct work_struct *work) wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS, TASK_UNINTERRUPTIBLE); - tlink = cifs_sb_tlink(cifs_sb); - if (IS_ERR(tlink)) { + tlink = cifs_get_tlink(cfile->tlink); + if (IS_ERR_OR_NULL(tlink)) { /* drop the reference taken when the break was queued */ _cifsFileInfo_put(cfile, false /* do not wait for ourself */, false); goto out; From 77be3641f3e3a56e42a5ed889372ef395a933f3c Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Thu, 3 Sep 2026 16:51:35 +0500 Subject: [PATCH 1535/2366] debugfs: don't warn about uninitialized debugfs for an error parent Since commit c3a280ff728a ("debugfs: warn if file creation failed due to uninitialized debugfs") every boot with CONFIG_REF_TRACKER=y and CONFIG_DEBUG_FS=y prints two errors before the root filesystem is mounted: debugfs: Unable to create file 'net_refcnt@(____ptrval____)', debugfs is not initialized yet debugfs: Unable to create file 'net_notrefcnt@(____ptrval____)', debugfs is not initialized yet Nothing is actually wrong. Both files show up under /sys/kernel/debug/ref_tracker/ once the system is up. The kernel is reporting an error for a condition the caller has already accounted for. net_ns_init() runs directly from start_kernel(), before any initcall, and calls ref_tracker_dir_init() for init_net's two trackers. debugfs_init() is a core_initcall, so debugfs cannot possibly be up at that point. That is by design: ref_tracker_dir_debugfs() is documented as safe to call again later, and net/core/net_namespace.c has a late_initcall() that re-registers both directories once debugfs exists. ref_tracker also states that intent to debugfs. ref_tracker_debug_dir is initialised to ERR_PTR(-ENOENT) and only gets a real dentry in a late_initcall, so the early call hands debugfs_create_file() a parent that is already an error. debugfs_start_creating() honours that and returns the parent error, but only after the new pr_err() has fired. Move the IS_ERR(parent) check above the debugfs_initialized() test. A caller passing an error parent is propagating an earlier failure, which is the pattern debugfs documents and which the warning is not aimed at. A caller passing a valid or NULL parent too early - the case the warning was added for - still gets it. One behaviour change: an early caller with an error parent now gets PTR_ERR(parent) back instead of -ENOENT. All callers of these interfaces are documented to ignore the return value. Fixes: c3a280ff728a ("debugfs: warn if file creation failed due to uninitialized debugfs") Link: https://lore.kernel.org/all/6d1dc775f7d5e754d734907514534054f682bac5.1781171918.git.yk@y-koj.net/ Signed-off-by: Mikhail Gavrilov Tested-by: Yohei Kojima Link: https://patch.msgid.link/20260903115135.63210-1-mikhail.v.gavrilov@gmail.com Signed-off-by: Danilo Krummrich --- fs/debugfs/inode.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index e054e62919ec..a4d08bd3743b 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -368,6 +368,9 @@ static struct dentry *debugfs_start_creating(const char *name, if (!debugfs_enabled) return ERR_PTR(-EPERM); + if (IS_ERR(parent)) + return parent; + if (!debugfs_initialized()) { pr_err("Unable to create file '%s', debugfs is not initialized yet\n", name); @@ -376,9 +379,6 @@ static struct dentry *debugfs_start_creating(const char *name, pr_debug("creating file '%s'\n", name); - if (IS_ERR(parent)) - return parent; - error = simple_pin_fs(&debug_fs_type, &debugfs_mount, &debugfs_mount_count); if (error) { From 2725ab3f5ad1c5f375c7c9fee4af02a9b138f701 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 21 Aug 2026 04:53:27 +0200 Subject: [PATCH 1536/2366] keys: fix lost wakeup when reaping a dead key type clear_bit() is atomic with respect to the word it modifies, but it is an unordered operation: it implies no memory barrier on either side (Documentation/atomic_bitops.txt). key_garbage_collector() clears KEY_GC_REAPING_KEYTYPE with clear_bit() and calls wake_up_bit() after reaping a dead key type. wake_up_bit() uses a lockless waitqueue check and requires a full barrier after the clear. The existing smp_mb() is before clear_bit(), so nothing orders the clear against that check. The GC can see an empty waitqueue while unregister_key_type() still sees the bit set. The final wakeup is then lost, leaving module unload stuck in wait_on_bit(). Use clear_and_wake_up_bit(). Its clear_bit_unlock() has RELEASE semantics, so the completed GC work stays ordered before the clear, and its smp_mb__after_atomic() orders the clear before the waitqueue check. Fixes: 0c061b5707ab ("KEYS: Correctly destroy key payloads when their keytype is removed") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Link: https://lore.kernel.org/r/20260821025327.61488-1-kmehltretter@gmail.com Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- security/keys/gc.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/security/keys/gc.c b/security/keys/gc.c index 748e83818a76..eda445f815d4 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -318,9 +318,7 @@ static void key_garbage_collector(struct work_struct *work) if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) { kdebug("dead wake"); - smp_mb(); - clear_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); - wake_up_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE); + clear_and_wake_up_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); } if (gc_state & KEY_GC_REAP_AGAIN) From 0d6a4268b06084baafd8ee5d66955c7e1c2e053b Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Fri, 21 Aug 2026 17:59:35 +0800 Subject: [PATCH 1537/2366] keys: translate request_key_auth pid for the reading procfs instance request_key_auth_describe() prints rka->pid into /proc/keys as a raw pid_t in the initial pid namespace. A reader can open /proc/keys through a mount in another pid namespace. That reader sees a number with no meaning there. The number can even name an unrelated task. The line needs VIEW on the key. So the reader either shares the key owner's uid or possesses the key. The fix keeps a struct pid. Commit 4f82f45730c6 ("net ip6 flowlabel: Make owner a union of struct pid * and kuid_t") gave /proc/net/ip6_flowlabel the same storage. The print goes through pid_nr_ns(). It renders against the pid namespace of the procfs instance the line is read through. Commit ad08978ab41c ("ipv6/flowlabel: simplify pid namespace lookup") moved that print to the same anchor. Output through an initial namespace /proc does not change. The line shows 0 for a requestor with no number in that namespace. Translating at read time was the alternative. find_pid_ns() can resolve a recycled number. The line would then name a live task with no connection to the key. A stored struct pid gives 0 instead when the requestor has no number there. Link: https://lore.kernel.org/keyrings/20260809110202.2180410-1-maoyixie.tju@gmail.com/ Fixes: 78b7280cce23 ("KEYS: Improve /proc/keys") Cc: stable@vger.kernel.org # v5.10+ Assisted-by: Claude:claude-opus-5 codeql Signed-off-by: Maoyi Xie Link: https://lore.kernel.org/r/20260821095935.1864998-1-maoyixie.tju@gmail.com Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- include/keys/request_key_auth-type.h | 2 +- security/keys/request_key_auth.c | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/include/keys/request_key_auth-type.h b/include/keys/request_key_auth-type.h index 01e42ee5f409..464636278c4f 100644 --- a/include/keys/request_key_auth-type.h +++ b/include/keys/request_key_auth-type.h @@ -22,7 +22,7 @@ struct request_key_auth { const struct cred *cred; void *callout_info; size_t callout_len; - pid_t pid; + struct pid *pid; char op[8]; } __randomize_layout; diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c index 282e09d8fa46..ed6f55b9cdd9 100644 --- a/security/keys/request_key_auth.c +++ b/security/keys/request_key_auth.c @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -73,7 +75,10 @@ static void request_key_auth_describe(const struct key *key, seq_puts(m, "key:"); seq_puts(m, key->description); if (key_is_positive(key)) - seq_printf(m, " pid:%d ci:%zu", rka->pid, rka->callout_len); + seq_printf(m, " pid:%d ci:%zu", + pid_nr_ns(rka->pid, + proc_pid_ns(file_inode(m->file)->i_sb)), + rka->callout_len); } /* @@ -113,6 +118,7 @@ static void free_request_key_auth(struct request_key_auth *rka) if (rka->cred) put_cred(rka->cred); kfree(rka->callout_info); + put_pid(rka->pid); kfree(rka); } @@ -226,14 +232,14 @@ struct key *request_key_auth_new(struct key *target, const char *op, irka = cred->request_key_auth->payload.data[0]; rka->cred = get_cred(irka->cred); - rka->pid = irka->pid; + rka->pid = get_pid(irka->pid); up_read(&cred->request_key_auth->sem); } else { /* it isn't - use this process as the context */ rka->cred = get_cred(cred); - rka->pid = current->pid; + rka->pid = get_pid(task_pid(current)); } rka->target_key = key_get(target); From 114f00d738f15dd8c7318369edcdc53dd6d08763 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Tue, 1 Sep 2026 23:58:06 +0300 Subject: [PATCH 1538/2366] KEYS: trusted: Fix tpm2_load_cmd() boundary check tpm2_load_cmd() does boundary checks against the ASN.1 size i.e., payload->blob_len. Address this by passing the decoded blob size to tpm2_load_cmd(), and use it for the boundary checks. Cc: stable@vger.kernel.org # v5.13+ Fixes: f2219745250f ("security: keys: trusted: use ASN.1 TPM2 key format for the blobs") Reported-by: co+6a581c4284f721d4@bugs.sh Closes: https://bugs.sh/b/6a581c4284f721d4/ Reviewed-by: Stefano Garzarella Tested-by: Srish Srinivasan Link: https://lore.kernel.org/r/20260901205809.2028454-1-jarkko@kernel.org Signed-off-by: Jarkko Sakkinen --- security/keys/trusted-keys/trusted_tpm2.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 67225dd562a9..01f18bb37047 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -99,7 +99,7 @@ struct tpm2_key_context { static int tpm2_key_decode(struct trusted_key_payload *payload, struct trusted_key_options *options, - u8 **buf) + u8 **buf, unsigned int *blob_len) { int ret; struct tpm2_key_context ctx; @@ -120,6 +120,7 @@ static int tpm2_key_decode(struct trusted_key_payload *payload, return -ENOMEM; *buf = blob; + *blob_len = ctx.priv_len + ctx.pub_len; options->keyhandle = ctx.parent; memcpy(blob, ctx.priv, ctx.priv_len); @@ -384,10 +385,11 @@ static int tpm2_load_cmd(struct tpm_chip *chip, int rc; u32 attrs; - rc = tpm2_key_decode(payload, options, &blob); + rc = tpm2_key_decode(payload, options, &blob, &blob_len); if (rc) { /* old form */ blob = payload->blob; + blob_len = payload->blob_len; payload->old_format = 1; } else { /* Bind for cleanup: */ @@ -399,17 +401,17 @@ static int tpm2_load_cmd(struct tpm_chip *chip, return -EINVAL; /* must be big enough for at least the two be16 size counts */ - if (payload->blob_len < 4) + if (blob_len < 4) return -EINVAL; private_len = get_unaligned_be16(blob); /* must be big enough for following public_len */ - if (private_len + 2 + 2 > (payload->blob_len)) + if (private_len + 2 + 2 > blob_len) return -E2BIG; public_len = get_unaligned_be16(blob + 2 + private_len); - if (private_len + 2 + public_len + 2 > payload->blob_len) + if (private_len + 2 + public_len + 2 > blob_len) return -E2BIG; pub = blob + 2 + private_len + 2; From 8697c431e297eb0d0ab13dda6bc172b48a34f05c Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 12 Sep 2026 00:31:16 +0300 Subject: [PATCH 1539/2366] KEYS: encrypted: fix integer overflow of datablob_len encrypted_key_alloc() stores datablob_len in a u16. It is computed from multiple string and payload lengths. If the result exceeds U16_MAX, the assignment truncates the allocation size. KASAN reports a 32760-byte slab-out-of-bounds write when __ekey_init() copies the master key description into the undersized buffer. The total payload length stored in key->datalen is also a u16. Use check_add_overflow() to reject values that do not fit either destination, and use kzalloc_flex() for the flexible-array allocation. Fixes: 7e70cb497850 ("keys: add new key-type encrypted") Cc: stable@vger.kernel.org Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Cen Zhang Signed-off-by: Francis Perron Reviewed-by: Jarkko Sakkinen Tested-by: R Nageswara Sastry Link: https://lore.kernel.org/r/20260909153433.83117-1-cenzhang@linux.microsoft.com Signed-off-by: Jarkko Sakkinen --- security/keys/encrypted-keys/encrypted.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index 59cb77b237b3..e07092ea301a 100644 --- a/security/keys/encrypted-keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -579,6 +580,7 @@ static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, { struct encrypted_key_payload *epayload = NULL; unsigned short datablob_len; + unsigned short payload_totallen; unsigned short decrypted_datalen; unsigned short payload_datalen; unsigned int encrypted_datalen; @@ -632,16 +634,22 @@ static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, encrypted_datalen = roundup(decrypted_datalen, blksize); - datablob_len = format_len + 1 + strlen(master_desc) + 1 - + strlen(datalen) + 1 + ivsize + 1 + encrypted_datalen; + if (check_add_overflow(format_len + 1 + strlen(master_desc) + 1 + + strlen(datalen) + 1 + ivsize + 1, + encrypted_datalen, &datablob_len)) + return ERR_PTR(-EINVAL); - ret = key_payload_reserve(key, payload_datalen + datablob_len - + HASH_SIZE + 1); + if (check_add_overflow(datablob_len, + payload_datalen + HASH_SIZE + 1, + &payload_totallen)) + return ERR_PTR(-EINVAL); + + ret = key_payload_reserve(key, payload_totallen); if (ret < 0) return ERR_PTR(ret); - epayload = kzalloc(sizeof(*epayload) + payload_datalen + - datablob_len + HASH_SIZE + 1, GFP_KERNEL); + epayload = kzalloc_flex(*epayload, payload_data, payload_totallen, + GFP_KERNEL); if (!epayload) return ERR_PTR(-ENOMEM); From 5225b8eec4c9bb21aecff6295fab6346a3c3738e Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 11 Sep 2026 15:45:37 -0600 Subject: [PATCH 1540/2366] mailmap: update entry for Jens Axboe I recently changed jobs, let's update the .mailmap entry so that patches are attributed to the right (current) company. Signed-off-by: Jens Axboe Signed-off-by: Linus Torvalds --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 7c02242d4ed7..29c556cf597b 100644 --- a/.mailmap +++ b/.mailmap @@ -421,6 +421,7 @@ Jens Axboe Jens Axboe Jens Axboe Jens Axboe +Jens Axboe Jens Osterkamp Jens Wiklander Jernej Skrabec From 764dcebb033764633700a036c7351a7c6350eec6 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 9 Sep 2026 23:31:23 +0000 Subject: [PATCH 1541/2366] neighbour: Add missing RCU annotation for neightbl_dump_info(). neightbl_dump_info() fetches the first non-default neigh_parms with list_next_entry(&tbl->parms, ...) and iterates through the list with list_for_each_entry_from_rcu(). However, list_next_entry() does not use RCU helper. Let's use list_for_each_entry_rcu() and skip the default parms. Fixes: 4ae34be50064 ("neighbour: Convert RTM_GETNEIGHTBL to RCU.") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260909233143.2401847-2-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/neighbour.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 1349c0eedb64..49dd7df149ef 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -2611,11 +2611,14 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) break; nidx = 0; - p = list_next_entry(&tbl->parms, list); - list_for_each_entry_from_rcu(p, &tbl->parms_list, list) { + + list_for_each_entry_rcu(p, &tbl->parms_list, list) { if (!net_eq(neigh_parms_net(p), net)) continue; + if (!p->dev) + continue; + if (nidx < neigh_skip) goto next; From 6d79b223ec44ada58ad37db42f539b60985a7722 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 9 Sep 2026 23:31:24 +0000 Subject: [PATCH 1542/2366] neighbour: Enforce min/max to NDTPA_INTERVAL_PROBE_TIME_MS. NDTPA_INTERVAL_PROBE_TIME_MS sets .type and .min but misses .validation_type, so no validation is applied: # ynl --family rt-neigh --do setneightbl \ --json '{"name": "arp_cache", "parms": {"interval-probe-time-ms": 0}}' # ynl --family rt-neigh --dump getneightbl --output-json | \ jq '.[] | select(.name == "arp_cache" and has("config")) | .parms["interval-probe-time-ms"]' 0 Moreover, nla_get_msecs() uses msecs_to_jiffies(), and u64 is silently cast to u32, so a larger value can bypass the min check: e.g. 4294967296 == 0x100000000 # ynl --family rt-neigh --do setneightbl \ --json '{"name": "arp_cache", "parms": {"interval-probe-time-ms": 4294967296}}' # ynl --family rt-neigh --dump getneightbl --output-json | \ jq '.[] | select(.name == "arp_cache" and has("config")) | .parms["interval-probe-time-ms"]' 0 msecs_to_jiffies() returns MAX_JIFFY_OFFSET if the value is larger than INT_MAX. Also, INT_MAX ms overflows int NEIGH_VAR() when HZ > 1000 (Alpha, MIPS), and passing a negative integer to queue_delayed_work(unsigned long delay) causes sign extension, which wraps around the expiry time to the past, resulting in it being handled as 0 delay in the timer wheel. Let's use NLA_POLICY_FULL_RANGE() and limit the max to 1 day. The same max check is applied to sysctl as well. Note that this controls the probe interval for NTF_MANAGED entries, so the max of 1 day is unlikely to break any deployments. Fixes: 211da42eaa45 ("net, neigh: introduce interval_probe_time_ms for periodic probe") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260909233143.2401847-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/rt-neigh.yaml | 3 +++ Documentation/networking/ip-sysctl.rst | 2 +- net/core/neighbour.c | 17 +++++++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Documentation/netlink/specs/rt-neigh.yaml b/Documentation/netlink/specs/rt-neigh.yaml index 0f46ef313590..c8e55c98d564 100644 --- a/Documentation/netlink/specs/rt-neigh.yaml +++ b/Documentation/netlink/specs/rt-neigh.yaml @@ -341,6 +341,9 @@ attribute-sets: - name: interval-probe-time-ms type: u64 + checks: + min: 1 + max: 86400000 operations: enum-model: directional diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index 208f46967ee5..b05829e44d8f 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -248,7 +248,7 @@ neigh/default/unres_qlen - INTEGER neigh/default/interval_probe_time_ms - INTEGER The probe interval for neighbor entries with NTF_MANAGED flag, - the min value is 1. + the min value is 1, and the max value is 86400000 (1 day). Default: 5000 diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 49dd7df149ef..0db78a0dfb51 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -2359,6 +2359,13 @@ static const struct nla_policy nl_neightbl_policy[NDTA_MAX+1] = { [NDTA_PARMS] = { .type = NLA_NESTED }, }; +#define NTBL_PARM_MS_MAX (24 * 60 * 60 * MSEC_PER_SEC) + +static const struct netlink_range_validation nl_ntbl_parm_ms_range = { + .min = 1, + .max = NTBL_PARM_MS_MAX, +}; + static const struct nla_policy nl_ntbl_parm_policy[NDTPA_MAX+1] = { [NDTPA_IFINDEX] = { .type = NLA_U32 }, [NDTPA_QUEUE_LEN] = { .type = NLA_U32 }, @@ -2375,7 +2382,8 @@ static const struct nla_policy nl_ntbl_parm_policy[NDTPA_MAX+1] = { [NDTPA_ANYCAST_DELAY] = { .type = NLA_U64 }, [NDTPA_PROXY_DELAY] = { .type = NLA_U64 }, [NDTPA_LOCKTIME] = { .type = NLA_U64 }, - [NDTPA_INTERVAL_PROBE_TIME_MS] = { .type = NLA_U64, .min = 1 }, + [NDTPA_INTERVAL_PROBE_TIME_MS] = NLA_POLICY_FULL_RANGE(NLA_U64, + &nl_ntbl_parm_ms_range), }; static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh, @@ -3672,12 +3680,13 @@ static int neigh_proc_dointvec_ms_jiffies_positive(const struct ctl_table *ctl, void *buffer, size_t *lenp, loff_t *ppos) { struct ctl_table tmp = *ctl; - int ret; + int ret, min, max; - int min = msecs_to_jiffies(1); + min = msecs_to_jiffies(1); + max = msecs_to_jiffies(NTBL_PARM_MS_MAX); tmp.extra1 = &min; - tmp.extra2 = NULL; + tmp.extra2 = &max; ret = proc_dointvec_ms_jiffies_minmax(&tmp, write, buffer, lenp, ppos); neigh_proc_update(ctl, write); From 7b430fcfc972f61b09cc19ca95997586af4a147d Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 9 Sep 2026 23:31:25 +0000 Subject: [PATCH 1543/2366] neighbour: Don't render blackhole_netdev via RTM_GETNEIGHTBL. The cited commits started to initialise blackhole_netdev with neigh_parms_alloc(). This is visible in init_net as the ifindex==0 entries via RTM_GETNEIGHTBL: # ynl --family rt-neigh --dump getneightbl --output-json \ | jq '.[] | select(.parms.ifindex == 0) | {name: .name, ifindex: .parms.ifindex}' { "name": "arp_cache", "ifindex": 0 } { "name": "ndisc_cache", "ifindex": 0 } For RTM_SETNEIGHTBL, ifindex being 0 means wildcard. Let's skip blackhole_netdev's parms in neightbl_dump_info(). Note that lookup_neigh_parms() does not need the same change because the default parms is always the first entry and matches with ifindex == 0. Fixes: e5f80fcf869a ("ipv6: give an IPv6 dev to blackhole_netdev") Fixes: 22600596b675 ("ipv4: give an IPv4 dev to blackhole_netdev") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260909233143.2401847-4-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/neighbour.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 0db78a0dfb51..02bf940ce00a 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -2624,7 +2624,7 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) if (!net_eq(neigh_parms_net(p), net)) continue; - if (!p->dev) + if (!p->dev || p->dev == blackhole_netdev) continue; if (nidx < neigh_skip) From 979aabdad8dd03394467ee484a1a70f3d40b19ba Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 9 Sep 2026 23:31:26 +0000 Subject: [PATCH 1544/2366] neighbour: Skip default parms when resumed in neightbl_dump_info(). neightbl_dump_info() calls neightbl_fill_info() in each loop to render the default parms. If there are many devices and neightbl_fill_param_info() failed, neightbl_fill_info() is called again when the dump resumes: # ynl --family rt-neigh --dump getneightbl --output-json | jq '.[] | {name: .name, ifindex: .parms.ifindex}' ... { "name": "ndisc_cache", "ifindex": null } ... { "name": "ndisc_cache", "ifindex": 6 } { "name": "ndisc_cache", "ifindex": null } { "name": "ndisc_cache", "ifindex": 5 } Let's skip neightbl_fill_info() if it is already called in neightbl_dump_info(). Note that we cannot use !neigh_skip instead of !default_skip because default_skip == 1 && neigh_skip == 0 could be true if the first neightbl_fill_param_info() fails. Also, nidx must be cleared at the end of each table loop; otherwise, if neightbl_fill_info() for a subsequent table fails, the leftover nidx from the previous table would be saved in cb->args[1], resulting in erroneously skipping parms of the subsequent table in the next dump. Fixes: c7fb64db001f ("[NETLINK]: Neighbour table configuration and statistics via rtnetlink") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260909233143.2401847-5-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/core/neighbour.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 02bf940ce00a..7448320f7ad5 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -2587,9 +2587,10 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) { const struct nlmsghdr *nlh = cb->nlh; struct net *net = sock_net(skb->sk); + int default_skip = cb->args[2]; + int neigh_skip = cb->args[1]; int family, tidx, nidx = 0; int tbl_skip = cb->args[0]; - int neigh_skip = cb->args[1]; struct neigh_table *tbl; if (cb->strict_check) { @@ -2613,12 +2614,13 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) if (tidx < tbl_skip || (family && tbl->family != family)) continue; - if (neightbl_fill_info(skb, tbl, NETLINK_CB(cb->skb).portid, + if (!default_skip && + neightbl_fill_info(skb, tbl, NETLINK_CB(cb->skb).portid, nlh->nlmsg_seq, RTM_NEWNEIGHTBL, NLM_F_MULTI) < 0) break; - nidx = 0; + default_skip = 1; list_for_each_entry_rcu(p, &tbl->parms_list, list) { if (!net_eq(neigh_parms_net(p), net)) @@ -2641,12 +2643,15 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb) } neigh_skip = 0; + nidx = 0; + default_skip = 0; } out: rcu_read_unlock(); cb->args[0] = tidx; cb->args[1] = nidx; + cb->args[2] = default_skip; return skb->len; } From c60ae98c5aa64021751b38ab1313b19d620bf640 Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 11 Sep 2026 10:25:04 +0100 Subject: [PATCH 1545/2366] 9p: Fix v9fs_issue_write() to update i_size and remote_i_size Fix v9fs_issue_write() to update i_size and remote_i_size to the new size of the server file if we made it larger, using the start fpos and the count returned by p9_client_write() to calculate the new minimum file size. This assumes that if the 9P server makes a short write (say it hits ENOSPC), a reduced count is returned. Fixes: 5fb70e7275a6 ("netfs, 9p: Implement helpers for new write code") Reported-by: Michael Mulqueen Closes: https://lore.kernel.org/r/fbb9e395-1e07-4212-8f70-23f3cd498074@method-b.uk/ Cc: stable@vger.kernel.org Signed-off-by: David Howells Message-ID: <2226525.1789118704@warthog.procyon.org.uk> Signed-off-by: Dominique Martinet --- fs/9p/vfs_addr.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c index 1ac0b3dcc077..13cf87a5f90c 100644 --- a/fs/9p/vfs_addr.c +++ b/fs/9p/vfs_addr.c @@ -54,11 +54,37 @@ static void v9fs_begin_writeback(struct netfs_io_request *wreq) static void v9fs_issue_write(struct netfs_io_subrequest *subreq) { struct p9_fid *fid = subreq->rreq->netfs_priv; + struct inode *inode = subreq->rreq->inode; + struct netfs_inode *ictx = netfs_inode(inode); int err, len; len = p9_client_write(fid, subreq->start, &subreq->io_iter, &err); - if (len > 0) + if (len > 0) { + uoff_t end = subreq->start + len, i_size, remote, zp; + bool set = false; + + spin_lock(&inode->i_lock); + + /* We can read the sizes directly as we hold i_lock. */ + i_size = inode->i_size; + remote = ictx->_remote_i_size; + zp = ictx->_zero_point; + + if (end > i_size) { + i_size = end; + set = true; + } + if (end > remote) { + remote = end; + set = true; + } + + if (set) + netfs_write_sizes(inode, i_size, remote, zp); + spin_unlock(&inode->i_lock); + __set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); + } netfs_write_subrequest_terminated(subreq, len ?: err); } From 5cddf63367ef894ba0026dfa2d524346f7048de9 Mon Sep 17 00:00:00 2001 From: Krish Gulati Date: Sat, 12 Sep 2026 12:36:13 +0530 Subject: [PATCH 1546/2366] ALSA: hda/realtek: Add quirk for HP Victus 15-fa1xxx (MB 8BB1) mute LED The mute LED on this board does not respond to mute state changes because no fixup is matched for SSID 103c:8bb1. The reporter verified the LED can be toggled manually via COEF index 0x0B. Reported-by: Mazen Ahmed Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221982 Tested-by: Mazen Ahmed Signed-off-by: Krish Gulati Link: https://patch.msgid.link/20260912070618.21272-1-krishgulati7@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 3abee617e86e..dc73fa95b00a 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7568,6 +7568,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bb1, "HP Victus 15-fa1xxx (MB 8BB1)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), From 221253723dc58bb901c3f27a7659823e63fc598c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 10 Sep 2026 17:52:23 +0200 Subject: [PATCH 1547/2366] ALSA: bcd2000: Fix race between rawmidi and disconnect Although we tried to fix the potential UAF issues at USB disconnect on bcd2000 driver, there is still an overlooked case -- namely, when a rawmidi trigger callback has been already running at USB disconnect handling, the in-flight function (e.g. bcd2000_midi_send()) could still access the URB, because the previous URB NULL-check & clearance was considered only for the URB complete callbacks, but not about the parallel rawmidi operations. For addressing the race, this patch introduced a new spinlock that covers each rawmidi operation as well as the rawmidi handling in the complete callback. The URB is cleared with the lock, so it guarantees that the pending rawmidi task already finished or a NULL check is effective. Fixes: 459d3a64766f ("ALSA: bcd2000: clear the URB pointers on disconnect") Link: https://patch.msgid.link/20260910155227.996210-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/bcd2000/bcd2000.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c index c5c542d17ccc..2bd49bf82748 100644 --- a/sound/usb/bcd2000/bcd2000.c +++ b/sound/usb/bcd2000/bcd2000.c @@ -43,6 +43,7 @@ struct bcd2000 { struct usb_interface *intf; int card_index; + spinlock_t midi_lock; int midi_out_active; struct snd_rawmidi *rmidi; struct snd_rawmidi_substream *midi_receive_substream; @@ -90,6 +91,8 @@ static void bcd2000_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) { struct bcd2000 *bcd2k = substream->rmidi->private_data; + + guard(spinlock_irqsave)(&bcd2k->midi_lock); bcd2k->midi_receive_substream = up ? substream : NULL; } @@ -195,6 +198,8 @@ static void bcd2000_midi_output_trigger(struct snd_rawmidi_substream *substream, { struct bcd2000 *bcd2k = substream->rmidi->private_data; + guard(spinlock_irqsave)(&bcd2k->midi_lock); + if (up) { bcd2k->midi_out_substream = substream; /* check if there is data userspace wants to send */ @@ -219,6 +224,7 @@ static void bcd2000_output_complete(struct urb *urb) return; /* check if there is more data userspace wants to send */ + guard(spinlock_irqsave)(&bcd2k->midi_lock); bcd2000_midi_send(bcd2k); } @@ -234,6 +240,8 @@ static void bcd2000_input_complete(struct urb *urb) if (!bcd2k || urb->status == -ESHUTDOWN) return; + guard(spinlock_irqsave)(&bcd2k->midi_lock); + if (urb->actual_length > 0) bcd2000_midi_handle_input(bcd2k, urb->transfer_buffer, urb->actual_length); @@ -348,16 +356,26 @@ static int bcd2000_init_midi(struct bcd2000 *bcd2k) return 0; } +static void bcd2000_midi_free(struct bcd2000 *bcd2k, + struct urb **urb_p) +{ + struct urb *urb = *urb_p; + + if (!urb) + return; + + usb_poison_urb(urb); + scoped_guard(spinlock_irq, &bcd2k->midi_lock) + *urb_p = NULL; + + usb_free_urb(urb); +} + static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k, struct usb_interface *interface) { - usb_poison_urb(bcd2k->midi_out_urb); - usb_poison_urb(bcd2k->midi_in_urb); - - usb_free_urb(bcd2k->midi_out_urb); - usb_free_urb(bcd2k->midi_in_urb); - bcd2k->midi_out_urb = NULL; - bcd2k->midi_in_urb = NULL; + bcd2000_midi_free(bcd2k, &bcd2k->midi_out_urb); + bcd2000_midi_free(bcd2k, &bcd2k->midi_in_urb); if (bcd2k->intf) { usb_set_intfdata(bcd2k->intf, NULL); @@ -393,6 +411,7 @@ static int bcd2000_probe(struct usb_interface *interface, bcd2k->card = card; bcd2k->card_index = card_index; bcd2k->intf = interface; + spin_lock_init(&bcd2k->midi_lock); snd_card_set_dev(card, &interface->dev); From effce1cb87ee0d8b3a8cbe7722968f4ea7efd360 Mon Sep 17 00:00:00 2001 From: Sophie D Date: Wed, 9 Sep 2026 21:49:10 -0400 Subject: [PATCH 1548/2366] drm/gud: Ignore damage clips in full update mode When running in full update mode, previously small updates (such as moving the mouse across the screen) would cause many full frames to be generated. This would bog down the bus and lower the effective framerate significantly - I was seeing a drop from 60 FPS to 2 FPS. Set ignore_damage_clips in full update mode so the damage iterator yields a single full-plane rectangle instead of one per clip. Fixes: 73cfd166e045 ("drm/gud: Replace simple display pipe with DRM atomic helpers") Cc: # 6.18.x Signed-off-by: Sophie D Reviewed-by: Thomas Zimmermann Acked-by: Ruben Wauters Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260910014910.8564-1-patches@scd31.com --- drivers/gpu/drm/gud/gud_pipe.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c index 5ef887d8485a..3388fdc8ea7b 100644 --- a/drivers/gpu/drm/gud/gud_pipe.c +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -482,6 +482,9 @@ int gud_plane_atomic_check(struct drm_plane *plane, if (!new_plane_state->visible) return 0; + if (gdrm->flags & GUD_DISPLAY_FLAG_FULL_UPDATE) + new_plane_state->ignore_damage_clips = true; + if (old_plane_state->rotation != new_plane_state->rotation) crtc_state->mode_changed = true; From 59ced288fcba9e91bd38e61a972ad782c4edb7d0 Mon Sep 17 00:00:00 2001 From: Sajal Gupta Date: Wed, 2 Sep 2026 18:00:57 +0530 Subject: [PATCH 1549/2366] drm/gud: fix out-of-bounds write in gud_plane_atomic_check() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plane property loop uses req->properties[num_properties + i] as write index while simultaneously incrementing `num_properties` inside the loop. At iteration i, num_properties has also incremented by i, so the write is done at `initial_num_properties + 2*i`, skipping every other index and advancing by 2 per iteration. With just 2 connector and 32 plane properties the last write happens at index 64, one slot past the end of the 64-slot (indices 0–63) allocation. A USB device can trigger OOB by advertising the maximum number of properties. Fix by dropping the redundant `+ i`; num_properties is already the correct running index, as gud_connector_fill_properties() fills the preceding slots. Fixes: 40e1a70b4aed ("drm: Add GUD USB Display driver") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260821071812.16500-1-sajal2005gupta%40gmail.com?part=1 Signed-off-by: Sajal Gupta Cc: Acked-by: Ruben Wauters Signed-off-by: Ruben Wauters Link: https://patch.msgid.link/20260902123254.36987-1-sajal2005gupta@gmail.com --- drivers/gpu/drm/gud/gud_pipe.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/gud/gud_pipe.c b/drivers/gpu/drm/gud/gud_pipe.c index 3388fdc8ea7b..aa7792966287 100644 --- a/drivers/gpu/drm/gud/gud_pipe.c +++ b/drivers/gpu/drm/gud/gud_pipe.c @@ -565,8 +565,8 @@ int gud_plane_atomic_check(struct drm_plane *plane, goto out; } - req->properties[num_properties + i].prop = cpu_to_le16(prop); - req->properties[num_properties + i].val = cpu_to_le64(val); + req->properties[num_properties].prop = cpu_to_le16(prop); + req->properties[num_properties].val = cpu_to_le64(val); num_properties++; } From ba970587a0e1203b6a0934b5b9174886b4c4d24c Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Sat, 12 Sep 2026 08:09:14 -0700 Subject: [PATCH 1550/2366] drm/msm/a6xx+: Increase GMU FW init timeout We were using 10ms, kgsl uses 100ms. In practice it is usually takes less than 10ms, but very occasionally goes a bit above 10ms, leading to a "GMU firmware inialization timed out", from which point things go south. There are probably some things that could be done to speed up init, like increasing GMU freq. But to be safe, increase the timeout to match kgsl. Signed-off-by: Rob Clark Reviewed-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/753014/ Message-ID: <20260912150915.28700-1-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index 27cac853975f..6d49c51df1a2 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -320,7 +320,7 @@ static int a6xx_gmu_start(struct a6xx_gmu *gmu) gmu_write(gmu, REG_A6XX_GMU_CM3_SYSRESET, 0); ret = gmu_poll_timeout(gmu, REG_A6XX_GMU_CM3_FW_INIT_RESULT, val, - (val & mask) == reset_val, 100, 10000); + (val & mask) == reset_val, 100, 100000); if (ret) DRM_DEV_ERROR(gmu->dev, "GMU firmware initialization timed out\n"); From 0d7823cd4cda35f2060a685fa276ff7709915abc Mon Sep 17 00:00:00 2001 From: Siddharth Chintamaneni Date: Wed, 2 Sep 2026 17:14:13 +0000 Subject: [PATCH 1551/2366] bpf: Allow terminal gotox instructions check_subprogs() treats gotox as a direct jump and validates its reserved zero offset. When gotox is the final instruction, this produces a synthetic successor one instruction past the end of the subprogram and rejects an otherwise valid program. Skip direct-offset validation for gotox and accept it as a non-fallthrough terminal instruction. Its actual targets remain validated from the instruction-array jump table during CFG construction. Fixes: 493d9e0d6083 ("bpf, x86: add support for indirect jumps") Signed-off-by: Siddharth Chintamaneni Reviewed-by: Anton Protopopov Link: https://lore.kernel.org/r/20260902171414.96165-1-sidchintamaneni@gmail.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/verifier.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 1b0b1fb62878..ddba53eaa333 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -3042,6 +3042,8 @@ static int check_subprogs(struct bpf_verifier_env *env) subprog[cur_subprog].exit_idx = i; goto next; } + if (insn_is_gotox(&insn[i])) + goto next; off = i + bpf_jmp_offset(&insn[i]) + 1; if (off < subprog_start || off >= subprog_end) { verbose(env, "jump out of range from insn %d to %d\n", i, off); @@ -3061,7 +3063,8 @@ static int check_subprogs(struct bpf_verifier_env *env) */ if (code != (BPF_JMP | BPF_EXIT) && code != (BPF_JMP32 | BPF_JA) && - code != (BPF_JMP | BPF_JA)) { + code != (BPF_JMP | BPF_JA) && + !insn_is_gotox(&insn[i])) { verbose(env, "last insn is not an exit or jmp\n"); bpf_diag_program_structure( env, i, "subprogram can fall through", From ac781acaef4904b9be64a7b5755c778e5d76ede6 Mon Sep 17 00:00:00 2001 From: Siddharth Chintamaneni Date: Wed, 2 Sep 2026 17:14:14 +0000 Subject: [PATCH 1552/2366] selftests/bpf: Test terminal gotox instructions Add tests that place gotox at the end of the main program and a subprogram, with each jump-table target preceding the gotox instruction. This tests gotox as a valid non-fallthrough terminal instruction. Signed-off-by: Siddharth Chintamaneni Reviewed-by: Anton Protopopov Link: https://lore.kernel.org/r/20260902171414.96165-2-sidchintamaneni@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/progs/verifier_gotox.c | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_gotox.c b/tools/testing/selftests/bpf/progs/verifier_gotox.c index 5b18c9a27717..0e27c2c79c57 100644 --- a/tools/testing/selftests/bpf/progs/verifier_gotox.c +++ b/tools/testing/selftests/bpf/progs/verifier_gotox.c @@ -47,6 +47,54 @@ DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_src_reg, BPF_REG_1, 0, 0, __fa DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_non_zero_off, BPF_REG_0, 1, 0, __failure __msg("BPF_JA|BPF_X uses reserved fields")) DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_non_zero_imm, BPF_REG_0, 0, 1, __failure __msg("BPF_JA|BPF_X uses reserved fields")) +#define DEFINE_TERMINAL_GOTOX_PROG(NAME, BASE) \ + __naked void NAME(void) \ + { \ + asm volatile (" \ + .pushsection .jumptables,\"\",@progbits; \ +jt0_%=: \ + .quad ret0_%= - " BASE "; \ + .size jt0_%=, 8; \ + .global jt0_%=; \ + .popsection; \ + \ + r0 = jt0_%= ll; \ + r0 = *(u64 *)(r0 + 0); \ + goto end_%=; \ +ret0_%=: \ + r0 = 0; \ + exit; \ +end_%=: \ + .8byte %[gotox_r0]; \ +" : \ + : __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, \ + BPF_REG_0, 0, 0, 0)) \ + : __clobber_all); \ + } + +SEC("socket") +__success __retval(0) +DEFINE_TERMINAL_GOTOX_PROG(jump_table_terminal_gotox, "socket") + +static __noinline __used +DEFINE_TERMINAL_GOTOX_PROG(terminal_gotox_subprog1, ".text") + +static __noinline __used int terminal_gotox_subprog2(void) +{ + return 0; +} + +SEC("socket") +__success __retval(0) +__naked void jump_table_terminal_gotox_subprog(void) +{ + asm volatile (" \ + call terminal_gotox_subprog1; \ + call terminal_gotox_subprog2; \ + exit; \ +" ::: __clobber_all); +} + /* * Gotox is forbidden when there is no jump table loaded * which points to the sub-function where the gotox is used From b4dcc18b97913888e8d009624e07c8014ce41b84 Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Fri, 11 Sep 2026 22:25:12 +0800 Subject: [PATCH 1553/2366] ftrace: Use rcu_assign_pointer() for tmp_ops filter hash tmp_ops.func_hash->filter_hash is annotated __rcu, but update_ftrace_direct_mod() assigns hash to it directly. Sparse reports an address-space mismatch. Use rcu_assign_pointer() for the assignment. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260911142512.19344-1-leon.hwang@linux.dev Fixes: 50b35c9e50a8 ("ftrace: Use hash argument for tmp_ops in update_ftrace_direct_mod") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609110704.Q3M5vCDV-lkp@intel.com/ Signed-off-by: Leon Hwang Signed-off-by: Steven Rostedt --- kernel/trace/ftrace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index 53d5db60bfa5..673a54fdf392 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -6675,7 +6675,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b /* Enable the tmp_ops to have the same functions as the hash object. */ ftrace_ops_init(&tmp_ops); - tmp_ops.func_hash->filter_hash = hash; + rcu_assign_pointer(tmp_ops.func_hash->filter_hash, hash); err = register_ftrace_function_nolock(&tmp_ops); if (err) From bcfe2816e6ec46c3f4c58aa4264476665ddb3f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Ahmet=20Memi=C5=9F?= Date: Fri, 11 Sep 2026 18:56:47 +0300 Subject: [PATCH 1554/2366] tracing: Don't dereference trace_event_file in deferred trigger free MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The enable_event trigger defers trace_event_put_ref() to the trigger free kthread, but the trace_event_file can already be freed when the instance is removed. Keep the trace_event_call directly in enable_trigger_data so the deferred free does not access the freed trace_event_file. Cc: stable@vger.kernel.org Fixes: e091351b3881 ("tracing: Delay module ref count for "enable_event" trigger") Reported-by: Alexander Gordeev Closes: https://lore.kernel.org/all/20260828134340.2501683A24-agordeev@linux.ibm.com/ Link: https://patch.msgid.link/20260911155650.354844-1-aliamemis@disroot.org Signed-off-by: Ali Ahmet Memiş Signed-off-by: Steven Rostedt --- kernel/trace/trace.h | 1 + kernel/trace/trace_events_trigger.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index bd3c8f80300f..3749485a7d85 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -1953,6 +1953,7 @@ struct event_trigger_data { struct enable_trigger_data { struct trace_event_file *file; + struct trace_event_call *call; bool enable; bool hist; }; diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c index 149300cc5e8a..4d2fde996c0f 100644 --- a/kernel/trace/trace_events_trigger.c +++ b/kernel/trace/trace_events_trigger.c @@ -1728,7 +1728,8 @@ static void enable_trigger_private_data_free(struct event_trigger_data *data) { struct enable_trigger_data *enable_data = data->private_data; - trace_event_put_ref(enable_data->file->event_call); + /* The file may already be freed here, only the call is kept alive */ + trace_event_put_ref(enable_data->call); kfree(enable_data); } @@ -1801,6 +1802,7 @@ int event_enable_trigger_parse(struct event_command *cmd_ops, enable_data->hist = hist; enable_data->enable = enable; enable_data->file = event_enable_file; + enable_data->call = event_enable_file->event_call; trigger_data = trigger_data_alloc(cmd_ops, cmd, param, enable_data); if (!trigger_data) From 85136bf22404474a815fc0ed26ec0d1cbc1bc3f9 Mon Sep 17 00:00:00 2001 From: "Jose Fernandez (Anthropic)" Date: Wed, 9 Sep 2026 17:51:04 +0000 Subject: [PATCH 1555/2366] bpf: Avoid soft lockup in __htab_map_lookup_and_delete_batch() __htab_map_lookup_and_delete_batch() has no rescheduling point. The batch count bounds how many entries are copied out, not how many buckets are visited, so one BPF_MAP_LOOKUP_BATCH call can walk the map end to end. The empty-bucket fast path is worse: it stays inside a single rcu_read_lock() / bpf_disable_instrumentation() section for any run of consecutive empty buckets. That holds up on small maps, but it falls apart at scale. On a 144-CPU arm64 host running a CONFIG_PREEMPT_NONE kernel, periodic BPF_MAP_LOOKUP_BATCH calls against an LRU hash map with 16,777,216 buckets held a CPU inside the batch op for 77+ seconds and triggered the soft lockup watchdog. Commit 75134f16e7dd ("bpf: Add schedule points in batch ops") fixed this same problem in the generic batch ops, but not in this htab-native path, which every htab-based hash map variant uses for its lookup[_and_delete] batch ops. Complete that fix here. Leave the critical section after 64 consecutive empty buckets, call cond_resched_tasks_rcu_qs(), and resume at the saved bucket cursor. No locks are held at that point, and resuming from the cursor is already the function's behavior for non-empty buckets. Add the same call to the per-bucket loop after copy_to_user(), where every lock has been dropped. cond_resched_rcu() is not enough here: sleeping with bpf_prog_active elevated makes tracing programs on that CPU silently skip their invocations. Plain cond_resched() is not enough either. It is a no-op under PREEMPT and PREEMPT_LAZY, the only models arm64 and x86 have offered since commit 7dadeaa6e851 ("sched: Further restrict the preemption modes"). It is also never a Tasks RCU quiescent state, in any model: the reschedule counts as a preemption. The walking task stays a holdout and stalls every synchronize_rcu_tasks() caller, ftrace and BPF trampoline teardown included, until the syscall returns [1]. cond_resched_tasks_rcu_qs() is the usual tool for that [2]. It reports the quiescent state at each yield and still reschedules as cond_resched() does on PREEMPT_NONE and PREEMPT_VOLUNTARY kernels. Fixes: 057996380a42 ("bpf: Add batch ops to all htab bpf map") Cc: "Paul E. McKenney" Cc: Rik van Riel Link: https://lore.kernel.org/bpf/20260715215314.44423f47@fangorn/ [1] Link: https://lore.kernel.org/bpf/9d444098-7c03-4163-af12-bd0a79a51443@paulmck-laptop/ [2] Assisted-by: LLM Signed-off-by: Jose Fernandez (Anthropic) Signed-off-by: Josef Bacik Reviewed-by: Rik van Riel Link: https://lore.kernel.org/r/20260909-b4-htab-batch-resched-v2-1-0cb529d8f95a@toxicpanda.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/hashtab.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index 6f331c80130d..a72dc5b9f184 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -1772,6 +1772,12 @@ static int htab_lru_percpu_map_lookup_and_delete_elem(struct bpf_map *map, flags); } +/* + * Max consecutive empty buckets to walk in one RCU + + * instrumentation-disabled section before rescheduling. + */ +#define HTAB_BATCH_EMPTY_RESCHED 64 + static int __htab_map_lookup_and_delete_batch(struct bpf_map *map, const union bpf_attr *attr, @@ -1793,6 +1799,7 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, unsigned long flags = 0; bool locked = false; struct htab_elem *l; + u32 empty_cnt = 0; struct bucket *b; int ret = 0; @@ -1971,12 +1978,21 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, } next_batch: - /* If we are not copying data, we can go to next bucket and avoid - * unlocking the rcu. + /* + * If we are not copying data, we can go to next bucket and avoid + * unlocking the rcu. Bound the walk though: after + * HTAB_BATCH_EMPTY_RESCHED consecutive empty buckets, fully exit + * the critical section (no locks are held here) and reschedule. */ if (!bucket_cnt && (batch + 1 < htab->n_buckets)) { batch++; - goto again_nocopy; + if (++empty_cnt < HTAB_BATCH_EMPTY_RESCHED) + goto again_nocopy; + empty_cnt = 0; + rcu_read_unlock(); + bpf_enable_instrumentation(); + cond_resched_tasks_rcu_qs(); + goto again; } rcu_read_unlock(); @@ -1990,11 +2006,13 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, } total += bucket_cnt; + empty_cnt = 0; batch++; if (batch >= htab->n_buckets) { ret = -ENOENT; goto after_loop; } + cond_resched_tasks_rcu_qs(); goto again; after_loop: From 06bb43d8c79762fa3452f292cd080e54bef5d431 Mon Sep 17 00:00:00 2001 From: Vlad Poenaru Date: Wed, 2 Sep 2026 09:13:47 -0700 Subject: [PATCH 1556/2366] kbuild: don't delete in-flight filechk temporaries in asm-headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 2d69b891e646 ("kbuild: Support generated asm-headers in subdirectories") switched the stale-wrapper sweep in scripts/Makefile.asm-headers from $(wildcard $(obj)/*.h) to a find(1) invocation, so that generated headers in subdirectories are considered. The two do not match the same set of files. Make's $(wildcard) uses glob semantics, where a leading '.' has to be matched explicitly, whereas find's -name uses fnmatch() without FNM_PERIOD, so '*.h' matches dotfiles as well. filechk writes its output to $(dir $@).tmp_$(notdir $@) before renaming it into place, so such a scratch file, if it happens to exist in $(obj) when the sub-make is parsed, is now picked up in old-headers. It appears in neither generic-y, generated-y nor syscall-y, is therefore classified as unwanted, and cmd_remove deletes it. On x86 this races with archprepare, which lists both asm-generic and arch/x86/include/generated/asm/cpufeaturemasks.h as prerequisites. Under -j they run concurrently against the same directory, and the build fails intermittently: mv: cannot stat 'arch/x86/include/generated/asm/.tmp_cpufeaturemasks.h': No such file or directory make[1]: *** [arch/x86/Makefile:269: arch/x86/include/generated/asm/cpufeaturemasks.h] Error 1 The same commit also converted the generic wrapper rule to filechk, so those wrappers now create .tmp_*.h in $(obj) too and can race among themselves. Restore the previous behaviour by excluding dotfiles from the sweep. Subdirectories, which is what the find(1) conversion was for, keep being descended into. While at it, quote the -name argument: it is currently expanded by the shell against the build directory before find sees it. Fixes: 2d69b891e646 ("kbuild: Support generated asm-headers in subdirectories") Signed-off-by: Vlad Poenaru Reviewed-by: Nathan Chancellor Reviewed-by: Thomas Weißschuh Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260902161347.4163577-1-vlad.wing@gmail.com Signed-off-by: Nicolas Schier --- scripts/Makefile.asm-headers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Makefile.asm-headers b/scripts/Makefile.asm-headers index b38931314ad7..f1c3d287c14b 100644 --- a/scripts/Makefile.asm-headers +++ b/scripts/Makefile.asm-headers @@ -48,7 +48,7 @@ syscall-y := $(addprefix $(obj)/, $(syscall-y)) generated-y := $(addprefix $(obj)/, $(generated-y)) # Remove stale wrappers when the corresponding files are removed from generic-y -old-headers := $(shell test -d $(obj) && find $(obj) -name *.h) +old-headers := $(shell test -d $(obj) && find $(obj) -name '*.h' ! -name '.*') unwanted := $(filter-out $(generic-y) $(generated-y) $(syscall-y),$(old-headers)) filechk_wrap = echo "\#include " From 4f73462856576797b8f3c55564a9be99f76dc67b Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Mon, 31 Aug 2026 18:46:31 -0700 Subject: [PATCH 1557/2366] scripts/sorttable: Mark long_size as __maybe_unused When building in a kernel tree prior to commit b055f4c431e3 ("sorttable: Move ELF parsing into scripts/elf-parse.[ch]") with clang-23 or newer, which implements a new warning under -Wunused-but-set-variable for static global variable, there is a warning from sorttable because long_size is unused when MCOUNT_SORT_ENABLED is not set: scripts/sorttable.c:452:12: error: variable 'long_size' set but not used [-Werror,-Wunused-but-set-global] 452 | static int long_size; | ^ Mark long_size as __maybe_unused to avoid inserting more ugly #ifdef directives while insuring the warning does not reappear, as the aforementioned change does not alter the uses of long_size, so it appears to be coincidence that the warning disappears after this refactoring. Cc: stable@vger.kernel.org Signed-off-by: Nathan Chancellor Tested-by: Nicolas Schier Link: https://patch.msgid.link/20260831-sorttable-long_size-unused-but-set-global-v1-1-8a96b88697e5@kernel.org Signed-off-by: Nicolas Schier --- scripts/sorttable.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/sorttable.c b/scripts/sorttable.c index d8dc2a1b7c31..d7b50581c732 100644 --- a/scripts/sorttable.c +++ b/scripts/sorttable.c @@ -116,7 +116,7 @@ static inline void *get_index(void *start, int entsize, int index) } static int extable_ent_size; -static int long_size; +static int long_size __maybe_unused; #define ERRSTR_MAXSZ 256 From 281b61d408d4c39544583e393c6707af0ef5ee50 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Tue, 8 Sep 2026 21:55:01 +0100 Subject: [PATCH 1558/2366] scripts/mksysmap: drop the MODULE_INFO() symbols from kallsyms Commit 3e86e4d74c04 ("kbuild: keep .modinfo section in vmlinux.unstripped") keeps .modinfo symbols out of System.map and kallsyms, which assumes unique IDs have a format like '__UNIQUE_ID_modinfo123'. However, commit afb026b6d35c ("compiler: Tweak __UNIQUE_ID() naming"), sent in the same cycle, changes this to '__UNIQUE_ID_modinfo_123'. As a result this regexp has never matched and every kernel since v6.18 has carried one kallsyms entries for every MODULE_INFO() declaration in the kernel whether the modules are compiled or not. That's 5,810 entries for an x86 defconfig build and 15,200 for arm64. On x86 defconfig that is 113 KiB of kallsyms tables and 32 KiB of bzImage, and every lookup walks past them. Fix the pattern. Fixes: 3e86e4d74c04 ("kbuild: keep .modinfo section in vmlinux.unstripped") Assisted-by: LLM Signed-off-by: Lorenzo Stoakes (ARM) Reviewed-by: Nicolas Schier Reviewed-by: Nathan Chancellor Link: https://patch.msgid.link/20260908-build-speedup-v1-1-5dc1ac01672d@kernel.org Signed-off-by: Nicolas Schier --- scripts/mksysmap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mksysmap b/scripts/mksysmap index c4531eacde20..56a8b8bbdb37 100755 --- a/scripts/mksysmap +++ b/scripts/mksysmap @@ -83,7 +83,7 @@ / _SDA2_BASE_$/d # MODULE_INFO() -/ __UNIQUE_ID_modinfo[0-9]*$/d +/ __UNIQUE_ID_modinfo_[0-9]*$/d # --------------------------------------------------------------------------- # Ignored patterns From 59351365ac271b5e0eb180f211c531476a36221f Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Tue, 8 Sep 2026 21:55:02 +0100 Subject: [PATCH 1559/2366] scripts/mksysmap: fix escape of '$' in the __pi_ pattern Commit b18b047002b7 ("kbuild: change scripts/mksysmap into sed script") converted scripts/mksysmap from a shell script to a sed script. However an error was made - escaping of '$' required \\ escaping in shell but only \ in a sed script. This was mostly corrected in commit 7a6c355b55c0 ("scripts/mksysmap: Fix escape chars '$'"), but this fix missed arm64 PIE namespace local symbols like __pi_$x and __pi_$d which appear in System.map and /proc/kallsyms: $ grep __pi_\\$ /proc/kallsyms | sort -u 0000000000000000 d __pi_$d 0000000000000000 t __pi_$x Fix the escaping properly. Fixes: b18b047002b7 ("kbuild: change scripts/mksysmap into sed script") Assisted-by: LLM Signed-off-by: Lorenzo Stoakes (ARM) Reviewed-by: Nathan Chancellor Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260908-build-speedup-v1-2-5dc1ac01672d@kernel.org Signed-off-by: Nicolas Schier --- scripts/mksysmap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mksysmap b/scripts/mksysmap index 56a8b8bbdb37..856b26ba2ac0 100755 --- a/scripts/mksysmap +++ b/scripts/mksysmap @@ -35,7 +35,7 @@ / __efistub_/d # arm64 local symbols in PIE namespace -/ __pi_\\$/d +/ __pi_\$/d / __pi_\.L/d # arm64 local symbols in non-VHE KVM namespace From 75f8cf22463d82bb1fb0239a3d485fc8f4c8ef03 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Thu, 3 Sep 2026 18:09:20 +0800 Subject: [PATCH 1560/2366] bpf: Fix out-of-bounds read of rtt_min in sock_ops A sockops prog reading skops->rtt_min never checks the sk type: on the tcp_conn_request() path sock_ops->sk is a request_sock (non-full), and the ctx rewrite casts it to a tcp_sock (full) and reads rtt_min past the end of the request_sock, returning dirty adjacent memory. SEC("sockops") int prog(struct bpf_sock_ops *skops) { switch (skops->op) { case BPF_SOCK_OPS_RWND_INIT: leak = skops->rtt_min; /* reads the request_sock OOB */ ... } } For instance one such read returned rtt_min=0xffff8881, the high half of a leaked kernel pointer. Guarding that cast is exactly what SOCK_OPS_GET_FIELD() does -- it checks is_locked_tcp_sock and returns 0 when sock_ops->sk is not a locked full socket. Every other tcp_sock field in sock_ops goes through it; rtt_min is the only one open-coded, so it skips the check. Read rtt_min through SOCK_OPS_GET_FIELD() too. rtt_min is a bit special: it is a struct minmax and we only want the current min, so pass rtt_min.s[0].v. That is equivalent to the old hand-computed offset offsetof(struct tcp_sock, rtt_min) + sizeof_field(struct minmax_sample, t) (s[0] sits at rtt_min + 0 and .v at + sizeof(.t), i.e. what minmax_get() returns), so the loaded field is unchanged and only the full-sock guard is added. The two BUILD_BUG_ON()s that protected the hand-computed offset are no longer needed. Before patch: 0: r1 = *(u64 *)(r1 +0) ; r1 = skops->sk 1: r1 = *(u32 *)(r1 +2324) ; ((tcp_sock *)sk)->rtt_min.s[0].v After patch: 0: *(u64 *)(r1 +56) = r9 1: r9 = *(u8 *)(r1 +50) ; is_locked_tcp_sock 2: if r9 == 0 goto pc+4 ; not a locked full sock -> 0 3: r9 = *(u64 *)(r1 +56) 4: r1 = *(u64 *)(r1 +0) ; r1 = skops->sk 5: r1 = *(u32 *)(r1 +2324) ; rtt_min.s[0].v 6: goto pc+2 7: r9 = *(u64 *)(r1 +56) 8: r1 = 0 Fixes: 44f0e43037d3 ("bpf: Add support for reading sk_state and more") Reported-by: VEGA Signed-off-by: Jiayuan Chen Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/r/20260903100921.113374-1-jiayuan.chen@linux.dev Signed-off-by: Alexei Starovoitov --- net/core/filter.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index cae43b999162..532405988fd9 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -11106,18 +11106,7 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type, break; case offsetof(struct bpf_sock_ops, rtt_min): - BUILD_BUG_ON(sizeof_field(struct tcp_sock, rtt_min) != - sizeof(struct minmax)); - BUILD_BUG_ON(sizeof(struct minmax) < - sizeof(struct minmax_sample)); - - *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF( - struct bpf_sock_ops_kern, sk), - si->dst_reg, si->src_reg, - offsetof(struct bpf_sock_ops_kern, sk)); - *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg, - offsetof(struct tcp_sock, rtt_min) + - sizeof_field(struct minmax_sample, t)); + SOCK_OPS_GET_FIELD(rtt_min, rtt_min.s[0].v, struct tcp_sock); break; case offsetof(struct bpf_sock_ops, bpf_sock_ops_cb_flags): From 7d70a0b02d262971201fdd1e221586bdd95c2910 Mon Sep 17 00:00:00 2001 From: Zhixing Chen Date: Thu, 3 Sep 2026 18:43:58 +0800 Subject: [PATCH 1561/2366] bpf: Use kvfree() in xdp_test_run_teardown() xdp_test_run_setup() allocates xdp->frames and xdp->skbs with kvmalloc_array(). The setup error path already releases both arrays with kvfree(), while the normal teardown path still uses kfree(). Use kvfree() in xdp_test_run_teardown() as well, so the release helper matches the allocator on both paths. Signed-off-by: Zhixing Chen Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/r/20260903104358.29228-1-running910@gmail.com Signed-off-by: Alexei Starovoitov --- net/bpf/test_run.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c index 5d51f6cb7d15..513354e928cb 100644 --- a/net/bpf/test_run.c +++ b/net/bpf/test_run.c @@ -205,8 +205,8 @@ static void xdp_test_run_teardown(struct xdp_test_data *xdp) { xdp_unreg_mem_model(&xdp->mem); page_pool_destroy(xdp->pp); - kfree(xdp->frames); - kfree(xdp->skbs); + kvfree(xdp->frames); + kvfree(xdp->skbs); } static bool frame_was_changed(const struct xdp_page_head *head) From 6d8c197c9992659a65525a07de4368c8401fdda7 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 8 Sep 2026 15:52:46 +0900 Subject: [PATCH 1562/2366] ntfs: use dynamic MFT tail reservation The ntfs MFT allocator historically treated records below 64 as a permanent extension area and stopped searching for $MFT extent records after record 400. Windows and ntfs3 do not maintain that on-disk layout, so an NTFS volume can have free MFT records while ntfs returns -ENOSPC when $MFT:$DATA needs another mapping-pairs extent. Use record 24 as the first normal record and maintain an in-memory tail reserve of up to four initialized records. Normal allocations skip the reserve, while $MFT metadata extent allocations consume it. When a new tail is initialized, allocate at least two records and reserve the following records to avoid recursive allocation during MFT extension. Existing free runs can seed the reserve on volumes mounted without one. For $MFT/$DATA, constrain an extent record to a record whose byte offset is below the new extent lowest VCN byte offset. This preserves bootstrap reachability without an arbitrary record 400 limit. If no safe record is available, validate and use reserved records 15, 12, 13, and 14 as bootstrap candidates while keeping their MFT bitmap entries in use. This allows existing Windows volumes to extend $MFT using their actual free records and prevents normal file allocation from consuming the metadata reserve. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 11 +- fs/ntfs/mft.c | 418 +++++++++++++++++++++++++++++++++++++---------- fs/ntfs/mft.h | 2 +- fs/ntfs/namei.c | 2 +- fs/ntfs/volume.h | 6 + 5 files changed, 348 insertions(+), 91 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 848a0d338b89..81b9e0971b3e 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -2917,7 +2917,7 @@ int ntfs_attr_add(struct ntfs_inode *ni, __le32 type, attr_ni = NULL; /* Allocate new extent. */ - err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL); + err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL, -1); if (err) { ntfs_error(sb, "Failed to allocate extent record"); goto err_out; @@ -3550,7 +3550,7 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra) * new extent and move attribute to it. */ ni = NULL; - err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL); + err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL, -1); if (err) { ntfs_error(sb, "Couldn't allocate MFT record, err : %d", err); return err; @@ -3976,7 +3976,10 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) unsigned int de_cnt = 0; /* Allocate new mft record. */ - err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL); + err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL, + base_ni->mft_no == FILE_MFT && + ni->type == AT_DATA && + ni->name == AT_UNNAMED ? stop_vcn : -1); if (err) { ntfs_error(sb, "Failed to allocate extent record"); goto put_err_out; @@ -4836,7 +4839,7 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi } /* Allocate new mft record. */ - err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL); + err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL, -1); if (err) { ntfs_error(sb, "Couldn't allocate MFT record"); goto put_err_out; diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 7e58c99f1728..61d4ee3a57fd 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -841,12 +841,126 @@ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no, static const char *es = " Leaving inconsistent metadata. Unmount and run chkdsk."; -#define RESERVED_MFT_RECORDS 64 +#define FIRST_NORMAL_MFT_RECORD 24 +#define MFT_RECORD_RESERVE 4 /* - * ntfs_mft_bitmap_find_and_alloc_free_rec_nolock - see name + * Records 12-15 are marked in use by Windows but normally have no name + * and no links. Keep them as the last bootstrap option when a volume + * mounted without an in-memory tail reserve needs its first $MFT metadata + * extent. + */ +static bool mft_reserved_is_free(struct ntfs_volume *vol, + struct ntfs_inode *mft_ni, s64 mft_no) +{ + struct attr_record *a; + struct mft_record *m; + struct folio *folio; + void *mapped; + pgoff_t index = NTFS_MFT_NR_TO_PIDX(vol, mft_no); + unsigned int ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no); + u32 attrs_offset, bytes_in_use; + bool available = false, have_std = false; + int i; + + for (i = 0; i < mft_ni->nr_extents; i++) { + if (mft_ni->ext.extent_ntfs_inos[i] && + mft_ni->ext.extent_ntfs_inos[i]->mft_no == mft_no) + return false; + } + m = kmalloc(vol->mft_record_size, GFP_NOFS); + if (!m) + return false; + + folio = read_mapping_folio(vol->mft_ino->i_mapping, index, NULL); + if (IS_ERR(folio)) + goto free_m; + + folio_lock(folio); + mapped = kmap_local_folio(folio, 0); + memcpy(m, (u8 *)mapped + ofs, vol->mft_record_size); + kunmap_local(mapped); + folio_unlock(folio); + folio_put(folio); + if (post_read_mst_fixup((struct ntfs_record *)m, vol->mft_record_size)) + goto free_m; + + if (!ntfs_is_mft_record(m->magic) || + !(m->flags & MFT_RECORD_IN_USE) || m->base_mft_record || + m->link_count) + goto out; + + attrs_offset = le16_to_cpu(m->attrs_offset); + bytes_in_use = le32_to_cpu(m->bytes_in_use); + if (attrs_offset > bytes_in_use || bytes_in_use > vol->mft_record_size || + bytes_in_use - attrs_offset < sizeof(a->type)) + goto out; + + for (a = (struct attr_record *)((u8 *)m + attrs_offset); + (u8 *)a + sizeof(a->type) <= (u8 *)m + bytes_in_use;) { + u32 len; + + if (a->type == AT_END) { + if ((u8 *)a + sizeof(a->type) + sizeof(a->length) > + (u8 *)m + bytes_in_use) + break; + /* Also accept a record emptied by an earlier bootstrap. */ + available = have_std || + (u8 *)a == (u8 *)m + attrs_offset; + break; + } + if (a->type == AT_FILE_NAME) + break; + len = le32_to_cpu(a->length); + if (len < offsetof(struct attr_record, data) || + (u8 *)a + len > (u8 *)m + bytes_in_use) + break; + if (a->type == AT_STANDARD_INFORMATION) { + u32 value_len, value_ofs; + + if (have_std || a->non_resident || + len < offsetof(struct attr_record, + data.resident.reserved) + 1) + break; + value_len = le32_to_cpu(a->data.resident.value_length); + value_ofs = le16_to_cpu(a->data.resident.value_offset); + if (value_ofs > len || value_len > len - value_ofs) + break; + have_std = true; + } + a = (struct attr_record *)((u8 *)a + len); + } +out: + kfree(m); + return available; +free_m: + kfree(m); + return false; +} + +static s64 mft_reserve_end(const u8 *buf, s64 buf_start, s64 buf_end, + s64 start, s64 pass_end, s64 initialized_mft_records) +{ + s64 end = start + 1; + s64 limit = min_t(s64, start + MFT_RECORD_RESERVE, pass_end); + + if (limit > initialized_mft_records) + limit = initialized_mft_records; + if (limit > buf_end) + limit = buf_end; + while (end < limit && + !(buf[(end - buf_start) >> 3] & + (1 << ((end - buf_start) & 7)))) + end++; + return end; +} + +/* + * mft_bitmap_alloc_free_rec - find and allocate a free MFT record * @vol: volume on which to search for a free mft record * @base_ni: open base inode if allocating an extent mft record or NULL + * @max_mft_no: first record which must not be allocated, or -1 + * @new_reserve_end: if not NULL, end of a free run starting after the result * * Search for a free mft record in the mft bitmap attribute on the ntfs volume * @vol. @@ -862,10 +976,12 @@ static const char *es = " Leaving inconsistent metadata. Unmount and run chkds * * Locking: Caller must hold vol->mftbmp_lock for writing. */ -static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vol, - struct ntfs_inode *base_ni) +static s64 mft_bitmap_alloc_free_rec(struct ntfs_volume *vol, + struct ntfs_inode *base_ni, + s64 max_mft_no, s64 *new_reserve_end) { s64 pass_end, ll, data_pos, pass_start, ofs, bit; + s64 initialized_mft_records; unsigned long flags; struct address_space *mftbmp_mapping; u8 *buf = NULL, *byte; @@ -882,30 +998,36 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo read_lock_irqsave(&NTFS_I(vol->mft_ino)->size_lock, flags); pass_end = NTFS_I(vol->mft_ino)->allocated_size >> vol->mft_record_size_bits; + initialized_mft_records = NTFS_I(vol->mft_ino)->initialized_size >> + vol->mft_record_size_bits; read_unlock_irqrestore(&NTFS_I(vol->mft_ino)->size_lock, flags); read_lock_irqsave(&NTFS_I(vol->mftbmp_ino)->size_lock, flags); ll = NTFS_I(vol->mftbmp_ino)->initialized_size << 3; read_unlock_irqrestore(&NTFS_I(vol->mftbmp_ino)->size_lock, flags); if (pass_end > ll) pass_end = ll; - pass = 1; - if (!base_ni) - data_pos = vol->mft_data_pos; - else - data_pos = base_ni->mft_no + 1; - if (data_pos < RESERVED_MFT_RECORDS) - data_pos = RESERVED_MFT_RECORDS; - if (data_pos >= pass_end) { - data_pos = RESERVED_MFT_RECORDS; + if (max_mft_no >= 0 && pass_end > max_mft_no) + pass_end = max_mft_no; + if (base_ni && base_ni->mft_no == FILE_MFT) { + data_pos = FILE_first_user; pass = 2; - /* This happens on a freshly formatted volume. */ if (data_pos >= pass_end) return -ENOSPC; - } - - if (base_ni && base_ni->mft_no == FILE_MFT) { - data_pos = 0; - pass = 2; + } else { + pass = 1; + if (!base_ni) + data_pos = vol->mft_data_pos; + else + data_pos = base_ni->mft_no + 1; + if (data_pos < FIRST_NORMAL_MFT_RECORD) + data_pos = FIRST_NORMAL_MFT_RECORD; + if (data_pos >= pass_end) { + data_pos = FIRST_NORMAL_MFT_RECORD; + pass = 2; + /* This happens on a freshly formatted volume. */ + if (data_pos >= pass_end) + return -ENOSPC; + } } pass_start = data_pos; @@ -940,38 +1062,28 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo size, data_pos, bit); for (; bit < size && data_pos + bit < pass_end; bit &= ~7ull, bit += 8) { - /* - * If we're extending $MFT and running out of the first - * mft record (base record) then give up searching since - * no guarantee that the found record will be accessible. - */ - if (base_ni && base_ni->mft_no == FILE_MFT && bit > 400) { - folio_unlock(folio); - kunmap_local(buf); - folio_put(folio); - return -ENOSPC; - } - byte = buf + (bit >> 3); if (*byte == 0xff) continue; - b = ffz((unsigned long)*byte); - if (b < 8 && b >= (bit & 7)) { + b = bit & 7; + for (; b < 8; b++) { + if (*byte & (1 << b)) + continue; ll = data_pos + (bit & ~7ull) + b; + if (ll >= pass_end) + break; + /* Keep the dynamic tail reserve for $MFT metadata. */ + if ((!base_ni || base_ni->mft_no != FILE_MFT) && + ll >= vol->mft_record_reserve_pos && + ll < vol->mft_record_reserve_end) + continue; if (unlikely(ll >= (1ll << 32))) { folio_unlock(folio); kunmap_local(buf); folio_put(folio); return -ENOSPC; } - *byte |= 1 << b; - folio_mark_dirty(folio); - folio_unlock(folio); - kunmap_local(buf); - folio_put(folio); - ntfs_debug("Done. (Found and allocated mft record 0x%llx.)", - ll); - return ll; + goto found; } } ntfs_debug("After inner for loop: size 0x%x, data_pos 0x%llx, bit 0x%llx", @@ -994,7 +1106,8 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo * part of the zone which we omitted earlier. */ pass_end = pass_start; - data_pos = pass_start = RESERVED_MFT_RECORDS; + data_pos = FIRST_NORMAL_MFT_RECORD; + pass_start = FIRST_NORMAL_MFT_RECORD; ntfs_debug("pass %i, pass_start 0x%llx, pass_end 0x%llx.", pass, pass_start, pass_end); if (data_pos >= pass_end) @@ -1004,6 +1117,18 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo /* No free mft records in currently initialized mft bitmap. */ ntfs_debug("Done. (No free mft records left in currently initialized mft bitmap.)"); return -ENOSPC; +found: + if (new_reserve_end) + *new_reserve_end = mft_reserve_end(buf, data_pos, + data_pos + size, ll, pass_end, + initialized_mft_records); + *byte |= 1 << b; + folio_mark_dirty(folio); + folio_unlock(folio); + kunmap_local(buf); + folio_put(folio); + ntfs_debug("Done. (Found and allocated mft record 0x%llx.)", ll); + return ll; } static int ntfs_mft_attr_extend(struct ntfs_inode *ni) @@ -1471,8 +1596,9 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol) * @vol: volume on which to extend the mft data attribute * * Extend the mft data attribute on the ntfs volume @vol by 16 mft records - * worth of clusters or if not enough space for this by one mft record worth - * of clusters. + * worth of clusters or if not enough space for this by two mft records worth + * of clusters. Keeping at least two new records breaks the recursion between + * extending $MFT and allocating a record for a new $MFT attribute extent. * * Note: Only changes allocated_size, i.e. does not touch initialized_size or * data_size. @@ -1526,10 +1652,8 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol) } lcn = rl->lcn + rl->length; ntfs_debug("Last lcn of mft data attribute is 0x%llx.", lcn); - /* Minimum allocation is one mft record worth of clusters. */ - min_nr = NTFS_B_TO_CLU(vol, vol->mft_record_size); - if (!min_nr) - min_nr = 1; + /* Keep room for the allocating record and at least one MFT reserve. */ + min_nr = DIV_ROUND_UP_ULL((u64)vol->mft_record_size * 2, vol->cluster_size); /* Want to allocate 16 mft records worth of clusters. */ nr = vol->mft_record_size << 4 >> vol->cluster_size_bits; if (!nr) @@ -1928,6 +2052,7 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n * @ni: [OUT] on success, set to the allocated ntfs inode * @base_ni: [IN] open base inode if allocating an extent mft record or NULL * @ni_mrec: [OUT] on successful return this is the mapped mft record + * @mft_data_vcn: [IN] lowest VCN of a new $MFT/$DATA extent, or -1 * * Allocate an mft record in $MFT/$DATA of an open ntfs volume @vol. * @@ -1955,30 +2080,23 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n * optimize this we start scanning at the place specified by @base_ni or if * @base_ni is NULL we start where we last stopped and we perform wrap around * when we reach the end. Note, we do not try to allocate mft records below - * number 64 because numbers 0 to 15 are the defined system files anyway and 16 - * to 64 are special in that they are used for storing extension mft records - * for the $DATA attribute of $MFT. This is required to avoid the possibility - * of creating a runlist with a circular dependency which once written to disk - * can never be read in again. Windows will only use records 16 to 24 for - * normal files if the volume is completely out of space. We never use them - * which means that when the volume is really out of space we cannot create any - * more files while Windows can still create up to 8 small files. We can start - * doing this at some later time, it does not matter much for now. + * number 24 because numbers 0 to 15 are the defined system files and records + * 16 to 23 are kept for metadata compatibility. Records reserved dynamically + * at the initialized MFT tail are skipped by normal allocation and consumed by + * $MFT metadata extent allocation. * * When scanning the mft bitmap, we only search up to the last allocated mft - * record. If there are no free records left in the range 64 to number of + * record. If there are no free records left in the range 24 to number of * allocated mft records, then we extend the $MFT/$DATA attribute in order to * create free mft records. We extend the allocated size of $MFT/$DATA by 16 * records at a time or one cluster, if cluster size is above 16kiB. If there - * is not sufficient space to do this, we try to extend by a single mft record - * or one cluster, if cluster size is above the mft record size. + * is not sufficient space to do this, we try to extend by two mft records or + * one cluster, if a cluster already contains at least two mft records. * - * No matter how many mft records we allocate, we initialize only the first - * allocated mft record, incrementing mft data size and initialized size - * accordingly, open an struct ntfs_inode for it and return it to the caller, unless - * there are less than 64 mft records, in which case we allocate and initialize - * mft records until we reach record 64 which we consider as the first free mft - * record for use by normal files. + * When extending the initialized MFT tail, we also initialize up to four + * additional records and reserve them in memory for future $MFT metadata + * extents. If there are less than 24 mft records, records are initialized + * until record 24, which is the first record used for normal files. * * If during any stage we overflow the initialized data in the mft bitmap, we * extend the initialized size (and data size) by 8 bytes, allocating another @@ -2014,9 +2132,12 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n */ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, struct ntfs_inode **ni, struct ntfs_inode *base_ni, - struct mft_record **ni_mrec) + struct mft_record **ni_mrec, const s64 mft_data_vcn) { s64 ll, bit, old_data_initialized, old_data_size; + s64 max_mft_no = -1, reserve_start = -1, reserve_end = -1; + s64 candidate_reserve_end = -1; + s64 *reserve_endp; unsigned long flags; struct folio *folio; struct ntfs_inode *mft_ni, *mftbmp_ni; @@ -2027,7 +2148,9 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, unsigned int ofs; int err; __le16 seq_no, usn; - bool record_formatted = false; + bool record_formatted = false, from_reserve = false, tail_alloc = false; + bool reserve_created = false; + bool forced_reserved_record = false; unsigned int memalloc_flags; if (base_ni && *ni) @@ -2036,6 +2159,21 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, /* @mode and @base_ni are mutually exclusive. */ if (mode && base_ni) return -EINVAL; + if (mft_data_vcn >= 0 && + (!base_ni || base_ni->mft_no != FILE_MFT)) + return -EINVAL; + if (mft_data_vcn >= 0) { + u64 vbo; + + if ((u64)mft_data_vcn > (U64_MAX >> vol->cluster_size_bits)) + return -EOVERFLOW; + vbo = (u64)mft_data_vcn << vol->cluster_size_bits; + /* + * The whole extent record must be reachable without this + * extent, including when an MFT record spans multiple clusters. + */ + max_mft_no = vbo >> vol->mft_record_size_bits; + } if (base_ni) ntfs_debug("Entering (allocating an extent mft record for base mft record 0x%llx).", @@ -2050,10 +2188,39 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mutex_lock(&mft_ni->mrec_lock); mftbmp_ni = NTFS_I(vol->mftbmp_ino); search_free_rec: + from_reserve = false; + reserve_created = false; + candidate_reserve_end = -1; if (!base_ni || base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); - bit = ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(vol, base_ni); + if (base_ni && base_ni->mft_no == FILE_MFT && + vol->mft_record_reserve_pos < vol->mft_record_reserve_end && + (max_mft_no < 0 || vol->mft_record_reserve_pos < max_mft_no)) { + bit = vol->mft_record_reserve_pos; + err = ntfs_bitmap_set_bit(vol->mftbmp_ino, bit); + if (unlikely(err)) { + ntfs_error(vol->sb, + "Failed to allocate reserved MFT record 0x%llx.", + bit); + goto err_out; + } + vol->mft_record_reserve_pos++; + from_reserve = true; + ntfs_debug("Allocated MFT metadata record 0x%llx from tail reserve.", + bit); + goto have_alloc_rec; + } + reserve_endp = vol->mft_record_reserve_pos >= + vol->mft_record_reserve_end ? &candidate_reserve_end : NULL; + bit = mft_bitmap_alloc_free_rec(vol, base_ni, max_mft_no, reserve_endp); if (bit >= 0) { + if (candidate_reserve_end > bit + 1) { + vol->mft_record_reserve_pos = bit + 1; + vol->mft_record_reserve_end = candidate_reserve_end; + reserve_created = true; + ntfs_debug("Reserved free MFT records [0x%llx, 0x%llx) for metadata.", + bit + 1, candidate_reserve_end); + } ntfs_debug("Found and allocated free record (#1), bit 0x%llx.", (long long)bit); goto have_alloc_rec; @@ -2068,6 +2235,24 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, } if (base_ni && base_ni->mft_no == FILE_MFT) { + static const u8 bootstrap_records[] = { + FILE_reserved15, FILE_reserved12, FILE_reserved13, + FILE_reserved14, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(bootstrap_records); i++) { + if (max_mft_no >= 0 && bootstrap_records[i] >= max_mft_no) + continue; + if (!mft_reserved_is_free(vol, mft_ni, + bootstrap_records[i])) + continue; + bit = bootstrap_records[i]; + forced_reserved_record = true; + ntfs_debug("Using reserved MFT record %lld to bootstrap metadata extension.", + bit); + goto have_alloc_rec; + } memalloc_nofs_restore(memalloc_flags); return bit; } @@ -2087,10 +2272,10 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, old_data_initialized = mftbmp_ni->initialized_size; read_unlock_irqrestore(&mftbmp_ni->size_lock, flags); if (old_data_initialized << 3 > ll && - old_data_initialized > RESERVED_MFT_RECORDS / 8) { + old_data_initialized << 3 > FIRST_NORMAL_MFT_RECORD) { bit = ll; - if (bit < RESERVED_MFT_RECORDS) - bit = RESERVED_MFT_RECORDS; + if (bit < FIRST_NORMAL_MFT_RECORD) + bit = FIRST_NORMAL_MFT_RECORD; if (unlikely(bit >= (1ll << 32))) goto max_err_out; ntfs_debug("Found free record (#2), bit 0x%llx.", @@ -2176,6 +2361,11 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, read_lock_irqsave(&mft_ni->size_lock, flags); old_data_initialized = mft_ni->initialized_size; read_unlock_irqrestore(&mft_ni->size_lock, flags); + tail_alloc = (!base_ni || base_ni->mft_no != FILE_MFT) && + bit >= (old_data_initialized >> vol->mft_record_size_bits) && + vol->mft_record_reserve_pos >= vol->mft_record_reserve_end; + if (tail_alloc) + ll = (bit + 2) << vol->mft_record_size_bits; if (ll <= old_data_initialized) { ntfs_debug("Allocated mft record already initialized."); goto mft_rec_already_initialized; @@ -2208,6 +2398,29 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mft_ni->initialized_size); } read_unlock_irqrestore(&mft_ni->size_lock, flags); + if (tail_alloc) { + s64 bitmap_records; + + read_lock_irqsave(&mft_ni->size_lock, flags); + reserve_end = mft_ni->allocated_size >> + vol->mft_record_size_bits; + read_unlock_irqrestore(&mft_ni->size_lock, flags); + read_lock_irqsave(&mftbmp_ni->size_lock, flags); + bitmap_records = mftbmp_ni->initialized_size << 3; + read_unlock_irqrestore(&mftbmp_ni->size_lock, flags); + if (reserve_end > bitmap_records) + reserve_end = bitmap_records; + if (reserve_end > bit + 1 + MFT_RECORD_RESERVE) + reserve_end = bit + 1 + MFT_RECORD_RESERVE; + reserve_start = bit + 1; + if (reserve_end > reserve_start) { + ll = reserve_end << vol->mft_record_size_bits; + } else { + reserve_start = -1; + reserve_end = -1; + ll = (bit + 1) << vol->mft_record_size_bits; + } + } } else if (ll > mft_ni->allocated_size) { err = -ENOSPC; goto undo_mftbmp_alloc_nolock; @@ -2276,6 +2489,12 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mark_mft_record_dirty(ctx->ntfs_ino); ntfs_attr_put_search_ctx(ctx); unmap_mft_record(mft_ni); + if (reserve_start >= 0 && reserve_end > reserve_start) { + vol->mft_record_reserve_pos = reserve_start; + vol->mft_record_reserve_end = reserve_end; + ntfs_debug("Reserved MFT records [0x%llx, 0x%llx) for metadata.", + reserve_start, reserve_end); + } read_lock_irqsave(&mft_ni->size_lock, flags); ntfs_debug("Status of mft data after mft record initialization: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.", mft_ni->allocated_size, i_size_read(vol->mft_ino), @@ -2315,8 +2534,8 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, /* If we just formatted the mft record no need to do it again. */ if (!record_formatted) { /* Sanity check that the mft record is really not in use. */ - if (ntfs_is_file_record(m->magic) && - (m->flags & MFT_RECORD_IN_USE)) { + if (!forced_reserved_record && ntfs_is_file_record(m->magic) && + (m->flags & MFT_RECORD_IN_USE)) { ntfs_warning(vol->sb, "Mft record 0x%llx was marked free in mft bitmap but is marked used itself. Unmount and run chkdsk.", bit); @@ -2389,9 +2608,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, ntfs_error(vol->sb, "Failed to map allocated extent mft record 0x%llx.", bit); err = PTR_ERR(m_tmp); - /* Set the mft record itself not in use. */ - m->flags &= cpu_to_le16( - ~le16_to_cpu(MFT_RECORD_IN_USE)); + if (forced_reserved_record) { + m->base_mft_record = 0; + m->flags |= MFT_RECORD_IN_USE; + } else { + /* Set the mft record itself not in use. */ + m->flags &= cpu_to_le16(~le16_to_cpu(MFT_RECORD_IN_USE)); + } /* Make sure the mft record is written out to disk. */ ntfs_mft_mark_dirty(folio); folio_unlock(folio); @@ -2463,7 +2686,8 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, (*ni)->mft_no = bit; if (ni_mrec) *ni_mrec = (*ni)->mrec; - ntfs_dec_free_mft_records(vol, 1); + if (!forced_reserved_record) + ntfs_dec_free_mft_records(vol, 1); return 0; undo_data_init: write_lock_irqsave(&mft_ni->size_lock, flags); @@ -2475,10 +2699,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, if (!base_ni || base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); undo_mftbmp_alloc_nolock: - if (ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) { + if (!forced_reserved_record && ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) { ntfs_error(vol->sb, "Failed to clear bit in mft bitmap.%s", es); NVolSetErrors(vol); } + if ((from_reserve || reserve_created) && + vol->mft_record_reserve_pos == bit + 1) + vol->mft_record_reserve_pos = bit; if (!base_ni || base_ni->mft_no != FILE_MFT) up_write(&vol->mftbmp_lock); err_out: @@ -2514,9 +2741,11 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) int err; u16 seq_no; __le16 old_seq_no; + __le64 old_base_mft_record; struct mft_record *ni_mrec; unsigned int memalloc_flags; struct ntfs_inode *base_ni; + bool keep_reserved; if (!vol || !ni) return -EINVAL; @@ -2529,9 +2758,23 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) /* Cache the mft reference for later. */ mft_no = ni->mft_no; + if (likely(ni->nr_extents >= 0)) + base_ni = ni; + else + base_ni = ni->ext.base_ntfs_ino; + keep_reserved = mft_no >= FILE_reserved12 && + mft_no <= FILE_reserved15 && + base_ni->mft_no == FILE_MFT; - /* Mark the mft record as not in use. */ - ni_mrec->flags &= ~MFT_RECORD_IN_USE; + old_base_mft_record = ni_mrec->base_mft_record; + if (keep_reserved) { + /* Restore the special, unnamed form used by reserved records. */ + ni_mrec->base_mft_record = 0; + ni_mrec->flags |= MFT_RECORD_IN_USE; + } else { + /* Mark the mft record as not in use. */ + ni_mrec->flags &= ~MFT_RECORD_IN_USE; + } /* Increment the sequence number, skipping zero, if it is not zero. */ old_seq_no = ni_mrec->sequence_number; @@ -2560,16 +2803,20 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) if (err) goto sync_rollback; - if (likely(ni->nr_extents >= 0)) - base_ni = ni; - else - base_ni = ni->ext.base_ntfs_ino; + if (keep_reserved) { + unmap_mft_record(ni); + return 0; + } /* Clear the bit in the $MFT/$BITMAP corresponding to this record. */ memalloc_flags = memalloc_nofs_save(); if (base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no); + if (!err && base_ni->mft_no == FILE_MFT && + mft_no + 1 == vol->mft_record_reserve_pos && + mft_no < vol->mft_record_reserve_end) + vol->mft_record_reserve_pos = mft_no; if (base_ni->mft_no != FILE_MFT) up_write(&vol->mftbmp_lock); memalloc_nofs_restore(memalloc_flags); @@ -2595,6 +2842,7 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) "Eeek! Rollback failed in %s. Leaving inconsistent metadata!\n", __func__); ni_mrec->flags |= MFT_RECORD_IN_USE; ni_mrec->sequence_number = old_seq_no; + ni_mrec->base_mft_record = old_base_mft_record; NInoSetDirty(ni); write_mft_record(ni, ni_mrec, 0); unmap_mft_record(ni); diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h index 75a51a98d0f6..ed5c1d595c0d 100644 --- a/fs/ntfs/mft.h +++ b/fs/ntfs/mft.h @@ -78,7 +78,7 @@ static inline int write_mft_record(struct ntfs_inode *ni, struct mft_record *m, int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, struct ntfs_inode **ni, struct ntfs_inode *base_ni, - struct mft_record **ni_mrec); + struct mft_record **ni_mrec, const s64 mft_data_vcn); int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni); int ntfs_mft_records_write(const struct ntfs_volume *vol, const u64 mref, const s64 count, struct mft_record *b); diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 7091b2496fac..fdf52fac4329 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -480,7 +480,7 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d mark_inode_dirty(dir); err = ntfs_mft_record_alloc(dir_ni->vol, mode, &ni, NULL, - &ni_mrec); + &ni_mrec, -1); if (err) { iput(vi); return ERR_PTR(err); diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h index 65fd3908af26..2b60d14fc7ef 100644 --- a/fs/ntfs/volume.h +++ b/fs/ntfs/volume.h @@ -55,6 +55,10 @@ * @attrdef_size: Size of the attribute definition table in bytes. * @attrdef: Table of attribute definitions. Obtained from FILE_AttrDef. * @mft_data_pos: Mft record number at which to allocate the next mft record. + * @mft_record_reserve_pos: First record in the in-memory MFT metadata reserve + * (protected by mftbmp_lock). + * @mft_record_reserve_end: First record beyond the MFT metadata reserve + * (protected by mftbmp_lock). * @mft_zone_start: First cluster of the mft zone. * @mft_zone_end: First cluster beyond the mft zone. * @mft_zone_pos: Current position in the mft zone. @@ -119,6 +123,8 @@ struct ntfs_volume { s32 attrdef_size; struct attr_def *attrdef; s64 mft_data_pos; + s64 mft_record_reserve_pos; + s64 mft_record_reserve_end; s64 mft_zone_start; s64 mft_zone_end; s64 mft_zone_pos; From b1d732e62a5b3942546e4edaab8976258e779287 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 6 Sep 2026 11:12:47 +0900 Subject: [PATCH 1563/2366] ntfs: repack $MFT/$ATTRIBUTE LIST Repack the non-resident $MFT/$ATTRIBUTE_LIST into a contiguous run when its mapping pairs no longer fit in the base MFT record. Propagate allocation and writeback errors, and check synchronous replacement writes. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 47 ++++++++--- fs/ntfs/attrlist.c | 202 ++++++++++++++++++++++++++++++++++++++++++--- fs/ntfs/inode.c | 10 ++- 3 files changed, 236 insertions(+), 23 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 81b9e0971b3e..d1696ce6acd5 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -3844,13 +3844,13 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) */ if (ni->type == AT_ATTRIBUTE_LIST) { ntfs_attr_put_search_ctx(ctx); - if (ntfs_inode_free_space(base_ni, mp_size - - cur_max_mp_size)) { - ntfs_debug("Attribute list is too big. Defragment the volume\n"); - return -ENOSPC; - } - if (ntfs_attrlist_update(base_ni)) - return -EIO; + err = ntfs_inode_free_space(base_ni, mp_size - + cur_max_mp_size); + if (err) + return err; + err = ntfs_attrlist_update(base_ni); + if (err) + return err; goto retry; } @@ -4522,13 +4522,39 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz ntfs_bytes_to_cluster(vol, ni->allocated_size), first_free_vcn - ntfs_bytes_to_cluster(vol, ni->allocated_size), - lcn_seek_from, DATA_ZONE, false, false, false); + lcn_seek_from, DATA_ZONE, false, + ni->type == AT_ATTRIBUTE_LIST, false); if (IS_ERR(rl)) { ntfs_debug("Cluster allocation failed (%lld)", (long long)first_free_vcn - ntfs_bytes_to_cluster(vol, ni->allocated_size)); return PTR_ERR(rl); } + /* + * A contiguous ATTRIBUTE_LIST allocation keeps its mapping + * pairs small enough to fit in the base MFT record. The + * allocator can return a short run when contiguity was + * requested, so discard it and retry normally if necessary. + */ + if (ni->type == AT_ATTRIBUTE_LIST && + (rl->vcn != ntfs_bytes_to_cluster(vol, + ni->allocated_size) || + rl->length != first_free_vcn - + ntfs_bytes_to_cluster(vol, ni->allocated_size) || + rl[1].length)) { + ntfs_cluster_free_from_rl(vol, rl); + kvfree(rl); + rl = ntfs_cluster_alloc(vol, + ntfs_bytes_to_cluster(vol, + ni->allocated_size), + first_free_vcn - + ntfs_bytes_to_cluster(vol, + ni->allocated_size), + lcn_seek_from, DATA_ZONE, false, + false, false); + if (IS_ERR(rl)) + return PTR_ERR(rl); + } } if (!NInoCompressed(ni)) { @@ -4921,7 +4947,8 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo ntfs_debug("Entering for inode 0x%llx, attr 0x%x, size %lld\n", (unsigned long long)ni->mft_no, ni->type, newsize); - if (ni->data_size == newsize) { + if (ni->data_size == newsize && + (!prealloc_size || prealloc_size <= ni->allocated_size)) { ntfs_debug("Size is already ok\n"); return 0; } @@ -4936,7 +4963,7 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo } if (NInoNonResident(ni)) { - if (newsize > ni->data_size) + if (newsize > ni->data_size || prealloc_size > ni->allocated_size) err = ntfs_non_resident_attr_expand(ni, newsize, prealloc_size, NVolDisableSparse(ni->vol) ? HOLES_NO : HOLES_OK, true); diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c index be3086d34338..4e60f7e93010 100644 --- a/fs/ntfs/attrlist.c +++ b/fs/ntfs/attrlist.c @@ -12,6 +12,9 @@ #include "mft.h" #include "attrib.h" #include "attrlist.h" +#include "lcnalloc.h" + +#define NTFS_MAX_ATTR_LIST_SIZE (256 * 1024) /* * ntfs_attrlist_need - check whether inode need attribute list @@ -51,11 +54,151 @@ int ntfs_attrlist_need(struct ntfs_inode *ni) return 0; } +/* + * Repack the $MFT/$ATTRIBUTE_LIST data into one run. + * + * The mapping pairs for an $ATTRIBUTE_LIST must remain in the base MFT + * record. Once that record has no room left, extending a fragmented list + * can require one more mapping-pairs byte than the record can hold. There + * is no attribute that can legally be moved out in that state: $STANDARD_ + * INFORMATION, $ATTRIBUTE_LIST, and the first $MFT/$DATA extent all have to + * stay in the base record. Move the list data to one contiguous run. The + * caller supplies the minimum allocation size so a recovery can use the + * smallest useful run while normal updates can still request the maximum + * legal list size as a reserve. + */ +static int ntfs_attrlist_repack(struct inode *attr_vi, + struct ntfs_inode *attr_ni, s64 min_alloc_size) +{ + struct ntfs_volume *vol = attr_ni->vol; + struct runlist_element *old_rl, *new_rl; + u8 *data = NULL; + s64 data_size, alloc_size, nr_clusters, written; + s64 old_alloc_size; + size_t old_rl_count, new_rl_count; + unsigned long flags; + int err, restore_err; + + if (attr_ni->mft_no != FILE_MFT || !NInoNonResident(attr_ni) || + min_alloc_size < 0) + return -EINVAL; + + err = ntfs_attr_map_whole_runlist(attr_ni); + if (err) + return err; + + data_size = attr_ni->data_size; + if (data_size < 0) + return -EIO; + + if (data_size) { + data = kvmalloc(data_size, GFP_NOFS); + if (!data) + return -ENOMEM; + + written = ntfs_inode_attr_pread(attr_vi, 0, data_size, data); + if (written != data_size) { + err = written < 0 ? (int)written : -EIO; + goto out_free_data; + } + } + + old_alloc_size = attr_ni->allocated_size; + alloc_size = max_t(s64, old_alloc_size, min_alloc_size); + nr_clusters = ntfs_bytes_to_cluster(vol, + alloc_size + vol->cluster_size - 1); + if (nr_clusters <= 0) { + err = -EFBIG; + goto out_free_data; + } + + /* A single run keeps the mapping pairs at the minimum size. */ + new_rl = ntfs_cluster_alloc(vol, 0, nr_clusters, -1, DATA_ZONE, + true, true, false); + if (IS_ERR(new_rl)) { + err = PTR_ERR(new_rl); + goto out_free_data; + } + + new_rl_count = 0; + if (new_rl->vcn == 0 && new_rl->length == nr_clusters && + !new_rl[1].length) + new_rl_count = 2; + + if (new_rl_count != 2) { + ntfs_cluster_free_from_rl(vol, new_rl); + kvfree(new_rl); + err = -ENOSPC; + goto out_free_data; + } + + old_rl = attr_ni->runlist.rl; + old_rl_count = attr_ni->runlist.count; + down_write(&attr_ni->runlist.lock); + attr_ni->runlist.rl = new_rl; + attr_ni->runlist.count = new_rl_count; + up_write(&attr_ni->runlist.lock); + + write_lock_irqsave(&attr_ni->size_lock, flags); + attr_ni->allocated_size = ntfs_cluster_to_bytes(vol, nr_clusters); + write_unlock_irqrestore(&attr_ni->size_lock, flags); + + /* Populate the replacement extent before publishing its mapping pairs. */ + if (data_size) { + written = ntfs_inode_attr_pwrite(attr_vi, 0, data_size, data, true); + if (written != data_size) { + err = written < 0 ? (int)written : -EIO; + goto restore_old_runlist; + } + } + + err = ntfs_attr_update_mapping_pairs(attr_ni, 0); + if (err) + goto restore_old_runlist; + + /* The new mapping is now authoritative; release the old data runs. */ + if (ntfs_cluster_free_from_rl(vol, old_rl)) { + ntfs_error(vol->sb, + "Failed to free old ATTRIBUTE_LIST extent: inode %#llx", + (long long)attr_ni->mft_no); + NVolSetErrors(vol); + } + kvfree(old_rl); + kvfree(data); + return 0; + +restore_old_runlist: + down_write(&attr_ni->runlist.lock); + attr_ni->runlist.rl = old_rl; + attr_ni->runlist.count = old_rl_count; + up_write(&attr_ni->runlist.lock); + + write_lock_irqsave(&attr_ni->size_lock, flags); + attr_ni->allocated_size = old_alloc_size; + write_unlock_irqrestore(&attr_ni->size_lock, flags); + + restore_err = ntfs_attr_update_mapping_pairs(attr_ni, 0); + if (restore_err) { + ntfs_error(vol->sb, "Failed to restore ATTRIBUTE_LIST mapping pairs (%d)", + restore_err); + NVolSetErrors(vol); + } + + ntfs_cluster_free_from_rl(vol, new_rl); + kvfree(new_rl); + err = err ? err : restore_err; + +out_free_data: + kvfree(data); + return err; +} + int ntfs_attrlist_update(struct ntfs_inode *base_ni) { struct inode *attr_vi; struct ntfs_inode *attr_ni; - int err; + s64 written; + int err, retry_err; /* * generic_shutdown_super() clears SB_ACTIVE before evicting cached @@ -74,21 +217,55 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) attr_ni = NTFS_I(attr_vi); err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO); - if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT) { - err = ntfs_attr_truncate(attr_ni, 0); - if (err || ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO) != 0) { + if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT && + NInoNonResident(attr_ni)) { + retry_err = ntfs_attrlist_repack(attr_vi, attr_ni, + base_ni->attr_list_size); + if (retry_err) { + ntfs_error(base_ni->vol->sb, "Failed to repack attribute list"); iput(attr_vi); + return retry_err; + } + + retry_err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, + HOLES_NO); + if (retry_err) { ntfs_error(base_ni->vol->sb, - "Failed to truncate attribute list of inode %#llx", - (long long)base_ni->mft_no); - return -EIO; + "Failed to resize attribute list after repack"); + iput(attr_vi); + return retry_err; } } else if (err) { iput(attr_vi); ntfs_error(base_ni->vol->sb, "Failed to truncate attribute list of inode %#llx", (long long)base_ni->mft_no); - return -EIO; + return err; + } + + /* + * Reserve the maximum legal list size while the MFT metadata area is + * still easy to allocate contiguously. This prevents a later list entry + * from needing another mapping-pairs byte in the full base MFT record. + * Failure to obtain the optional reserve must not reject the current + * metadata update; the repack retry above remains available if needed. + */ + if (base_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni) && + attr_ni->allocated_size < NTFS_MAX_ATTR_LIST_SIZE) { + retry_err = ntfs_attr_expand(attr_ni, base_ni->attr_list_size, + NTFS_MAX_ATTR_LIST_SIZE); + if (retry_err == -ENOSPC) { + retry_err = ntfs_attrlist_repack(attr_vi, attr_ni, + NTFS_MAX_ATTR_LIST_SIZE); + if (retry_err == -ENOSPC) + retry_err = 0; + } + if (retry_err) { + ntfs_error(base_ni->vol->sb, + "Failed to reserve attribute list space"); + iput(attr_vi); + return retry_err; + } } i_size_write(attr_vi, base_ni->attr_list_size); @@ -96,14 +273,15 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni)) NInoSetAttrListNonResident(base_ni); - if (ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size, - base_ni->attr_list, false) != - base_ni->attr_list_size) { + written = ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size, + base_ni->attr_list, false); + if (written != base_ni->attr_list_size) { + err = written < 0 ? (int)written : -EIO; iput(attr_vi); ntfs_error(base_ni->vol->sb, "Failed to write attribute list of inode %#llx", (long long)base_ni->mft_no); - return -EIO; + return err; } NInoSetAttrListDirty(base_ni); diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 5aedc045f65a..332825db477a 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -3745,6 +3745,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, u64 rl_length = 0; s64 vcn; struct runlist_element *rl; + int bio_err; lcn_count = max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len)); vcn = ntfs_pidx_to_cluster(vol, folio->index); @@ -3787,8 +3788,15 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, goto err_unlock_folio; } - submit_bio_wait(bio); + bio_err = submit_bio_wait(bio); bio_put(bio); + if (bio_err) { + ntfs_error(vi->i_sb, + "Synchronous attribute write failed (%d)", + bio_err); + ret = bio_err; + goto err_unlock_folio; + } vcn += rl_length; offset += length; } while (lcn_count != 0); From 631946431ddc66a472c5cc629cd654e62dfa1f88 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 4 Sep 2026 14:46:38 +0900 Subject: [PATCH 1564/2366] ntfs: account for MFT records added during allocation When no free MFT record is available in the initialized $MFT/$BITMAP, ntfs_mft_record_alloc() extends $MFT/$DATA and formats the requested record together with a dynamically sized tail reserve. Those records become visible through the $MFT file size before charging the requested record to the free-record counter. Account for all newly visible records before releasing the MFT allocation lock, then subtract the one record being allocated. Keep MFT counter updates independent of the asynchronous free-cluster scan and update the counter when a record is successfully cleared in the MFT bitmap. Store the clamped result of the MFT bitmap scan and keep statfs from exposing an invalid cached count if an accounting error occurs. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/mft.c | 11 ++++++++--- fs/ntfs/super.c | 12 +++++++++--- fs/ntfs/volume.h | 6 ------ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 61d4ee3a57fd..f0656d0bbeeb 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -1539,7 +1539,6 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol) ret = ntfs_attr_set(mftbmp_ni, old_initialized_size, 8, 0); if (likely(!ret)) { ntfs_debug("Done. (Wrote eight initialized bytes to mft bitmap."); - ntfs_inc_free_mft_records(vol, 8 * 8); return 0; } ntfs_error(vol->sb, "Failed to write to mft bitmap."); @@ -2135,6 +2134,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, struct mft_record **ni_mrec, const s64 mft_data_vcn) { s64 ll, bit, old_data_initialized, old_data_size; + s64 nr_new_mft_records = 0; s64 max_mft_no = -1, reserve_start = -1, reserve_end = -1; s64 candidate_reserve_end = -1; s64 *reserve_endp; @@ -2501,8 +2501,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, mft_ni->initialized_size); WARN_ON(i_size_read(vol->mft_ino) > mft_ni->allocated_size); WARN_ON(mft_ni->initialized_size > i_size_read(vol->mft_ino)); + nr_new_mft_records = (i_size_read(vol->mft_ino) - old_data_size) >> + vol->mft_record_size_bits; read_unlock_irqrestore(&mft_ni->size_lock, flags); mft_rec_already_initialized: + /* Account for newly visible MFT records before dropping the lock. */ + if (nr_new_mft_records > 0) + ntfs_inc_free_mft_records(vol, nr_new_mft_records); /* * We can finally drop the mft bitmap lock as the mft data attribute * has been fully updated. The only disparity left is that the @@ -2813,6 +2818,8 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) if (base_ni->mft_no != FILE_MFT) down_write(&vol->mftbmp_lock); err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no); + if (!err) + ntfs_inc_free_mft_records(vol, 1); if (!err && base_ni->mft_no == FILE_MFT && mft_no + 1 == vol->mft_record_reserve_pos && mft_no < vol->mft_record_reserve_end) @@ -2822,9 +2829,7 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni) memalloc_nofs_restore(memalloc_flags); if (err) goto bitmap_rollback; - unmap_mft_record(ni); - ntfs_inc_free_mft_records(vol, 1); return 0; /* Rollback what we did... */ diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c index 60d43339c590..0867d6a82ebe 100644 --- a/fs/ntfs/super.c +++ b/fs/ntfs/super.c @@ -2064,8 +2064,7 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol, /* If errors occurred we may well have gone below zero, fix this. */ if (nr_free < 0) nr_free = 0; - else - atomic64_set(&vol->free_mft_records, nr_free); + atomic64_set(&vol->free_mft_records, nr_free); ntfs_debug("Exiting."); return nr_free; @@ -2131,7 +2130,14 @@ static int ntfs_statfs(struct dentry *dentry, struct kstatfs *sfs) read_unlock_irqrestore(&mft_ni->size_lock, flags); /* Free inodes in fs (based on current total count). */ - sfs->f_ffree = atomic64_read(&vol->free_mft_records); + size = atomic64_read(&vol->free_mft_records); + if (unlikely(size < 0 || size > (s64)sfs->f_files)) + ntfs_warning(vol->sb, "Invalid free MFT record count %lld.", size); + if (size < 0) + size = 0; + else if (size > (s64)sfs->f_files) + size = sfs->f_files; + sfs->f_ffree = size; /* * File system id. This is extremely *nix flavour dependent and even diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h index 2b60d14fc7ef..bc85a9592245 100644 --- a/fs/ntfs/volume.h +++ b/fs/ntfs/volume.h @@ -258,17 +258,11 @@ static inline void ntfs_dec_free_clusters(struct ntfs_volume *vol, s64 nr) static inline void ntfs_inc_free_mft_records(struct ntfs_volume *vol, s64 nr) { - if (!NVolFreeClusterKnown(vol)) - return; - atomic64_add(nr, &vol->free_mft_records); } static inline void ntfs_dec_free_mft_records(struct ntfs_volume *vol, s64 nr) { - if (!NVolFreeClusterKnown(vol)) - return; - atomic64_sub(nr, &vol->free_mft_records); } From 91709ba5d6d709b2b663287b7e871e2c6b480502 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 6 Sep 2026 21:37:28 +0900 Subject: [PATCH 1565/2366] ntfs: protect runlist updates with the runlist lock ntfs_non_resident_attr_shrink() calls runlist helpers that require the runlist write lock, but did not hold it while freeing clusters and truncating the runlist. Serialize those operations and the resident conversion with the runlist lock. ntfs_attr_map_cluster() can merge a newly allocated run before updating mapping pairs. If the update fails, free the clusters and restore both the in-memory runlist and on-disk mapping pairs from a saved runlist. Mark the volume in error if either rollback step fails. Fixes: 495e90fa3348 ("ntfs: update attrib operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 205 +++++++++++++++++++++++++++++++++++---------- fs/ntfs/attrib.h | 9 ++ fs/ntfs/attrlist.c | 46 ++++++---- fs/ntfs/attrlist.h | 2 + fs/ntfs/compress.c | 2 +- fs/ntfs/file.c | 3 +- fs/ntfs/inode.c | 2 +- fs/ntfs/mft.c | 13 +-- 8 files changed, 216 insertions(+), 66 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index d1696ce6acd5..c949ff765075 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -3574,7 +3574,8 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra) * update allocated and compressed size. */ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, - struct mft_record *m, struct ntfs_attr_search_ctx *ctx) + struct mft_record *m, struct ntfs_attr_search_ctx *ctx, + struct ntfs_inode *locked_ni, bool defer_attrlist) { int sparse, err = 0; struct ntfs_inode *base_ni; @@ -3610,6 +3611,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, le16_to_cpu(a->data.non_resident.mapping_pairs_offset) == 8) && !(le32_to_cpu(m->bytes_allocated) - le32_to_cpu(m->bytes_in_use))) { + if (defer_attrlist) + return -ENOSPC; if (!NInoAttrList(base_ni)) { err = ntfs_inode_add_attrlist(base_ni); if (err) @@ -3623,7 +3626,7 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, goto out; } - err = ntfs_attrlist_update(base_ni); + err = ntfs_attrlist_update_locked(base_ni, locked_ni); if (err) goto out; err = -EAGAIN; @@ -3703,6 +3706,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, * ntfs_attr_update_mapping_pairs - update mapping pairs for ntfs attribute * @ni: non-resident ntfs inode for which we need update * @from_vcn: update runlist starting this VCN + * @locked_ni: inode whose runlist write lock is already held + * @defer_attrlist: return -ENOSPC instead of updating an attribute list * * Build mapping pairs from @na->rl and write them to the disk. Also, this * function updates sparse bit, allocated and compressed size (allocates/frees @@ -3712,7 +3717,10 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, * call to this function. Vice-versa @na->compressed_size will be calculated and * set to correct value during this function. */ -int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) +static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, + s64 from_vcn, + struct ntfs_inode *locked_ni, + bool defer_attrlist) { struct ntfs_attr_search_ctx *ctx; struct ntfs_inode *base_ni; @@ -3804,7 +3812,8 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) continue; } - err = ntfs_attr_update_meta(a, ni, m, ctx); + err = ntfs_attr_update_meta(a, ni, m, ctx, locked_ni, + defer_attrlist); if (err < 0) { if (err == -EAGAIN) { ntfs_attr_put_search_ctx(ctx); @@ -3844,11 +3853,17 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) */ if (ni->type == AT_ATTRIBUTE_LIST) { ntfs_attr_put_search_ctx(ctx); + ctx = NULL; + if (locked_ni == ni || defer_attrlist) { + err = -ENOSPC; + goto put_err_out; + } err = ntfs_inode_free_space(base_ni, mp_size - cur_max_mp_size); if (err) return err; - err = ntfs_attrlist_update(base_ni); + err = ntfs_attrlist_update_locked( + base_ni, locked_ni); if (err) return err; goto retry; @@ -3856,6 +3871,10 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) /* Add attribute list if it isn't present, and retry. */ if (!NInoAttrList(base_ni)) { + if (defer_attrlist) { + err = -ENOSPC; + goto put_err_out; + } ntfs_attr_put_search_ctx(ctx); if (ntfs_inode_add_attrlist(base_ni)) { ntfs_error(sb, "Can not add attrlist"); @@ -3883,13 +3902,21 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) } } + if (defer_attrlist && + (ctx->ntfs_ino->nr_extents == -1 || + NInoAttrList(ctx->ntfs_ino)) && + ctx->attr->type != AT_ATTRIBUTE_LIST) { + err = -ENOSPC; + goto put_err_out; + } + /* Update lowest vcn. */ a->data.non_resident.lowest_vcn = cpu_to_le64(stop_vcn); mark_mft_record_dirty(ctx->ntfs_ino); if ((ctx->ntfs_ino->nr_extents == -1 || NInoAttrList(ctx->ntfs_ino)) && ctx->attr->type != AT_ATTRIBUTE_LIST) { ctx->al_entry->lowest_vcn = cpu_to_le64(stop_vcn); - err = ntfs_attrlist_update(base_ni); + err = ntfs_attrlist_update_locked(base_ni, locked_ni); if (err) goto put_err_out; } @@ -4064,6 +4091,19 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) return err; } +int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni, + s64 from_vcn, + struct ntfs_inode *locked_ni) +{ + return __ntfs_attr_update_mapping_pairs(ni, from_vcn, locked_ni, + false); +} + +int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) +{ + return ntfs_attr_update_mapping_pairs_locked(ni, from_vcn, NULL); +} + /* * ntfs_attr_make_resident - convert a non-resident to a resident attribute * @ni: open ntfs attribute to make resident @@ -4197,7 +4237,9 @@ static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_searc * * Reduce the size of a non-resident, open ntfs attribute @na to @newsize bytes. */ -static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsize) +static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, + const s64 newsize, + struct ntfs_inode *locked_ni) { struct ntfs_volume *vol; struct ntfs_attr_search_ctx *ctx; @@ -4205,6 +4247,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz s64 nr_freed_clusters; int err; struct ntfs_inode *base_ni; + bool runlist_locked = locked_ni == ni; ntfs_debug("Inode 0x%llx attr 0x%x new size %lld\n", (unsigned long long)ni->mft_no, ni->type, (long long)newsize); @@ -4250,18 +4293,24 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz * clusters if there is a change. */ if (ntfs_bytes_to_cluster(vol, ni->allocated_size) != first_free_vcn) { - struct ntfs_attr_search_ctx *ctx; + /* + * ntfs_cluster_free() and ntfs_rl_truncate_nolock() + * both require this lock. + */ + if (!runlist_locked) + down_write(&ni->runlist.lock); err = ntfs_attr_map_whole_runlist(ni); if (err) { ntfs_debug("Eeek! ntfs_attr_map_whole_runlist failed.\n"); - return err; + goto unlock_runlist; } ctx = ntfs_attr_get_search_ctx(ni, NULL); if (!ctx) { ntfs_error(vol->sb, "%s: Failed to get search context", __func__); - return -ENOMEM; + err = -ENOMEM; + goto unlock_runlist; } /* Deallocate all clusters starting with the first free one. */ @@ -4269,7 +4318,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz if (nr_freed_clusters < 0) { ntfs_debug("Eeek! Freeing of clusters failed. Aborting...\n"); ntfs_attr_put_search_ctx(ctx); - return (int)nr_freed_clusters; + err = (int)nr_freed_clusters; + goto unlock_runlist; } ntfs_attr_put_search_ctx(ctx); @@ -4282,7 +4332,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz kvfree(ni->runlist.rl); ni->runlist.rl = NULL; ntfs_error(vol->sb, "Eeek! Run list truncation failed.\n"); - return -EIO; + err = -EIO; + goto unlock_runlist; } /* Prepare to mapping pairs update. */ @@ -4298,11 +4349,13 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz VFS_I(base_ni)->i_blocks = ni->allocated_size >> 9; /* Write mapping pairs for new runlist. */ - err = ntfs_attr_update_mapping_pairs(ni, 0 /*first_free_vcn*/); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (err) { ntfs_debug("Eeek! Mapping pairs update failed. Leaving inconstant metadata. Run chkdsk.\n"); - return err; + goto unlock_runlist; } + if (!runlist_locked) + up_write(&ni->runlist.lock); } /* Get the first attribute record. */ @@ -4344,7 +4397,11 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz /* If the attribute now has zero size, make it resident. */ if (!newsize && !NInoEncrypted(ni) && !NInoCompressed(ni)) { + if (!runlist_locked) + down_write(&ni->runlist.lock); err = ntfs_attr_make_resident(ni, ctx); + if (!runlist_locked) + up_write(&ni->runlist.lock); if (err) { /* If couldn't make resident, just continue. */ if (err != -EPERM) @@ -4361,6 +4418,11 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz put_err_out: ntfs_attr_put_search_ctx(ctx); return err; + +unlock_runlist: + if (!runlist_locked) + up_write(&ni->runlist.lock); + return err; } /* @@ -4369,13 +4431,14 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz * @prealloc_size: preallocation size (in bytes) to which to expand the attribute * @newsize: new size (in bytes) to which to expand the attribute * @holes: how to create a hole if expanding - * @need_lock: whether mrec lock is needed or not + * @locked_ni: inode whose runlist lock is already held * * Expand the size of a non-resident, open ntfs attribute @na to @newsize bytes, * by allocating new clusters. */ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsize, - const s64 prealloc_size, unsigned int holes, bool need_lock) + const s64 prealloc_size, unsigned int holes, + struct ntfs_inode *locked_ni) { s64 lcn_seek_from; s64 first_free_vcn; @@ -4573,7 +4636,8 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz /* Prepare to mapping pairs update. */ ni->allocated_size = ntfs_cluster_to_bytes(vol, first_free_vcn); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked( + ni, 0, locked_ni); if (err) { ntfs_debug("Mapping pairs update failed"); goto rollback; @@ -4617,11 +4681,11 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz ntfs_debug("Leaking clusters"); /* Now, truncate the runlist itself. */ - if (need_lock) + if (ni != locked_ni) down_write(&ni->runlist.lock); err2 = ntfs_rl_truncate_nolock(vol, &ni->runlist, ntfs_bytes_to_cluster(vol, org_alloc_size)); - if (need_lock) + if (ni != locked_ni) up_write(&ni->runlist.lock); if (err2) { /* @@ -4635,11 +4699,11 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz /* Prepare to mapping pairs update. */ ni->allocated_size = org_alloc_size; /* Restore mapping pairs. */ - if (need_lock) + if (ni != locked_ni) down_read(&ni->runlist.lock); - if (ntfs_attr_update_mapping_pairs(ni, 0)) + if (__ntfs_attr_update_mapping_pairs(ni, 0, locked_ni, true)) ntfs_error(sb, "Failed to restore old mapping pairs"); - if (need_lock) + if (ni != locked_ni) up_read(&ni->runlist.lock); if (NInoSparse(ni) || NInoCompressed(ni)) { @@ -4744,7 +4808,8 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi mark_mft_record_dirty(ctx->ntfs_ino); ntfs_attr_put_search_ctx(ctx); /* Resize non-resident attribute */ - return ntfs_non_resident_attr_expand(attr_ni, newsize, prealloc_size, holes, true); + return ntfs_non_resident_attr_expand( + attr_ni, newsize, prealloc_size, holes, NULL); } else if (err != -ENOSPC && err != -EPERM) { ntfs_error(sb, "Failed to make attribute non-resident"); goto put_err_out; @@ -4919,13 +4984,14 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, if (NInoNonResident(ni)) { if (newsize > i_size) { down_write(&ni->runlist.lock); - err = ntfs_non_resident_attr_expand(ni, newsize, 0, - NVolDisableSparse(ni->vol) ? - HOLES_NO : HOLES_OK, - false); + err = ntfs_non_resident_attr_expand( + ni, newsize, 0, + NVolDisableSparse(ni->vol) ? + HOLES_NO : HOLES_OK, ni); up_write(&ni->runlist.lock); } else - err = ntfs_non_resident_attr_shrink(ni, newsize); + err = ntfs_non_resident_attr_shrink( + ni, newsize, NULL); } else err = ntfs_resident_attr_resize(ni, newsize, 0, NVolDisableSparse(ni->vol) ? @@ -4934,7 +5000,9 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, return err; } -int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size) +int ntfs_attr_expand_locked(struct ntfs_inode *ni, const s64 newsize, + const s64 prealloc_size, + struct ntfs_inode *locked_ni) { int err = 0; @@ -4964,9 +5032,10 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo if (NInoNonResident(ni)) { if (newsize > ni->data_size || prealloc_size > ni->allocated_size) - err = ntfs_non_resident_attr_expand(ni, newsize, prealloc_size, - NVolDisableSparse(ni->vol) ? - HOLES_NO : HOLES_OK, true); + err = ntfs_non_resident_attr_expand( + ni, newsize, prealloc_size, + NVolDisableSparse(ni->vol) ? + HOLES_NO : HOLES_OK, locked_ni); } else err = ntfs_resident_attr_resize(ni, newsize, prealloc_size, NVolDisableSparse(ni->vol) ? @@ -4977,6 +5046,12 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo return err; } +int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, + const s64 prealloc_size) +{ + return ntfs_attr_expand_locked(ni, newsize, prealloc_size, NULL); +} + /* * ntfs_attr_truncate_i - resize an ntfs attribute * @ni: open ntfs inode to resize @@ -4989,7 +5064,9 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo * newly allocated space is marked as not initialised and no real allocation * on disk is performed. */ -int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes) +int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize, + unsigned int holes, + struct ntfs_inode *locked_ni) { int err; @@ -5023,15 +5100,23 @@ int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int if (NInoNonResident(ni)) { if (newsize > ni->data_size) - err = ntfs_non_resident_attr_expand(ni, newsize, 0, holes, true); + err = ntfs_non_resident_attr_expand( + ni, newsize, 0, holes, locked_ni); else - err = ntfs_non_resident_attr_shrink(ni, newsize); + err = ntfs_non_resident_attr_shrink( + ni, newsize, locked_ni); } else err = ntfs_resident_attr_resize(ni, newsize, 0, holes); ntfs_debug("Return status %d\n", err); return err; } +int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, + unsigned int holes) +{ + return ntfs_attr_truncate_i_locked(ni, newsize, holes, NULL); +} + /* * Resize an attribute, creating a hole if relevant */ @@ -5049,10 +5134,11 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, struct ntfs_volume *vol = ni->vol; struct ntfs_attr_search_ctx *ctx; struct runlist_element *rl, *rlc; + struct runlist_element *old_rl = NULL; s64 vcn = vcn_start, lcn, clu_count; s64 lcn_seek_from = -1; int err = 0; - size_t new_rl_count; + size_t new_rl_count, old_rl_count; err = ntfs_attr_map_whole_runlist(ni); if (err) @@ -5145,6 +5231,19 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, WARN_ON(rlc->vcn != vcn); lcn = rlc->lcn; clu_count = rlc->length; + old_rl_count = ni->runlist.count; + old_rl = kmemdup(ni->runlist.rl, + old_rl_count * sizeof(*old_rl), GFP_NOFS); + if (!old_rl) { + err = -ENOMEM; + if (ntfs_cluster_free_from_rl(vol, rlc)) { + ntfs_error(vol->sb, + "Failed to free cluster allocation after runlist backup failure."); + NVolSetErrors(vol); + } + kvfree(rlc); + goto out; + } rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, &new_rl_count); if (IS_ERR(rl)) { @@ -5168,15 +5267,32 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, if (update_mp) { ntfs_attr_reinit_search_ctx(ctx); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (err) { int err2; err2 = ntfs_cluster_free(ni, vcn, clu_count, ctx); - if (err2 < 0) + if (err2 < 0 || err2 != clu_count) { ntfs_error(vol->sb, - "Failed to free cluster allocation. Leaving inconstant metadata.\n"); - goto out; + "Failed to free cluster allocation. Leaving inconsistent metadata.\n"); + NVolSetErrors(vol); + goto out; + } + + /* + * Restore the runlist before repairing the on-disk + * mapping pairs. + */ + kvfree(ni->runlist.rl); + ni->runlist.rl = old_rl; + ni->runlist.count = old_rl_count; + old_rl = NULL; + if (ntfs_attr_update_mapping_pairs_locked( + ni, 0, ni)) { + ntfs_error(vol->sb, + "Failed to restore mapping pairs after allocation rollback.\n"); + NVolSetErrors(vol); + } } } else { VFS_I(ni)->i_blocks += clu_count << (vol->cluster_size_bits - 9); @@ -5188,6 +5304,7 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start, *lcn_count = clu_count; *balloc = true; out: + kvfree(old_rl); ntfs_attr_put_search_ctx(ctx); return err; } @@ -5431,7 +5548,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6 ni->data_size += ntfs_cluster_to_bytes(vol, len); if (ntfs_cluster_to_bytes(vol, start_vcn) < ni->initialized_size) ni->initialized_size += ntfs_cluster_to_bytes(vol, len); - ret = ntfs_attr_update_mapping_pairs(ni, 0); + ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); up_write(&ni->runlist.lock); if (ret) return ret; @@ -5516,7 +5633,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn, } if (ni->allocated_size > 0) { - ret = ntfs_attr_update_mapping_pairs(ni, 0); + ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (ret) { up_write(&ni->runlist.lock); goto out_rl; @@ -5594,7 +5711,7 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 ni->runlist.rl = rl; ni->runlist.count = new_rl_count; - ret = ntfs_attr_update_mapping_pairs(ni, 0); + ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); up_write(&ni->runlist.lock); if (ret) { kvfree(punch_rl); @@ -5770,7 +5887,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo if (NInoRunlistDirty(ni)) { mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); down_write(&ni->runlist.lock); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (err) ntfs_error(ni->vol->sb, "Updating mapping pairs failed"); else diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h index e2224fbfaabe..6b4fa9f57640 100644 --- a/fs/ntfs/attrib.h +++ b/fs/ntfs/attrib.h @@ -112,7 +112,13 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize, const s64 i_size); int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size); +int ntfs_attr_expand_locked(struct ntfs_inode *ni, const s64 newsize, + const s64 prealloc_size, + struct ntfs_inode *locked_ni); int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes); +int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize, + unsigned int holes, + struct ntfs_inode *locked_ni); int ntfs_attr_truncate(struct ntfs_inode *ni, const s64 newsize); int ntfs_attr_rm(struct ntfs_inode *ni); int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name, @@ -133,6 +139,9 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type, __le16 *name, u8 name_len, u8 *val, u32 size, __le16 flags); int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn); +int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni, + s64 from_vcn, + struct ntfs_inode *locked_ni); struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn); /* diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c index 4e60f7e93010..bb191953dcb1 100644 --- a/fs/ntfs/attrlist.c +++ b/fs/ntfs/attrlist.c @@ -68,7 +68,8 @@ int ntfs_attrlist_need(struct ntfs_inode *ni) * legal list size as a reserve. */ static int ntfs_attrlist_repack(struct inode *attr_vi, - struct ntfs_inode *attr_ni, s64 min_alloc_size) + struct ntfs_inode *attr_ni, s64 min_alloc_size, + struct ntfs_inode *locked_ni) { struct ntfs_volume *vol = attr_ni->vol; struct runlist_element *old_rl, *new_rl; @@ -78,10 +79,12 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, size_t old_rl_count, new_rl_count; unsigned long flags; int err, restore_err; - if (attr_ni->mft_no != FILE_MFT || !NInoNonResident(attr_ni) || min_alloc_size < 0) return -EINVAL; + /* The buffered I/O below can reacquire the attribute runlist lock. */ + if (attr_ni == locked_ni) + return -ENOSPC; err = ntfs_attr_map_whole_runlist(attr_ni); if (err) @@ -131,7 +134,6 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, err = -ENOSPC; goto out_free_data; } - old_rl = attr_ni->runlist.rl; old_rl_count = attr_ni->runlist.count; down_write(&attr_ni->runlist.lock); @@ -152,7 +154,7 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, } } - err = ntfs_attr_update_mapping_pairs(attr_ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(attr_ni, 0, locked_ni); if (err) goto restore_old_runlist; @@ -177,7 +179,8 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, attr_ni->allocated_size = old_alloc_size; write_unlock_irqrestore(&attr_ni->size_lock, flags); - restore_err = ntfs_attr_update_mapping_pairs(attr_ni, 0); + restore_err = ntfs_attr_update_mapping_pairs_locked( + attr_ni, 0, locked_ni); if (restore_err) { ntfs_error(vol->sb, "Failed to restore ATTRIBUTE_LIST mapping pairs (%d)", restore_err); @@ -193,7 +196,8 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, return err; } -int ntfs_attrlist_update(struct ntfs_inode *base_ni) +int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni, + struct ntfs_inode *locked_ni) { struct inode *attr_vi; struct ntfs_inode *attr_ni; @@ -215,20 +219,27 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) return err; } attr_ni = NTFS_I(attr_vi); + /* Truncation and page-cache writes can reacquire this runlist lock. */ + if (attr_ni == locked_ni) { + iput(attr_vi); + return -ENOSPC; + } - err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO); + err = ntfs_attr_truncate_i_locked( + attr_ni, base_ni->attr_list_size, HOLES_NO, locked_ni); if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni)) { retry_err = ntfs_attrlist_repack(attr_vi, attr_ni, - base_ni->attr_list_size); + base_ni->attr_list_size, locked_ni); if (retry_err) { ntfs_error(base_ni->vol->sb, "Failed to repack attribute list"); iput(attr_vi); return retry_err; } - retry_err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, - HOLES_NO); + retry_err = ntfs_attr_truncate_i_locked( + attr_ni, base_ni->attr_list_size, + HOLES_NO, locked_ni); if (retry_err) { ntfs_error(base_ni->vol->sb, "Failed to resize attribute list after repack"); @@ -252,11 +263,13 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) */ if (base_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni) && attr_ni->allocated_size < NTFS_MAX_ATTR_LIST_SIZE) { - retry_err = ntfs_attr_expand(attr_ni, base_ni->attr_list_size, - NTFS_MAX_ATTR_LIST_SIZE); + retry_err = ntfs_attr_expand_locked( + attr_ni, base_ni->attr_list_size, + NTFS_MAX_ATTR_LIST_SIZE, locked_ni); if (retry_err == -ENOSPC) { - retry_err = ntfs_attrlist_repack(attr_vi, attr_ni, - NTFS_MAX_ATTR_LIST_SIZE); + retry_err = ntfs_attrlist_repack( + attr_vi, attr_ni, + NTFS_MAX_ATTR_LIST_SIZE, locked_ni); if (retry_err == -ENOSPC) retry_err = 0; } @@ -289,6 +302,11 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) return 0; } +int ntfs_attrlist_update(struct ntfs_inode *base_ni) +{ + return ntfs_attrlist_update_locked(base_ni, NULL); +} + /* * ntfs_attrlist_entry_add - add an attribute list attribute entry * @ni: opened ntfs inode, which contains that attribute diff --git a/fs/ntfs/attrlist.h b/fs/ntfs/attrlist.h index 1892a3934d3a..10cc2cc8e208 100644 --- a/fs/ntfs/attrlist.h +++ b/fs/ntfs/attrlist.h @@ -16,5 +16,7 @@ int ntfs_attrlist_need(struct ntfs_inode *ni); int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr); int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx); int ntfs_attrlist_update(struct ntfs_inode *base_ni); +int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni, + struct ntfs_inode *locked_ni); #endif /* defined _NTFS_ATTRLIST_H */ diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c index 197d8607fc63..6927d115d6af 100644 --- a/fs/ntfs/compress.c +++ b/fs/ntfs/compress.c @@ -1450,7 +1450,7 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages, ni->runlist.rl = rl; rlc = NULL; - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); up_write(&ni->runlist.lock); if (err) err = -EIO; diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 8164326b7812..007d1614b9ac 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -111,7 +111,8 @@ static int ntfs_trim_prealloc(struct inode *vi) ntfs_error(vol->sb, "Preallocated block rollback failed"); } else { ni->allocated_size = ntfs_cluster_to_bytes(vol, vcn_tr); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked( + ni, 0, ni); if (err) ntfs_error(vol->sb, "Failed to rollback mapping pairs for prealloc"); diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 332825db477a..5d9d48135ecd 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -2772,7 +2772,7 @@ int __ntfs_write_inode(struct inode *vi, int sync) if (NInoNonResident(ni) && NInoRunlistDirty(ni)) { down_write(&ni->runlist.lock); - err = ntfs_attr_update_mapping_pairs(ni, 0); + err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni); if (!err) NInoClearRunlistDirty(ni); up_write(&ni->runlist.lock); diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index f0656d0bbeeb..5b9723abb10f 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -1131,7 +1131,8 @@ static s64 mft_bitmap_alloc_free_rec(struct ntfs_volume *vol, return ll; } -static int ntfs_mft_attr_extend(struct ntfs_inode *ni) +static int ntfs_mft_attr_extend(struct ntfs_inode *ni, + struct ntfs_inode *locked_ni) { int ret = 0; struct ntfs_inode *base_ni; @@ -1152,7 +1153,7 @@ static int ntfs_mft_attr_extend(struct ntfs_inode *ni) } } - ret = ntfs_attr_update_mapping_pairs(ni, 0); + ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, locked_ni); if (ret) pr_err("MP update failed\n"); @@ -1340,7 +1341,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)); if (unlikely(ret)) { - ret = ntfs_mft_attr_extend(mftbmp_ni); + ret = ntfs_mft_attr_extend(mftbmp_ni, mftbmp_ni); if (!ret) goto extended_ok; if (ret != -EAGAIN) @@ -1451,7 +1452,9 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol) NVolSetErrors(vol); } mark_mft_record_dirty(ctx->ntfs_ino); - } else if (status.mp_extended && ntfs_attr_update_mapping_pairs(mftbmp_ni, 0)) { + } else if (status.mp_extended && + ntfs_attr_update_mapping_pairs_locked(mftbmp_ni, 0, + mftbmp_ni)) { ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es); NVolSetErrors(vol); } @@ -1776,7 +1779,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol) ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size + le16_to_cpu(a->data.non_resident.mapping_pairs_offset)); if (unlikely(ret)) { - ret = ntfs_mft_attr_extend(mft_ni); + ret = ntfs_mft_attr_extend(mft_ni, NULL); if (!ret) goto extended_ok; if (ret != -EAGAIN) From 1923eeffa63edeff427d76fc302bc5eb835771ce Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 7 Sep 2026 13:34:01 +0900 Subject: [PATCH 1566/2366] ntfs: propagate folio errors Return the error from __filemap_get_folio() instead of replacing it with -ENOMEM. Fixes: af0db57d4293 ("ntfs: update inode operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 5d9d48135ecd..0a0b7c5547f7 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -3713,7 +3713,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi, FGP_CREAT | FGP_LOCK, mapping_gfp_mask(mapping)); if (IS_ERR(folio)) { - ret = -ENOMEM; + ret = PTR_ERR(folio); break; } } else { From 8c5dc7587fdd45f957af81a9adc1e16863f300fc Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 7 Sep 2026 13:34:25 +0900 Subject: [PATCH 1567/2366] ntfs: ignore interrupted inode reads as corruption Do not mark the volume in error or report an inode as corrupt when reading it was interrupted by a signal. -EINTR and -ERESTARTSYS indicate a transient read failure rather than on-disk NTFS corruption. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 21 +++++++++++++-------- fs/ntfs/mft.c | 3 ++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 0a0b7c5547f7..210adf589319 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -1241,7 +1241,8 @@ static int ntfs_read_locked_inode(struct inode *vi) if (m) unmap_mft_record(ni); err_out: - if (err != -EOPNOTSUPP && err != -ENOMEM && vol_err == true) { + if (err != -EOPNOTSUPP && err != -ENOMEM && + err != -EINTR && err != -ERESTARTSYS && vol_err == true) { ntfs_error(vol->sb, "Failed with error code %i. Marking corrupt inode 0x%llx as bad. Run chkdsk.", err, ni->mft_no); @@ -1467,12 +1468,13 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi) ntfs_attr_put_search_ctx(ctx); unmap_mft_record(base_ni); err_out: - if (err != -ENOENT) + if (err != -ENOENT && err != -EINTR && err != -ERESTARTSYS) ntfs_error(vol->sb, "Failed with error code %i while reading attribute inode (mft_no 0x%llx, type 0x%x, name_len %i). Marking corrupt inode and base inode 0x%llx as bad. Run chkdsk.", err, ni->mft_no, ni->type, ni->name_len, base_ni->mft_no); - if (err != -ENOENT && err != -ENOMEM) + if (err != -ENOENT && err != -ENOMEM && + err != -EINTR && err != -ERESTARTSYS) NVolSetErrors(vol); return err; } @@ -1676,8 +1678,9 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi) /* Get the index bitmap attribute inode. */ bvi = ntfs_attr_iget(base_vi, AT_BITMAP, ni->name, ni->name_len); if (IS_ERR(bvi)) { - ntfs_error(vi->i_sb, "Failed to get bitmap attribute."); err = PTR_ERR(bvi); + if (err != -EINTR && err != -ERESTARTSYS) + ntfs_error(vi->i_sb, "Failed to get bitmap attribute."); goto unm_err_out; } bni = NTFS_I(bvi); @@ -1721,10 +1724,12 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi) if (m) unmap_mft_record(base_ni); err_out: - ntfs_error(vi->i_sb, - "Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.", - err, ni->mft_no, ni->name_len); - if (err != -EOPNOTSUPP && err != -ENOMEM) + if (err != -EINTR && err != -ERESTARTSYS) + ntfs_error(vi->i_sb, + "Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.", + err, ni->mft_no, ni->name_len); + if (err != -EOPNOTSUPP && err != -ENOMEM && + err != -EINTR && err != -ERESTARTSYS) NVolSetErrors(vol); return err; } diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 5b9723abb10f..6d5e56378afd 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -213,7 +213,8 @@ struct mft_record *map_mft_record(struct ntfs_inode *ni) return m; atomic_dec(&ni->count); - ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m)); + if (PTR_ERR(m) != -EINTR && PTR_ERR(m) != -ERESTARTSYS) + ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m)); return m; } From fc440366c47000b768d013e347f60e81d60328ca Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 7 Sep 2026 13:34:44 +0900 Subject: [PATCH 1568/2366] ntfs: discard inodes that fail initialization Discard a newly allocated normal, attribute, or index inode when initialization fails. Keeping an incompletely initialized inode in the inode cache can expose stale sequence data to later directory lookups. Fixes: d7aa04984f12 ("ntfs: return errors from inode initialization") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 210adf589319..b1f9a72ea963 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -170,17 +170,18 @@ struct inode *ntfs_iget(struct super_block *sb, u64 mft_no) /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_inode(vi); - unlock_new_inode(vi); + if (err) + discard_new_inode(vi); + else + unlock_new_inode(vi); } /* * There is no point in keeping bad inodes around. This also * simplifies things in that we never need to check for bad inodes * elsewhere. */ - if (unlikely(err)) { - iput(vi); + if (unlikely(err)) vi = ERR_PTR(err); - } return vi; } @@ -231,17 +232,18 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type, /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_attr_inode(base_vi, vi); - unlock_new_inode(vi); + if (err) + discard_new_inode(vi); + else + unlock_new_inode(vi); } /* * There is no point in keeping bad attribute inodes around. This also * simplifies things in that we never need to check for bad attribute * inodes elsewhere. */ - if (unlikely(err)) { - iput(vi); + if (unlikely(err)) vi = ERR_PTR(err); - } return vi; } @@ -286,17 +288,18 @@ struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name, /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_index_inode(base_vi, vi); - unlock_new_inode(vi); + if (err) + discard_new_inode(vi); + else + unlock_new_inode(vi); } /* * There is no point in keeping bad index inodes around. This also * simplifies things in that we never need to check for bad index * inodes elsewhere. */ - if (unlikely(err)) { - iput(vi); + if (unlikely(err)) vi = ERR_PTR(err); - } return vi; } From 0c32a42fd96a0e7c06c8a648a6dd8f1ec0bf643b Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 7 Sep 2026 13:38:17 +0900 Subject: [PATCH 1569/2366] ntfs: unhash failed inode reads Remove a newly allocated inode from the inode hash before discarding it. NTFS may set i_nlink before a later initialization step fails, in which case iput alone can retain the incomplete inode in the cache. Fixes: bf6be898fbc5 ("ntfs: discard inodes that fail initialization") Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fs/ntfs/inode.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index b1f9a72ea963..a777de8a80c7 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -170,9 +170,10 @@ struct inode *ntfs_iget(struct super_block *sb, u64 mft_no) /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_inode(vi); - if (err) + if (err) { + remove_inode_hash(vi); discard_new_inode(vi); - else + } else unlock_new_inode(vi); } /* @@ -232,9 +233,10 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type, /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_attr_inode(base_vi, vi); - if (err) + if (err) { + remove_inode_hash(vi); discard_new_inode(vi); - else + } else unlock_new_inode(vi); } /* @@ -288,9 +290,10 @@ struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name, /* If this is a freshly allocated inode, need to read it now. */ if (inode_state_read_once(vi) & I_NEW) { err = ntfs_read_locked_index_inode(base_vi, vi); - if (err) + if (err) { + remove_inode_hash(vi); discard_new_inode(vi); - else + } else unlock_new_inode(vi); } /* From 229e8188307b9724cc676a49e9600c4acd24b571 Mon Sep 17 00:00:00 2001 From: Zhu Tianhao Date: Tue, 8 Sep 2026 05:41:00 +0900 Subject: [PATCH 1570/2366] ntfs: fix $MFTMirr write offset when it spans multiple folios When commit 115380f9a2f9 ("ntfs: update mft operations") refactored the MFT code to use folios, it assumed $MFTMirr is allocated contiguously, which is indeed what mkfs arranges. However, the folio rewrite kept a leftover from the old buffer-head/runlist based implementation: vol->cluster_size_mask is still applied to the mirror write offset. In fact it is no longer needed -- and in some configurations it is actively wrong. The bug triggers whenever the four mirror records do not fit in a single folio, i.e. when mft_record_size exceeds PAGE_SIZE / 4 (for example mft_record_size > 1KiB on 4KiB pages). For example, on the built-in 4Kn SSD (Apple SSD AP0256J) of a MacBookPro14,1 with 4096-byte MFT records, mirror record 3 is still written to the location of record 0. $MFTMirr therefore gets out of sync with $MFT and the following warning is printed on remount: ntfs: (device nvme0n1p3): check_mft_mirror(): $MFT and $MFTMirr record 0 do not match. Run chkdsk. Fix it by always adding the folio offset (folio->index << PAGE_SHIFT) to the base LCN address instead of applying the cluster_size_mask truncation. This is the standard file-offset calculation and is correct for every combination of cluster size, page size and MFT record size, provided $MFTMirr data is contiguous from mftmirr_lcn (which mkfs always arranges). Tested on the volume above: before the change mirror records 1-3 are all written to record 0's sector; after the change the write-back probe reports: ntfs: NTFSDBG pre mft_no=0x3 folio_idx=0x3 ofs=0x0 mftmirr_lcn=0x2540c5 clu_bits=12 rec_bits=12 PAGE_SHIFT=12 sect=0x12a0640 ntfs: NTFSDBG pre mft_no=0x0 folio_idx=0x0 ofs=0x0 mftmirr_lcn=0x2540c5 clu_bits=12 rec_bits=12 PAGE_SHIFT=12 sect=0x12a0628 which matches the expected sectors computed as: (NTFS_CLU_TO_B(mftmirr_lcn) + (folio->index << PAGE_SHIFT)) >> SECTOR_SHIFT record 0: (0x2540c5 << 12) + 0x0000 = 0x2540c5000 >> 9 = 0x12a0628 record 3: (0x2540c5 << 12) + 0x3000 = 0x2540c8000 >> 9 = 0x12a0640 A 13 MB file write succeeds on this volume and the file is byte-for-byte identical after a umount/mount cycle. Fixes: 115380f9a2f9 ("ntfs: update mft operations") Assisted-by: UOS-AI Assisted-by: CodeBuddy:Hy4 preview Signed-off-by: Zhu Tianhao Reviewed-by: Baolin Liu Signed-off-by: Namjae Jeon --- fs/ntfs/mft.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 6d5e56378afd..a09496622c7a 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -463,7 +463,7 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, { u8 *kmirr; struct folio *folio; - unsigned int folio_ofs, lcn_folio_off = 0; + unsigned int folio_ofs; int err = 0; struct bio *bio; @@ -493,15 +493,11 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, memcpy(kmirr, m, vol->mft_record_size); kunmap_local(kmirr); - if (vol->cluster_size_bits > PAGE_SHIFT) { - lcn_folio_off = folio->index << PAGE_SHIFT; - lcn_folio_off &= vol->cluster_size_mask; - } - bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO); bio->bi_iter.bi_sector = ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) + - lcn_folio_off + folio_ofs); + ((u64)folio->index << PAGE_SHIFT) + + folio_ofs); if (bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs)) err = submit_bio_wait(bio); From 76a986c980bb502c7688d605ac7a67fd257a9a1b Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Sat, 12 Sep 2026 13:05:30 -0700 Subject: [PATCH 1571/2366] ALSA: usb-audio: Clamp implicit feedback packet count to URB capacity data_ep_set_params() allocates each data URB for exactly u->packets isochronous frames, so urb->iso_frame_desc[] has u->packets slots and ctx->packets is the driver's only record of that limit. For an implicit feedback sink, snd_usb_queue_pending_output_urbs() overwrites it with the sync source's packet count, which is calculated independently from the capture endpoint's parameters. When that count is larger, prepare_playback_urb() and prepare_silent_urb() can write iso_frame_desc[] past the allocation; their existing bounds limit payload bytes, not the descriptor index. The reproducer uses a high-speed UAC2 device declaring bInterval 1 for implicit feedback capture (8 packets) and bInterval 4 for playback (1 packet). On the first capture completion after the stream starts, it accesses seven descriptors spanning 112 bytes beyond the one-packet URB: BUG: KASAN: slab-out-of-bounds in prepare_playback_urb (sound/usb/pcm.c:1560) Write of size 4 at addr ffff88801e696ad0 by task vhci_rx/178 prepare_playback_urb (sound/usb/pcm.c:1560) prepare_outbound_urb (sound/usb/endpoint.c:340) snd_usb_queue_pending_output_urbs (sound/usb/endpoint.c:501) snd_complete_urb (sound/usb/endpoint.c:1834) __usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1657) usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1741) vhci_rx_loop (drivers/usb/usbip/vhci_rx.c:107) kthread (kernel/kthread.c:436) The buggy address belongs to the object at ffff88801e696a00 which belongs to the cache kmalloc-256 of size 256 The buggy address is located 0 bytes to the right of allocated 208-byte region [ffff88801e696a00, ffff88801e696ad0) Record the allocated packet count per endpoint and clamp both the adopted count and the packet-size copy to it. Fold the Format Type II delimiter into urb_packs before the allocation loop so the recorded limit matches every URB. Fixes: cf044e441902 ("ALSA: usb-audio: Update the number of packets properly at receiving") Reported-by: co+8eacd4fa193b1b28@bugs.sh Closes: https://lore.kernel.org/all/22xPn8drvIUtYgVeQnBiNqXuevOTpBAjepLz%40bugs.sh/ Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260912200530.1955491-1-xmei5@asu.edu Signed-off-by: Takashi Iwai --- sound/usb/card.h | 1 + sound/usb/endpoint.c | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/sound/usb/card.h b/sound/usb/card.h index e34d92d576a2..8299ac241c60 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -116,6 +116,7 @@ struct snd_usb_endpoint { unsigned int phase; /* phase accumulator */ unsigned int maxpacksize; /* max packet size in bytes */ unsigned int maxframesize; /* max packet size in frames */ + unsigned int max_urb_packs; /* packets allocated per data URB */ unsigned int max_urb_frames; /* max URB size in frames */ unsigned int curpacksize; /* current packet size in bytes (for capture) */ unsigned int curframesize; /* current packet size in frames (for capture) */ diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 0835943d7b0e..b72ce1e9bfb1 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -492,9 +492,10 @@ int snd_usb_queue_pending_output_urbs(struct snd_usb_endpoint *ep, /* copy over the length information */ if (implicit_fb) { - ctx->packets = packet->packets; + ctx->packets = min_t(int, packet->packets, + ep->max_urb_packs); memcpy(ctx->packet_size, packet->packet_size, - packet->packets * sizeof(packet->packet_size[0])); + ctx->packets * sizeof(packet->packet_size[0])); } /* call the data handler to fill in playback data */ @@ -1242,15 +1243,16 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); } + if (fmt->fmt_type == UAC_FORMAT_TYPE_II) + urb_packs++; /* for transfer delimiter */ + ep->max_urb_packs = urb_packs; + /* allocate and initialize data urbs */ for (i = 0; i < ep->nurbs; i++) { struct snd_urb_ctx *u = &ep->urb[i]; u->index = i; u->ep = ep; u->packets = urb_packs; - - if (fmt->fmt_type == UAC_FORMAT_TYPE_II) - u->packets++; /* for transfer delimiter */ u->buffer_size = maxsize * u->packets; u->urb = usb_alloc_urb(u->packets, GFP_KERNEL); if (!u->urb) From fd95e68df6fe66344161a1329cbe5e5805e7b704 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 12 Sep 2026 18:21:42 +0200 Subject: [PATCH 1572/2366] ALSA: core: Fix potential UAF after asynchronous card release Usually a sound driver releases the resources assigned to the card via snd_card_free(), and it synchronizes with the whole release procedure. However, when the card is released asynchronously via snd_card_free_when_closed() like USB-audio driver, the situation is slightly different; although the snd_card_disconnect() call at the disconnection guarantees that any newer accesses will be gated, the in-flight tasks might be still accessing to the underlying card->dev device even after the disconnection, which would cause a use-after-free in the end, as reported by fuzzers. For addressing the bug above, this patch takes the refcount of card->dev at initialization of the card object, and releases at its destructor. This assures the availability of the card->dev in its whole lifecycle. Reported-by: Farhad Alemi Closes: https://lore.kernel.org/CA+0ovChexj4TrZL_2iG_P0WBEbZc5+73GfB3DkciQi=R8pZOnA@mail.gmail.com Closes: https://lore.kernel.org/CA+0ovCgQUQNN=Z1tJTouiCsDaXR5M-3-SQEGk-cpPXQkM5Xh+w@mail.gmail.com Cc: Link: https://patch.msgid.link/20260912162150.455144-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/init.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/core/init.c b/sound/core/init.c index 2f7f83a7611b..d05bea3c87f5 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -310,7 +310,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, kfree(card); /* manually free here, as no destructor called */ return err; } - card->dev = parent; + card->dev = get_device(parent); card->number = idx; WARN_ON(IS_MODULE(CONFIG_SND) && !module); card->module = module; @@ -603,6 +603,7 @@ static int snd_card_do_free(struct snd_card *card) dev_warn(card->dev, "unable to free card info\n"); /* Not fatal error */ } + put_device(card->dev); if (card->release_completion) complete(card->release_completion); if (!managed) From 6c05d00af307560e6a9f1631d6270d3df5aa2272 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Thu, 10 Sep 2026 23:11:21 -0400 Subject: [PATCH 1573/2366] ALSA: virtio: reset device before deleting virtqueues virtsnd_remove() and virtsnd_freeze() delete the virtqueues before resetting the device. del_vqs() frees the vring backing, but does not provide a generic device quiesce operation. In particular, modern virtio-pci keeps enabled queues active until the device is reset. Reset the device before deleting the virtqueues so it can no longer access the vring memory when that memory is released. This also covers probe failures after DRIVER_OK, which unwind through virtsnd_remove(). Fixes: de3a9980d8c3 ("ALSA: virtio: add virtio sound driver") Fixes: 575483e90a32 ("ALSA: virtio: introduce device suspend/resume support") Cc: stable@vger.kernel.org Signed-off-by: Yuho Choi Link: https://patch.msgid.link/20260911031121.1542502-1-oss.patchbox@gmail.com Signed-off-by: Takashi Iwai --- sound/virtio/virtio_card.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/virtio/virtio_card.c b/sound/virtio/virtio_card.c index 647190f4d5af..6f35276416fe 100644 --- a/sound/virtio/virtio_card.c +++ b/sound/virtio/virtio_card.c @@ -354,8 +354,8 @@ static void virtsnd_remove(struct virtio_device *vdev) if (snd->card) snd_card_free(snd->card); - vdev->config->del_vqs(vdev); virtio_reset_device(vdev); + vdev->config->del_vqs(vdev); for (i = 0; snd->substreams && i < snd->nsubstreams; ++i) { struct virtio_pcm_substream *vss = &snd->substreams[i]; @@ -383,8 +383,8 @@ static int virtsnd_freeze(struct virtio_device *vdev) virtsnd_disable_event_vq(snd); virtsnd_ctl_msg_cancel_all(snd); - vdev->config->del_vqs(vdev); virtio_reset_device(vdev); + vdev->config->del_vqs(vdev); for (i = 0; i < snd->nsubstreams; ++i) cancel_work_sync(&snd->substreams[i].elapsed_period); From 48e97c59a49c9e90270f5e3d221db253b76de5da Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Thu, 16 Jul 2026 21:32:46 +0300 Subject: [PATCH 1574/2366] phy: renesas: rcar-gen3-usb2: Avoid long delay in atomic context The OTG PHY initialization sequence needs to wait for 20 ms at a specific step, as described in commit 72c0339c115b ("phy: renesas: rcar-gen3-usb2: follow the hardware manual procedure"). Commit 55a387ebb921 ("phy: renesas: rcar-gen3-usb2: Lock around hardware registers and driver data") tried to address various problems in the rcar-gen3-usb2 driver and converted the mutex protecting HW register accesses to a spin lock, leaving, however, a long delay in the critical section protected by the spin lock. This may become a problem, especially on RT kernels. To address this, release the spin lock before sleeping for 20 ms as required by the HW manual and reacquire it afterwards. To avoid other threads entering the critical section and configuring the HW while the software is waiting for the OTG initialization to complete, introduce the otg_initializing variable alongside the otg_init_done wait queue. Any other thread trying to configure the HW while the OTG PHY initialization is in progress waits for the wait queue instead of immediately returning errors to PHY users. The IRQs were also disabled while waiting for the OTG PHY initialization to complete, as the interrupt handler may also apply HW settings. The OTG can only be initialized once. It is initialized by the first PHY that calls struct phy_ops::rcar_gen3_phy_usb2_init(). To avoid failures when multiple PHYs call struct phy_ops::rcar_gen3_phy_usb2_init() simultaneously, and the PHY responsible for initializing the OTG either fails or deinit quiqly and another PHY takes over the PHY init role), the code waiting for the channel->otg_init_done wait queue retries up to NUM_OF_PHYS times. Fixes: 55a387ebb921 ("phy: renesas: rcar-gen3-usb2: Lock around hardware registers and driver data") Cc: stable@vger.kernel.org Reported-by: Pavel Machek Closes: https://lore.kernel.org/all/afhkX2Ys2BG1gnqy@duo.ucw.cz Reported-by: Nobuhiro Iwamatsu Closes: https://lore.kernel.org/all/afhkX2Ys2BG1gnqy@duo.ucw.cz Signed-off-by: Claudiu Beznea Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/all/afhkX2Ys2BG1gnqy@duo.ucw.cz Link: https://patch.msgid.link/20260716183246.3183877-1-claudiu.beznea+renesas@tuxon.dev Signed-off-by: Vinod Koul --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 310 +++++++++++++++++++---- 1 file changed, 265 insertions(+), 45 deletions(-) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 9ae9975d3255..b5ba751805aa 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -27,6 +27,7 @@ #include #include #include +#include #include /******* USB2.0 Host registers (original offset is +0x200) *******/ @@ -106,6 +107,13 @@ /* RZ/G2L specific */ #define USB2_LINECTRL1_USB2_IDMON BIT(0) +/* + * The OTG initialization is expected to finish in 20ms. Choose a large enough + * timeout to avoid waiters exit prematurely the waiting section under heavy + * CPU load. + */ +#define USB2_OTG_INIT_TIMEOUT msecs_to_jiffies(120) + #define NUM_OF_PHYS 4 enum rcar_gen3_phy_index { PHY_INDEX_BOTH_HC, @@ -138,12 +146,20 @@ struct rcar_gen3_chan { struct rcar_gen3_phy rphys[NUM_OF_PHYS]; struct regulator *vbus; struct work_struct work; + wait_queue_head_t otg_init_done; spinlock_t lock; /* protects access to hardware and driver data structure. */ enum usb_dr_mode dr_mode; bool extcon_host; bool is_otg_channel; bool uses_otg_pins; bool otg_internal_reg; + /* + * The OTG can be initialized only once and needs to release the spinlock + * and wait for 20 ms due to hardware constraints. If a thread executes + * PHY configuration code while the OTG PHY is waiting for the 20 ms, the + * thread will have to wait for the OTG PHY initialization to complete. + */ + bool otg_initializing; }; struct rcar_gen3_phy_drv_data { @@ -392,26 +408,58 @@ static ssize_t role_store(struct device *dev, struct device_attribute *attr, struct rcar_gen3_chan *ch = dev_get_drvdata(dev); bool is_b_device; enum phy_mode cur_mode, new_mode; + int retries = NUM_OF_PHYS; + unsigned long flags; + int ret = -EIO; - guard(spinlock_irqsave)(&ch->lock); + spin_lock_irqsave(&ch->lock, flags); - if (!ch->is_otg_channel || !rcar_gen3_is_any_otg_rphy_initialized(ch)) - return -EIO; + if (!ch->is_otg_channel) + goto unlock; - if (sysfs_streq(buf, "host")) + while (retries-- && ch->otg_initializing) { + spin_unlock_irqrestore(&ch->lock, flags); + + ret = wait_event_timeout(ch->otg_init_done, !ch->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + goto exit; + + spin_lock_irqsave(&ch->lock, flags); + } + + /* If another thread started a new initialization just return -EBUSY. */ + if (ch->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } + + if (!rcar_gen3_is_any_otg_rphy_initialized(ch)) { + ret = -EIO; + goto unlock; + } + + if (sysfs_streq(buf, "host")) { new_mode = PHY_MODE_USB_HOST; - else if (sysfs_streq(buf, "peripheral")) + } else if (sysfs_streq(buf, "peripheral")) { new_mode = PHY_MODE_USB_DEVICE; - else - return -EINVAL; + } else { + ret = -EINVAL; + goto unlock; + } /* is_b_device: true is B-Device. false is A-Device. */ is_b_device = rcar_gen3_check_id(ch); cur_mode = rcar_gen3_get_phy_mode(ch); /* If current and new mode is the same, this returns the error */ - if (cur_mode == new_mode) - return -EINVAL; + if (cur_mode == new_mode) { + ret = -EINVAL; + goto unlock; + } if (new_mode == PHY_MODE_USB_HOST) { /* And is_host must be false */ if (!is_b_device) /* A-Peripheral */ @@ -425,7 +473,10 @@ static ssize_t role_store(struct device *dev, struct device_attribute *attr, rcar_gen3_init_for_peri(ch); } - return count; +unlock: + spin_unlock_irqrestore(&ch->lock, flags); +exit: + return ret ?: count; } static ssize_t role_show(struct device *dev, struct device_attribute *attr, @@ -441,14 +492,11 @@ static ssize_t role_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RW(role); -static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) +static void rcar_gen3_init_otg_phase0(struct rcar_gen3_chan *ch) { void __iomem *usb2_base = ch->base; u32 val; - if (!ch->is_otg_channel || rcar_gen3_is_any_otg_rphy_initialized(ch)) - return; - /* Should not use functions of read-modify-write a register */ val = readl(usb2_base + USB2_LINECTRL1); val = (val & ~USB2_LINECTRL1_DP_RPD) | USB2_LINECTRL1_DPRPD_EN | @@ -471,7 +519,11 @@ static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); } } - mdelay(20); +} + +static void rcar_gen3_init_otg_phase1(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->base; writel(0xffffffff, usb2_base + USB2_OBINTSTA); writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTEN); @@ -502,6 +554,7 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) void __iomem *usb2_base = ch->base; struct device *dev = ch->dev; irqreturn_t ret = IRQ_NONE; + unsigned long flags; u32 status; pm_runtime_get_noresume(dev); @@ -509,33 +562,102 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) if (pm_runtime_suspended(dev)) goto rpm_put; - scoped_guard(spinlock, &ch->lock) { - status = readl(usb2_base + USB2_OBINTSTA); - if (status & ch->phy_data->obint_enable_bits) { - dev_vdbg(dev, "%s: %08x\n", __func__, status); - if (ch->phy_data->vblvl_ctrl) - writel(USB2_OBINTSTA_CLEAR, usb2_base + USB2_OBINTSTA); - else - writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTSTA); - rcar_gen3_device_recognition(ch); - rcar_gen3_configure_vblvl_ctrl(ch); - ret = IRQ_HANDLED; - } + spin_lock_irqsave(&ch->lock, flags); + + status = readl(usb2_base + USB2_OBINTSTA); + if (status & ch->phy_data->obint_enable_bits) { + dev_vdbg(dev, "%s: %08x\n", __func__, status); + if (ch->phy_data->vblvl_ctrl) + writel(USB2_OBINTSTA_CLEAR, usb2_base + USB2_OBINTSTA); + else + writel(ch->phy_data->obint_enable_bits, usb2_base + USB2_OBINTSTA); + + ret = IRQ_HANDLED; + + /* This should not happen! */ + if (ch->otg_initializing) + goto unlock; + + rcar_gen3_device_recognition(ch); + rcar_gen3_configure_vblvl_ctrl(ch); } +unlock: + spin_unlock_irqrestore(&ch->lock, flags); rpm_put: pm_runtime_put_noidle(dev); return ret; } +static void rcar_gen3_phy_usb2_irqs_mask_all(struct rcar_gen3_chan *channel, + u32 *masked_irqs_bits) +{ + u32 val, bitmask = USB2_INT_ENABLE_UCOM_INTEN; + void __iomem *usb2_base = channel->base; + + for (unsigned int i = 0; i < NUM_OF_PHYS; i++) + bitmask |= channel->rphys[i].int_enable_bits; + + val = readl(usb2_base + USB2_INT_ENABLE); + *masked_irqs_bits = val & bitmask; + val &= ~bitmask; + writel(val, usb2_base + USB2_INT_ENABLE); + + /* + * Don't report channel->phy_data->obint_enable_bits IRQs. These are + * unmasked anyway in rcar_gen3_init_otg_phase1(). + */ + val = readl(usb2_base + USB2_OBINTEN); + val &= ~channel->phy_data->obint_enable_bits; + writel(val, usb2_base + USB2_OBINTEN); +} + +static void rcar_gen3_phy_usb2_irqs_unmask(struct rcar_gen3_chan *channel, + u32 irqs_bits) +{ + u32 val, bitmask = USB2_INT_ENABLE_UCOM_INTEN; + void __iomem *usb2_base = channel->base; + + for (unsigned int i = 0; i < NUM_OF_PHYS; i++) + bitmask |= channel->rphys[i].int_enable_bits; + + val = readl(usb2_base + USB2_INT_ENABLE); + val &= ~bitmask; + val |= irqs_bits; + writel(val, usb2_base + USB2_INT_ENABLE); +} + static int rcar_gen3_phy_usb2_init(struct phy *p) { struct rcar_gen3_phy *rphy = phy_get_drvdata(p); struct rcar_gen3_chan *channel = rphy->ch; void __iomem *usb2_base = channel->base; + int retries = NUM_OF_PHYS; + unsigned long flags; u32 val; + int ret; - guard(spinlock_irqsave)(&channel->lock); + spin_lock_irqsave(&channel->lock, flags); + + while (retries-- && channel->otg_initializing) { + spin_unlock_irqrestore(&channel->lock, flags); + + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + return ret; + + spin_lock_irqsave(&channel->lock, flags); + } + + /* If another thread started a new initialization just return -EBUSY. */ + if (channel->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } /* Initialize USB2 part */ val = readl(usb2_base + USB2_INT_ENABLE); @@ -548,8 +670,23 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) } /* Initialize otg part (only if we initialize a PHY with IRQs). */ - if (rphy->int_enable_bits) - rcar_gen3_init_otg(channel); + if (rphy->int_enable_bits && channel->is_otg_channel && + !rcar_gen3_is_any_otg_rphy_initialized(channel)) { + u32 masked_irq_bits = 0; + + rcar_gen3_init_otg_phase0(channel); + rcar_gen3_phy_usb2_irqs_mask_all(channel, &masked_irq_bits); + channel->otg_initializing = true; + spin_unlock_irqrestore(&channel->lock, flags); + + fsleep(20000); + + spin_lock_irqsave(&channel->lock, flags); + rcar_gen3_phy_usb2_irqs_unmask(channel, masked_irq_bits); + rcar_gen3_init_otg_phase1(channel); + channel->otg_initializing = false; + wake_up_all(&channel->otg_init_done); + } if (channel->phy_data->vblvl_ctrl) { /* SIDDQ mode release */ @@ -568,7 +705,10 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) rphy->initialized = true; - return 0; +unlock: + spin_unlock_irqrestore(&channel->lock, flags); + + return ret; } static int rcar_gen3_phy_usb2_exit(struct phy *p) @@ -576,9 +716,32 @@ static int rcar_gen3_phy_usb2_exit(struct phy *p) struct rcar_gen3_phy *rphy = phy_get_drvdata(p); struct rcar_gen3_chan *channel = rphy->ch; void __iomem *usb2_base = channel->base; + int retries = NUM_OF_PHYS; + unsigned long flags; u32 val; + int ret; - guard(spinlock_irqsave)(&channel->lock); + spin_lock_irqsave(&channel->lock, flags); + + while (retries-- && channel->otg_initializing) { + spin_unlock_irqrestore(&channel->lock, flags); + + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + return ret; + + spin_lock_irqsave(&channel->lock, flags); + } + + /* If another thread started a new initialization just return -EBUSY. */ + if (channel->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } rphy->initialized = false; @@ -588,7 +751,9 @@ static int rcar_gen3_phy_usb2_exit(struct phy *p) val &= ~USB2_INT_ENABLE_UCOM_INTEN; writel(val, usb2_base + USB2_INT_ENABLE); - return 0; +unlock: + spin_unlock_irqrestore(&channel->lock, flags); + return ret; } static int rcar_gen3_phy_usb2_power_on(struct phy *p) @@ -596,8 +761,10 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) struct rcar_gen3_phy *rphy = phy_get_drvdata(p); struct rcar_gen3_chan *channel = rphy->ch; void __iomem *usb2_base = channel->base; + int retries = NUM_OF_PHYS; + unsigned long flags; u32 val; - int ret = 0; + int ret; if (channel->vbus && !channel->otg_internal_reg) { ret = regulator_enable(channel->vbus); @@ -605,7 +772,27 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) return ret; } - guard(spinlock_irqsave)(&channel->lock); + spin_lock_irqsave(&channel->lock, flags); + + while (retries-- && channel->otg_initializing) { + spin_unlock_irqrestore(&channel->lock, flags); + + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + goto disable_regulator; + + spin_lock_irqsave(&channel->lock, flags); + } + + /* If another thread started a new initialization just return -EBUSY. */ + if (channel->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } if (!rcar_gen3_are_all_rphys_power_off(channel)) goto out; @@ -620,27 +807,59 @@ static int rcar_gen3_phy_usb2_power_on(struct phy *p) /* The powered flag should be set for any other phys anyway */ rphy->powered = true; - return 0; +unlock: + spin_unlock_irqrestore(&channel->lock, flags); + +disable_regulator: + if (ret && channel->vbus && !channel->otg_internal_reg) + regulator_disable(channel->vbus); + + return ret; } static int rcar_gen3_phy_usb2_power_off(struct phy *p) { struct rcar_gen3_phy *rphy = phy_get_drvdata(p); struct rcar_gen3_chan *channel = rphy->ch; - int ret = 0; + int retries = NUM_OF_PHYS; + unsigned long flags; + int ret; - scoped_guard(spinlock_irqsave, &channel->lock) { - rphy->powered = false; + spin_lock_irqsave(&channel->lock, flags); - if (rcar_gen3_are_all_rphys_power_off(channel)) { - u32 val = readl(channel->base + USB2_USBCTR); + while (retries-- && channel->otg_initializing) { + spin_unlock_irqrestore(&channel->lock, flags); - val |= USB2_USBCTR_PLL_RST; - writel(val, channel->base + USB2_USBCTR); - } + ret = wait_event_timeout(channel->otg_init_done, !channel->otg_initializing, + USB2_OTG_INIT_TIMEOUT); + ret = ret ? 0 : -ETIMEDOUT; + if (ret && !retries) + return ret; + + spin_lock_irqsave(&channel->lock, flags); } - if (channel->vbus && !channel->otg_internal_reg) + /* If another thread started a new initialization just return -EBUSY. */ + if (channel->otg_initializing) { + ret = -EBUSY; + goto unlock; + } else { + ret = 0; + } + + rphy->powered = false; + + if (rcar_gen3_are_all_rphys_power_off(channel)) { + u32 val = readl(channel->base + USB2_USBCTR); + + val |= USB2_USBCTR_PLL_RST; + writel(val, channel->base + USB2_USBCTR); + } + +unlock: + spin_unlock_irqrestore(&channel->lock, flags); + + if (!ret && channel->vbus && !channel->otg_internal_reg) ret = regulator_disable(channel->vbus); return ret; @@ -1022,6 +1241,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) return ret; spin_lock_init(&channel->lock); + init_waitqueue_head(&channel->otg_init_done); for (i = 0; i < NUM_OF_PHYS; i++) { channel->rphys[i].phy = devm_phy_create(dev, NULL, channel->phy_data->phy_usb2_ops); From de7f29a1fe1dc2864d8a47f8c39d508442cae167 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 11 Sep 2026 09:40:14 +0200 Subject: [PATCH 1575/2366] phy: mediatek: phy-mtk-hdmi-mt8195: Fix PLL calc divisor overflow When trying to calculate a PLL rate for target display resolutions above 2560x1440, 24bpp, 30Hz, the pixel clock value will be more than 32-bits long but the division to finally calculate the digital clock divider is being done with div_u64(), which expects a 32bit unsigned divisor. Fix the overflow by using div64_u64() instead. Fixes: 9d9ff3d2a4a5 ("phy: mediatek: hdmi: mt8195: fix wrong pll calculus") Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260911074015.9994-2-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c index 1426a2db984d..e6ee8e080022 100644 --- a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c +++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c @@ -290,7 +290,7 @@ static int mtk_hdmi_pll_calc(struct mtk_hdmi_phy *hdmi_phy, struct clk_hw *hw, posdiv2 = 1; /* Digital clk divider, max /32 */ - digital_div = div_u64(ns_hdmipll_ck, posdiv1 * posdiv2 * pixel_clk); + digital_div = div64_u64(ns_hdmipll_ck, posdiv1 * posdiv2 * pixel_clk); if (!(digital_div <= 32 && digital_div >= 1)) return -EINVAL; From 486a70ef848264dcf9a57f0bb0452848db9537de Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 11 Sep 2026 09:40:15 +0200 Subject: [PATCH 1576/2366] phy: mediatek: phy-mtk-hdmi-mt8195: Fix TMDS clk bit ratio setting The comment in the mtk_phy_tmds_clk_ratio() function clearly and correctly explains that the TMDS ratio has to be 1/10 for data rates under 3.4Gbps, and 1/40 over that. Unfortunately though, the TXC_DIV register setting was wrong, as in value 3 means to divide by 8 and, in order to achieve the in spec 1/40 (tmds) data rate, this has to divide by 4 instead! Add definitions for the TXC_DIV register values clearly explaining the meanings (DIV2, DIV4, DIV8), and program the correct, DIV 4, value to the register in mtk_phy_tmds_clk_ratio(). This fixes out of spec clocking and, with this change, SoCs using the MT8195 class HDMI PHYs can now successfully be configured to output 3840x2160@60Hz over HDMI. Fixes: 45810d486bb4 ("phy: mediatek: add support for phy-mtk-hdmi-mt8195") Reviewed-by: Manivannan Sadhasivam Signed-off-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260911074015.9994-3-angelogioacchino.delregno@collabora.com Signed-off-by: Vinod Koul --- drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c | 2 +- drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c index e6ee8e080022..a4bc1268946d 100644 --- a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c +++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.c @@ -36,7 +36,7 @@ mtk_phy_tmds_clk_ratio(struct mtk_hdmi_phy *hdmi_phy, bool enable) * clock bit ratio 1:40, under 3.4Gbps, clock bit ratio 1:10 */ if (enable) - mtk_phy_update_field(regs + HDMI20_CLK_CFG, REG_TXC_DIV, 3); + mtk_phy_update_field(regs + HDMI20_CLK_CFG, REG_TXC_DIV, VAL_TXC_DIV4); else mtk_phy_clear_bits(regs + HDMI20_CLK_CFG, REG_TXC_DIV); } diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h index e26caaf4d104..58800d7659ca 100644 --- a/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h +++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt8195.h @@ -17,6 +17,9 @@ #define HDMI20_CLK_CFG 0x70 #define REG_TXC_DIV GENMASK(31, 30) +#define VAL_TXC_DIV2 1 +#define VAL_TXC_DIV4 2 +#define VAL_TXC_DIV8 3 #define HDMI_1_CFG_0 0x00 #define RG_HDMITX21_DRV_IBIAS_CLK GENMASK(10, 5) From c7a1c6e8004ab12a9c9bfdcb603f60f9bf4a3cee Mon Sep 17 00:00:00 2001 From: fangqiurong Date: Sat, 12 Sep 2026 21:15:18 +0800 Subject: [PATCH 1577/2366] sched_ext: Close the pre-enable ops error claim window scx_alloc_and_add_sched() publishes ops->priv before scx_root_enable_workfn() switches the state to SCX_ENABLING. An error claimed via scx_bpf_error_bstr() from an associated BPF program in that window is consumed by scx_disable_workfn(), which takes the pre-enable shortcut in scx_root_disable(). The shortcut returns without any teardown and restores SCX_DISABLED with an unconditional scx_set_enable_state() xchg racing the enable workfn's own transition. The enable then completes with the claim consumed: the scheduler stays up but can never be disabled again, and bpf_scx_unreg() frees it while still in use, resulting in a use-after-free. Both WARN_ON_ONCE()s fire back to back: WARNING: kernel/sched/ext/ext.c:7522 at scx_root_enable_workfn+0xeec/0x1be0, CPU#3: scx_enable_help/276 WARNING: kernel/sched/ext/ext.c:6398 at scx_root_disable+0xb50/0xdb8, CPU#0: sched_ext_helpe/664 scx_root_enable_workfn() switches to SCX_ENABLING before the scheduler allocation, so ops->priv is never visible while SCX_DISABLED. The allocation failure path restores SCX_DISABLED. Fixes: 105dcd005be2 ("sched_ext: Introduce scx_prog_sched()") Cc: stable@vger.kernel.org Signed-off-by: fangqiurong Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index adf5993fa597..83999203a63a 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -7516,22 +7516,24 @@ static void scx_root_enable_workfn(struct kthread_work *work) #ifdef CONFIG_EXT_SUB_SCHED cgroup_get(cgrp); #endif + /* + * Transition to ENABLING to arm the disable path. Allocation failure + * still unwinds locally. Full disabling on failure applies only after + * scx_alloc_and_add_sched() succeeds. + */ + WARN_ON_ONCE(scx_set_enable_state(SCX_ENABLING) != SCX_DISABLED); + WARN_ON_ONCE(scx_root); + sch = scx_alloc_and_add_sched(cmd, cgrp, NULL); if (IS_ERR(sch)) { ret = PTR_ERR(sch); + WARN_ON_ONCE(scx_set_enable_state(SCX_DISABLED) != SCX_ENABLING); goto err_free_tid_hash; } if (sch->is_cid_type) static_branch_enable(&__scx_is_cid_type); - /* - * Transition to ENABLING and clear exit info to arm the disable path. - * Failure triggers full disabling from here on. - */ - WARN_ON_ONCE(scx_set_enable_state(SCX_ENABLING) != SCX_DISABLED); - WARN_ON_ONCE(scx_root); - atomic_long_set(&scx_nr_rejected, 0); for_each_possible_cpu(cpu) { From 1e713f9bb2ac583521f06b0eb4e22440b1e3d078 Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Sun, 13 Sep 2026 20:44:46 +0700 Subject: [PATCH 1578/2366] ALSA: pcm: set timer->private_data before registering the PCM timer snd_pcm_timer_init() calls snd_device_register() to link the new struct snd_timer into the global timer list while it still carries hw.c_resolution = snd_pcm_timer_resolution (and hw.start/hw.stop), and only afterwards sets timer->private_data = substream. Once the timer is on the list under register_mutex, a concurrent reader can already reach it through the same mutex and invoke these callbacks. /proc/asound/timers does this via c_resolution(), and snd_timer_open()+snd_timer_start() reach start()/stop() the same way. All three dereference timer->private_data, which for this brief window is NULL, giving a NULL-pointer dereference: substream = timer->private_data; return substream->runtime ? ... // substream is NULL Move the private_data/private_free assignment before snd_device_register() so the timer is never visible on the list without its private_data set. On the snd_device_register() failure path, private_free() (snd_pcm_timer_free()) can now run, but it only does substream->timer = NULL, which is already NULL at that point since substream->timer is set to the new timer just once, after a successful registration -- so the failure path stays safe. Reported-by: syzbot+19da64013c46df87f971@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=19da64013c46df87f971 Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Nguyen Ngoc Thang Link: https://patch.msgid.link/20260913134446.114724-1-ngocthang2710.1999@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_timer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/core/pcm_timer.c b/sound/core/pcm_timer.c index ab0e5bd70f8f..18bedd66435d 100644 --- a/sound/core/pcm_timer.c +++ b/sound/core/pcm_timer.c @@ -111,12 +111,15 @@ void snd_pcm_timer_init(struct snd_pcm_substream *substream) snd_pcm_direction_name(substream->stream), tid.card, tid.device, tid.subdevice); timer->hw = snd_pcm_timer; + /* Set before registering: a concurrent reader can invoke our hw + * callbacks as soon as the timer is on the global list. + */ + timer->private_data = substream; + timer->private_free = snd_pcm_timer_free; if (snd_device_register(timer->card, timer) < 0) { snd_device_free(timer->card, timer); return; } - timer->private_data = substream; - timer->private_free = snd_pcm_timer_free; substream->timer = timer; } From 442ffa742daa65a0e8fe003abe9fbe472366e4de Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Fri, 11 Sep 2026 20:39:35 +0100 Subject: [PATCH 1579/2366] tracing/remotes: Account for ring buffer page header in size calculation trace_buffer_desc_size() and trace_remote_alloc_buffer() undercount the required pages because every ring buffer page contains a header (BUF_PAGE_HDR_SIZE). Account for that header to ensure allocated remote ring buffers aren't smaller than requested by the user. The newly introduced helper __calc_nr_pages_ring_buffer_desc() can return a value that overflows the descriptor nr_pages field (32 bits). Link: https://patch.msgid.link/20260911193937.602202-2-vdonnefort@google.com Fixes: 2e67fabd8b77 ("ring-buffer: Introduce ring-buffer remotes") Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- include/linux/ring_buffer.h | 15 +++++++++++++-- kernel/trace/trace_remote.c | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/include/linux/ring_buffer.h b/include/linux/ring_buffer.h index afc7daa6ee7d..11bffb6a142d 100644 --- a/include/linux/ring_buffer.h +++ b/include/linux/ring_buffer.h @@ -3,8 +3,9 @@ #define _LINUX_RING_BUFFER_H #include -#include #include +#include +#include #include @@ -279,9 +280,19 @@ static inline struct ring_buffer_desc *__first_ring_buffer_desc(struct trace_buf return (struct ring_buffer_desc *)(&desc->__data[0]); } +/* + * Returns the number of pages for a ring_buffer_desc. The caller must ensure it + * does not overflow ring_buffer_desc::nr_page_va. + */ +static inline unsigned long __calc_nr_pages_ring_buffer_desc(size_t size) +{ + /* Takes into account the reader page */ + return max(DIV_ROUND_UP(size, PAGE_SIZE - BUF_PAGE_HDR_SIZE), 2UL) + 1; +} + static inline size_t trace_buffer_desc_size(size_t buffer_size, unsigned int nr_cpus) { - unsigned int nr_pages = max(DIV_ROUND_UP(buffer_size, PAGE_SIZE), 2UL) + 1; + unsigned long nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size); struct ring_buffer_desc *rbdesc; return size_add(offsetof(struct trace_buffer_desc, __data), diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c index 75fa1ffc4c96..c17902e42ef1 100644 --- a/kernel/trace/trace_remote.c +++ b/kernel/trace/trace_remote.c @@ -980,7 +980,7 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, const struct cpumask *cpumask) { size_t min_desc_size = trace_buffer_desc_size(buffer_size, cpumask_weight(cpumask)); - unsigned int nr_pages = max(DIV_ROUND_UP(buffer_size, PAGE_SIZE), 2UL) + 1; + unsigned int nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size); struct ring_buffer_desc *rb_desc; int cpu, ret = -ENOMEM; From d059d8bf2c9b5d563d15e7552d73e17d7535013a Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Fri, 11 Sep 2026 20:39:36 +0100 Subject: [PATCH 1580/2366] tracing/remotes: Catch nr_page_va overflow in ring_buffer_desc sizing The number of pages per remote ring buffer is capped by ring_buffer_desc::nr_page_va (32 bits). A buffer_size large enough to overflow that field would silently allocate a descriptor smaller than what was asked for. Return SIZE_MAX from trace_buffer_desc_size() on nr_page_va overflow. Link: https://patch.msgid.link/20260911193937.602202-3-vdonnefort@google.com Fixes: 2e67fabd8b77 ("ring-buffer: Introduce ring-buffer remotes") Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- include/linux/ring_buffer.h | 4 ++++ kernel/trace/trace_remote.c | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/linux/ring_buffer.h b/include/linux/ring_buffer.h index 11bffb6a142d..eac3e9080c3c 100644 --- a/include/linux/ring_buffer.h +++ b/include/linux/ring_buffer.h @@ -295,6 +295,10 @@ static inline size_t trace_buffer_desc_size(size_t buffer_size, unsigned int nr_ unsigned long nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size); struct ring_buffer_desc *rbdesc; + /* Capped by ring_buffer_desc::nr_page_va */ + if (nr_pages > UINT_MAX) + return SIZE_MAX; + return size_add(offsetof(struct trace_buffer_desc, __data), size_mul(nr_cpus, struct_size(rbdesc, page_va, nr_pages))); } diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c index c17902e42ef1..2d5bc423efca 100644 --- a/kernel/trace/trace_remote.c +++ b/kernel/trace/trace_remote.c @@ -980,9 +980,12 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, const struct cpumask *cpumask) { size_t min_desc_size = trace_buffer_desc_size(buffer_size, cpumask_weight(cpumask)); - unsigned int nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size); struct ring_buffer_desc *rb_desc; int cpu, ret = -ENOMEM; + unsigned int nr_pages; + + if (min_desc_size == SIZE_MAX) + return -E2BIG; if (desc_size < min_desc_size) return -EINVAL; @@ -991,6 +994,7 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size, desc->struct_len = min_desc_size; rb_desc = __first_ring_buffer_desc(desc); + nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size); for_each_cpu(cpu, cpumask) { unsigned int id; From d860c67c051685abb0460b593b193f0f45f4fa92 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 12 Sep 2026 11:39:38 +0100 Subject: [PATCH 1581/2366] ring-buffer: Check resize_disabled before publishing the new subbuf order ring_buffer_subbuf_order_set() stores the new order and only then walks the CPUs, returning -EBUSY if any of them has resizing disabled. A user mapped buffer has resizing disabled, and __rb_map_vma() reads buffer->subbuf_order without buffer->mutex, so an mmap of an already mapped CPU racing the failing order change sizes the mapping with the new order and inserts pages past the sub-buffer into the VMA. Check the CPUs before storing the new order. Cc: stable@vger.kernel.org Fixes: 117c39200d9d ("ring-buffer: Introducing ring-buffer mapping functions") Link: https://patch.msgid.link/20260912103938.1127021-1-devnexen@gmail.com Signed-off-by: David Carlier Signed-off-by: Steven Rostedt --- kernel/trace/ring_buffer.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 9bc8ce8c5676..04bb94c29f58 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -7473,6 +7473,14 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order) old_capacity = rb_subbuf_capacity(buffer); + /* The mmap fast path reads subbuf_order without buffer->mutex. */ + for_each_buffer_cpu(buffer, cpu) { + if (!cpumask_test_cpu(cpu, buffer->cpumask)) + continue; + if (atomic_read(&buffer->buffers[cpu]->resize_disabled)) + return -EBUSY; + } + atomic_inc(&buffer->record_disabled); /* Make sure all commits have finished */ From 1a296bfd3e775e515233f746218824fc7dd5ff16 Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Sun, 16 Aug 2026 23:33:40 +0545 Subject: [PATCH 1582/2366] wifi: mt76: mt7921: skip unknown CLC firmware records Treat an out-of-range CLC index as newer firmware rather than a malformed image. linux-firmware 20260810 ships MT7922 records with idx 3, and rejecting them made mt7921e fail to probe. Keep the record-length checks, and report those as errors so a truncated table is visible instead of a silent retry loop. Fixes: 9417c5818a01 ("wifi: mt76: mt7921: validate CLC firmware records") Reported-by: Mikhail Gavrilov Signed-off-by: Laxman Acharya Padhya Reviewed-by: Junjie Cao Tested-by: Mikhail Gavrilov Signed-off-by: Linus Torvalds --- drivers/net/wireless/mediatek/mt76/mt7921/mcu.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index a118a301564c..40546005c743 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -477,18 +477,23 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) for (offset = 0; offset < len; offset += clc_len) { if (len - offset < sizeof(*clc)) { + dev_err(mdev->dev, "Invalid CLC record\n"); ret = -EINVAL; goto out; } clc = (const struct mt7921_clc *)(clc_base + offset); clc_len = le32_to_cpu(clc->len); - if (clc_len < sizeof(*clc) || clc_len > len - offset || - clc->idx >= ARRAY_SIZE(phy->clc)) { + if (clc_len < sizeof(*clc) || clc_len > len - offset) { + dev_err(mdev->dev, "Invalid CLC record\n"); ret = -EINVAL; goto out; } + /* Newer firmware may add records this driver does not use yet */ + if (clc->idx >= ARRAY_SIZE(phy->clc)) + continue; + /* do not init buf again if chip reset triggered */ if (phy->clc[clc->idx]) continue; From 7825de3f75d184612d77655669a04ea0da252c17 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Tue, 25 Aug 2026 11:17:12 -0700 Subject: [PATCH 1583/2366] wifi: mt76: mt792x: fix NULL dereference in ACPI SAR init during probe Some laptops carry a MediaTek power table in their firmware, and the driver reads it to set a transmit limit for each frequency range. It only fills in the ranges themselves when it registers the device. The startup step that does this existed already, but it never programmed anything. Two recent commits made it run a regulatory update instead, which sets the limits on the way through, long before registration. As a result, on a machine that has the table the driver reads through an empty pointer and the interface never appears: BUG: kernel NULL pointer dereference, address: 0000000000000004 RIP: 0010:mt792x_init_acpi_sar_power Call Trace: mt7921_set_tx_sar_pwr mt7921_mcu_regd_update mt7921_regd_update mt7921_run_firmware mt7921e_mcu_init mt7921_init_work Skip it when the ranges are missing. They are applied again once the device is up, which is where they came from before. Reported-by: Klara Modin Closes: https://lore.kernel.org/linux-wireless/aoyxqHYvSuaBeubf@soda.int.kasm.eu/ Fixes: 9b80bd9cab40 ("wifi: mt76: mt7921: add regulatory wiphy self manager support") Fixes: e9f3f1cc133f ("wifi: mt76: mt7925: add regulatory wiphy self manager support") Signed-off-by: Devin Wittmayer Tested-by: David Gow Tested-by: Klara Modin Signed-off-by: Linus Torvalds --- drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c index 946dd7956e4a..b468051fbe68 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c @@ -323,7 +323,8 @@ int mt792x_init_acpi_sar_power(struct mt792x_phy *phy, bool set_default) const struct cfg80211_sar_capa *capa = phy->mt76->hw->wiphy->sar_capa; int i; - if (!phy->acpisar || !((struct mt792x_acpi_sar *)phy->acpisar)->dyn) + if (!capa || !phy->acpisar || + !((struct mt792x_acpi_sar *)phy->acpisar)->dyn) return 0; /* When ACPI SAR enabled in HW, we should apply rules for .frp From 856c562c94964a74f63c6d5f38a1509a59a2357d Mon Sep 17 00:00:00 2001 From: Sergey Zagursky Date: Wed, 2 Sep 2026 22:15:24 +0100 Subject: [PATCH 1584/2366] media: ipu-bridge: do not use the CVS device lookup for IVSC Since commit c6b1b34b5090 ("media: pci: intel: Add CVS support for IPU bridge driver") the internal camera no longer works on laptops where the sensor sits behind an IVSC, for example a Dell XPS 16 9640 (IPU6, INTC10CF, ov02c10): intel-ipu6 0000:00:05.0: Found supported sensor OVTI02C1:00 intel-ipu6 0000:00:05.0: Connected 1 cameras ivsc_csi intel_vsc-92335fcf-3203-4472-af93-7b4453ac29da: mei-csi probed without device fwnode! No sensor subdevice is registered, the media graph has no sensor entity and userspace finds no camera at all. ipu_bridge_get_ivsc_csi_dev() first looks for the platform device named "intel_vsc" and returns its mei-csi child. That device is created by mei_vsc, which on this machine only appears once the LJCA USB bridge and its SPI controller have probed, about a second after the IPU6 probe that runs the bridge: 07:59:29.297 platform INTC10CF:00 created (ACPI scan) 07:59:41 intel-ipu6 probe -> ipu_bridge_init() 07:59:42.391 platform intel_vsc created (mei_vsc) The commit above added two fallbacks for CVS which match on the ACPI companion alone. They are reached for every entry of ivsc_acpi_ids[], IVSC IDs included. The IVSC ACPI device has two physical nodes: INTC10CF:00/physical_node -> platform/INTC10CF:00 (no driver bound) INTC10CF:00/physical_node1 -> platform/intel_vsc (mei_vsc) so bus_find_device_by_acpi_dev(&platform_bus_type, adev) returns the bare platform device. ipu_bridge_instantiate_ivsc() then attaches the IVSC software node to that device instead of to the mei-csi client, the bridge reports success, and the probe is never retried. mei_csi later probes without a fwnode, the CSI-2 link is never described, and the sensor ACPI device, which has an honoured _DEP on the IVSC device, is never enumerated. Before those fallbacks existed the lookup returned NULL here, the bridge failed with -ENODEV and the probe was retried once the IVSC device had shown up. Skip those fallbacks for IVSC devices, keying on the IVSC IDs rather than the CVS ones: new CVS IDs keep being added, whereas the IVSC list is complete. CVS binds a driver to the ACPI device itself, so matching on the companion stays unambiguous there. Fixes: c6b1b34b5090 ("media: pci: intel: Add CVS support for IPU bridge driver") Link: https://lore.kernel.org/linux-media/20260901194526.6369-1-gvozdoder@gmail.com/ Cc: stable@vger.kernel.org Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Sergey Zagursky Signed-off-by: Linus Torvalds --- drivers/media/pci/intel/ipu-bridge.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 1bb3a3e98d6b..bd64c0400c0d 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -232,6 +232,19 @@ static const struct acpi_device_id ivsc_acpi_ids[] = { { "INTC10FA" }, /* NVL */ }; +/* + * The subset of ivsc_acpi_ids[] which are IVSC, rather than CVS, devices. The + * CVS IDs are deliberately not listed here: new ones keep being added, whereas + * this list is complete. + */ +static const struct acpi_device_id ivsc_only_acpi_ids[] = { + { "INTC1059" }, + { "INTC1095" }, + { "INTC100A" }, + { "INTC10CF" }, + { } +}; + static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev) { unsigned int i; @@ -283,6 +296,17 @@ static struct device *ipu_bridge_get_ivsc_csi_dev(struct acpi_device *adev) return csi_dev; } + /* + * The lookups below match on the ACPI companion alone. That is fine for + * CVS, which binds a driver to that very device, but not for IVSC: there + * the ACPI device also has a driverless platform device, which would be + * returned instead of the mei-csi client. Return NULL for IVSC so that + * the caller fails and the probe is retried once the IVSC device shows + * up. + */ + if (!acpi_match_device_ids(adev, ivsc_only_acpi_ids)) + return NULL; + /* Try to locate CVS device on the I2C bus */ csi_dev = bus_find_device_by_acpi_dev(&i2c_bus_type, adev); if (csi_dev) From c65eae6f61d1778ff7a82e4aae4080e26f486af1 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Fri, 11 Sep 2026 22:38:04 -0300 Subject: [PATCH 1585/2366] smb: client: cancel reconnect work in clean_demultiplex_info() clean_demultiplex_info() cancels server->echo delayed work but not server->reconnect, which can cause a use-after-free when the demultiplex thread exits while a reconnect work is still queued: cifs_demultiplex_thread() cifs_readv_from_socket() cifs_reconnect() __cifs_reconnect() cifs_queue_server_reconn() mod_delayed_work(cifsiod_wq, &server->reconnect, 0) clean_demultiplex_info() cancel_delayed_work_sync(&server->echo) // echo canceled // reconnect NOT canceled kfree_sensitive(server) // server freed ...later, on cifsiod_wq: smb2_reconnect_server() server->srv_count // UAF read of freed server Fix this by canceling server->reconnect delayed work in clean_demultiplex_info() before the server is freed, the same way cifs_put_tcp_session() already does. Reported-by: syzbot+5003556314abc915a71f@syzkaller.appspotmail.com Closes: https://lore.kernel.org/r/6aa4a12d.f81106d8.2ab401.0023.GAE@google.com Fixes: 53e0e11efe92 ("CIFS: Fix a possible memory corruption during reconnect") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: David Howells Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Tom Talpey Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/connect.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index b6e98eb31673..bef710d9a3c3 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1067,6 +1067,7 @@ clean_demultiplex_info(struct TCP_Server_Info *server) spin_unlock(&server->srv_lock); cancel_delayed_work_sync(&server->echo); + cancel_delayed_work_sync(&server->reconnect); spin_lock(&server->srv_lock); server->tcpStatus = CifsExiting; From 5f270f091256da1338c3631083e15d7f83cc05e1 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Wed, 9 Sep 2026 13:52:14 -0300 Subject: [PATCH 1586/2366] smb: client: fix rlist race and missing initialization TCP_Server_Info.rlist is allocated via kzalloc which zeros both ->next and ->prev to NULL instead of pointing to itself, making list_empty() always return false and list_add() dereference a NULL ->prev pointer. Also, cifs_signal_cifsd_for_reconnect() can be called concurrently from multiple cifsd threads, allowing the same server's rlist node to be added twice into the local list, corrupting it. Closes: https://sashiko.dev/#/patchset/20260911204446.1719356-1-pc%40manguebit.org Fixes: df0e03a4fb94 ("smb: client: fix potential deadlock when reconnecting channels") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: David Howells Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Tom Talpey Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/connect.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index bef710d9a3c3..4bd9f9fb93bb 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -174,6 +174,8 @@ cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server, nserver = ses->chans[i].server; if (!nserver) continue; + if (!list_empty(&nserver->rlist)) + continue; nserver->srv_count++; list_add(&nserver->rlist, &reco); } @@ -182,11 +184,15 @@ cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server, } } + spin_lock(&cifs_tcp_ses_lock); list_for_each_entry_safe(server, nserver, &reco, rlist) { list_del_init(&server->rlist); set_need_reco(server); + spin_unlock(&cifs_tcp_ses_lock); cifs_put_tcp_session(server, 0); + spin_lock(&cifs_tcp_ses_lock); } + spin_unlock(&cifs_tcp_ses_lock); } /* @@ -1824,6 +1830,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, spin_lock_init(&tcp_ses->mid_counter_lock); INIT_LIST_HEAD(&tcp_ses->tcp_ses_list); INIT_LIST_HEAD(&tcp_ses->smb_ses_list); + INIT_LIST_HEAD(&tcp_ses->rlist); INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request); INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server); mutex_init(&tcp_ses->reconnect_mutex); From e75c96157d45e498970158c8f7373d90102e33b9 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sat, 12 Sep 2026 14:20:08 -0300 Subject: [PATCH 1587/2366] smb: client: fix smbd_connection leak on cifs_get_tcp_session() error When an RDMA connection is successfully established via smbd_get_connection() but cifs_get_tcp_session() later fails (e.g. kthread_create() returns an error), the error path frees tcp_ses without first destroying the smbd_connection. Fix this by calling smbd_destroy() in the out_err cleanup path before kfree(tcp_ses). smbd_destroy() safely handles the case where smbd_conn is NULL, so it can be called unconditionally. Closes: https://sashiko.dev/#/patchset/20260912165503.521597-1-pc%40manguebit.org Fixes: 2f8946464b11 ("CIFS: SMBD: Upper layer connects to SMBDirect session") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Tom Talpey Cc: Stefan Metzmacher Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/connect.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index 4bd9f9fb93bb..28e1ddeb6182 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1934,6 +1934,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx, kfree(tcp_ses->leaf_fullpath); if (tcp_ses->ssocket) sock_release(tcp_ses->ssocket); + smbd_destroy(tcp_ses); kfree(tcp_ses); } return ERR_PTR(rc); From ea9dadeac79cef509a4b8b4a3e3b39a741e63313 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 30 Jul 2026 16:05:13 +0300 Subject: [PATCH 1588/2366] drm/msm: mark the fbdev framebuffer as system memory msm_fbdev_driver_fbdev_probe() points screen_buffer at a kernel virtual mapping of the GEM object and uses the deferred sysmem fb ops, but never sets FBINFO_VIRTFB. The framebuffer core then assumes the memory is not in the virtual address space and warns on the first console draw: fb0: sys_fillrect: framebuffer is not in virtual address space. The drm_fbdev_dma, drm_fbdev_shmem and drm_fbdev_ttm helpers all set the flag for system memory. Do the same here. Signed-off-by: Dmitry Baryshkov Assisted-by: Claude:claude-opus-5 Patchwork: https://patchwork.freedesktop.org/patch/743293/ Link: https://lore.kernel.org/r/20260730-drm-msm-fbinfo-virt-v1-1-a27099a6dc58@oss.qualcomm.com Acked-by: Rob Clark # on IRC Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/msm_fbdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c index 89ca9da3e1f2..dd6d6c507d77 100644 --- a/drivers/gpu/drm/msm/msm_fbdev.c +++ b/drivers/gpu/drm/msm/msm_fbdev.c @@ -155,6 +155,7 @@ int msm_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper, helper->fb = buffer->fb; fbi->fbops = &msm_fb_ops; + fbi->flags |= FBINFO_VIRTFB; /* system memory */ drm_fb_helper_fill_info(fbi, helper, sizes); From e249a6e2a130c08bb4d8b0a55cbe29754307e5c9 Mon Sep 17 00:00:00 2001 From: Jesse Casco Date: Sat, 8 Aug 2026 13:13:25 -0400 Subject: [PATCH 1589/2366] drm/msm/dp: skip PUSH_IDLE when the link was never enabled msm_dp_display_atomic_enable() returns early when link training fails, leaving ->power_on false and the main link down. msm_dp_display_atomic_disable() nevertheless writes DP_STATE_CTRL_PUSH_IDLE and waits for an idle-pattern completion that cannot arrive, so every failed enable is followed by "PUSH_IDLE pattern timedout". Every other step of the teardown is already gated on that flag: msm_dp_display_disable(), called from .atomic_post_disable(), returns early on !power_on. The PUSH_IDLE write is the only one that is not, so the controller's runtime-PM reference is then dropped without the link having been taken down. On glymur (Snapdragon X2 Elite) the consequence is not a warning. The SoC does not survive it: TrustZone force-stops the SOCCP and ADSP remote processors and the machine resets silently about 50 ms later, with no oops and no panic. On an ASUS Zenbook A16 (UX3607OA), whose eDP panel does not currently train, this reproduces without any compositor or GPU involvement: # eDP enable has already failed with "Failed link training (rc=-104)" echo 1 > /sys/class/graphics/fb0/blank [535.645455] === marker === [535.694833] qcom_q6v5_pas d00000.remoteproc: fatal error received: \ sys_m_smsm.c:512:TZ force stop [535.694875] remoteproc remoteproc0: crash detected in soccp: type fatal error [535.728857] qcom_q6v5_pas 6800000.remoteproc: fatal error received: \ sys_m_smsm.c:783:err fatal notification received from TZ Gate the PUSH_IDLE write on ->power_on so the disable path is consistent with the rest of the teardown. With this applied the same sequence is harmless and the machine stays up; without it, it resets every time. The unconditional write dates back to the original DP driver (c943b4948b58 ("drm/msm/dp: add displayPort driver support")), but the surrounding code has been restructured several times since, so no Fixes: tag is offered. Note that the eDP link-training failure that exposes this on the A16 is a separate problem in the glymur eDP PHY and is reported separately; this change is about not damaging the machine when training fails, for whatever reason. Tested on ASUS Zenbook A16 (UX3607OA), Snapdragon X2 Elite Extreme, on linux-next next-20260803 and next-20260807. The machine has since been running next-20260807 with this patch as its daily driver. Assisted-by: Anthropic:Claude-Opus-5 Signed-off-by: Jesse Casco Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/745167/ Link: https://lore.kernel.org/r/20260808171325.133041-1-jesse.casco@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index bc646d172abe..5d2ddf1808fe 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1458,6 +1458,20 @@ void msm_dp_display_atomic_disable(struct msm_dp *dp) msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); + /* + * If .atomic_enable() bailed out - link training failure is the common + * case - the mainlink was never brought up and ->power_on stayed false. + * Driving the PUSH_IDLE pattern into a controller that was never + * enabled times out, and .atomic_post_disable() then drops the + * controller's runtime-PM reference without tearing the PHY back down, + * because msm_dp_display_disable() returns early on !power_on. On + * glymur (Snapdragon X2 Elite) that combination is answered by a + * TrustZone-level SOCCP/ADSP force-stop and a silent SoC reset. + * There is nothing to push idle, so leave it alone. + */ + if (!dp->power_on) + return; + msm_dp_ctrl_push_idle(msm_dp_display->ctrl); } From 6fbbf1e152f34ad3913e4a6476680aba672c5068 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sat, 8 Aug 2026 21:16:24 +0800 Subject: [PATCH 1590/2366] drm/msm/adreno: fix autosuspend cleanup during teardown adreno_gpu_init() calls pm_runtime_use_autosuspend(), but adreno_gpu_cleanup() does not call the matching pm_runtime_dont_use_autosuspend() during teardown. If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without calling pm_runtime_dont_use_autosuspend() during teardown, this reference is not dropped and usage_count remains unbalanced. The documentation for pm_runtime_use_autosuspend() also notes that it is important to undo it with pm_runtime_dont_use_autosuspend() at driver exit time, unless runtime PM was initially enabled with devm_pm_runtime_enable(). Add the missing pm_runtime_dont_use_autosuspend() call to adreno_gpu_cleanup(). This issue was found by manual code inspection. Fixes: eeb754746b14 ("drm/msm/gpu: use pm-runtime") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/745110/ Link: https://lore.kernel.org/r/20260808131624.2854412-1-lgs201920130244@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index ca5e4e560cde..5832dc25d6bf 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -1261,6 +1261,8 @@ void adreno_gpu_cleanup(struct adreno_gpu *adreno_gpu) for (i = 0; i < ARRAY_SIZE(adreno_gpu->info->fw); i++) release_firmware(adreno_gpu->fw[i]); + pm_runtime_dont_use_autosuspend(&gpu->pdev->dev); + if (priv && pm_runtime_enabled(&priv->gpu_pdev->dev)) pm_runtime_disable(&priv->gpu_pdev->dev); From 58995b11dfb7dda095d23f22fa4dc79b923b5adf Mon Sep 17 00:00:00 2001 From: William Bright Date: Wed, 12 Aug 2026 11:05:52 +0100 Subject: [PATCH 1591/2366] drm/msm/dp: fix link bandwidth check when wide bus is enabled msm_dp_display_mode_valid() halves the pixel clock when either YUV420 or wide bus is in use, then uses that halved value both for the controller pixel clock limit and for the DP link bandwidth check. Only YUV420 halves the data crossing the link. Wide bus widens the internal DPU to DP interface to two pixels per clock, halving the controller clock. Every pixel is still transmitted, so the link bandwidth requirement remains. As a result, modes needing up to twice the available link bandwidth pass validation. On the IMDT QCS8550 SBC (rev5 with CYPD6125), where DP runs over USB-C alt mode where only two lanes are available, 3840x2160@60 was accepted despite needing 9.6 Gbps against the 8.64 Gbps the link can carry. Use a separate link pixel clock that is only halved for YUV420 for the bandwidth calculation, leaving the wide bus halving to apply solely to the controller pixel clock limit. With this, 4k@60 is correctly rejected and 4k@30 selected instead. Fixes: df9cf852ca30 ("drm/msm/dp: account for widebus and yuv420 during mode validation") Assisted-by: Claude:claude-opus-5 Signed-off-by: William Bright Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/746145/ Link: https://lore.kernel.org/r/20260812-msm-dp-link-bw-v1-1-b0e3ce1190be@imd-tec.com [DB: dropped useless comment] Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 5d2ddf1808fe..4dcbd9b99d06 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -756,6 +756,7 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, struct msm_dp_link_info *link_info; u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; int mode_pclk_khz = mode->clock; + int link_pclk_khz; bool is_yuv_420; if (!dp || !mode_pclk_khz || !dp->connector) { @@ -775,6 +776,8 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, if (is_yuv_420 && !msm_dp_display->panel->vsc_sdp_supported) return MODE_NO_420; + link_pclk_khz = is_yuv_420 ? mode_pclk_khz / 2 : mode_pclk_khz; + if (is_yuv_420 || msm_dp_display->wide_bus_supported) mode_pclk_khz /= 2; @@ -786,9 +789,9 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, mode_bpp = default_bpp; mode_bpp = msm_dp_panel_get_mode_bpp(msm_dp_display->panel, - mode_bpp, mode_pclk_khz); + mode_bpp, link_pclk_khz); - mode_rate_khz = mode_pclk_khz * mode_bpp; + mode_rate_khz = link_pclk_khz * mode_bpp; supported_rate_khz = link_info->num_lanes * link_info->rate * 8; if (mode_rate_khz > supported_rate_khz) From fd73f4a6659897191fa0d40695fe370925dd3780 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 13 Sep 2026 14:38:02 -0700 Subject: [PATCH 1592/2366] Linux 7.3-rc3 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 66654fa71655..0f1b80100b47 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc2 +EXTRAVERSION = -rc3 NAME = Baby Opossum Posse # *DOCUMENTATION* From a5b5cc909931572aec446e129c035b76b3f0c1fa Mon Sep 17 00:00:00 2001 From: Saim Shujah Date: Fri, 28 Aug 2026 11:54:40 +0500 Subject: [PATCH 1593/2366] drm/msm/dpu: clear pending peripheral flush state dpu_hw_ctl_clear_pending_flush() resets the cached per-block state after a flush transaction, but misses pending_periph_flush_mask. The peripheral flush updater accumulates interface bits in this mask. A later transaction which sets the top-level peripheral flush bit can write stale interface bits to CTL_PERIPH_FLUSH together with the current state. Peripheral flush support was added after the helper started clearing every individual pending flush mask. Clear the peripheral mask together with the other cached child masks. Fixes: 64f7b81f0358 ("drm/msm/dpu: add support of new peripheral flush mechanism") Cc: stable@vger.kernel.org Signed-off-by: Saim Shujah Patchwork: https://patchwork.freedesktop.org/patch/748968/ Link: https://lore.kernel.org/r/20260828065440.140410-1-saimzst@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c index 36a497f1d6c1..e8729fe1cfef 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c @@ -118,6 +118,7 @@ static inline void dpu_hw_ctl_clear_pending_flush(struct dpu_hw_ctl *ctx) ctx->pending_intf_flush_mask = 0; ctx->pending_wb_flush_mask = 0; ctx->pending_cwb_flush_mask = 0; + ctx->pending_periph_flush_mask = 0; ctx->pending_merge_3d_flush_mask = 0; ctx->pending_dsc_flush_mask = 0; ctx->pending_cdm_flush_mask = 0; From 26eb3d92c7a4d7adb1ae1740ca6e8e100b11d1ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Larocque?= Date: Thu, 10 Sep 2026 12:44:25 -0400 Subject: [PATCH 1594/2366] Input: synaptics - disable InterTouch on ThinkPad T440p (board id 2722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lenovo ThinkPad T440p (PNP ID LEN0036, board id 2722) has a Synaptics touchpad whose SMBus companion is not ready at boot and takes roughly 200 seconds to appear. During this window the touchpad and TrackPoint are completely unresponsive on approximately 50% of boots, making the machine unusable until the companion finally registers. The device is in the topbuttonpad_pnp_ids[] SMBus allowlist, so the kernel attempts to use SMBus/RMI4 mode by default. When the companion is not ready, psmouse_smbus_init() leaves breadcrumbs and returns -EAGAIN, the PS/2 fallback path is taken, but the device does not function properly until the companion appears and RMI4 takes over. Disable SMBus InterTouch for board id 2722 so the touchpad and TrackPoint work immediately via PS/2 from boot. Users can still force SMBus with psmouse.synaptics_intertouch=1 if needed. Tested-by: Raphaël Larocque Signed-off-by: Raphaël Larocque Link: https://patch.msgid.link/20260910164425.12832-1-rlarocque@disroot.org Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/synaptics.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index 2170bbe4c589..a8ce89d41faa 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -1838,6 +1838,14 @@ static int synaptics_setup_intertouch(struct psmouse *psmouse, return -ENXIO; } + + /* Disable intertouch on known-broken board revisions */ + if (info->board_id == 2722) { + psmouse_info(psmouse, + "Disabling intertouch for board id %u\n", + info->board_id); + return -ENXIO; + } } psmouse_info(psmouse, "Trying to set up SMBus access\n"); From 25e424eb4ae1a662d9c3573218d06ac32f797fc5 Mon Sep 17 00:00:00 2001 From: Chris Sommers Date: Mon, 7 Sep 2026 11:27:23 -0700 Subject: [PATCH 1595/2366] Input: i8042 - add quirk for Acer Aspire Go 15 AG15-42P On the Acer Aspire Go 15 (AG15-42P), the internal keyboard drops out ~5 seconds after boot on both Linux and Linux-LTS kernels. Keystrokes on the built-in keyboard stop registering while the trackpad and external keyboards remain functional. Testing confirms that booting with the i8042.reset kernel parameter resolves the issue and keeps the internal keyboard responsive. Add SERIO_QUIRK_RESET_ALWAYS to i8042_dmi_quirk_table for the Acer Aspire AG15-42P to automatically apply this quirk on boot. Signed-off-by: Chris Sommers Link: https://patch.msgid.link/20260907182723.2709981-1-chris.sommers@icloud.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/serio/i8042-acpipnpio.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h index 9ecb0eed48c4..54eb3687000a 100644 --- a/drivers/input/serio/i8042-acpipnpio.h +++ b/drivers/input/serio/i8042-acpipnpio.h @@ -259,6 +259,13 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = { }, .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS) }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire AG15-42P"), + }, + .driver_data = (void *)(SERIO_QUIRK_RESET_ALWAYS) + }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Acer"), From 88c4aff39452d7f0ab59f13730182a43bc7257c6 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 10 Sep 2026 13:10:46 +0100 Subject: [PATCH 1596/2366] ASoC: wm_adsp: Firmware search progress log should not look like an error In wm_adsp_request_firmware_file() only log the "Failed to request FILENAME" message when there is a real error (not when the file is missing). Add a new debug message to log the sequence of filenames tried during the file search. People have enabled debug messages, seen the "Failed to request" messages that are only logging the normal file search sequence, and reported them as errors. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260910121047.1592541-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm_adsp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 90c24c4b318e..b8f3035af3ba 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -775,9 +775,12 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, s++; } + adsp_dbg(dsp, "Try '%s'\n", fw->filename); ret = wm_adsp_firmware_request(&fw->firmware, fw->filename, cs_dsp->dev); if (ret < 0) { - adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); + if (ret != -ENOENT) + adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); + kfree(fw->filename); fw->filename = NULL; if (ret != -ENOENT) From d56fe35c1bce1f547eb600695f60c139a508ff6e Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Wed, 9 Sep 2026 16:54:49 +0800 Subject: [PATCH 1597/2366] ASoC: rt712-sdca: reconfigure PLL2 to fix calibration time-out Add pll2 reconfiguration sequence in order to fix calibration time-out issue and to support 24.576MHz MCLK on specific platforms. Signed-off-by: Jack Yu Link: https://patch.msgid.link/20260909085449.862350-1-jack.yu@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca-sdw.c | 8 +++ sound/soc/codecs/rt712-sdca.c | 86 +++++++++++++++++++++++++++++-- sound/soc/codecs/rt712-sdca.h | 18 +++++++ 3 files changed, 109 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c index c50e74e20a88..edba0367d9ce 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.c +++ b/sound/soc/codecs/rt712-sdca-sdw.c @@ -18,12 +18,16 @@ static bool rt712_sdca_readable_register(struct device *dev, unsigned int reg) { switch (reg) { + case 0x004d: case 0x201a ... 0x201f: case 0x2029 ... 0x202a: case 0x202d ... 0x2034: case 0x2230 ... 0x2232: case 0x2f01 ... 0x2f0a: case 0x2f35 ... 0x2f36: + case 0x2f3a: + case 0x2f3d: + case 0x2f41: case 0x2f50: case 0x2f54: case 0x2f58 ... 0x2f5d: @@ -48,6 +52,7 @@ static bool rt712_sdca_readable_register(struct device *dev, unsigned int reg) static bool rt712_sdca_volatile_register(struct device *dev, unsigned int reg) { switch (reg) { + case 0x004d: case 0x201b: case 0x201c: case 0x201d: @@ -56,6 +61,9 @@ static bool rt712_sdca_volatile_register(struct device *dev, unsigned int reg) case 0x2230: case 0x2f01: case 0x2f35: + case 0x2f3a: + case 0x2f3d: + case 0x2f41: case 0x320c: case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT712_SDCA_ENT_GE49, RT712_SDCA_CTL_DETECTED_MODE, 0): case SDW_SDCA_CTL(FUNC_NUM_HID, RT712_SDCA_ENT_HID01, RT712_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c index eda87eb9ab66..38052cb19790 100644 --- a/sound/soc/codecs/rt712-sdca.c +++ b/sound/soc/codecs/rt712-sdca.c @@ -73,14 +73,57 @@ static int rt712_sdca_index_update_bits(struct rt712_sdca_priv *rt712, return rt712_sdca_index_write(rt712, nid, reg, tmp); } +static void rt712_sdca_clk_patch(struct rt712_sdca_priv *rt712) +{ + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); + regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); + usleep_range(1000, 1100); + regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x02); + usleep_range(1000, 1100); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x0080, 0x0000); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x001f, 0x0017); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF3, 0x0010, 0x0000); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF1, 0x0081, 0x0001); + regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); + usleep_range(1000, 1100); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF1, 0x0081, 0x0081); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x0080, 0x0080); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x001f, 0x0000); + regmap_update_bits(rt712->regmap, RT712_PLL2_CONF3, 0x0010, 0x0010); + usleep_range(1000, 1100); + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0081); +} + +static void rt712_sdca_clk_patch2(struct rt712_sdca_priv *rt712) +{ + rt712_sdca_index_update_bits(rt712, RT712_VENDOR_REG, 0x49, 0x0800, + 0x0000); + rt712_sdca_index_update_bits(rt712, RT712_VENDOR_REG, 0x49, 0xf000, + 0x0000); + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); + rt712_sdca_index_update_bits(rt712, RT712_VENDOR_ANALOG_CTL, 0x0c, 0xc000, + 0xc000); + rt712_sdca_index_update_bits(rt712, RT712_VENDOR_ANALOG_CTL, 0x00, 0xc000, + 0xc000); + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0081); + regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x02); + usleep_range(1000, 1100); + regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); + usleep_range(1000, 1100); + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); +} + static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) { unsigned int val, loop_rc = 0, loop_dc = 0; struct device *dev; struct regmap *regmap = rt712->regmap; + unsigned int clk_base; int chk_cnt = 100; int ret = 0; + regmap_read(rt712->regmap, RT712_SDW_ROOT_CLK, &clk_base); + mutex_lock(&rt712->calibrate_mutex); dev = regmap_get_device(regmap); @@ -109,8 +152,35 @@ static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) if (ret < 0) goto _cali_fail_; } - if (loop_dc == chk_cnt) - dev_err(dev, "%s, calibration time-out!\n", __func__); + + if (loop_dc == chk_cnt) { + if (clk_base == RT712_CLK_FREQ_24_576MHZ) { + rt712_sdca_clk_patch(rt712); + rt712_sdca_clk_patch2(rt712); + } + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_FSM_CTL, 0x4100); + rt712_sdca_index_write(rt712, RT712_VENDOR_CALI, + RT712_DAC_DC_CALI_CTL1, 0x7883); + rt712_sdca_index_write(rt712, RT712_VENDOR_CALI, + RT712_DAC_DC_CALI_CTL1, 0xf893); + rt712_sdca_index_read(rt712, RT712_VENDOR_CALI, + RT712_DAC_DC_CALI_CTL1, &val); + + for (loop_dc = 0; loop_dc < chk_cnt && + (val & RT712_DAC_DC_CALI_TRIGGER); loop_dc++) { + usleep_range(10000, 11000); + ret = rt712_sdca_index_read(rt712, RT712_VENDOR_CALI, + RT712_DAC_DC_CALI_CTL1, &val); + + if (ret < 0) + goto _cali_fail_; + } + + if (loop_dc == chk_cnt) + dev_err(dev, "%s, calibration time-out!\n", __func__); + else + dev_dbg(dev, "%s, calibration success!\n", __func__); + } if (loop_dc == chk_cnt || loop_rc == chk_cnt) ret = -ETIMEDOUT; @@ -1759,9 +1829,13 @@ static void rt712_sdca_va_io_init(struct rt712_sdca_priv *rt712) static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) { - int ret = 0; unsigned int jack_func_status, mic_func_status, amp_func_status; struct device *dev = &rt712->slave->dev; + unsigned int clk_base; + int ret = 0; + + regmap_read(rt712->regmap, RT712_SDW_ROOT_CLK, &clk_base); + dev_dbg(dev, "%s clk_base=%x", __func__, clk_base); regmap_read(rt712->regmap, SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT712_SDCA_ENT0, RT712_SDCA_CTL_FUNC_STATUS, 0), &jack_func_status); @@ -1773,6 +1847,12 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) __func__, jack_func_status, mic_func_status, amp_func_status); rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_JD_CTL3, 0x7778); + + if (clk_base == RT712_CLK_FREQ_24_576MHZ) { + rt712_sdca_clk_patch(rt712); + rt712_sdca_clk_patch2(rt712); + } + /* DMIC */ if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { rt712_sdca_index_write(rt712, RT712_VENDOR_HDA_CTL, RT712_DMIC2_FU_IT_FLOAT_CTL, 0x1526); diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h index 46740281a5c1..6229fe341bb5 100644 --- a/sound/soc/codecs/rt712-sdca.h +++ b/sound/soc/codecs/rt712-sdca.h @@ -162,6 +162,16 @@ struct rt712_dmic_kctrl_priv { #define RT712_EAPD_HIGH 0x2 #define RT712_EAPD_LOW 0x0 +/* SDW clock root frequency */ +#define RT712_SDW_ROOT_CLK 0x004d +#define RT712_SDW_SCALE_CLK0 0x0062 +#define RT712_SDW_SCALE_CLK1 0x0072 + +/* PLL2 config */ +#define RT712_PLL2_CONF1 0x2f3a +#define RT712_PLL2_CONF2 0x2f3d +#define RT712_PLL2_CONF3 0x2f41 + /* RC Calibration register */ #define RT712_RC_CAL 0x3201 @@ -254,6 +264,14 @@ enum rt712_sdca_version { RT712_VB, }; +enum { + RT712_CLK_FREQ_19_2_MHZ = 1, + RT712_CLK_FREQ_24MHZ = 2, + RT712_CLK_FREQ_24_576MHZ = 3, + RT712_CLK_FREQ_22_5792MHZ = 4, +}; + + int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave); int rt712_sdca_init(struct device *dev, struct regmap *regmap, struct regmap *mbq_regmap, struct sdw_slave *slave); From 4d855d747521505b54457c96bc73577bf74b2374 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 10 Sep 2026 12:44:56 +0100 Subject: [PATCH 1598/2366] ASoC: Rename snd_soc_dai_link_ch_map.ch_mask to cpu_ch_mask Rename the ch_mask member of snd_soc_dai_link_ch_map to cpu_ch_mask, as that is what it is used for. The CPU and codec channel masks are not necessarily the same, and are quite likely different. SoundWire and I2S/TDM both support assigning different sample slots to each codec, so for example channel 0 on each codec could map to different channels at the CPU. So it's quite normal that the channel mask at the CPU end is different for each codec, but the codec channel masks are the same for each codec. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260910114500.1586637-2-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/soc.h | 2 +- sound/soc/sdw_utils/soc_sdw_utils.c | 2 +- sound/soc/soc-pcm.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/sound/soc.h b/include/sound/soc.h index f46b2bc2a022..94c9b75e27e3 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -699,7 +699,7 @@ struct snd_soc_dai_link_component { struct snd_soc_dai_link_ch_map { unsigned int cpu; unsigned int codec; - unsigned int ch_mask; + unsigned int cpu_ch_mask; }; struct snd_soc_dai_link { diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index a66dcc02fb59..f0cebcbf6288 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -1596,7 +1596,7 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, * ASoC will set the corresponding channel numbers for each cpu dai. */ for_each_link_ch_maps(rtd->dai_link, i, ch_maps) - ch_maps->ch_mask = ch_mask << (i * step); + ch_maps->cpu_ch_mask = ch_mask << (i * step); return 0; } diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 0e49290a8c90..cb64ced21149 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1264,7 +1264,7 @@ static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, */ for_each_rtd_ch_maps(rtd, j, ch_maps) if (ch_maps->cpu == i) - ch_mask |= ch_maps->ch_mask; + ch_mask |= ch_maps->cpu_ch_mask; /* fixup cpu channel number */ if (ch_mask) From 88b14c0d0bab5c0f3e7c641f274e3c70210c0e36 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 10 Sep 2026 12:44:57 +0100 Subject: [PATCH 1599/2366] ASoC: Add codec_ch_mask to snd_soc_dai_link_ch_map Add a codec_ch_mask member to snd_soc_dai_link_ch_map. The CPU and codec channel masks are not necessarily the same, and are quite likely different. SoundWire and I2S/TDM both support assigning different sample slots to each codec, so for example channel 0 on each codec could map to different channels at the CPU. It is also possible for one TX channel to map to multiple RX channels. So it isn't _always_ safe to assume that the total number of set bits in the CPU ch_mask is the same as the total number of enabled channels on the codec. For example consider this mapping on a capture stream: CPU0 CODEC0 cpu_ch_mask = 0x03 CPU1 CODEC0 cpu_ch_mask = 0x03 This could be either four TX channels on the codec split across two receiving CPUs, or two TX channels on the codec duplicated to two CPUs. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260910114500.1586637-3-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/soc.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/sound/soc.h b/include/sound/soc.h index 94c9b75e27e3..5afc34b147b5 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -700,6 +700,7 @@ struct snd_soc_dai_link_ch_map { unsigned int cpu; unsigned int codec; unsigned int cpu_ch_mask; + unsigned int codec_ch_mask; }; struct snd_soc_dai_link { From 6b382bdfe26a2232091bf743e454e6794295783e Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 10 Sep 2026 12:44:58 +0100 Subject: [PATCH 1600/2366] ASoC: soc-pcm: Apply snd_soc_dai_link_ch_map.codec_ch_mask to codec params In __soc_pcm_hw_params() if there is a snd_soc_dai_link_ch_map with non-zero codec_ch_mask, use that channel mask to restrict which channels are enabled on the codec. But only if there isn't a TDM mask. It is possible that a snd_soc_dai_link_ch_map could include the same codec multiple times on different CPUs so the for_each_rtd_ch_maps() loop accumulates the channel masks for all entries of that codec. If a TDM mask was also set, it takes priority and is used instead of any possible snd_soc_dai_link_ch_map entries. (They cannot be ANDed together because the bit positions are indicating different things: TDM is a bit for each TDM slot, codec_ch_mask is a bit for each codec channel.) This fixes a problem of incorrect TX channels enabled on the codec when multiple codecs are aggregated on a single capture link. For example: - Two CPUs with six 4-channel codecs. - The machine driver chooses to assign one channel from each codec to one channel on the CPU - But the codec hw_params() would be passed a channel count of 6, which (a) is more channels than the codec has and (b) allows enabling channels that should not be driving the audio bus. Fixes: ac950278b087 ("ASoC: add N cpus to M codecs dai link support") Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260910114500.1586637-4-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/soc-pcm.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index cb64ced21149..3137c091bdb8 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1206,7 +1206,9 @@ static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, goto out; for_each_rtd_codec_dais(rtd, i, codec_dai) { - unsigned int tdm_mask = snd_soc_dai_tdm_mask_get(codec_dai, substream->stream); + unsigned int ch_mask = snd_soc_dai_tdm_mask_get(codec_dai, substream->stream); + struct snd_soc_dai_link_ch_map *ch_maps; + int j; /* * Skip CODECs which don't support the current stream type, @@ -1228,9 +1230,15 @@ static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, /* copy params for each codec */ tmp_params = *params; - /* fixup params based on TDM slot masks */ - if (tdm_mask) - soc_pcm_codec_params_fixup(&tmp_params, tdm_mask); + /* fixup params based on TDM or ch_map masks */ + if (!ch_mask) { + for_each_rtd_ch_maps(rtd, j, ch_maps) + if (ch_maps->codec == i) + ch_mask |= ch_maps->codec_ch_mask; + } + + if (ch_mask) + soc_pcm_codec_params_fixup(&tmp_params, ch_mask); ret = snd_soc_dai_hw_params(codec_dai, substream, &tmp_params); From 290845e151cd4e307cc1f25319583aaceb4eeb30 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 10 Sep 2026 12:44:59 +0100 Subject: [PATCH 1601/2366] ASoC: sdw_utils: Set snd_soc_dai_link_ch_map.codec_ch_mask for capture In asoc_sdw_hw_params() set the codec_ch_mask member of struct snd_soc_dai_link_ch_map for capture streams. ASoC will then pass the correct number of channels to each codec hw_params(). This prevents trying to enable more channels on the codec DP than have been allocated bitslots in the SoundWire frame, which would cause bus clash errors. In theory codec_ch_mask could also be set for playback streams, but for those the CPU is the only sender so there is no risk of bus clash. For playback streams codec_ch_mask is set to 0 to preserve the existing behavior and avoid introducing bugs. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260910114500.1586637-5-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdw_utils/soc_sdw_utils.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index f0cebcbf6288..293574a6ccca 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -1565,7 +1565,7 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_dai_link_ch_map *ch_maps; int ch = params_channels(params); - unsigned int ch_mask; + unsigned int cpu_ch_mask, codec_ch_mask; int num_codecs; int step; int i; @@ -1575,8 +1575,9 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, /* Identical data will be sent to all codecs in playback */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - ch_mask = GENMASK(ch - 1, 0); + cpu_ch_mask = GENMASK(ch - 1, 0); step = 0; + codec_ch_mask = 0; } else { num_codecs = rtd->dai_link->num_codecs; @@ -1586,17 +1587,24 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - ch_mask = GENMASK(ch / num_codecs - 1, 0); - step = hweight_long(ch_mask); + cpu_ch_mask = GENMASK(ch / num_codecs - 1, 0); + step = hweight_long(cpu_ch_mask); + codec_ch_mask = cpu_ch_mask; } /* * The captured data will be combined from each cpu DAI if the dai * link has more than one codec DAIs. Set codec channel mask and * ASoC will set the corresponding channel numbers for each cpu dai. + * + * sdw_stream_add_slave() assigns different payload offsets to each + * codec in a capture stream, so that the same channels on each + * codec map to different channels on the CPU. */ - for_each_link_ch_maps(rtd->dai_link, i, ch_maps) - ch_maps->cpu_ch_mask = ch_mask << (i * step); + for_each_link_ch_maps(rtd->dai_link, i, ch_maps) { + ch_maps->cpu_ch_mask = cpu_ch_mask << (i * step); + ch_maps->codec_ch_mask = codec_ch_mask; + } return 0; } From b5b00a57868b1eabdf90a29a51f0eb732c609f3a Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 10 Sep 2026 12:45:00 +0100 Subject: [PATCH 1602/2366] ASoC: sdw_utils: cs_amp: Delete bogus and incorrect capture channel fixup Delete the asoc_sdw_cs_spk_feedback_rtd_init(). This is not needed now that the ASoC bug it was working around has been fixed. And it was broken anyway because it didn't match the way the core SoundWire code mapped codec channels to frame bitslots. This code was added to avoid a problem where multiple codec DP outputs were mapped to the same SoundWire frame bit slot. This would allow a user to break the SoundWire bus just by enabling mixer outputs using ALSA controls. As no production system has used the capture stream, this workaround was of little consequence and the problem of conflicting DP mappings was not investigated. The ASoC bug that enabled too many channels on each codec has now been fixed. So this workaround can be completely deleted. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260910114500.1586637-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/soc_sdw_utils.h | 2 -- sound/soc/sdw_utils/soc_sdw_cs_amp.c | 46 ---------------------------- sound/soc/sdw_utils/soc_sdw_utils.c | 4 --- 3 files changed, 52 deletions(-) diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h index 9b28e9aef4f1..9fbb69b9052d 100644 --- a/include/sound/soc_sdw_utils.h +++ b/include/sound/soc_sdw_utils.h @@ -250,8 +250,6 @@ int asoc_sdw_cs_amp_init(struct snd_soc_card *card, struct snd_soc_dai_link *dai_links, struct asoc_sdw_codec_info *info, bool playback); -int asoc_sdw_cs_spk_feedback_rtd_init(struct snd_soc_pcm_runtime *rtd, - struct snd_soc_dai *dai); int asoc_sdw_cs35l56_volume_limit(struct snd_soc_card *card, const char *name_prefix); /* MAXIM codec support */ diff --git a/sound/soc/sdw_utils/soc_sdw_cs_amp.c b/sound/soc/sdw_utils/soc_sdw_cs_amp.c index 325ab7230481..6e21ef8f87e2 100644 --- a/sound/soc/sdw_utils/soc_sdw_cs_amp.c +++ b/sound/soc/sdw_utils/soc_sdw_cs_amp.c @@ -14,7 +14,6 @@ #include #include -#define CS_AMP_CHANNELS_PER_AMP 4 #define CS35L56_SPK_VOLUME_0DB 400 /* 0dB Max */ int asoc_sdw_cs35l56_volume_limit(struct snd_soc_card *card, const char *name_prefix) @@ -64,51 +63,6 @@ int asoc_sdw_cs_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai } EXPORT_SYMBOL_NS(asoc_sdw_cs_spk_rtd_init, "SND_SOC_SDW_UTILS"); -int asoc_sdw_cs_spk_feedback_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) -{ - const struct snd_soc_dai_link *dai_link = rtd->dai_link; - const struct snd_soc_dai_link_ch_map *ch_map; - const struct snd_soc_dai_link_component *codec_dlc; - struct snd_soc_dai *codec_dai; - u8 ch_slot[8] = {}; - unsigned int amps_per_bus, ch_per_amp, mask; - int i, ret; - - WARN_ON(dai_link->num_cpus > ARRAY_SIZE(ch_slot)); - - /* - * CS35L56 has 4 TX channels. When the capture is aggregated the - * same bus slots will be allocated to all the amps on a bus. Only - * one amp on that bus can be transmitting in each slot so divide - * the available 4 slots between all the amps on a bus. - */ - amps_per_bus = dai_link->num_codecs / dai_link->num_cpus; - if ((amps_per_bus == 0) || (amps_per_bus > CS_AMP_CHANNELS_PER_AMP)) { - dev_err(rtd->card->dev, "Illegal num_codecs:%u / num_cpus:%u\n", - dai_link->num_codecs, dai_link->num_cpus); - return -EINVAL; - } - - ch_per_amp = CS_AMP_CHANNELS_PER_AMP / amps_per_bus; - - for_each_rtd_ch_maps(rtd, i, ch_map) { - codec_dlc = snd_soc_link_to_codec(rtd->dai_link, i); - codec_dai = snd_soc_find_dai(codec_dlc); - mask = GENMASK(ch_per_amp - 1, 0) << ch_slot[ch_map->cpu]; - - ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, mask, 4, 32); - if (ret < 0) { - dev_err(rtd->card->dev, "Failed to set TDM slot:%d\n", ret); - return ret; - } - - ch_slot[ch_map->cpu] += ch_per_amp; - } - - return 0; -} -EXPORT_SYMBOL_NS(asoc_sdw_cs_spk_feedback_rtd_init, "SND_SOC_SDW_UTILS"); - int asoc_sdw_cs_amp_init(struct snd_soc_card *card, struct snd_soc_dai_link *dai_links, struct asoc_sdw_codec_info *info, diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index 293574a6ccca..d2eeef4931c6 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -811,7 +811,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_name = "cs35l56-sdw1c", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, - .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, }, }, .dai_num = 2, @@ -840,7 +839,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_name = "cs35l56-sdw1c", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, - .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, }, }, .dai_num = 2, @@ -869,7 +867,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_name = "cs35l56-sdw1c", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, - .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, }, }, .dai_num = 2, @@ -898,7 +895,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { .dai_name = "cs35l56-sdw1c", .dai_type = SOC_SDW_DAI_TYPE_AMP, .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, - .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, }, }, .dai_num = 2, From 576725ded009f09a28da19852f7edf62dbc5f94c Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Fri, 11 Sep 2026 16:19:32 +0800 Subject: [PATCH 1603/2366] ASoC: Intel: sof_es8336: Add a quirk for Huawei Matebook B3-420 Add DMI entry for Huawei Matebook B3-420 (BDZ-WXX9) with HEADPHONE_GPIO and HEADSET_MIC1 quirks. Similar to Huawei Matebook D (BOD-WXX9). On the same machine,audio routing between speakers and headphones works correctly when running Windows with the Huawei audio driver. However, after reinstalling Linux, both the speakers and headphones output sound simultaneously,indicating that the amplifier enable GPIOs are not being toggled correctly to separate the two outputs. Signed-off-by: Ai Chao Link: https://patch.msgid.link/20260911081932.2605407-1-aichao@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_es8336.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c index 9b016136c639..f1e62c2e79fe 100644 --- a/sound/soc/intel/boards/sof_es8336.c +++ b/sound/soc/intel/boards/sof_es8336.c @@ -360,6 +360,15 @@ static const struct dmi_system_id sof_es8336_quirk_table[] = { .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | SOC_ES8336_HEADSET_MIC1) }, + { + .callback = sof_es8336_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"), + DMI_MATCH(DMI_PRODUCT_NAME, "NDZ-WXX9"), + }, + .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | + SOC_ES8336_HEADSET_MIC1) + }, {} }; From 930a7312c946bf4731721cadd82bb9a2ada496ca Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Wed, 9 Sep 2026 16:08:25 +0200 Subject: [PATCH 1604/2366] spi: spi-qpic-snand: avoid writing QPIC_EBI2_ECC_BUF_CFG register The description of commit bfb34eced559 ("mtd: rawnand: qcom: avoid writing to obsolete register") says this: "QPIC_EBI2_ECC_BUF_CFG register got obsolete from QPIC V2.0 onwards. Avoid writing this register if QPIC version is V2.0 or newer." Although the referenced commit is related to the 'qcom-nandc' driver, however the hardware supported by the current driver is also based on QPIC v2.0 so we should avoid writing that register here as well. Remove the register writing code to avoid undefined behaviour. Fixes: 7304d1909080 ("spi: spi-qpic: add driver for QCOM SPI NAND flash Interface") Signed-off-by: Gabor Juhos Reviewed-by: Md Sadre Alam Link: https://patch.msgid.link/20260909-qpic-snand-avoid-ebi2-reg-write-v1-1-9b1b1466cc75@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-qpic-snand.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/spi/spi-qpic-snand.c b/drivers/spi/spi-qpic-snand.c index 61b1f2eb19ce..05efe6313b7f 100644 --- a/drivers/spi/spi-qpic-snand.c +++ b/drivers/spi/spi-qpic-snand.c @@ -765,8 +765,6 @@ static int qcom_spi_read_cw_raw(struct qcom_nand_controller *snandc, u8 *data_bu qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, 0); - qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_clr, NAND_ERASED_CW_DETECT_CFG, 1, 0); qcom_write_reg_dma(snandc, &snandc->regs->erased_cw_detect_cfg_set, @@ -1104,8 +1102,6 @@ static void qcom_spi_config_page_write(struct qcom_nand_controller *snandc) { qcom_write_reg_dma(snandc, &snandc->regs->addr0, NAND_ADDR0, 2, 0); qcom_write_reg_dma(snandc, &snandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); - qcom_write_reg_dma(snandc, &snandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, - 1, NAND_BAM_NEXT_SGL); } static void qcom_spi_config_cw_write(struct qcom_nand_controller *snandc) From ed22ad5fdbdbf9b4cb4ad3003f60314b5a5eb89d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Sep 2026 11:39:33 +0200 Subject: [PATCH 1605/2366] Input: soc_button_array - fix MS Surface Pro 11 probe failure On the MS Surface Pro 11 soc_button_array probing races with the GPIO driver probing. If soc_button_array wins the race then gpiod_get() returns EPROBE_DEFER, which should normally take care of retrying later, but the soc_button_array code deliberately ignores EPROBE_DEFER causing it to fail its probe() which causes the volume and power buttons to now work. The ignoring of EPROBE_DEFER is there to deal with a problem specific to older Bay Trail (BYT) and Cherry Trail (CHT) tablets which often use this driver. Modify the error handling to only ignore EPROBE_DEFER on BYT and CHT platforms and propagate EPROBE_DEFER normally on other platforms. Fixes: bcf059578980 ("Input: soc_button_array - partial revert of support for newer surface devices") Cc: stable@vger.kernel.org Reported-by: Sergey Lebedev Closes: https://lore.kernel.org/lkml/20260830141355.55898-1-lsa.uz@pm.me/ Signed-off-by: Hans de Goede Tested-by: Sergey Lebedev Link: https://patch.msgid.link/20260909093934.29411-1-johannes.goede@oss.qualcomm.com Signed-off-by: Dmitry Torokhov --- drivers/input/misc/soc_button_array.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c index b8cad415c62c..264c41f80d2b 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -16,6 +16,7 @@ #include #include #include +#include #include static bool use_low_level_irq; @@ -160,7 +161,7 @@ soc_button_device_create(struct platform_device *pdev, struct gpio_keys_platform_data *gpio_keys_pdata; const struct dmi_system_id *dmi_id; int invalid_acpi_index = -1; - int error, gpio, irq; + int error, gpio, irq = 0; int n_buttons = 0; for (info = button_info; info->name; info++) @@ -191,8 +192,9 @@ soc_button_device_create(struct platform_device *pdev, error = soc_button_lookup_gpio(&pdev->dev, info->acpi_index, &gpio, &irq); if (error || irq < 0) { /* - * Skip GPIO if not present. Note we deliberately - * ignore -EPROBE_DEFER errors here. On some devices + * Propagate -EPROBE_DEFER, skip button on other errors. + * + * -EPROBE_DEFER is ignored on Bay & Cherry Trail. Here * Intel is using so called virtual GPIOs which are not * GPIOs at all but some way for AML code to check some * random status bits without need a custom opregion. @@ -201,6 +203,12 @@ soc_button_device_create(struct platform_device *pdev, * we do not have a driver for these so they will never * show up, therefore we ignore -EPROBE_DEFER. */ + if ((error == -EPROBE_DEFER || irq == -EPROBE_DEFER) && + !(soc_intel_is_byt() || soc_intel_is_cht())) { + error = -EPROBE_DEFER; + goto err_free_mem; + } + continue; } From fb5022278b6ea7f1838e3ef78028d5d5e3375f65 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Sep 2026 11:39:34 +0200 Subject: [PATCH 1606/2366] Input: soc_button_array - check btns_desc->package.count Check that btns_desc->package.count is not 0 before accessing btns_desc->package.elements[0]. Fixes: 4c3362f44980 ("Input: soc_button_array - add support for ACPI 6.0 Generic Button Device") Cc: stable@vger.kernel.org Reported-by: Shashiko Closes: https://lore.kernel.org/linux-input/20260909091440.3384C1F00A3A@smtp.kernel.org/ Signed-off-by: Hans de Goede Link: https://patch.msgid.link/20260909093934.29411-2-johannes.goede@oss.qualcomm.com Signed-off-by: Dmitry Torokhov --- drivers/input/misc/soc_button_array.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c index 264c41f80d2b..f08e29af531f 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -377,7 +377,7 @@ static struct soc_button_info *soc_button_get_button_info(struct device *dev) } } - if (!btns_desc) { + if (!btns_desc || !btns_desc->package.count) { dev_err(dev, "ACPI Button Descriptors not found\n"); button_info = ERR_PTR(-ENODEV); goto out; From ea48250a0dc9708c198e8665391973bfc6c86fd2 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 13 Sep 2026 17:19:48 -0700 Subject: [PATCH 1607/2366] Input: document that no new LED codes should be added Add a comment to input-event-codes.h clarifying that no new LED definitions should be added to the input subsystem. The existing LED_* definitions are legacy and grandfathered for backwards compatibility with userspace via evdev. Any new LED indicators should instead use the dedicated LED subsystem. Signed-off-by: Dmitry Torokhov --- include/uapi/linux/input-event-codes.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 3528168f7c6d..4217950e5d16 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -970,6 +970,12 @@ /* * LEDs + * + * Do not add any new LED definitions to the input subsystem. The existing + * definitions are legacy and grandfathered for backwards compatibility with + * userspace (via evdev). Any new LEDs should be implemented using the + * LED subsystem (struct led_classdev). The input core provides a bridge to + * the LED subsystem in drivers/input/input-leds.c. */ #define LED_NUML 0x00 From 7bc369cb3d3f3656eb77285628ee264264d28ad4 Mon Sep 17 00:00:00 2001 From: Jeremy Nyberg Date: Sun, 13 Sep 2026 17:43:02 -0700 Subject: [PATCH 1608/2366] Input: xpad - fix PDP Marvel Xbox 360 controller The PDP Marvel Xbox 360 controller with USB ID 0e6f:0147 is incorrectly classified as an Xbox One controller. With the current XTYPE_XBOXONE classification, the controller is detected but produces no input, while its four player LEDs continue blinking indefinitely. Classify USB ID 0e6f:0147 as an Xbox 360 controller instead. Tested on a PDP Marvel Xbox 360 controller with USB ID 0e6f:0147. All inputs register correctly and the player LED indicates the current player. Fixes: c225370e01b8 ("Input: xpad - sync supported devices with 360Controller") Cc: stable@vger.kernel.org Signed-off-by: Jeremy Nyberg Link: https://patch.msgid.link/20260910071627.236014-1-SlickStretch3.0@gmail.com Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 2da0b7f1722a..b7a9a940d250 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -215,7 +215,7 @@ static const struct xpad_device { { 0x0e6f, 0x0139, "Afterglow Prismatic Wired Controller", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x013a, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x0146, "Rock Candy Wired Controller for Xbox One", 0, XTYPE_XBOXONE }, - { 0x0e6f, 0x0147, "PDP Marvel Xbox One Controller", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x0147, "PDP Marvel Xbox 360 Controller", 0, XTYPE_XBOX360 }, { 0x0e6f, 0x015c, "PDP Xbox One Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE }, { 0x0e6f, 0x015d, "PDP Mirror's Edge Official Wired Controller for Xbox One", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x0161, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, From cba76c0f47af1a389d718c5bb69e75cbd67bba98 Mon Sep 17 00:00:00 2001 From: Roberts Kursitis Date: Sun, 6 Sep 2026 17:30:40 +0300 Subject: [PATCH 1609/2366] Input: xpad - add support for Azeron devices Azeron controllers (Cyro, Cyborg, Classic/Compact, Cyro Lefty, Cyborg II and Keyzen) present a standard Xbox 360 controller interface, so they work with the existing xpad driver once their USB IDs are added. The 0x16d0 vendor ID is a shared block, but this is safe because xpad only binds interfaces that match the Xbox 360 signature. Tested with an Azeron Keyzen. Signed-off-by: Roberts Kursitis Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260906143040.162418-1-roberts.kursitis@azeron.eu Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index b7a9a940d250..8e4e06b53c2a 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -292,6 +292,12 @@ static const struct xpad_device { { 0x1689, 0xfd00, "Razer Onza Tournament Edition", 0, XTYPE_XBOX360 }, { 0x1689, 0xfd01, "Razer Onza Classic Edition", 0, XTYPE_XBOX360 }, { 0x1689, 0xfe00, "Razer Sabertooth", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x1103, "Azeron Cyro", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x113c, "Azeron Cyborg", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x1192, "Azeron Classic/Compact", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x1212, "Azeron Cyro Lefty", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x12f7, "Azeron Cyborg II", 0, XTYPE_XBOX360 }, + { 0x16d0, 0x13ea, "Azeron Keyzen", 0, XTYPE_XBOX360 }, { 0x17ef, 0x6182, "Lenovo Legion Controller for Windows", 0, XTYPE_XBOX360 }, { 0x1949, 0x041a, "Amazon Game Controller", 0, XTYPE_XBOX360 }, { 0x1a86, 0xe310, "Legion Go S", 0, XTYPE_XBOX360 }, @@ -534,6 +540,7 @@ static const struct usb_device_id xpad_table[] = { XPAD_XBOX360_VENDOR(0x15e4), /* Numark Xbox 360 controllers */ XPAD_XBOX360_VENDOR(0x162e), /* Joytech Xbox 360 controllers */ XPAD_XBOX360_VENDOR(0x1689), /* Razer Onza */ + XPAD_XBOX360_VENDOR(0x16d0), /* Azeron controllers */ XPAD_XBOX360_VENDOR(0x17ef), /* Lenovo */ XPAD_XBOX360_VENDOR(0x1949), /* Amazon controllers */ XPAD_XBOX360_VENDOR(0x1a86), /* Nanjing Qinheng Microelectronics (WCH) */ From a52ae68a937efc353251aec27fc995ff66cbe1ca Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:40:04 +0800 Subject: [PATCH 1610/2366] Input: eeti_ts - publish the OF module alias The EETI driver matches eeti,exc3000-i2c Device Tree clients, but only publishes the legacy eeti_ts I2C ID. The I2C core emits an OF modalias for a Device Tree client. Publish the existing OF match table within its CONFIG_OF guard. Fixes: e32d7f1b246c ("Input: eeti - add device tree matching table") Signed-off-by: hpp.iscas Link: https://patch.msgid.link/20260905134004.66336-1-hppiscas@163.com Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/eeti_ts.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/touchscreen/eeti_ts.c b/drivers/input/touchscreen/eeti_ts.c index b12602bc368e..b870a939508b 100644 --- a/drivers/input/touchscreen/eeti_ts.c +++ b/drivers/input/touchscreen/eeti_ts.c @@ -276,6 +276,7 @@ static const struct of_device_id of_eeti_ts_match[] = { { .compatible = "eeti,exc3000-i2c", }, { } }; +MODULE_DEVICE_TABLE(of, of_eeti_ts_match); #endif static struct i2c_driver eeti_ts_driver = { From 45b0037899704caf9078be2be4de69361ca7d933 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 12:20:38 +0200 Subject: [PATCH 1611/2366] Input: trackpoint - fix the inertia attribute name in the ABI document The attribute is created as "inertia" (TRACKPOINT_INT_ATTR(inertia, ...) in drivers/input/mouse/trackpoint.c); the ABI file spells the path "intertia". The description below it already says inertia. Fix the spelling. Fixes: aebb47d4e7a9 ("Input: trackpoint: document sysfs interface") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260905102038.42882-1-kmehltretter@gmail.com Signed-off-by: Dmitry Torokhov --- Documentation/ABI/testing/sysfs-devices-platform-trackpoint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-devices-platform-trackpoint b/Documentation/ABI/testing/sysfs-devices-platform-trackpoint index df11901a6b3d..7954434b0434 100644 --- a/Documentation/ABI/testing/sysfs-devices-platform-trackpoint +++ b/Documentation/ABI/testing/sysfs-devices-platform-trackpoint @@ -5,7 +5,7 @@ Contact: linux-input@vger.kernel.org Description: (RW) Trackpoint sensitivity. -What: /sys/devices/platform/i8042/.../intertia +What: /sys/devices/platform/i8042/.../inertia Date: Aug, 2005 KernelVersion: 2.6.14 Contact: linux-input@vger.kernel.org From 7e61560628d17ea6b1d8ee370f6d42694cff8758 Mon Sep 17 00:00:00 2001 From: Ulises Mendez Martinez Date: Fri, 4 Sep 2026 15:07:09 +0000 Subject: [PATCH 1612/2366] objtool: Validate disassembler headers in libopcodes probe commit 3f2de814c059 ("objtool: Fix libopcodes linking with static libraries") tested for libopcodes availability by linking a test snippet with a forward declaration of disassemble_init_for_target(). However, testing symbol linkage with an extern declaration only verifies the presence of the library (.so/.a) and bypasses checking for development headers (binutils-dev). On systems where libopcodes is present without development headers installed, the probe succeeds, enabling BUILD_DISAS. Subsequent compilation of objtool then fails: fatal error: 'bfd.h' file not found 113 | #include Additionally, the probe invokes $(HOSTCC) without $(HOSTCFLAGS), ignoring any sysroot or include flags specified for the host compiler. Fix this by including and directly in the test snippet, passing $(HOSTCFLAGS) so host compiler options are respected, and defining PACKAGE="objtool" to satisfy the configuration check in . Fixes: 3f2de814c059 ("objtool: Fix libopcodes linking with static libraries") Fixes: 436326bc525d ("objtool: fix build failure due to missing libopcodes check") Reported-by: Alice Ryhl Assisted-by: Antigravity:Gemini-Next Signed-off-by: Ulises Mendez Martinez Link: https://patch.msgid.link/20260904150710.2997558-1-umendez@google.com Signed-off-by: Josh Poimboeuf --- tools/objtool/Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index a4484fd22a96..4cc2e756af84 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -89,9 +89,11 @@ LIBOPCODES_LIBS := $(shell \ "-lopcodes -lbfd" \ "-lopcodes -lbfd -liberty" \ "-lopcodes -lbfd -liberty -lz"; do \ - echo 'extern void disassemble_init_for_target(void *);' \ - 'int main(void) { disassemble_init_for_target(0); return 0; }' | \ - $(HOSTCC) -xc - -o /dev/null $$libs 2>/dev/null && \ + printf '%s\n' \ + '$(pound)include ' \ + '$(pound)include ' \ + 'int main(void) { disassemble_init_for_target(0); return 0; }' | \ + $(HOSTCC) $(HOSTCFLAGS) -DPACKAGE='"objtool"' -xc - -o /dev/null $$libs 2>/dev/null && \ echo "$$libs" && break; \ done) From 55fc280e951ab2b39f3dcb640edc1b1eebc6d173 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Mon, 31 Aug 2026 14:43:51 -0500 Subject: [PATCH 1613/2366] Input: tsc2007 - read "ti,poll-period" as u32 The "ti,poll-period" property is documented as a normal uint32 cell. The driver used a u64 helper, which makes the helper type disagree with the schema even though the stored value is still small. Read "ti,poll-period" with the u32 helper matching the documented DT cell size. Assisted-by: Codex:gpt-5-5 Signed-off-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260831194352.1185860-1-robh@kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/tsc2007_core.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/input/touchscreen/tsc2007_core.c b/drivers/input/touchscreen/tsc2007_core.c index e4d7da0f4434..e2f49b37e18c 100644 --- a/drivers/input/touchscreen/tsc2007_core.c +++ b/drivers/input/touchscreen/tsc2007_core.c @@ -221,7 +221,6 @@ static int tsc2007_get_pendown_state_gpio(struct device *dev) static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) { u32 val32; - u64 val64; if (!device_property_read_u32(dev, "ti,max-rt", &val32)) ts->max_rt = val32; @@ -237,8 +236,8 @@ static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) if (!device_property_read_u32(dev, "ti,fuzzz", &val32)) ts->fuzzz = val32; - if (!device_property_read_u64(dev, "ti,poll-period", &val64)) - ts->poll_period = msecs_to_jiffies(val64); + if (!device_property_read_u32(dev, "ti,poll-period", &val32)) + ts->poll_period = msecs_to_jiffies(val32); else ts->poll_period = msecs_to_jiffies(1); From 971fa7ea8621e123feb9c8d7dc61be1c656bd945 Mon Sep 17 00:00:00 2001 From: Erich Sartison Date: Thu, 3 Sep 2026 12:31:37 +0200 Subject: [PATCH 1614/2366] Input: xpad - add support for Victrix Pro BFG Controller The controller doesn't currently work via USB-cable. Signed-off-by: Erich Sartison Link: https://patch.msgid.link/20260903103137.630170-1-byt.es@mailbox.org Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 8e4e06b53c2a..6406ceab988a 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -227,6 +227,7 @@ static const struct xpad_device { { 0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360", 0, XTYPE_XBOX360 }, { 0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360", 0, XTYPE_XBOX360 }, { 0x0e6f, 0x0246, "Rock Candy Gamepad for Xbox One 2015", 0, XTYPE_XBOXONE }, + { 0x0e6f, 0x024c, "PDP Victrix Pro BFG Wired Controller for Xbox", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x02a0, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x02a1, "PDP Xbox One Controller", 0, XTYPE_XBOXONE }, { 0x0e6f, 0x02a2, "PDP Wired Controller for Xbox One - Crimson Red", 0, XTYPE_XBOXONE }, From 309731e95917125bbd13626a7a5600490a5bf44f Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 2 Sep 2026 23:40:04 +0800 Subject: [PATCH 1615/2366] Input: hp_sdc - shut down kicker timer on module exit hp_sdc_kicker() rearms hp_sdc.kicker with mod_timer() after scheduling the tasklet. The module exit path uses timer_delete_sync(). That waits for a callback already running but can still leave the timer rearmed. A callback can therefore leave the timer pending while hp_sdc_exit() tears down the driver, allowing timer activity to access dismantled driver state. Use timer_shutdown_sync() for final teardown. It waits for a running callback and prevents rearming after module exit begins. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Acked-by: Helge Deller Link: https://patch.msgid.link/20260902154004.3595416-1-runyu.xiao@seu.edu.cn Signed-off-by: Dmitry Torokhov --- drivers/input/serio/hp_sdc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/serio/hp_sdc.c b/drivers/input/serio/hp_sdc.c index 1461ef319f92..ecf5347b63ab 100644 --- a/drivers/input/serio/hp_sdc.c +++ b/drivers/input/serio/hp_sdc.c @@ -981,7 +981,7 @@ static void hp_sdc_exit(void) free_irq(hp_sdc.irq, &hp_sdc); write_unlock_irq(&hp_sdc.lock); - timer_delete_sync(&hp_sdc.kicker); + timer_shutdown_sync(&hp_sdc.kicker); tasklet_kill(&hp_sdc.task); From 1c21452d02eec2f008e2c5535820f85adbd7587a Mon Sep 17 00:00:00 2001 From: Pu Lehui Date: Sat, 5 Sep 2026 02:11:39 +0000 Subject: [PATCH 1616/2366] bpf: Fix UAF due to concurrent consumption of ttrace lists in alloc_bulk Syzkaller repeatedly triggered UAF splats related to nodes in waiting_for_gp_ttrace within the bpf memalloc: BUG: KASAN: slab-use-after-free in llist_del_first+0x85/0x110 lib/llist.c:61 Read of size 8 at addr ffff8881572cd080 by task syz.4.470/5112 ... llist_del_first+0x85/0x110 lib/llist.c:61 alloc_bulk+0x193/0x460 kernel/bpf/memalloc.c:229 bpf_mem_refill+0x386/0x560 kernel/bpf/memalloc.c:436 Freed by task 14: ... __free_rcu kernel/bpf/memalloc.c:281 [inline] __free_rcu_tasks_trace+0x48/0xd0 kernel/bpf/memalloc.c:291 rcu_tasks_invoke_cbs+0x1ec/0x3e0 kernel/rcu/tasks.h:571 rcu_tasks_one_gp+0x13d/0x220 kernel/rcu/tasks.h:621 rcu_tasks_kthread+0xf3/0x120 kernel/rcu/tasks.h:651 The reason is that the UAF occurs after the RCU Tasks Trace GP expires: when the __free_rcu() callback runs, there is no synchronization protecting llist_del_all() against concurrent alloc_bulk() operating on waiting_for_gp_ttrace, leading to the race condition below: CPU0 CPU1 __free_rcu (RCU Tasks Trace callback) alloc_bulk llist_del_first(&c->waiting_for_gp_ttrace) entry = smp_load_acquire(&head->first); do { if (entry == NULL) return NULL; free_all(llist_del_all(&c->waiting_for_gp_ttrace)) llist_for_each_safe(pos, t, llnode) free_one(pos); next = READ_ONCE(entry->next); <-- trigger UAF } while (!try_cmpxchg(&head->first, &entry, next)); In addition, there is also a theoretical race condition on the free_by_rcu_ttrace list. This race requires two preconditions: an in-flight Tasks Trace GP keeping c->call_rcu_ttrace_in_progress == 1, and concurrent cross-CPU frees repopulating c->free_by_rcu_ttrace with new nodes. Under these conditions, the following scenario triggers UAF: // CPU0 // irq work is still busy (on PREEMPT_RT) alloc_bulk() llist_del_first(&c->free_by_rcu_ttrace) entry = smp_load_acquire(&head->first); do { if (entry == NULL) return NULL; // CPU1 bpf_mem_alloc_destroy() WRITE_ONCE(c->draining, true) // wait for CPU0 irq_work_sync() // CPU2 do_call_rcu_ttrace(tgt(CPU0)) if (c->draining) { llist_del_all(&c->free_by_rcu_ttrace) free_all() } // CPU0 continue next = READ_ONCE(entry->next); <-- trigger UAF while (!try_cmpxchg(&head->first, &entry, next)); Fix this by introducing a raw spinlock to synchronize the concurrent consumption on waiting_for_gp_ttrace and free_by_rcu_ttrace. Fixes: 04fabf00b4d3 ("bpf: Allow reuse from waiting_for_gp_ttrace list.") Suggested-by: Alexei Starovoitov Suggested-by: Hou Tao Signed-off-by: Pu Lehui Acked-by: Hou Tao Link: https://lore.kernel.org/r/20260905021139.4116529-1-pulehui@huaweicloud.com Signed-off-by: Alexei Starovoitov --- kernel/bpf/memalloc.c | 50 ++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c index e9662db7198f..8a8f088e83e6 100644 --- a/kernel/bpf/memalloc.c +++ b/kernel/bpf/memalloc.c @@ -119,6 +119,7 @@ struct bpf_mem_cache { struct llist_head waiting_for_gp_ttrace; struct rcu_head rcu_ttrace; atomic_t call_rcu_ttrace_in_progress; + raw_spinlock_t lock; }; struct bpf_mem_caches { @@ -214,25 +215,24 @@ static void alloc_bulk(struct bpf_mem_cache *c, int cnt, int node, bool atomic) gfp = __GFP_NOWARN | __GFP_ACCOUNT; gfp |= atomic ? GFP_NOWAIT : GFP_KERNEL; - for (i = 0; i < cnt; i++) { - /* - * For every 'c' llist_del_first(&c->free_by_rcu_ttrace); is - * done only by one CPU == current CPU. Other CPUs might - * llist_add() and llist_del_all() in parallel. - */ - obj = llist_del_first(&c->free_by_rcu_ttrace); - if (!obj) - break; - add_obj_to_free_list(c, obj); - } - if (i >= cnt) - return; + /* + * c->lock serializes concurrent llist_del_first() against + * llist_del_all() in __free_rcu() and do_call_rcu_ttrace(). + */ + scoped_guard(raw_spinlock_irqsave, &c->lock) { + for (i = 0; i < cnt; i++) { + obj = llist_del_first(&c->free_by_rcu_ttrace); + if (!obj) + break; + add_obj_to_free_list(c, obj); + } - for (; i < cnt; i++) { - obj = llist_del_first(&c->waiting_for_gp_ttrace); - if (!obj) - break; - add_obj_to_free_list(c, obj); + for (; i < cnt; i++) { + obj = llist_del_first(&c->waiting_for_gp_ttrace); + if (!obj) + break; + add_obj_to_free_list(c, obj); + } } if (i >= cnt) return; @@ -279,8 +279,12 @@ static int free_all(struct bpf_mem_cache *c, struct llist_node *llnode, bool per static void __free_rcu(struct rcu_head *head) { struct bpf_mem_cache *c = container_of(head, struct bpf_mem_cache, rcu_ttrace); + struct llist_node *llnode; - free_all(c, llist_del_all(&c->waiting_for_gp_ttrace), !!c->percpu_size); + scoped_guard(raw_spinlock_irqsave, &c->lock) + llnode = llist_del_all(&c->waiting_for_gp_ttrace); + + free_all(c, llnode, !!c->percpu_size); atomic_set(&c->call_rcu_ttrace_in_progress, 0); } @@ -300,7 +304,8 @@ static void do_call_rcu_ttrace(struct bpf_mem_cache *c) if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 1)) { if (unlikely(READ_ONCE(c->draining))) { - llnode = llist_del_all(&c->free_by_rcu_ttrace); + scoped_guard(raw_spinlock_irqsave, &c->lock) + llnode = llist_del_all(&c->free_by_rcu_ttrace); free_all(c, llnode, !!c->percpu_size); } return; @@ -535,6 +540,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu) c->objcg = objcg; c->percpu_size = percpu_size; c->tgt = c; + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } @@ -557,7 +563,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu) c->objcg = objcg; c->percpu_size = percpu_size; c->tgt = c; - + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } @@ -609,7 +615,7 @@ int bpf_mem_alloc_percpu_unit_init(struct bpf_mem_alloc *ma, int size) c->objcg = objcg; c->percpu_size = percpu_size; c->tgt = c; - + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } From 451b1c19dc7cbbad194a6e717b6a732c443d7dd5 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Fri, 11 Sep 2026 02:39:25 +0800 Subject: [PATCH 1617/2366] hwmon: (k10temp) Fix model id range of Zen5 Turin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Model 20h-2Fh are mobile processors with single CCD. For example, model 24h is Strix Point, i.e., Ryzen AI 7 (PRO) (H/HX) 360/365/370. Including mobile processors in the model id range of Zen5 Turin processors leads to bogus reporting: k10temp-pci-00c3 Adapter: PCI adapter Tctl: +54.1°C Tccd4: +148.6°C Tccd6: +148.4°C Tccd7: +149.1°C Tccd8: +149.2°C Tccd9: +149.2°C Tccd12: +149.1°C Tccd14: +22.0°C Tccd15: +22.0°C Tccd16: +22.0°C Fix it by removing the said range. Fixes: 8440d5aca227 ("hwmon: (k10temp) Add per-CCD temperature monitoring for Zen5 Turin") Signed-off-by: Rong Zhang Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260911-k10temp-fix-zen5-epyc-v1-1-643f5a248ae1@rong.moe Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 75a45010d687..3e7e63edc6a3 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -523,7 +523,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) } } else if (boot_cpu_data.x86 == 0x1a) { switch (boot_cpu_data.x86_model) { - case 0x00 ... 0x2f: /* Zen5 Turin */ + case 0x00 ... 0x1f: /* Zen5 Turin */ data->ccd_offset = 0x1F0; k10temp_get_ccd_support(data, 16); break; From 26d5ff79768548efb1e604bb6e8697c101e06269 Mon Sep 17 00:00:00 2001 From: Yibo Tan Date: Fri, 11 Sep 2026 15:18:09 +0800 Subject: [PATCH 1618/2366] hwmon: (pwm-fan) Stop RPM timer before freeing tach data sample_timer() rearms the RPM timer and accesses the devm-managed ctx->tachs and ctx->pulses_per_revolution arrays. The cleanup action which stops the timer is registered before those arrays are allocated. Since devres releases entries in reverse order, driver detach can free the arrays before pwm_fan_cleanup() shuts down the timer. A timer expiry in that window accesses the freed tach data. With a KASAN kernel, a test-only kprobe delayed entry to pwm_fan_cleanup() while normal sysfs unbind ran. Each of three runs reported three four-byte reads and two four-byte writes in sample_timer() after its backing devm allocations had been freed. The helper did not invoke the timer callback, cleanup actions or free functions. With the fix, three matching unbind runs completed without KASAN, BUG, WARNING, Oops or panic. Instrumentation confirmed that timer retirement completed before the first timer backing allocation was released. Split timer retirement from the power cleanup and register its devres action after the timer backing data and IRQ actions are installed. This preserves the early power rollback action while ensuring the timer is retired before its backing data is released. Use timer_shutdown_sync() because the callback can rearm itself. Fixes: 01695410d452 ("hwmon: (pwm-fan) Store tach data separately") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Yibo Tan Link: https://patch.msgid.link/20260911071809.130151-1-lhfff@tju.edu.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/pwm-fan.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index 3b87f65bae05..c633d7f6464c 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -483,7 +483,6 @@ static void pwm_fan_cleanup(void *__ctx) { struct pwm_fan_ctx *ctx = __ctx; - timer_delete_sync(&ctx->rpm_timer); if (ctx->pwm_shutdown) { ctx->enable_mode = pwm_enable_reg_enable; __set_pwm(ctx, ctx->pwm_shutdown); @@ -494,6 +493,13 @@ static void pwm_fan_cleanup(void *__ctx) } } +static void pwm_fan_timer_cleanup(void *__ctx) +{ + struct pwm_fan_ctx *ctx = __ctx; + + timer_shutdown_sync(&ctx->rpm_timer); +} + static int pwm_fan_probe(struct platform_device *pdev) { struct thermal_cooling_device *cdev; @@ -644,6 +650,10 @@ static int pwm_fan_probe(struct platform_device *pdev) } if (ctx->tach_count > 0) { + ret = devm_add_action_or_reset(dev, pwm_fan_timer_cleanup, ctx); + if (ret) + return ret; + ctx->sample_start = ktime_get(); mod_timer(&ctx->rpm_timer, jiffies + HZ); @@ -700,6 +710,7 @@ static void pwm_fan_shutdown(struct platform_device *pdev) { struct pwm_fan_ctx *ctx = platform_get_drvdata(pdev); + pwm_fan_timer_cleanup(ctx); pwm_fan_cleanup(ctx); } From 7bae83ffb133bc373d098fa6828cef2ef4da49fe Mon Sep 17 00:00:00 2001 From: "Thomas Richard (congatec GmbH)" Date: Fri, 11 Sep 2026 19:31:58 +0200 Subject: [PATCH 1619/2366] hwmon: (cgbc-hwmon) Fix current sensors ID lookup Current sensors on the Congatec Board Controller don't use consecutive IDs, unlike other sensor types (voltage, temperature, fan). The driver assumed consecutive IDs and performed a simple lookup, which caused an unknown sensor warning. Define current sensor IDs explicitly. Changes the warning on conga-SA7 (type and channel are correct now). Before: Board Controller returned an unknown sensor (type=2, channel=17), ignore it After: Board Controller returned an unknown sensor (bc_type=1, bc_id=11), ignore it Cc: stable@kernel.org Fixes: 08ebc9def79f ("hwmon: Add Congatec Board Controller monitoring driver") Signed-off-by: Thomas Richard (congatec GmbH) Link: https://patch.msgid.link/20260911-cgbc-hwmon-fix-and-new-sensors-v2-1-0c6bf078d173@bootlin.com Signed-off-by: Guenter Roeck --- drivers/hwmon/cgbc-hwmon.c | 95 ++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/drivers/hwmon/cgbc-hwmon.c b/drivers/hwmon/cgbc-hwmon.c index 3aff4e092132..2effa0b56286 100644 --- a/drivers/hwmon/cgbc-hwmon.c +++ b/drivers/hwmon/cgbc-hwmon.c @@ -52,30 +52,35 @@ static const char * const cgbc_hwmon_labels_temp[] = { "BOTTOMDIM Temperature", }; -static const struct { - enum hwmon_sensor_types type; - const char *label; -} cgbc_hwmon_labels_in[] = { - { hwmon_in, "CPU Voltage" }, - { hwmon_in, "DC Runtime Voltage" }, - { hwmon_in, "DC Standby Voltage" }, - { hwmon_in, "CMOS Battery Voltage" }, - { hwmon_in, "Battery Voltage" }, - { hwmon_in, "AC Voltage" }, - { hwmon_in, "Other Voltage" }, - { hwmon_in, "5V Voltage" }, - { hwmon_in, "5V Standby Voltage" }, - { hwmon_in, "3V3 Voltage" }, - { hwmon_in, "3V3 Standby Voltage" }, - { hwmon_in, "VCore A Voltage" }, - { hwmon_in, "VCore B Voltage" }, - { hwmon_in, "12V Voltage" }, - { hwmon_curr, "DC Current" }, - { hwmon_curr, "5V Current" }, - { hwmon_curr, "12V Current" }, +static const char * const cgbc_hwmon_labels_in[] = { + "CPU Voltage", + "DC Runtime Voltage", + "DC Standby Voltage", + "CMOS Battery Voltage", + "Battery Voltage", + "AC Voltage", + "Other Voltage", + "5V Voltage", + "5V Standby Voltage", + "3V3 Voltage", + "3V3 Standby Voltage", + "VCore A Voltage", + "VCore B Voltage", + "12V Voltage", }; -#define CGBC_HWMON_NB_IN_SENSORS 14 +/* + * Current sensors are a bit special, they don't have consecutive IDs like + * other types of sensors. So they need to be defined explicitly. + */ +static const struct { + const char *label; + int id; +} cgbc_hwmon_labels_curr[] = { + { "DC Current", 0x12 }, + { "5V Current", 0x18 }, + { "12V Current", 0x1E }, +}; static const char * const cgbc_hwmon_labels_fan[] = { "CPU Fan", @@ -114,7 +119,8 @@ static int cgbc_hwmon_probe_sensors(struct device *dev, struct cgbc_hwmon_data * for (i = 0; i < nb_sensors; i++) { enum cgbc_sensor_types type; - unsigned int channel; + unsigned int channel, id; + int j; /* * No need to request data for the first sensor. @@ -128,32 +134,49 @@ static int cgbc_hwmon_probe_sensors(struct device *dev, struct cgbc_hwmon_data * } type = FIELD_GET(CGBC_HWMON_TYPE_MASK, data[1]); - channel = FIELD_GET(CGBC_HWMON_ID_MASK, data[1]) - 1; + id = FIELD_GET(CGBC_HWMON_ID_MASK, data[1]); + channel = id - 1; if (type == CGBC_HWMON_TYPE_TEMP && channel < ARRAY_SIZE(cgbc_hwmon_labels_temp)) { sensor->type = hwmon_temp; sensor->label = cgbc_hwmon_labels_temp[channel]; - } else if (type == CGBC_HWMON_TYPE_IN && - channel < ARRAY_SIZE(cgbc_hwmon_labels_in)) { + } else if (type == CGBC_HWMON_TYPE_IN) { /* * The Board Controller doesn't differentiate current and voltage sensors. - * Get the sensor type from cgbc_hwmon_labels_in[channel].type instead. + * First check if it is a current sensor. */ - sensor->type = cgbc_hwmon_labels_in[channel].type; - sensor->label = cgbc_hwmon_labels_in[channel].label; + for (j = 0; j < ARRAY_SIZE(cgbc_hwmon_labels_curr); j++) { + if (id == cgbc_hwmon_labels_curr[j].id) { + sensor->type = hwmon_curr; + sensor->label = cgbc_hwmon_labels_curr[j].label; + channel = j; + } + } + + /* If it's not a current sensor, it may be a voltage sensor. */ + if (!sensor->label && channel < ARRAY_SIZE(cgbc_hwmon_labels_in)) { + sensor->type = hwmon_in; + sensor->label = cgbc_hwmon_labels_in[channel]; + } } else if (type == CGBC_HWMON_TYPE_FAN && channel < ARRAY_SIZE(cgbc_hwmon_labels_fan)) { sensor->type = hwmon_fan; sensor->label = cgbc_hwmon_labels_fan[channel]; - } else { - dev_warn(dev, "Board Controller returned an unknown sensor (type=%d, channel=%d), ignore it", - type, channel); + } + + if (!sensor->label) { + dev_warn(dev, "Board Controller returned an unknown sensor (bc_type=%d, bc_id=%d), ignore it", + type, id); continue; } sensor->active = FIELD_GET(CGBC_HWMON_ACTIVE_BIT, data[1]); sensor->channel = channel; sensor->index = i; + + dev_dbg(dev, "Found sensor: bc_type=%d, bc_id=%d, hwmon_type=%d, hwmon_channel=%d, hwmon_label='%s', active=%d\n", + type, id, sensor->type, sensor->channel, sensor->label, sensor->active); + sensor++; hwmon->nb_sensors++; } @@ -167,14 +190,6 @@ static struct cgbc_hwmon_sensor *cgbc_hwmon_find_sensor(struct cgbc_hwmon_data * struct cgbc_hwmon_sensor *sensor = NULL; int i; - /* - * The Board Controller doesn't differentiate current and voltage sensors. - * The channel value (from the Board Controller point of view) shall be computed for current - * sensors. - */ - if (type == hwmon_curr) - channel += CGBC_HWMON_NB_IN_SENSORS; - for (i = 0; i < hwmon->nb_sensors; i++) { if (hwmon->sensors[i].type == type && hwmon->sensors[i].channel == channel) { sensor = &hwmon->sensors[i]; From 3550d1dbbcb9f51b77e077e8958423ee2c401c6c Mon Sep 17 00:00:00 2001 From: "Thomas Richard (congatec GmbH)" Date: Fri, 11 Sep 2026 19:31:59 +0200 Subject: [PATCH 1620/2366] hwmon: (cgbc-hwmon) Add missing sensors Add the following sensors: - Alternate Board Temperature (temp11_input) - Top DIMM 1-7 Temperature (temp12_input to temp18_input) - Bottom DIMM 1 Temperature (temp19_input) - 12V Standby Voltage (in14_input) This fixes the following warning on conga-SA7: Board Controller returned an unknown sensor (bc_type=1, bc_id=11), ignore it Also update existing labels to match Congatec documentation. Cc: stable@kernel.org Fixes: 08ebc9def79f ("hwmon: Add Congatec Board Controller monitoring driver") Signed-off-by: Thomas Richard (congatec GmbH) Link: https://patch.msgid.link/20260911-cgbc-hwmon-fix-and-new-sensors-v2-2-0c6bf078d173@bootlin.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/cgbc-hwmon.rst | 42 +++++++++++++++--------- drivers/hwmon/cgbc-hwmon.c | 52 +++++++++++++++++++----------- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/Documentation/hwmon/cgbc-hwmon.rst b/Documentation/hwmon/cgbc-hwmon.rst index 3a5e6e6e8639..c6d09232392a 100644 --- a/Documentation/hwmon/cgbc-hwmon.rst +++ b/Documentation/hwmon/cgbc-hwmon.rst @@ -28,34 +28,44 @@ system. Name Description ============= ====================== temp1_input CPU temperature -temp2_input Box temperature +temp2_input Case temperature temp3_input Ambient temperature -temp4_input Board temperature -temp5_input Carrier temperature -temp6_input Chipset temperature -temp7_input Video temperature +temp4_input CPU Board temperature +temp5_input Carrier Board temperature +temp6_input System Chipset temperature +temp7_input Video Controller/Board temperature temp8_input Other temperature -temp9_input TOPDIM temperature -temp10_input BOTTOMDIM temperature -in0_input CPU voltage +temp9_input Top DIMM 0 temperature +temp10_input Bottom DIMM 0 temperature +temp11_input Alternate Board temperature +temp12_input Top DIMM 1 temperature +temp13_input Top DIMM 2 temperature +temp14_input Top DIMM 3 temperature +temp15_input Top DIMM 4 temperature +temp16_input Top DIMM 5 temperature +temp17_input Top DIMM 6 temperature +temp18_input Top DIMM 7 temperature +temp19_input Bottom DIMM 1 temperature +in0_input CPU Core voltage in1_input DC Runtime voltage in2_input DC Standby voltage in3_input CMOS Battery voltage -in4_input Battery voltage +in4_input Battery Supply voltage in5_input AC voltage in6_input Other voltage -in7_input 5V voltage +in7_input 5V Runtime voltage in8_input 5V Standby voltage -in9_input 3V3 voltage +in9_input 3V3 Runtime voltage in10_input 3V3 Standby voltage in11_input VCore A voltage in12_input VCore B voltage -in13_input 12V voltage -curr1_input DC current -curr2_input 5V current -curr3_input 12V current +in13_input 12V Runtime voltage +in14_input 12V Standby voltage +curr1_input DC Runtime current +curr2_input 5V Runtime current +curr3_input 12V Runtime current fan1_input CPU fan -fan2_input Box fan +fan2_input Case fan fan3_input Ambient fan fan4_input Chiptset fan fan5_input Video fan diff --git a/drivers/hwmon/cgbc-hwmon.c b/drivers/hwmon/cgbc-hwmon.c index 2effa0b56286..230062a46907 100644 --- a/drivers/hwmon/cgbc-hwmon.c +++ b/drivers/hwmon/cgbc-hwmon.c @@ -41,32 +41,42 @@ enum cgbc_sensor_types { static const char * const cgbc_hwmon_labels_temp[] = { "CPU Temperature", - "Box Temperature", + "Case Temperature", "Ambient Temperature", - "Board Temperature", - "Carrier Temperature", - "Chipset Temperature", - "Video Temperature", + "CPU Board Temperature", + "Carrier Board Temperature", + "System Chipset Temperature", + "Video Controller/Board Temperature", "Other Temperature", - "TOPDIM Temperature", - "BOTTOMDIM Temperature", + "Top DIMM 0 Temperature", + "Bottom DIMM 0 Temperature", + "Alternate Board Temperature", + "Top DIMM 1 Temperature", + "Top DIMM 2 Temperature", + "Top DIMM 3 Temperature", + "Top DIMM 4 Temperature", + "Top DIMM 5 Temperature", + "Top DIMM 6 Temperature", + "Top DIMM 7 Temperature", + "Bottom DIMM 1 Temperature", }; static const char * const cgbc_hwmon_labels_in[] = { - "CPU Voltage", + "CPU Core Voltage", "DC Runtime Voltage", "DC Standby Voltage", "CMOS Battery Voltage", - "Battery Voltage", + "Battery Supply Voltage", "AC Voltage", "Other Voltage", - "5V Voltage", + "5V Runtime Voltage", "5V Standby Voltage", - "3V3 Voltage", + "3V3 Runtime Voltage", "3V3 Standby Voltage", "VCore A Voltage", "VCore B Voltage", - "12V Voltage", + "12V Runtime Voltage", + "12V Standby Voltage", }; /* @@ -77,14 +87,14 @@ static const struct { const char *label; int id; } cgbc_hwmon_labels_curr[] = { - { "DC Current", 0x12 }, - { "5V Current", 0x18 }, - { "12V Current", 0x1E }, + { "DC Runtime Current", 0x12 }, + { "5V Runtime Current", 0x18 }, + { "12V Runtime Current", 0x1E }, }; static const char * const cgbc_hwmon_labels_fan[] = { "CPU Fan", - "Box Fan", + "Case Fan", "Ambient Fan", "Chipset Fan", "Video Fan", @@ -255,7 +265,12 @@ static const struct hwmon_channel_info * const cgbc_hwmon_info[] = { HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, - HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, @@ -263,7 +278,8 @@ static const struct hwmon_channel_info * const cgbc_hwmon_info[] = { HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, - HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL), + HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL), From 06bd6794b5fd2163880ac3bfe973d4cc61f359f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 11 Sep 2026 14:53:37 +0100 Subject: [PATCH 1621/2366] hwmon: (pmbus/core) increase number of phases and add new mask MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Increase the number of phases to 16 as a new upcoming device supports such a number. While at it, add a new mask for controlling the source of the output voltage. Note (groeck): This patch was meant to prepare for support of MAX20826 and compatible devices, which support more than 10 phases per page. However, Sashiko reports that the mp2975 driver already supports up to 14 phases, and the mp2856 driver supports up to 12 phases. This already has the potential for out-of-bounds writes when probing the affected chips, making this patch a bug fix. Fixes: 2c6fcbb21149 ("hwmon: (pmbus) Add support for MPS Multi-phase mp2975 controller") Fixes: f9e5f289b686 ("hwmon: (pmbus) Add support for MPS Multi-phase mp2856/mp2857 controller") Signed-off-by: Nuno Sá Link: https://patch.msgid.link/20260911-hwmon-max20826-support-v2-1-5e30cbd97d84@analog.com Cc: stable@vger.kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 2cd3216b3cd9..920c1102ab6d 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -242,6 +242,7 @@ enum pmbus_regs { /* * OPERATION */ +#define PB_OPERATION_CONTROL_V_SRC GENMASK(5, 4) #define PB_OPERATION_CONTROL_ON BIT(7) /* @@ -386,7 +387,7 @@ enum pmbus_sensor_classes { }; #define PMBUS_PAGES 32 /* Per PMBus specification */ -#define PMBUS_PHASES 10 /* Maximum number of phases per page */ +#define PMBUS_PHASES 16 /* Maximum number of phases per page */ /* Functionality bit mask */ #define PMBUS_HAVE_VIN BIT(0) From 490a83d6386eec1d29f470c8d7331677fb46c3b7 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Tue, 8 Sep 2026 17:08:32 +0800 Subject: [PATCH 1622/2366] bpf, sockmap: Fix self-redirect copied_seq double-counting When a BPF stream_verdict program redirects an skb back to the same socket (self-redirect with BPF_F_INGRESS), sk_psock_verdict_apply() calls tcp_eat_skb() which advances tcp_sk->copied_seq. However, the skb is then delivered to the socket's psock ingress queue and later read by tcp_bpf_recvmsg_parser(), which also advances copied_seq via the copied_from_self accounting path. This double-counting causes copied_seq to advance by 2x the actual data length, triggering: TCP recvmsg seq # bug 2: copied BF2E806, seq BF2E7FD, \ rcvnxt BF2E806, fl 0 WARNING: net/ipv4/tcp.c:2745 at tcp_recvmsg_locked+0x72b/0x2640 Call Trace: tcp_recvmsg+0x10a/0x500 sock_recvmsg+0x168/0x1d0 __sys_recvfrom+0x19a/0x2a0 __x64_sys_recvfrom+0xe4/0x1f0 do_syscall_64+0xf7/0x530 entry_SYSCALL_64_after_hwframe+0x77/0x7f cleanup rbuf bug: copied BF2E806 seq BF2E806 rcvnxt BF2E806 WARNING: net/ipv4/tcp.c:1609 at tcp_cleanup_rbuf+0xf2/0x1c0 Call Trace: tcp_recvmsg_locked+0x8d1/0x2640 tcp_recvmsg+0x10a/0x500 sock_recvmsg+0x168/0x1d0 __sys_recvfrom+0x19a/0x2a0 __x64_sys_recvfrom+0xe4/0x1f0 do_syscall_64+0xf7/0x530 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fix this by converting self-redirect verdict to __SK_PASS at the beginning of sk_psock_verdict_apply(). This bypasses the __SK_REDIRECT case entirely (which calls sk_psock_eat_skb), letting the __SK_PASS path queue the skb to the psock ingress queue. The data is then read via tcp_bpf_recvmsg_parser(), which advances copied_seq exactly once through copied_from_self. Cross-socket redirects continue through __SK_REDIRECT with sk_psock_eat_skb() unchanged. Fixes: e5c6de5fa025 ("bpf, sockmap: Incorrectly handling copied_seq") Suggested-by: Jakub Sitnicki Suggested-by: Jiayuan Chen Signed-off-by: Geliang Tang Reviewed-by: Emil Tsalapatis Reviewed-by: Jiayuan Chen Link: https://lore.kernel.org/r/1a8e797a1b26e2f695aaac22ac644c2862f63466.1788858299.git.tanggeliang@kylinos.cn Signed-off-by: Alexei Starovoitov --- net/core/skmsg.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/core/skmsg.c b/net/core/skmsg.c index 2521b643fa05..df385a5a961e 100644 --- a/net/core/skmsg.c +++ b/net/core/skmsg.c @@ -1000,6 +1000,10 @@ static int sk_psock_verdict_apply(struct sk_psock *psock, struct sk_buff *skb, int err = 0; u32 len, off; + if (verdict == __SK_REDIRECT && skb_bpf_ingress(skb) && + skb_bpf_redirect_fetch(skb) == psock->sk) + verdict = __SK_PASS; + switch (verdict) { case __SK_PASS: err = -EIO; From 953824e508b27d12837e32ef37ef6248e1f6fc7a Mon Sep 17 00:00:00 2001 From: Masoud Aghasi Date: Thu, 3 Sep 2026 09:27:34 +0100 Subject: [PATCH 1623/2366] bpf: Fix u32 overflow issue in map batch operations Several map batch operation implementations such as generic_map_lookup_batch() use calculations in the form of "values + cp * map->value_size" to compute the desired userspace memory address for reading or writing. This can overflow the u32 type (the result of "cp * map->value_size") when the map size exceeds 4GB. generic_map_lookup_batch() may corrupt values for some keys in userspace memory, and in some cases it mismatches values for some keys while still reporting success. Other batch operations may fail to delete or update some keys, or the syscall may return unexpected errors. Add size_t casts to prevent the affected offset and size calculations from overflowing. Fixes: cb4d03ab499d ("bpf: Add generic support for lookup batch op") Fixes: aa2e93b8e58e ("bpf: Add generic support for update and delete batch ops") Fixes: 057996380a42 ("bpf: Add batch ops to all htab bpf map") Signed-off-by: Masoud Aghasi Link: https://lore.kernel.org/r/20260903082734.623904-1-maghasi@disroot.org Signed-off-by: Alexei Starovoitov --- kernel/bpf/hashtab.c | 8 ++++---- kernel/bpf/syscall.c | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index a72dc5b9f184..4f495dcbf670 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -1997,10 +1997,10 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, rcu_read_unlock(); bpf_enable_instrumentation(); - if (bucket_cnt && (copy_to_user(ukeys + total * key_size, keys, - key_size * bucket_cnt) || - copy_to_user(uvalues + total * value_size, values, - value_size * bucket_cnt))) { + if (bucket_cnt && (copy_to_user(ukeys + (size_t)total * key_size, keys, + (size_t)key_size * bucket_cnt) || + copy_to_user(uvalues + (size_t)total * value_size, values, + (size_t)value_size * bucket_cnt))) { ret = -EFAULT; goto after_loop; } diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index c7bc9ba9b331..853b47f81384 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2036,7 +2036,7 @@ int generic_map_delete_batch(struct bpf_map *map, for (cp = 0; cp < max_count; cp++) { err = -EFAULT; - if (copy_from_user(key, keys + cp * map->key_size, + if (copy_from_user(key, keys + (size_t)cp * map->key_size, map->key_size)) break; @@ -2098,9 +2098,9 @@ int generic_map_update_batch(struct bpf_map *map, struct file *map_file, for (cp = 0; cp < max_count; cp++) { err = -EFAULT; - if (copy_from_user(key, keys + cp * map->key_size, + if (copy_from_user(key, keys + (size_t)cp * map->key_size, map->key_size) || - copy_from_user(value, values + cp * value_size, value_size)) + copy_from_user(value, values + (size_t)cp * value_size, value_size)) break; err = bpf_map_update_value(map, map_file, key, value, @@ -2179,12 +2179,12 @@ int generic_map_lookup_batch(struct bpf_map *map, if (err) goto free_buf; - if (copy_to_user(keys + cp * map->key_size, key, + if (copy_to_user(keys + (size_t)cp * map->key_size, key, map->key_size)) { err = -EFAULT; goto free_buf; } - if (copy_to_user(values + cp * value_size, value, value_size)) { + if (copy_to_user(values + (size_t)cp * value_size, value, value_size)) { err = -EFAULT; goto free_buf; } From ef1fb82f12186dd26153b14d9fbcf4ec98db81b3 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 22:06:23 +0900 Subject: [PATCH 1624/2366] bpf, arm64: set up the frame pointer for the exception callback A program acting as exception boundary saves all callee-saved registers, so build_prologue() takes the exception_cb path and never calls push_callee_regs(). That is the only place find_used_callee_regs() runs, and with it the only place ctx->fp_used is set, so the callback prologue does not emit the mov x25, sp that points BPF_REG_FP at the frame the callback runs on. x25 keeps whatever it held when bpf_throw() was called. If the throw came from a subprogram that uses its own BPF stack, that is the subprogram's frame pointer, and since the subprogram never returns it never restores x25 either. Stack accesses through BPF_REG_FP are rewritten to be stack pointer relative, so those still land in the callback's own frame. Materializing the register does not: a callback that passes the address of a local variable to a helper hands over an address in the dead subprogram's frame. That address is below the callback's stack pointer by then, and the helper's own call chain covers it, so the helper can write over its own return address. 0x1234 below is the value the helper was asked to store: pc : 0x1234 lr : 0x1234 Call trace: 0x1234 (P) bpf_test_run+0x188/0x3e0 bpf_prog_test_run_skb+0x47c/0x998 __sys_bpf+0xbdc/0xdd8 Kernel panic - not syncing: Oops: Fatal exception in interrupt Set ctx->fp_used on the exception callback path so that the existing code further down sets x25 from the stack pointer. The epilogue restores it from the main program's save area along with the other callee-saved registers, as it already does. x86 sets the frame pointer for the callback from the argument it is passed, and powerpc computes it from the stack pointer. Fixes: 5d4fa9ec5643 ("bpf, arm64: Avoid blindly saving/restoring all callee-saved registers") Acked-by: Xu Kuohai Signed-off-by: Donggeun Yoo Link: https://lore.kernel.org/r/20260907130624.611942-2-donggeunyoo.kernel@gmail.com Signed-off-by: Alexei Starovoitov --- arch/arm64/net/bpf_jit_comp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c index c18e005a41db..c5f55d6161fe 100644 --- a/arch/arm64/net/bpf_jit_comp.c +++ b/arch/arm64/net/bpf_jit_comp.c @@ -600,6 +600,8 @@ static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf) * 12 registers are on the stack */ emit(A64_SUB_I(1, A64_SP, A64_FP, 96), ctx); + /* The callback may use its own BPF stack, set up fp for it. */ + ctx->fp_used = true; } /* Stack must be multiples of 16B */ From 26a43a5f8c31b9132dc40a449d0f6c7ecfaa1f40 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 7 Sep 2026 22:06:24 +0900 Subject: [PATCH 1625/2366] selftests/bpf: cover the exception callback using its own BPF stack The existing exception tests do not reach a callback that materializes BPF_REG_FP into a register. They either throw from the main program, where BPF_REG_FP already holds the value the callback needs, or use a callback whose only stack accesses are frame pointer relative, which the arm64 JIT rewrites to be stack pointer relative. Add a test that throws from a subprogram using its own BPF stack, with a callback that hands the address of a local variable to bpf_probe_read_kernel(). The helper and the callback have to name the same slot for the value read back to be the one the helper stored. Signed-off-by: Donggeun Yoo Link: https://lore.kernel.org/r/20260907130624.611942-3-donggeunyoo.kernel@gmail.com Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/prog_tests/exceptions.c | 1 + .../testing/selftests/bpf/progs/exceptions.c | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/exceptions.c b/tools/testing/selftests/bpf/prog_tests/exceptions.c index 3588d6f97fd4..639866ce09a9 100644 --- a/tools/testing/selftests/bpf/prog_tests/exceptions.c +++ b/tools/testing/selftests/bpf/prog_tests/exceptions.c @@ -55,6 +55,7 @@ static void test_exceptions_success(void) RUN_SUCCESS(exception_ext, 0); RUN_SUCCESS(exception_ext_mod_cb_runtime, 35); RUN_SUCCESS(exception_throw_subprog, 1); + RUN_SUCCESS(exception_throw_subprog_stack_cb, 0x1234); RUN_SUCCESS(exception_assert_nz_gfunc, 1); RUN_SUCCESS(exception_assert_zero_gfunc, 1); RUN_SUCCESS(exception_assert_neg_gfunc, 1); diff --git a/tools/testing/selftests/bpf/progs/exceptions.c b/tools/testing/selftests/bpf/progs/exceptions.c index c8d716fbd419..91c81971e58c 100644 --- a/tools/testing/selftests/bpf/progs/exceptions.c +++ b/tools/testing/selftests/bpf/progs/exceptions.c @@ -212,6 +212,36 @@ int exception_throw_subprog(struct __sk_buff *ctx) return 0; } +u64 exception_cb_stack_src = 0x1234; + +/* + * The address handed to the helper has to be this callback's own stack + * slot, not one from a frame that is already gone. + */ +__noinline int exception_cb_stack(u64 cookie) +{ + volatile u64 val = 0xdead; + + bpf_probe_read_kernel((void *)&val, sizeof(val), &exception_cb_stack_src); + return val; +} + +/* Throws from a subprogram that has a stack of its own. */ +__noinline static int throwing_subprog_stack(struct __sk_buff *ctx) +{ + volatile u64 pad[4] = {}; + + bpf_throw(pad[0]); + return 0; +} + +SEC("tc") +__exception_cb(exception_cb_stack) +int exception_throw_subprog_stack_cb(struct __sk_buff *ctx) +{ + return throwing_subprog_stack(ctx); +} + __noinline int assert_nz_gfunc(u64 c) { volatile u64 cookie = c; From 2de887f891662814b1160cbfb8c1bf2a5a46d418 Mon Sep 17 00:00:00 2001 From: Xavier Goffin Date: Sun, 13 Sep 2026 23:11:55 +0200 Subject: [PATCH 1626/2366] ALSA: hda/realtek: Add mute LED quirk for HP OMEN 15-ax On the HP OMEN 15 ax-202nf, the keyboard mute LED is exposed through NID 0x1b rather than 0x18. This reuses the existing quirk (ALC269_FIXUP_HP_MUTE_LED_MIC3) to allow the keyboard LED to reflect the built-in speaker mute state. Tested on HP OMEN 15 ax-202nf with Realtek ALC295: - hda::mute/brightness properly follows mute state - mute/unmute via keyboard shortcut or via GUI volume control - state is kept on suspend & resume, and reboot - plugging a 3.5mm jack headset reflects the headset mute status - unplugging reverts the LED to the speaker mute status - USB/Bluetooth headsets are not covered Signed-off-by: Xavier Goffin Link: https://patch.msgid.link/20260913211155.20305-1-xaviergoffin42@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index dc73fa95b00a..fa83b739fb50 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7358,6 +7358,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8158, "HP", ALC256_FIXUP_HP_HEADSET_MIC), SND_PCI_QUIRK(0x103c, 0x820d, "HP Pavilion 15", ALC295_FIXUP_HP_X360), SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), + SND_PCI_QUIRK(0x103c, 0x8259, "HP OMEN 15-ax202nf", ALC269_FIXUP_HP_MUTE_LED_MIC3), SND_PCI_QUIRK(0x103c, 0x827e, "HP x360", ALC295_FIXUP_HP_X360), SND_PCI_QUIRK(0x103c, 0x827f, "HP x360", ALC269_FIXUP_HP_MUTE_LED_MIC3), SND_PCI_QUIRK(0x103c, 0x82bf, "HP G3 mini", ALC221_FIXUP_HP_MIC_NO_PRESENCE), From beb34fe8312eda37b9cf1568550d722530444822 Mon Sep 17 00:00:00 2001 From: Kitty Makin Date: Mon, 14 Sep 2026 00:24:24 +0000 Subject: [PATCH 1627/2366] ALSA: usb-audio: Add capture quirk for Behringer FCA1616 The Behringer FCA1616 (1397:0004) returns silent capture samples unless its playback endpoint is active. Use the existing fixed implicit-feedback mechanism to keep playback endpoint 0x01 on interface 1 active during capture. Tested with 16-channel S32_LE capture at 44.1 and 48 kHz. Signed-off-by: Kitty Makin Link: https://patch.msgid.link/20260914002334.12691-1-autumnull@posteo.net Signed-off-by: Takashi Iwai --- sound/usb/implicit.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/usb/implicit.c b/sound/usb/implicit.c index 77f06da93151..bd4d569a8190 100644 --- a/sound/usb/implicit.c +++ b/sound/usb/implicit.c @@ -76,6 +76,7 @@ static const struct snd_usb_implicit_fb_match playback_implicit_fb_quirks[] = { /* Implicit feedback quirk table for capture: only FIXED type */ static const struct snd_usb_implicit_fb_match capture_implicit_fb_quirks[] = { + IMPLICIT_FB_FIXED_DEV(0x1397, 0x0004, 0x01, 1), /* Behringer FCA1616 */ {} /* terminator */ }; From 5f90f85eae4e9d2e9628b2019870994ba830b533 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 9 Sep 2026 14:37:07 +0200 Subject: [PATCH 1628/2366] power: sequencing: don't call .post_enable() if pwrseq_unit_enable() failed If the call to pwrseq_unit_enable() failed in pwrseq_enable(), bail out instead of calling target->post_enable() which assumes the target was successfully enabled. Fixes: 249ebf3f65f8 ("power: sequencing: implement the pwrseq core") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260909-pwrseq-kunit-v2-1-ef496afc89d2@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 721e888b658d..76c39600f690 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -912,6 +912,8 @@ int pwrseq_enable(struct pwrseq_desc *desc) if (!ret) desc->powered_on = true; } + if (ret) + return ret; if (target->post_enable) { ret = target->post_enable(pwrseq); From 115b303e8e093d964089ec6f3c40d984d77b33d0 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 9 Sep 2026 14:37:08 +0200 Subject: [PATCH 1629/2366] power: sequencing: fix NULL-pointer dereference in pwrseq_unit_new() If memory allocation fails in pwrseq_unit_setup_deps(), pwrseq_unit_put() is called to release the partially initialized unit. However, we've never initialized unit->list and pwrseq_unit_release() will unconditionally call list_del() on it. Initialize unit->list right after allocating the unit struct. Fixes: 249ebf3f65f8 ("power: sequencing: implement the pwrseq core") Cc: stable@vger.kernel.org Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260903-pwrseq-kunit-v1-0-1f893d2cabc2%40oss.qualcomm.com?part=1 Link: https://patch.msgid.link/20260909-pwrseq-kunit-v2-2-ef496afc89d2@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 76c39600f690..7751ce8cd8f5 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -101,6 +101,7 @@ static struct pwrseq_unit *pwrseq_unit_new(const struct pwrseq_unit_data *data) } kref_init(&unit->ref); + INIT_LIST_HEAD(&unit->list); INIT_LIST_HEAD(&unit->deps); unit->enable = data->enable; unit->disable = data->disable; From 242da4318d97380741516b595af3920207b2f0f1 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 9 Sep 2026 14:37:09 +0200 Subject: [PATCH 1630/2366] power: sequencing: fix NULL-pointer dereference in pwrseq_device_register() If dev_set_name() fails in pwrseq_device_register(), we jump to the err_put_pwrseq label before initializing pwrseq->targets. pwrseq_release() will try to iterate over targets unconditionally and subsequently dereference an invalid pointer. Move the call to dev_set_name() after the list head is initialized. Fixes: 249ebf3f65f8 ("power: sequencing: implement the pwrseq core") Cc: stable@vger.kernel.org Reported-by: sashiko-bot Closes: https://sashiko.dev/#/patchset/20260903-pwrseq-kunit-v1-0-1f893d2cabc2%40oss.qualcomm.com?part=2 Link: https://patch.msgid.link/20260909-pwrseq-kunit-v2-3-ef496afc89d2@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c index 7751ce8cd8f5..392d72537485 100644 --- a/drivers/power/sequencing/core.c +++ b/drivers/power/sequencing/core.c @@ -505,10 +505,6 @@ pwrseq_device_register(const struct pwrseq_config *config) */ device_initialize(&pwrseq->dev); - ret = dev_set_name(&pwrseq->dev, "pwrseq.%d", pwrseq->id); - if (ret) - goto err_put_pwrseq; - pwrseq->owner = config->owner ?: THIS_MODULE; pwrseq->match = config->match; @@ -517,6 +513,10 @@ pwrseq_device_register(const struct pwrseq_config *config) INIT_LIST_HEAD(&pwrseq->targets); INIT_LIST_HEAD(&pwrseq->units); + ret = dev_set_name(&pwrseq->dev, "pwrseq.%d", pwrseq->id); + if (ret) + goto err_put_pwrseq; + ret = pwrseq_setup_targets(config->targets, pwrseq); if (ret) goto err_put_pwrseq; From 8dc2615d5702059b2b71fca6f93c0d7d10ae54cb Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Fri, 11 Sep 2026 16:30:58 +0900 Subject: [PATCH 1631/2366] arm64: dts: renesas: r8a779f0: Set UFS lane count Since commit e72323f3b09f ("scsi: ufs: core: Configure only active lanes during link"), the following error is observed on R-Car S4: ufshcd-renesas e6860000.ufs: Tx lane mismatch [config,reported] [2,1] ufshcd-renesas e6860000.ufs: link startup failed -67 ufshcd-renesas e6860000.ufs: error -ENOLINK: Initialization failed with error -67 ufshcd-renesas e6860000.ufs: probe with driver ufshcd-renesas failed with error -67 R-Car S4 has one UFS lane per direction, as described in section 152.1 of its hardware manual. Without lanes-per-direction, the UFS platform driver defaults to two lanes. Previously, the core used PA_CONNECTEDRXDATALANES and PA_CONNECTEDTXDATALANES to configure the link without checking them against lanes-per-direction, so the missing property did not prevent initialization. Explicitly set lanes-per-direction to 1, now that the validation is in place. Fixes: 5235d551779d ("arm64: dts: renesas: r8a779f0: Add UFS node") Cc: stable@vger.kernel.org # 7.2+ Signed-off-by: Koichiro Den Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260911073058.253000-1-den@valinux.co.jp Signed-off-by: Geert Uytterhoeven --- arch/arm64/boot/dts/renesas/r8a779f0.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/renesas/r8a779f0.dtsi b/arch/arm64/boot/dts/renesas/r8a779f0.dtsi index cbb161c863ac..a5118c2f2742 100644 --- a/arch/arm64/boot/dts/renesas/r8a779f0.dtsi +++ b/arch/arm64/boot/dts/renesas/r8a779f0.dtsi @@ -901,6 +901,7 @@ ufs: ufs@e6860000 { clocks = <&cpg CPG_MOD 1514>, <&ufs30_clk>; clock-names = "fck", "ref_clk"; freq-table-hz = <200000000 200000000>, <38400000 38400000>; + lanes-per-direction = <1>; power-domains = <&sysc R8A779F0_PD_ALWAYS_ON>; resets = <&cpg 1514>; status = "disabled"; From 692f32609a30f75ca3401e25b504bfd06bd5662a Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 17 Aug 2026 12:22:11 -0400 Subject: [PATCH 1632/2366] pinctrl: meson: Fix typo in s4 group name One of the i2c pin groups has some junk at the end. The name should be i2c2_scl_h1, and indeed that's the name used by i2c2_pins3 in meson-s4.dtsi. Fixes: 775214d389c25 ("pinctrl: meson: add pinctrl driver support for Meson-S4 Soc") Signed-off-by: Sean Anderson Reviewed-by: Neil Armstrong Signed-off-by: Linus Walleij --- drivers/pinctrl/meson/pinctrl-meson-s4.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/meson/pinctrl-meson-s4.c b/drivers/pinctrl/meson/pinctrl-meson-s4.c index 872948699e9f..365dafe457a9 100644 --- a/drivers/pinctrl/meson/pinctrl-meson-s4.c +++ b/drivers/pinctrl/meson/pinctrl-meson-s4.c @@ -854,7 +854,7 @@ static const char * const i2c1_groups[] = { static const char * const i2c2_groups[] = { "i2c2_sda_d", "i2c2_scl_d", "i2c2_sda_h8", "i2c2_scl_h9", - "i2c2_sda_h0", "i2c2_scl_h1l," + "i2c2_sda_h0", "i2c2_scl_h1", }; static const char * const i2c3_groups[] = { From 50fd0ada8d37587223001600933270b59cb30e19 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Mon, 14 Sep 2026 13:15:37 +0800 Subject: [PATCH 1633/2366] gpio: virtuser: skip free_irq when no IRQ is installed Disabling interrupt monitoring uses atomic_xchg() to clear the stored IRQ. When monitoring is already disabled, atomic_xchg() returns 0. It must not be passed to free_irq(). The bug is reproducible on an x86_64 QEMU guest with CONFIG_GPIO_VIRTUSER=y and CONFIG_GPIO_SIM=y. Configure a live gpio-virtuser device through configfs. Its input lookup must refer to a live gpio-sim bank, such as key gpio-sim-test with offset 0. The consumer's dev_name attribute is shown as below; then run: echo 0 > /sys/kernel/debug/gpio-virtuser//gpiod:input:0/interrupts On an unpatched kernel, this reaches gpio_virtuser_interrupts_set() with ld->irq still at its initial value 0, and free_irq() reports: Trying to free already-free IRQ 0 The same reproducer completes without the warning on the patched kernel. Fixes: 91581c4b3f29 ("gpio: virtuser: new virtual testing driver for the GPIO API") Assisted-by: LLM Signed-off-by: Runyu Xiao Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260914051537.15320-1-runyu.xiao@seu.edu.cn Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-virtuser.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-virtuser.c b/drivers/gpio/gpio-virtuser.c index 7d0d366be37a..d5876c19cfef 100644 --- a/drivers/gpio/gpio-virtuser.c +++ b/drivers/gpio/gpio-virtuser.c @@ -692,7 +692,8 @@ static int gpio_virtuser_interrupts_set(void *data, u64 val) atomic_set(&ld->irq, irq); } else { irq = atomic_xchg(&ld->irq, 0); - free_irq(irq, ld); + if (irq) + free_irq(irq, ld); } return 0; From 51ae99659469edba2e83931efa26b77d36ca02c2 Mon Sep 17 00:00:00 2001 From: Sarah Emery Date: Fri, 28 Aug 2026 17:58:17 +0200 Subject: [PATCH 1634/2366] pinctrl: generic: serialise pinctrl_generic_dt_node_to_map() pinctrl_generic_add_group() documents that the caller must take care of locking, and pinmux_generic_add_function() needs it too, but pinctrl_generic_dt_node_to_map() calls them without holding pctldev->mutex, and the core caller in create_pinctrl() does not take it either. The driver core calls pinctrl_bind_pins() before probing a device, so two devices that reference the same pin controller can run pinctrl_generic_dt_node_to_map() on one pctldev at the same time. Both `add` functions take the new selector from pctldev->num_groups or pctldev->num_functions, and radix_tree_insert() at that index. Two racing callers can read the same selector before either has inserted, so the second insert collides and fails: k1-pinctrl d401e000.pinctrl: error -EEXIST: error adding function pcie2-0-cfg k1-pinctrl d401e000.pinctrl: does not have pin group pcie0-0-cfg.pcie0-0-pins leaving one consumer without its pin configuration. This was hit on a SpacemiT K3 board, where PCIe devices probe in parallel against the single shared pin controller. Take pctldev->mutex across the whole function, so that the groups and the function referring are in a single critical section. Fixes: 43722575e5cd ("pinctrl: add generic functions + pins mapper") Signed-off-by: Sarah Emery Signed-off-by: Linus Walleij --- drivers/pinctrl/pinctrl-generic.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pinctrl/pinctrl-generic.c b/drivers/pinctrl/pinctrl-generic.c index fd6bdb74028a..4277c8748513 100644 --- a/drivers/pinctrl/pinctrl-generic.c +++ b/drivers/pinctrl/pinctrl-generic.c @@ -3,8 +3,10 @@ #define pr_fmt(fmt) "generic pinconfig core: " fmt #include +#include #include #include +#include #include #include @@ -196,6 +198,8 @@ static int pinctrl_generic_dt_node_to_map(struct pinctrl_dev *pctldev, int ngroups = 0; int ret; + guard(mutex)(&pctldev->mutex); + *maps = NULL; *num_maps = 0; From e6c5d6c589764a41218cbd81bd7d6c9b906e2cf0 Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Mon, 31 Aug 2026 11:50:58 +0100 Subject: [PATCH 1635/2366] pinctrl: mpfs-mssio: fix width of unused bank voltage setting The bank voltages are only 4 bits wide, so when a pin was unused the driver was not correctly interpreting it as being at zero volts, because the driver's value for unused had two extra set bits. CC: stable@vger.kernel.org Fixes: 488d704ed7b7 ("pinctrl: add polarfire soc mssio pinctrl driver") Signed-off-by: Conor Dooley Signed-off-by: Linus Walleij --- drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c index ea1026a0d22c..dafca82f3e54 100644 --- a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c +++ b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c @@ -86,7 +86,7 @@ static struct mpfs_pinctrl_bank_voltage mpfs_pinctrl_bank_voltages[8] = { { .uv = 1800000, .val = 4 }, { .uv = 2500000, .val = 6 }, { .uv = 3300000, .val = 8 }, - { .uv = 0, .val = 0x3f }, // pin unused + { .uv = 0, .val = 0xf }, // pin unused }; static int mpfs_pinctrl_get_drive_strength_ma(u32 drive_strength) From 8039b75af5808572ff3656eaed07d348aed3989a Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Mon, 31 Aug 2026 11:50:59 +0100 Subject: [PATCH 1636/2366] pinctrl: mpfs-mssio: use correct regmap function to set bank voltage regmap_assign_bits() is not the correct function to use for an RMW operation, as it maps to regmap_set_bits() or regmap_clear_bits() and the former will never zero a bit. Use regmap_update_bits() instead, which will actually set the bank voltages to what have been requested. CC: stable@vger.kernel.org Fixes: 488d704ed7b7 ("pinctrl: add polarfire soc mssio pinctrl driver") Signed-off-by: Conor Dooley Signed-off-by: Linus Walleij --- drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c index dafca82f3e54..92d38e5336de 100644 --- a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c +++ b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c @@ -156,10 +156,10 @@ static void mpfs_pinctrl_set_bank_voltage(struct mpfs_pinctrl *pctrl, unsigned i u32 val = FIELD_PREP(MPFS_PINCTRL_BANK_VOLTAGE_MASK, bank_voltage); if (pin < MPFS_PINCTRL_BANK2_START) - regmap_assign_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK4_CFG_CR, + regmap_update_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK4_CFG_CR, MPFS_PINCTRL_BANK_VOLTAGE_MASK, val); else - regmap_assign_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK2_CFG_CR, + regmap_update_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK2_CFG_CR, MPFS_PINCTRL_BANK_VOLTAGE_MASK, val); } From 1589afe2d099d3e817873bc474676968d7080410 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Mon, 14 Sep 2026 00:43:24 -0700 Subject: [PATCH 1637/2366] ALSA: 6fire: fix OOB write from device-reported iso length usb6fire_pcm_in_urb_handler() sizes each outgoing isochronous packet as (actual_length - 4) / (in_n_analog << 2) * (out_n_analog << 2) + 4, where actual_length is the unsigned length the device reported for the matching IN packet. A packet completed with status 0 and actual_length < 4 wraps the subtraction to 0x7fffffec; a zero-length isochronous packet is legal on the bus, and the preceding loop rejects only non-zero status. The sum reaches memset() on out_urb->buffer, a 4832-byte object from kcalloc(PCM_MAX_PACKET_SIZE, PCM_N_PACKETS_PER_URB). Even without the wrap the result is out of bounds: at 88.2/96 kHz the 4-in/6-out scaling turns a full 420-byte IN packet into 628, so eight packets span 5024 bytes of that buffer. usb_submit_urb() rejects an over-long descriptor only after the memset() and the usb6fire_pcm_playback() copy of user PCM data have run. Guard the subtraction as the sibling usb6fire_pcm_capture() already does, and limit the frame count to what fits in rt->out_packet_size, the OUT endpoint's wMaxPacketSize. This bounds total_length by the buffer size while keeping each packet length aligned to a whole output frame. BUG: KASAN: out-of-bounds in usb6fire_pcm_in_urb_handler (sound/usb/6fire/pcm.c:338) Write of size 18446744073709551456 at addr ffff88802a3d0000 by task vhci_rx/5018 Call Trace: dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120) print_report (mm/kasan/report.c:378 mm/kasan/report.c:482) kasan_report (mm/kasan/report.c:595) kasan_check_range (mm/kasan/generic.c:186 mm/kasan/generic.c:200) __asan_memset (mm/kasan/shadow.c:84) usb6fire_pcm_in_urb_handler (sound/usb/6fire/pcm.c:338) __usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1657) usb_hcd_giveback_urb (drivers/usb/core/hcd.c:1741) vhci_rx_loop (drivers/usb/usbip/vhci_rx.c:107 drivers/usb/usbip/vhci_rx.c:242) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) Allocated by task 10: __kmalloc_cache_noprof (mm/slub.c:5563) usb6fire_pcm_init (sound/usb/6fire/pcm.c:560 sound/usb/6fire/pcm.c:595) usb6fire_chip_probe (sound/usb/6fire/chip.c:133) usb_probe_interface (drivers/usb/core/driver.c:399) The buggy address belongs to the object at ffff88802a3d0000 which belongs to the cache kmalloc-8k of size 8192 The buggy address is located 0 bytes inside of 4832-byte region [ffff88802a3d0000, ffff88802a3d12e0) Kernel panic - not syncing: Fatal exception in interrupt Fixes: c6d43ba816d1 ("ALSA: usb/6fire - Driver for TerraTec DMX 6Fire USB") Reported-by: co+855929c2df672879@bugs.sh Closes: https://lore.kernel.org/all/gisnub8aWGLbyZLcDCSc7zWsHonMWGcyRgt5%40bugs.sh/ Assisted-by: LLM Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260914074324.3590843-1-xmei5@asu.edu Signed-off-by: Takashi Iwai --- sound/usb/6fire/pcm.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c index 21789db6657d..0285d79ace0f 100644 --- a/sound/usb/6fire/pcm.c +++ b/sound/usb/6fire/pcm.c @@ -335,11 +335,19 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) /* setup out urb structure */ for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { + unsigned int frames = 0; + isoc_out = &out_urb->instance->iso_frame_desc[i]; isoc_in = &in_urb->instance->iso_frame_desc[i]; + if (isoc_in->actual_length > 4) + frames = (isoc_in->actual_length - 4) + / (rt->in_n_analog << 2); + frames = min_t(unsigned int, frames, + (rt->out_packet_size - 4) + / (rt->out_n_analog << 2)); + isoc_out->offset = total_length; - isoc_out->length = (isoc_in->actual_length - 4) / (rt->in_n_analog << 2) - * (rt->out_n_analog << 2) + 4; + isoc_out->length = frames * (rt->out_n_analog << 2) + 4; isoc_out->status = 0; total_length += isoc_out->length; } From 4396d70bb7fec531bcf934fed016b2f3300c670b Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 18:35:38 -0400 Subject: [PATCH 1638/2366] mmc: sdhci-of-aspeed: Remove children before releasing SDC resources Probe failure and removal leave SDHCI child devices registered after the parent clock and managed resources are released. Unregister the OF children in reverse order before disabling the parent clock on both paths. Use of_platform_device_destroy() because manual child creation does not set the flag required by of_platform_depopulate(). This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: bb7b8ec62dfb ("mmc: sdhci-of-aspeed: Add support for the ASPEED SD controller") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Assisted-by: OpenAI:GPT-5.6 Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-aspeed.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-of-aspeed.c b/drivers/mmc/host/sdhci-of-aspeed.c index f5d973783cbe..d317626feb4a 100644 --- a/drivers/mmc/host/sdhci-of-aspeed.c +++ b/drivers/mmc/host/sdhci-of-aspeed.c @@ -560,12 +560,14 @@ static int aspeed_sdc_probe(struct platform_device *pdev) cpdev = of_platform_device_create(child, NULL, &pdev->dev); if (!cpdev) { ret = -ENODEV; - goto err_clk; + goto err_children; } } return 0; +err_children: + device_for_each_child_reverse(&pdev->dev, NULL, of_platform_device_destroy); err_clk: clk_disable_unprepare(sdc->clk); return ret; @@ -575,6 +577,7 @@ static void aspeed_sdc_remove(struct platform_device *pdev) { struct aspeed_sdc *sdc = dev_get_drvdata(&pdev->dev); + device_for_each_child_reverse(&pdev->dev, NULL, of_platform_device_destroy); clk_disable_unprepare(sdc->clk); } From d7fd1f98607f2cd358e583f9548bdf0173090a86 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 3 Sep 2026 14:06:02 +0900 Subject: [PATCH 1639/2366] ksmbd: follow SMB2 session expiration semantics ksmbd_session_register() destroys valid sessions after ten seconds of inactivity whenever a client starts another SessionSetup exchange. This confuses Session.IdleTime with Session.ExpirationTime. Windows can create additional authenticated sessions on an existing connection, so deleting the older session invalidates its tree connects and makes mapped drives fail with STATUS_NETWORK_NAME_DELETED. The session expiration rules require the server to change a valid session to expired only after its credential expiration time passes. A valid or expired session otherwise keeps its connection from being scavenged. Connections that have not negotiated a dialect, have no sessions, or have only InProgress sessions are disconnected after an implementation-specific timeout. Use the Windows-compatible 45 second value and run the expiration check periodically for both TCP and SMB Direct. Keep zero as an infinite credential expiration time, and count each Valid-to-Expired transition. Set expired sessions to InProgress when they reauthenticate. If authentication fails, remove the session from the global and per-connection tables immediately, including SMB3 multichannel connections. Retain protection against abandoned SessionId-zero exchanges by allowing only one InProgress authentication per connection. Additional exchanges fail with STATUS_INSUFFICIENT_RESOURCES, and stale InProgress sessions are reaped after the same 45 second setup timeout. This bounds the original unauthenticated memory-exhaustion path without evicting established sessions. Fixes: ea174a918939 ("ksmbd: destroy expired sessions") Reported-by: Mobin Aydinfar Signed-off-by: Namjae Jeon --- fs/smb/server/connection.c | 72 +++++++++++++++++ fs/smb/server/connection.h | 3 + fs/smb/server/mgmt/user_session.c | 124 ++++++++++++++++++++++++++++-- fs/smb/server/mgmt/user_session.h | 8 +- fs/smb/server/proc.c | 2 + fs/smb/server/server.c | 2 +- fs/smb/server/smb2pdu.c | 37 ++++++--- fs/smb/server/smb2pdu.h | 2 - fs/smb/server/stats.h | 1 + 9 files changed, 227 insertions(+), 24 deletions(-) diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c index 4cb92d6599ee..d211861ff86f 100644 --- a/fs/smb/server/connection.c +++ b/fs/smb/server/connection.c @@ -22,6 +22,8 @@ static DEFINE_MUTEX(init_lock); static struct ksmbd_conn_ops default_conn_ops; +static struct delayed_work session_expiration_work; +static bool stopping_session_expiration_work; DEFINE_HASHTABLE(conn_list, CONN_HASH_BITS); DECLARE_RWSEM(conn_list_lock); @@ -158,18 +160,28 @@ static void delete_proc_clients(void) {} static struct workqueue_struct *ksmbd_conn_wq; +static void ksmbd_session_expiration_worker(struct work_struct *work); + 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; + + WRITE_ONCE(stopping_session_expiration_work, false); + INIT_DELAYED_WORK(&session_expiration_work, + ksmbd_session_expiration_worker); + queue_delayed_work(ksmbd_conn_wq, &session_expiration_work, + KSMBD_SESSION_EXPIRATION_INTERVAL); return 0; } void ksmbd_conn_wq_destroy(void) { if (ksmbd_conn_wq) { + WRITE_ONCE(stopping_session_expiration_work, true); + cancel_delayed_work_sync(&session_expiration_work); destroy_workqueue(ksmbd_conn_wq); ksmbd_conn_wq = NULL; } @@ -279,6 +291,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void) return NULL; conn->need_neg = true; + conn->creation_time = jiffies; ksmbd_conn_set_new(conn); conn->local_nls = load_nls("utf8"); if (!conn->local_nls) @@ -588,6 +601,20 @@ bool ksmbd_conn_alive(struct ksmbd_conn *conn) if (kthread_should_stop()) return false; + /* + * Stale connections that have not completed NEGOTIATE and SESSION_SETUP + * must be disconnected. Do not race a request that is currently + * completing authentication. + */ + if (!atomic_read(&conn->req_running) && + time_after(jiffies, conn->creation_time + + KSMBD_UNAUTHENTICATED_CONN_TIMEOUT) && + (READ_ONCE(conn->need_neg) || + !ksmbd_conn_has_valid_or_expired_session(conn))) { + ksmbd_debug(CONN, "Connection setup timed out\n"); + return false; + } + if (atomic_read(&conn->stats.open_files_count) > 0) return true; @@ -605,6 +632,51 @@ bool ksmbd_conn_alive(struct ksmbd_conn *conn) return true; } +static void ksmbd_session_expiration_worker(struct work_struct *work) +{ + struct ksmbd_conn *conn, *target; + int bkt; + + if (!ksmbd_server_running()) + goto reschedule; + + ksmbd_expire_sessions(); + + /* + * An old connection without a Valid or Expired session must be + * disconnected. Process one connection at a time without holding + * conn_list_lock across transport shutdown. + */ +again: + target = NULL; + down_read(&conn_list_lock); + hash_for_each(conn_list, bkt, conn, hlist) { + if (ksmbd_conn_exiting(conn) || ksmbd_conn_releasing(conn) || + atomic_read(&conn->req_running) || + time_before_eq(jiffies, conn->creation_time + + KSMBD_UNAUTHENTICATED_CONN_TIMEOUT) || + (!READ_ONCE(conn->need_neg) && + ksmbd_conn_has_valid_or_expired_session(conn))) + continue; + + target = ksmbd_conn_get(conn); + break; + } + up_read(&conn_list_lock); + + if (target) { + ksmbd_debug(CONN, "Connection setup timed out\n"); + ksmbd_conn_abort(target); + ksmbd_conn_put(target); + goto again; + } + +reschedule: + if (!READ_ONCE(stopping_session_expiration_work)) + queue_delayed_work(ksmbd_conn_wq, &session_expiration_work, + KSMBD_SESSION_EXPIRATION_INTERVAL); +} + /* "+2" for BCC field (ByteCount, 2 bytes) */ #define SMB1_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb_hdr) + 2) #define SMB2_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb2_pdu)) diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h index 63484c8efbbd..371f17b4f02a 100644 --- a/fs/smb/server/connection.h +++ b/fs/smb/server/connection.h @@ -77,6 +77,7 @@ struct ksmbd_conn { struct rw_semaphore session_lock; /* smb session 1 per user */ struct xarray sessions; + unsigned long creation_time; unsigned long last_active; /* How many request are running currently */ atomic_t req_running; @@ -192,6 +193,8 @@ struct ksmbd_transport { #define KSMBD_TCP_RECV_TIMEOUT (7 * HZ) #define KSMBD_TCP_SEND_TIMEOUT (5 * HZ) +#define KSMBD_SESSION_EXPIRATION_INTERVAL (5 * HZ) +#define KSMBD_UNAUTHENTICATED_CONN_TIMEOUT (45 * HZ) #define KSMBD_TCP_PEER_SOCKADDR(c) ((struct sockaddr *)&((c)->peer_addr)) #define CONN_HASH_BITS 12 diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c index 2eb8f730e99e..44dc3f800cd4 100644 --- a/fs/smb/server/mgmt/user_session.c +++ b/fs/smb/server/mgmt/user_session.c @@ -22,6 +22,7 @@ static DEFINE_IDA(session_ida); #define SESSION_HASH_BITS 12 +#define KSMBD_MAX_PENDING_SESSIONS 1 static DEFINE_HASHTABLE(sessions_table, SESSION_HASH_BITS); static DECLARE_RWSEM(sessions_table_lock); @@ -432,26 +433,31 @@ struct ksmbd_session *__session_lookup(unsigned long long id) return NULL; } -static void ksmbd_expire_session(struct ksmbd_conn *conn) +static bool ksmbd_too_many_session_setups(struct ksmbd_conn *conn) { unsigned long id; struct ksmbd_session *sess; + unsigned int pending = 0; down_write(&sessions_table_lock); down_write(&conn->session_lock); xa_for_each(&conn->sessions, id, sess) { + if (READ_ONCE(sess->state) != SMB2_SESSION_IN_PROGRESS) + continue; + if (atomic_read(&sess->refcnt) <= 1 && - (sess->state != SMB2_SESSION_VALID || - time_after(jiffies, - sess->last_active + SMB2_SESSION_TIMEOUT))) { + time_after(jiffies, sess->last_active + + KSMBD_UNAUTHENTICATED_CONN_TIMEOUT)) { xa_erase(&conn->sessions, sess->id); ksmbd_session_remove_from_table(sess); ksmbd_session_destroy(sess); continue; } + pending++; } up_write(&conn->session_lock); up_write(&sessions_table_lock); + return pending >= KSMBD_MAX_PENDING_SESSIONS; } int ksmbd_session_register(struct ksmbd_conn *conn, @@ -461,9 +467,12 @@ int ksmbd_session_register(struct ksmbd_conn *conn, sess->dialect = conn->dialect; memcpy(sess->ClientGUID, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE); - ksmbd_expire_session(conn); - ret = xa_err(xa_store(&conn->sessions, sess->id, sess, - KSMBD_DEFAULT_GFP)); + /* Bound abandoned SessionId-zero authentication exchanges. */ + if (ksmbd_too_many_session_setups(conn)) + ret = -ENOSPC; + else + ret = xa_err(xa_store(&conn->sessions, sess->id, sess, + KSMBD_DEFAULT_GFP)); if (ret) { down_write(&sessions_table_lock); ksmbd_session_remove_from_table(sess); @@ -474,6 +483,105 @@ int ksmbd_session_register(struct ksmbd_conn *conn, return ret; } +void ksmbd_session_unregister(struct ksmbd_conn *conn, + struct ksmbd_session *sess) +{ + struct ksmbd_conn *session_conns[KSMBD_MAX_CHANNELS]; + struct channel *chann; + unsigned long index; + unsigned int nr_conns = 0, i; + bool removed = false; + + down_write(&sessions_table_lock); + if (!hlist_unhashed(&sess->hlist)) { + /* Keep each channel connection stable under sessions_table_lock. */ + down_read(&sess->chann_lock); + xa_for_each(&sess->ksmbd_chann_list, index, chann) { + if (nr_conns == ARRAY_SIZE(session_conns)) + break; + session_conns[nr_conns++] = chann->conn; + } + up_read(&sess->chann_lock); + + ksmbd_session_remove_from_table(sess); + removed = true; + } + + down_write(&conn->session_lock); + if (xa_load(&conn->sessions, sess->id) == sess) + xa_erase(&conn->sessions, sess->id); + up_write(&conn->session_lock); + for (i = 0; i < nr_conns; i++) { + if (session_conns[i] == conn) + continue; + down_write(&session_conns[i]->session_lock); + if (xa_load(&session_conns[i]->sessions, sess->id) == sess) + xa_erase(&session_conns[i]->sessions, sess->id); + up_write(&session_conns[i]->session_lock); + } + up_write(&sessions_table_lock); + + if (removed) + ksmbd_user_session_put(sess); +} + +bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn) +{ + struct ksmbd_session *sess; + unsigned long id; + int state, bkt; + bool found = false; + + down_read(&conn->session_lock); + xa_for_each(&conn->sessions, id, sess) { + state = READ_ONCE(sess->state); + if (state == SMB2_SESSION_VALID || + state == SMB2_SESSION_EXPIRED) { + found = true; + break; + } + } + up_read(&conn->session_lock); + if (found) + return true; + + /* A session bound through SMB3 multichannel is not in conn->sessions. */ + down_read(&sessions_table_lock); + hash_for_each(sessions_table, bkt, sess, hlist) { + state = READ_ONCE(sess->state); + if (state != SMB2_SESSION_VALID && + state != SMB2_SESSION_EXPIRED) + continue; + + down_read(&sess->chann_lock); + found = xa_load(&sess->ksmbd_chann_list, (long)conn); + up_read(&sess->chann_lock); + if (found) + break; + } + up_read(&sessions_table_lock); + return found; +} + +void ksmbd_expire_sessions(void) +{ + struct ksmbd_session *sess; + u64 now = ktime_get_real_seconds(); + int bkt; + + down_read(&sessions_table_lock); + hash_for_each(sessions_table, bkt, sess, hlist) { + if (READ_ONCE(sess->state) != SMB2_SESSION_VALID || + !sess->kerberos_expiry || now < sess->kerberos_expiry) + continue; + + if (cmpxchg(&sess->state, SMB2_SESSION_VALID, + SMB2_SESSION_EXPIRED) == SMB2_SESSION_VALID) + ksmbd_counter_inc(KSMBD_COUNTER_SESSION_TIMEOUTS); + } + up_read(&sessions_table_lock); +} + static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess) { struct channel *chann; @@ -488,7 +596,7 @@ static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess) return 0; } -void ksmbd_sessions_deregister(struct ksmbd_conn *conn) +void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn) { struct ksmbd_session *sess; unsigned long id; diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h index 3e52d4cc1324..217258551d6d 100644 --- a/fs/smb/server/mgmt/user_session.h +++ b/fs/smb/server/mgmt/user_session.h @@ -72,6 +72,8 @@ struct ksmbd_session { struct rw_semaphore rpc_lock; }; +#define KSMBD_MAX_CHANNELS 32 + static inline int test_session_flag(struct ksmbd_session *sess, int bit) { return sess->flags & bit; @@ -98,7 +100,11 @@ bool is_ksmbd_session_in_connection(struct ksmbd_conn *conn, unsigned long long id); int ksmbd_session_register(struct ksmbd_conn *conn, struct ksmbd_session *sess); -void ksmbd_sessions_deregister(struct ksmbd_conn *conn); +void ksmbd_session_unregister(struct ksmbd_conn *conn, + struct ksmbd_session *sess); +void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn); +bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn); +void ksmbd_expire_sessions(void); struct ksmbd_session *__session_lookup(unsigned long long id); struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn, unsigned long long id); diff --git a/fs/smb/server/proc.c b/fs/smb/server/proc.c index 826353ed0553..19f0f2cfbf54 100644 --- a/fs/smb/server/proc.c +++ b/fs/smb/server/proc.c @@ -178,6 +178,8 @@ static int proc_show_ksmbd_stats(struct seq_file *m, void *v) proc_show_runtime_totals(m); seq_printf(m, "sessions:\t%lld\n", ksmbd_counter_sum(KSMBD_COUNTER_SESSIONS)); + seq_printf(m, "session_timeouts:\t%lld\n", + ksmbd_counter_sum(KSMBD_COUNTER_SESSION_TIMEOUTS)); seq_printf(m, "tree_connects:\t%lld\n", ksmbd_counter_sum(KSMBD_COUNTER_TREE_CONNS)); seq_printf(m, "requests:\t%lld\n", diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c index 0069d4e6a60a..0827c8c51006 100644 --- a/fs/smb/server/server.c +++ b/fs/smb/server/server.c @@ -414,7 +414,7 @@ static int ksmbd_server_process_request(struct ksmbd_conn *conn) static int ksmbd_server_terminate_conn(struct ksmbd_conn *conn) { - ksmbd_sessions_deregister(conn); + ksmbd_conn_sessions_cleanup(conn); destroy_lease_table(conn); return 0; } diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index b7ce67094626..8acc5174530b 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -85,8 +85,6 @@ struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn return chann; } -#define KSMBD_MAX_CHANNELS 32 - static int register_session_channel(struct ksmbd_session *sess, struct ksmbd_conn *conn, const char *sess_key) @@ -933,8 +931,14 @@ static bool smb2_session_expired_cmd_allowed(struct ksmbd_work *work, static bool smb2_session_kerberos_expired(struct ksmbd_session *sess) { - return sess->kerberos_expiry && - ktime_get_real_seconds() >= sess->kerberos_expiry; + if (!sess->kerberos_expiry || + ktime_get_real_seconds() < sess->kerberos_expiry) + return false; + + if (cmpxchg(&sess->state, SMB2_SESSION_VALID, + SMB2_SESSION_EXPIRED) == SMB2_SESSION_VALID) + ksmbd_counter_inc(KSMBD_COUNTER_SESSION_TIMEOUTS); + return true; } /** @@ -969,9 +973,8 @@ int smb2_check_user_session(struct ksmbd_work *work) if (!work->next_smb2_rcv_hdr_off && sess_id) work->sess = ksmbd_session_lookup_all_states(conn, sess_id); if (work->sess) { - if (smb2_session_kerberos_expired(work->sess)) { - work->sess->state = SMB2_SESSION_EXPIRED; - } else if (work->sess->state != SMB2_SESSION_VALID) { + if (!smb2_session_kerberos_expired(work->sess) && + work->sess->state != SMB2_SESSION_VALID) { ksmbd_user_session_put(work->sess); work->sess = NULL; } @@ -996,8 +999,7 @@ int smb2_check_user_session(struct ksmbd_work *work) sess_id, work->sess->id); return -EINVAL; } - if (smb2_session_kerberos_expired(work->sess)) - work->sess->state = SMB2_SESSION_EXPIRED; + smb2_session_kerberos_expired(work->sess); if (work->sess->state != SMB2_SESSION_VALID) { pr_err("compound request on a non-valid session (state %d)\n", work->sess->state); @@ -1014,7 +1016,6 @@ int smb2_check_user_session(struct ksmbd_work *work) work->sess = ksmbd_session_lookup_all_states(conn, sess_id); if (work->sess) { if (smb2_session_kerberos_expired(work->sess)) { - work->sess->state = SMB2_SESSION_EXPIRED; return smb2_session_expired_cmd_allowed(work, cmd) ? 1 : -EKEYEXPIRED; } @@ -2436,7 +2437,7 @@ int smb2_sess_setup(struct ksmbd_work *work) struct ksmbd_conn *conn = work->conn; struct smb2_sess_setup_req *req; struct smb2_sess_setup_rsp *rsp; - struct ksmbd_session *sess; + struct ksmbd_session *sess = NULL; struct negotiate_message *negblob; unsigned int negblob_len, negblob_off; int rc = 0; @@ -2592,6 +2593,9 @@ int smb2_sess_setup(struct ksmbd_work *work) goto out_err; } + if (work->session_setup_reauth) + WRITE_ONCE(sess->state, SMB2_SESSION_IN_PROGRESS); + conn->binding = false; } work->sess = sess; @@ -2703,6 +2707,14 @@ int smb2_sess_setup(struct ksmbd_work *work) } if (rc < 0) { + bool setup_in_progress = sess && + READ_ONCE(sess->state) == SMB2_SESSION_IN_PROGRESS && + !(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING); + + /* Authentication errors must not leave the new session published. */ + if (setup_in_progress) + ksmbd_session_unregister(conn, sess); + if (sess && conn->dialect == SMB311_PROT_ID && (req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) { struct preauth_session *preauth_sess; @@ -2736,7 +2748,8 @@ int smb2_sess_setup(struct ksmbd_work *work) * For binding requests, session belongs to another * connection. Do not expire it. */ - if (!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) { + if (!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING) && + !setup_in_progress) { sess->last_active = jiffies; sess->kerberos_expiry = 0; sess->state = SMB2_SESSION_EXPIRED; diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h index 3f08d1ca5a38..a6200d8630e2 100644 --- a/fs/smb/server/smb2pdu.h +++ b/fs/smb/server/smb2pdu.h @@ -61,8 +61,6 @@ struct preauth_integrity_info { #define SMB2_SESSION_IN_PROGRESS BIT(0) #define SMB2_SESSION_VALID BIT(1) -#define SMB2_SESSION_TIMEOUT (10 * HZ) - /* Apple Defined Contexts */ #define SMB2_CREATE_AAPL "AAPL" diff --git a/fs/smb/server/stats.h b/fs/smb/server/stats.h index bc864efa0d46..8b32b8b4e8be 100644 --- a/fs/smb/server/stats.h +++ b/fs/smb/server/stats.h @@ -15,6 +15,7 @@ enum { KSMBD_COUNTER_SESSIONS = 0, + KSMBD_COUNTER_SESSION_TIMEOUTS, KSMBD_COUNTER_TREE_CONNS, KSMBD_COUNTER_REQUESTS, KSMBD_COUNTER_STATUS_SUCCESS, From 65bcc5f89704efe5b9d69d4ea2c1002d90c31382 Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Mon, 14 Sep 2026 11:50:07 +0200 Subject: [PATCH 1640/2366] HID: amd_sfh: Validate PCI BAR size before mapping The amd_sfh driver maps PCI BAR 2 using pcim_iomap_regions() and subsequently accesses MMIO registers at offsets up to 0x10958 (e.g., AMD_P2C_MSG3 at 0x1068C). However, the driver never validates that the BAR size is large enough to cover these accesses. If the driver is bound to a device with a smaller BAR 2, this leads to an out-of-bounds memory access and a page fault during the probe function. For example, a page fault can occur when reading from privdata->mmio + AMD_P2C_MSG3 in mp2_select_ops(): BUG: unable to handle page fault for address: ffffc9000390368c PGD 100000067 P4D 100000067 PUD 1012c1067 PMD 105b64067 PTE 0 Oops: Oops: 0000 [#1] SMP KASAN NOPTI RIP: 0010:readl arch/x86/include/asm/io.h:59 [inline] RIP: 0010:mp2_select_ops drivers/hid/amd-sfh-hid/amd_sfh_pcie.c:282 [inline] RIP: 0010:amd_mp2_pci_probe+0x337/0x5f0 drivers/hid/amd-sfh-hid/amd_sfh_pcie.c:487 Call Trace: local_pci_probe drivers/pci/pci-driver.c:332 [inline] pci_call_probe drivers/pci/pci-driver.c:394 [inline] __pci_device_probe drivers/pci/pci-driver.c:455 [inline] pci_device_probe+0x431/0xc90 drivers/pci/pci-driver.c:489 Fix this by verifying that the length of BAR 2 is at least 128KB before attempting to map it. Since the maximum accessed offset is 0x10958, and PCI BAR sizes are powers of 2, any legitimate hardware will have a BAR size of at least 128KB. Fixes: 4f567b9f8141 ("SFH: PCIe driver to add support of AMD sensor fusion hub") Assisted-by: Gemini:gemini-3.7-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+4eadd4dfe9e66522bae8@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4eadd4dfe9e66522bae8 Link: https://syzkaller.appspot.com/ai_job?id=3bc1c45c-548f-4ab5-8243-d2c8ec321d6c Signed-off-by: Slawomir Stepien Acked-by: Basavaraj Natikar Link: https://syzkaller.appspot.com/bug?extid=4eadd4dfe9e66522bae8 Signed-off-by: Jiri Kosina --- drivers/hid/amd-sfh-hid/amd_sfh_common.h | 4 ++++ drivers/hid/amd-sfh-hid/amd_sfh_pcie.c | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h index f8c6b7fc34fb..3d29119a3765 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h +++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h @@ -13,11 +13,15 @@ #include #include #include +#include #include "amd_sfh_hid.h" #define PCI_DEVICE_ID_AMD_MP2 0x15E4 #define PCI_DEVICE_ID_AMD_MP2_1_1 0x164A +/* The BAR 2 size must cover the highest register offset (0x10958) */ +#define AMD_SFH_MIN_BAR_SIZE SZ_128K + #define AMD_C2P_MSG(regno) (0x10500 + ((regno) * 4)) #define AMD_P2C_MSG(regno) (0x10680 + ((regno) * 4)) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index eda26a094d3f..061a63519d44 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -497,6 +497,16 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i if (rc) return rc; + if (!(pci_resource_flags(pdev, 2) & IORESOURCE_MEM)) { + dev_err(&pdev->dev, "BAR 2 is not IORESOURCE_MEM\n"); + return -ENODEV; + } + + if (pci_resource_len(pdev, 2) < AMD_SFH_MIN_BAR_SIZE) { + dev_err(&pdev->dev, "BAR 2 is too small\n"); + return -EINVAL; + } + rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME); if (rc) return rc; From ba7a79b9bc87776c8c1808407a7508a8be3a789e Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Mon, 14 Sep 2026 10:13:50 +0200 Subject: [PATCH 1641/2366] wifi: cfg80211: verify if AP_VLAN belongs to the correct AP The get_vlan() only checks if NL80211_ATTR_STA_VLAN target is an AP/AP_VLAN/P2P_GO interface on the same wiphy. It has no notion of which specific AP a given AP_VLAN belongs to. Fix that by comparing the ethernet addresses of the two net devices. Given VLAN A' must have the same address as AP A. Otherwise, return error code. Signed-off-by: Slawomir Stepien Reported-by: Johannes Berg Link: https://lore.kernel.org/all/22e7ddfc50d7a6a16c437b876dab5fe223799610.camel@sipsolutions.net/ Link: https://patch.msgid.link/20260914081350.83484-1-sst@poczta.fm Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 44f2bad08670..9fd1367483c6 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -8966,10 +8966,12 @@ int cfg80211_check_station_change(struct wiphy *wiphy, EXPORT_SYMBOL(cfg80211_check_station_change); /* - * Get vlan interface making sure it is running and on the right wiphy. + * Get vlan interface making sure it is running, on the right wiphy + * and actually belongs to the given AP/P2P_GO interface. */ static struct net_device *get_vlan(struct genl_info *info, - struct cfg80211_registered_device *rdev) + struct cfg80211_registered_device *rdev, + struct net_device *dev) { struct nlattr *vlanattr = info->attrs[NL80211_ATTR_STA_VLAN]; struct net_device *v; @@ -8999,6 +9001,12 @@ static struct net_device *get_vlan(struct genl_info *info, goto error; } + /* Check if the VLAN interface belongs to the AP interface */ + if (!dev || !ether_addr_equal(v->dev_addr, dev->dev_addr)) { + ret = -EINVAL; + goto error; + } + return v; error: dev_put(v); @@ -9296,7 +9304,7 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info) if (err) return err; - params.vlan = get_vlan(info, rdev); + params.vlan = get_vlan(info, rdev, dev); if (IS_ERR(params.vlan)) return PTR_ERR(params.vlan); @@ -9597,7 +9605,7 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) } /* must be last in here for error handling */ - params.vlan = get_vlan(info, rdev); + params.vlan = get_vlan(info, rdev, dev); if (IS_ERR(params.vlan)) return PTR_ERR(params.vlan); break; From 4ae3128c230372b43d0c417e0fcf816e8290e9fa Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Thu, 10 Sep 2026 10:04:16 +0200 Subject: [PATCH 1642/2366] wifi: cfg80211: do not support direct add of station to AP_VLAN interfaces Prevent userspace from adding stations directly to AP_VLAN type interfaces. Userspace should first add the station to the base interface (AP type) and then can use CMD_SET_STATION to move it to AP_VLAN. The other way is by using NL80211_ATTR_STA_VLAN. Without this path, we cannot check if the AP has been started before adding the station - wdev for AP_VLAN does not store information about the base AP interface. Signed-off-by: Slawomir Stepien Link: https://patch.msgid.link/20260910080418.725741-1-sst@poczta.fm Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 9fd1367483c6..677a78f72b0e 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -9564,7 +9564,6 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) switch (wdev->iftype) { case NL80211_IFTYPE_AP: - case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_P2P_GO: /* ignore WME attributes if iface/sta is not capable */ if (!(rdev->wiphy.flags & WIPHY_FLAG_AP_UAPSD) || From e3d1acb0276742f288094cd7a497745364876bb8 Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Thu, 10 Sep 2026 10:04:17 +0200 Subject: [PATCH 1643/2366] wifi: cfg80211: move link_id validation earlier in nl80211_new_station() I do not see a reason why this check is so low in the function. Move it up right next to param fetch. This new position is more beneficial for AP/Link state check that will be added in upcoming commit. Signed-off-by: Slawomir Stepien Link: https://patch.msgid.link/20260910080418.725741-2-sst@poczta.fm Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 677a78f72b0e..7b0ad66cf587 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -9384,6 +9384,16 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) params.link_sta_params.link_id = nl80211_link_id_or_invalid(info->attrs); + if (wdev->valid_links) { + if (params.link_sta_params.link_id < 0) + return -EINVAL; + if (!(wdev->valid_links & BIT(params.link_sta_params.link_id))) + return -ENOLINK; + } else { + if (params.link_sta_params.link_id >= 0) + return -EINVAL; + } + if (info->attrs[NL80211_ATTR_MLD_ADDR]) { mac_addr = nla_data(info->attrs[NL80211_ATTR_MLD_ADDR]); params.link_sta_params.mld_mac = mac_addr; @@ -9656,27 +9666,10 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) /* be aware of params.vlan when changing code here */ - if (wdev->valid_links) { - if (params.link_sta_params.link_id < 0) { - err = -EINVAL; - goto out; - } - if (!(wdev->valid_links & BIT(params.link_sta_params.link_id))) { - err = -ENOLINK; - goto out; - } - } else { - if (params.link_sta_params.link_id >= 0) { - err = -EINVAL; - goto out; - } - } - params.epp_peer = nla_get_flag(info->attrs[NL80211_ATTR_EPP_PEER]); err = rdev_add_station(rdev, wdev, mac_addr, ¶ms); -out: dev_put(params.vlan); return err; } From a842cfc1d6d85b34ad73959460def4d4641e82e8 Mon Sep 17 00:00:00 2001 From: Slawomir Stepien Date: Thu, 10 Sep 2026 10:04:18 +0200 Subject: [PATCH 1644/2366] wifi: cfg80211: check if AP has been started or joined a mesh before adding new station Adding a new station to AP makes only sense when the AP has been started (nl80211_start_ap()) or joined a mesh (__cfg80211_join_mesh()). Check if AP is up and beaconing on the link or joined the mesh, when adding new station. Return error if this isn't the case. Note that libertas devices need special handling since they do not implement join_mesh() and the decision must be made on channel definition. Reported-by: syzbot+9bdc0c5998ab45b05030@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9bdc0c5998ab45b05030 Signed-off-by: Slawomir Stepien Link: https://patch.msgid.link/20260910080418.725741-3-sst@poczta.fm Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 7b0ad66cf587..f18d526149c6 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -9336,7 +9336,7 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info) static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; - int err; + int err, link_id; struct wireless_dev *wdev = info->user_ptr[1]; struct net_device *dev = wdev->netdev; struct station_parameters params; @@ -9575,6 +9575,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) switch (wdev->iftype) { case NL80211_IFTYPE_AP: case NL80211_IFTYPE_P2P_GO: + /* Add a new station only after the AP and link has been started */ + link_id = wdev->valid_links ? params.link_sta_params.link_id : 0; + if (!wdev->links[link_id].ap.beacon_interval) + return -ENETDOWN; + /* ignore WME attributes if iface/sta is not capable */ if (!(rdev->wiphy.flags & WIPHY_FLAG_AP_UAPSD) || !(params.sta_flags_set & BIT(NL80211_STA_FLAG_WME))) @@ -9619,6 +9624,19 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) return PTR_ERR(params.vlan); break; case NL80211_IFTYPE_MESH_POINT: + /* + * Add a new station only after the mesh has been started. + * libertas doesn't implement join_mesh(); it configures the + * mesh via sysfs and joins it when the channel is set, so + * use that as the started indication instead. + */ + if (rdev->ops->libertas_set_mesh_channel) { + if (!wdev->u.mesh.chandef.chan) + return -ENETDOWN; + } else if (!wdev->u.mesh.beacon_interval) { + return -ENETDOWN; + } + /* ignore uAPSD data */ params.sta_modify_mask &= ~STATION_PARAM_APPLY_UAPSD; From e5c8d7acd31b27057ea42cd405d0b3ece097bc89 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 9 Sep 2026 12:37:18 +0000 Subject: [PATCH 1645/2366] wifi: virt_wifi: don't transfer operstate before register virt_wifi_newlink() calls netif_stacked_transfer_operstate() before register_netdevice(). If the lower device is dormant, that queues the new netdev on lweventlist while it is still uninitialized. If registration fails after that, for example because of an invalid name such as "bad/name", free_netdev() immediately frees the object. A later linkwatch_fire_event() then use-after-frees the list entry. Move the transfer to after netdev_upper_dev_link(), as macvlan and ipvlan already do. Fixes: c7cdba31ed8b ("mac80211-next: rtnetlink wifi simulation device") Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Link: https://patch.msgid.link/f5a832fb0ab228ce6e2b5a91fba4ca8b79198a2f.1788948455.git.zihanx@nebusec.ai Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/virt_wifi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/virtual/virt_wifi.c b/drivers/net/wireless/virtual/virt_wifi.c index b69a4650fba8..48afc2432f93 100644 --- a/drivers/net/wireless/virtual/virt_wifi.c +++ b/drivers/net/wireless/virtual/virt_wifi.c @@ -558,7 +558,6 @@ static int virt_wifi_newlink(struct net_device *dev, } eth_hw_addr_inherit(dev, priv->lowerdev); - netif_stacked_transfer_operstate(priv->lowerdev, dev); dev->ieee80211_ptr = kzalloc_obj(*dev->ieee80211_ptr); @@ -584,6 +583,8 @@ static int virt_wifi_newlink(struct net_device *dev, goto unregister_netdev; } + netif_stacked_transfer_operstate(priv->lowerdev, dev); + dev->priv_destructor = virt_wifi_net_device_destructor; priv->being_deleted = false; priv->is_connected = false; From 06f42accaf3c6aecab1dcc57f68dde6c06c8b380 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Wed, 9 Sep 2026 15:11:24 +0900 Subject: [PATCH 1646/2366] wifi: libipw: reject TKIP frames without a full MIC libipw_michael_mic_verify() assumes that an skb contains an eight-byte Michael MIC. A short TKIP frame makes the unsigned payload length wrap, causing michael_mic() to read past the skb. Check that the MIC is present before verifying it, and use the existing MICHAEL_MIC_LEN constant for all MIC lengths in the verifier. Fixes: b453872c35cf ("[NET] ieee80211 subsystem") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/20260909061124.3802517-1-4ncienth@gmail.com Signed-off-by: Johannes Berg --- .../net/wireless/intel/ipw2x00/libipw_crypto_tkip.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_crypto_tkip.c b/drivers/net/wireless/intel/ipw2x00/libipw_crypto_tkip.c index 24bb28ab7a49..2b0cf0ec496a 100644 --- a/drivers/net/wireless/intel/ipw2x00/libipw_crypto_tkip.c +++ b/drivers/net/wireless/intel/ipw2x00/libipw_crypto_tkip.c @@ -474,14 +474,16 @@ static int libipw_michael_mic_verify(struct sk_buff *skb, int keyidx, int hdr_len, void *priv) { struct libipw_tkip_data *tkey = priv; - u8 mic[8]; + u8 mic[MICHAEL_MIC_LEN]; - if (!tkey->key_set) + if (!tkey->key_set || skb->len < hdr_len + MICHAEL_MIC_LEN) return -1; michael_mic(&tkey->key[24], (struct ieee80211_hdr *)skb->data, - skb->data + hdr_len, skb->len - 8 - hdr_len, mic); - if (memcmp(mic, skb->data + skb->len - 8, 8) != 0) { + skb->data + hdr_len, + skb->len - MICHAEL_MIC_LEN - hdr_len, mic); + if (memcmp(mic, skb->data + skb->len - MICHAEL_MIC_LEN, + MICHAEL_MIC_LEN) != 0) { struct ieee80211_hdr *hdr; hdr = (struct ieee80211_hdr *)skb->data; printk(KERN_DEBUG "%s: Michael MIC verification failed for " @@ -499,7 +501,7 @@ static int libipw_michael_mic_verify(struct sk_buff *skb, int keyidx, tkey->rx_iv32 = tkey->rx_iv32_new; tkey->rx_iv16 = tkey->rx_iv16_new; - skb_trim(skb, skb->len - 8); + skb_trim(skb, skb->len - MICHAEL_MIC_LEN); return 0; } From 247a82da6f563dcfd9074a68f99a0c0997d0679c Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:48 +0100 Subject: [PATCH 1647/2366] drm/xe/mmio_gem: forbid VMA split The fault handler assumes it always operates on a VMA spanning the entire GEM object. This does not hold when the VMA has been split, e.g. by a partial munmap or mprotect. In that case the handler may map wrong physical pages or cause SIGBUS. Handle this by forbidding VMA split, as partial unmaps are not deemed useful for MMIO GEMs. Suggested-by: Matthew Auld Signed-off-by: Ilia Levi Fixes: 1ffcf8b8ae8a ("drm/xe: Support for mmap-ing mmio regions") Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-11-matthew.auld@intel.com (cherry picked from commit f3391a0b12d7bf826a0b21600d2f294f3dce4c14) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 3741ae60f532..d54477b93b6e 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -39,10 +39,20 @@ struct xe_mmio_gem { phys_addr_t phys_addr; }; +static int xe_mmio_gem_vm_may_split(struct vm_area_struct *area, unsigned long addr) +{ + /* + * Forbid splitting. Together with VM_DONTEXPAND, this keeps the VMA + * matching the GEM object exactly. + */ + return -EINVAL; +} + static const struct vm_operations_struct vm_ops = { .open = drm_gem_vm_open, .close = drm_gem_vm_close, .fault = xe_mmio_gem_vm_fault, + .may_split = xe_mmio_gem_vm_may_split, }; static const struct drm_gem_object_funcs xe_mmio_gem_funcs = { From 819f189265a5955da743e78e20a713a038982e89 Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:49 +0100 Subject: [PATCH 1648/2366] drm/xe/mmio_gem: use write-back mapping for dummy page Currently vmf_insert_pfn() maps the dummy page as UC, inheriting the VMA's page protection which was set for the real MMIO region. This conflicts with the direct map's WB mapping of the same page, creating a cache type alias which is architecturally undefined on some platforms. Use vmf_insert_pfn_prot() with a WB pgprot instead. Also simplify to fault in the requested page instead of the whole VMA. Fixes: 1ffcf8b8ae8a ("drm/xe: Support for mmap-ing mmio regions") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260525125801.975038-6-ilia.levi%40intel.com Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Ilia Levi Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-12-matthew.auld@intel.com (cherry picked from commit 1e8e28e35df0e77ae1b22fc091c1f422f62fa5e9) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index d54477b93b6e..96f3bf46fd75 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -172,14 +172,13 @@ static void xe_mmio_gem_release_dummy_page(struct drm_device *dev, void *res) __free_page((struct page *)res); } -static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_area_struct *vma) +static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_fault *vmf) { + struct vm_area_struct *vma = vmf->vma; struct drm_gem_object *base = vma->vm_private_data; struct drm_device *dev = base->dev; - vm_fault_t ret = VM_FAULT_NOPAGE; struct page *page; unsigned long pfn; - unsigned long i; page = alloc_page(GFP_KERNEL | __GFP_ZERO); if (!page) @@ -190,16 +189,8 @@ static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_area_struct *vma) pfn = page_to_pfn(page); - /* Map the entire VMA to the same dummy page */ - for (i = 0; i < base->size; i += PAGE_SIZE) { - unsigned long addr = vma->vm_start + i; - - ret = vmf_insert_pfn(vma, addr, pfn); - if (ret & VM_FAULT_ERROR) - break; - } - - return ret; + return vmf_insert_pfn_prot(vma, vmf->address, pfn, + vm_get_page_prot(vma->vm_flags)); } static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) @@ -219,7 +210,7 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) * It is assumed the userspace will receive the notification via some * other channel (e.g. drm uevent). */ - return xe_mmio_gem_vm_fault_dummy_page(vma); + return xe_mmio_gem_vm_fault_dummy_page(vmf); } for (i = 0; i < base->size; i += PAGE_SIZE) { From 2b04d6556964ae9f89819b86a0a7801e39c3aae5 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Fri, 4 Sep 2026 13:03:38 -0700 Subject: [PATCH 1649/2366] wifi: mac80211: refuse to make a monitor active when it has no queue A monitor interface only gets a TXQ if it's created active, and one can't be added later. Setting the flag on a down interface is still allowed, so the driver is handed a monitor with no queue. ath9k dereferences it: BUG: kernel NULL pointer dereference, address: 0000000000000066 RIP: 0010:ath_tx_node_init+0x49/0x170 [ath9k] ath9k_add_interface+0x10c/0x140 [ath9k] drv_add_interface+0x54/0x250 [mac80211] ieee80211_do_open+0x32f/0x800 [mac80211] Reached with CAP_NET_ADMIN by "iw dev X set monitor active" followed by "ip link set X up". RTNL is held, so netlink operations block behind it. Refuse the flag when there is no queue to give. Fixes: 79af1f866193 ("mac80211: avoid allocating TXQs that won't be used") Cc: stable@vger.kernel.org Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260904200338.10829-1-lucid_duck@justthetip.ca Signed-off-by: Johannes Berg --- net/mac80211/cfg.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index d3558f0c7550..a1753335eb9d 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -115,6 +115,10 @@ static int ieee80211_set_mon_options(struct ieee80211_sub_if_data *sdata, return -EBUSY; } + /* TXQs are reserved in ieee80211_if_add() and cannot be added later */ + if ((params->flags & MONITOR_FLAG_ACTIVE) && !sdata->vif.txq) + return -EOPNOTSUPP; + /* validate whether MU-MIMO can be configured */ if (!ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF) && !ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR) && From 0c50663403b7aa271b1974ba32ee6c8296711142 Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:50 +0100 Subject: [PATCH 1650/2366] drm/xe/mmio_gem: simplify fault handler loop Make the iteration over the addresses in the VMA more explicit. No functional change, as the VMA matches the GEM object exactly. Signed-off-by: Ilia Levi Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-13-matthew.auld@intel.com (cherry picked from commit 6666ca9192f3bdf839aad33b9e1c9ebb7a222a29) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 96f3bf46fd75..3b6d04efe81a 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -200,7 +200,7 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) struct xe_mmio_gem *obj = to_xe_mmio_gem(base); struct drm_device *dev = base->dev; vm_fault_t ret = VM_FAULT_NOPAGE; - unsigned long i; + unsigned long addr, pfn; int idx; if (!drm_dev_enter(dev, &idx)) { @@ -213,13 +213,13 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) return xe_mmio_gem_vm_fault_dummy_page(vmf); } - for (i = 0; i < base->size; i += PAGE_SIZE) { - unsigned long addr = vma->vm_start + i; - unsigned long phys_addr = obj->phys_addr + i; - - ret = vmf_insert_pfn(vma, addr, PHYS_PFN(phys_addr)); + pfn = PHYS_PFN(obj->phys_addr); + for (addr = vma->vm_start; addr < vma->vm_end; addr += PAGE_SIZE) { + ret = vmf_insert_pfn(vma, addr, pfn); if (ret & VM_FAULT_ERROR) break; + + pfn++; } drm_dev_exit(idx); From 37fcbd7b2f8996d783932dab11bc668e169b0de6 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Tue, 8 Sep 2026 17:50:51 +0100 Subject: [PATCH 1651/2366] drm/xe/mmio_gem: Revoke drm_vma_node on xe_mmio_gem destroy xe_mmio_gem_create() calls drm_vma_node_allow() but nothing ever calls drm_vma_node_revoke(). The drm_vma_offset_file rb-tree entry allocated by drm_vma_node_allow() is not freed by drm_gem_object_release(), so it is leaked on every create/destroy cycle. Add a struct drm_file * parameter to xe_mmio_gem_destroy() and call drm_vma_node_revoke() from there, mirroring the drm_vma_node_allow() call in xe_mmio_gem_create(). Fixes: 1ffcf8b8ae8a ("drm/xe: Support for mmap-ing mmio regions") Suggested-by: Ilia Levi Assisted-by: Claude:claude-opus-4.6 Signed-off-by: Shuicheng Lin Reviewed-by: Ilia Levi Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-14-matthew.auld@intel.com (cherry picked from commit 32f0cb250598456d812fb7ca57a040282858323d) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 4 +++- drivers/gpu/drm/xe/xe_mmio_gem.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 3b6d04efe81a..3c42d8358c7d 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -138,14 +138,16 @@ static void xe_mmio_gem_free(struct drm_gem_object *base) /** * xe_mmio_gem_destroy - Destroy the GEM object that exposes an MMIO region * @gem: the GEM object to destroy + * @file: DRM file descriptor previously passed to xe_mmio_gem_create() * * This function releases resources associated with the GEM object created by * xe_mmio_gem_create(). * * See: "Exposing MMIO regions to userspace" */ -void xe_mmio_gem_destroy(struct xe_mmio_gem *gem) +void xe_mmio_gem_destroy(struct xe_mmio_gem *gem, struct drm_file *file) { + drm_vma_node_revoke(&gem->base.vma_node, file); xe_mmio_gem_free(&gem->base); } diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.h b/drivers/gpu/drm/xe/xe_mmio_gem.h index 4b76d5586ebb..80d7795f07c8 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.h +++ b/drivers/gpu/drm/xe/xe_mmio_gem.h @@ -15,6 +15,6 @@ struct xe_mmio_gem; struct xe_mmio_gem *xe_mmio_gem_create(struct xe_device *xe, struct drm_file *file, phys_addr_t phys_addr, size_t size); u64 xe_mmio_gem_mmap_offset(struct xe_mmio_gem *gem); -void xe_mmio_gem_destroy(struct xe_mmio_gem *gem); +void xe_mmio_gem_destroy(struct xe_mmio_gem *gem, struct drm_file *file); #endif /* _XE_MMIO_GEM_H_ */ From de40d31275cd57408ff1fdd97ea117a3f8c50cc5 Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:52 +0100 Subject: [PATCH 1652/2366] drm/xe/mmio_gem: cache the dummy page per object Currently, when the fault handler provides a dummy page, it allocates a new one on every invocation and ties its lifetime to the drm_device via drmm_add_action_or_reset(). Concurrent faults after hot-unplug therefore accumulate pages that persist until device teardown. Cache a single dummy page in the xe_mmio_gem object and use dma_resv lock to protect its allocation. Free it with the object. v2: use dma_resv lock to protect the allocation (Matt Auld) Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Ilia Levi Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-15-matthew.auld@intel.com (cherry picked from commit 8bf6213f9831e46313af4722a1ee6db1b7596378) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 3c42d8358c7d..970b1e2f4981 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -5,9 +5,9 @@ #include "xe_mmio_gem.h" +#include #include #include -#include #include "xe_device_types.h" @@ -37,6 +37,7 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *); struct xe_mmio_gem { struct drm_gem_object base; phys_addr_t phys_addr; + struct page *dummy_page; /* protected by the GEM's dma_resv */ }; static int xe_mmio_gem_vm_may_split(struct vm_area_struct *area, unsigned long addr) @@ -131,6 +132,8 @@ static void xe_mmio_gem_free(struct drm_gem_object *base) { struct xe_mmio_gem *obj = to_xe_mmio_gem(base); + if (obj->dummy_page) + __free_page(obj->dummy_page); drm_gem_object_release(base); kfree(obj); } @@ -169,27 +172,29 @@ static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct * return 0; } -static void xe_mmio_gem_release_dummy_page(struct drm_device *dev, void *res) +static int alloc_dummy_page_if_needed(struct drm_gem_object *base) { - __free_page((struct page *)res); + struct xe_mmio_gem *obj = to_xe_mmio_gem(base); + + dma_resv_lock(base->resv, NULL); + if (!obj->dummy_page) + obj->dummy_page = alloc_page(GFP_KERNEL | __GFP_ZERO); + dma_resv_unlock(base->resv); + + return obj->dummy_page ? 0 : -ENOMEM; } static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; struct drm_gem_object *base = vma->vm_private_data; - struct drm_device *dev = base->dev; - struct page *page; + struct xe_mmio_gem *obj = to_xe_mmio_gem(base); unsigned long pfn; - page = alloc_page(GFP_KERNEL | __GFP_ZERO); - if (!page) + if (alloc_dummy_page_if_needed(base)) return VM_FAULT_OOM; - if (drmm_add_action_or_reset(dev, xe_mmio_gem_release_dummy_page, page)) - return VM_FAULT_OOM; - - pfn = page_to_pfn(page); + pfn = page_to_pfn(obj->dummy_page); return vmf_insert_pfn_prot(vma, vmf->address, pfn, vm_get_page_prot(vma->vm_flags)); From d0c09528781938362655d9c336364e777119bd6b Mon Sep 17 00:00:00 2001 From: Ilia Levi Date: Tue, 8 Sep 2026 17:50:53 +0100 Subject: [PATCH 1653/2366] drm/xe/mmio_gem: fix destroy flow xe_mmio_gem_destroy() currently frees the GEM object directly, bypassing reference counting. Since existing VMAs hold a reference and the fault handler accesses the object through vma->vm_private_data, this is use-after-free. Additionally, nothing prevents the fault handler from installing PTEs to the real MMIO after destroy. Fix this with proper synchronization and refcounting. Also, do not set vm_pgoff to zero. Many DRM drivers do this because helpers like dma_mmap_pages() interpret vm_pgoff as an intra-buffer page offset; leaving the DRM fake offset there would break these helpers. Those drivers can get away with zeroing it because they map eagerly - all PTEs are established before mmap returns, so vm_pgoff is never consulted again. Our driver does not use such helpers and the newly introduced call to drm_vma_node_unmap() relies on vm_pgoff being untouched. v2: (Matt Auld) - use dma_resv lock to serialize fault handler with destroy - SIGBUS on access after destroy Fixes: 1ffcf8b8ae8a ("drm/xe: Support for mmap-ing mmio regions") Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Ilia Levi Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260908165046.1393557-16-matthew.auld@intel.com (cherry picked from commit fb2ee38bab8025ad6a7a9cbb4635c5a178e4a7bc) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_mmio_gem.c | 42 ++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c index 970b1e2f4981..5ffe03d36190 100644 --- a/drivers/gpu/drm/xe/xe_mmio_gem.c +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c @@ -38,6 +38,7 @@ struct xe_mmio_gem { struct drm_gem_object base; phys_addr_t phys_addr; struct page *dummy_page; /* protected by the GEM's dma_resv */ + bool destroyed; /* protected by the GEM's dma_resv */ }; static int xe_mmio_gem_vm_may_split(struct vm_area_struct *area, unsigned long addr) @@ -150,8 +151,22 @@ static void xe_mmio_gem_free(struct drm_gem_object *base) */ void xe_mmio_gem_destroy(struct xe_mmio_gem *gem, struct drm_file *file) { - drm_vma_node_revoke(&gem->base.vma_node, file); - xe_mmio_gem_free(&gem->base); + struct drm_gem_object *base = &gem->base; + struct drm_device *dev = base->dev; + + drm_vma_node_revoke(&base->vma_node, file); + + dma_resv_lock(base->resv, NULL); + gem->destroyed = true; + dma_resv_unlock(base->resv); + /* + * Setting 'destroyed' under lock takes care of the subsequent faults. + * Zap the existing PTEs to cut off access to the real MMIO through + * currently mapped pages. + */ + drm_vma_node_unmap(&base->vma_node, dev->anon_inode->i_mapping); + + drm_gem_object_put(base); } static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct *vma) @@ -162,8 +177,6 @@ static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct * if ((vma->vm_flags & VM_SHARED) == 0) return -EINVAL; - /* Set vm_pgoff (used as a fake buffer offset by DRM) to 0 */ - vma->vm_pgoff = 0; vma->vm_page_prot = pgprot_noncached(vma_get_page_prot(vma)); vm_flags_set(vma, VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP | VM_DONTCOPY | VM_NORESERVE); @@ -176,10 +189,9 @@ static int alloc_dummy_page_if_needed(struct drm_gem_object *base) { struct xe_mmio_gem *obj = to_xe_mmio_gem(base); - dma_resv_lock(base->resv, NULL); + dma_resv_assert_held(base->resv); if (!obj->dummy_page) obj->dummy_page = alloc_page(GFP_KERNEL | __GFP_ZERO); - dma_resv_unlock(base->resv); return obj->dummy_page ? 0 : -ENOMEM; } @@ -200,7 +212,7 @@ static vm_fault_t xe_mmio_gem_vm_fault_dummy_page(struct vm_fault *vmf) vm_get_page_prot(vma->vm_flags)); } -static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) +static vm_fault_t xe_mmio_gem_vm_fault_locked(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; struct drm_gem_object *base = vma->vm_private_data; @@ -210,6 +222,10 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) unsigned long addr, pfn; int idx; + dma_resv_assert_held(base->resv); + if (obj->destroyed) + return VM_FAULT_SIGBUS; + if (!drm_dev_enter(dev, &idx)) { /* * Provide a dummy page to avoid SIGBUS for events such as hot-unplug. @@ -232,3 +248,15 @@ static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) drm_dev_exit(idx); return ret; } + +static vm_fault_t xe_mmio_gem_vm_fault(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; + struct drm_gem_object *base = vma->vm_private_data; + vm_fault_t ret; + + dma_resv_lock(base->resv, NULL); + ret = xe_mmio_gem_vm_fault_locked(vmf); + dma_resv_unlock(base->resv); + return ret; +} From 3c90e42a01426262f0cd166bc01b45c05562640d Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Wed, 9 Sep 2026 16:21:01 +0000 Subject: [PATCH 1654/2366] drm/xe/shrinker: Return the freed page count through a parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit __xe_shrinker_walk() and xe_shrinker_walk() return either the number of pages freed or a negative error, so the two cannot be reported at once. On error the pages already freed are dropped, and since xe_shrinker_scan() only accumulates non-negative returns while *scanned is updated by pointer, the shrinker tells mm that it scanned without freeing. Accumulate the count into a caller-provided counter and return only the status, so an error no longer discards what the walk had freed. Fixes: 00c8efc3180f ("drm/xe: Add a shrinker for xe bos") Assisted-by: Claude:claude-opus-5 Reviewed-by: Thomas Hellström Cc: Matthew Brost Link: https://patch.msgid.link/20260909162102.1097006-2-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin (cherry picked from commit d7aac1a0235a6ce41e30cec385e2db8c33dad12d) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_shrinker.c | 62 ++++++++++++++------------------ 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c index 83374cd57660..89445cd20238 100644 --- a/drivers/gpu/drm/xe/xe_shrinker.c +++ b/drivers/gpu/drm/xe/xe_shrinker.c @@ -54,13 +54,14 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea write_unlock(&shrinker->lock); } -static s64 __xe_shrinker_walk(struct xe_device *xe, +static int __xe_shrinker_walk(struct xe_device *xe, struct ttm_operation_ctx *ctx, const struct xe_bo_shrink_flags flags, - unsigned long to_scan, unsigned long *scanned) + unsigned long to_scan, unsigned long *scanned, + unsigned long *freed) { unsigned int mem_type; - s64 freed = 0, lret; + s64 lret; for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { struct ttm_resource_manager *man = ttm_manager_type(&xe->ttm, mem_type); @@ -82,7 +83,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, if (lret < 0) return lret; - freed += lret; + *freed += lret; if (*scanned >= to_scan) break; } @@ -90,7 +91,7 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, xe_assert(xe, !IS_ERR(ttm_bo)); } - return freed; + return 0; } /* @@ -99,40 +100,35 @@ static s64 __xe_shrinker_walk(struct xe_device *xe, * add writeback. This avoids stalls and explicit writebacks with light or * moderate memory pressure. */ -static s64 xe_shrinker_walk(struct xe_device *xe, +static int xe_shrinker_walk(struct xe_device *xe, struct ttm_operation_ctx *ctx, const struct xe_bo_shrink_flags flags, - unsigned long to_scan, unsigned long *scanned) + unsigned long to_scan, unsigned long *scanned, + unsigned long *freed) { bool no_wait_gpu = true; struct xe_bo_shrink_flags save_flags = flags; - s64 lret, freed; + int ret; swap(no_wait_gpu, ctx->no_wait_gpu); save_flags.writeback = false; - lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); + ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); swap(no_wait_gpu, ctx->no_wait_gpu); - if (lret < 0 || *scanned >= to_scan) - return lret; + if (ret || *scanned >= to_scan) + return ret; - freed = lret; if (!ctx->no_wait_gpu) { - lret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned); - if (lret < 0) - return lret; - freed += lret; - if (*scanned >= to_scan) - return freed; + ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, + freed); + if (ret || *scanned >= to_scan) + return ret; } - if (flags.writeback) { - lret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned); - if (lret < 0) - return lret; - freed += lret; - } + if (flags.writeback) + ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, + freed); - return freed; + return ret; } static unsigned long @@ -214,7 +210,6 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con bool runtime_pm; bool purgeable; bool can_backup = !!(sc->gfp_mask & __GFP_FS); - s64 lret; nr_to_scan = sc->nr_to_scan; @@ -225,12 +220,9 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con /* Might need runtime PM. Try to wake early if it looks like it. */ runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); - if (purgeable && nr_scanned < nr_to_scan) { - lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, - nr_to_scan, &nr_scanned); - if (lret >= 0) - freed += lret; - } + if (purgeable && nr_scanned < nr_to_scan) + xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); sc->nr_scanned = nr_scanned; if (nr_scanned >= nr_to_scan || !can_backup) @@ -242,10 +234,8 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con shrink_flags.purge = false; - lret = xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, - nr_to_scan, &nr_scanned); - if (lret >= 0) - freed += lret; + xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, + nr_to_scan, &nr_scanned, &freed); sc->nr_scanned = nr_scanned; out: From 985862be16c7e4da808c51f393d631fb60c0be5c Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Wed, 9 Sep 2026 16:21:02 +0000 Subject: [PATCH 1655/2366] drm/xe/shrinker: Take a runtime PM ref before shrinking non-system memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit __xe_shrinker_walk() walks the SYSTEM and TT LRUs without a runtime PM reference. Shrinking a bo outside system memory invalidates its GPU mappings, which needs the device resumed, so while it is runtime suspended the page table zap trips an assert and the TLB invalidation returns -ENODEV: WARNING: drivers/gpu/drm/xe/xe_bo.c:770 at xe_bo_move_notify+0x1fc/0x450 [xe] xe_bo_shrink+0x20f/0x2b0 [xe] __xe_shrinker_walk+0x174/0x410 [xe] xe_shrinker_scan+0x10c/0x1e0 [xe] do_shrink_slab+0x176/0x7e0 drop_caches_sysctl_handler+0x9c/0xf0 Take a reference before walking a memory type other than XE_PL_SYSTEM and stop there if it cannot be acquired. Reuse the shrinker's existing acquire path, which resumes the device directly where reclaim allows that and otherwise queues the PM worker for a later scan. Stop the walk once the scan target is met, so a satisfied scan does not wake the device. System memory is still reclaimed while the device is suspended. Gate this on xe_device_is_l2_flush_optimized(), the same condition under which xe_bo_trigger_rebind() issues the invalidation for a non-fault-mode vm, so reclaim is unaffected elsewhere. The System CCS copy already has its own reference in xe_bo_shrink(). Only a non-fault-mode vm can reach this, since a fault-mode vm requires LR mode and that holds a runtime PM reference for the vm's lifetime. Reproduced with igt@xe_madvise@dontneed-before-exec while the GPU is runtime suspended. v2: simplify needs_rpm check. (Matt) retarget Fixes tag since the issue occurs with the non-fault-mode path added by 4e7ebff69aed. v3: handle this in xe_shrinker.c instead of xe_bo.c (Thomas) v4: stop the walk once the scan target is met. (Sashiko) v5: rebase on the freed page accounting fix. (Sashiko) v6: reuse the shrinker acquire path so runtime pm can be resumed directly instead of always queueing a worker. (Thomas) v7: replace xe_pm_runtime_put() with xe_shrinker_runtime_pm_put(). (Thomas) Fixes: 4e7ebff69aed ("drm/xe/xe3p_lpg: flush shrinker bo cachelines manually") Assisted-by: Claude:claude-opus-5 Cc: Tejas Upadhyay Cc: Matthew Brost Reviewed-by: Thomas Hellström Link: https://patch.msgid.link/20260909162102.1097006-3-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin (cherry picked from commit 628f92b28bf4c371c10207daf6fc4caee0c0db2e) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_shrinker.c | 78 +++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c index 89445cd20238..deb4378c1ec1 100644 --- a/drivers/gpu/drm/xe/xe_shrinker.c +++ b/drivers/gpu/drm/xe/xe_shrinker.c @@ -54,13 +54,39 @@ xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgea write_unlock(&shrinker->lock); } -static int __xe_shrinker_walk(struct xe_device *xe, +static bool __xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker) +{ + struct xe_device *xe = shrinker->xe; + + if (xe_pm_runtime_get_if_active(xe)) + return true; + + if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { + xe_pm_runtime_get(xe); + return true; + } + + queue_work(xe->unordered_wq, &shrinker->pm_worker); + + return false; +} + +static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) +{ + if (runtime_pm) + xe_pm_runtime_put(shrinker->xe); +} + +static int __xe_shrinker_walk(struct xe_shrinker *shrinker, struct ttm_operation_ctx *ctx, const struct xe_bo_shrink_flags flags, unsigned long to_scan, unsigned long *scanned, unsigned long *freed) { + struct xe_device *xe = shrinker->xe; unsigned int mem_type; + bool rpm = false; + int ret = 0; s64 lret; for (mem_type = XE_PL_SYSTEM; mem_type <= XE_PL_TT; ++mem_type) { @@ -75,23 +101,35 @@ static int __xe_shrinker_walk(struct xe_device *xe, if (!man || !man->use_tt) continue; + if (mem_type != XE_PL_SYSTEM && !rpm && + xe_device_is_l2_flush_optimized(xe)) { + if (!__xe_shrinker_runtime_pm_get(shrinker)) + break; + rpm = true; + } + ttm_bo_lru_for_each_reserved_guarded(&curs, man, &arg, ttm_bo) { if (!ttm_bo_shrink_suitable(ttm_bo, ctx)) continue; lret = xe_bo_shrink(ctx, ttm_bo, flags, scanned); - if (lret < 0) - return lret; + if (lret < 0) { + ret = lret; + goto out; + } *freed += lret; if (*scanned >= to_scan) - break; + goto out; } /* Trylocks should never error, just fail. */ xe_assert(xe, !IS_ERR(ttm_bo)); } - return 0; +out: + xe_shrinker_runtime_pm_put(shrinker, rpm); + + return ret; } /* @@ -100,7 +138,7 @@ static int __xe_shrinker_walk(struct xe_device *xe, * add writeback. This avoids stalls and explicit writebacks with light or * moderate memory pressure. */ -static int xe_shrinker_walk(struct xe_device *xe, +static int xe_shrinker_walk(struct xe_shrinker *shrinker, struct ttm_operation_ctx *ctx, const struct xe_bo_shrink_flags flags, unsigned long to_scan, unsigned long *scanned, @@ -112,20 +150,21 @@ static int xe_shrinker_walk(struct xe_device *xe, swap(no_wait_gpu, ctx->no_wait_gpu); save_flags.writeback = false; - ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, freed); + ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, + freed); swap(no_wait_gpu, ctx->no_wait_gpu); if (ret || *scanned >= to_scan) return ret; if (!ctx->no_wait_gpu) { - ret = __xe_shrinker_walk(xe, ctx, save_flags, to_scan, scanned, + ret = __xe_shrinker_walk(shrinker, ctx, save_flags, to_scan, scanned, freed); if (ret || *scanned >= to_scan) return ret; } if (flags.writeback) - ret = __xe_shrinker_walk(xe, ctx, flags, to_scan, scanned, + ret = __xe_shrinker_walk(shrinker, ctx, flags, to_scan, scanned, freed); return ret; @@ -176,22 +215,7 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force, return false; } - if (!xe_pm_runtime_get_if_active(xe)) { - if (xe_rpm_reclaim_safe(xe) && !ttm_bo_shrink_avoid_wait()) { - xe_pm_runtime_get(xe); - return true; - } - queue_work(xe->unordered_wq, &shrinker->pm_worker); - return false; - } - - return true; -} - -static void xe_shrinker_runtime_pm_put(struct xe_shrinker *shrinker, bool runtime_pm) -{ - if (runtime_pm) - xe_pm_runtime_put(shrinker->xe); + return __xe_shrinker_runtime_pm_get(shrinker); } static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc) @@ -221,7 +245,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup); if (purgeable && nr_scanned < nr_to_scan) - xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, + xe_shrinker_walk(shrinker, &ctx, shrink_flags, nr_to_scan, &nr_scanned, &freed); sc->nr_scanned = nr_scanned; @@ -234,7 +258,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con shrink_flags.purge = false; - xe_shrinker_walk(shrinker->xe, &ctx, shrink_flags, + xe_shrinker_walk(shrinker, &ctx, shrink_flags, nr_to_scan, &nr_scanned, &freed); sc->nr_scanned = nr_scanned; From 073a30d75f309812ed61af134f24ffef4107b13a Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 16:31:10 +0200 Subject: [PATCH 1656/2366] drm/vc4: Use managed KMS polling to fix UAF on unbind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vc4_kms_load() calls drm_kms_helper_poll_init() but the driver provides no matching drm_kms_helper_poll_fini(). The output poll work stays scheduled after unbind and runs on the freed drm_device: # modprobe vc4; rmmod vc4; sleep 10 BUG: KASAN: slab-use-after-free in delayed_work_timer_fn BUG: KASAN: slab-use-after-free in drm_client_dev_hotplug [drm] Workqueue: events output_poll_execute [drm_kms_helper] Allocated by task 171: __devm_drm_dev_alloc Freed by task 262 (rmmod): drm_dev_put / component_del Use drmm_kms_helper_poll_init() so polling is finalized with the device, as other drivers do. Fixes: c8b75bca92cb ("drm/vc4: Add KMS support for Raspberry Pi.") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260822143110.68594-1-kmehltretter@gmail.com Reviewed-by: Maíra Canal Signed-off-by: Maíra Canal --- drivers/gpu/drm/vc4/vc4_kms.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c index b17e73bce384..82a7f2ac4c7e 100644 --- a/drivers/gpu/drm/vc4/vc4_kms.c +++ b/drivers/gpu/drm/vc4/vc4_kms.c @@ -1188,7 +1188,7 @@ int vc4_kms_load(struct drm_device *dev) drm_mode_config_reset(dev); - drm_kms_helper_poll_init(dev); + drmm_kms_helper_poll_init(dev); return 0; } From bdf5f731957de48acada392f28e82bc019713adb Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Mon, 14 Sep 2026 17:41:36 +0700 Subject: [PATCH 1657/2366] hwmon: (gpio-fan) return IRQ_HANDLED from the shared alarm IRQ handler fan_alarm_irq_handler() always schedules alarm_work but returns IRQ_NONE, so the kernel treats every alarm interrupt as unhandled. On a shared line that risks the whole line being disabled as spurious. v1 just fixed that, but it was still IRQF_SHARED, and always returning IRQ_HANDLED there defeats spurious-interrupt detection for the line -- if the interrupt ever fires without a real event, nothing catches it, and a fault could spin the CPU in the handler. Sashiko flagged this in v1, and Guenter confirmed: this interrupt must not be shared. So v2 drops IRQF_SHARED too. Fixes: d6fe1360f42e ("hwmon: add generic GPIO fan driver") Reported-by: Sashiko AI review Link: https://lore.kernel.org/r/20260901160931.DD3811F00A3D@smtp.kernel.org Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Link: https://patch.msgid.link/20260914104136.1797979-1-congnt264@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/gpio-fan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index df8bd9707605..3f78375eeb44 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -68,7 +68,7 @@ static irqreturn_t fan_alarm_irq_handler(int irq, void *dev_id) schedule_work(&fan_data->alarm_work); - return IRQ_NONE; + return IRQ_HANDLED; } static ssize_t fan1_alarm_show(struct device *dev, @@ -103,7 +103,7 @@ static int fan_alarm_init(struct gpio_fan_data *fan_data) irq_set_irq_type(alarm_irq, IRQ_TYPE_EDGE_BOTH); return devm_request_irq(dev, alarm_irq, fan_alarm_irq_handler, - IRQF_SHARED, "GPIO fan alarm", fan_data); + 0, "GPIO fan alarm", fan_data); } /* From 0ff9c7775e51ac6d47b1bb5c46f06b1434fe58a8 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 14:28:09 +0800 Subject: [PATCH 1658/2366] hwmon: (w83791d) remove fan/pwm 4-5 sysfs group on remove When the fan/pwm 4-5 pins are not used as GPIO, w83791d_probe() creates the w83791d_group_fanpwm45 sysfs group on the I2C client device. The probe error path removes this group when a later initialization step fails, but the normal remove path only removes w83791d_group. As a result, the optional fan/pwm 4-5 sysfs files can remain after the driver is unbound. The callbacks associated with these files access the driver data, which is devm allocated and released after driver unbind. Leaving the sysfs files behind can therefore result in accesses to stale driver data. Remove w83791d_group_fanpwm45 during normal teardown as well. This issue was found by manual code inspection. Fixes: 6e1ecd9b8f13 ("hwmon: (w83791d) fan 4/5 pins can also be used for gpio") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260914062809.1650538-1-lgs201920130244@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/w83791d.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/w83791d.c b/drivers/hwmon/w83791d.c index 4a777430af5c..4b07a25ae59e 100644 --- a/drivers/hwmon/w83791d.c +++ b/drivers/hwmon/w83791d.c @@ -1415,6 +1415,7 @@ static void w83791d_remove(struct i2c_client *client) struct w83791d_data *data = i2c_get_clientdata(client); hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &w83791d_group_fanpwm45); sysfs_remove_group(&client->dev.kobj, &w83791d_group); } From c702a5f18b780e477eccbbab558e590e9673e4cb Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 15:36:38 +0800 Subject: [PATCH 1659/2366] hwmon: (w83793) release probe data through kref w83793_probe() initializes data->kref to manage the lifetime of the driver data. The normal remove path drops the driver-owned reference with kref_put(), while watchdog users take and release additional references through the same kref. However, the probe error path still frees data directly with kfree(). This bypasses the kref-managed lifetime and discards the initial reference without a matching kref_put(), leaving the reference accounting unbalanced. Drop the probe-owned reference with kref_put() instead and let w83793_release_resources() perform the final free, matching the normal remove path. This issue was found by manual code inspection. Fixes: 5852f9609d21 ("hwmon: (w83793) Add watchdog functionality") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260914073638.1662500-1-lgs201920130244@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/w83793.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/w83793.c b/drivers/hwmon/w83793.c index a548586369e1..c6ef04c69856 100644 --- a/drivers/hwmon/w83793.c +++ b/drivers/hwmon/w83793.c @@ -1928,7 +1928,9 @@ static int w83793_probe(struct i2c_client *client) for (i = 0; i < ARRAY_SIZE(w83793_temp); i++) device_remove_file(dev, &w83793_temp[i].dev_attr); free_mem: - kfree(data); + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, w83793_release_resources); + mutex_unlock(&watchdog_data_mutex); exit: return err; } From 1c4f6202876a74e752a8cfd3696955f761671c7a Mon Sep 17 00:00:00 2001 From: Jaeho Cho Date: Mon, 14 Sep 2026 10:24:42 -0400 Subject: [PATCH 1660/2366] ALSA: hda/realtek: Enable mute LEDs on HP OmniBook 7 17-dc0xxx The HP OmniBook 7 Laptop 17-dc0xxx (SSID 103c:8d9c) has mute and mic-mute LEDs on its F6 and F9 keys, but neither lights up. Its quirk entry only sets up the two CS35L41 amplifiers, so no LED control is registered for either key. Writing to the ALC245 by hand with hda-verb, the mute LED responds to COEF 0x0b bits 2-3 and the mic-mute LED to GPIO 0x04, lit when the pin is low. That appears to match what ALC245_FIXUP_HP_X360_MUTE_LEDS already does, so add a fixup that chains the two-amp I2C setup to it and use it for this model. Tested on 7.2.4 with the patched module: hda::mute and hda::micmute are registered, and both LEDs follow the speaker and microphone mute state, including from the F6 and F9 keys. Signed-off-by: Jaeho Cho Link: https://patch.msgid.link/20260914142445.3476212-1-jaeho2025@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index fa83b739fb50..5127a111c59c 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4303,6 +4303,7 @@ enum { ALC287_FIXUP_LEGION_16ACHG6, ALC287_FIXUP_CS35L41_I2C_2, ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED, + ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS, ALC287_FIXUP_CS35L41_I2C_4, ALC245_FIXUP_CS35L41_SPI_1, ALC245_FIXUP_CS35L41_SPI_2, @@ -6614,6 +6615,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_HP_MUTE_LED, }, + [ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_two, + .chained = true, + .chain_id = ALC245_FIXUP_HP_X360_MUTE_LEDS, + }, [ALC287_FIXUP_CS35L41_I2C_4] = { .type = HDA_FIXUP_FUNC, .v.func = cs35l41_fixup_i2c_four, @@ -7662,7 +7669,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8d92, "HP ZBook Firefly 16 G12", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8dcd, "HP Victus 15-fa2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8d9b, "HP 17 Turbine OmniBook 7 UMA", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8d9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8d9e, "HP 17 Turbine OmniBook X DIS", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2), From 5ab3dc647751996784cff20a51f3730f4e88afe4 Mon Sep 17 00:00:00 2001 From: Asai Neko Date: Mon, 14 Sep 2026 18:58:29 +0800 Subject: [PATCH 1661/2366] ALSA: usb-audio: skip the broken mute control on AVerMedia GC553Pro Skip the nonfunctional master mute control on the AVerMedia Live Gamer ULTRA S GC553Pro (07ca:1553). USB tracing shows that GET_CUR returns zero bytes instead of the required one-byte value, both through usbfs and during ALSA initialization. SET_CUR succeeds, but switching capture off does not mute HDMI audio. Before the change, the driver exposed a misleading PCM Capture Switch and logged: 3:2: failed to get current value for ch 0 (-22) With the patch applied, the switch and warning are absent. A ten-second sound recording through PipeWire confirmed that stereo 48 kHz, 16-bit capture still works. Tested on NixOS with the patched 7.3.0-rc3 kernel. The USB audio driver object builds with Clang and W=1; sparse and strict checkpatch pass. Signed-off-by: Asai Neko Link: https://patch.msgid.link/20260914-avermedia-gc553pro-alsa-v1-1-4c694e8b0cd5@sne.moe Signed-off-by: Takashi Iwai --- sound/usb/mixer_maps.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 69093c666282..41454cb403d0 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -531,6 +531,15 @@ static const struct usbmix_name_map audient_id24_map[] = { {} }; +/* + * The GC553Pro returns no data for GET_CUR on its advertised mute control. + * SET_CUR succeeds but does not mute capture, so skip the control entirely. + */ +static const struct usbmix_name_map avermedia_gc553pro_map[] = { + { 3, NULL, UAC_FU_MUTE }, + {} +}; + /* * Control map entries */ @@ -577,6 +586,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x0763, 0x2031), .selector_map = c400_selectors, }, + { + .id = USB_ID(0x07ca, 0x1553), + .map = avermedia_gc553pro_map, + }, { .id = USB_ID(0x08bb, 0x2702), .map = linex_map, From 9d1e523b92d8cb11a4a7e5a2655d6824c2d0f6e3 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 4 Sep 2026 14:52:59 +0200 Subject: [PATCH 1662/2366] selftests/hid: add define for commonly used buf size If we want to add another report descriptor without report IDs with a report size bigger than 10, we have multiple magic values to replace. Put a #define once and for all, so we don't have dangling ones. Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hid_bpf.c | 38 ++++++++++++------------ tools/testing/selftests/hid/hid_common.h | 3 +- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index 7ab86296ff23..0d03ad5245fc 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -5,7 +5,7 @@ #include struct hid_hw_request_syscall_args { - __u8 data[10]; + __u8 data[MAX_BUF_SIZE]; unsigned int hid; int retval; size_t size; @@ -175,7 +175,7 @@ TEST_F(hid_bpf, raw_event) const struct test_program progs[] = { { .name = "hid_first_event" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -226,7 +226,7 @@ TEST_F(hid_bpf, subprog_raw_event) const struct test_program progs[] = { { .name = "hid_subprog_first_event" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -284,7 +284,7 @@ TEST_F(hid_bpf, test_attach_detach) { .name = "hid_second_event" }, }; struct bpf_link *link; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err, link_fd; LOAD_PROGRAMS(progs); @@ -369,7 +369,7 @@ TEST_F(hid_bpf, test_hid_change_report) const struct test_program progs[] = { { .name = "hid_change_report_id" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -396,13 +396,13 @@ TEST_F(hid_bpf, test_hid_user_input_report_call) { struct hid_hw_request_syscall_args args = { .retval = -1, - .size = 10, + .size = MAX_BUF_SIZE, }; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, .ctx_in = &args, .ctx_size_in = sizeof(args), ); - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err, prog_fd; LOAD_BPF; @@ -442,7 +442,7 @@ TEST_F(hid_bpf, test_hid_user_output_report_call) { struct hid_hw_request_syscall_args args = { .retval = -1, - .size = 10, + .size = MAX_BUF_SIZE, }; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, .ctx_in = &args, @@ -491,7 +491,7 @@ TEST_F(hid_bpf, test_hid_user_raw_request_call) .retval = -1, .type = HID_FEATURE_REPORT, .request_type = HID_REQ_GET_REPORT, - .size = 10, + .size = MAX_BUF_SIZE, }; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, .ctx_in = &args, @@ -524,7 +524,7 @@ TEST_F(hid_bpf, test_hid_filter_raw_request_call) const struct test_program progs[] = { { .name = "hid_test_filter_raw_request" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -577,7 +577,7 @@ TEST_F(hid_bpf, test_hid_change_raw_request_call) const struct test_program progs[] = { { .name = "hid_test_hidraw_raw_request" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -603,7 +603,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_raw_request_call) const struct test_program progs[] = { { .name = "hid_test_infinite_loop_raw_request" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -626,7 +626,7 @@ TEST_F(hid_bpf, test_hid_filter_output_report_call) const struct test_program progs[] = { { .name = "hid_test_filter_output_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -679,7 +679,7 @@ TEST_F(hid_bpf, test_hid_change_output_report_call) const struct test_program progs[] = { { .name = "hid_test_hidraw_output_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -703,7 +703,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_output_report_call) const struct test_program progs[] = { { .name = "hid_test_infinite_loop_output_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -729,7 +729,7 @@ TEST_F(hid_bpf, test_multiply_events_wq) const struct test_program progs[] = { { .name = "hid_test_multiply_events_wq" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -767,7 +767,7 @@ TEST_F(hid_bpf, test_multiply_events) const struct test_program progs[] = { { .name = "hid_test_multiply_events" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -801,7 +801,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_input_report_call) const struct test_program progs[] = { { .name = "hid_test_infinite_loop_input_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -855,7 +855,7 @@ TEST_F(hid_bpf, test_hid_attach_flags) .insert_head = 0, }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); diff --git a/tools/testing/selftests/hid/hid_common.h b/tools/testing/selftests/hid/hid_common.h index e3b267446fa0..4567336f131d 100644 --- a/tools/testing/selftests/hid/hid_common.h +++ b/tools/testing/selftests/hid/hid_common.h @@ -13,6 +13,7 @@ #include #define SHOW_UHID_DEBUG 0 +#define MAX_BUF_SIZE 10 #define min(a, b) \ ({ __typeof__(a) _a = (a); \ @@ -110,7 +111,7 @@ static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER; static pthread_mutex_t uhid_output_mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t uhid_output_cond = PTHREAD_COND_INITIALIZER; -static unsigned char output_report[10]; +static unsigned char output_report[MAX_BUF_SIZE]; /* no need to protect uhid_stopped, only one thread accesses it */ static bool uhid_stopped; From c4afa4862b878d56e0cc1021298794ac1b45bc49 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 4 Sep 2026 14:53:00 +0200 Subject: [PATCH 1663/2366] HID: bpf: fix __hid_bpf_hw_check_params report length Turns out that USB, I2C and other transport drivers (except uhid which just passes the data) still need to have the report ID in the first byte. Because they expect the first byte to be the report ID or 0, when the report ID is 0, they strip that first byte before forwarding to the device. This means that the transport layer forwards a buffer of size N-1 to the device, which gets rejected. Fixes: 5599f8019661 ("HID: bpf: export hid_hw_output_report as a BPF kfunc") Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/hid_bpf_dispatch.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index 536f6d01fd14..b1de1dd0f21d 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -359,7 +359,7 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx) static int __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, - enum hid_report_type rtype) + enum hid_report_type rtype, bool hw_request) { struct hid_report_enum *report_enum; struct hid_report *report; @@ -388,6 +388,10 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, report_len = hid_report_len(report); + /* unnumbered reports need to have a report ID reserved in the first byte */ + if (hw_request && report_enum->numbered == 0) + report_len += 1; + if (*buf__sz > report_len) *buf__sz = report_len; @@ -420,7 +424,7 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype, true); if (ret) return ret; @@ -480,7 +484,7 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz) return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT, true); if (ret) return ret; @@ -506,7 +510,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, type); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, type, false); if (ret) return ret; From b6f69097c8271cca30314fd6e4c802f90737bfcc Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 4 Sep 2026 14:53:01 +0200 Subject: [PATCH 1664/2366] selftests/hid: add unnumbered variant to the hid_bpf tests A bug appeared in hid_bpf_dispatch.c where it wasn't properly handling unnumbered reports. Add a device variant without report IDs so we can also test them. Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hid_bpf.c | 53 ++++++++++++++++++++---- tools/testing/selftests/hid/hid_common.h | 24 ++++++++++- tools/testing/selftests/hid/progs/hid.c | 2 +- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index 0d03ad5245fc..32d81ba15a25 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -54,11 +54,27 @@ FIXTURE_TEARDOWN(hid_bpf) { hid_bpf_teardown(_metadata, self, variant); \ } while (0) +FIXTURE_VARIANT(hid_bpf) { + __u8 *rdesc; + size_t rdesc_size; +}; + +FIXTURE_VARIANT_ADD(hid_bpf, numbered) { + .rdesc = rdesc, + .rdesc_size = sizeof(rdesc), +}; + +FIXTURE_VARIANT_ADD(hid_bpf, unnumbered) { + .rdesc = fido2_rdesc, + .rdesc_size = sizeof(fido2_rdesc), +}; + FIXTURE_SETUP(hid_bpf) { int err; - err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a36, rdesc, sizeof(rdesc)); + err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a36, + variant->rdesc, variant->rdesc_size); ASSERT_OK(err); } @@ -409,8 +425,11 @@ TEST_F(hid_bpf, test_hid_user_input_report_call) args.hid = self->hid.hid_id; args.data[0] = 1; /* report ID */ - args.data[1] = 2; /* report ID */ - args.data[2] = 42; /* report ID */ + args.data[1] = 2; + args.data[2] = 42; + + if (variant->rdesc == fido2_rdesc) + args.data[0] = 0; prog_fd = bpf_program__fd(self->skel->progs.hid_user_input_report); @@ -428,8 +447,13 @@ TEST_F(hid_bpf, test_hid_user_input_report_call) /* read the data from hidraw */ memset(buf, 0, sizeof(buf)); err = read(self->hidraw_fd, buf, sizeof(buf)); - ASSERT_EQ(err, 6) TH_LOG("read_hidraw"); - ASSERT_EQ(buf[0], 1); + if (variant->rdesc == rdesc) { + ASSERT_EQ(err, 6) TH_LOG("read_hidraw"); + } else { + ASSERT_EQ(err, 64) + TH_LOG("read_hidraw"); + } + ASSERT_EQ(buf[0], args.data[0]); ASSERT_EQ(buf[1], 2); ASSERT_EQ(buf[2], 42); } @@ -455,8 +479,11 @@ TEST_F(hid_bpf, test_hid_user_output_report_call) args.hid = self->hid.hid_id; args.data[0] = 1; /* report ID */ - args.data[1] = 2; /* report ID */ - args.data[2] = 42; /* report ID */ + args.data[1] = 2; + args.data[2] = 42; + + if (variant->rdesc == fido2_rdesc) + args.data[0] = 0; prog_fd = bpf_program__fd(self->skel->progs.hid_user_output_report); @@ -472,9 +499,14 @@ TEST_F(hid_bpf, test_hid_user_output_report_call) ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts"); ASSERT_OK(cond_err) TH_LOG("error while calling waiting for the condition"); - ASSERT_EQ(args.retval, 3); + if (variant->rdesc == rdesc) { + ASSERT_EQ(args.retval, 3); + } else if (variant->rdesc == fido2_rdesc) { + ASSERT_EQ(args.retval, 65) + TH_LOG("report size error, should have 64 + 1 extra byte for the report ID 0"); + } - ASSERT_EQ(output_report[0], 1); + ASSERT_EQ(output_report[0], args.data[0]); ASSERT_EQ(output_report[1], 2); ASSERT_EQ(output_report[2], 42); @@ -886,6 +918,9 @@ TEST_F(hid_bpf, test_rdesc_fixup) }; int err, desc_size; + if (variant->rdesc != rdesc) + SKIP(return, "not compatible report descriptor"); + LOAD_PROGRAMS(progs); /* check that hid_rdesc_fixup() was executed */ diff --git a/tools/testing/selftests/hid/hid_common.h b/tools/testing/selftests/hid/hid_common.h index 4567336f131d..b7890ba2878f 100644 --- a/tools/testing/selftests/hid/hid_common.h +++ b/tools/testing/selftests/hid/hid_common.h @@ -13,7 +13,7 @@ #include #define SHOW_UHID_DEBUG 0 -#define MAX_BUF_SIZE 10 +#define MAX_BUF_SIZE 128 #define min(a, b) \ ({ __typeof__(a) _a = (a); \ @@ -98,6 +98,28 @@ static unsigned char rdesc[] = { static __u8 feature_data[] = { 1, 2 }; +static __maybe_unused unsigned char fido2_rdesc[] = { + 0x06, 0xd0, 0xf1, /* Usage Page (FIDO Alliance) */ + 0x09, 0x01, /* Usage (U2F Authenticator Device) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x20, /* Usage (Input Report Data) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x00, /* Logical Maximum (255) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x40, /* Report Count (64) */ + 0x81, 0x02, /* Input (Data,Var,Abs) */ + 0x09, 0x21, /* Usage (Output Report Data) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x00, /* Logical Maximum (255) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x40, /* Report Count (64) */ + 0x91, 0x02, /* Output (Data,Var,Abs) */ + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x09, 0x22, /* Usage (Vendor Usage 0x22) */ + 0xb1, 0x02, /* Feature (Data,Var,Abs) */ + 0xc0, /* End Collection */ +}; + #define ASSERT_OK(data) ASSERT_FALSE(data) #define ASSERT_OK_PTR(ptr) ASSERT_NE(NULL, ptr) diff --git a/tools/testing/selftests/hid/progs/hid.c b/tools/testing/selftests/hid/progs/hid.c index 361dc7eaad22..48aa8088cc53 100644 --- a/tools/testing/selftests/hid/progs/hid.c +++ b/tools/testing/selftests/hid/progs/hid.c @@ -98,7 +98,7 @@ struct hid_bpf_ops change_report_id = { struct hid_hw_request_syscall_args { /* data needs to come at offset 0 so we can use it in calls */ - __u8 data[10]; + __u8 data[128]; unsigned int hid; int retval; size_t size; From c9e6e5f38bf75276605f1952b22285f5f3abcaff Mon Sep 17 00:00:00 2001 From: Slavin Liu Date: Sun, 13 Sep 2026 20:51:54 +0800 Subject: [PATCH 1665/2366] ALSA: hda: trace PCM open only after assigning a stream Stream assignment can fail when hardware streams are exhausted. Move the tracepoint after the NULL check because its payload accesses the assigned stream tag. Detected by static analysis and reviewed with AI-assisted source auditing. Fixes: 184865085b88 ("ALSA: hda - rename hda_intel_trace.h to hda_controller_trace.h") Assisted-by: LLM Signed-off-by: Slavin Liu Link: https://patch.msgid.link/20260913125154.109944-1-bolin.liu@seu.edu.cn Signed-off-by: Takashi Iwai --- sound/hda/common/controller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index afec5c5546ec..18dae022b324 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -586,11 +586,11 @@ static int azx_pcm_open(struct snd_pcm_substream *substream) snd_hda_codec_pcm_get(apcm->info); mutex_lock(&chip->open_mutex); azx_dev = azx_assign_device(chip, substream); - trace_azx_pcm_open(chip, azx_dev); if (azx_dev == NULL) { err = -EBUSY; goto unlock; } + trace_azx_pcm_open(chip, azx_dev); runtime->private_data = azx_dev; runtime->hw = azx_pcm_hw; From ce9d5197d651cdd0fbb586c3d77c28438abe1b10 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 25 Aug 2026 10:13:14 +0200 Subject: [PATCH 1666/2366] wifi: ath12k: ahb: Revert undocumented ABI and dead code Commit 96f46607bbce ("wifi: ath12k: add AHB platform descriptor support") added undocumented OF ABI, by relying on a very specific node name. This is not allowed and was never acked by Devicetree maintainers. Additionally that part of code is not even used, because all devices have exactly the same user pd, so this was added "for future". Adding dead code just "for future" is heavily discouraged in kernel coding. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260825081313.71351-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/ahb.c | 51 +-------------------- 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c index 98a6606ffd76..6e9e9034cba1 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c @@ -15,21 +15,6 @@ #include "dp.h" #include "core.h" -/* - * Node name to UserPD ID mapping - * - * The io_start field is used for additional validation when the reg - * property is present in the device tree. If io_start is 0, only - * node_name matching is performed. - * - * For platforms where not all WiFi nodes have a 'reg' property, set - * io_start to 0 for those entries. The driver will match purely by - * node name in such cases. - */ -static const struct ath12k_ahb_userpd_map ath12k_wifi7_ahb_userpd_map[] = { - { .io_start = 0x0c000000, .node_name = "wifi", .upd_id = ATH12K_AHB_USERPD_ID_0 }, -}; - static const struct ath12k_ahb_desc ath12k_wifi7_ahb_desc[] = { [ATH12K_HW_IPQ5332_HW10] = { .hw_rev = ATH12K_HW_IPQ5332_HW10, @@ -55,40 +40,6 @@ static const struct of_device_id ath12k_wifi7_ahb_of_match[] = { MODULE_DEVICE_TABLE(of, ath12k_wifi7_ahb_of_match); -/* - * ath12k_wifi7_ahb_get_userpd_id - Resolve UserPD ID from DT properties - * @ab: ath12k base structure - * - * Returns: UserPD ID (1-based) on success, 0 on failure - * - * Resolution logic: - * 1. If reg property exist in DT, get userpd_id from io_start - * 2. If reg property is absent, get userpd_id from DT node name - * 3. Return 0 if no match found (probe will fail) - */ -static u32 ath12k_wifi7_ahb_get_userpd_id(struct ath12k_base *ab) -{ - const struct ath12k_ahb_userpd_map *map; - struct resource *res; - size_t i; - - res = platform_get_resource(ab->pdev, IORESOURCE_MEM, 0); - - for (i = 0; i < ARRAY_SIZE(ath12k_wifi7_ahb_userpd_map); i++) { - map = &ath12k_wifi7_ahb_userpd_map[i]; - - if (res) { - if (map->io_start && map->io_start == res->start) - return map->upd_id; - } else if (map->node_name && - of_node_name_eq(ab->dev->of_node, map->node_name)) { - return map->upd_id; - } - } - - return 0; -} - static int ath12k_wifi7_ahb_probe(struct platform_device *pdev) { const struct ath12k_ahb_desc *desc; @@ -106,7 +57,7 @@ static int ath12k_wifi7_ahb_probe(struct platform_device *pdev) ab->hw_rev = desc->hw_rev; ab->hif.ops = desc->ops; ab_ahb->scm_auth_enabled = desc->auth_enabled; - ab_ahb->userpd_id = ath12k_wifi7_ahb_get_userpd_id(ab); + ab_ahb->userpd_id = ATH12K_AHB_USERPD_ID_0; if (!ab_ahb->userpd_id) return -EOPNOTSUPP; From d9be5e75530772fc31637070d51e5717d6aeaa2a Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 10 Sep 2026 02:09:07 +0000 Subject: [PATCH 1667/2366] wifi: wcn36xx: Fix potential use-after-free in TX ack timer teardown wcn36xx_dxe_deinit() tears down the TX ack timer with timer_delete(), which only dequeues the timer and does not wait for a callback that is already executing; the preceding free_irq() calls synchronize the interrupt handlers only. The callback, wcn36xx_dxe_tx_timer(), can therefore be running past the teardown and use the wcn freed along with the ieee80211_hw in wcn36xx_remove(): it takes wcn->dxe_lock, reads wcn->tx_ack_skb and passes wcn->hw to ieee80211_tx_status_irqsafe(). Fix this by using timer_shutdown_sync(), which waits for a running callback and also prevents the timer from being rearmed again. The timer is set up again by wcn36xx_dxe_init() on the next start, so the start/stop cycle is unaffected. This issue was found by an in-house static analysis tool. Fixes: fdf21cc37149 ("wcn36xx: Add TX ack support") Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Reviewed-by: Loic Poulain Link: https://patch.msgid.link/20260910020907.3353-1-fanwu01@zju.edu.cn Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/wcn36xx/dxe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/wcn36xx/dxe.c b/drivers/net/wireless/ath/wcn36xx/dxe.c index 44020ec265fb..801f1218ef89 100644 --- a/drivers/net/wireless/ath/wcn36xx/dxe.c +++ b/drivers/net/wireless/ath/wcn36xx/dxe.c @@ -1055,7 +1055,7 @@ void wcn36xx_dxe_deinit(struct wcn36xx *wcn) free_irq(wcn->tx_irq, wcn); free_irq(wcn->rx_irq, wcn); - timer_delete(&wcn->tx_ack_timer); + timer_shutdown_sync(&wcn->tx_ack_timer); if (wcn->tx_ack_skb) { ieee80211_tx_status_irqsafe(wcn->hw, wcn->tx_ack_skb); From 820b8cff81c796ba20573e04722ab62500713f97 Mon Sep 17 00:00:00 2001 From: Nicolas Escande Date: Fri, 31 Jul 2026 16:58:30 +0200 Subject: [PATCH 1668/2366] wifi: ath11k: cleanup arsta in ath11k_mac_peer_cleanup_all() When mac80211 removes a sta, it calls .sta_state() which in turn calls ath11k_mac_station_remove(). In that function we clean up both peers & arsta related resources. But when the firmware crashes, ath11k calls ieee80211_restart_hw(), which assumes that all driver related resources are cleaned up beforehand. This cleanup is supposedly done by ath11k_mac_peer_cleanup_all() but does not in fact free arsta->rx_stats / tx_stats. Extract the arsta cleanup from ath11k_mac_station_remove() into a new ath11k_mac_station_cleanup() and call it from both there and ath11k_mac_peer_cleanup_all(). This should handle kmemleaks reports like: unreferenced object 0xffffff801ae66400 (size 1024): comm "hostapd", pid 1306, jiffies 4295011565 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ backtrace (crc d61c08ec): kmemleak_alloc+0x3c/0x50 __kmalloc_cache_noprof+0x2b0/0x3e0 ath11k_mac_op_sta_state+0x1dc/0xb10 drv_sta_state+0xac/0x6f8 sta_info_insert_rcu+0x314/0x5e0 sta_info_insert+0x14/0x38 ieee80211_add_station+0x10c/0x1a0 nl80211_new_station+0x3e8/0x680 genl_family_rcv_msg_doit+0xc0/0x120 genl_rcv_msg+0x1b4/0x258 netlink_rcv_skb+0x4c/0x108 genl_rcv+0x38/0x60 netlink_unicast+0x190/0x278 netlink_sendmsg+0x15c/0x370 ____sys_sendmsg+0x120/0x290 ___sys_sendmsg+0x70/0xa0 Tested-on: QCN9074 hw1.0 PCI WLAN.HK.2.9.0.1-01977-QCAHKSWPL_SILICONZ-1 Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Signed-off-by: Nicolas Escande Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260731145830.769811-1-nico.escande@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/mac.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c index 2d55cdc4d165..ae91b57c8422 100644 --- a/drivers/net/wireless/ath/ath11k/mac.c +++ b/drivers/net/wireless/ath/ath11k/mac.c @@ -873,6 +873,22 @@ static int ath11k_mac_set_kickout(struct ath11k_vif *arvif) return 0; } +static void ath11k_mac_station_cleanup(struct ieee80211_sta *sta) +{ + struct ath11k_sta *arsta; + + if (!sta) + return; + + arsta = ath11k_sta_to_arsta(sta); + + kfree(arsta->tx_stats); + arsta->tx_stats = NULL; + + kfree(arsta->rx_stats); + arsta->rx_stats = NULL; +} + void ath11k_mac_peer_cleanup_all(struct ath11k *ar) { struct ath11k_peer *peer, *tmp; @@ -885,6 +901,7 @@ void ath11k_mac_peer_cleanup_all(struct ath11k *ar) list_for_each_entry_safe(peer, tmp, &ab->peers, list) { ath11k_peer_rx_tid_cleanup(ar, peer); ath11k_peer_rhash_delete(ab, peer); + ath11k_mac_station_cleanup(peer->sta); list_del(&peer->list); kfree(peer); } @@ -9892,7 +9909,6 @@ static int ath11k_mac_station_remove(struct ath11k *ar, { struct ath11k_base *ab = ar->ab; struct ath11k_vif *arvif = ath11k_vif_to_arvif(vif); - struct ath11k_sta *arsta = ath11k_sta_to_arsta(sta); int ret; if (ab->hw_params.vdev_start_delay && @@ -9916,12 +9932,7 @@ static int ath11k_mac_station_remove(struct ath11k *ar, sta->addr, arvif->vdev_id); ath11k_mac_dec_num_stations(arvif, sta); - - kfree(arsta->tx_stats); - arsta->tx_stats = NULL; - - kfree(arsta->rx_stats); - arsta->rx_stats = NULL; + ath11k_mac_station_cleanup(sta); return ret; } From 3a35e787ac1acf94d33eebb26fd7248811cc66c9 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Mon, 24 Aug 2026 18:19:10 +0200 Subject: [PATCH 1669/2366] btrfs: zoned: handle RAID profiles in btrfs_can_activate_zone() btrfs_can_activate_zone() only accounts for the single and DUP profiles. For a RAID0, RAID1, RAID1C3, RAID1C4 or RAID10 block group the profile switch matches no case, so 'ret' stays false and the function reports that no zone can be activated, even when the devices have plenty of active zones left. As a side effect BTRFS_FS_NEED_ZONE_FINISH gets set and, since btrfs_can_activate_zone() bails out early once that bit is set, data allocations will fail permanently: writers loop on -EAGAIN and hang in btrfs_new_extent_direct() waiting for the bit to clear. Each of these profiles needs one active zone per device, just like single, so handle them the same way. Reviewed-by: Boris Burkov Signed-off-by: Johannes Thumshirn Signed-off-by: David Sterba --- fs/btrfs/zoned.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c index 9cc2c9c1a606..08a15465a087 100644 --- a/fs/btrfs/zoned.c +++ b/fs/btrfs/zoned.c @@ -2688,6 +2688,11 @@ bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, u64 flags) switch (flags & BTRFS_BLOCK_GROUP_PROFILE_MASK) { case 0: /* single */ + case BTRFS_BLOCK_GROUP_RAID0: + case BTRFS_BLOCK_GROUP_RAID1: + case BTRFS_BLOCK_GROUP_RAID1C3: + case BTRFS_BLOCK_GROUP_RAID1C4: + case BTRFS_BLOCK_GROUP_RAID10: ret = (atomic_read(&zinfo->active_zones_left) >= (1 + reserved)); break; case BTRFS_BLOCK_GROUP_DUP: From 0594e3423f4ba3137c734371169491f9a98e9af4 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Mon, 31 Aug 2026 13:38:01 +0800 Subject: [PATCH 1670/2366] btrfs: take commit root semaphore when iterating in mark_block_group_to_copy() mark_block_group_to_copy() iterates over the commit root with skip_locking=true. A concurrent transaction commit can swap and free the commit root during iteration, causing use-after-free when accessing extent buffers. Fix it by using path->need_commit_sem to protect the commit root search. Fixes: 78ce9fc269af ("btrfs: zoned: mark block groups to copy for device-replace") CC: stable@vger.kernel.org Assisted-by: Codex:gpt-5.5 Reviewed-by: Johannes Thumshirn Signed-off-by: Hongling Zeng Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/dev-replace.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c index bf0b78790171..1894c7ebe9d9 100644 --- a/fs/btrfs/dev-replace.c +++ b/fs/btrfs/dev-replace.c @@ -493,6 +493,7 @@ static int mark_block_group_to_copy(struct btrfs_fs_info *fs_info, path->reada = READA_FORWARD; path->search_commit_root = true; path->skip_locking = true; + path->need_commit_sem = true; key.objectid = src_dev->devid; key.type = BTRFS_DEV_EXTENT_KEY; From a1167d9420474ab9ed9efca99d86aeb6217c0265 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 10 Sep 2026 17:37:48 +0100 Subject: [PATCH 1671/2366] btrfs: tree-checker: print dev extent offset in error message If a dev extent's offset is not sector size aligned, the error message is printing the dev extent's objectid instead of the offset. This is a copy paste error, as before this check we check the objectid field. Fixes: 008e2512dc56 ("btrfs: tree-checker: add dev extent item checks") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index ab5abbb475e2..8b1b706e0996 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -2129,7 +2129,7 @@ static int check_dev_extent_item(const struct extent_buffer *leaf, sectorsize))) { generic_err(leaf, slot, "invalid dev extent chunk offset, has %llu not aligned to %u", - btrfs_dev_extent_chunk_objectid(leaf, de), + btrfs_dev_extent_chunk_offset(leaf, de), sectorsize); return -EUCLEAN; } From f59d86d25e41c5ac511584d9d6808ee448fd42b0 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 10 Sep 2026 17:48:06 +0100 Subject: [PATCH 1672/2366] btrfs: tree-checker: fix error message regarding free space extent items The error message mentions a free space info item, but we are processing a free space extent item, so fix the message. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index 8b1b706e0996..4b1e47173c63 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -2306,7 +2306,7 @@ static int check_free_space_extent(struct extent_buffer *leaf, struct btrfs_key if (unlikely(btrfs_item_size(leaf, slot) != 0)) { generic_err(leaf, slot, - "invalid item size for free space info, has %u expect 0", + "invalid item size for free space extent, has %u expect 0", btrfs_item_size(leaf, slot)); return -EUCLEAN; } From 057dac23d329d5c5ed62352f2659a39fd46c6d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Koutn=C3=BD?= Date: Mon, 14 Sep 2026 14:19:10 +0200 Subject: [PATCH 1673/2366] cgroup: Avoid iteration of dying tasks with zero refcount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit 260fbcb92bbea ("cgroup: Move dying_tasks cleanup from cgroup_task_release() to cgroup_task_free()") extended the lifetime of tasks on the dying_tasks list. The iterators have provision to go through dying_tasks because of dying threadgroup leaders or explicit CSS_TASK_ITER_WITH_DEAD, however, it was expected that such tasks can obtain a new reference (that is possible before cgroup_task_release()/put_task_struct_rcu_user()). The tasks after cgroup_task_release() and before cgroup_task_free() are subject to race when they may or may not have ->usage count > 0. The race window is between css_task_iter_next() invocations when css_set_lock is released and we may arrive at a new ->task_pos. The iterator should not attempt to resurrect tasks whose ->usage count dropped to zero. (When that happens, __put_task_struct_rcu_cb() is already imminent and the returned task_struct would could be used after free.) As for the fix, we cannot simply check the signal->live count of a task on the dying list because that won't distinguish regular zombies waiting to be reaped from RCU remnant tasks that are going to be free'd. Therefore add an extra check to rule out ->usage==0 tasks from any iteration. The repeat: loop in css_task_iter_advance() doesn't consider ->usage count, so add a new loop to css_task_iter_next() to skip de-used tasks on the dying_list. Rough illustration of the possible race R (reader of cgroup.procs) T (thread) L (group leader) --------------------------------- -------------------------------- -------------------------------- L exits, signal->live > 0 cgroup_task_dead(L) css_set_skip_task_iters() // skips only cset->tasks list_add_tail(&L->cg_list, &cset->dying_tasks) css_task_iter_next() take css_set_lock css_task_iter_advance() leader && signal->live != 0 => it->task_pos = &L->cg_list release css_set_lock T exits --signal->live == 0 cgroup_task_dead(T) // css_set_lock release_task(T) cgroup_task_release(T) release_task(L) // zap_leader cgroup_task_release(L) put_task_struct_rcu_user(L) ...RCU... put_task_struct(L) L->usage = 0 /* L still on dying_tasks */ ...RCU... __put_task_struct(L) css_task_iter_next() // another iteration take css_set_lock it->task_pos = &L->cg_list get_task_struct(L) => addition on 0 drop css_set_lock cgroup_task_free(L) css_set_skip_task_iters() // dying skip comes too late free_task(L) cgroup_procs_show() task_pid_vnr(L) Fixes: 260fbcb92bbea ("cgroup: Move dying_tasks cleanup from cgroup_task_release() to cgroup_task_free()") Cc: stable@vger.kernel.org # v6.19+ Link: https://lists.debian.org/debian-kernel/2026/08/msg00220.html Reported-by: Noah Elias Feldt Reported-by: Salvatore Bonaccorso Tested-by: Salvatore Bonaccorso Signed-off-by: Michal Koutný Signed-off-by: Tejun Heo --- kernel/cgroup/cgroup.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index 353c8f83439a..a3d363502b7b 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -5207,10 +5207,13 @@ struct task_struct *css_task_iter_next(struct css_task_iter *it) if (it->flags & CSS_TASK_ITER_SKIPPED) css_task_iter_advance(it); - if (it->task_pos) { + while (it->task_pos && !it->cur_task) { it->cur_task = list_entry(it->task_pos, struct task_struct, cg_list); - get_task_struct(it->cur_task); + /* a task on dying_tasks with zero refcount is only valid for + * RCU readers, not even interesting for + * CSS_TASK_ITER_WITH_DEAD, find another one */ + it->cur_task = tryget_task_struct(it->cur_task); css_task_iter_advance(it); } From 2028280686f4fa78e2f1f6dede4b6c1fd782b9e3 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Thu, 3 Sep 2026 15:19:10 +0300 Subject: [PATCH 1674/2366] drm/msm/dsi: round the byte clock rate after reparenting to the PHY PLL DSI 6G v2.9 hosts (SM8650, SM8750, Kaanapali, etc.) reparent the byte and pixel RCGs to the DSI PHY PLL at runtime from dsi_link_clk_set_rate_6g_v2_9(), after the PHY has been enabled. However dsi_calc_clk_rate_6g() runs earlier, in order to compute the bit clock request for the PHY. At that point the byte RCG still has its reset parent (XO), so clk_round_rate() returns a bogus rate, which then ends up in the PHY bit clock request and the PLL gets programmed to a wrong frequency, breaking the panel. Move the rounding to dsi_link_clk_set_rate_6g(), which is called after the RCGs have been reparented to the PLL. Storing the rounded rate at this point still makes later link_clk_set_rate() calls no-ops in the CCF. Derive the byte interface clock rate from the rounded byte clock rate, otherwise it would keep requesting the idealized rate and retrigger the PLL on every transfer. Reported-by: Abel Vesa Reported-by: Krzysztof Kozlowski Fixes: 6cd33b6f4155 ("drm/msm/dsi: round 6G byte clock rate to the PLL-achievable value") Assisted-by: LLM Signed-off-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Tested-by: Konrad Dybcio # SM6115P J606F Tested-by: Abel Vesa Reviewed-by: Abel Vesa Patchwork: https://patchwork.freedesktop.org/patch/750496/ Link: https://lore.kernel.org/r/20260903-fix-eliza-dsi-v1-1-3474a6c9f2e0@oss.qualcomm.com --- drivers/gpu/drm/msm/dsi/dsi_host.c | 36 ++++++++++++++++-------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c index 7e4e3718b536..b292dfd266d1 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_host.c +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c @@ -129,7 +129,7 @@ struct msm_dsi_host { struct clk *dsi_pll_pixel_clk; unsigned long byte_clk_rate; - unsigned long byte_intf_clk_rate; + bool byte_intf_clk_div_2; unsigned long pixel_clk_rate; unsigned long esc_clk_rate; @@ -382,8 +382,20 @@ int msm_dsi_runtime_resume(struct device *dev) int dsi_link_clk_set_rate_6g(struct msm_dsi_host *msm_host) { + unsigned long byte_intf_clk_rate; + long rounded_byte_clk_rate; int ret; + rounded_byte_clk_rate = clk_round_rate(msm_host->byte_clk, + msm_host->byte_clk_rate); + if (rounded_byte_clk_rate < 0) { + pr_err("%s: failed to round byte clock rate, %ld\n", + __func__, rounded_byte_clk_rate); + return rounded_byte_clk_rate; + } + + msm_host->byte_clk_rate = rounded_byte_clk_rate; + DBG("Set clk rates: pclk=%lu, byteclk=%lu", msm_host->pixel_clk_rate, msm_host->byte_clk_rate); @@ -401,7 +413,11 @@ int dsi_link_clk_set_rate_6g(struct msm_dsi_host *msm_host) } if (msm_host->byte_intf_clk) { - ret = clk_set_rate(msm_host->byte_intf_clk, msm_host->byte_intf_clk_rate); + byte_intf_clk_rate = msm_host->byte_clk_rate; + if (msm_host->byte_intf_clk_div_2) + byte_intf_clk_rate /= 2; + + ret = clk_set_rate(msm_host->byte_intf_clk, byte_intf_clk_rate); if (ret) { pr_err("%s: Failed to set rate byte intf clk, %d\n", __func__, ret); @@ -669,24 +685,12 @@ static void dsi_calc_pclk(struct msm_dsi_host *msm_host, bool is_bonded_dsi) int dsi_calc_clk_rate_6g(struct msm_dsi_host *msm_host, bool is_bonded_dsi) { - long rounded_byte_clk_rate; - if (!msm_host->mode) { pr_err("%s: mode not set\n", __func__); return -EINVAL; } dsi_calc_pclk(msm_host, is_bonded_dsi); - - rounded_byte_clk_rate = clk_round_rate(msm_host->byte_clk, - msm_host->byte_clk_rate); - if (rounded_byte_clk_rate < 0) { - pr_err("%s: failed to round byte clock rate, %ld\n", - __func__, rounded_byte_clk_rate); - return rounded_byte_clk_rate; - } - - msm_host->byte_clk_rate = rounded_byte_clk_rate; msm_host->esc_clk_rate = clk_get_rate(msm_host->esc_clk); return 0; } @@ -2495,9 +2499,7 @@ int msm_dsi_host_power_on(struct mipi_dsi_host *host, goto unlock_ret; } - msm_host->byte_intf_clk_rate = msm_host->byte_clk_rate; - if (phy_shared_timings->byte_intf_clk_div_2) - msm_host->byte_intf_clk_rate /= 2; + msm_host->byte_intf_clk_div_2 = phy_shared_timings->byte_intf_clk_div_2; msm_dsi_sfpb_config(msm_host, true); From f4fae975db08a9aeec0b15e145c7d4d0fe02a0ec Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sun, 13 Sep 2026 16:58:14 +0800 Subject: [PATCH 1675/2366] drm/msm/hdmi_phy: fix runtime PM cleanup on probe failure msm_hdmi_phy_probe() enables runtime PM before enabling the PHY resources and initializing the PLL, but failures from either operation return without calling the matching pm_runtime_disable(). The remove path disables runtime PM, but it is not called when probe fails. As a result, runtime PM remains enabled after an unsuccessful probe. Route failures after pm_runtime_enable() through a common error path and disable runtime PM before returning. This issue was found by manual code inspection. Fixes: 15b4a4523859 ("drm/msm/hdmi: Create a separate HDMI PHY driver") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Krzysztof Kozlowski Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/753043/ Link: https://lore.kernel.org/r/20260913085814.1509352-1-lgs201920130244@gmail.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/hdmi/hdmi_phy.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_phy.c b/drivers/gpu/drm/msm/hdmi/hdmi_phy.c index eb1088755cb3..77dce35cd45e 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_phy.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_phy.c @@ -168,13 +168,13 @@ static int msm_hdmi_phy_probe(struct platform_device *pdev) ret = msm_hdmi_phy_resource_enable(phy); if (ret) - return ret; + goto err_pm_disable; ret = msm_hdmi_phy_pll_init(pdev, phy->cfg->type); if (ret) { DRM_DEV_ERROR(dev, "couldn't init PLL\n"); msm_hdmi_phy_resource_disable(phy); - return ret; + goto err_pm_disable; } msm_hdmi_phy_resource_disable(phy); @@ -182,6 +182,10 @@ static int msm_hdmi_phy_probe(struct platform_device *pdev) platform_set_drvdata(pdev, phy); return 0; + +err_pm_disable: + pm_runtime_disable(dev); + return ret; } static void msm_hdmi_phy_remove(struct platform_device *pdev) From a15fac810c76397ec9f62a6fc26c4d7ab6e238a7 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 13 Sep 2026 14:33:33 +0200 Subject: [PATCH 1676/2366] dt-bindings: display/msm: Use consistent indentation in the example Correct indentation in the examples to consistent 2- or 4-spaces indentation to fix dt-check-style warnings ("example 0 [indent-consistent] indent mismatch ..."). Preferred is 4-spaces, but re-indenting entire example just for that is too much churn. Signed-off-by: Krzysztof Kozlowski Patchwork: https://patchwork.freedesktop.org/patch/753054/ Link: https://lore.kernel.org/r/20260913123331.100293-4-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- .../display/msm/qcom,qcs8300-mdss.yaml | 6 +- .../display/msm/qcom,sar2130p-mdss.yaml | 4 +- .../bindings/display/msm/qcom,sm6150-dpu.yaml | 14 ++--- .../display/msm/qcom,sm8350-mdss.yaml | 2 +- .../display/msm/qcom,x1e80100-mdss.yaml | 60 +++++++++---------- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Documentation/devicetree/bindings/display/msm/qcom,qcs8300-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,qcs8300-mdss.yaml index c41a86203e78..287cce4dbddc 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,qcs8300-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,qcs8300-mdss.yaml @@ -150,7 +150,7 @@ examples: reg = <0>; dpu_intf0_out: endpoint { - remote-endpoint = <&mdss_dp0_in>; + remote-endpoint = <&mdss_dp0_in>; }; }; @@ -352,9 +352,9 @@ examples: }; port@1 { - reg = <1>; + reg = <1>; - mdss_dp_out: endpoint { }; + mdss_dp_out: endpoint { }; }; }; diff --git a/Documentation/devicetree/bindings/display/msm/qcom,sar2130p-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,sar2130p-mdss.yaml index 44c1bb9e4109..fb3d24bbac7d 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,sar2130p-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,sar2130p-mdss.yaml @@ -248,9 +248,9 @@ examples: remote-endpoint = <&usb_dp_qmpphy_dp_in>; }; }; - }; + }; - dp_opp_table: opp-table { + dp_opp_table: opp-table { compatible = "operating-points-v2"; opp-162000000 { diff --git a/Documentation/devicetree/bindings/display/msm/qcom,sm6150-dpu.yaml b/Documentation/devicetree/bindings/display/msm/qcom,sm6150-dpu.yaml index b4f437172218..8cbf99f9fcba 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,sm6150-dpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,sm6150-dpu.yaml @@ -81,7 +81,7 @@ examples: port@1 { reg = <1>; dpu_intf1_out: endpoint { - remote-endpoint = <&mdss_dsi0_in>; + remote-endpoint = <&mdss_dsi0_in>; }; }; }; @@ -90,18 +90,18 @@ examples: compatible = "operating-points-v2"; opp-19200000 { - opp-hz = /bits/ 64 <19200000>; - required-opps = <&rpmhpd_opp_low_svs>; + opp-hz = /bits/ 64 <19200000>; + required-opps = <&rpmhpd_opp_low_svs>; }; opp-25600000 { - opp-hz = /bits/ 64 <25600000>; - required-opps = <&rpmhpd_opp_svs>; + opp-hz = /bits/ 64 <25600000>; + required-opps = <&rpmhpd_opp_svs>; }; opp-307200000 { - opp-hz = /bits/ 64 <307200000>; - required-opps = <&rpmhpd_opp_nom>; + opp-hz = /bits/ 64 <307200000>; + required-opps = <&rpmhpd_opp_nom>; }; }; }; diff --git a/Documentation/devicetree/bindings/display/msm/qcom,sm8350-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,sm8350-mdss.yaml index 68176de854b3..f5acf528d0e4 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,sm8350-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,sm8350-mdss.yaml @@ -223,7 +223,7 @@ examples: phys = <&mdss_dsi0_phy>; ports { - #address-cells = <1>; + #address-cells = <1>; #size-cells = <0>; port@0 { diff --git a/Documentation/devicetree/bindings/display/msm/qcom,x1e80100-mdss.yaml b/Documentation/devicetree/bindings/display/msm/qcom,x1e80100-mdss.yaml index 8d698a2e055a..8c65a80b46e4 100644 --- a/Documentation/devicetree/bindings/display/msm/qcom,x1e80100-mdss.yaml +++ b/Documentation/devicetree/bindings/display/msm/qcom,x1e80100-mdss.yaml @@ -208,47 +208,47 @@ examples: #sound-dai-cells = <0>; ports { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - port@0 { - reg = <0>; + port@0 { + reg = <0>; - mdss_dp0_in: endpoint { - remote-endpoint = <&mdss_intf0_out>; - }; - }; + mdss_dp0_in: endpoint { + remote-endpoint = <&mdss_intf0_out>; + }; + }; - port@1 { - reg = <1>; + port@1 { + reg = <1>; - mdss_dp0_out: endpoint { - }; - }; + mdss_dp0_out: endpoint { + }; + }; }; mdss_dp0_opp_table: opp-table { - compatible = "operating-points-v2"; + compatible = "operating-points-v2"; - opp-160000000 { - opp-hz = /bits/ 64 <160000000>; - required-opps = <&rpmhpd_opp_low_svs>; - }; + opp-160000000 { + opp-hz = /bits/ 64 <160000000>; + required-opps = <&rpmhpd_opp_low_svs>; + }; - opp-270000000 { - opp-hz = /bits/ 64 <270000000>; - required-opps = <&rpmhpd_opp_svs>; - }; + opp-270000000 { + opp-hz = /bits/ 64 <270000000>; + required-opps = <&rpmhpd_opp_svs>; + }; - opp-540000000 { - opp-hz = /bits/ 64 <540000000>; - required-opps = <&rpmhpd_opp_svs_l1>; - }; + opp-540000000 { + opp-hz = /bits/ 64 <540000000>; + required-opps = <&rpmhpd_opp_svs_l1>; + }; - opp-810000000 { - opp-hz = /bits/ 64 <810000000>; - required-opps = <&rpmhpd_opp_nom>; - }; + opp-810000000 { + opp-hz = /bits/ 64 <810000000>; + required-opps = <&rpmhpd_opp_nom>; + }; }; }; }; From e1aeaf79dea51e6065da56924bc07e22d59012ac Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 13 Sep 2026 21:09:15 -0300 Subject: [PATCH 1677/2366] smb: client: fix unaligned access in WSL reparse point parser When wsl_to_fattr() parses WSL extended attributes, it computes a payload pointer from ea->ea_data + ea_name_length + 1. Since the smb2_file_full_ea_info struct is __packed and all WSL xattr names are 6 bytes long, the value pointer always lands at an odd byte offset, never satisfying __le32 or __le64 alignment requirements. The code then casts this pointer to __le32 * or __le64 * and dereferences it directly, which may cause alignment faults on some architectures. Replace all such casts with get_unaligned_le32() and get_unaligned_le64() in reparse_mkdev(), wsl_make_kuid(), wsl_make_kgid() and wsl_to_fattr(). Closes: https://sashiko.dev/#/patchset/20260906200517.725015-1-pc%40manguebit.org Fixes: 78e26bec4d6d ("smb: client: parse uid, gid, mode and dev from WSL reparse points") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: David Howells Cc: Tom Talpey Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 4 ++-- fs/smb/client/reparse.h | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 9e31fce7e0a5..6ac69f4d391a 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1201,9 +1201,9 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, fattr->cf_gid = wsl_make_kgid(cifs_sb, v); } else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) { /* File type in reparse point tag and in xattr mode must match. */ - if (S_DT(fattr->cf_mode) != S_DT(le32_to_cpu(*(__le32 *)v))) + if (S_DT(fattr->cf_mode) != S_DT(get_unaligned_le32(v))) return false; - fattr->cf_mode = (umode_t)le32_to_cpu(*(__le32 *)v); + fattr->cf_mode = (umode_t)get_unaligned_le32(v); } else if (!strncmp(name, SMB2_WSL_XATTR_DEV, nlen)) { fattr->cf_rdev = reparse_mkdev(v); have_xattr_dev = true; diff --git a/fs/smb/client/reparse.h b/fs/smb/client/reparse.h index 49efd85b1e94..05b2cecb4495 100644 --- a/fs/smb/client/reparse.h +++ b/fs/smb/client/reparse.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "fs_context.h" #include "cifsglob.h" #include "../common/smbfsctl.h" @@ -23,7 +24,7 @@ static inline dev_t reparse_mkdev(void *ptr) { - u64 v = le64_to_cpu(*(__le64 *)ptr); + u64 v = get_unaligned_le64(ptr); return MKDEV(v & 0xffffffff, v >> 32); } @@ -31,7 +32,7 @@ static inline dev_t reparse_mkdev(void *ptr) static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb, void *ptr) { - u32 uid = le32_to_cpu(*(__le32 *)ptr); + u32 uid = get_unaligned_le32(ptr); if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_OVERR_UID) return cifs_sb->ctx->linux_uid; @@ -41,7 +42,7 @@ static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb, static inline kgid_t wsl_make_kgid(struct cifs_sb_info *cifs_sb, void *ptr) { - u32 gid = le32_to_cpu(*(__le32 *)ptr); + u32 gid = get_unaligned_le32(ptr); if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_OVERR_GID) return cifs_sb->ctx->linux_gid; From e1253a82bb4c0fed6706a5839fc8b6e01be1abe2 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 13 Sep 2026 21:15:41 -0300 Subject: [PATCH 1678/2366] smb: client: fix fattr leaking on wsl_to_fattr() failure wsl_to_fattr() mutates fattr fields as it parses each WSL EA. If validation later fails, the function returns false with partially mutated fattr fields that callers do not reset. Fix this by parsing into local variables and only committing them to fattr on success. Closes: https://sashiko.dev/#/patchset/20260906200517.725015-1-pc%40manguebit.org Fixes: 78e26bec4d6d ("smb: client: parse uid, gid, mode and dev from WSL reparse points") Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: David Howells Cc: Tom Talpey Cc: Shyam Prasad N Cc: Ronnie Sahlberg Cc: Bharath SM Cc: Namjae Jeon Cc: stable@vger.kernel.org --- fs/smb/client/reparse.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 6ac69f4d391a..3a27773186ae 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -1149,29 +1149,30 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, u32 tag, struct cifs_fattr *fattr) { unsigned int sbflags = cifs_sb_flags(cifs_sb); + kuid_t uid = cifs_sb->ctx->linux_uid; + kgid_t gid = cifs_sb->ctx->linux_gid; struct smb2_file_full_ea_info *ea; bool have_xattr_dev = false; + dev_t rdev = 0; + umode_t mode; u32 next = 0; - fattr->cf_uid = cifs_sb->ctx->linux_uid; - fattr->cf_gid = cifs_sb->ctx->linux_gid; - - fattr->cf_mode &= ~S_IFMT; + mode = fattr->cf_mode & ~S_IFMT; switch (tag) { case IO_REPARSE_TAG_LX_SYMLINK: - fattr->cf_mode |= S_IFLNK; + mode |= S_IFLNK; break; case IO_REPARSE_TAG_LX_FIFO: - fattr->cf_mode |= S_IFIFO; + mode |= S_IFIFO; break; case IO_REPARSE_TAG_AF_UNIX: - fattr->cf_mode |= S_IFSOCK; + mode |= S_IFSOCK; break; case IO_REPARSE_TAG_LX_CHR: - fattr->cf_mode |= S_IFCHR; + mode |= S_IFCHR; break; case IO_REPARSE_TAG_LX_BLK: - fattr->cf_mode |= S_IFBLK; + mode |= S_IFBLK; break; } @@ -1195,26 +1196,29 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data, if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen)) { if (!(sbflags & CIFS_MOUNT_OVERR_UID)) - fattr->cf_uid = wsl_make_kuid(cifs_sb, v); + uid = wsl_make_kuid(cifs_sb, v); } else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen)) { if (!(sbflags & CIFS_MOUNT_OVERR_GID)) - fattr->cf_gid = wsl_make_kgid(cifs_sb, v); + gid = wsl_make_kgid(cifs_sb, v); } else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) { /* File type in reparse point tag and in xattr mode must match. */ - if (S_DT(fattr->cf_mode) != S_DT(get_unaligned_le32(v))) + if (S_DT(mode) != S_DT(get_unaligned_le32(v))) return false; - fattr->cf_mode = (umode_t)get_unaligned_le32(v); + mode = get_unaligned_le32(v); } else if (!strncmp(name, SMB2_WSL_XATTR_DEV, nlen)) { - fattr->cf_rdev = reparse_mkdev(v); + rdev = reparse_mkdev(v); have_xattr_dev = true; } } while (next); out: - /* Major and minor numbers for char and block devices are mandatory. */ if (!have_xattr_dev && (tag == IO_REPARSE_TAG_LX_CHR || tag == IO_REPARSE_TAG_LX_BLK)) return false; + fattr->cf_uid = uid; + fattr->cf_gid = gid; + fattr->cf_mode = mode; + fattr->cf_rdev = rdev; return true; } From f97d8c7bab7843631206a114986c9059da03efeb Mon Sep 17 00:00:00 2001 From: Aohan Mei Date: Fri, 11 Sep 2026 15:34:32 +0800 Subject: [PATCH 1679/2366] rds: ib: use rds_conn_drop() on protocol version mismatch rds_ib_cm_connect_complete() runs from the RDMA-CM event handler with conn->c_cm_lock held. When the peer negotiates a protocol version older than RDS_PROTOCOL_COMPAT_VERSION, the handler calls rds_conn_destroy(), which is only safe in the rmmod path: it synchronously tears the connection down and flush_work()es the shutdown work cp_down_w. That shutdown work (rds_conn_shutdown()) needs cp_cm_lock, which is the very lock the event handler still holds, so the flush never completes: the two workers wait on each other and the RDS connection workqueues stall for good. All other RDMA-CM failure paths (REJECTED, CONNECT_ERROR, DISCONNECTED) use rds_conn_drop(), which marks the connection RDS_CONN_ERROR and schedules the shutdown work asynchronously. Use it here as well. Fixes: f147dd9ecabf ("RDS/IB: Disallow connections less than RDS 3.1") Reported-by: TencentOS Corvus AI Cc: stable@vger.kernel.org Reviewed-by: Allison Henderson Signed-off-by: Aohan Mei Link: https://patch.msgid.link/20260911073436.3542080-1-ljp1205831794@gmail.com Signed-off-by: Jakub Kicinski --- net/rds/ib_cm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/rds/ib_cm.c b/net/rds/ib_cm.c index 4feb0edc360c..6e3110a04ae6 100644 --- a/net/rds/ib_cm.c +++ b/net/rds/ib_cm.c @@ -115,7 +115,7 @@ void rds_ib_cm_connect_complete(struct rds_connection *conn, struct rdma_cm_even &conn->c_laddr, &conn->c_faddr, RDS_PROTOCOL_MAJOR(conn->c_version), RDS_PROTOCOL_MINOR(conn->c_version)); - rds_conn_destroy(conn); + rds_conn_drop(conn); return; } } From 6fb0a9d9071f1ff0cc5cfc0782302d9c90d642cb Mon Sep 17 00:00:00 2001 From: Chunfeng Song Date: Thu, 10 Sep 2026 05:51:10 +0000 Subject: [PATCH 1680/2366] rust: net: phy: fix off-by-one bit positions in device status accessors The hand-written bitfield offsets in is_link_up(), is_autoneg_enabled() and is_autoneg_completed() were correct when the abstraction was merged: at that time autoneg, link, and autoneg_complete were at bits 13, 14, and 15 of struct phy_device's first bitfield unit. Commit 2796ff1e3dca ("net: phy: add flag is_genphy_driven to struct phy_device") later inserted is_genphy_driven just before autoneg, shifting the three fields up by one, so the accessors now read: is_link_up() reads bit 14 = autoneg is_autoneg_enabled() reads bit 13 = is_genphy_driven is_autoneg_completed() reads bit 15 = link The official ax88796b Rust driver uses all three accessors in its read_status() implementation, so it inherits the bug. phy_attach_direct() sets is_genphy_driven only when it falls back to the generic driver, and ax88796b has a real driver, so is_genphy_driven stays 0. The broken is_autoneg_enabled() therefore reads bit 13 as 0, compares it against AUTONEG_ENABLE (1), and always returns false, so read_status() never reaches the resolve_aneg_linkmode() call. The ordinary bindgen accessors take &self. Calling them through (*phydev).link() would create a shared reference to the complete bindings::phy_device, which is not appropriate for an object wrapped in Opaque. Use the bindgen-generated raw accessors (link_raw(), autoneg_raw(), and autoneg_complete_raw()) instead. They retain the bit positions and endianness handling generated from the C layout without creating a Rust reference to the complete phy_device. Drop the hand-written numbers together with the TODO comment that marked them as a stopgap. The raw accessors are only emitted by bindgen 0.71 and later, and were added at the Rust-for-Linux project's request, so this fix can only be backported to stable branches whose minimum bindgen version is at least that, hence the scope on the Cc: stable line below. Found by a static equivalence audit (C2RustDrv, a C-to-Rust driver migration tool) that compares hand-written bitfield offsets against the bindgen layout of struct phy_device. Verified by building the bindings and checking the generated accessors; no runtime testing was possible without PHY hardware. Fixes: 2796ff1e3dca ("net: phy: add flag is_genphy_driven to struct phy_device") Cc: stable@vger.kernel.org # Only 7.1.y and later (requires bindgen's raw pointer accessors). Link: https://github.com/rust-lang/rust-bindgen/issues/2674 Signed-off-by: Chunfeng Song Reviewed-by: FUJITA Tomonori Link: https://patch.msgid.link/20260910055110.167110-1-springbreeze@stu.pku.edu.cn Signed-off-by: Jakub Kicinski --- rust/kernel/net/phy.rs | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs index 956cda573ddb..c4e7b1d6c6f4 100644 --- a/rust/kernel/net/phy.rs +++ b/rust/kernel/net/phy.rs @@ -123,39 +123,37 @@ pub fn state(&self) -> DeviceState { /// Gets the current link state. /// /// It returns true if the link is up. + #[inline] pub fn is_link_up(&self) -> bool { - const LINK_IS_UP: u64 = 1; - // TODO: the code to access to the bit field will be replaced with automatically - // generated code by bindgen when it becomes possible. - // SAFETY: The struct invariant ensures that we may access - // this field without additional synchronization. - let bit_field = unsafe { &(*self.0.get())._bitfield_1 }; - bit_field.get(14, 1) == LINK_IS_UP + let phydev = self.0.get().cast_const(); + // SAFETY: By the type invariant of `Device`, `phydev` points to a valid + // `struct phy_device`, and there is no concurrent write to this field. + let link = unsafe { bindings::phy_device::link_raw(phydev) }; + link == 1 } /// Gets the current auto-negotiation configuration. /// /// It returns true if auto-negotiation is enabled. + #[inline] pub fn is_autoneg_enabled(&self) -> bool { - // TODO: the code to access to the bit field will be replaced with automatically - // generated code by bindgen when it becomes possible. - // SAFETY: The struct invariant ensures that we may access - // this field without additional synchronization. - let bit_field = unsafe { &(*self.0.get())._bitfield_1 }; - bit_field.get(13, 1) == u64::from(bindings::AUTONEG_ENABLE) + let phydev = self.0.get().cast_const(); + // SAFETY: By the type invariant of `Device`, `phydev` points to a valid + // `struct phy_device`, and there is no concurrent write to this field. + let autoneg = unsafe { bindings::phy_device::autoneg_raw(phydev) }; + autoneg == bindings::AUTONEG_ENABLE } /// Gets the current auto-negotiation state. /// /// It returns true if auto-negotiation is completed. + #[inline] pub fn is_autoneg_completed(&self) -> bool { - const AUTONEG_COMPLETED: u64 = 1; - // TODO: the code to access to the bit field will be replaced with automatically - // generated code by bindgen when it becomes possible. - // SAFETY: The struct invariant ensures that we may access - // this field without additional synchronization. - let bit_field = unsafe { &(*self.0.get())._bitfield_1 }; - bit_field.get(15, 1) == AUTONEG_COMPLETED + let phydev = self.0.get().cast_const(); + // SAFETY: By the type invariant of `Device`, `phydev` points to a valid + // `struct phy_device`, and there is no concurrent write to this field. + let completed = unsafe { bindings::phy_device::autoneg_complete_raw(phydev) }; + completed == 1 } /// Sets the speed of the PHY. From bde5212360bd44506edec073ebbd6d0c72f75820 Mon Sep 17 00:00:00 2001 From: Ahmed Naseef Date: Sat, 12 Sep 2026 17:43:06 +0400 Subject: [PATCH 1681/2366] net: phy: mediatek: do not report link and per-speed LED rules together mtk_phy_led_hw_ctrl_get() reports TRIGGER_NETDEV_LINK whenever any of the speed bits in on_set is on, and in addition reports every individual TRIGGER_NETDEV_LINK_* bit that is set. The netdev trigger refuses that combination: netdev_led_attr_store() rejects TRIGGER_NETDEV_LINK together with any per-speed rule, and it validates the whole resulting mode rather than just the bit being written. Once the hardware has any link bit programmed, every write to the trigger attributes of that LED therefore fails with -EINVAL and the LED can no longer be configured. The rules are also fed back into the hardware: the trigger stores what is read back, and a later write of device_name programs it again, expanding TRIGGER_NETDEV_LINK to every speed in on_set. An LED configured for a single speed is thereby silently widened to "on at any link speed". Both are easy to see on the EcoNet EN7528, whose four PHYs share one LED block. The first LED programs the block correctly, the second reads those rules back and rewrites them widened, and the remaining two then read the widened value, so an LED configured for "link_10 link_100" ends up lit on a 1000 Mbps link. on_set holds every speed the LED can indicate and is exactly what mtk_phy_led_hw_ctrl_set() programs for TRIGGER_NETDEV_LINK, so report the speed independent rule only when all of them are on, and the individual speeds otherwise. The mapping is then the inverse of the one used when programming the LED and round trips without changing the register. Fixes: c66937b0f8db ("net: phy: mediatek-ge-soc: support PHY LEDs") Cc: stable@vger.kernel.org Signed-off-by: Ahmed Naseef Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260912134306.3544329-1-naseefkm@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/mediatek/mtk-phy-lib.c | 27 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/drivers/net/phy/mediatek/mtk-phy-lib.c b/drivers/net/phy/mediatek/mtk-phy-lib.c index dfd0f4e439a2..608072fbfde9 100644 --- a/drivers/net/phy/mediatek/mtk-phy-lib.c +++ b/drivers/net/phy/mediatek/mtk-phy-lib.c @@ -156,20 +156,27 @@ int mtk_phy_led_hw_ctrl_get(struct phy_device *phydev, u8 index, if (!rules) return 0; - if (on & on_set) + /* TRIGGER_NETDEV_LINK must not be reported together with any of the + * per-speed rules, the netdev trigger rejects that combination. + * on_set holds every speed this LED can indicate and is what + * mtk_phy_led_hw_ctrl_set() programs for TRIGGER_NETDEV_LINK, so + * report the speed independent rule only when they are all on. + */ + if ((on & on_set) == on_set) { *rules |= BIT(TRIGGER_NETDEV_LINK); + } else { + if (on & MTK_PHY_LED_ON_LINK10) + *rules |= BIT(TRIGGER_NETDEV_LINK_10); - if (on & MTK_PHY_LED_ON_LINK10) - *rules |= BIT(TRIGGER_NETDEV_LINK_10); + if (on & MTK_PHY_LED_ON_LINK100) + *rules |= BIT(TRIGGER_NETDEV_LINK_100); - if (on & MTK_PHY_LED_ON_LINK100) - *rules |= BIT(TRIGGER_NETDEV_LINK_100); + if (on & MTK_PHY_LED_ON_LINK1000) + *rules |= BIT(TRIGGER_NETDEV_LINK_1000); - if (on & MTK_PHY_LED_ON_LINK1000) - *rules |= BIT(TRIGGER_NETDEV_LINK_1000); - - if (on & MTK_PHY_LED_ON_LINK2500) - *rules |= BIT(TRIGGER_NETDEV_LINK_2500); + if (on & MTK_PHY_LED_ON_LINK2500) + *rules |= BIT(TRIGGER_NETDEV_LINK_2500); + } if (on & MTK_PHY_LED_ON_FDX) *rules |= BIT(TRIGGER_NETDEV_FULL_DUPLEX); From 7616242a2b37883f7322aaa1d2bd6cd0fed28315 Mon Sep 17 00:00:00 2001 From: Andrea Mayer Date: Sun, 13 Sep 2026 21:44:21 +0200 Subject: [PATCH 1682/2366] seg6: set IPSKB_L3SLAVE from IP6SKB_L3SLAVE on IPIP decapsulation When an SRv6 packet arrives on an interface enslaved to a VRF, vrf_ip6_rcv() sets IP6SKB_L3SLAVE in IP6CB, but decap_and_validate() has never set IPSKB_L3SLAVE in IPCB. The bit stayed clear in the common case, and with CONFIG_IPV6_MIP6 the leftover frag_max_size of a reassembled outer packet could even set it, with no VRF involved. Commit 44930446dde4 ("ipv6: seg6: clear IPv4 control block on IPIP decapsulation") then made the unreliable bit reliably clear. The effect of the missing flag is visible with End.DX4 when a delivery to a local address of the node reaches the socket lookup. For example, a UDP socket bound to the enslaved ingress interface does not receive any of the decapsulated packets, while an unbound socket outside the VRF does. This contradicts Documentation/networking/vrf.rst: by default the scope of an unbound UDP or TCP socket is limited to the default VRF. Set IPSKB_L3SLAVE for IPv4 in decap_and_validate(), which already does the same for IPv6. The socket lookup then matches the decapsulated packet like any other packet received on that enslaved interface. Such a packet matches an unbound UDP or TCP socket only when udp_l3mdev_accept or tcp_l3mdev_accept is set. Fixes: 891ef8dd2a8d ("ipv6: sr: implement additional seg6local actions") Signed-off-by: Andrea Mayer Reviewed-by: David Ahern Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260913194421.31-1-andrea.mayer@uniroma2.it Signed-off-by: Jakub Kicinski --- net/ipv6/seg6_local.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c index 7b5212220185..d1070aec7b72 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -257,10 +257,13 @@ static bool decap_and_validate(struct sk_buff *skb, int proto) return false; if (proto == IPPROTO_IPIP) { + bool l3slave = ipv6_l3mdev_skb(IP6CB(skb)->flags); int iif = IP6CB(skb)->iif; memset(IPCB(skb), 0, sizeof(*IPCB(skb))); IPCB(skb)->iif = iif; + if (l3slave) + IPCB(skb)->flags |= IPSKB_L3SLAVE; } else if (proto == IPPROTO_IPV6) { bool l3slave = ipv6_l3mdev_skb(IP6CB(skb)->flags); int iif = IP6CB(skb)->iif; From 9e92ad4630f5dd1838ce6bbe6b1bd2c73d34de36 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Thu, 10 Sep 2026 14:13:15 +0100 Subject: [PATCH 1683/2366] net: dsa: mxl862xx: disable the stats poll on teardown mxl862xx_setup() arms the stats poll before mxl862xx_setup_mdio(), and nothing stops it until dsa_register_switch() has returned an error to mxl862xx_probe(). DSA frees the dsa_port list before it returns, so a poll that fires once .setup or a later step of dsa_tree_setup() has failed walks freed ports. On shutdown the user ports stay registered, and the WORK_STOPPED flag test in mxl862xx_get_stats64() is not atomic with the cancel in mxl862xx_shutdown(), so a re-arm that read the flag before it was set queues the poll after cancel_delayed_work_sync() has returned. Arm the poll once .setup has succeeded and stop it from a .teardown op, which DSA calls on unregister and after a failed registration, in both cases before it frees the ports. Use disable_delayed_work_sync() there and in shutdown(): it drains a running poll as the cancel did and turns every later attempt to queue the work into a no-op, so the re-arm cannot bring the poll back. remove() and the probe error path only set WORK_STOPPED, which crc_err_work tests before it walks the ports. Fixes: a21d33a5265f ("net: dsa: mxl862xx: implement .get_stats64") Signed-off-by: Daniel Golle Link: https://patch.msgid.link/1eb6f7fc1789b67e4b11e3f4d5ff080d0b6f7cbb.1789045590.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mxl862xx/mxl862xx.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c index cfa7e3e269a2..e05ad52cd297 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c @@ -685,10 +685,22 @@ static int mxl862xx_setup(struct dsa_switch *ds) if (ret) return ret; + ret = mxl862xx_setup_mdio(ds); + if (ret) + return ret; + schedule_delayed_work(&priv->stats_work, MXL862XX_STATS_POLL_INTERVAL); - return mxl862xx_setup_mdio(ds); + return 0; +} + +static void mxl862xx_teardown(struct dsa_switch *ds) +{ + struct mxl862xx_priv *priv = ds->priv; + + set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); + disable_delayed_work_sync(&priv->stats_work); } static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable) @@ -2047,9 +2059,7 @@ static void mxl862xx_get_stats64(struct dsa_switch *ds, int port, spin_unlock_bh(&priv->ports[port].stats_lock); - /* Trigger a fresh poll so the next read sees up-to-date counters. - * No-op if the work is already pending, running, or teardown started. - */ + /* Trigger a fresh poll so the next read sees up-to-date counters. */ if (!test_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags)) schedule_delayed_work(&priv->stats_work, 0); } @@ -2057,6 +2067,7 @@ static void mxl862xx_get_stats64(struct dsa_switch *ds, int port, static const struct dsa_switch_ops mxl862xx_switch_ops = { .get_tag_protocol = mxl862xx_get_tag_protocol, .setup = mxl862xx_setup, + .teardown = mxl862xx_teardown, .port_setup = mxl862xx_port_setup, .port_teardown = mxl862xx_port_teardown, .phylink_get_caps = mxl862xx_phylink_get_caps, @@ -2131,7 +2142,6 @@ static int mxl862xx_probe(struct mdio_device *mdiodev) err = dsa_register_switch(ds); if (err) { set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); - cancel_delayed_work_sync(&priv->stats_work); mxl862xx_host_shutdown(priv); for (i = 0; i < MXL862XX_MAX_PORTS; i++) cancel_work_sync(&priv->ports[i].host_flood_work); @@ -2152,7 +2162,6 @@ static void mxl862xx_remove(struct mdio_device *mdiodev) priv = ds->priv; set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); - cancel_delayed_work_sync(&priv->stats_work); dsa_unregister_switch(ds); @@ -2181,7 +2190,7 @@ static void mxl862xx_shutdown(struct mdio_device *mdiodev) dsa_switch_shutdown(ds); set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags); - cancel_delayed_work_sync(&priv->stats_work); + disable_delayed_work_sync(&priv->stats_work); mxl862xx_host_shutdown(priv); From 23ca4ddc4fce2c233a49e9fd34d4b5b02bd7324e Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Sun, 13 Sep 2026 21:00:52 +0200 Subject: [PATCH 1684/2366] net: bcmgenet: restore the hardware filters on open bcmgenet_hfb_init() runs INIT_LIST_HEAD() on priv->rxnfc_list, which drops every rule off the list, and bcmgenet_open() calls it on each ifup. Every rule the user configured is silently lost: # ethtool -N eth0 flow-type ether dst $MAC action 0 Added rule with ID 0 # ethtool -n eth0 | grep -c Filter: 1 # ip link set eth0 down && ip link set eth0 up # ethtool -n eth0 | grep -c Filter: 0 Initialise the lists once at probe and restore the rules on open, as bcmgenet_resume() already does. Fixes: 3e370952287c ("net: bcmgenet: add support for ethtool rxnfc flows") Signed-off-by: Nicolai Buchwitz Reviewed-by: Justin Chen Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260913190052.939955-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- .../net/ethernet/broadcom/genet/bcmgenet.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index a2305e6428d1..b916080f4ff1 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -749,8 +749,17 @@ static void bcmgenet_hfb_init(struct bcmgenet_priv *priv) INIT_LIST_HEAD(&priv->rxnfc_rules[i].list); priv->rxnfc_rules[i].state = BCMGENET_RXNFC_STATE_UNUSED; } +} + +static void bcmgenet_hfb_restore(struct bcmgenet_priv *priv) +{ + struct bcmgenet_rxnfc_rule *rule; bcmgenet_hfb_clear(priv); + + list_for_each_entry(rule, &priv->rxnfc_list, list) + if (rule->state != BCMGENET_RXNFC_STATE_UNUSED) + bcmgenet_hfb_create_rxnfc_filter(priv, rule); } static int bcmgenet_begin(struct net_device *dev) @@ -3376,8 +3385,8 @@ static int bcmgenet_open(struct net_device *dev) bcmgenet_set_hw_addr(priv, dev->dev_addr); - /* HFB init */ - bcmgenet_hfb_init(priv); + /* Restore the filters, the MAC was reset above */ + bcmgenet_hfb_restore(priv); /* Reinitialize TDMA and RDMA and SW housekeeping */ ret = bcmgenet_init_dma(priv, true); @@ -4075,6 +4084,7 @@ static int bcmgenet_probe(struct platform_device *pdev) /* Mii wait queue */ init_waitqueue_head(&priv->wq); + bcmgenet_hfb_init(priv); INIT_WORK(&priv->bcmgenet_irq_work, bcmgenet_irq_task); priv->clk_wol = devm_clk_get_optional(&priv->pdev->dev, "enet-wol"); @@ -4272,10 +4282,7 @@ static int bcmgenet_resume(struct device *d) bcmgenet_set_hw_addr(priv, dev->dev_addr); /* Restore hardware filters */ - bcmgenet_hfb_clear(priv); - list_for_each_entry(rule, &priv->rxnfc_list, list) - if (rule->state != BCMGENET_RXNFC_STATE_UNUSED) - bcmgenet_hfb_create_rxnfc_filter(priv, rule); + bcmgenet_hfb_restore(priv); /* Reinitialize TDMA and RDMA and SW housekeeping */ ret = bcmgenet_init_dma(priv, false); From 82431877d837a6c2593efd8e66ba28b35a220ca4 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 5 Sep 2026 23:36:30 +0000 Subject: [PATCH 1685/2366] sysctl: Check range in proc_dointvec_ms_jiffies_minmax Add the range check to do_proc_int_conv_ms_jiffies_minmax that commit d174174c6776 ("sysctl: replace SYSCTL_INT_CONV_CUSTOM macro with functions") incorrectly removed. Fixes: d174174c6776 ("sysctl: replace SYSCTL_INT_CONV_CUSTOM macro with functions") Signed-off-by: Kuniyuki Iwashima Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 213ae1d6a014..70926b73905a 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -181,7 +181,7 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, int *k_ptr, int dir, const struct ctl_table *tbl) { - return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, false, + return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, true, sysctl_u2k_int_conv_ms, sysctl_k2u_int_conv_ms); } From 318012c56576e09806747f64f89f8f3cde1f999f Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 5 Sep 2026 23:36:31 +0000 Subject: [PATCH 1686/2366] sysctl: Check range in do_proc_ulong_conv_ms_jiffies Add the range check back to do_proc_ulong_conv_ms_jiffies that commit b96b5c6708ea ("sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec") incorrectly removed. Append "_minmax" to the end of do_proc_ulong_conv_ms_jiffies so it is clear that there should be a range check. Fixes: b96b5c6708ea ("sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec") Signed-off-by: Kuniyuki Iwashima Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 70926b73905a..41f88ebac74a 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -195,10 +195,10 @@ static int sysctl_k2u_ulong_conv_ms(ulong *u_ptr, const ulong *k_ptr) return proc_ulong_k2u_conv_kop(u_ptr, k_ptr, sysctl_jiffies_to_msecs); } -static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, - int dir, const struct ctl_table *tbl) +static int do_proc_ulong_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *tbl) { - return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, false, + return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, true, sysctl_u2k_ulong_conv_ms, sysctl_k2u_ulong_conv_ms); } @@ -229,8 +229,8 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, return -ENOSYS; } -static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, - int dir, const struct ctl_table *tbl) +static int do_proc_ulong_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *tbl) { return -ENOSYS; } @@ -333,7 +333,7 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return proc_doulongvec_conv(table, dir, buffer, lenp, ppos, - do_proc_ulong_conv_ms_jiffies); + do_proc_ulong_conv_ms_jiffies_minmax); } EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax); From afdf35cfae0d039a4a6c907fa5d8391f1ef0a0aa Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 10 Sep 2026 12:22:16 +0200 Subject: [PATCH 1687/2366] sysctl: Fix type truncation in sysctl_msec_to_jiffies Return MAX_JIFFY_OFFSET for all the values truncated when val (u64) is passed to msecs_to_jiffies (u32). This aligns with how very large millisecond values get translated into MAX_JIFFY_OFFSET. Fixes: b96b5c6708ea ("sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec") Suggested-by: Kuniyuki Iwashima Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 41f88ebac74a..80c354811538 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -136,6 +136,8 @@ static int sysctl_k2u_int_conv_userhz(bool *negp, ulong *u_ptr, const int *k_ptr static ulong sysctl_msecs_to_jiffies(const ulong val) { + if (val > jiffies_to_msecs(MAX_JIFFY_OFFSET)) + return MAX_JIFFY_OFFSET; return msecs_to_jiffies(val); } From 3ed11c671ff7ec58c8fd96410233c677df23f407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Fri, 4 Sep 2026 10:26:13 +0200 Subject: [PATCH 1688/2366] dma-buf/dma-fence: fix checking signaling bit for timeline and driver name v3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The patch "dma-buf: dma-fence: Fix potential NULL pointer dereference" changed the check to test for the ops pointer instead of the signaled bit to avoid a potential NULL dereference when the ops pointer has been cleared. The problem is now that the ops pointer is cleared only when neither the release nor the wait callback is implemented and this isn't true for a lot of dma_fence implementations yet. So those implementations lost the RCU protection after signaling of the returned string resulting in potential use after free. Add the signaling check additional to the ops pointer check so that we have both the protection against NULL dereference as well as the RCU protection after signaling for the returned string. v2: improve comments to note RCU protection and explain why we check both signaling state and ops pointer v3: some comment improvements suggested by Philip Signed-off-by: Christian König Fixes: 035219a760ed ("dma-buf: dma-fence: Fix potential NULL pointer dereference") CC: stable@vger.kernel.org # 7.2+ Reported-by: Jonghyuk Kim(MalHyuk) Tested-by: Jonghyuk Kim(MalHyuk) Reviewed-by: Philipp Stanner Link: https://lore.kernel.org/r/20260914182740.1587-1-christian.koenig@amd.com --- drivers/dma-buf/dma-fence.c | 14 ++++++++++++-- include/linux/dma-fence.h | 6 ++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c index 05090fb0fd5a..bd58688b81a7 100644 --- a/drivers/dma-buf/dma-fence.c +++ b/drivers/dma-buf/dma-fence.c @@ -1170,7 +1170,12 @@ const char __rcu *dma_fence_driver_name(struct dma_fence *fence) /* RCU protection is required for safe access to returned string */ ops = rcu_dereference(fence->ops); - if (ops) + + /* + * Make load ordering irrelevant by checking both signaled state and ops + * pointer and ops pointer is only set to NULL on newer implementations. + */ + if (!dma_fence_test_signaled_flag(fence) && ops) return (const char __rcu *)ops->get_driver_name(fence); else return (const char __rcu *)"detached-driver"; @@ -1203,7 +1208,12 @@ const char __rcu *dma_fence_timeline_name(struct dma_fence *fence) /* RCU protection is required for safe access to returned string */ ops = rcu_dereference(fence->ops); - if (ops) + + /* + * Make load ordering irrelevant by checking both signaled state and ops + * pointer and ops pointer is only set to NULL on newer implementations. + */ + if (!dma_fence_test_signaled_flag(fence) && ops) return (const char __rcu *)ops->get_timeline_name(fence); else return (const char __rcu *)"signaled-timeline"; diff --git a/include/linux/dma-fence.h b/include/linux/dma-fence.h index 158cd609f103..ffa99b930843 100644 --- a/include/linux/dma-fence.h +++ b/include/linux/dma-fence.h @@ -141,6 +141,9 @@ struct dma_fence_ops { * compute the name at runtime, without having it to store permanently * for each fence, or build a cache of some sort. * + * The returned string is RCU protected and can be freed after the fence + * signaled and a RCU grace period passed. + * * This callback is mandatory. */ const char * (*get_driver_name)(struct dma_fence *fence); @@ -153,6 +156,9 @@ struct dma_fence_ops { * having it to store permanently for each fence, or build a cache of * some sort. * + * The returned string is RCU protected and can be freed after the fence + * signaled and a RCU grace period passed. + * * This callback is mandatory. */ const char * (*get_timeline_name)(struct dma_fence *fence); From 5b644229bd677d52c4efa5973c1fab842c57f896 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 22:59:59 -0700 Subject: [PATCH 1689/2366] xfs: guard against igrab failure in xrep_findparent_from_dcache LOLLM suggests that we need to handle igrab returning NULL here. I don't think it's possible for the inode to enter I_FREEING or I_WILL_FREE while we have an active reference to the corresponding dentry, but we can code defensively anyway. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/findparent.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/xfs/scrub/findparent.c b/fs/xfs/scrub/findparent.c index eab3ac2704be..d921fe5a9b0c 100644 --- a/fs/xfs/scrub/findparent.c +++ b/fs/xfs/scrub/findparent.c @@ -473,6 +473,9 @@ xrep_findparent_from_dcache( pip = igrab(d_inode(parent)); dput(parent); + if (!pip) + goto out_dput; + if (S_ISDIR(pip->i_mode)) { ret = pip->i_ino; trace_xrep_findparent_from_dcache(sc->ip, ret); From afbccf99f7f82117cba9ad4b0b006692030f49e8 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:00:16 -0700 Subject: [PATCH 1690/2366] xfs: don't assert when XFS_SCRUB_TYPE_HEALTHY scans return corruption XFS_SCRUB_TYPE_HEALTHY is a synthentic scrub type so that xfs_scrub can tell the kernel "Hey, I finished a scan and saw no problems" and have the kernel forget that it saw indirect evidence of corruption. Unfortunately, as LOLLM points out, it's possible for the health system to record a new corruption just before xfs_scrub gets to XFS_SCRUB_TYPE_HEALTHY. In this case, the existing logic doesn't return early and instead wanders into unknown regions of type_to_health_flag and trips the assert because HEALTHY doesn't have a group assignment. Fix the logic so that we always return early for a HEALTHY scrub type, even if we decide not to call xchk_mark_all_healthy. Cc: stable@vger.kernel.org # v6.9 Fixes: a1f3e0cca41036 ("xfs: update health status if we get a clean bill of health") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/health.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/xfs/scrub/health.c b/fs/xfs/scrub/health.c index 2171bcf0f6c1..487ecc5f9f3c 100644 --- a/fs/xfs/scrub/health.c +++ b/fs/xfs/scrub/health.c @@ -202,9 +202,9 @@ xchk_update_health( * there's no sick flag defined for it, so we branch here ahead of the * mask check. */ - if (sc->sm->sm_type == XFS_SCRUB_TYPE_HEALTHY && - !(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) { - xchk_mark_all_healthy(sc->mp); + if (sc->sm->sm_type == XFS_SCRUB_TYPE_HEALTHY) { + if (!(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) + xchk_mark_all_healthy(sc->mp); return; } From bb991b7f79dd34cc5f24db0f736bf75630c970e7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:00:31 -0700 Subject: [PATCH 1691/2366] xfs: fix attr fork block count checks in xrep_inode_blockcounts LOLLM points out that a file has an attr fork, it will call xchk_inode_count_blocks to set @ablocks to the number of fsblocks mapped by the attr fork; but then it'll compare @blocks (aka the count of fsblocks mapped by the data fork). We already checked that and we never do anything with @acount, so I think this is clearly a bug. Fix the comparison. Cc: stable@vger.kernel.org # v6.8 Fixes: 2d295fe65776d1 ("xfs: repair inode records") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index 8bc508336aa5..b87c22146233 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1702,7 +1702,7 @@ xrep_inode_blockcounts( &acount); if (error) return error; - if (count >= sc->mp->m_sb.sb_dblocks) + if (acount >= sc->mp->m_sb.sb_dblocks) return -EFSCORRUPTED; error = xrep_ino_ensure_extent_count(sc, XFS_ATTR_FORK, nextents); From 1c32cdc986467eaffeedb6c5334852809555b82d Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:00:47 -0700 Subject: [PATCH 1692/2366] xfs: release orphanage dir inode if chown fails LOLLM points out that we leak the igrab'd reference to the orphanage directory inode if chowning it fails. Fix that. Cc: stable@vger.kernel.org # v6.10 Fixes: 1e58a8ccf2597c ("xfs: move orphan files to the orphanage") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/orphanage.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/orphanage.c b/fs/xfs/scrub/orphanage.c index 3aca66869b80..21e31eeaa042 100644 --- a/fs/xfs/scrub/orphanage.c +++ b/fs/xfs/scrub/orphanage.c @@ -192,12 +192,16 @@ xrep_orphanage_create( /* Make sure the orphanage is owned by root. */ error = xrep_chown_orphanage(sc, XFS_I(orphanage_inode)); if (error) - goto out_dput_orphanage; + goto out_rele_orphanage; /* Stash the reference for later and bail out. */ sc->orphanage = XFS_I(orphanage_inode); sc->orphanage_ilock_flags = 0; + orphanage_inode = NULL; +out_rele_orphanage: + if (orphanage_inode) + xchk_irele(sc, XFS_I(orphanage_inode)); out_dput_orphanage: end_creating(orphanage_dentry); out_dput_root: From 8b4ad2814274d43ed8bdfcf857efb8c79815d9c6 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:01:03 -0700 Subject: [PATCH 1693/2366] xfs: release AGFL after walking it during rmapbt repair LOLLM suggests that we not hold the AGFL locked to the scrub transaction for any longer than we have to when we're rebuilding the rmapbt. Since we only took it to generate an rmap record for the AGFL blocks, I think we can safely release it. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rmap_repair.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/scrub/rmap_repair.c b/fs/xfs/scrub/rmap_repair.c index 590f9f41856e..725035bf4903 100644 --- a/fs/xfs/scrub/rmap_repair.c +++ b/fs/xfs/scrub/rmap_repair.c @@ -1109,6 +1109,7 @@ xrep_rmap_try_reserve( return error; error = xfs_agfl_walk(sc->mp, agf, agfl_bp, xrep_rmap_walk_agfl, &ra); + xfs_trans_brelse(sc->tp, agfl_bp); if (error) return error; From 984aab2d905a8557fafb27cd9e8713d6d12b3437 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 9 Sep 2026 23:01:18 -0700 Subject: [PATCH 1694/2366] xfs: use correct jiffies comparison function in xchk_maybe_relax LOLLM points out that we're supposed to use time_after_eq, not a raw >= operation here, or else jiffies wraps can go unnoticed. Fix this. Cc: stable@vger.kernel.org # v6.10 Fixes: 271557de7cbfde ("xfs: reduce the rate of cond_resched calls inside scrub") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/scrub.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/scrub.h b/fs/xfs/scrub/scrub.h index 737a5d6db15f..b093945f3631 100644 --- a/fs/xfs/scrub/scrub.h +++ b/fs/xfs/scrub/scrub.h @@ -40,7 +40,7 @@ static inline int xchk_maybe_relax(struct xchk_relax *widget) return 0; widget->resched_nr = 0; - if (unlikely(widget->next_resched <= jiffies)) { + if (unlikely(time_after_eq(jiffies, widget->next_resched))) { cond_resched(); widget->next_resched = XCHK_RELAX_NEXT; } From 3083ba8dde765a9ab2337f3db68d00724a6b1202 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:53:39 -0700 Subject: [PATCH 1695/2366] xfs: check padding field in xfs_ioc_commit_range LOLLM points out that we don't check the ioctl padding field here, so let's do that. I don't think there are many users yet since exchrange requires a new feature flag, so it's a good time to try to plug this hole. Cc: stable@vger.kernel.org # v6.12 Fixes: 398597c3ef7fb1 ("xfs: introduce new file range commit ioctls") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_exchrange.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index c69ecd6a19de..07090487c581 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -902,7 +902,7 @@ xfs_ioc_commit_range( if (copy_from_user(&args, argp, sizeof(args))) return -EFAULT; - if (args.flags & ~XFS_EXCHANGE_RANGE_ALL_FLAGS) + if (args.pad || (args.flags & ~XFS_EXCHANGE_RANGE_ALL_FLAGS)) return -EINVAL; if (kern_f->magic != XCR_FRESH_MAGIC) return -EBUSY; From 8fc18580ec17f90beac4c933fbe4c74dcd3b7f36 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:53:55 -0700 Subject: [PATCH 1696/2366] xfs: don't call xfs_exchange_range_finish for a dry run LOLLM noticed that we strip file privileges and whatnot even for a dry run. We also shouldn't flush dirty data to disk or trim COW staging events for a dry run. Neither of those behaviors are allowed by the manpage, so fix that by exiting early on DRY_RUN in various functions. Cc: stable@vger.kernel.org # v6.10 Fixes: 42672471f938cd ("xfs: bind together the front and back ends of the file range exchange code") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_exchrange.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index 07090487c581..fafb4e3f065c 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -633,6 +633,9 @@ xfs_exchrange_prep( if (error) return error; + if (fxr->flags & XFS_EXCHANGE_RANGE_DRY_RUN) + return 0; + trace_xfs_exchrange_flush(fxr, ip1, ip2); /* Flush the relevant ranges of both files. */ @@ -709,9 +712,11 @@ xfs_exchrange_contents( * other file write would do. This may involve turning on support for * logged xattrs if either file has security capabilities. */ - error = xfs_exchange_range_finish(fxr); - if (error) - goto out_unlock; + if (!(fxr->flags & XFS_EXCHANGE_RANGE_DRY_RUN)) { + error = xfs_exchange_range_finish(fxr); + if (error) + goto out_unlock; + } out_unlock: xfs_iunlock2_io_mmap(ip1, ip2); From 471e0b6e2ddac9e16b8dc2153d6e5575fefb8a2f Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:54:11 -0700 Subject: [PATCH 1697/2366] xfs: use the correct reservations for rtrmap/refcount recovery LOLLM noticed that we might reserve the wrong number of blocks for recovering rtrmap and rtrefcount updates after a crash. Fix that. Cc: stable@vger.kernel.org # v6.14 Fixes: 5e0679d1c62f25 ("xfs: support recovering rmap intent items targetting realtime extents") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_refcount_item.c | 8 ++++++-- fs/xfs/xfs_rmap_item.c | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_refcount_item.c b/fs/xfs/xfs_refcount_item.c index 8bccf89a7766..682c6e1b45e3 100644 --- a/fs/xfs/xfs_refcount_item.c +++ b/fs/xfs/xfs_refcount_item.c @@ -508,6 +508,7 @@ xfs_refcount_recover_work( struct xfs_cui_log_item *cuip = CUI_ITEM(lip); struct xfs_trans *tp; struct xfs_mount *mp = lip->li_log->l_mp; + unsigned int dblocks; bool isrt = xfs_cui_item_isrt(lip); int i; int error = 0; @@ -543,8 +544,11 @@ xfs_refcount_recover_work( * full btree split on either end of the refcount range. */ resv = xlog_recover_resv(&M_RES(mp)->tr_itruncate); - error = xfs_trans_alloc(mp, &resv, mp->m_refc_maxlevels * 2, 0, - XFS_TRANS_RESERVE, &tp); + if (isrt) + dblocks = mp->m_rtrefc_maxlevels * 2; + else + dblocks = mp->m_refc_maxlevels * 2; + error = xfs_trans_alloc(mp, &resv, dblocks, 0, XFS_TRANS_RESERVE, &tp); if (error) return error; diff --git a/fs/xfs/xfs_rmap_item.c b/fs/xfs/xfs_rmap_item.c index 2a3a73a8566d..000cff1ce324 100644 --- a/fs/xfs/xfs_rmap_item.c +++ b/fs/xfs/xfs_rmap_item.c @@ -573,6 +573,7 @@ xfs_rmap_recover_work( struct xfs_rui_log_item *ruip = RUI_ITEM(lip); struct xfs_trans *tp; struct xfs_mount *mp = lip->li_log->l_mp; + unsigned int dblocks; bool isrt = xfs_rui_item_isrt(lip); int i; int error = 0; @@ -596,8 +597,11 @@ xfs_rmap_recover_work( } resv = xlog_recover_resv(&M_RES(mp)->tr_itruncate); - error = xfs_trans_alloc(mp, &resv, mp->m_rmap_maxlevels, 0, - XFS_TRANS_RESERVE, &tp); + if (isrt) + dblocks = mp->m_rtrmap_maxlevels; + else + dblocks = mp->m_rmap_maxlevels; + error = xfs_trans_alloc(mp, &resv, dblocks, 0, XFS_TRANS_RESERVE, &tp); if (error) return error; From 46c1b6674a7b3a8385e7fa27f4efc6f45eddf967 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:54:26 -0700 Subject: [PATCH 1698/2366] xfs: fix integer overflows in xbitmap set functions LOLLM complains that the xbitmap set functions can suffer an integer underflow or overflow and thereby return the wrong left and right pointers. Fix that logic bomb, even though (AFAICT) we never actually try to set the *entire* bitmap. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bitmap.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/fs/xfs/scrub/bitmap.c b/fs/xfs/scrub/bitmap.c index c7fa908d92b2..08f216d26ec6 100644 --- a/fs/xfs/scrub/bitmap.c +++ b/fs/xfs/scrub/bitmap.c @@ -122,8 +122,8 @@ xbitmap64_set( uint64_t start, uint64_t len) { - struct xbitmap64_node *left; - struct xbitmap64_node *right; + struct xbitmap64_node *left = NULL; + struct xbitmap64_node *right = NULL; uint64_t last = start + len - 1; int error; @@ -131,6 +131,7 @@ xbitmap64_set( left = xbitmap64_tree_iter_first(&bitmap->xb_root, start, last); if (left && left->bn_start <= start && left->bn_last >= last) return 0; + left = NULL; /* Clear out everything in the range we want to set. */ error = xbitmap64_clear(bitmap, start, len); @@ -138,11 +139,15 @@ xbitmap64_set( return error; /* Do we have a left-adjacent extent? */ - left = xbitmap64_tree_iter_first(&bitmap->xb_root, start - 1, start - 1); + if (start > 0) + left = xbitmap64_tree_iter_first(&bitmap->xb_root, start - 1, + start - 1); ASSERT(!left || left->bn_last + 1 == start); /* Do we have a right-adjacent extent? */ - right = xbitmap64_tree_iter_first(&bitmap->xb_root, last + 1, last + 1); + if (last < U64_MAX) + right = xbitmap64_tree_iter_first(&bitmap->xb_root, last + 1, + last + 1); ASSERT(!right || right->bn_start == last + 1); if (left && right) { @@ -397,8 +402,8 @@ xbitmap32_set( uint32_t start, uint32_t len) { - struct xbitmap32_node *left; - struct xbitmap32_node *right; + struct xbitmap32_node *left = NULL; + struct xbitmap32_node *right = NULL; uint32_t last = start + len - 1; int error; @@ -406,6 +411,7 @@ xbitmap32_set( left = xbitmap32_tree_iter_first(&bitmap->xb_root, start, last); if (left && left->bn_start <= start && left->bn_last >= last) return 0; + left = NULL; /* Clear out everything in the range we want to set. */ error = xbitmap32_clear(bitmap, start, len); @@ -413,11 +419,15 @@ xbitmap32_set( return error; /* Do we have a left-adjacent extent? */ - left = xbitmap32_tree_iter_first(&bitmap->xb_root, start - 1, start - 1); + if (start > 0) + left = xbitmap32_tree_iter_first(&bitmap->xb_root, start - 1, + start - 1); ASSERT(!left || left->bn_last + 1 == start); /* Do we have a right-adjacent extent? */ - right = xbitmap32_tree_iter_first(&bitmap->xb_root, last + 1, last + 1); + if (last < U32_MAX) + right = xbitmap32_tree_iter_first(&bitmap->xb_root, last + 1, + last + 1); ASSERT(!right || right->bn_start == last + 1); if (left && right) { From c54110d814c3e8ed6bbbb5e02874994ed9f668ac Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:54:42 -0700 Subject: [PATCH 1699/2366] xfs: only flag zero padding for dir3 data blocks, not dir3 block blocks LOLLM complains that xchk_directory_data_bestfree can be passed a directory block that is either in "block" or "data" format, but the check here unconditionally treats the dir3_block and dir3_data blocks as if they have the same header format (they don't). Consequently, we can incorrectly set the preen state on dir3_block blocks, which of course we can't preen away because dir3_block blocks do not have a padding field. Fix this. Cc: stable@vger.kernel.org # v7.1-rc4 Fixes: 939919ccddfcc3 ("xfs: check directory data block header padding in scrub") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/dir.c b/fs/xfs/scrub/dir.c index 2a037aae904d..19d974c7e2b7 100644 --- a/fs/xfs/scrub/dir.c +++ b/fs/xfs/scrub/dir.c @@ -492,7 +492,7 @@ xchk_directory_data_bestfree( goto out; xchk_buffer_recheck(sc, bp); - if (xfs_has_crc(sc->mp)) { + if (!is_block && xfs_has_crc(sc->mp)) { struct xfs_dir3_data_hdr *hdr3 = bp->b_addr; if (hdr3->pad) From e9193f2f1ce32d02b9230094ffdbfab715ab6137 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 10 Sep 2026 22:54:58 -0700 Subject: [PATCH 1700/2366] xfs: check di_forkoff correctly in scrub The di_forkoff check in xchk_dinode is incorrect, according to LOLLM. XFS_DFORK_BOFF returns a byte count relative to the start of the literal area, not the start of the inode. Therefore, this check won't flag di_forkoff values that are larger than the literal area but not the inode size itself. Fix this check; sadly the old APTR code was correct. Cc: stable@vger.kernel.org # v6.8 Fixes: 6b5d917780219d ("xfs: dont cast to char * for XFS_DFORK_*PTR macros") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c index 65b13e311916..46e9bf4a4317 100644 --- a/fs/xfs/scrub/inode.c +++ b/fs/xfs/scrub/inode.c @@ -607,7 +607,7 @@ xchk_dinode( } /* di_forkoff */ - if (XFS_DFORK_BOFF(dip) >= mp->m_sb.sb_inodesize) + if (dip->di_forkoff >= (XFS_LITINO(mp) >> 3)) xchk_ino_set_corrupt(sc, ino); if (naextents != 0 && dip->di_forkoff == 0) xchk_ino_set_corrupt(sc, ino); From 14e379600d3e57ab0872b049c28cfe5ef519d439 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 14 Sep 2026 13:35:38 +0200 Subject: [PATCH 1701/2366] xfs: don't try to get a reference to a NULL oz in xfs_get_cached_zone oz can be NULL when we resample it after taking i_flags_lock, so account for that. Fixes: 2d829cc76777 ("xfs: fix racy open zone caching") Signed-off-by: Christoph Hellwig Reviewed-by: Hans Holmberg Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_alloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index 28c1e48909fa..d7ec055a9a06 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -820,7 +820,7 @@ xfs_get_cached_zone( spin_unlock(&ip->i_flags_lock); } - if (!atomic_inc_not_zero(&oz->oz_ref)) + if (oz && !atomic_inc_not_zero(&oz->oz_ref)) oz = NULL; out_unlock: rcu_read_unlock(); From ee415ce8cba154d07d02a6d2fbb27ff518264c3a Mon Sep 17 00:00:00 2001 From: Luca Coelho Date: Tue, 8 Sep 2026 13:06:51 +0300 Subject: [PATCH 1702/2366] drm/i915/display: check configuration index before shifting The calc_allowed_config_filter() function passes the return value of iter_pos_to_idx() directly to BIT(), but the helper can return -1 for an invalid iterator. The iterator already rejects negative indices before doing a configuration, so this should not matter in normal flows. In any case, for robustness, check the index explicitly and warn if it is negative, avoiding an undefined shift. Fixes: 39e30bdf2f92 ("drm/i915/dp_link_caps: Add link configuration iterator") Reviewed-by: Imre Deak Link: https://patch.msgid.link/20260908100659.113555-1-luciano.coelho@intel.com Signed-off-by: Luca Coelho (cherry picked from commit fe05cb9b9fb0ecc10409c4c6133257214b6cd8c8) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_dp_link_caps.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 7b6cc6055da8..98657aa4d3d5 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -426,12 +426,15 @@ calc_allowed_config_filter(struct intel_dp_link_caps *link_caps, const struct intel_dp_link_config *forced_params) { struct intel_dp_link_caps_filter allowed_configs = INTEL_DP_LINK_CAPS_FILTER_NONE; + struct intel_display *display = to_intel_display(link_caps->dp); struct intel_dp_link_caps_order order = bw_desc_config_order(); struct intel_dp_link_caps_iter iter; struct intel_dp_link_config config; iter_start(&iter, link_caps, order, enabled_configs); for_each_dp_link_config(&iter, &config) { + int config_idx; + if (forced_params->rate && forced_params->rate != config.rate) continue; @@ -446,7 +449,11 @@ calc_allowed_config_filter(struct intel_dp_link_caps *link_caps, if (config.lane_count > max_limits->lane_count) continue; - allowed_configs.config_mask |= BIT(iter_pos_to_idx(link_caps, order, iter.pos)); + config_idx = iter_pos_to_idx(link_caps, order, iter.pos); + if (drm_WARN_ON(display->drm, config_idx < 0)) + continue; + + allowed_configs.config_mask |= BIT(config_idx); } intel_dp_link_caps_iter_end(&iter); From ce2b91bebc7bc0495fe3ad5ee47e4977fbb77fc5 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Mon, 14 Sep 2026 16:21:11 +0800 Subject: [PATCH 1703/2366] xfs: remove duplicate INO1_WRITTEN check Commit a23eca88448e ("xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN") duplicated commit b2d5a81dae38 ("xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN"), so xmi_can_exchange_reflink_flags() ended up with two identical XFS_EXCHMAPS_INO1_WRITTEN checks. The second one is dead code, since the first one already returned false. Remove it. Signed-off-by: Jiangshan Yi Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_exchmaps.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 49eda8d0994d..3efed37cb98a 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -959,16 +959,6 @@ xmi_can_exchange_reflink_flags( { struct xfs_mount *mp = req->ip1->i_mount; - /* - * The INO1_WRITTEN optimization can skip exchanging hole and - * unwritten mappings, which means we cannot guarantee that all - * shared extents actually moved to the other file. Clearing the - * reflink flag of an inode that still holds shared extents breaks - * the CoW write path, so refuse to exchange the flags in that case. - */ - if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN) - return false; - /* * The INO1_WRITTEN optimization can skip exchanging hole and * unwritten mappings, which means we cannot guarantee that all From c16b885ad6b589b233534ee99ade82110d2bc381 Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Fri, 4 Sep 2026 18:06:41 +0530 Subject: [PATCH 1704/2366] xfs: remove unused xfs_reflink_remap_range declaration The implementation was inlined into xfs_file_remap_range() by commit 3fc9f5e40931 ("xfs: remove xfs_reflink_remap_range"), leaving this declaration orphaned. Signed-off-by: Anuj Gupta Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_reflink.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/fs/xfs/xfs_reflink.h b/fs/xfs/xfs_reflink.h index 9d1ed9bb0bee..683c1841e640 100644 --- a/fs/xfs/xfs_reflink.h +++ b/fs/xfs/xfs_reflink.h @@ -48,9 +48,6 @@ extern int xfs_reflink_end_cow(struct xfs_inode *ip, xfs_off_t offset, int xfs_reflink_end_atomic_cow(struct xfs_inode *ip, xfs_off_t offset, xfs_off_t count); extern int xfs_reflink_recover_cow(struct xfs_mount *mp); -extern loff_t xfs_reflink_remap_range(struct file *file_in, loff_t pos_in, - struct file *file_out, loff_t pos_out, loff_t len, - unsigned int remap_flags); extern int xfs_reflink_inode_has_shared_extents(struct xfs_trans *tp, struct xfs_inode *ip, bool *has_shared); extern int xfs_reflink_clear_inode_flag(struct xfs_inode *ip, From 2998147b59c9df0a51477c7a6b3d1f0ba3127dd4 Mon Sep 17 00:00:00 2001 From: Dong Chenchen Date: Thu, 10 Sep 2026 22:00:42 +0800 Subject: [PATCH 1705/2366] ipv4: icmp: reject RTN_UNREACHABLE input routes in icmp_route_lookup When the forward output route cannot be used in icmp_route_lookup(), it enters the "reverse path" and calls ip_route_input() on fl4_dec.daddr, the original packet's source address. ip_route_input() only returns an error for truly invalid packets. For unreachable addresses it will succeed and return an input route whose dst.output is set to ip_rt_bug(). The existing check only rejects RTN_LOCAL routes, so the RTN_UNREACHABLE route types can still be returned and later used for output, syzkaller triggering a WARN_ON_ONCE() in ip_rt_bug() as bellow: ------------[ cut here ]------------ WARNING: net/ipv4/route.c:1273 at ip_rt_bug+0x14/0x20 RIP: 0010:ip_rt_bug+0x14/0x20 Call Trace: ip_push_pending_frames+0xfa/0x100 __icmp_send+0x905/0xf10 ip_options_compile+0xc0/0xd0 ip_rcv_finish_core+0x321/0xae0 ip_rcv+0x1de/0x260 __netif_receive_skb_one_core+0x11a/0x130 netif_receive_skb+0x7b/0x260 tun_get_user+0x11bf/0x1c10 ------------[ cut here ]------------ Reject input route that is RTN_UNREACHABLE to fix it. The net warning is only printed for RTN_LOCAL, as RTN_UNREACHABLE is not the result of a race condition. Fixes: 8b7817f3a959 ("[IPSEC]: Add ICMP host relookup support") Suggested-by: Ido Schimmel Reviewed-by: Jiayuan Chen Reviewed-by: Ido Schimmel Signed-off-by: Dong Chenchen Link: https://patch.msgid.link/20260910140042.1880242-1-dongchenchen2@huawei.com Signed-off-by: Paolo Abeni --- net/ipv4/icmp.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c index 0caedfc7ca92..90c0e22c29be 100644 --- a/net/ipv4/icmp.c +++ b/net/ipv4/icmp.c @@ -581,16 +581,19 @@ static struct rtable *icmp_route_lookup(struct net *net, struct flowi4 *fl4, skb_dstref_restore(skb_in, orefdst); /* - * At this point, fl4_dec.daddr should NOT be local (we - * checked fl4_dec.saddr above). However, a race condition - * may occur if the address is added to the interface - * concurrently. In that case, ip_route_input() returns a - * LOCAL route with dst.output=ip_rt_bug, which must not - * be used for output. + * fl4_dec.daddr is not expected to be local here, but it can be + * added to an interface concurrently, in which case + * ip_route_input() returns a LOCAL route. It can also fail to + * build a forwarding route towards fl4_dec.daddr, for example, + * when forwarding is disabled, and return an UNREACHABLE route. + * Both cases will result in a route with dst.output=ip_rt_bug, + * which must not be used for output. */ - if (!err && rt2 && rt2->rt_type == RTN_LOCAL) { + if (!err && rt2 && rt2->rt_type == RTN_LOCAL) net_warn_ratelimited("detected local route for %pI4 during ICMP sending, src %pI4\n", &fl4_dec.daddr, &fl4_dec.saddr); + if (!err && rt2 && + (rt2->rt_type == RTN_LOCAL || rt2->rt_type == RTN_UNREACHABLE)) { dst_release(&rt2->dst); err = -EINVAL; } From 7c8810c2e69c3d9ca6df870b40ae9218e50b4fb1 Mon Sep 17 00:00:00 2001 From: Hohyun Sim Date: Thu, 10 Sep 2026 15:37:43 +0900 Subject: [PATCH 1706/2366] net: fddi: skfp: fix NULL deref when setting the MAC address while down skfp_ctl_set_mac_address() calls ResetAdapter() unconditionally, without checking netif_running(). ResetAdapter() first calls card_stop(), which sets smc->hw.hw_state to STOPPED, and then mac_drv_clear_tx_queue(), which walks the two transmit queues: for (i = QUEUE_S; i <= QUEUE_A0; i++) { queue = smc->hw.fp.tx[i] ; ... t = queue->tx_curr_get ; smc->hw.fp.tx[] is only populated by init_tx(), which is reached from skfp_open() through init_smt() -> init_fddi_driver() -> init_fplus() -> init_mac() -> init_tx(). The private area is allocated and zeroed by alloc_fddidev(), so on an interface that has never been brought up both queue pointers are still NULL. The hw_state test at the top of mac_drv_clear_tx_queue() does not catch this, because card_stop() has just set STOPPED; the function proceeds into the loop and dereferences NULL. ResetAdapter() does call init_smt() itself, but only after the queues have been cleared. Setting the MAC address on a down interface therefore oopses: ip link set dev fddi0 address 02:00:00:00:00:01 BUG: KASAN: null-ptr-deref in mac_drv_clear_tx_queue+0x68/0x2c0 [skfp] Read of size 8 at addr 0000000000000010 by task ip/302 Call Trace: mac_drv_clear_tx_queue+0x68/0x2c0 [skfp 6c01d4bab63c36978bd0a7d7e90837adb44cc37b] ResetAdapter+0x29/0x100 [skfp 6c01d4bab63c36978bd0a7d7e90837adb44cc37b] skfp_ctl_set_mac_address+0x57/0x80 [skfp 6c01d4bab63c36978bd0a7d7e90837adb44cc37b] netif_set_mac_address+0x1e4/0x2c0 do_setlink+0x684/0x2680 Address 0x10 is the offset of tx_curr_get, the third pointer in struct s_smt_tx_queue, on 64-bit. mac_drv_clear_rx_queue(), which ResetAdapter() calls immediately afterwards, dereferences smc->hw.fp.rx[QUEUE_R1] in the same way behind the same ineffective hw_state test; the transmit queue merely crashes first. Both are covered by the guard below. Skip the adapter reset when the interface is down. dev_addr_set() is left unconditional, so the new address is still recorded in dev->dev_addr. Nothing is lost by not resetting the adapter here: skfp_open() deliberately re-reads the factory address on every open, read_address(smc, NULL); eth_hw_addr_set(dev, smc->hw.fddi_canon_addr.a); and the comment above it states this is done to discard exactly such an address override across a close/open cycle. An address set while the interface is down could not have survived the following open even before this change, so the guard removes no working behaviour. Guarding the hardware side of ndo_set_mac_address() with netif_running() is established practice; skge_set_mac_address() has done so since commit 2eb3e621c4e0 ("skge: set mac address bonding fix"). Guarding the reset as a whole, rather than NULL-checking the queues, is also what the rest of the driver expects. After a previous open/close the queue pointers are stale but non-NULL, so there is no crash, yet ResetAdapter() goes on to call smt_online() and STI_FBI() ("Enable Board Interrupts") while skfp_close() has already called free_irq() - the adapter would be brought back online with no handler installed. The only other ResetAdapter() caller is skfp_interrupt(), which by construction runs only while the device is open. Found by automated driver testing against an emulated SysKonnect FDDI adapter under a KASAN-enabled 7.0.0 kernel. Triggering it requires CAP_NET_ADMIN. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Assisted-by: LLM KASAN Signed-off-by: Hohyun Sim Link: https://patch.msgid.link/20260910063743.110747-1-tlaghgus0425@korea.ac.kr Signed-off-by: Paolo Abeni --- drivers/net/fddi/skfp/skfddi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/fddi/skfp/skfddi.c b/drivers/net/fddi/skfp/skfddi.c index a273362c9e70..feea7baa4816 100644 --- a/drivers/net/fddi/skfp/skfddi.c +++ b/drivers/net/fddi/skfp/skfddi.c @@ -928,7 +928,8 @@ static int skfp_ctl_set_mac_address(struct net_device *dev, void *addr) dev_addr_set(dev, p_sockaddr->sa_data); spin_lock_irqsave(&bp->DriverLock, Flags); - ResetAdapter(smc); + if (netif_running(dev)) + ResetAdapter(smc); spin_unlock_irqrestore(&bp->DriverLock, Flags); return 0; /* always return zero */ From 2f3c2a6f963e57cf4f0f34cbf24ab11abb64d118 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 11 Sep 2026 13:00:07 +0530 Subject: [PATCH 1707/2366] xfs: fix typos and repeated words in comments Correct ten misspellings and eight accidentally doubled words in comments. No code changes. The doubled word in xfs_zone_alloc.c was not a duplicate: "so that is is reused" is "it is" misspelt, so that one reads "so that it is reused" rather than dropping a word. Signed-off-by: Hemanth Selam Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_ag.h | 2 +- fs/xfs/libxfs/xfs_alloc.c | 4 ++-- fs/xfs/libxfs/xfs_attr_leaf.c | 2 +- fs/xfs/libxfs/xfs_errortag.h | 2 +- fs/xfs/libxfs/xfs_exchmaps.c | 2 +- fs/xfs/libxfs/xfs_format.h | 2 +- fs/xfs/libxfs/xfs_inode_buf.c | 2 +- fs/xfs/scrub/agheader_repair.c | 2 +- fs/xfs/scrub/alloc_repair.c | 2 +- fs/xfs/scrub/dirtree.c | 2 +- fs/xfs/scrub/reap.c | 2 +- fs/xfs/xfs_bmap_item.c | 2 +- fs/xfs/xfs_inode.c | 2 +- fs/xfs/xfs_log_cil.c | 2 +- fs/xfs/xfs_platform.h | 2 +- fs/xfs/xfs_zone_alloc.c | 2 +- fs/xfs/xfs_zone_gc.c | 2 +- 17 files changed, 18 insertions(+), 18 deletions(-) diff --git a/fs/xfs/libxfs/xfs_ag.h b/fs/xfs/libxfs/xfs_ag.h index fd22fe598931..ee636b66a72f 100644 --- a/fs/xfs/libxfs/xfs_ag.h +++ b/fs/xfs/libxfs/xfs_ag.h @@ -207,7 +207,7 @@ xfs_perag_next( } /* - * Per-ag geometry infomation and validation + * Per-ag geometry information and validation */ xfs_agblock_t xfs_ag_block_count(struct xfs_mount *mp, xfs_agnumber_t agno); void xfs_agino_range(struct xfs_mount *mp, xfs_agnumber_t agno, diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c index d99602bcc16f..f762dcce8d13 100644 --- a/fs/xfs/libxfs/xfs_alloc.c +++ b/fs/xfs/libxfs/xfs_alloc.c @@ -3487,7 +3487,7 @@ xfs_alloc_read_agf( } /* - * Pre-proces allocation arguments to set initial state that we don't require + * Pre-process allocation arguments to set initial state that we don't require * callers to set up correctly, as well as bounds check the allocation args * that are set up. */ @@ -3608,7 +3608,7 @@ xfs_alloc_vextent_finish( * ABBA AGF deadlocks because a future allocation attempt in this * transaction may attempt to lock a lower number AGF. * - * We can't release the AGF until the transaction is commited, so at + * We can't release the AGF until the transaction is committed, so at * this point we must update the "first allocation" tracker to point at * this AG if the tracker is empty or points to a lower AG. This allows * the next allocation attempt to be modified appropriately to avoid diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c index b6288395f853..2c80f4fd0b78 100644 --- a/fs/xfs/libxfs/xfs_attr_leaf.c +++ b/fs/xfs/libxfs/xfs_attr_leaf.c @@ -1715,7 +1715,7 @@ xfs_attr3_leaf_add_work( /* * This freemap entry starts at the old end of the * leaf entry array, so we need to adjust its base - * upward to accomodate the larger array. + * upward to accommodate the larger array. */ diff = sizeof(struct xfs_attr_leaf_entry); } else if (ichdr->freemap[i].size > 0 && diff --git a/fs/xfs/libxfs/xfs_errortag.h b/fs/xfs/libxfs/xfs_errortag.h index 6de207fed2d8..f0c83f1f0b3b 100644 --- a/fs/xfs/libxfs/xfs_errortag.h +++ b/fs/xfs/libxfs/xfs_errortag.h @@ -83,7 +83,7 @@ #define XFS_RANDOM_DEFAULT 100 /* - * Table of errror injection knobs. The parameters to the XFS_ERRTAG macro are: + * Table of error injection knobs. The parameters to the XFS_ERRTAG macro are: * 1. The XFS_ERRTAG_ flag but without the prefix; * 2. The name of the sysfs knob; and * 3. The default value for the knob. diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 3efed37cb98a..6a66b6075e0a 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -395,7 +395,7 @@ xfs_exchmaps_one_step( /* * Re-add both mappings. We exchange the file offsets between the two * maps and add the opposite map, which has the effect of filling the - * logical offsets we just unmapped, but with with the physical mapping + * logical offsets we just unmapped, but with the physical mapping * information exchanged. */ swap(irec1->br_startoff, irec2->br_startoff); diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h index dd0ed046fbe9..1a7a7e60a170 100644 --- a/fs/xfs/libxfs/xfs_format.h +++ b/fs/xfs/libxfs/xfs_format.h @@ -1051,7 +1051,7 @@ enum xfs_dinode_fmt { * block is 1KB in size. * * With XFS_MAX_EXTCNT_DATA_FORK_SMALL representing maximum extent count and - * with 1KB sized blocks, a file can reach upto, + * with 1KB sized blocks, a file can reach up to, * 1KB * (2^31) = 2TB * * This is much larger than the theoretical maximum size of a directory diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c index e4c3f7b24e95..0340e2189921 100644 --- a/fs/xfs/libxfs/xfs_inode_buf.c +++ b/fs/xfs/libxfs/xfs_inode_buf.c @@ -626,7 +626,7 @@ xfs_dinode_verify( * have di_nlink track the link count, even if the actual filesystem * only supported V1 inodes (i.e. di_onlink). When writing out the * ondisk inode, it would set both the ondisk di_nlink and di_onlink to - * the the incore di_nlink value, which is why we cannot check for + * the incore di_nlink value, which is why we cannot check for * di_nlink==0 on a V1 inode. V2/3 inodes would get written out with * di_onlink==0, so we can check that. */ diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index a66b611588c4..ff1b4b361cf2 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1369,7 +1369,7 @@ xrep_iunlink_mark_ondisk( /* * Walk an iunlink bucket's inode list. For each inode that should be on this - * chain, clear its entry in in iunlink_bmp because it's ok and we don't need + * chain, clear its entry in iunlink_bmp because it's ok and we don't need * to touch it further. */ STATIC int diff --git a/fs/xfs/scrub/alloc_repair.c b/fs/xfs/scrub/alloc_repair.c index 95e318e4f3a6..2398e3819597 100644 --- a/fs/xfs/scrub/alloc_repair.c +++ b/fs/xfs/scrub/alloc_repair.c @@ -338,7 +338,7 @@ xrep_cntbt_extent_cmp( } /* - * Sort the free extents by length so so that we can put the records into the + * Sort the free extents by length so that we can put the records into the * cntbt in the correct order. Don't let userspace kill us if we're resorting * after allocating btree blocks. */ diff --git a/fs/xfs/scrub/dirtree.c b/fs/xfs/scrub/dirtree.c index 9b0ab2316612..887383d2e941 100644 --- a/fs/xfs/scrub/dirtree.c +++ b/fs/xfs/scrub/dirtree.c @@ -1021,7 +1021,7 @@ xchk_dirtree( return error; } -/* Does the directory targetted by this scrub have no parents? */ +/* Does the directory targeted by this scrub have no parents? */ bool xchk_dirtree_parentless(const struct xchk_dirtree *dl) { diff --git a/fs/xfs/scrub/reap.c b/fs/xfs/scrub/reap.c index f698b9be3dd1..0dfe61bafc6a 100644 --- a/fs/xfs/scrub/reap.c +++ b/fs/xfs/scrub/reap.c @@ -172,7 +172,7 @@ static inline bool xreap_is_dirty(const struct xreap_state *rs) } /* - * Decide if we need to roll the transaction to clear out the the log + * Decide if we need to roll the transaction to clear out the log * reservation that we allocated to buffer invalidations. */ static inline bool xreap_want_binval_roll(const struct xreap_state *rs) diff --git a/fs/xfs/xfs_bmap_item.c b/fs/xfs/xfs_bmap_item.c index 89f6e79a955f..aa5b41629747 100644 --- a/fs/xfs/xfs_bmap_item.c +++ b/fs/xfs/xfs_bmap_item.c @@ -339,7 +339,7 @@ xfs_bmap_update_get_group( /* * Bump the intent count on behalf of the deferred rmap and refcount - * intent items that that we can queue when we finish this bmap work. + * intent items that we can queue when we finish this bmap work. * This new intent item will bump the intent count before the bmap * intent drops the intent count, ensuring that the intent count * remains nonzero across the transaction roll. diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 030a7c8f2c12..621513d7215e 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -2669,7 +2669,7 @@ xfs_irele( } /* - * Ensure all commited transactions touching the inode are written to the log. + * Ensure all committed transactions touching the inode are written to the log. */ int xfs_log_force_inode( diff --git a/fs/xfs/xfs_log_cil.c b/fs/xfs/xfs_log_cil.c index f9e07a32f60f..9446ac44ba88 100644 --- a/fs/xfs/xfs_log_cil.c +++ b/fs/xfs/xfs_log_cil.c @@ -1370,7 +1370,7 @@ xlog_cil_cleanup_whiteouts( * allocation context. However, we do not want to block on memory reclaim * recursing back into the filesystem because this push may have been triggered * by memory reclaim itself. Hence we really need to run under full GFP_NOFS - * contraints here. + * constraints here. */ static void xlog_cil_push_work( diff --git a/fs/xfs/xfs_platform.h b/fs/xfs/xfs_platform.h index 5d542e95fe44..745d715b4c64 100644 --- a/fs/xfs/xfs_platform.h +++ b/fs/xfs/xfs_platform.h @@ -153,7 +153,7 @@ static inline void delay(long ticks) /* * XFS wrapper structure for sysfs support. It depends on external data * structures and is embedded in various internal data structures to implement - * the XFS sysfs object heirarchy. Define it here for broad access throughout + * the XFS sysfs object hierarchy. Define it here for broad access throughout * the codebase. */ struct xfs_kobj { diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index d7ec055a9a06..b75cf3bfe33c 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -828,7 +828,7 @@ xfs_get_cached_zone( } /* - * Stash our zone in the inode so that is is reused for future allocations. + * Stash our zone in the inode so that it is reused for future allocations. * * The open_zone structure will be pinned until either the inode is freed or * until the cached open zone is replaced with a different one because the diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c index 5fdcf98a2133..54b70ed2922f 100644 --- a/fs/xfs/xfs_zone_gc.c +++ b/fs/xfs/xfs_zone_gc.c @@ -46,7 +46,7 @@ * before remapping. * * Once a zone does not contain any valid data, be that through GC or user - * block removal, it is queued for for a zone reset. The reset operation + * block removal, it is queued for a zone reset. The reset operation * carefully ensures that the RT device cache is flushed and all transactions * referencing the rmap have been committed to disk. */ From 82e47533221d4746947b74d2e79a478c36c6433a Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 4 Sep 2026 15:43:11 +0200 Subject: [PATCH 1708/2366] ata: libahci: clear PxCLBU and PxFBU for AHCI_HFLAG_32BIT_ONLY A user reported that commit 105c42566a55 ("ata: ahci: force 32-bit DMA for JMicron JMB582/JMB585") made the JMicron JMB585 unusable on his board. The failure is seen as soon as the ahci driver is probed, and booting with iommu=off does not solve the problem. Looking at the AHCI specification, PxCLBU and PxFBU are both read only '0' for HBAs that do not support 64-bit addressing. For HBAs that do support 64-bit addressing, the registers are read write, with a reset value that is Implementation Specific. When using the AHCI_HFLAG_32BIT_ONLY flag, the HBA does support 64-bit addressing, and a 32-bit DMA mask is set by simply clearing HOST_CAP_64. Thus, in this case, we need to explicitly clear the registers to 0. Fixes: 105c42566a55 ("ata: ahci: force 32-bit DMA for JMicron JMB582/JMB585") Fixes: c7a42156d99b ("ahci: disable 64bit dma on sb600") Cc: stable@vger.kernel.org Reported-by: Roland Waltersson Closes: https://lore.kernel.org/linux-ide/IA0PR17MB668730A4ECCD65F7A1DC3EDC9EB62@IA0PR17MB6687.namprd17.prod.outlook.com/ Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20260904134310.1465051-2-cassel@kernel.org Signed-off-by: Niklas Cassel --- drivers/ata/libahci.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index 6d72eb017b49..08ab56e95566 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -744,15 +744,28 @@ void ahci_start_fis_rx(struct ata_port *ap) struct ahci_port_priv *pp = ap->private_data; u32 tmp; - /* set FIS registers */ + /* + * On HBAs that only support 32-bit addressing PxCLBU is read only '0'. + * When applying the AHCI_HFLAG_32BIT_ONLY quirk, PxCLBU is RW, and the + * reset value is Implementation Specific, so we need to clear it to 0. + */ if (hpriv->cap & HOST_CAP_64) writel((pp->cmd_slot_dma >> 16) >> 16, port_mmio + PORT_LST_ADDR_HI); + else if (hpriv->flags & AHCI_HFLAG_32BIT_ONLY) + writel(0, port_mmio + PORT_LST_ADDR_HI); writel(pp->cmd_slot_dma & 0xffffffff, port_mmio + PORT_LST_ADDR); + /* + * On HBAs that only support 32-bit addressing PxFBU is read only '0'. + * When applying the AHCI_HFLAG_32BIT_ONLY quirk, PxFBU is RW, and the + * reset value is Implementation Specific, so we need to clear it to 0. + */ if (hpriv->cap & HOST_CAP_64) writel((pp->rx_fis_dma >> 16) >> 16, port_mmio + PORT_FIS_ADDR_HI); + else if (hpriv->flags & AHCI_HFLAG_32BIT_ONLY) + writel(0, port_mmio + PORT_FIS_ADDR_HI); writel(pp->rx_fis_dma & 0xffffffff, port_mmio + PORT_FIS_ADDR); /* enable FIS reception */ From 621d90169cef6c8da5b6134db5c0c4e23cdd09ce Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Tue, 11 Aug 2026 10:27:02 +0200 Subject: [PATCH 1709/2366] wifi: brcmfmac: fix lost 802.1x TX completion wakeup brcmf_txfinalize() decrements pend_8021x_cnt before a lockless waitqueue_active() check. atomic_dec() does not order the decrement against the check. The waiter can therefore observe a nonzero count while the waker observes an empty queue, losing the final wakeup and delaying key installation until the 950 ms timeout. Add smp_mb__after_atomic() to order the decrement before the queue check. wait_event_timeout() provides the matching barrier. LKMM confirms that this forbids the lost-wakeup outcome. Fixes: 21fff75d2fb6 ("brcmfmac: use wait_event_timeout for 8021x pending count") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260811082702.44521-1-kmehltretter@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index dad6f4563d14..d2ae67985606 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -555,6 +555,8 @@ void brcmf_txfinalize(struct brcmf_if *ifp, struct sk_buff *txp, bool success) if (type == ETH_P_PAE) { atomic_dec(&ifp->pend_8021x_cnt); + /* Order the decrement before waitqueue_active() */ + smp_mb__after_atomic(); if (waitqueue_active(&ifp->pend_8021x_wait)) wake_up(&ifp->pend_8021x_wait); } From 1eeca1d5e0920fbdad6449768fd2d4364e714180 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Sat, 15 Aug 2026 20:10:43 +0800 Subject: [PATCH 1710/2366] wifi: brcmsmac: fix UAF in brcms_free_timer() brcms_free_timer() calls brcms_del_timer() which uses the non-synchronous cancel_delayed_work() to cancel the timer's underlying delayed work. If the work callback (_brcms_timer) is already running, cancel_delayed_work() returns false without waiting, and brcms_free_timer() proceeds to kfree(t) while the callback still accesses t through container_of(). Add an explicit cancel_delayed_work_sync() after brcms_del_timer() to guarantee that any in-flight callback has completed before the timer structure is freed. Fixes: 5b435de0d786 ("net: wireless: add brcm80211 drivers") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260815121043.938414-1-yijiangshan@kylinos.cn Signed-off-by: Johannes Berg --- .../net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c index 6255d673d2d3..c1a2318d7ea6 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c @@ -1571,6 +1571,10 @@ void brcms_free_timer(struct brcms_timer *t) /* delete the timer in case it is active */ brcms_del_timer(t); + /* Ensure the callback has finished before freeing the timer + * structure, since brcms_del_timer() uses non-synchronous cancel. + */ + cancel_delayed_work_sync(&t->dly_wrk); if (wl->timers == t) { wl->timers = wl->timers->next; From 18a6fe05fb6e18de29fa90d388bb34044114b3d8 Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Fri, 11 Sep 2026 13:50:21 +0300 Subject: [PATCH 1711/2366] net: bridge: mst: move switchdev call outside rcu This is a follow-up of one of sashiko's pre-existing bug reports. br_mst_set_state() calls switchdev_port_attr_set() for nonzero MSTIs while holding rcu_read_lock() which invokes the blocking switchdev notifier chain and may sleep. Nonzero MSTI changes come from netlink with rtnl held. Move the switchdev call before entering the rcu section and assert that rtnl is held. The call cannot be deferred because netlink needs its error and extack. Also DSA reads the old bridge MST state during the callback and checks it. A deferred callback will be late and will see the updated state. Fixes: 3a7c1661ae13 ("net: bridge: mst: fix vlan use-after-free") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260911105021.1385934-1-razor@blackwall.org Signed-off-by: Paolo Abeni --- net/bridge/br_mst.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/net/bridge/br_mst.c b/net/bridge/br_mst.c index 43a300ae6bfa..1654efd3045b 100644 --- a/net/bridge/br_mst.c +++ b/net/bridge/br_mst.c @@ -107,21 +107,24 @@ int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state, struct net_bridge_vlan *v; int err = 0; - rcu_read_lock(); - vg = nbp_vlan_group_rcu(p); - if (!vg) - goto out; - /* MSTI 0 (CST) state changes are notified via the regular - * SWITCHDEV_ATTR_ID_PORT_STP_STATE. + * SWITCHDEV_ATTR_ID_PORT_STP_STATE. All other MSTIs are handled via + * netlink with RTNL held */ if (msti) { + ASSERT_RTNL(); + err = switchdev_port_attr_set(p->dev, &attr, extack); if (err && err != -EOPNOTSUPP) goto out; + err = 0; } - err = 0; + rcu_read_lock(); + vg = nbp_vlan_group_rcu(p); + if (!vg) + goto out_rcu_unlock; + list_for_each_entry_rcu(v, &vg->vlan_list, vlist) { if (v->brvlan->msti != msti) continue; @@ -129,8 +132,9 @@ int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state, br_mst_vlan_set_state(vg, v, state); } -out: +out_rcu_unlock: rcu_read_unlock(); +out: return err; } From 2cef2588c995722a901368def30befeef9ae55c6 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 12 Sep 2026 14:09:19 -0400 Subject: [PATCH 1712/2366] net/sched: hhf: cap hh_flows_limit at change time hhf_change() stores TCA_HHF_HH_FLOWS_LIMIT with no upper bound. A huge hh_flows_limit lets each new heavy-hitter flow pass the hh_flows_current_cnt check in alloc_new_hh() and forces a fixed-size kzalloc(GFP_ATOMIC) per flow under spoofed traffic, for unbounded memory growth. Bound the attribute with NLA_POLICY_MAX() at 2*HH_FLOWS_CNT (the hhf_init() default) and report the rejected value via extack. The deprecated nested parse is kept: legacy tc does not set NLA_F_NESTED on TCA_OPTIONS. Configs relying on hh_limit above the default were relying on unbounded, unsafe behaviour and are not supported going forward. hhf_init() also ran hhf_change() before setting the default hh_flows_limit, so a user-supplied hh_limit at add time was clobbered back to 2048. Set the default before hhf_change() so the configured value sticks. This is a follow-up to commit eb56a495f59b ("net/sched: hhf: clamp quantum in change and init paths"), which bounded the quantum of the same qdisc; the hh_flows_limit bound is the remaining unbounded knob of that series' scope. Conditions to recreate the bug: CAP_NET_ADMIN in a user namespace; tc qdisc change dev X root hhf hh_limit 4294967295 succeeds and the value is echoed by tc qdisc show, unbounding heavy-hitter flow allocations; also tc qdisc add dev X root hhf hh_limit 500 stores 2048 instead of 500. Fixes: 10239edf86f1 ("net-qdisc-hhf: Heavy-Hitter Filter (HHF) qdisc") Cc: stable@vger.kernel.org Reported-by: Sashiko (gemini) Closes: https://sashiko.dev/#/patchset/20260822195509.112717-1-jhs@mojatatu.com Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-B855.v1.20260911153152@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_hhf.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/net/sched/sch_hhf.c b/net/sched/sch_hhf.c index fc72f825fbd9..5dec1ed969ad 100644 --- a/net/sched/sch_hhf.c +++ b/net/sched/sch_hhf.c @@ -527,7 +527,7 @@ static void hhf_destroy(struct Qdisc *sch) static const struct nla_policy hhf_policy[TCA_HHF_MAX + 1] = { [TCA_HHF_BACKLOG_LIMIT] = { .type = NLA_U32 }, [TCA_HHF_QUANTUM] = { .type = NLA_U32 }, - [TCA_HHF_HH_FLOWS_LIMIT] = { .type = NLA_U32 }, + [TCA_HHF_HH_FLOWS_LIMIT] = NLA_POLICY_MAX(NLA_U32, 2 * HH_FLOWS_CNT), [TCA_HHF_RESET_TIMEOUT] = { .type = NLA_U32 }, [TCA_HHF_ADMIT_BYTES] = { .type = NLA_U32 }, [TCA_HHF_EVICT_TIMEOUT] = { .type = NLA_U32 }, @@ -546,7 +546,7 @@ static int hhf_change(struct Qdisc *sch, struct nlattr *opt, u32 new_hhf_non_hh_weight = q->hhf_non_hh_weight; err = nla_parse_nested_deprecated(tb, TCA_HHF_MAX, opt, hhf_policy, - NULL); + extack); if (err < 0) return err; @@ -624,6 +624,9 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt, q->hhf_evict_timeout = HZ; /* 1 sec */ q->hhf_non_hh_weight = 2; + /* Cap max active HHs at twice len of hh_flows table. */ + q->hh_flows_limit = 2 * HH_FLOWS_CNT; + if (opt) { int err = hhf_change(sch, opt, extack); @@ -639,8 +642,6 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt, for (i = 0; i < HH_FLOWS_CNT; i++) INIT_LIST_HEAD(&q->hh_flows[i]); - /* Cap max active HHs at twice len of hh_flows table. */ - q->hh_flows_limit = 2 * HH_FLOWS_CNT; q->hh_flows_overlimit = 0; q->hh_flows_total_cnt = 0; q->hh_flows_current_cnt = 0; From 0654f4dba1fbc697f2653aba30cd68587fcbf10e Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 12 Sep 2026 14:09:20 -0400 Subject: [PATCH 1713/2366] selftests/tc-testing: add hhf hh_limit cap tests Cover the new TCA_HHF_HH_FLOWS_LIMIT bound: values above 2*HH_FLOWS_CNT (4294967295, 65536, 2049) are rejected with the configured limit left untouched on both the change and the add path, the boundary value 2048 is accepted (installed at 100 first so the boundary change is load-bearing), and an add-time hh_limit 500 is preserved instead of being clobbered by the default. Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-B855.v1.20260911153152@mojatatu.com.2 Signed-off-by: Paolo Abeni --- .../tc-tests/qdiscs/hhf_flows_limit.json | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 tools/testing/selftests/tc-testing/tc-tests/qdiscs/hhf_flows_limit.json diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hhf_flows_limit.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hhf_flows_limit.json new file mode 100644 index 000000000000..44538b9266b6 --- /dev/null +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hhf_flows_limit.json @@ -0,0 +1,128 @@ +[ + { + "id": "e3cc", + "name": "HHF hh_limit rejects value above 2*HH_FLOWS_CNT cap (4294967295)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY handle 1: root hhf" + ], + "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 1: root hhf hh_limit 4294967295", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 2048", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "f681", + "name": "HHF hh_limit rejects 65536 (above 2*HH_FLOWS_CNT cap)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY handle 1: root hhf" + ], + "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 1: root hhf hh_limit 65536", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 2048", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "223d", + "name": "HHF hh_limit accepts boundary value 2048 (2*HH_FLOWS_CNT)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY handle 1: root hhf hh_limit 100" + ], + "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 1: root hhf hh_limit 2048", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 2048", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "147f", + "name": "HHF hh_limit rejects first value above cap (2049)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY handle 1: root hhf" + ], + "cmdUnderTest": "$TC qdisc change dev $DUMMY handle 1: root hhf hh_limit 2049", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 2048", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "4d4f", + "name": "HHF add-time hh_limit 500 is preserved (init does not clobber user value)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root hhf hh_limit 500", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root refcnt [0-9]+.*hh_limit 500", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY handle 1: root" + ] + }, + { + "id": "ca99", + "name": "HHF add-time hh_limit 4294967295 is rejected (no qdisc installed)", + "category": [ + "qdisc", + "hhf" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root hhf hh_limit 4294967295", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc hhf 1: root", + "matchCount": "0", + "teardown": [] + } +] From ba13f1f32d96d7ce9069ae4f32404ecde8da89c4 Mon Sep 17 00:00:00 2001 From: Meijing Zhao Date: Tue, 15 Sep 2026 20:46:41 +0800 Subject: [PATCH 1714/2366] mm: memblock: add missing HugeTLB flag name Commit 7d163a75f821 ("memblock: make HugeTLB bootmem allocation work with KHO") added MEMBLOCK_RSRV_HUGETLB but did not add the corresponding entry to flagname[]. As a result, memblock debugfs cannot report the flag by name. Add the missing RSV_HUGETLB entry. Fixes: 7d163a75f821 ("memblock: make HugeTLB bootmem allocation work with KHO") Signed-off-by: Meijing Zhao Signed-off-by: Wandun Chen Link: https://lore.kernel.org/lkml/20260821020910.3428585-2-zhaomeijing100@gmail.com/ Link: https://patch.msgid.link/20260915124641.2498280-1-chenwandun1@gmail.com Signed-off-by: Mike Rapoport (Microsoft) --- mm/memblock.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mm/memblock.c b/mm/memblock.c index 021db49eb7fc..e6fdf95a0534 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -2886,6 +2886,7 @@ static const char * const flagname[] = { [ilog2(MEMBLOCK_RSRV_NOINIT)] = "RSV_NIT", [ilog2(MEMBLOCK_RSRV_KERN)] = "RSV_KERN", [ilog2(MEMBLOCK_KHO_SCRATCH)] = "KHO_SCRATCH", + [ilog2(MEMBLOCK_RSRV_HUGETLB)] = "RSV_HUGETLB", }; static int memblock_debug_show(struct seq_file *m, void *private) From 8e759cd1f6444a946bd1fd2b2b29eea582eea1d5 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Mon, 14 Sep 2026 01:14:01 +0000 Subject: [PATCH 1715/2366] tcp: Don't call skb_clone_and_charge_r() for close()d listener in tcp_v6_do_rcv(). tcp_v6_do_rcv() no longer calls skb_clone_and_charge_r() for TCP_LISTEN since commit 073d89808c06 ("net: fix data-races around sk->sk_forward_alloc"). However, there is still a small race window between tcp_v6_rcv() and tcp_v6_do_rcv(), where concurrent close() changes TCP_LISTEN to TCP_CLOSE, causing skb_clone_and_charge_r() to be called locklessly and resulting in the splat below. [0] Let's avoid calling skb_clone_and_charge_r() for TCP_CLOSE as well. This is fine for non-listeners because tcp_rcv_state_process() drops skb for TCP_CLOSE and opt_skb was freed immediately anyway. [0]: sk->sk_forward_alloc WARNING: net/ipv4/af_inet.c:162 at inet_sock_destruct+0x64d/0x810 net/ipv4/af_inet.c:162, CPU#1: ksoftirqd/1/28 Modules linked in: CPU: 1 UID: 0 PID: 28 Comm: ksoftirqd/1 Not tainted 7.2.0 #17 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.17.0-debian-1.17.0-1 04/01/2014 RIP: 0010:inet_sock_destruct+0x64d/0x810 net/ipv4/af_inet.c:162 Code: 3d 49 ff e9 06 fd ff ff e8 d0 5b 83 f8 90 0f 0b 90 e9 35 fe ff ff e8 c2 5b 83 f8 90 0f 0b 90 e9 c5 fe ff ff e8 b4 5b 83 f8 90 <0f> 0b 90 e9 04 ff ff ff e8 a6 5b 83 f8 90 0f 0b 90 e9 65 fe ff ff RSP: 0018:ffffc90000677bb8 EFLAGS: 00010246 RAX: 0000000000000000 RBX: ffff8880117bde80 RCX: ffffffff8957eb41 RDX: ffff88801dad5d00 RSI: ffffffff8957ec3c RDI: 0000000000000005 RBP: 00000000fffff000 R08: ffffffff8957eb41 R09: 00000000fffff000 R10: 0000000000000005 R11: 0000000000000000 R12: dffffc0000000000 R13: ffff8880117bdf10 R14: ffffffff81c08eb7 R15: 0000000000000003 FS: 0000000000000000(0000) GS:ffff8880d7ae5000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f93a1021138 CR3: 00000000207a9000 CR4: 0000000000350ef0 Call Trace: __sk_destruct+0x82/0xae0 net/core/sock.c:2356 rcu_do_batch kernel/rcu/tree.c:2645 [inline] rcu_core+0x59c/0x1100 kernel/rcu/tree.c:2897 handle_softirqs+0x1e4/0x9b0 kernel/softirq.c:622 run_ksoftirqd kernel/softirq.c:1076 [inline] run_ksoftirqd+0x38/0x60 kernel/softirq.c:1068 smpboot_thread_fn+0x458/0xc80 kernel/smpboot.c:160 kthread+0x396/0x4a0 kernel/kthread.c:436 ret_from_fork+0x8e0/0xe40 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Fixes: e994b2f0fb92 ("tcp: do not lock listener to process SYN packets") Reported-by: Taras Madan Signed-off-by: Kuniyuki Iwashima Reviewed-by: Xuanqiang Luo Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260914011420.115556-1-kuniyu@google.com Signed-off-by: Paolo Abeni --- net/ipv6/tcp_ipv6.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index df9c29eb5c1f..7fa4ed2fd4f1 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -1604,7 +1604,8 @@ int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb) by tcp. Feel free to propose better solution. --ANK (980728) */ - if (np->rxopt.all && sk->sk_state != TCP_LISTEN) + if (np->rxopt.all && + !((1 << sk->sk_state) & (TCPF_LISTEN | TCPF_CLOSE))) opt_skb = skb_clone_and_charge_r(skb, sk); if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */ From 83a945a529d6e002dd7339c532288a931f463dba Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sat, 12 Sep 2026 14:48:48 +0000 Subject: [PATCH 1716/2366] tcp: do not let tcp_rmem be set below 4096 We can hit a division by zero crash in tcp_rcvbuf_grow() and tcp_rcv_space_adjust(): divide error: 0000 [#1] PREEMPT SMP RIP: 0010:tcp_rcvbuf_grow+0x187/0x450 net/ipv4/tcp_input.c:939 ... grow = div_u64(((u64)rcvwin << 1) * (newval - oldval), oldval); The division uses oldval = tp->rcvq_space.space as divisor. When tp->rcvq_space.space is zero, this leads to a divide-by-zero exception. tp->rcvq_space.space is initialized in tcp_init_buffer_space(): tp->rcvq_space.space = min3(tp->rcv_ssthresh, tp->rcv_wnd, (u32)TCP_INIT_CWND * tp->advmss); If tcp_rmem[1] is configured to very small values (such as 1), sk->sk_rcvbuf is initialized to 1. Then tcp_full_space(sk), which computes (sk->sk_rcvbuf * scaling_ratio) >> 8, truncates to 0. This sets tp->window_clamp = 0, tp->rcv_ssthresh = 0, and tp->rcvq_space.space = 0. Later, when data arrives and DRS is invoked, tcp_rcvbuf_grow() divides by oldval == 0. Back in 2015, commit b1cb59cf2efe ("net: sysctl_net_core: check SNDBUF and RCVBUF for min length") ensured that net.core.rmem_default and net.core.rmem_max cannot be set below SOCK_MIN_RCVBUF. Similarly, SO_RCVBUF setsockopt enforces max_t(int, val * 2, SOCK_MIN_RCVBUF). However, net.ipv4.tcp_rmem still had .extra1 = SYSCTL_ONE, allowing arbitrarily small values. Because SOCK_MIN_RCVBUF depends on sizeof(struct sk_buff) and cacheline alignment, its value varies across architectures and configuration options. Using a fixed constant of 4096 ensures a predictable, architecture- independent lower bound that is safely above SOCK_MIN_RCVBUF everywhere and matches the documented 4K default. Fix this by setting tcp_rmem.extra1 to 4096 and updating the documentation. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260912144848.3448026-1-edumazet@google.com Signed-off-by: Paolo Abeni --- Documentation/networking/ip-sysctl.rst | 2 ++ net/ipv4/sysctl_net_ipv4.c | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index b05829e44d8f..f7af0286341c 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -874,6 +874,8 @@ tcp_rmem - vector of 3 INTEGERs: min, default, max case this value is ignored. Default: between 131072 and 32MB, depending on RAM size. + Each of the three values cannot be set below 4096. + tcp_sack - BOOLEAN Enable select acknowledgments (SACKS). diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c index 2f0363bca2a8..e3760daa3470 100644 --- a/net/ipv4/sysctl_net_ipv4.c +++ b/net/ipv4/sysctl_net_ipv4.c @@ -51,6 +51,8 @@ static int tcp_ecn_mode_max = 5; static u32 icmp_errors_extension_mask_all = GENMASK_U8(ICMP_ERR_EXT_COUNT - 1, 0); +static int tcp_min_rcvbuf = 4096; + /* obsolete */ static int sysctl_tcp_low_latency __read_mostly; @@ -1462,7 +1464,7 @@ static const struct ctl_table ipv4_net_table[] = { .maxlen = sizeof(init_net.ipv4.sysctl_tcp_rmem), .mode = 0644, .proc_handler = proc_dointvec_minmax, - .extra1 = SYSCTL_ONE, + .extra1 = &tcp_min_rcvbuf, }, { .procname = "tcp_comp_sack_delay_ns", From aaad136d56d91252517272b68cd533e5714698d5 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sat, 1 Aug 2026 01:35:50 +0900 Subject: [PATCH 1717/2366] RISC-V: KVM: Synchronize hrtimer callback during teardown The non-Sstc hrtimer callback clears next_set before its final uses of the enclosing vCPU. If teardown observes next_set as false while the callback is still running, kvm_riscv_vcpu_timer_cancel() skips hrtimer_cancel() and kvm_destroy_vcpus() can free the vCPU before the callback enters kvm_riscv_vcpu_set_interrupt(). A guest can arm the timer with SBI TIME and request shutdown with SBI legacy shutdown or SRST. A VMM that honors KVM_EXIT_SYSTEM_EVENT and destroys the VM supplies the teardown side of the race; no post-launch host ioctl is needed to arm or request teardown. On upstream master 62cc90241548, generic KASAN reported: BUG: KASAN: slab-use-after-free in do_raw_spin_lock Write of size 4 at addr ff60000005e58898 kvm_riscv_vcpu_set_interrupt kvm_riscv_vcpu_hrtimer_expired __hrtimer_run_queues hrtimer_interrupt The object was allocated by KVM_CREATE_VCPU and freed concurrently by: kvm_destroy_vcpus kvm_arch_destroy_vm kvm_destroy_vm __fput For deterministic validation, I added mdelay(1000) immediately after the existing next_set = false assignment. This only widens the existing post-clear callback window. A no-delay trace build naturally reached the callback-after-teardown-start/before-deinit ordering in 12 of 200 runs, but 1,500 stock-kernel stress iterations did not produce a KASAN report, so natural reproduction is timing-sensitive. Always invoke hrtimer_cancel() for an initialized timer. Preserve the existing -EINVAL result when the timer is no longer set, but only after synchronizing with a running callback. With this patch, hrtimer_cancel() blocked for the full widened callback window before vCPU destruction. KASAN reported no error in 100 fixed-and-widened runs or 200 fix-only timing-sweep runs. Fixes: 3a9f66cb25e1 ("RISC-V: KVM: Add timer functionality") Cc: stable@vger.kernel.org Assisted-by: OpenAI:GPT-5.6 Signed-off-by: Myeonghun Pak Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260731163550.46991-1-mhun512@gmail.com Signed-off-by: Anup Patel --- arch/riscv/kvm/vcpu_timer.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/riscv/kvm/vcpu_timer.c b/arch/riscv/kvm/vcpu_timer.c index ae53133c7ab0..a2cd277a4059 100644 --- a/arch/riscv/kvm/vcpu_timer.c +++ b/arch/riscv/kvm/vcpu_timer.c @@ -61,10 +61,13 @@ static enum hrtimer_restart kvm_riscv_vcpu_hrtimer_expired(struct hrtimer *h) static int kvm_riscv_vcpu_timer_cancel(struct kvm_vcpu_timer *t) { - if (!t->init_done || !t->next_set) + if (!t->init_done) return -EINVAL; hrtimer_cancel(&t->hrt); + + if (!t->next_set) + return -EINVAL; t->next_set = false; return 0; From 52c6b7d20d3e791a9e75aa2be2a467990154c7cd Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Tue, 4 Aug 2026 21:40:18 +0800 Subject: [PATCH 1718/2366] RISC-V: KVM: Fix the conversion between vsip and hvip Per AIA spec 1.0 Section 6.3.2, the interrupt numbers 13-63 shares same bit position between related VS shadow CSRs and hypervisor CSRs. So there's a shift only for SSI, STI and SEI interrupt. Currently the KVM always does a shift for all the interrupts (include LCOFI with number 13) when doing the conversion between vsip and hvip. Fix this by only doing shift the SSI, STI and SEI. Add wrappers for doing the conversion between vsip and hvip. Fixes: 16b0bde9a37c ("RISC-V: KVM: Add perf sampling support for guests") Signed-off-by: Yicong Yang Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260804134018.85497-1-yang.yicong@picoheart.com Signed-off-by: Anup Patel --- arch/riscv/include/asm/csr.h | 20 ++++++++++++++++---- arch/riscv/kvm/vcpu.c | 3 +-- arch/riscv/kvm/vcpu_onereg.c | 8 +++----- tools/arch/riscv/include/asm/csr.h | 20 ++++++++++++++++---- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h index 6c823361be86..621f84d1898f 100644 --- a/arch/riscv/include/asm/csr.h +++ b/arch/riscv/include/asm/csr.h @@ -188,12 +188,24 @@ #define HGATP_MODE_SHIFT HGATP32_MODE_SHIFT #endif -/* VSIP & HVIP relation */ +/* + * VSIP & HVIP relation + * + * The bit positions are same between VSIP and HVIP for interrupt + * numbers 13-63, where there's a shift for the SSI, STI and SEI. + */ #define VSIP_TO_HVIP_SHIFT (IRQ_VS_SOFT - IRQ_S_SOFT) -#define VSIP_VALID_MASK ((_AC(1, UL) << IRQ_S_SOFT) | \ +#define VSIP_BIAS_MASK ((_AC(1, UL) << IRQ_S_SOFT) | \ (_AC(1, UL) << IRQ_S_TIMER) | \ - (_AC(1, UL) << IRQ_S_EXT) | \ - (_AC(1, UL) << IRQ_PMU_OVF)) + (_AC(1, UL) << IRQ_S_EXT)) +#define VSIP_NO_BIAS_MASK (_AC(1, UL) << IRQ_PMU_OVF) +#define VSIP_VALID_MASK (VSIP_BIAS_MASK | VSIP_NO_BIAS_MASK) +#define vsip_to_hvip(_vsip) ((((_vsip) & VSIP_BIAS_MASK) << \ + VSIP_TO_HVIP_SHIFT) | \ + ((_vsip) & VSIP_NO_BIAS_MASK)) +#define hvip_to_vsip(_hvip) ((((_hvip) >> VSIP_TO_HVIP_SHIFT) & \ + VSIP_BIAS_MASK) | \ + ((_hvip) & VSIP_NO_BIAS_MASK)) /* AIA CSR bits */ #define TOPI_IID_SHIFT 16 diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c index e062ca19f9d8..7f1636c44d41 100644 --- a/arch/riscv/kvm/vcpu.c +++ b/arch/riscv/kvm/vcpu.c @@ -491,8 +491,7 @@ bool kvm_riscv_vcpu_has_interrupts(struct kvm_vcpu *vcpu, u64 mask) bool ret; raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); - ie = ((vcpu->arch.guest_csr.vsie & VSIP_VALID_MASK) - << VSIP_TO_HVIP_SHIFT) & (unsigned long)mask; + ie = vsip_to_hvip(vcpu->arch.guest_csr.vsie) & (unsigned long)mask; ie |= vcpu->arch.guest_csr.vsie & ~IRQ_LOCAL_MASK & (unsigned long)mask; ret = vcpu->arch.irqs_pending[0] & ie; diff --git a/arch/riscv/kvm/vcpu_onereg.c b/arch/riscv/kvm/vcpu_onereg.c index 841f2cf87168..f16e25098af1 100644 --- a/arch/riscv/kvm/vcpu_onereg.c +++ b/arch/riscv/kvm/vcpu_onereg.c @@ -272,7 +272,7 @@ static int kvm_riscv_vcpu_general_get_csr(struct kvm_vcpu *vcpu, if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) { kvm_riscv_vcpu_flush_interrupts(vcpu); - *out_val = (csr->hvip >> VSIP_TO_HVIP_SHIFT) & VSIP_VALID_MASK; + *out_val = hvip_to_vsip(csr->hvip); *out_val |= csr->hvip & ~IRQ_LOCAL_MASK; } else *out_val = ((unsigned long *)csr)[reg_num]; @@ -293,10 +293,8 @@ static int kvm_riscv_vcpu_general_set_csr(struct kvm_vcpu *vcpu, reg_num = array_index_nospec(reg_num, regs_max); - if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) { - reg_val &= VSIP_VALID_MASK; - reg_val <<= VSIP_TO_HVIP_SHIFT; - } + if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) + reg_val = vsip_to_hvip(reg_val); ((unsigned long *)csr)[reg_num] = reg_val; diff --git a/tools/arch/riscv/include/asm/csr.h b/tools/arch/riscv/include/asm/csr.h index 21d8cee04638..8df64314d613 100644 --- a/tools/arch/riscv/include/asm/csr.h +++ b/tools/arch/riscv/include/asm/csr.h @@ -163,12 +163,24 @@ #define HGATP_MODE_SHIFT HGATP32_MODE_SHIFT #endif -/* VSIP & HVIP relation */ +/* + * VSIP & HVIP relation + * + * The bit positions are same between VSIP and HVIP for interrupt + * numbers 13-63, where there's a shift for the SSI, STI and SEI. + */ #define VSIP_TO_HVIP_SHIFT (IRQ_VS_SOFT - IRQ_S_SOFT) -#define VSIP_VALID_MASK ((_AC(1, UL) << IRQ_S_SOFT) | \ +#define VSIP_BIAS_MASK ((_AC(1, UL) << IRQ_S_SOFT) | \ (_AC(1, UL) << IRQ_S_TIMER) | \ - (_AC(1, UL) << IRQ_S_EXT) | \ - (_AC(1, UL) << IRQ_PMU_OVF)) + (_AC(1, UL) << IRQ_S_EXT)) +#define VSIP_NO_BIAS_MASK (_AC(1, UL) << IRQ_PMU_OVF) +#define VSIP_VALID_MASK (VSIP_BIAS_MASK | VSIP_NO_BIAS_MASK) +#define vsip_to_hvip(_vsip) ((((_vsip) & VSIP_BIAS_MASK) << \ + VSIP_TO_HVIP_SHIFT) | \ + ((_vsip) & VSIP_NO_BIAS_MASK)) +#define hvip_to_vsip(_hvip) ((((_hvip) >> VSIP_TO_HVIP_SHIFT) & \ + VSIP_BIAS_MASK) | \ + ((_hvip) & VSIP_NO_BIAS_MASK)) /* AIA CSR bits */ #define TOPI_IID_SHIFT 16 From 8ae12ccaec6ec74945d8c1ef39f2c1b8df779abc Mon Sep 17 00:00:00 2001 From: Xie Bo Date: Mon, 10 Aug 2026 09:21:15 +0800 Subject: [PATCH 1719/2366] RISC-V: KVM: Serialize IMSIC attributes with vCPU migration KVM device ioctls are not serialized against KVM_RUN. As a result, kvm_riscv_aia_imsic_rw_attr() can snapshot the physical CPU and HGEI of an IMSIC VS-file before a concurrent vCPU migration releases it. The HGEI can then be allocated to another vCPU before imsic_vsfile_rw() uses the stale tuple. A GET or SET attribute may consequently access the new owner's interrupt file. Serialize the entire IMSIC attribute operation with the target vCPU mutex. This prevents the VS-file from being migrated and recycled until the attribute access completes. Acquire the mutex killably so that the device ioctl remains interruptible while waiting for KVM_RUN to finish. Fixes: db8b7e97d613 ("RISC-V: KVM: Add in-kernel virtualization of AIA IMSIC") Cc: stable@vger.kernel.org Signed-off-by: Xie Bo Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260810-imsic-attr-race-v2-1-00ed95ad321e@ultrarisc.com Signed-off-by: Anup Patel --- arch/riscv/kvm/aia_imsic.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/arch/riscv/kvm/aia_imsic.c b/arch/riscv/kvm/aia_imsic.c index c1af23e79ae0..f17dc559a709 100644 --- a/arch/riscv/kvm/aia_imsic.c +++ b/arch/riscv/kvm/aia_imsic.c @@ -965,9 +965,14 @@ int kvm_riscv_aia_imsic_rw_attr(struct kvm *kvm, unsigned long type, if (!vcpu) return -ENODEV; + if (mutex_lock_killable(&vcpu->mutex)) + return -EINTR; + imsic = vcpu->arch.aia_context.imsic_state; - if (!imsic) - return -ENODEV; + if (!imsic) { + rc = -ENODEV; + goto out_unlock; + } isel = KVM_DEV_RISCV_AIA_IMSIC_GET_ISEL(type); read_lock_irqsave(&imsic->vsfile_lock, flags); @@ -991,6 +996,8 @@ int kvm_riscv_aia_imsic_rw_attr(struct kvm *kvm, unsigned long type, rc = imsic_vsfile_rw(vsfile_hgei, vsfile_cpu, imsic->nr_eix, isel, write, val); +out_unlock: + mutex_unlock(&vcpu->mutex); return rc; } From ed54fdb460a6e81d5f8f38388d1f10b385dd4a58 Mon Sep 17 00:00:00 2001 From: Xie Bo Date: Mon, 10 Aug 2026 13:15:43 +0800 Subject: [PATCH 1720/2366] RISC-V: KVM: Release unused page after MMU invalidation If an MMU invalidation races with a G-stage fault, the fault handler skips installing the page but leaves ret set to zero. As a result, kvm_release_faultin_page() treats the page as used and can unnecessarily mark it dirty. Track the invalidation retry separately and release the page as unused, while preserving the existing return value so that the vCPU retries the fault. Fixes: 2ed90cb0938a ("KVM: RISC-V: Retry fault if vma_lookup() results become invalid") Cc: stable@vger.kernel.org Signed-off-by: Xie Bo Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260810051544.3953925-2-xb@ultrarisc.com Signed-off-by: Anup Patel --- arch/riscv/kvm/mmu.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c index 6035b5ec9503..d189fd58d7bf 100644 --- a/arch/riscv/kvm/mmu.c +++ b/arch/riscv/kvm/mmu.c @@ -627,6 +627,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, int ret; kvm_pfn_t hfn; bool is_hugetlb; + bool unused = false; bool writable; unsigned int vma_pageshift; gfn_t gfn = gpa >> PAGE_SHIFT; @@ -719,8 +720,10 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, write_lock(&kvm->mmu_lock); - if (mmu_invalidate_retry(kvm, mmu_seq)) + if (mmu_invalidate_retry(kvm, mmu_seq)) { + unused = true; goto out_unlock; + } /* * Check if we are backed by a THP and thus use block mapping if @@ -743,7 +746,8 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, kvm_err("Failed to map in G-stage\n"); out_unlock: - kvm_release_faultin_page(kvm, page, ret && ret != -EEXIST, writable); + kvm_release_faultin_page(kvm, page, + unused || (ret && ret != -EEXIST), writable); write_unlock(&kvm->mmu_lock); return ret; } From f41fb17143df890855d3de980f8eb92dfd595817 Mon Sep 17 00:00:00 2001 From: Xie Bo Date: Mon, 10 Aug 2026 13:15:44 +0800 Subject: [PATCH 1721/2366] RISC-V: KVM: Propagate interrupted G-stage faults __kvm_faultin_pfn() reports an interrupted host page fault with KVM_PFN_ERR_SIGPENDING. RISC-V currently handles it as a generic error PFN and returns -EFAULT. Return -EINTR for the signal-pending sentinel so callers can distinguish an interrupted fault from an invalid userspace mapping. Do not log the expected interruption as a vCPU exit error. Fixes: 9d05c1fee837 ("RISC-V: KVM: Implement stage2 page table programming") Cc: stable@vger.kernel.org Signed-off-by: Xie Bo Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260810051544.3953925-3-xb@ultrarisc.com Signed-off-by: Anup Patel --- arch/riscv/kvm/mmu.c | 2 ++ arch/riscv/kvm/vcpu_exit.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c index d189fd58d7bf..3e955d808743 100644 --- a/arch/riscv/kvm/mmu.c +++ b/arch/riscv/kvm/mmu.c @@ -708,6 +708,8 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, vma_pageshift, current); return 0; } + if (is_sigpending_pfn(hfn)) + return -EINTR; if (is_error_noslot_pfn(hfn)) return -EFAULT; diff --git a/arch/riscv/kvm/vcpu_exit.c b/arch/riscv/kvm/vcpu_exit.c index 88e0c369b354..8d36eb8abce1 100644 --- a/arch/riscv/kvm/vcpu_exit.c +++ b/arch/riscv/kvm/vcpu_exit.c @@ -283,7 +283,7 @@ int kvm_riscv_vcpu_exit(struct kvm_vcpu *vcpu, struct kvm_run *run, } /* Print details in-case of error */ - if (ret < 0) { + if (ret < 0 && ret != -EINTR) { kvm_err("VCPU exit error %d\n", ret); kvm_err("SEPC=0x%lx SSTATUS=0x%lx HSTATUS=0x%lx\n", vcpu->arch.guest_context.sepc, From b3d346838ec65fac7fd83f5dbcedd13cadfffddb Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Wed, 26 Aug 2026 15:50:09 +0800 Subject: [PATCH 1722/2366] KVM: riscv: Fix NACL hfence entry update order The SBI v3.0 specification (section 15.1.2) requires a nested HFENCE entry to be populated as follows: 1) find an unused entry with Config.Pending == 0 2) update the Page_Number and Page_Count words 3) update the Config word with Config.Pending set __kvm_riscv_nacl_hfence() writes the Config word first, so the SBI implementation (or NACL hardware) can observe a pending entry with pnum/pcount values left over from the previous use of that entry, resulting in incorrect TLB flush ranges. Write pnum and pcount first and the Config word last. Since the consumer is an external agent on coherent shared memory, use WRITE_ONCE() to stop the compiler from reordering the stores and smp_wmb() to make the parameter words globally visible before the Pending bit is set. Fixes: d466c19cead5 ("RISC-V: KVM: Add common nested acceleration support") Signed-off-by: Zongmin Zhou Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260826075009.68952-1-min_halo@163.com Signed-off-by: Anup Patel --- arch/riscv/kvm/nacl.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/arch/riscv/kvm/nacl.c b/arch/riscv/kvm/nacl.c index 9aff03c4f667..a5cda9a65156 100644 --- a/arch/riscv/kvm/nacl.c +++ b/arch/riscv/kvm/nacl.c @@ -42,12 +42,24 @@ void __kvm_riscv_nacl_hfence(void *shmem, } } - entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_CONFIG(i); - *entp = cpu_to_lelong(control); + /* + * Per SBI v3.0 section 15.1.2, the Page_Number and Page_Count + * words must be updated before the Config word with its Pending + * bit set. WRITE_ONCE() stops the compiler from reordering the + * stores and smp_wmb() makes the parameter words globally + * visible to the SBI implementation (or NACL hardware) before + * the Pending bit is set. + */ entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_PNUM(i); - *entp = cpu_to_lelong(page_num); + WRITE_ONCE(*entp, cpu_to_lelong(page_num)); entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_PCOUNT(i); - *entp = cpu_to_lelong(page_count); + WRITE_ONCE(*entp, cpu_to_lelong(page_count)); + + /* Ensure the parameter words are visible before the Pending bit */ + smp_wmb(); + + entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_CONFIG(i); + WRITE_ONCE(*entp, cpu_to_lelong(control)); } int kvm_riscv_nacl_enable(void) From b7749531a9b195f4fd7db92ca3cc49bda1a4dc8d Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Wed, 26 Aug 2026 14:41:17 +0800 Subject: [PATCH 1723/2366] RISC-V: KVM: Fix sdata leak and stale snapshot_addr in snapshot_set_shmem A guest may call SBI_PMU_SNAPSHOT_SET_SHMEM repeatedly. Each call overwrites kvpmu->sdata without freeing the old buffer (memory leak), and if a later kvm_vcpu_write_guest() fails, the error path frees sdata but leaves snapshot_addr stale. A subsequent SBI_PMU_COUNTER_START then passes the INVALID_GPA check and crashes the host with a NULL buffer in kvm_vcpu_read_guest(). Fix this by clearing the previously installed snapshot area before installing a new one, which keeps sdata and snapshot_addr consistent. The SBI spec suggests a single invocation but defines no error code for repeated calls, so KVM must tolerate them. Fixes: c2f41ddbcdd756 ("RISC-V: KVM: Implement SBI PMU Snapshot feature") Cc: stable@vger.kernel.org Signed-off-by: Zongmin Zhou Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260826064117.58029-1-min_halo@163.com Signed-off-by: Anup Patel --- arch/riscv/kvm/vcpu_pmu.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/riscv/kvm/vcpu_pmu.c b/arch/riscv/kvm/vcpu_pmu.c index 6ff741ee7803..cc0c138651bc 100644 --- a/arch/riscv/kvm/vcpu_pmu.c +++ b/arch/riscv/kvm/vcpu_pmu.c @@ -454,6 +454,14 @@ int kvm_riscv_vcpu_pmu_snapshot_set_shmem(struct kvm_vcpu *vcpu, unsigned long s } } + /* + * Clear any previously installed snapshot area to avoid leaking + * the old sdata and to keep sdata/snapshot_addr consistent if + * the re-install fails below. + */ + if (kvpmu->snapshot_addr != INVALID_GPA) + kvm_pmu_clear_snapshot_area(vcpu); + kvpmu->sdata = kzalloc(snapshot_area_size, GFP_ATOMIC | __GFP_ACCOUNT); if (!kvpmu->sdata) { sbiret = SBI_ERR_FAILURE; From 8b3fd1a8b305321171602bfa7c41212441cf69e4 Mon Sep 17 00:00:00 2001 From: SeungJu Cheon Date: Tue, 25 Aug 2026 17:37:17 +0900 Subject: [PATCH 1724/2366] RISC-V: KVM: Preserve firmware counter value across stop/start Firmware events accumulate in kvpmu->fw_event[].value while running, but counter stop only clears fw_event[].started without saving the value back to pmc->counter_val. A subsequent counter start without SBI_PMU_START_FLAG_SET_INIT_VALUE reloads the stale counter_val into fw_event[].value, losing all events counted so far. Save fw_event[].value into counter_val when actually stopping a running counter, and remove the now redundant synchronization from the snapshot path. Fixes: badc386869e2c ("RISC-V: KVM: Support firmware events") Signed-off-by: SeungJu Cheon Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260825083719.643970-2-suunj1331@gmail.com Signed-off-by: Anup Patel --- arch/riscv/kvm/vcpu_pmu.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/riscv/kvm/vcpu_pmu.c b/arch/riscv/kvm/vcpu_pmu.c index cc0c138651bc..5dfe2054d12b 100644 --- a/arch/riscv/kvm/vcpu_pmu.c +++ b/arch/riscv/kvm/vcpu_pmu.c @@ -683,10 +683,12 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, goto out; } - if (!kvpmu->fw_event[fevent_code].started) + if (!kvpmu->fw_event[fevent_code].started) { sbiret = SBI_ERR_ALREADY_STOPPED; - - kvpmu->fw_event[fevent_code].started = false; + } else { + kvpmu->fw_event[fevent_code].started = false; + pmc->counter_val = kvpmu->fw_event[fevent_code].value; + } } else if (pmc->perf_event) { if (pmc->started) { /* Stop counting the counter */ @@ -704,9 +706,7 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, } if (snap_flag_set && !sbiret) { - if (pmc->cinfo.type == SBI_PMU_CTR_TYPE_FW) - pmc->counter_val = kvpmu->fw_event[fevent_code].value; - else if (pmc->perf_event) + if (pmc->perf_event) pmc->counter_val += perf_event_read_value(pmc->perf_event, &enabled, &running); /* From 057dd2639ceae79adced5d8fe52c32d562edcb3a Mon Sep 17 00:00:00 2001 From: SeungJu Cheon Date: Tue, 25 Aug 2026 17:37:18 +0900 Subject: [PATCH 1725/2366] RISC-V: KVM: Report snapshot write failure to the guest If kvm_vcpu_write_guest() fails while updating the PMU snapshot area on counter stop, the guest may receive SBI_SUCCESS without the snapshot being updated, leaving stale data in shared memory. Return SBI_ERR_FAILURE when the snapshot write fails. Fixes: c2f41ddbcdd7 ("RISC-V: KVM: Implement SBI PMU Snapshot feature") Signed-off-by: SeungJu Cheon Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260825083719.643970-3-suunj1331@gmail.com Signed-off-by: Anup Patel --- arch/riscv/kvm/vcpu_pmu.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/riscv/kvm/vcpu_pmu.c b/arch/riscv/kvm/vcpu_pmu.c index 5dfe2054d12b..8be87f879473 100644 --- a/arch/riscv/kvm/vcpu_pmu.c +++ b/arch/riscv/kvm/vcpu_pmu.c @@ -735,9 +735,10 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, } } - if (shmem_needs_update) - kvm_vcpu_write_guest(vcpu, kvpmu->snapshot_addr, kvpmu->sdata, - sizeof(struct riscv_pmu_snapshot_data)); + if (shmem_needs_update && + kvm_vcpu_write_guest(vcpu, kvpmu->snapshot_addr, kvpmu->sdata, + sizeof(struct riscv_pmu_snapshot_data))) + sbiret = SBI_ERR_FAILURE; out: retdata->err_val = sbiret; From c7e2cc38c56142cdab25e6f73602a8222bf9479b Mon Sep 17 00:00:00 2001 From: SeungJu Cheon Date: Tue, 25 Aug 2026 17:37:19 +0900 Subject: [PATCH 1726/2366] RISC-V: KVM: Fix perf-backed counter accounting across stop and read pmu_ctr_read() adds the event count returned by perf_event_read_value() to counter_val, which can accumulate the same count repeatedly across reads. kvm_riscv_vcpu_pmu_ctr_stop() also leaves counter_val stale by not folding the current event count into it. Make reads of perf-backed counters side-effect free, and use perf_event_pause() when stopping a counter to fold the current event count into counter_val while resetting it. This preserves the counter value across stop/start and lets the snapshot path use counter_val directly. Fixes: 0cb74b65d2e5 ("RISC-V: KVM: Implement perf support without sampling") Signed-off-by: SeungJu Cheon Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260825083719.643970-4-suunj1331@gmail.com Signed-off-by: Anup Patel --- arch/riscv/kvm/vcpu_pmu.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/arch/riscv/kvm/vcpu_pmu.c b/arch/riscv/kvm/vcpu_pmu.c index 8be87f879473..e095d1ff439f 100644 --- a/arch/riscv/kvm/vcpu_pmu.c +++ b/arch/riscv/kvm/vcpu_pmu.c @@ -270,12 +270,13 @@ static int pmu_ctr_read(struct kvm_vcpu *vcpu, unsigned long cidx, return -EINVAL; pmc->counter_val = kvpmu->fw_event[fevent_code].value; + *out_val = pmc->counter_val; } else if (pmc->perf_event) { - pmc->counter_val += perf_event_read_value(pmc->perf_event, &enabled, &running); + *out_val = pmc->counter_val + + perf_event_read_value(pmc->perf_event, &enabled, &running); } else { return -EINVAL; } - *out_val = pmc->counter_val; return 0; } @@ -653,7 +654,6 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, { struct kvm_pmu *kvpmu = vcpu_to_pmu(vcpu); int i, pmc_index, sbiret = 0; - u64 enabled, running; struct kvm_pmc *pmc; int fevent_code; bool snap_flag_set = flags & SBI_PMU_STOP_FLAG_TAKE_SNAPSHOT; @@ -691,8 +691,11 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, } } else if (pmc->perf_event) { if (pmc->started) { - /* Stop counting the counter */ - perf_event_disable(pmc->perf_event); + /* + * Stop the counter and fold the live count into counter_val. + * Reset the event value to avoid redundant accumulation. + */ + pmc->counter_val += perf_event_pause(pmc->perf_event, true); pmc->started = false; } else { sbiret = SBI_ERR_ALREADY_STOPPED; @@ -706,9 +709,6 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, } if (snap_flag_set && !sbiret) { - if (pmc->perf_event) - pmc->counter_val += perf_event_read_value(pmc->perf_event, - &enabled, &running); /* * The counter and overflow indices in the snapshot region are w.r.to * cbase. Modify the set bit in the counter mask instead of the pmc_index From 41e81f7e3ef96594fb840445343c0ee7723aa550 Mon Sep 17 00:00:00 2001 From: Tan Chi Date: Mon, 14 Sep 2026 11:11:46 +0800 Subject: [PATCH 1727/2366] RISC-V: KVM: Fix HSM hart status error propagation kvm_sbi_hsm_vcpu_get_status() returns SBI_ERR_INVALID_PARAM when the requested hart does not exist. However, the HART_STATUS case returns from the SBI handler without storing this error in retdata->err_val. As a result, a guest querying the status of a non-existent hart observes SBI_SUCCESS instead of SBI_ERR_INVALID_PARAM. Use the common SBI error handling path for HART_STATUS after saving a valid hart state in retdata->out_val. This preserves the returned error when kvm_sbi_hsm_vcpu_get_status() fails. Fixes: bae0dfd74e01 ("RISC-V: KVM: Modify SBI extension handler to return SBI error code") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Tan Chi Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260914031146.446157-1-tanchi25@mails.ucas.ac.cn Signed-off-by: Anup Patel --- arch/riscv/kvm/vcpu_sbi_hsm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/riscv/kvm/vcpu_sbi_hsm.c b/arch/riscv/kvm/vcpu_sbi_hsm.c index f26207f84bab..06a15629c26b 100644 --- a/arch/riscv/kvm/vcpu_sbi_hsm.c +++ b/arch/riscv/kvm/vcpu_sbi_hsm.c @@ -95,9 +95,9 @@ static int kvm_sbi_ext_hsm_handler(struct kvm_vcpu *vcpu, struct kvm_run *run, ret = kvm_sbi_hsm_vcpu_get_status(vcpu); if (ret >= 0) { retdata->out_val = ret; - retdata->err_val = 0; + ret = 0; } - return 0; + break; case SBI_EXT_HSM_HART_SUSPEND: switch (lower_32_bits(cp->a0)) { case SBI_HSM_SUSPEND_RET_DEFAULT: From f4fafaf02174c32bce2f9bb4196fadf13f1fd96e Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 9 Sep 2026 09:58:22 +0900 Subject: [PATCH 1728/2366] ksmbd: fix partial normalized name responses Windows may request FILE_NORMALIZED_NAME_INFORMATION with an output buffer that only fits the fixed portion of the variable-length response. Treat the fixed portion as FILE_NORMALIZED_NAME_INFORMATION_SIZE so ksmbd returns STATUS_BUFFER_OVERFLOW instead of STATUS_INFO_LENGTH_MISMATCH. This avoids rejecting valid partial normalized-name responses. Fixes: 6b8b79226bc3 ("ksmbd: fix partial file information responses") Reported-by: Mobin Aydinfar Reviewed-by: ChenXiaoSong Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 3 +++ fs/smb/server/smb2pdu.h | 1 + 2 files changed, 4 insertions(+) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 8acc5174530b..0436b7c898b1 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -7312,6 +7312,9 @@ static int smb2_get_info_file(struct ksmbd_work *work, case FILE_ALTERNATE_NAME_INFORMATION: fixed_len = FILE_ALTERNATE_NAME_INFORMATION_SIZE; break; + case FILE_NORMALIZED_NAME_INFORMATION: + fixed_len = FILE_NORMALIZED_NAME_INFORMATION_SIZE; + break; case FILE_STREAM_INFORMATION: fixed_len = FILE_STREAM_INFORMATION_SIZE; break; diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h index a6200d8630e2..ca8e27f7b712 100644 --- a/fs/smb/server/smb2pdu.h +++ b/fs/smb/server/smb2pdu.h @@ -212,6 +212,7 @@ struct file_sparse { #define FILE_ALLOCATION_INFORMATION_SIZE 19 #define FILE_END_OF_FILE_INFORMATION_SIZE 20 #define FILE_ALTERNATE_NAME_INFORMATION_SIZE 8 +#define FILE_NORMALIZED_NAME_INFORMATION_SIZE 8 #define FILE_STREAM_INFORMATION_SIZE 32 #define FILE_PIPE_INFORMATION_SIZE 23 #define FILE_PIPE_LOCAL_INFORMATION_SIZE 24 From 9fa26285ae70ac2d3d1b47459a6b4463ab053e1c Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 9 Sep 2026 09:58:48 +0900 Subject: [PATCH 1729/2366] ksmbd: keep compound responses on query info errors Do not reset the RFC1002 length of the complete response when a query info buffer is too small. The current command will add its error response through ksmbd_iov_pin_rsp(), while resetting the base length can truncate earlier responses in a compound request. This lets ksmbd return the earlier responses and the query-info error response together. Remove the now-unused rsp_org parameter from the pipe query-info helpers. Fixes: e2b76ab8b5c9 ("ksmbd: add support for read compound") Reported-by: Mobin Aydinfar Reviewed-by: ChenXiaoSong Signed-off-by: Namjae Jeon --- fs/smb/server/smb2pdu.c | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 0436b7c898b1..6b8809f67b92 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -6275,21 +6275,18 @@ int smb2_query_dir(struct ksmbd_work *work) * @reqOutputBufferLength: max buffer length expected in command response * @fixed_len: minimum fixed response length * @rsp: query info response buffer contains output buffer length - * @rsp_org: base response buffer pointer in case of chained response * * Return: 0 on success, otherwise error */ static int buffer_check_err(int reqOutputBufferLength, unsigned int fixed_len, - struct smb2_query_info_rsp *rsp, - void *rsp_org) + struct smb2_query_info_rsp *rsp) { unsigned int output_len = le32_to_cpu(rsp->OutputBufferLength); if (reqOutputBufferLength < fixed_len) { pr_err("Invalid Buffer Size Requested\n"); rsp->hdr.Status = STATUS_INFO_LENGTH_MISMATCH; - *(__be32 *)rsp_org = cpu_to_be32(sizeof(struct smb2_hdr)); return -EINVAL; } @@ -6300,8 +6297,7 @@ static int buffer_check_err(int reqOutputBufferLength, return 0; } -static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp, - void *rsp_org) +static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp) { struct smb2_file_standard_info *sinfo; @@ -6316,8 +6312,7 @@ static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp, cpu_to_le32(sizeof(struct smb2_file_standard_info)); } -static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num, - void *rsp_org) +static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num) { struct smb2_file_internal_info *file_info; @@ -6331,8 +6326,7 @@ static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num, static int smb2_get_info_file_pipe(struct ksmbd_session *sess, struct smb2_query_info_req *req, - struct smb2_query_info_rsp *rsp, - void *rsp_org) + struct smb2_query_info_rsp *rsp) { u64 id; int rc; @@ -6357,16 +6351,16 @@ static int smb2_get_info_file_pipe(struct ksmbd_session *sess, switch (req->FileInfoClass) { case FILE_STANDARD_INFORMATION: - get_standard_info_pipe(rsp, rsp_org); + get_standard_info_pipe(rsp); rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), le32_to_cpu(rsp->OutputBufferLength), - rsp, rsp_org); + rsp); break; case FILE_INTERNAL_INFORMATION: - get_internal_info_pipe(rsp, id, rsp_org); + get_internal_info_pipe(rsp, id); rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), le32_to_cpu(rsp->OutputBufferLength), - rsp, rsp_org); + rsp); break; default: ksmbd_debug(SMB, "smb2_info_file_pipe for %u not supported\n", @@ -7202,8 +7196,7 @@ static int smb2_get_info_file(struct ksmbd_work *work, if (test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_PIPE)) { /* smb2 info file called for pipe */ - rc = smb2_get_info_file_pipe(work->sess, req, rsp, - work->response_buf); + rc = smb2_get_info_file_pipe(work->sess, req, rsp); goto iov_pin_out; } @@ -7321,7 +7314,7 @@ static int smb2_get_info_file(struct ksmbd_work *work, } rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), fixed_len, - rsp, work->response_buf); + rsp); } ksmbd_fd_put(work, fp); @@ -7592,7 +7585,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, } rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), fixed_len, - rsp, work->response_buf); + rsp); path_put(&path); if (!rc) @@ -7706,7 +7699,7 @@ static int smb2_get_info_sec(struct ksmbd_work *work, rsp->OutputBufferLength = cpu_to_le32(secdesclen); rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength), le32_to_cpu(rsp->OutputBufferLength), - rsp, work->response_buf); + rsp); if (rc) goto err_out; From 075bc7b1d3dde5ed43fbaabbc1a69f09b7fc3a47 Mon Sep 17 00:00:00 2001 From: Baineng Shou Date: Thu, 10 Sep 2026 10:16:52 +0800 Subject: [PATCH 1730/2366] dmaengine: mmp_pdma: fix wrong sg length in mmp_pdma_prep_slave_sg() In mmp_pdma_prep_slave_sg(), for_each_sg() iterates the scatterlist putting each entry into 'sg', but the entry length is read from 'sgl' (the list head) instead of 'sg' (the current entry): for_each_sg(sgl, sg, sg_len, i) { addr = sg_dma_address(sg); avail = sg_dma_len(sgl); /* should be 'sg' */ Consequently 'avail' is always the length of the first entry. For multi-sg lists this causes out-of-bounds reads when a later entry is shorter than the first, and silent data loss when it is longer. Single-sg or uniformly-sized lists happen to mask the issue. Fixes: c8acd6aa6bed3 ("dmaengine: mmp-pdma support") Signed-off-by: Baineng Shou Reviewed-by: Frank Li Link: https://patch.msgid.link/20260910021652.1296640-1-shoubaineng@gmail.com Signed-off-by: Vinod Koul --- drivers/dma/mmp_pdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/mmp_pdma.c b/drivers/dma/mmp_pdma.c index 78e3e07e681d..ed520737882b 100644 --- a/drivers/dma/mmp_pdma.c +++ b/drivers/dma/mmp_pdma.c @@ -712,7 +712,7 @@ mmp_pdma_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl, for_each_sg(sgl, sg, sg_len, i) { addr = sg_dma_address(sg); - avail = sg_dma_len(sgl); + avail = sg_dma_len(sg); do { len = min_t(size_t, avail, PDMA_MAX_DESC_BYTES); From 095858324f063dba830041f067872f0a08765d2f Mon Sep 17 00:00:00 2001 From: Hao-Qun Huang Date: Sun, 13 Sep 2026 03:20:49 +0800 Subject: [PATCH 1731/2366] spi: virtio: Use the per-transfer bits per word virtio_spi_transfer_one() puts spi->bits_per_word into the request header, so a transfer that sets its own word size reaches the backend with the device default instead. The SPI core has already copied that default into xfer->bits_per_word when the transfer leaves it at zero, the same way it does for xfer->speed_hz, which this function already uses. Per-transfer word sizes are ordinary SPI usage. mipi_dbi, for one, sends a 9-bit command and reads the reply as 8-bit data in the same message. With a 16-bit device default, a one-byte transfer asking for 8 bits goes out as a partial 16-bit word, which the backend may reject. Fixes: f98cabe3f6cf ("SPI: Add virtio SPI driver") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hao-Qun Huang Link: https://patch.msgid.link/20260913032049.11209.alvinhuang0603@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-virtio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-virtio.c b/drivers/spi/spi-virtio.c index 2256dfec5407..3e181bd8bc94 100644 --- a/drivers/spi/spi-virtio.c +++ b/drivers/spi/spi-virtio.c @@ -168,7 +168,7 @@ static int virtio_spi_transfer_one(struct spi_controller *ctrl, /* Fill struct spi_transfer_head */ th->chip_select_id = spi_get_chipselect(spi, 0); - th->bits_per_word = spi->bits_per_word; + th->bits_per_word = xfer->bits_per_word; th->cs_change = xfer->cs_change; th->tx_nbits = xfer->tx_nbits; th->rx_nbits = xfer->rx_nbits; From 9a0b159ff18c8f6fcf982bb81e15a9ceb14db43a Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Mon, 14 Sep 2026 22:12:34 -1000 Subject: [PATCH 1732/2366] sched_ext: scx_qmap: Restore unused idle claims from ops.dispatch() scx_qmap tracks idle cids itself. pick_direct_dispatch_cid() claims a cid by clearing its bit and the task is inserted into that cid's local DSQ, which kicks the CPU. When the task does not arrive, for example because the insert fell back to the global DSQ after an affinity change, the CPU wakes, finds nothing and picks idle again. That is not an idle transition, so ops.update_idle() is not called and the cid stays marked busy until an unrelated task runs on it. Restore the claim from ops.dispatch(). The kick guarantees a dispatch on the kicked CPU, and when it finds nothing to run with a NULL @prev, the CPU is going back to idle. Document the pattern in ops.update_idle(), which reports only actual transitions. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi Cc: Andrea Righi --- kernel/sched/ext/internal.h | 6 ++++++ tools/sched_ext/scx_qmap.bpf.c | 14 ++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 076a351bb3f2..0adaf649d5e0 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -572,6 +572,12 @@ struct sched_ext_ops { * * Specify the %SCX_OPS_KEEP_BUILTIN_IDLE flag to keep the built-in idle * tracking. + * + * Only actual transitions are reported. A CPU that is claimed with an + * idle pick and kicked but dispatches no task returns to idle without a + * transition. A scheduler tracking idle CPUs itself must restore the + * idle state from ops.dispatch() when it returns without the next task + * to run. */ void (*update_idle)(s32 cpu, bool idle); diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index bda56c37acb5..67b7c01cae55 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -818,10 +818,10 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cid, struct task_struct *prev) batch--; cpuc->dsp_cnt--; if (!batch || !scx_bpf_dispatch_nr_slots()) { - if (scan_shared_dsq(false)) + if (scan_shared_dsq(false) || + scx_bpf_dsq_move_to_local(SHARED_DSQ, needs_immed(cid))) return; - scx_bpf_dsq_move_to_local(SHARED_DSQ, needs_immed(cid)); - return; + goto prev; } if (!cpuc->dsp_cnt) break; @@ -832,10 +832,14 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cid, struct task_struct *prev) if (scan_shared_dsq(false)) return; - +prev: /* * No other tasks. @prev will keep running. Update its core_sched_seq as * if the task were enqueued and dispatched immediately. + * + * No @prev to keep running means the CPU goes idle. If its claim was + * never used, that is not a transition and ops.update_idle() stays + * silent. Restore the claim here. */ if (prev) { taskc = lookup_task_ctx(prev); @@ -844,6 +848,8 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cid, struct task_struct *prev) taskc->core_sched_seq = qa.core_sched_tail_seqs[weight_to_idx(prev->scx.weight)]++; + } else { + cmask_set(cid, &qa.idle_cids.mask); } } From a9e3760b0838299649c0d57cca44daaf40ba3c33 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Mon, 14 Sep 2026 22:12:34 -1000 Subject: [PATCH 1733/2366] sched_ext: Maintain an online cid mask in the scheduler arena Schedulers on the default cid mapping treat [0, nr_online_cids) as the online set and restart on hotplug. Schedulers that install their own mapping with scx_bpf_cid_override() have no way to learn which cids are online: the count no longer identifies members and the CPU-form cpumask is unusable from cid programs. This is an obvious hole in the cid API. Add scx_bpf_online_cmask(), a kernel-maintained cmask in the scheduler's arena, allocated alongside the per-CPU scratch masks and populated after the cid mapping is finalized and before ops.init(), for child schedulers too. The pointer stays valid through ops.exit() with no reference to take. It is the arena offset as a void pointer, the same form struct_ops arena arguments arrive in. The verifier types the void return as a scalar for the program's arena cast. The mask follows the SCX hotplug notifications: seeded from cpu_active_mask and updated before ops.cid_online/offline() runs, so it lags cpu_online_mask only inside a hotplug transition. Updates walk the scheduler list under the lock that also serializes unlinking. Reads are live, not atomic snapshots. Root initialization excludes hotplug. v2: Reworded the getter kerneldoc (Andrea Righi). Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 75 ++++++++++++++++++++++-- kernel/sched/ext/internal.h | 3 +- kernel/sched/ext/sub.c | 10 ++-- tools/sched_ext/include/scx/common.bpf.h | 1 + 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 83999203a63a..70b711c4de6e 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -3670,8 +3670,20 @@ static void handle_hotplug(struct rq *rq, bool online) s16 *tbl = rcu_dereference_check(scx_cpu_to_cid_tbl, lockdep_is_cpus_held()); - if (tbl) + if (tbl) { + struct scx_sched *pos; + cpu_or_cid = tbl[cpu]; + + guard(raw_spinlock_irqsave)(&scx_sched_lock); + list_for_each_entry(pos, &scx_sched_all, all) { + struct scx_cmask *mask = pos->online_cmask; + + if (mask) + __assign_bit(cpu_or_cid, (unsigned long *)mask->bits, + online); + } + } } if (online && SCX_HAS_OP(sch, cpu_online)) @@ -5280,12 +5292,17 @@ static void free_exit_info(struct scx_exit_info *ei); static const char *scx_exit_reason(enum scx_exit_kind kind); static bool scx_claim_exit(struct scx_sched *sch, enum scx_exit_kind kind); -s32 scx_set_cmask_scratch_alloc(struct scx_sched *sch) +s32 scx_alloc_kern_arena_objs(struct scx_sched *sch) { size_t size = struct_size_t(struct scx_cmask, bits, SCX_CMASK_NR_WORDS(num_possible_cpus())); + struct scx_cmask *online; + struct scx_cmask_ref ref; int cpu; + /* hotplug stays excluded until the online mask is published */ + lockdep_assert_cpus_held(); + if (!sch->is_cid_type || !sch->arena_pool) return 0; @@ -5301,15 +5318,28 @@ s32 scx_set_cmask_scratch_alloc(struct scx_sched *sch) return -ENOMEM; scx_cmask_init(*slot, 0, num_possible_cpus()); } + + /* pack the online mask alongside the scratch masks */ + online = scx_arena_alloc(sch, size); + if (!online) + return -ENOMEM; + + scoped_guard(rcu) { + scx_cmask_ref_init_kern(sch, online, 0, num_possible_cpus(), &ref); + scx_cmask_ref_from_cpumask(&ref, cpu_active_mask); + } + sch->online_cmask = online; + return 0; } -static void scx_set_cmask_scratch_free(struct scx_sched *sch) +static void scx_free_kern_arena_objs(struct scx_sched *sch) { size_t size = struct_size_t(struct scx_cmask, bits, SCX_CMASK_NR_WORDS(num_possible_cpus())); int cpu; + scx_arena_free(sch, sch->online_cmask, size); if (!sch->set_cmask_scratch) return; @@ -5396,7 +5426,7 @@ static void scx_sched_free_rcu_work(struct work_struct *work) rhashtable_free_and_destroy(&sch->dsq_hash, NULL, NULL); free_exit_info(sch->exit_info); - scx_set_cmask_scratch_free(sch); + scx_free_kern_arena_objs(sch); scx_arena_pool_destroy(sch); if (sch->arena_map) bpf_map_put(sch->arena_map); @@ -7601,7 +7631,7 @@ static void scx_root_enable_workfn(struct kthread_work *work) goto err_disable; } - ret = scx_set_cmask_scratch_alloc(sch); + ret = scx_alloc_kern_arena_objs(sch); if (ret) { cpus_read_unlock(); goto err_disable; @@ -10338,13 +10368,45 @@ __bpf_kfunc u32 scx_bpf_nr_cids(void) * hotplug, which lets schedulers treat [0, nr_online_cids) as the online * range. Schedulers that prefer to handle hotplug without a restart should * install a custom mapping via scx_bpf_cid_override() and track onlining - * through the ops.cid_online / ops.cid_offline callbacks. + * through the ops.cid_online / ops.cid_offline callbacks, starting from the + * mask scx_bpf_online_cmask() returns. */ __bpf_kfunc u32 scx_bpf_nr_online_cids(void) { return num_online_cpus(); } +/** + * scx_bpf_online_cmask - Return the online cid mask in the scheduler arena + * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs + * + * Return a kernel-maintained cmask covering [0, scx_bpf_nr_cids()), or NULL if + * the calling program is not associated with a live cid-form scheduler or the + * mask is not allocated yet, as in ops.init_cids(). Treat the mask as read-only + * even though arena memory stays writable by the BPF scheduler. The mask + * follows the SCX hotplug notifications: a cid's bit is updated before + * ops.cid_online/offline() runs for it. The pointer is valid from ops.init() + * through ops.exit(). Root ops.init() runs with hotplug excluded. Other + * contexts can observe concurrent updates. + */ +__bpf_kfunc const void *scx_bpf_online_cmask(const struct bpf_prog_aux *aux) +{ + struct scx_sched *sch; + struct scx_cmask *online; + + guard(rcu)(); + + sch = scx_prog_sched(aux); + if (unlikely(!sch)) + return NULL; + online = sch->online_cmask; + if (unlikely(!online)) + return NULL; + + /* BPF rebases by the low 32 bits, like __arena callback args */ + return (void *)((unsigned long)online - sch->arena_kern_base); +} + /** * scx_bpf_this_cid - Return the cid of the CPU this program is running on * @@ -10708,6 +10770,7 @@ BTF_ID_FLAGS(func, scx_bpf_nr_node_ids) BTF_ID_FLAGS(func, scx_bpf_nr_cpu_ids) BTF_ID_FLAGS(func, scx_bpf_nr_cids) BTF_ID_FLAGS(func, scx_bpf_nr_online_cids) +BTF_ID_FLAGS(func, scx_bpf_online_cmask, KF_IMPLICIT_ARGS | KF_ARENA_RET) BTF_ID_FLAGS(func, scx_bpf_this_cid) BTF_ID_FLAGS(func, scx_bpf_get_possible_cpumask, KF_ACQUIRE) BTF_ID_FLAGS(func, scx_bpf_get_online_cpumask, KF_ACQUIRE) diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 0adaf649d5e0..3464e0f113c1 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -1561,6 +1561,7 @@ struct scx_sched { * and passes it to the callback's __arena argument. */ struct scx_cmask * __percpu *set_cmask_scratch; + struct scx_cmask *online_cmask; DECLARE_BITMAP(has_op, SCX_OPI_END); @@ -2087,7 +2088,7 @@ void scx_disable_and_exit_task(struct scx_sched *sch, struct task_struct *p); void scx_cgroup_lock(void); void scx_cgroup_unlock(void); #endif -s32 scx_set_cmask_scratch_alloc(struct scx_sched *sch); +s32 scx_alloc_kern_arena_objs(struct scx_sched *sch); void scx_disable_bypass_dsp(struct scx_sched *sch); void scx_bypass(struct scx_sched *sch, bool bypass); s32 scx_link_sched(struct scx_sched *sch); diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index 385302d19914..f7aeb1488566 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -1805,6 +1805,12 @@ void scx_sub_enable_workfn(struct kthread_work *work) goto err_disable; } + scoped_guard(cpus_read_lock) { + ret = scx_alloc_kern_arena_objs(sch); + if (ret) + goto err_disable; + } + if (sch->ops.init) { ret = SCX_CALL_OP_RET(sch, init, NULL); if (ret) { @@ -1815,10 +1821,6 @@ void scx_sub_enable_workfn(struct kthread_work *work) sch->exit_info->flags |= SCX_EFLAG_INITIALIZED; } - ret = scx_set_cmask_scratch_alloc(sch); - if (ret) - goto err_disable; - struct scx_sub_attach_args sub_attach_args = { .ops = &sch->ops, .cgroup_path = sch->cgrp_path, diff --git a/tools/sched_ext/include/scx/common.bpf.h b/tools/sched_ext/include/scx/common.bpf.h index 76f5e025e107..2ddb01a059fd 100644 --- a/tools/sched_ext/include/scx/common.bpf.h +++ b/tools/sched_ext/include/scx/common.bpf.h @@ -113,6 +113,7 @@ s32 scx_bpf_this_cid(void) __ksym __weak; struct task_struct *scx_bpf_cid_curr(s32 cid) __ksym __weak; u32 scx_bpf_nr_cids(void) __ksym __weak; u32 scx_bpf_nr_online_cids(void) __ksym __weak; +const void __arena *scx_bpf_online_cmask(void) __ksym __weak; u32 scx_bpf_cidperf_cap(s32 cid) __ksym __weak; u32 scx_bpf_cidperf_cur(s32 cid) __ksym __weak; s32 scx_bpf_cidperf_set(s32 cid, u32 perf) __ksym __weak; From 2b50adefed9808a56d84d1de803cad882cc787fa Mon Sep 17 00:00:00 2001 From: Nicolas Thibert Date: Tue, 8 Sep 2026 10:01:08 +0200 Subject: [PATCH 1734/2366] Bluetooth: btusb: fix NXP IW610 composite device handling The NXP IW610 module exposes itself as a composite USB device (0471:0215) with three interfaces: two real Bluetooth HCI interfaces (class 0xe0) and one vendor-specific WiFi interface (class 0xff) used by mwifiex-nxp. The composite device's whole USB descriptor reports class 0xe0/01/01 (Bluetooth), so btusb_table's generic USB_DEVICE_INFO(0xe0, 0x01, 0x01) entry matches every interface, not just the two real HCI ones -- btusb ends up binding the WiFi interface too, and mwifiex-nxp never gets it. Fix: 1. In btusb_table (the table the USB core actually matches against), explicitly ignore the WiFi interface via BTUSB_IGNORE, ahead of the generic entry. 2. In quirks_table, scope the existing BTUSB_MARVELL entry to the BT interface class instead of matching the whole device by VID/PID (harmless either way since quirks_table isn't consulted for initial binding, but keep it correct). Not upstream anywhere: checked NXP's own i.MX kernel fork (nxp-imx/linux-imx), no IW610 references in btusb.c on any branch -- their reference designs wire this chip differently (WiFi over SDIO per their release notes), so they never hit this. Signed-off-by: Nicolas Thibert Cc: stable@vger.kernel.org Assisted-by: LLM (Claude Sonnet 5, Anthropic) Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btusb.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 002b9f975710..dc7191bf4234 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -71,6 +71,15 @@ static struct usb_driver btusb_driver; #define BTUSB_BROKEN_EXT_SCAN BIT(29) static const struct usb_device_id btusb_table[] = { + /* + * NXP IW610 (0471:0215): the composite device reports Bluetooth + * class at the whole-device level, so the generic entry below + * would also match this WiFi vendor interface. Ignore it here + * first so mwifiex-nxp can bind it instead. + */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0471, 0x0215, 0xff, 0xff, 0xff), + .driver_info = BTUSB_IGNORE }, + /* Generic Bluetooth USB device */ { USB_DEVICE_INFO(0xe0, 0x01, 0x01) }, @@ -477,6 +486,14 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, + /* + * NXP IW610 BT interfaces (Marvell-lineage silicon, same quirk as + * the 0x1286 entries above). Scoped to the BT interface class, + * not just VID/PID -- see the btusb_table entry above. + */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0471, 0x0215, 0xe0, 0x01, 0x01), + .driver_info = BTUSB_MARVELL }, + /* Intel Bluetooth devices */ { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, { USB_DEVICE(0x8087, 0x0026), .driver_info = BTUSB_INTEL_COMBINED }, From e8241766794cf551d787fa3a77c0d54bbea6f6aa Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Mon, 7 Sep 2026 00:37:43 +0100 Subject: [PATCH 1735/2366] Bluetooth: eir: validate service data length before reading UUID eir_get_service_data() reads a 16-bit UUID from the service data using get_unaligned_le16() without first checking that the data is long enough to hold a UUID16 (2 bytes). If a malformed EIR entry has a service data field with only 1 byte of payload (field_len=2), eir_get_data() returns dlen=1. The subsequent get_unaligned_le16() then reads 1 byte past the field boundary. Additionally, if the corrupted UUID happens to match, the length calculation "dlen - 2" underflows to SIZE_MAX since dlen is size_t. Current callers either pass NULL for the length parameter or bounds-check the returned length, but future callers may not. Add a check that dlen >= sizeof(u16) and skip fields that are too short to contain a valid UUID16. Fixes: 8f9ae5b3ae80 ("Bluetooth: eir: Add helpers for managing service data") Cc: stable@vger.kernel.org Signed-off-by: Aamir Ahmed Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/eir.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/eir.c b/net/bluetooth/eir.c index a55696820b22..ee0136bfae40 100644 --- a/net/bluetooth/eir.c +++ b/net/bluetooth/eir.c @@ -373,7 +373,15 @@ void *eir_get_service_data(u8 *eir, size_t eir_len, u16 uuid, size_t *len) size_t dlen; while ((eir = eir_get_data(eir, eir_len, EIR_SERVICE_DATA, &dlen))) { - u16 value = get_unaligned_le16(eir); + u16 value; + + if (dlen < sizeof(value)) { + eir += dlen; + eir_len = eir_end - eir; + continue; + } + + value = get_unaligned_le16(eir); if (uuid == value) { if (len) From 6610c6fe4b8936c232048e6049bf77c70a6f759c Mon Sep 17 00:00:00 2001 From: ThangNN99 Date: Sun, 6 Sep 2026 22:21:27 +0700 Subject: [PATCH 1736/2366] Bluetooth: hci_core: Fix queuing tx_work after workqueue is drained hci_send_acl(), hci_send_sco() and hci_send_iso() queue hdev->tx_work unconditionally. They can run from the L2CAP/SCO/ISO socket send path while hci_dev_close_sync() is draining hdev->workqueue (HCIDEVDOWN racing with a socket write). Since that queue_work() is not chained work from the tx_work worker itself, __queue_work() sees the queue marked __WQ_DRAINING, warns "cannot queue %ps on wq %s", and drops the work: WARNING: CPU: 1 PID: 5985 at kernel/workqueue.c:2352 __queue_work Call Trace: queue_work_on l2cap_chan_send l2cap_sock_sendmsg ... hci_dev_close_sync() already sets HCI_CMD_DRAIN_WORKQUEUE before draining, but only hci_cmd_work() and handle_cmd_cnt_and_timer() check it before queuing. Route the tx_work producers through the same guard via a shared hci_sched_tx() helper. Fixes: 525daaea459f ("Bluetooth: hci_sync: Set HCI_CMD_DRAIN_WORKQUEUE during device close") Reported-by: syzbot+b6919040d9958e2fc1ae@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b6919040d9958e2fc1ae Signed-off-by: ThangNN99 Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index d7355c73f93e..c322e4736621 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -3236,6 +3236,17 @@ static void hci_queue_acl(struct hci_chan *chan, struct sk_buff_head *queue, bt_dev_dbg(hdev, "chan %p queued %d", chan, skb_queue_len(queue)); } +/* Queue hdev->tx_work, unless hdev->workqueue is being drained by + * hci_dev_close_sync(), which would otherwise WARN and drop the work. + */ +static void hci_sched_tx(struct hci_dev *hdev) +{ + rcu_read_lock(); + if (!hci_dev_test_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE)) + queue_work(hdev->workqueue, &hdev->tx_work); + rcu_read_unlock(); +} + void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags) { struct hci_dev *hdev = chan->conn->hdev; @@ -3244,7 +3255,7 @@ void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags) hci_queue_acl(chan, &chan->data_q, skb, flags); - queue_work(hdev->workqueue, &hdev->tx_work); + hci_sched_tx(hdev); } /* Send SCO data */ @@ -3269,7 +3280,7 @@ void hci_send_sco(struct hci_conn *conn, struct sk_buff *skb) bt_dev_dbg(hdev, "hcon %p queued %d", conn, skb_queue_len(&conn->data_q)); - queue_work(hdev->workqueue, &hdev->tx_work); + hci_sched_tx(hdev); } /* Send ISO data */ @@ -3340,7 +3351,7 @@ void hci_send_iso(struct hci_conn *conn, struct sk_buff *skb) hci_queue_iso(conn, &conn->data_q, skb); - queue_work(hdev->workqueue, &hdev->tx_work); + hci_sched_tx(hdev); } /* ---- HCI TX task (outgoing data) ---- */ From 4b837ebd0ea21ae5cc26f02dc042edc6fe7b46b9 Mon Sep 17 00:00:00 2001 From: Chandrashekar Devegowda Date: Tue, 8 Sep 2026 15:26:58 +0530 Subject: [PATCH 1737/2366] Bluetooth: btintel_pcie: validate TX skb length in send_sync btintel_pcie_prepare_tx() copies skb->len bytes into a fixed BTINTEL_PCIE_BUFFER_SIZE (4096) DMA slot via an unchecked memcpy. Oversized packets are currently rejected only in btintel_pcie_send_frame(); any future caller of btintel_pcie_send_sync() would silently overflow the DMA buffer. Add the bounds check in btintel_pcie_send_sync() itself, right before skb_push() and the DMA copy. Assisted-by: Copilot:claude-sonnet-5 code-review code-generation Fixes: 6e65a09f9275 ("Bluetooth: btintel_pcie: Add *setup* function to download firmware") Signed-off-by: Chandrashekar Devegowda Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 6d9649776ae7..405b23e21473 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -404,6 +404,12 @@ static int btintel_pcie_send_sync(struct btintel_pcie_data *data, if (tfd_index > txq->count) return -ERANGE; + if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { + bt_dev_err(hdev, "TX skb too large (%u > %u)", skb->len, + BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN); + return -EMSGSIZE; + } + /* Firmware raises alive interrupt on HCI_OP_RESET or * BTINTEL_HCI_OP_RESET */ From d236517c264e41dc09833c708ef23bccb7a91219 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 6 Sep 2026 23:43:32 +0800 Subject: [PATCH 1738/2366] Bluetooth: coredump: Quiesce dump work on unregister hci_devcd_handle_pkt_init() arms dump_timeout and coredump producers queue dump_rx without holding an hdev reference. Unregister leaves both works live, so disconnecting during an active dump lets them access hdev after hci_release_dev() frees it. Shut down coredump processing during unregister. Close the producer gate under dump_q.lock before disabling both works, then free the active buffer and queued packets under hci_dev_lock. Serializing the gate with enqueue prevents controller-specific workers from adding packets after the final purge. Fixes: 9695ef876fd1 ("Bluetooth: Add support for hci devcoredump") Reported-by: syzbot+b170dbf55520ebf5969a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b170dbf55520ebf5969a Reported-by: Aby Sam Ross Link: https://lore.kernel.org/r/20260322210849.68743-1-abysamross@gmail.com Suggested-by: Aby Sam Ross Reported-by: Tristan Madani Link: https://lore.kernel.org/r/20260814231248.3096377-1-tristmd@gmail.com Reported-by: Xiang Mei Assisted-by: OpenAI Codex:gpt-5 Signed-off-by: Weiming Shi Reported-by: Xiang Mei Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/coredump.h | 2 + net/bluetooth/coredump.c | 65 +++++++++++++++++++++----------- net/bluetooth/hci_core.c | 1 + 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/include/net/bluetooth/coredump.h b/include/net/bluetooth/coredump.h index 1f071ab55416..acc1849f66c0 100644 --- a/include/net/bluetooth/coredump.h +++ b/include/net/bluetooth/coredump.h @@ -70,6 +70,7 @@ struct hci_devcoredump { const char *hci_devcd_state_name(enum devcoredump_state state); void hci_devcd_reset(struct hci_dev *hdev); +void hci_devcd_shutdown(struct hci_dev *hdev); void hci_devcd_rx(struct work_struct *work); void hci_devcd_timeout(struct work_struct *work); @@ -89,6 +90,7 @@ static inline const char *hci_devcd_state_name(enum devcoredump_state state) } static inline void hci_devcd_reset(struct hci_dev *hdev) {} +static inline void hci_devcd_shutdown(struct hci_dev *hdev) {} static inline void hci_devcd_rx(struct work_struct *work) {} static inline void hci_devcd_timeout(struct work_struct *work) {} diff --git a/net/bluetooth/coredump.c b/net/bluetooth/coredump.c index 5bee863bd6d2..71fc8dab4004 100644 --- a/net/bluetooth/coredump.c +++ b/net/bluetooth/coredump.c @@ -104,6 +104,22 @@ static void hci_devcd_free(struct hci_dev *hdev) hci_devcd_reset(hdev); } +void hci_devcd_shutdown(struct hci_dev *hdev) +{ + unsigned long flags; + + spin_lock_irqsave(&hdev->dump.dump_q.lock, flags); + hdev->dump.supported = false; + spin_unlock_irqrestore(&hdev->dump.dump_q.lock, flags); + + disable_work_sync(&hdev->dump.dump_rx); + disable_delayed_work_sync(&hdev->dump.dump_timeout); + + hci_dev_lock(hdev); + hci_devcd_free(hdev); + hci_dev_unlock(hdev); +} + /* Call with hci_dev_lock only. */ static int hci_devcd_alloc(struct hci_dev *hdev, u32 size) { @@ -442,7 +458,29 @@ EXPORT_SYMBOL(hci_devcd_register); static inline bool hci_devcd_enabled(struct hci_dev *hdev) { - return hdev->dump.supported; + return READ_ONCE(hdev->dump.supported); +} + +static int hci_devcd_queue(struct hci_dev *hdev, struct sk_buff *skb) +{ + unsigned long flags; + int err = 0; + + spin_lock_irqsave(&hdev->dump.dump_q.lock, flags); + if (!hdev->dump.supported) + err = -EOPNOTSUPP; + else + __skb_queue_tail(&hdev->dump.dump_q, skb); + spin_unlock_irqrestore(&hdev->dump.dump_q.lock, flags); + + if (err) { + kfree_skb(skb); + return err; + } + + queue_work(hdev->workqueue, &hdev->dump.dump_rx); + + return 0; } int hci_devcd_init(struct hci_dev *hdev, u32 dump_size) @@ -459,10 +497,7 @@ int hci_devcd_init(struct hci_dev *hdev, u32 dump_size) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_INIT; put_unaligned_le32(dump_size, skb_put(skb, 4)); - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_init); @@ -478,10 +513,7 @@ int hci_devcd_append(struct hci_dev *hdev, struct sk_buff *skb) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_SKB; - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_append); @@ -503,10 +535,7 @@ int hci_devcd_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_PATTERN; skb_put_data(skb, &p, sizeof(p)); - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_append_pattern); @@ -523,10 +552,7 @@ int hci_devcd_complete(struct hci_dev *hdev) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_COMPLETE; - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_complete); @@ -543,10 +569,7 @@ int hci_devcd_abort(struct hci_dev *hdev) hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_ABORT; - skb_queue_tail(&hdev->dump.dump_q, skb); - queue_work(hdev->workqueue, &hdev->dump.dump_rx); - - return 0; + return hci_devcd_queue(hdev, skb); } EXPORT_SYMBOL(hci_devcd_abort); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index c322e4736621..d183efaf9063 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2673,6 +2673,7 @@ void hci_unregister_dev(struct hci_dev *hdev) disable_work_sync(&hdev->error_reset); disable_delayed_work_sync(&hdev->cmd_timer); disable_delayed_work_sync(&hdev->ncmd_timer); + hci_devcd_shutdown(hdev); hci_cmd_sync_clear(hdev); From 4914c499896121ae8b9d5b90f0abc5c8287ff396 Mon Sep 17 00:00:00 2001 From: Radek Podgorny Date: Wed, 9 Sep 2026 00:29:37 +0200 Subject: [PATCH 1739/2366] Bluetooth: put the peer's on-air address on air when we cannot resolve An identity address only reaches a peer that is advertising an RPA if the controller resolves it on our behalf. Where it cannot, the host has to put the peer's on-air address on air itself. hci_connect_le() still swaps the caller's identity address for the peer's cached RPA before creating the connection, but __hci_conn_add() resolves the RPA back to the identity address when it stores it, so the identity is what goes out. Storing the identity is right when the controller translates it on the way to the radio; without LL Privacy, or with this peer absent from the resolving list, nothing does. A peer advertising an RPA cannot answer its identity address, so the attempt burns a full create-connection timeout. That is not merely a slow connect: a controller without extended scanning cannot scan while it is initiating, so every dead attempt also takes the scanner off the air for the whole timeout. Measured on a CYW43438, which reports neither LL Privacy nor extended advertising (LE features 3f 00 00 08 00 00 00 00), against a peer advertising a resolvable private address the host holds the IRK for, with the connection requested on the peer's identity address: before: LE Create Connection to the identity address, public type 1.61s -> 22.07s, then LE Create Connection Cancel LE Connection Complete: Unknown Connection Identifier (0x02) after: LE Create Connection to the peer's RPA, random type LE Connection Complete: Success Advertising reports reaching the host per second, same window, same five unrelated devices on the adapter: before 1s:2 [nothing from 2s through 21s] 22s:5 23s:3 after 0s:11 1s:5 2s:2 3s:5 4s:3 5s:4 ... 21s:2 22s:1 23s:2 One dead connect costs twenty seconds of scanning for every device on the adapter, not just the one being dialled. Keep the RPA in conn->dst unless the controller will translate the identity address: address resolution enabled and the peer's identity actually programmed into the resolving list. Testing ll_privacy_capable() alone would not be enough: it reports the feature bit, not whether resolution is switched on and not whether this peer is in the list. Resolution is cleared with the other volatile flags on power-off and switched off again while suspend pauses scanning, and a peer's IRK is only programmed along the accept list path, so a direct-connect target, a peer without HCI_CONN_FLAG_ADDRESS_RESOLUTION, and one that did not fit in a full list are all absent from it. With the peer programmed, the identity address stays in conn->dst and the controller translates it: measured on an Intel controller, the host dials the identity and LE Enhanced Connection Complete reports Resolved Public with the peer's RPA in the separate peer resolvable private address field. With the peer absent from the list the same setup dials the RPA itself. Everything downstream already copes with an RPA in conn->dst: it is what every outgoing LE connection stored before 14b06c3a88f7, the connection complete event names the address that was dialled, and le_conn_complete_evt() resolves it back to the identity once the link is up. ISO links keep the unconditional conversion: they are created from an existing ACL or a periodic sync and never dial this address themselves. Keeping the RPA is only right while the peer is still using it, which is why the preceding patch drops the cached RPA as soon as the peer is seen advertising its identity address. Without that, a peer that turns privacy off would be dialled on the address it abandoned rather than the one it is answering on. Fixes: 14b06c3a88f7 ("Bluetooth: HCI: Always use the identity address when initializing a connection") Assisted-by: Claude:claude-opus-5 Assisted-by: Claude:claude-fable-5 Signed-off-by: Radek Podgorny Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 8de98af2fb58..c9466cb2c7c0 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -1023,6 +1023,19 @@ static struct hci_conn *__hci_conn_add(struct hci_dev *hdev, int type, if (!hdev->le_mtu && hdev->acl_mtu < HCI_MIN_LE_MTU) return ERR_PTR(-ECONNREFUSED); irk = hci_get_irk(hdev, dst, dst_type); + /* An identity address only reaches a peer advertising an RPA + * if the controller translates it. Unless address resolution + * is enabled and this peer is programmed into the resolving + * list, keep the RPA the peer is on air with; + * le_conn_complete_evt() resolves it back once the link is + * up. + */ + if (irk && + (!hci_dev_test_flag(hdev, HCI_LL_RPA_RESOLUTION) || + !hci_bdaddr_list_lookup_with_irk(&hdev->le_resolv_list, + &irk->bdaddr, + irk->addr_type))) + irk = NULL; break; case SCO_LINK: case ESCO_LINK: From d0795cfd6f655f4de84868a4f4bb41a03f037b3d Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Mon, 24 Aug 2026 21:42:36 +0545 Subject: [PATCH 1740/2366] Bluetooth: hci_codec: validate vendor codec count length The Read Local Supported Codecs parsers consume the variable-sized standard codec array before parsing the vendor codec count. Although the initial reply-size check includes a vendor count byte in the fixed layout, it does not guarantee that the byte remains after the standard codec array. If a controller reply ends immediately after that array, calculating the vendor codec array size reads vnd_codecs->num beyond the skb data. Use skb_pull_data() to validate and consume each codec header before using its count in both command variants. Fixes: 8961987f3f5f ("Bluetooth: Enumerate local supported codec and cache details") Fixes: 9ae664028a9e ("Bluetooth: Add support for Read Local Supported Codecs V2") Cc: stable@vger.kernel.org Suggested-by: Luiz Augusto von Dentz Signed-off-by: Laxman Acharya Padhya Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_codec.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/net/bluetooth/hci_codec.c b/net/bluetooth/hci_codec.c index 5bc5003c387c..7a7e813dcdda 100644 --- a/net/bluetooth/hci_codec.c +++ b/net/bluetooth/hci_codec.c @@ -145,11 +145,12 @@ void hci_read_supported_codecs(struct hci_dev *hdev) skb_pull(skb, sizeof(rp->status)); - std_codecs = (void *)skb->data; + std_codecs = skb_pull_data(skb, sizeof(*std_codecs)); + if (!std_codecs) + goto error; /* validate codecs length before accessing */ - if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num) - + sizeof(std_codecs->num)) + if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num)) goto error; /* enumerate codec capabilities of standard codecs */ @@ -161,15 +162,14 @@ void hci_read_supported_codecs(struct hci_dev *hdev) LOCAL_CODEC_ACL_MASK | LOCAL_CODEC_SCO_MASK, &caps); } - skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num) - + sizeof(std_codecs->num)); + skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num)); - vnd_codecs = (void *)skb->data; + vnd_codecs = skb_pull_data(skb, sizeof(*vnd_codecs)); + if (!vnd_codecs) + goto error; /* validate vendor codecs length before accessing */ - if (skb->len < - flex_array_size(vnd_codecs, codec, vnd_codecs->num) - + sizeof(vnd_codecs->num)) + if (skb->len < flex_array_size(vnd_codecs, codec, vnd_codecs->num)) goto error; /* enumerate vendor codec capabilities */ @@ -214,11 +214,12 @@ void hci_read_supported_codecs_v2(struct hci_dev *hdev) skb_pull(skb, sizeof(rp->status)); - std_codecs = (void *)skb->data; + std_codecs = skb_pull_data(skb, sizeof(*std_codecs)); + if (!std_codecs) + goto error; /* check for payload data length before accessing */ - if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num) - + sizeof(std_codecs->num)) + if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num)) goto error; memset(&caps, 0, sizeof(caps)); @@ -229,15 +230,14 @@ void hci_read_supported_codecs_v2(struct hci_dev *hdev) &caps); } - skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num) - + sizeof(std_codecs->num)); + skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num)); - vnd_codecs = (void *)skb->data; + vnd_codecs = skb_pull_data(skb, sizeof(*vnd_codecs)); + if (!vnd_codecs) + goto error; /* check for payload data length before accessing */ - if (skb->len < - flex_array_size(vnd_codecs, codec, vnd_codecs->num) - + sizeof(vnd_codecs->num)) + if (skb->len < flex_array_size(vnd_codecs, codec, vnd_codecs->num)) goto error; for (i = 0; i < vnd_codecs->num; i++) { From 4e93c65f87825e1e012bce56615320aeb123815d Mon Sep 17 00:00:00 2001 From: Ibrahim Abdelkader Date: Wed, 19 Aug 2026 14:54:25 +0200 Subject: [PATCH 1741/2366] Bluetooth: hci_qca: Do not write to the serial port after it is closed hci_uart_close() closes the serdev port if HCI_QUIRK_NON_PERSISTENT_SETUP is set (for example, for the WCN399x family). A failed hci_dev_open_sync() following a successful qca_setup() calls hdev->close() but not hdev->shutdown(), so the port is closed while power->vregs_on is left true. qca_serdev_remove() then passes its power->vregs_on test and calls qca_power_off(), which writes to the closed port unconditionally. Seen on a WCN3988 by unbinding the driver after a controller failure. The trace below is from a 7.0.0 based kernel, where qca_power_off() was still named qca_power_shutdown(): Unable to handle kernel NULL pointer dereference at virtual address 0000000000000038 Call trace: tty_set_termios+0x50/0x238 (P) ttyport_set_baudrate+0x84/0xc0 serdev_device_set_baudrate+0x24/0x40 qca_power_shutdown+0x158/0x1fc [hci_uart] qca_serdev_remove+0x54/0x68 [hci_uart] serdev_drv_remove+0x1c/0x2c device_remove+0x4c/0x80 device_release_driver_internal+0x1cc/0x224 device_driver_detach+0x18/0x24 unbind_store+0xb4/0xc0 Check HCI_UART_PROTO_READY, which hci_uart_close() clears in the same place it closes the port, before writing to it. The regulator disable is left unconditional so the controller is still powered down. The dangling serport->tty that turns this into a use-after-free is addressed in a separate patch. Fixes: fa9ad876b8e0 ("Bluetooth: hci_qca: Add support for Qualcomm Bluetooth chip wcn3990") Signed-off-by: Ibrahim Abdelkader Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_qca.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index faa964735adb..7089e9b639b2 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -2228,8 +2228,8 @@ static void qca_power_off(struct hci_uart *hu) bool sw_ctrl_state; struct qca_power *power; - /* From this point we go into power off state. But serial port is - * still open, stop queueing the IBS data and flush all the buffered + /* From this point we go into power off state. But serial port may + * still be open, stop queueing the IBS data and flush all the buffered * data in skb's. */ spin_lock_irqsave(&qca->hci_ibs_lock, flags); @@ -2251,8 +2251,14 @@ static void qca_power_off(struct hci_uart *hu) case QCA_WCN3990: case QCA_WCN3991: case QCA_WCN3998: - host_set_baudrate(hu, 2400); - qca_send_power_pulse(hu, false); + /* Both of these write to the serial port which may have + * already been closed by hci_uart_close(), which closes + * the port if HCI_QUIRK_NON_PERSISTENT_SETUP is set. + */ + if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) { + host_set_baudrate(hu, 2400); + qca_send_power_pulse(hu, false); + } break; default: break; From 9a10987a2f160a44a638c9a35994ca6e3089696e Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sat, 22 Aug 2026 01:43:50 +0800 Subject: [PATCH 1742/2366] Bluetooth: hci_sync: Serialize local codec list cleanup hci_dev_close_sync() clears hdev->local_codecs after releasing hdev->lock. Codec list additions and both traversals in sco_sock_getsockopt() use that lock, but the close path does not. A close and BT_CODEC query can therefore interleave as follows: hci_dev_close_sync() sco_sock_getsockopt() hci_dev_lock() fetch codec entry hci_codec_list_clear() kfree(entry) read entry->id The reader then accesses an entry which the close path has freed. KASAN reported: BUG: KASAN: slab-use-after-free in sco_sock_getsockopt+0xfa0/0xfe0 Read of size 1 at addr ffff8881001c3450 Call Trace: sco_sock_getsockopt+0xfa0/0xfe0 do_sock_getsockopt+0x537/0x7b0 __sys_getsockopt+0xf2/0x170 Allocated by task 92: hci_codec_list_add.isra.0+0x2c/0x440 hci_read_codec_capabilities+0x224/0x590 hci_read_supported_codecs+0x2c2/0x640 Freed by task 92: kfree+0x131/0x3c0 hci_codec_list_clear+0xd8/0x160 hci_dev_close_sync+0x92a/0xfa0 Take hdev->lock around the clear operation at its existing point in the close path. This makes the clear wait for active readers and prevents a new traversal until the list is empty without changing teardown ordering. Fixes: b938790e7054 ("Bluetooth: hci_codec: Fix leaking content of local_codecs") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 2a651a4d60e6..74e2b04c84b2 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -5673,7 +5673,9 @@ int hci_dev_close_sync(struct hci_dev *hdev) memset(hdev->eir, 0, sizeof(hdev->eir)); memset(hdev->dev_class, 0, sizeof(hdev->dev_class)); bacpy(&hdev->random_addr, BDADDR_ANY); + hci_dev_lock(hdev); hci_codec_list_clear(&hdev->local_codecs); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; From ca18ee413a7cb6f09885778039225e58bae0d607 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Thu, 10 Sep 2026 14:06:27 -0400 Subject: [PATCH 1743/2366] Bluetooth: ISO: Fix parent socket leak in iso_conn_ready() iso_get_sock() returns the parent socket with a reference held, which is dropped by sock_put() once the child socket has been set up. The error path taken when iso_sock_alloc() fails only calls release_sock() and returns, leaking the reference and thus the parent socket itself. Drop the reference on that path as well. Fixes: fa224d0c094a ("Bluetooth: ISO: Reassociate a socket with an active BIS") Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 75bfd5938b2e..4de332b8901f 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -2289,6 +2289,7 @@ static void iso_conn_ready(struct iso_conn *conn) BTPROTO_ISO, GFP_ATOMIC, 0); if (!sk) { release_sock(parent); + sock_put(parent); return; } From 296e7f3c5071cc02dc22e1566e759179fa1792ae Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Thu, 10 Sep 2026 14:07:24 -0400 Subject: [PATCH 1744/2366] Bluetooth: ISO: set BT_LISTEN before requesting a BIG sync A BIS connection is matched to its parent socket by looking for a socket in BT_LISTEN state with the same BIG handle: iso_conn_ready() if (test_bit(HCI_CONN_BIG_SYNC, &hcon->flags)) parent = iso_get_sock(hdev, &hcon->src, &hcon->dst, BT_LISTEN, iso_match_big_hcon, hcon); The socket was only moved to BT_LISTEN after iso_conn_big_sync() returned, while the LE BIG Create Sync command has already been queued by then. If the BIG sync is established before the state is updated, which is easy to hit with an emulated controller as the command may complete in a few hundred microseconds, no parent is found and the BIS connections are never notified to the listening socket. The user space is then left waiting for connections that never arrive, e.g. bluetoothd never completes a MediaTransport1.Acquire of a Broadcast Sink transport. Move the socket to BT_LISTEN before requesting the BIG sync, so the state is visible by the time the command is queued, and restore the previous state if the request could not be started. Since the socket is briefly visible as a listening socket, child sockets may have been queued in the meantime, so drain the accept queue before restoring the state: the cleanup paths of BT_CONNECT2/BT_CONNECTED don't do it and the children would be left with a dangling parent pointer. Fixes: fbdc4bc47268 ("Bluetooth: ISO: Use defer setup to separate PA sync and BIG sync") Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 52 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 4de332b8901f..eb99653f33f9 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -819,19 +819,24 @@ static void iso_sock_destruct(struct sock *sk) skb_queue_purge(&sk->sk_error_queue); } -static void iso_sock_cleanup_listen(struct sock *parent) +/* Close not yet accepted channels */ +static void iso_sock_flush_accept_q(struct sock *parent) { struct sock *sk; - BT_DBG("parent %p", parent); - - /* Close not yet accepted channels */ while ((sk = bt_accept_dequeue(parent, NULL))) { iso_sock_close(sk); iso_sock_kill(sk); /* Drop the reference handed back by bt_accept_dequeue(). */ sock_put(sk); } +} + +static void iso_sock_cleanup_listen(struct sock *parent) +{ + BT_DBG("parent %p", parent); + + iso_sock_flush_accept_q(parent); /* If listening socket has a hcon, properly disconnect it */ if (iso_pi(parent)->conn && iso_pi(parent)->conn->hcon) { @@ -1737,6 +1742,13 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, switch (sk->sk_state) { case BT_CONNECT2: if (test_bit(BT_SK_PA_SYNC, &pi->flags)) { + /* Move to BT_LISTEN before requesting the BIG + * sync: the BIS connections are matched to a + * parent socket in BT_LISTEN state, and they + * may be notified before the request returns. + */ + sk->sk_state = BT_LISTEN; + release_sock(sk); err = iso_conn_big_sync(sk); lock_sock(sk); @@ -1745,12 +1757,20 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, * connection may have been torn down * meanwhile and iso_chan_del() may have * already moved the socket to BT_CLOSED. - * Only move on to BT_LISTEN if the BIG sync - * was actually started and nothing else has - * changed the state. + * Only move back if the BIG sync could not be + * started and nothing else has changed the + * state. */ - if (!err && sk->sk_state == BT_CONNECT2) - sk->sk_state = BT_LISTEN; + if (err && sk->sk_state == BT_LISTEN) { + /* Discard any child socket that may + * have been queued while the socket + * was in BT_LISTEN, as the cleanup of + * BT_CONNECT2 doesn't drain the + * accept queue. + */ + iso_sock_flush_accept_q(sk); + sk->sk_state = BT_CONNECT2; + } } else { iso_conn_defer_accept(pi->conn->hcon); sk->sk_state = BT_CONFIG; @@ -1760,12 +1780,22 @@ static int iso_sock_recvmsg(struct socket *sock, struct msghdr *msg, break; case BT_CONNECTED: if (test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags)) { + /* As above, the BIS connections may be + * notified before the request returns. + */ + sk->sk_state = BT_LISTEN; + release_sock(sk); err = iso_conn_big_sync(sk); lock_sock(sk); - if (!err && sk->sk_state == BT_CONNECTED) - sk->sk_state = BT_LISTEN; + if (err && sk->sk_state == BT_LISTEN) { + /* As above, don't leave any child + * socket behind in the accept queue. + */ + iso_sock_flush_accept_q(sk); + sk->sk_state = BT_CONNECTED; + } early_ret = true; } From 78b6abd6c7a7591aacdae657f813214dae4fcd3b Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 14 Sep 2026 14:56:53 +0800 Subject: [PATCH 1745/2366] Bluetooth: btmtk: fix wrong status for short WMT FUNC_CTRL events A too-short BTMTK_WMT_FUNC_CTRL event (WMT header only, no trailing 2-byte status word) is always treated as BTMTK_WMT_ON_UNDONE. This short form is how firmware acks a plain enable/disable request, and the actual result is carried in the header's own flag byte (0 = success), not a separate status word. Decode it from there instead of assuming failure. Verified setup on MT7920, MT7921, MT7922 and MT7925: no regression. Fixes: e3ac0d9f1a20 ("Bluetooth: btmtk: accept too short WMT FUNC_CTRL events") Assisted-by: Claude:claude-opus-5 Signed-off-by: Chris Lu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index 26d525acd659..7ea8bcd8a7ec 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -721,7 +721,12 @@ static int btmtk_usb_hci_wmt_sync(struct hci_dev *hdev, case BTMTK_WMT_FUNC_CTRL: if (!skb_pull_data(data->evt_skb, sizeof(wmt_evt_funcc->status))) { - status = BTMTK_WMT_ON_UNDONE; + /* A plain enable/disable request is acked with just + * the WMT header and no trailing status word; the + * result is carried in the header's own flag byte. + */ + status = wmt_evt->whdr.flag ? BTMTK_WMT_ON_UNDONE : + BTMTK_WMT_ON_DONE; break; } From 8879e3e0a84a86954c855caceead4867e74a9a27 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 14 Sep 2026 14:56:54 +0800 Subject: [PATCH 1746/2366] Bluetooth: btmtksdio, btmtkuart: validate WMT event length before struct access btmtksdio.c and btmtkuart.c cast a received WMT event straight to struct btmtk_hci_wmt_evt and read its op/flag fields without checking the event is long enough to contain them, unlike btmtk.c. The FUNC_CTRL case then further casts to struct btmtk_hci_wmt_evt_funcc and reads its 2-byte status field, again without a length check. Firmware that sends a short or malformed WMT event makes both drivers read past the end of the received SKB. Mirror btmtk.c: validate the base WMT header with skb_pull_data() before touching any of its fields, and when a FUNC_CTRL event turns out to be the short, header-only form (a plain enable/disable ack with no status word), decode the result from the header's own flag byte instead (0 = success, otherwise failure). Verified setup on MT7920, MT7921, MT7922 and MT7925: no regression. Fixes: 9aebfd4a2200 ("Bluetooth: mediatek: add support for MediaTek MT7663S and MT7668S SDIO devices") Fixes: e0b67035a90b ("Bluetooth: mediatek: update the common setup between MT7622 and other devices") Assisted-by: Claude:claude-opus-5 Signed-off-by: Chris Lu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtksdio.c | 20 +++++++++++++++++++- drivers/bluetooth/btmtkuart.c | 20 +++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index 94aa60d9cc20..7fab678925d1 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -217,7 +217,14 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev, } /* Parse and handle the return WMT event */ - wmt_evt = (struct btmtk_hci_wmt_evt *)bdev->evt_skb->data; + wmt_evt = skb_pull_data(bdev->evt_skb, sizeof(*wmt_evt)); + if (!wmt_evt) { + bt_dev_err(hdev, "WMT event too short (%u bytes)", + bdev->evt_skb->len); + err = -EINVAL; + goto err_free_skb; + } + if (wmt_evt->whdr.op != hdr->op) { bt_dev_err(hdev, "Wrong op received %d expected %d", wmt_evt->whdr.op, hdr->op); @@ -233,6 +240,17 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev, status = BTMTK_WMT_PATCH_DONE; break; case BTMTK_WMT_FUNC_CTRL: + if (!skb_pull_data(bdev->evt_skb, + sizeof(wmt_evt_funcc->status))) { + /* A plain enable/disable request is acked with just + * the WMT header and no trailing status word; the + * result is carried in the header's own flag byte. + */ + status = wmt_evt->whdr.flag ? BTMTK_WMT_ON_UNDONE : + BTMTK_WMT_ON_DONE; + break; + } + wmt_evt_funcc = (struct btmtk_hci_wmt_evt_funcc *)wmt_evt; if (be16_to_cpu(wmt_evt_funcc->status) == 0x404) status = BTMTK_WMT_ON_DONE; diff --git a/drivers/bluetooth/btmtkuart.c b/drivers/bluetooth/btmtkuart.c index 27aa48ff3ac2..4af6fbbbd302 100644 --- a/drivers/bluetooth/btmtkuart.c +++ b/drivers/bluetooth/btmtkuart.c @@ -151,7 +151,14 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev, } /* Parse and handle the return WMT event */ - wmt_evt = (struct btmtk_hci_wmt_evt *)bdev->evt_skb->data; + wmt_evt = skb_pull_data(bdev->evt_skb, sizeof(*wmt_evt)); + if (!wmt_evt) { + bt_dev_err(hdev, "WMT event too short (%u bytes)", + bdev->evt_skb->len); + err = -EINVAL; + goto err_free_wc; + } + if (wmt_evt->whdr.op != hdr->op) { bt_dev_err(hdev, "Wrong op received %d expected %d", wmt_evt->whdr.op, hdr->op); @@ -167,6 +174,17 @@ static int mtk_hci_wmt_sync(struct hci_dev *hdev, status = BTMTK_WMT_PATCH_DONE; break; case BTMTK_WMT_FUNC_CTRL: + if (!skb_pull_data(bdev->evt_skb, + sizeof(wmt_evt_funcc->status))) { + /* A plain enable/disable request is acked with just + * the WMT header and no trailing status word; the + * result is carried in the header's own flag byte. + */ + status = wmt_evt->whdr.flag ? BTMTK_WMT_ON_UNDONE : + BTMTK_WMT_ON_DONE; + break; + } + wmt_evt_funcc = (struct btmtk_hci_wmt_evt_funcc *)wmt_evt; if (be16_to_cpu(wmt_evt_funcc->status) == 0x404) status = BTMTK_WMT_ON_DONE; From 7b60ee5f46f2ee329de661f7c68b6818d8136220 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Mon, 14 Sep 2026 09:47:29 +0000 Subject: [PATCH 1747/2366] Bluetooth: btmtksdio: Fix PM runtime reference leak in shutdown In btmtksdio_shutdown(), pm_runtime_get_sync() is called at the beginning of the function. However, if sending the WMT function control command fails later, the driver returns early. It bypasses the corresponding pm_runtime_put_noidle() and pm_runtime_disable() calls, leaking the PM usage counter and leaving PM runtime enabled indefinitely. Fall through to execute the PM runtime cleanup block even if WMT errors. Fixes: 7f3c563c575e ("Bluetooth: btmtksdio: Add runtime PM support to SDIO based Bluetooth") Signed-off-by: Tzung-Bi Shih Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtksdio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c index 7fab678925d1..a15ae6598c66 100644 --- a/drivers/bluetooth/btmtksdio.c +++ b/drivers/bluetooth/btmtksdio.c @@ -1262,10 +1262,8 @@ static int btmtksdio_shutdown(struct hci_dev *hdev) wmt_params.status = NULL; err = mtk_hci_wmt_sync(hdev, &wmt_params); - if (err < 0) { + if (err < 0) bt_dev_err(hdev, "Failed to send wmt func ctrl (%d)", err); - return err; - } ignore_wmt_cmd: pm_runtime_put_noidle(bdev->dev); From 2ea5a87a5a7ae58cb2662b8a7d06f209383e1765 Mon Sep 17 00:00:00 2001 From: Sai Teja Aluvala Date: Fri, 11 Sep 2026 17:22:22 +0530 Subject: [PATCH 1748/2366] Bluetooth: btintel_pcie: fix off-by-one bounds check in RX submit btintel_pcie_submit_rx() used frbd_index > rxq->count to guard the FRBD array access, allowing frbd_index == rxq->count to pass through and index one element past the end of the array. Change the check to >= rxq->count so every out-of-range index is rejected. This issue was reported by Claude Mythos. Fixes: c2b636b3f788 (Bluetooth: btintel_pcie: Add support for PCIe transport) Signed-off-by: Sai Teja Aluvala Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 405b23e21473..6e6e2b19815c 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -508,7 +508,7 @@ static int btintel_pcie_submit_rx(struct btintel_pcie_data *data) frbd_index = data->ia.tr_hia[BTINTEL_PCIE_RXQ_NUM]; - if (frbd_index > rxq->count) + if (frbd_index >= rxq->count) return -ERANGE; /* Prepare for RX submit. It updates the FRBD with the address of DMA From 555cd2bd860e7c4bdc3f4e4405b05515b0d9bc87 Mon Sep 17 00:00:00 2001 From: Radek Podgorny Date: Sun, 13 Sep 2026 22:28:02 +0200 Subject: [PATCH 1749/2366] Bluetooth: keep dst_type with dst when reusing an LE connection hci_connect_le() swaps the caller's identity address for the peer's cached RPA when one is known, and stamps the matching ADDR_LE_DEV_RANDOM on the local dst_type. On the conn-reuse path only the address is copied into the connection: if (conn) { bacpy(&conn->dst, dst); so conn->dst ends up holding an RPA while conn->dst_type still names the identity it was resolved from, and hci_le_create_conn_sync() puts that pair on air unchanged. An RPA declared as a public address is not something any peer can answer. Measured on a CYW43438 against a peer advertising an RPA the host holds the IRK for, connecting to the identity address over a raw L2CAP socket. The first attempt creates the connection, the second takes the reuse path: LE Create Connection 3C:78:95:78:37:C3 type public LE Create Connection 5B:75:A2:26:D6:18 type public LE Connection Complete: Unknown Connection Identifier (0x02) The second address is the peer's RPA. btmon annotates it with an OUI lookup rather than "(Resolvable)" precisely because the command declares it public; the same bit pattern annotates as resolvable once the type is right. The mistyped pair is also why nothing downstream repairs it. hci_bdaddr_is_rpa() tests the type before the address, so an RPA carrying a public type is not recognised as one, and hci_find_irk_by_addr() then searches for an identity address that does not match it either. Copy the type along with the address. The assignment used to be unconditional just below this block and covered both paths; it moved into hci_conn_add_unset(), which the reuse path does not go through. Cc: stable@vger.kernel.org Fixes: 14b06c3a88f7 ("Bluetooth: HCI: Always use the identity address when initializing a connection") Assisted-by: Claude:claude-opus-5 Signed-off-by: Radek Podgorny Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index c9466cb2c7c0..fa72cf8aaa7a 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -1518,7 +1518,15 @@ struct hci_conn *hci_connect_le(struct hci_dev *hdev, bdaddr_t *dst, } if (conn) { + /* dst may just have been swapped for the peer's RPA above, and + * dst_type describes dst -- it has to travel with it. Leaving + * the identity type behind makes the pair describe a peer that + * does not exist, and nothing downstream repairs it: + * hci_bdaddr_is_rpa() tests the type before the address, so + * the RPA is never treated as one. + */ bacpy(&conn->dst, dst); + conn->dst_type = dst_type; } else { conn = hci_conn_add_unset(hdev, LE_LINK, dst, dst_type, role); if (IS_ERR(conn)) From 801fb950cae7048eb7d83b18857d1ca37b8cd5a4 Mon Sep 17 00:00:00 2001 From: Juan Perdomo Date: Sat, 12 Sep 2026 23:09:45 -0400 Subject: [PATCH 1750/2366] Bluetooth: RFCOMM: avoid socket lock inversion in listener cleanup rfcomm_sock_cleanup_listen() closes unaccepted child sockets through rfcomm_sock_close(), which takes the child socket lock before rfcomm_dlc_close() acquires rfcomm_mutex. The RFCOMM worker takes these locks in reverse order while handling connections and DLC state changes, so lockdep reports a possible deadlock. Close dequeued children without taking their socket lock. The accept queue owns a reference to each child, and bt_accept_dequeue() locks the child while unlinking it and clearing its parent pointer. Dropping the child lock makes it important to prevent a concurrent rfcomm_connect_ind() from enqueueing a new child after cleanup observes an empty queue. Set a listening socket to BT_CLOSED while its lock is still held, before dropping the lock and draining the queue. The state check in rfcomm_connect_ind() then rejects new children once cleanup starts. Reported-by: syzbot+0cece8fa7d83523f47a3@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0cece8fa7d83523f47a3 Fixes: b7ce436a5d79 ("Bluetooth: switch to lock_sock in RFCOMM") Signed-off-by: Juan Perdomo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/sock.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index 958081adb9b5..e2486bc11cbc 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -242,9 +242,7 @@ static void __rfcomm_sock_close(struct sock *sk) */ static void rfcomm_sock_close(struct sock *sk) { - lock_sock(sk); __rfcomm_sock_close(sk); - release_sock(sk); } static void rfcomm_sock_init(struct sock *sk, struct sock *parent) @@ -905,6 +903,7 @@ static int rfcomm_sock_compat_ioctl(struct socket *sock, unsigned int cmd, unsig static int rfcomm_sock_shutdown(struct socket *sock, int how) { struct sock *sk = sock->sk; + bool cleanup_listen = false; int err = 0; BT_DBG("sock %p, sk %p", sock, sk); @@ -915,9 +914,17 @@ static int rfcomm_sock_shutdown(struct socket *sock, int how) lock_sock(sk); if (!sk->sk_shutdown) { sk->sk_shutdown = SHUTDOWN_MASK; + if (sk->sk_state == BT_LISTEN) { + /* Block new children before cleaning up without sk lock. */ + sk->sk_state = BT_CLOSED; + cleanup_listen = true; + } release_sock(sk); - __rfcomm_sock_close(sk); + if (cleanup_listen) + rfcomm_sock_cleanup_listen(sk); + else + __rfcomm_sock_close(sk); lock_sock(sk); if (sock_flag(sk, SOCK_LINGER) && sk->sk_lingertime && From 8861db305103107199b1426f25fde1fb6d465583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 3 Sep 2026 13:43:38 +0200 Subject: [PATCH 1751/2366] selinux: always fill AVC decision in avc_has_perm_noaudit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit avc_has_perm_noaudit() is documented to return a copy of the access decision in @avd, but its early return for an empty requested permission set leaves the buffer untouched. All callers pass an uninitialized stack variable and afterwards feed it to avc_audit(), and the inode hook even stores it in the per-task decision cache. Fill in a deny-all, audit-all decision, similar to avd_init(), so every caller receives a defined value at no cost on the hot path. Cc: stable@vger.kernel.org Fixes: e6f2f381e4015386 ("selinux: replace BUG_ONs with WARN_ONs in avc.c") Signed-off-by: Christian Göttsche Reviewed-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/avc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/security/selinux/avc.c b/security/selinux/avc.c index a9401d6c2e5f..560a82c6d682 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -1149,8 +1149,11 @@ inline int avc_has_perm_noaudit(u32 ssid, u32 tsid, u32 denied; struct avc_node *node; - if (WARN_ON(!requested)) + if (WARN_ON(!requested)) { + /* Provide a deny-all, audit-all decision to the caller. */ + *avd = (struct av_decision){ .auditdeny = 0xffffffff }; return -EACCES; + } rcu_read_lock(); node = avc_lookup(ssid, tsid, tclass); From 8cd92f77ae4f5371a7d581f8324c24919670b304 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 21 Aug 2026 07:44:42 +0100 Subject: [PATCH 1752/2366] KVM: arm64: vgic-its: Free the caches when GITS_BASER changes A guest that disables the ITS and re-points or shrinks GITS_BASER with VALID still set keeps the devices and collections it mapped against the old table, as KVM frees them only when VALID is cleared. The contents of the table are IMPLEMENTATION DEFINED, so a write that gives GITS_BASER a different address or size may lose whatever the old value described. Free the list whenever the stored value changes, and drop the translation cache with it. The cache is not empty just because the ITS is disabled: its->enabled is written under the cmd_lock, while vgic_its_resolve_lpi() tests it under the its_lock, so an injection can still cache an entry after the ITS was disabled. Hence the invalidation inside the its_lock section. Test for a change rather than a write: its_restore_enable() rewrites GITS_BASER from its probe-time cache on resume, and KVM reports GITS_TYPER.HCC as 0, so nothing re-maps the boot CPU's collection afterwards. Fixes: 36d6961c2b481 ("KVM: arm/arm64: vgic-its: Free caches when GITS_BASER Valid bit is cleared") Suggested-by: Marc Zyngier Link: https://lore.kernel.org/all/87ecg9owwa.wl-maz@kernel.org/ Signed-off-by: Fuad Tabba Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260821064445.615838-2-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/vgic/vgic-its.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 9e782a4fea7e..ab89b0138efd 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -1658,7 +1658,7 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, unsigned long val) { const struct vgic_its_abi *abi = vgic_its_get_abi(its); - u64 entry_size, table_type; + u64 old, entry_size, table_type; u64 reg, *regptr, clearbits = 0; /* When GITS_CTLR.Enable is 1, we ignore write accesses. */ @@ -1681,7 +1681,9 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, return; } - reg = update_64bit_reg(*regptr, addr & 7, len, val); + old = *regptr; + + reg = update_64bit_reg(old, addr & 7, len, val); reg &= ~GITS_BASER_RO_MASK; reg &= ~clearbits; @@ -1691,7 +1693,8 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, *regptr = reg; - if (!(reg & GITS_BASER_VALID)) { + /* The ITS driver rewrites an unchanged GITS_BASER on resume. */ + if (reg != old) { /* Take the its_lock to prevent a race with a save/restore */ mutex_lock(&its->its_lock); switch (table_type) { @@ -1702,6 +1705,8 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, vgic_its_free_collection_list(kvm, its); break; } + /* A concurrent injection may have cached a translation. */ + vgic_its_invalidate_cache(its); mutex_unlock(&its->its_lock); } } From 30908e7272479b6453745022abb9f2eb9c9933f7 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 21 Aug 2026 07:44:43 +0100 Subject: [PATCH 1753/2366] Revert "KVM: arm64: vgic-its: Don't save collections the table cannot hold" This reverts commit 9b10fb74e4b661543d188701bd4d024fc5c18f58. Freeing the collections when GITS_BASER changes removes the state this check rejected: vgic_its_cmd_handle_mapi(), vgic_its_cmd_handle_mapc() and vgic_its_restore_cte() all validate the ID against the current table before allocating, and the table can no longer change under the list. What remains is a collection whose entry is not backed by a memslot, which the write fails on anyway, so the check costs a save userspace should be able to issue reliably and buys nothing. The reverted commit credited the check with bounding the walk as well. It stays bounded without it: collection IDs are unique and each is below the table's capacity, so the list cannot be longer than the table. Suggested-by: Marc Zyngier Link: https://lore.kernel.org/all/87ecg9owwa.wl-maz@kernel.org/ Signed-off-by: Fuad Tabba Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260821064445.615838-3-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/vgic/vgic-its.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index ab89b0138efd..313bf9e802bf 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -2546,9 +2546,6 @@ static int vgic_its_save_collection_table(struct vgic_its *its) max_size = GITS_BASER_NR_PAGES(baser) * SZ_64K; list_for_each_entry(collection, &its->collection_list, coll_list) { - if (!vgic_its_check_id(its, baser, collection->collection_id, NULL)) - return -EINVAL; - ret = vgic_its_save_cte(its, collection, gpa); if (ret) return ret; From cc5d96036e01ac330d24b2f0c336d60f82ab4930 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 21 Aug 2026 07:44:44 +0100 Subject: [PATCH 1754/2366] KVM: arm64: vgic-its: Skip unreachable devices instead of failing the save vgic_its_save_device_tables() aborts with -EINVAL when a device's entry falls outside the device table, which a guest can arrange on its own: an indirect table lets it clear an L1 entry's valid bit without touching GITS_BASER. That fails a save userspace should be able to issue reliably. Skip the device instead, and point the saved DTE chain past it, as commit ad1e686e2378d ("KVM: arm64: vgic-its: Point saved ITEs at the next valid entry") does for ITEs. compute_next_devid_offset() takes the next device off the list whether or not it was saved, so the predecessor would otherwise point at an entry the save never wrote. Restore follows that offset while it stays inside the table being scanned: within an L2 block, or anywhere in a flat table. Both need userspace to remove a memslot under the table, since dropping an L1 entry takes the whole block with it and scan_its_table() stops at the block boundary. Fixes: 57a9a117154c9 ("KVM: arm64: vgic-its: Device table save/restore") Suggested-by: Marc Zyngier Link: https://lore.kernel.org/all/86bjaz5s6v.wl-maz@kernel.org/ Signed-off-by: Fuad Tabba Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260821064445.615838-4-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/vgic/vgic-its.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 313bf9e802bf..0904ae850c35 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -2024,18 +2024,22 @@ static int vgic_its_attr_regs_access(struct kvm_device *dev, return ret; } -static u32 compute_next_devid_offset(struct list_head *h, +static u32 compute_next_devid_offset(struct vgic_its *its, u64 baser, struct its_device *dev) { - struct its_device *next; - u32 next_offset; + struct its_device *next = dev; - if (list_is_last(&dev->dev_list, h)) - return 0; - next = list_next_entry(dev, dev_list); - next_offset = next->device_id - dev->device_id; + /* + * Point at the next device vgic_its_save_device_tables() saves. It + * sorts device_list first, so the subtraction cannot underflow. + */ + list_for_each_entry_continue(next, &its->device_list, dev_list) { + if (vgic_its_check_id(its, baser, next->device_id, NULL)) + return min_t(u32, next->device_id - dev->device_id, + VITS_DTE_MAX_DEVID_OFFSET); + } - return min_t(u32, next_offset, VITS_DTE_MAX_DEVID_OFFSET); + return 0; } static u32 compute_next_eventid_offset(struct list_head *h, struct its_ite *ite) @@ -2276,17 +2280,18 @@ static int vgic_its_restore_itt(struct vgic_its *its, struct its_device *dev) * vgic_its_save_dte - Save a device table entry at a given GPA * * @its: ITS handle + * @baser: GITS_BASER the caller is saving against * @dev: ITS device * @ptr: GPA */ -static int vgic_its_save_dte(struct vgic_its *its, struct its_device *dev, - gpa_t ptr) +static int vgic_its_save_dte(struct vgic_its *its, u64 baser, + struct its_device *dev, gpa_t ptr) { u64 val, itt_addr_field; u32 next_offset; itt_addr_field = dev->itt_addr >> 8; - next_offset = compute_next_devid_offset(&its->device_list, dev); + next_offset = compute_next_devid_offset(its, baser, dev); val = (1ULL << KVM_ITS_DTE_VALID_SHIFT | ((u64)next_offset << KVM_ITS_DTE_NEXT_SHIFT) | (itt_addr_field << KVM_ITS_DTE_ITTADDR_SHIFT) | @@ -2385,15 +2390,16 @@ static int vgic_its_save_device_tables(struct vgic_its *its) int ret; gpa_t eaddr; + /* Don't fail a save that userspace must be able to issue. */ if (!vgic_its_check_id(its, baser, dev->device_id, &eaddr)) - return -EINVAL; + continue; ret = vgic_its_save_itt(its, dev); if (ret) return ret; - ret = vgic_its_save_dte(its, dev, eaddr); + ret = vgic_its_save_dte(its, baser, dev, eaddr); if (ret) return ret; } From 6f182db39fb00548c26d689163ceb2fe4a802793 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 21 Aug 2026 07:44:45 +0100 Subject: [PATCH 1755/2366] KVM: arm64: selftests: Add ITS table save tests Cover the two ways a guest can leave a table that KVM_DEV_ARM_ITS_SAVE_TABLES has to cope with: a GITS_BASER write that changes the table, which drops the collections it described, and a device whose L2 block the guest invalidated, which the save skips. Each case then resets and restores, which is what the save exists for. Both fail without the preceding patches. The first save returns -EINVAL, and the second saves a DTE pointing 8192 entries ahead, at an entry the save never wrote. Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Fuad Tabba Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260821064445.615838-5-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/arm64/vgic_its_save.c | 441 ++++++++++++++++++ 2 files changed, 442 insertions(+) create mode 100644 tools/testing/selftests/kvm/arm64/vgic_its_save.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 96bab7002d39..6a1482e3a286 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -189,6 +189,7 @@ TEST_GEN_PROGS_arm64 += arm64/stage2_block_transitions TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config TEST_GEN_PROGS_arm64 += arm64/vgic_init TEST_GEN_PROGS_arm64 += arm64/vgic_irq +TEST_GEN_PROGS_arm64 += arm64/vgic_its_save TEST_GEN_PROGS_arm64 += arm64/vgic_lpi_stress TEST_GEN_PROGS_arm64 += arm64/vgic_v5 TEST_GEN_PROGS_arm64 += arm64/vpmu_counter_access diff --git a/tools/testing/selftests/kvm/arm64/vgic_its_save.c b/tools/testing/selftests/kvm/arm64/vgic_its_save.c new file mode 100644 index 000000000000..864da01539f3 --- /dev/null +++ b/tools/testing/selftests/kvm/arm64/vgic_its_save.c @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * vgic_its_save - KVM_DEV_ARM_ITS_SAVE_TABLES against tables a guest broke. + * + * Both cases are reachable by a guest on its own, and neither may fail a save + * that userspace has to be able to issue: + * + * - Changing GITS_BASER drops the collections it described, so the save + * writes nothing but the terminating invalid entry. + * - A device the device table can no longer address is skipped, and the saved + * DTE chain skips it too rather than pointing at an entry never written. + * + * Both cases then reset and restore, which is what the save exists for. + * + * Copyright (c) 2026 Google LLC + * Author: Fuad Tabba + */ + +#include +#include +#include +#include + +#include "kvm_util.h" +#include "gic.h" +#include "gic_v3.h" +#include "gic_v3_its.h" +#include "processor.h" +#include "ucall.h" +#include "vgic.h" + +#define TEST_MEMSLOT_INDEX 1 + +/* All three ITS table entry sizes are 8 bytes in ABI 0. */ +#define ESZ 8 +#define ENTRIES_PER_PAGE (SZ_64K / ESZ) + +/* CTE and DTE layout, mirroring KVM's KVM_ITS_* in arch/arm64/kvm/vgic/vgic.h */ +#define CTE_VALID_MASK BIT_ULL(63) +#define DTE_VALID_MASK BIT_ULL(63) +#define DTE_NEXT_SHIFT 49 +#define DTE_NEXT_MASK GENMASK_ULL(62, 49) + +/* L1 entry of an indirect table: valid bit plus a 64K aligned L2 address. */ +#define L1E_VALID_MASK BIT_ULL(63) +#define L1E_ADDR_MASK GENMASK_ULL(51, 16) + +#define GITS_BASER_PAGES_MASK GENMASK_ULL(7, 0) + +#define POISON 0xdeadbeefdeadbeefULL + +/* The collection table starts at two pages and is shrunk to one. */ +#define COLL_TBL_PAGES 2 +#define COLL_TBL_SZ (COLL_TBL_PAGES * SZ_64K) + +/* One more collection than the shrunken table can hold. */ +#define NR_COLLECTIONS (ENTRIES_PER_PAGE + 1) + +/* Two devices, one per L2 block of the indirect device table. */ +#define DEVICE_A_ID 0 +#define DEVICE_B_ID ENTRIES_PER_PAGE + +/* + * its_send_mapd_cmd() encodes ilog2(itt_size) - 1 as num_eventid_bits, and + * vgic_its_restore_itt() scans BIT_ULL(num_eventid_bits) * ESZ, so the size + * handed to MAPD has to match the ITT allocated for it. + */ +#define ITT_EVENTID_BITS 13 +#define ITT_MAPD_SIZE BIT_ULL(ITT_EVENTID_BITS + 1) +#define ITT_SZ (BIT_ULL(ITT_EVENTID_BITS) * ESZ) + +static struct kvm_vm *vm; +static struct kvm_vcpu *vcpu; +static int its_fd; +static gpa_t gpa_base; + +static struct test_data { + gpa_t device_table; + gpa_t collection_table; + gpa_t cmdq_base; + void *cmdq_base_va; + + gpa_t lpi_prop_table; + gpa_t lpi_pend_table; + + void *device_l1_va; + gpa_t device_l2[2]; + gpa_t itt_tables; +} test_data; + +static unsigned long its_baser_offset(unsigned int type) +{ + int i; + + for (i = 0; i < GITS_BASER_NR_REGS; i++) { + unsigned long offset = GITS_BASER + (i * sizeof(u64)); + u64 baser = readq_relaxed(GITS_BASE_GVA + offset); + + if (GITS_BASER_TYPE(baser) == type) + return offset; + } + + GUEST_FAIL("Couldn't find an ITS BASER of type %u", type); + return -1; +} + +static void its_set_enable(bool enable) +{ + u32 ctlr = readl_relaxed(GITS_BASE_GVA + GITS_CTLR); + + if (enable) + ctlr |= GITS_CTLR_ENABLE; + else + ctlr &= ~GITS_CTLR_ENABLE; + + writel_relaxed(ctlr, GITS_BASE_GVA + GITS_CTLR); +} + +/* + * Shrink the collection table to a single page, leaving VALID set. BASER + * writes are ignored while the ITS is enabled. + */ +static void guest_shrink_coll_table(void) +{ + unsigned long offset = its_baser_offset(GITS_BASER_TYPE_COLLECTION); + u64 baser; + + its_set_enable(false); + + baser = readq_relaxed(GITS_BASE_GVA + offset); + baser &= ~GITS_BASER_PAGES_MASK; + writeq_relaxed(baser, GITS_BASE_GVA + offset); +} + +static void guest_baser_change(void) +{ + u32 coll_id; + + gic_init(GIC_V3, 1); + gic_rdist_enable_lpis(test_data.lpi_prop_table, SZ_64K, + test_data.lpi_pend_table); + + its_init(test_data.collection_table, COLL_TBL_SZ, + test_data.device_table, SZ_64K, + test_data.cmdq_base, SZ_64K); + + for (coll_id = 0; coll_id < NR_COLLECTIONS; coll_id++) + its_send_mapc_cmd(test_data.cmdq_base_va, 0, coll_id, true); + + guest_shrink_coll_table(); + + GUEST_DONE(); +} + +/* Turn the already installed device table into an indirect one. */ +static void guest_make_device_table_indirect(void) +{ + unsigned long offset = its_baser_offset(GITS_BASER_TYPE_DEVICE); + u64 baser; + + its_set_enable(false); + + baser = readq_relaxed(GITS_BASE_GVA + offset); + writeq_relaxed(baser | GITS_BASER_INDIRECT, GITS_BASE_GVA + offset); + + its_set_enable(true); +} + +static void guest_unreachable_device(void) +{ + u64 *l1; + + gic_init(GIC_V3, 1); + gic_rdist_enable_lpis(test_data.lpi_prop_table, SZ_64K, + test_data.lpi_pend_table); + + its_init(test_data.collection_table, SZ_64K, + test_data.device_table, SZ_64K, + test_data.cmdq_base, SZ_64K); + + guest_make_device_table_indirect(); + + /* Both L2 blocks present, so both MAPDs are in range. */ + l1 = test_data.device_l1_va; + l1[0] = L1E_VALID_MASK | (test_data.device_l2[0] & L1E_ADDR_MASK); + l1[1] = L1E_VALID_MASK | (test_data.device_l2[1] & L1E_ADDR_MASK); + + its_send_mapd_cmd(test_data.cmdq_base_va, DEVICE_A_ID, + test_data.itt_tables, ITT_MAPD_SIZE, true); + its_send_mapd_cmd(test_data.cmdq_base_va, DEVICE_B_ID, + test_data.itt_tables + ITT_SZ, ITT_MAPD_SIZE, true); + + /* + * Drop the block holding device B. No ITS command and no GITS_BASER + * write is involved, so nothing tells KVM the device is now + * unreachable. + */ + l1[1] = 0; + + GUEST_DONE(); +} + +static void run_guest(void) +{ + struct ucall uc; + + vcpu_run(vcpu); + switch (get_ucall(vcpu, &uc)) { + case UCALL_DONE: + break; + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + break; + default: + TEST_FAIL("Unexpected ucall: %lu", uc.cmd); + } +} + +static int save_tables(void) +{ + return __kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL, + KVM_DEV_ARM_ITS_SAVE_TABLES, NULL); +} + +static u64 its_reg_get(unsigned long offset) +{ + u64 val; + + kvm_device_attr_get(its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, offset, + &val); + return val; +} + +static void its_reg_set(unsigned long offset, u64 val) +{ + kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, offset, + &val); +} + +/* + * What a migration target does with the saved tables, in the order + * Documentation/virt/kvm/devices/arm-vgic-its.rst gives: the GITS_ registers + * first, then the tables. The reset in between clears GITS_BASER.Valid, + * which is why the registers have to be written back before the restore. + */ +static void reset_and_restore_tables(void) +{ + u64 baser[GITS_BASER_NR_REGS]; + int ret, i; + + for (i = 0; i < GITS_BASER_NR_REGS; i++) + baser[i] = its_reg_get(GITS_BASER + (i * sizeof(u64))); + + ret = __kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL, + KVM_DEV_ARM_ITS_CTRL_RESET, NULL); + TEST_ASSERT(!ret, "Expected the reset to succeed, got ret %d errno %d", + ret, errno); + + for (i = 0; i < GITS_BASER_NR_REGS; i++) + its_reg_set(GITS_BASER + (i * sizeof(u64)), baser[i]); + + ret = __kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL, + KVM_DEV_ARM_ITS_RESTORE_TABLES, NULL); + TEST_ASSERT(!ret, "Expected the restore to succeed, got ret %d errno %d", + ret, errno); +} + +static void poison_range(gpa_t base, size_t size) +{ + u64 *entry = addr_gpa2hva(vm, base); + size_t i; + + for (i = 0; i < size / ESZ; i++) + entry[i] = POISON; +} + +static void setup_memslot(size_t sz) +{ + size_t pages = sz / vm->page_size; + + gpa_base = ((vm_compute_max_gfn(vm) + 1) * vm->page_size) - sz; + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa_base, + TEST_MEMSLOT_INDEX, pages, 0); +} + +static gpa_t alloc_64k(size_t nr) +{ + size_t pages_per_64k = vm_calc_num_guest_pages(vm->mode, SZ_64K); + gpa_t gpa = vm_phy_pages_alloc(vm, nr * pages_per_64k, gpa_base, + TEST_MEMSLOT_INDEX); + + TEST_ASSERT(IS_ALIGNED(gpa, SZ_64K), + "Allocation at 0x%lx is not 64K aligned, GITS_BASER cannot address it", + gpa); + return gpa; +} + +static void map_to_guest(gpa_t gpa, size_t nr) +{ + size_t pages_per_64k = vm_calc_num_guest_pages(vm->mode, SZ_64K); + + virt_map(vm, gpa, gpa, nr * pages_per_64k); +} + +static void setup_common(void) +{ + test_data.cmdq_base = alloc_64k(1); + map_to_guest(test_data.cmdq_base, 1); + test_data.cmdq_base_va = (void *)test_data.cmdq_base; + + test_data.lpi_prop_table = alloc_64k(1); + test_data.lpi_pend_table = alloc_64k(1); +} + +static void teardown(void) +{ + close(its_fd); + kvm_vm_free(vm); + memset(&test_data, 0, sizeof(test_data)); +} + +/* + * A GITS_BASER write that changes the table drops the collections it + * described. The save then has an empty list, so it writes the terminating + * invalid entry and nothing else. + */ +static void test_baser_change_drops_collections(void) +{ + u64 *cte; + int ret, i; + + pr_info("Testing that a GITS_BASER change drops the collections\n"); + + vm = vm_create_with_one_vcpu(&vcpu, guest_baser_change); + setup_memslot((4 + COLL_TBL_PAGES) * SZ_64K); + its_fd = vgic_its_setup(vm); + + test_data.device_table = alloc_64k(1); + test_data.collection_table = alloc_64k(COLL_TBL_PAGES); + setup_common(); + + sync_global_to_guest(vm, test_data); + run_guest(); + + /* Anything KVM writes is then the only thing that changed. */ + poison_range(test_data.collection_table, COLL_TBL_SZ); + + ret = save_tables(); + TEST_ASSERT(!ret, "Expected the save to succeed, got %d errno %d", + ret, errno); + + cte = addr_gpa2hva(vm, test_data.collection_table); + + /* + * Finding the terminator at the head of the table is also what proves + * the reads below landed in the saved table rather than elsewhere. + */ + TEST_ASSERT(le64toh(cte[0]) == 0, + "CTE 0: expected the terminating invalid entry, got 0x%llx", + (unsigned long long)le64toh(cte[0])); + + for (i = 1; i < COLL_TBL_SZ / ESZ; i++) + TEST_ASSERT(cte[i] == POISON, + "CTE %d: expected it untouched, got 0x%llx", + i, (unsigned long long)cte[i]); + + reset_and_restore_tables(); + + teardown(); +} + +/* + * A device whose L2 block the guest dropped is skipped by the save, and the + * DTE chain skips it too: left alone, the surviving device would point at an + * entry the save never wrote. + */ +static void test_unreachable_device_skipped(void) +{ + u64 dte; + int ret; + + pr_info("Testing that an unreachable device is skipped by the save\n"); + + vm = vm_create_with_one_vcpu(&vcpu, guest_unreachable_device); + setup_memslot(9 * SZ_64K); + its_fd = vgic_its_setup(vm); + + test_data.device_table = alloc_64k(1); + test_data.collection_table = alloc_64k(1); + test_data.device_l2[0] = alloc_64k(1); + test_data.device_l2[1] = alloc_64k(1); + test_data.itt_tables = alloc_64k(2); + setup_common(); + + map_to_guest(test_data.device_table, 1); + test_data.device_l1_va = (void *)test_data.device_table; + + sync_global_to_guest(vm, test_data); + run_guest(); + + poison_range(test_data.device_l2[0], SZ_64K); + poison_range(test_data.device_l2[1], SZ_64K); + + ret = save_tables(); + TEST_ASSERT(!ret, "Expected the save to succeed, got %d errno %d", + ret, errno); + + dte = le64toh(*(u64 *)addr_gpa2hva(vm, test_data.device_l2[0])); + + /* Device A is still reachable, so it is saved. */ + TEST_ASSERT(dte & DTE_VALID_MASK, + "Device A: expected a valid DTE, got 0x%llx", + (unsigned long long)dte); + + /* + * Device B is the only device after it and was skipped, so nothing + * follows A in the saved chain. + */ + TEST_ASSERT(FIELD_GET(DTE_NEXT_MASK, dte) == 0, + "Device A: expected no next device, got offset %llu", + (unsigned long long)FIELD_GET(DTE_NEXT_MASK, dte)); + + /* And nothing was written into the block the guest dropped. */ + TEST_ASSERT(*(u64 *)addr_gpa2hva(vm, test_data.device_l2[1]) == POISON, + "Device B: expected its entry untouched"); + + reset_and_restore_tables(); + + teardown(); +} + +int main(void) +{ + TEST_REQUIRE(kvm_supports_vgic_v3()); + + test_baser_change_drops_collections(); + test_unreachable_device_skipped(); + + pr_info("All ok!\n"); + return 0; +} From 38b70fc453c3112f1a62583b89903ae41116cc27 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Tue, 1 Sep 2026 18:28:59 +0100 Subject: [PATCH 1756/2366] KVM: arm64: Fix spurious warning for benign stage 2 teardown race kvmtool was used to establish an L1 guest with 8 CPUs and 8 GiB of RAM, an L2 guest with 4 CPUs and 4 GiB of RAM and an L3 guest with 2 CPUs and 2 GiB of RAM, all of which was then exited. Under memory pressure in the L0 host warnings were observed due to migration triggered by compaction: WARNING: arch/arm64/kvm/mmu.c:336 at __unmap_stage2_range+0x64/0x80, CPU#5: kcompactd0/66 Which was, in turn, triggered by an MMU notifier for the host invalidation: mmu_notifier_invalidate_range_start() -> ... -> kvm_mmu_notifier_invalidate_range_start() -> kvm_mmu_unmap_gfn_range() -> kvm_unmap_gfn_range() -> kvm_nested_s2_unmap() -> kvm_stage2_unmap_range() -> __unmap_stage2_range() -> stage2_apply_range() <- -EINVAL, triggering a WARN_ON() Racing with L0's teardown of stage 2 page tables: exit_mm() -> mmput() -> __mmput() -> exit_mmap() -> mmu_notifier_release() -> ... -> kvm_mmu_notifier_release() -> kvm_flush_shadow_all() -> kvm_arch_flush_shadow_all() -> kvm_free_stage2_pgd() -> [ acquire kvm->mmu_lock for write ] -> mmu->pgt = NULL [ among other tasks ] -> [ release kvm->mmu_lock for write ] It turns out there is a benign race resulting in a spurious warning: Thread A - notify: migration | Thread B - notify: release -------------------------------|--------------------------------- < kvm->mmu_lock held > | stage2_apply_range() | get mmu->pgt, check !NULL | ... | kvm_arch_flush_shadow_all() cond_resched_rwlock_write(); | < contend, sleep kvm->mmu_lock > < drop kvm->mmu_lock > | < acquire kvm->mmu_lock> | ... | kvm_free_stage2_pgd() | mmu->pgt = NULL | < invalidate MMU > | ... | < release kvm->mmu_lock > [ scheduled ] | stage2_apply_range() | < loop to next > | get, mmu->pgt, check !NULL | is NULL, return -EINVAL | __unmap_stage2_range() | WARN_ON(-EINVAL) <--- entirely spurious - the race was handled correctly. Fix the spurious warning by updating stage2_apply_range() to no longer treat concurrent PGT teardown on lock release as an error - whether the walker is tearing down page tables or doing something else this is a legitimate reason to abort the operation without error. This keeps the warning in place for all other circumstances. In practice only __unmap_stage2_range() actually does anything with the error so this only impacts that. Fixes: ec14c272408a ("KVM: arm64: nv: Unmap/flush shadow stage 2 page tables") Cc: stable@vger.kernel.org Reviewed-by: Yuan Yao Reviewed-by: Marc Zyngier Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260901-kvm-arm-nested-virt-fix-v3-1-b154676f7e4c@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/kvm/mmu.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 9ba86450fe4a..2d44cd6a5aed 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -59,27 +59,36 @@ static phys_addr_t stage2_range_addr_end(phys_addr_t addr, phys_addr_t end) * long will also starve other vCPUs. We have to also make sure that the page * tables are not freed while we released the lock. */ -static int stage2_apply_range(struct kvm_s2_mmu *mmu, phys_addr_t addr, +static int stage2_apply_range(struct kvm_s2_mmu *mmu, phys_addr_t start, phys_addr_t end, int (*fn)(struct kvm_pgtable *, u64, u64), bool resched) { struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu); + bool lock_dropped = false; + phys_addr_t addr = start; int ret; u64 next; do { struct kvm_pgtable *pgt = mmu->pgt; + /* + * We may be raced on PGT teardown when we release the + * kvm->mmu_lock. That's fine as the PGT is legitimately no + * longer present. + */ if (!pgt) - return -EINVAL; + return lock_dropped ? 0 : -EINVAL; next = stage2_range_addr_end(addr, end); ret = fn(pgt, addr, next - addr); if (ret) break; - if (resched && next != end) + if (resched && next != end) { cond_resched_rwlock_write(&kvm->mmu_lock); + lock_dropped = true; + } } while (addr = next, addr != end); return ret; From 4c74e233cdedd11592775fae2a6243e67ca3f891 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Tue, 1 Sep 2026 18:29:00 +0100 Subject: [PATCH 1757/2366] KVM: arm64: nv: Fix null ptr deref on nested wp/unmap, teardown race Commit 7270cc9157f4 ("KVM: arm64: nv: Handle VNCR_EL2 invalidation from MMU notifiers") introduced VNCR_EL2 invalidation in both kvm_nested_s2_unmap() and kvm_nested_s2_wp(). However at the point of this being performed concurrent stage 2 teardown of a nested guest can cause kvm->arch.mmu.pgt to be set to NULL. This happens in kvm_flush_shadow_all() -> kvm_arch_flush_shadow_all() -> kvm_free_stage2_pgd() and is performed under the kvm->mmu_lock. Commit ec14c272408a ("KVM: arm64: nv: Unmap/flush shadow stage 2 page tables") introduced the teardown of the entire nested MMU range, which then invokes stage2_apply_range() with resched=true: mmu_notifier_invalidate_range_start() -> ... -> kvm_mmu_notifier_invalidate_range_start() -> kvm_mmu_unmap_gfn_range() -> kvm_unmap_gfn_range() -> kvm_nested_s2_unmap() -> kvm_stage2_unmap_range() -> __unmap_stage2_range() -> stage2_apply_range() This means that stage2_apply_range() can drop the kvm->mmu_lock and thus concurrent progress can be made in lockstep with kvm_arch_flush_shadow_all(). If kvm_arch_flush_shadow_all() advances ahead of stage2_apply_range() and completes its operation it guarantees a NULL pointer deref. Since kvm_free_stage2_pgd() is performed under the kvm->mmu_lock this will either be observed NULL or not and serialised against kvm_free_stage2_pgd(). Resolve the issue by abstracting the invalidation to a new function, kvm_invalidate_vncr_ipa_all(), and check that the pgt is non-NULL before dereferencing it. Fixes: 7270cc9157f4 ("KVM: arm64: nv: Handle VNCR_EL2 invalidation from MMU notifiers") Cc: stable@vger.kernel.org Reviewed-by: Marc Zyngier Signed-off-by: Lorenzo Stoakes (ARM) Tested-by: Jonathan Davies Link: https://patch.msgid.link/20260901-kvm-arm-nested-virt-fix-v3-2-b154676f7e4c@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/kvm/nested.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index 3c4fc566eafc..a0808391a456 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -1260,6 +1260,17 @@ void kvm_handle_s1e2_tlbi(struct kvm_vcpu *vcpu, u32 inst, u64 val) invalidate_vncr_va(vcpu->kvm, &scope); } +static void kvm_invalidate_vncr_ipa_all(struct kvm *kvm) +{ + struct kvm_pgtable *pgt = kvm->arch.mmu.pgt; + + lockdep_assert_held_write(&kvm->mmu_lock); + + /* if the mmu lock was dropped, pgt teardown may have raced. */ + if (pgt) + kvm_invalidate_vncr_ipa(kvm, 0, BIT(pgt->ia_bits)); +} + void kvm_nested_s2_wp(struct kvm *kvm) { int i; @@ -1276,7 +1287,7 @@ void kvm_nested_s2_wp(struct kvm *kvm) kvm_stage2_wp_range(mmu, 0, kvm_phys_size(mmu)); } - kvm_invalidate_vncr_ipa(kvm, 0, BIT(kvm->arch.mmu.pgt->ia_bits)); + kvm_invalidate_vncr_ipa_all(kvm); } void kvm_nested_s2_unmap(struct kvm *kvm, bool may_block) @@ -1295,7 +1306,7 @@ void kvm_nested_s2_unmap(struct kvm *kvm, bool may_block) kvm_stage2_unmap_range(mmu, 0, kvm_phys_size(mmu), may_block); } - kvm_invalidate_vncr_ipa(kvm, 0, BIT(kvm->arch.mmu.pgt->ia_bits)); + kvm_invalidate_vncr_ipa_all(kvm); } void kvm_nested_s2_flush(struct kvm *kvm) From 2a2eb10795a1e495aebc7f829ccecb72c05b4fd9 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 25 Aug 2026 09:59:45 +0100 Subject: [PATCH 1758/2366] KVM: arm64: Validate the SVE vector length in pkvm_vcpu_init_sve() pkvm_vcpu_init_sve() clamps only the upper bound of the host-provided sve_max_vl, so an invalid vector length reaches sve_state_size_from_vl() and the WARN_ON() there, which is fatal at EL2. The existing !sve_state_size test rejects such a length, but only after the macro has run. Check sve_vl_valid() before deriving the state size. A valid length cannot yield a zero size, so the !sve_state_size test goes with it. Fixes: 5db1bef93342 ("KVM: arm64: Track SVE state in the hypervisor vcpu structure") Reported-by: Stefan Teodorescu Reviewed-by: Marc Zyngier Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260825085948.1674721-2-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/pkvm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index 459bd9eb7e4b..627f13fc98d4 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -459,14 +459,15 @@ static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *h /* Limit guest vector length to the maximum supported by the host. */ sve_max_vl = min(READ_ONCE(host_vcpu->arch.sve_max_vl), kvm_host_sve_max_vl); - sve_state_size = sve_state_size_from_vl(sve_max_vl); sve_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sve_state)); - if (!sve_state || !sve_state_size) { + if (!sve_vl_valid(sve_max_vl) || !sve_state) { ret = -EINVAL; goto err; } + sve_state_size = sve_state_size_from_vl(sve_max_vl); + ret = hyp_pin_shared_mem(sve_state, sve_state + sve_state_size); if (ret) goto err; From a1b3c788ad31837e348075e93dbba3f447492776 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 25 Aug 2026 09:59:46 +0100 Subject: [PATCH 1759/2366] KVM: arm64: Do not clear VM-wide SVE feature on vCPU init failure pkvm_vcpu_init_sve() clears KVM_ARM_VCPU_SVE in kvm->arch.vcpu_features when it fails, but vcpu_has_sve() tests KVM_ARCH_FLAG_GUEST_HAS_SVE, which is left set. Later vCPUs on that VM then skip the SVE setup and register with a NULL sve_state, which the guest's first FP access hands to sve_load_state(). Return the error without touching vcpu_features. Fixes: 5db1bef93342 ("KVM: arm64: Track SVE state in the hypervisor vcpu structure") Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260825085948.1674721-3-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/pkvm.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index 627f13fc98d4..4857a11d4292 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -450,7 +450,7 @@ static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *h unsigned int sve_max_vl; size_t sve_state_size; void *sve_state; - int ret = 0; + int ret; if (!vcpu_has_feature(vcpu, KVM_ARM_VCPU_SVE)) { vcpu_clear_flag(vcpu, VCPU_SVE_FINALIZED); @@ -461,24 +461,19 @@ static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *h sve_max_vl = min(READ_ONCE(host_vcpu->arch.sve_max_vl), kvm_host_sve_max_vl); sve_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sve_state)); - if (!sve_vl_valid(sve_max_vl) || !sve_state) { - ret = -EINVAL; - goto err; - } + if (!sve_vl_valid(sve_max_vl) || !sve_state) + return -EINVAL; sve_state_size = sve_state_size_from_vl(sve_max_vl); ret = hyp_pin_shared_mem(sve_state, sve_state + sve_state_size); if (ret) - goto err; + return ret; vcpu->arch.sve_state = sve_state; vcpu->arch.sve_max_vl = sve_max_vl; return 0; -err: - clear_bit(KVM_ARM_VCPU_SVE, vcpu->kvm->arch.vcpu_features); - return ret; } static int vm_copy_id_regs(struct pkvm_hyp_vcpu *hyp_vcpu) From 0d62fbf34d8fe7a3ff56692d39fbb8e6e9bf15fb Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 25 Aug 2026 09:59:47 +0100 Subject: [PATCH 1760/2366] KVM: arm64: Key unpin_host_sve_state() on the state it unpins unpin_host_sve_state() gates on the VM's SVE feature bit, but what it unpins is the state pkvm_vcpu_init_sve() pinned. A vCPU that completed init has sve_state set exactly when that bit is set, so the two agree. Gate on sve_state, which is what is being unpinned. Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260825085948.1674721-4-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/pkvm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index 4857a11d4292..453d3fa4a420 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -398,10 +398,10 @@ static void unpin_host_sve_state(struct pkvm_hyp_vcpu *hyp_vcpu) { void *sve_state; - if (!vcpu_has_feature(&hyp_vcpu->vcpu, KVM_ARM_VCPU_SVE)) + sve_state = hyp_vcpu->vcpu.arch.sve_state; + if (!sve_state) return; - sve_state = hyp_vcpu->vcpu.arch.sve_state; hyp_unpin_shared_mem(sve_state, sve_state + vcpu_sve_state_size(&hyp_vcpu->vcpu)); } From 4f16c5fc8dc4c5596e3777ab9f449a54e3f85fd5 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 25 Aug 2026 09:59:48 +0100 Subject: [PATCH 1761/2366] KVM: arm64: Derive GUEST_HAS_SVE from the SVE feature bit at EL2 pkvm_init_features_from_host() takes KVM_ARCH_FLAG_GUEST_HAS_SVE and KVM_ARM_VCPU_SVE from the host separately, but pkvm_vcpu_init_sve() tests the bit while vcpu_has_sve() reads the flag. A host that sets the flag without the bit gets a vCPU with a NULL sve_state that the world switch loads the guest's SVE state from. Derive the flag from the bit, and drop the protected path's copy of the host's flag, which is dead code since protected VMs are not allowed SVE. Fixes: 41d6028e28bd ("KVM: arm64: Convert the SVE guest vcpu flag to a vm flag") Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260825085948.1674721-5-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/pkvm.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index 453d3fa4a420..bb3e0dc0676e 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -360,7 +360,7 @@ static void pkvm_init_features_from_host(struct pkvm_hyp_vm *hyp_vm, const struc if (test_bit(KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS, &host_arch_flags)) hyp_vm->kvm.arch.midr_el1 = host_kvm->arch.midr_el1; - return; + goto out; } if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_MTE)) @@ -379,13 +379,14 @@ static void pkvm_init_features_from_host(struct pkvm_hyp_vm *hyp_vm, const struc if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_PTRAUTH_GENERIC)) set_bit(KVM_ARM_VCPU_PTRAUTH_GENERIC, allowed_features); - if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_SVE)) { + if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_SVE)) set_bit(KVM_ARM_VCPU_SVE, allowed_features); - kvm->arch.flags |= host_arch_flags & BIT(KVM_ARCH_FLAG_GUEST_HAS_SVE); - } bitmap_and(kvm->arch.vcpu_features, host_kvm->arch.vcpu_features, allowed_features, KVM_VCPU_MAX_FEATURES); +out: + __assign_bit(KVM_ARCH_FLAG_GUEST_HAS_SVE, &kvm->arch.flags, + kvm_vcpu_has_feature(kvm, KVM_ARM_VCPU_SVE)); } static void unpin_host_vcpu(struct kvm_vcpu *host_vcpu) From 64dc6f1db7e620f2e9337bb181f305fb0561da79 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 29 Aug 2026 07:48:55 +0200 Subject: [PATCH 1762/2366] KVM: arm64: Return -EINVAL for an empty SMCCC filter range at base 0 kvm_smccc_set_filter() only rejects a range if its inclusive end, base + nr_functions - 1, is below base. That catches an empty range (nr_functions == 0) at every nonzero base, but at base 0 the end wraps to U32_MAX and KVM tries to insert [0, U32_MAX], which overlaps the reserved Arm Architecture Calls ranges. KVM_ARM_VM_SMCCC_FILTER then returns -EEXIST instead of the -EINVAL that the smccc_filter selftest expects for an empty range. Reject a zero function count explicitly. Tested with a userspace reproducer on an arm64 VHE host under QEMU TCG: EEXIST before, EINVAL after. Fixes: 821d935c87bc ("KVM: arm64: Introduce support for userspace SMCCC filtering") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Steffen Eiden Reviewed-by: Fuad Tabba Tested-by: Fuad Tabba Link: https://patch.msgid.link/20260829054856.70549-2-kmehltretter@gmail.com Signed-off-by: Oliver Upton --- arch/arm64/kvm/hypercalls.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/hypercalls.c b/arch/arm64/kvm/hypercalls.c index b11b8821c9fb..dfa25bb6f25d 100644 --- a/arch/arm64/kvm/hypercalls.c +++ b/arch/arm64/kvm/hypercalls.c @@ -185,7 +185,8 @@ static int kvm_smccc_set_filter(struct kvm *kvm, struct kvm_smccc_filter __user start = filter.base; end = start + filter.nr_functions - 1; - if (end < start || filter.action >= NR_SMCCC_FILTER_ACTIONS) + if (!filter.nr_functions || end < start || + filter.action >= NR_SMCCC_FILTER_ACTIONS) return -EINVAL; mutex_lock(&kvm->arch.config_lock); From 0a46eb5719fa57cc9d06025dcd8ba271643dee61 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 29 Aug 2026 07:48:56 +0200 Subject: [PATCH 1763/2366] KVM: arm64: selftests: Test empty SMCCC filter range at base 0 test_invalid_nr_functions() only checks an empty range at PSCI_0_2_FN64_CPU_ON, which KVM's end < start check happens to catch. It never exercised base 0, where the inclusive end wraps to U32_MAX instead. Add the base 0 case. Without the preceding fix it fails with EEXIST. Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Steffen Eiden Reviewed-by: Fuad Tabba Tested-by: Fuad Tabba Link: https://patch.msgid.link/20260829054856.70549-3-kmehltretter@gmail.com Signed-off-by: Oliver Upton --- tools/testing/selftests/kvm/arm64/smccc_filter.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/testing/selftests/kvm/arm64/smccc_filter.c b/tools/testing/selftests/kvm/arm64/smccc_filter.c index 21e41880261b..a41ed3e016ba 100644 --- a/tools/testing/selftests/kvm/arm64/smccc_filter.c +++ b/tools/testing/selftests/kvm/arm64/smccc_filter.c @@ -140,6 +140,10 @@ static void test_invalid_nr_functions(void) TEST_ASSERT(r < 0 && errno == EINVAL, "Attempt to filter 0 functions should return EINVAL"); + r = __set_smccc_filter(vm, 0, 0, KVM_SMCCC_FILTER_DENY); + TEST_ASSERT(r < 0 && errno == EINVAL, + "Attempt to filter 0 functions at base 0 should return EINVAL"); + kvm_vm_free(vm); } From 3a8c562892b96f35bba1e00d5e455a15963bbb92 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 8 Sep 2026 12:07:10 +0100 Subject: [PATCH 1764/2366] KVM: arm64: Transfer the hyp stack pages out of the host stage-2 fix_host_ownership() walks only the linear-map alias of each memblock region, and the per-CPU hyp stack, mapped in the private VA range for its guard page, has none. Walk each stack's VA range with the same walker. Fixes: 1a919b17ef012 ("KVM: arm64: Add guard pages for pKVM (protected nVHE) hypervisor stack") Reported-by: Hiroyuki Katsura Cc: stable@vger.kernel.org Signed-off-by: Fuad Tabba Reviewed-by: Vincent Donnefort Tested-by: Vincent Donnefort Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260908110713.1540304-2-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/setup.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/kvm/hyp/nvhe/setup.c b/arch/arm64/kvm/hyp/nvhe/setup.c index 75b00c323310..362f2891cb32 100644 --- a/arch/arm64/kvm/hyp/nvhe/setup.c +++ b/arch/arm64/kvm/hyp/nvhe/setup.c @@ -269,6 +269,16 @@ static int fix_host_ownership(void) return ret; } + /* The stacks sit in the private VA range, not the linear map. */ + for (i = 0; i < hyp_nr_cpus; i++) { + struct kvm_nvhe_init_params *params = per_cpu_ptr(&kvm_init_params, i); + u64 start = params->stack_hyp_va - NVHE_STACK_SIZE; + + ret = kvm_pgtable_walk(&pkvm_pgtable, start, NVHE_STACK_SIZE, &walker); + if (ret) + return ret; + } + return 0; } From 5a8b505ede133fb30ca3b3a19d0db00c08237615 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 8 Sep 2026 12:07:11 +0100 Subject: [PATCH 1765/2366] KVM: arm64: Match hyp text by physical address in fix_host_ownership() On a non-hVHE host, fix_host_ownership_walker()'s test for PAGE_HYP_EXEC never matches: KVM_PGTABLE_PROT_UX is cleared at map time and only PX is reported on read-back. Hyp text is therefore donated rather than left read-only in the host stage-2, and the instruction dump in nvhe_hyp_panic_handler() reads a page the host has no access to. Match the text by physical address instead, in a helper a later patch reuses. A test on the permissions would leave any other executable mapping host-readable too. Fixes: 80cbfd7174f31 ("KVM: arm64: Honor UX/PX attributes for EL2 S1 mappings") Signed-off-by: Fuad Tabba Reviewed-by: Vincent Donnefort Tested-by: Vincent Donnefort Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260908110713.1540304-3-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 1 + arch/arm64/kvm/hyp/nvhe/mem_protect.c | 8 ++++++++ arch/arm64/kvm/hyp/nvhe/setup.c | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h index 29935c7da1de..cab27f7bd423 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h +++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h @@ -52,6 +52,7 @@ int __pkvm_host_test_clear_young_guest(u64 gfn, u64 nr_pages, bool mkold, struct int __pkvm_host_mkyoung_guest(u64 gfn, struct pkvm_hyp_vcpu *vcpu); bool addr_is_memory(phys_addr_t phys); +bool addr_is_hyp_text(phys_addr_t phys); int host_stage2_idmap_locked(phys_addr_t addr, u64 size, enum kvm_pgtable_prot prot); int host_stage2_set_owner_locked(phys_addr_t addr, u64 size, u8 owner_id); int kvm_host_prepare_stage2(void *pgt_pool_base); diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c index 39aa8911f62c..d026f446bd8e 100644 --- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c +++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c @@ -450,6 +450,14 @@ bool addr_is_memory(phys_addr_t phys) return !!find_mem_range(phys, &range); } +bool addr_is_hyp_text(phys_addr_t phys) +{ + phys_addr_t start = ALIGN_DOWN(__hyp_pa(__hyp_text_start), PAGE_SIZE); + phys_addr_t end = PAGE_ALIGN(__hyp_pa(__hyp_text_end)); + + return phys >= start && phys < end; +} + static bool is_in_mem_range(u64 addr, struct kvm_mem_range *range) { return range->start <= addr && addr < range->end; diff --git a/arch/arm64/kvm/hyp/nvhe/setup.c b/arch/arm64/kvm/hyp/nvhe/setup.c index 362f2891cb32..bb667cd7080b 100644 --- a/arch/arm64/kvm/hyp/nvhe/setup.c +++ b/arch/arm64/kvm/hyp/nvhe/setup.c @@ -217,7 +217,7 @@ static int fix_host_ownership_walker(const struct kvm_pgtable_visit_ctx *ctx, case PKVM_PAGE_OWNED: set_hyp_state(page, PKVM_PAGE_OWNED); /* hyp text is RO in the host stage-2 to be inspected on panic. */ - if (prot == PAGE_HYP_EXEC) { + if (addr_is_hyp_text(phys)) { set_host_state(page, PKVM_NOPAGE); return host_stage2_idmap_locked(phys, PAGE_SIZE, KVM_PGTABLE_PROT_R); } else { From 2245841401147b8022a5857061b870d82e595352 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 8 Sep 2026 12:07:12 +0100 Subject: [PATCH 1766/2366] KVM: arm64: Move the private VA allocation cursor to __io_map_next __io_map_base is the start of the private VA range only until the first allocation from it, after which it is the allocation cursor. Keep it as the start and move the cursor to __io_map_next, for the walk of the range the next patch adds. No functional change intended. Signed-off-by: Fuad Tabba Reviewed-by: Vincent Donnefort Tested-by: Vincent Donnefort Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260908110713.1540304-4-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/mm.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/arch/arm64/kvm/hyp/nvhe/mm.c b/arch/arm64/kvm/hyp/nvhe/mm.c index 3b0bee496bff..422ee57be956 100644 --- a/arch/arm64/kvm/hyp/nvhe/mm.c +++ b/arch/arm64/kvm/hyp/nvhe/mm.c @@ -25,6 +25,7 @@ struct memblock_region hyp_memory[HYP_MEMBLOCK_REGIONS]; unsigned int hyp_memblock_nr; static u64 __io_map_base; +static u64 __io_map_next; struct hyp_fixmap_slot { u64 addr; @@ -50,7 +51,7 @@ static int __pkvm_alloc_private_va_range(unsigned long start, size_t size) hyp_assert_lock_held(&pkvm_pgd_lock); - if (!start || start < __io_map_base) + if (!start || start < __io_map_next) return -EINVAL; /* The allocated size is always a multiple of PAGE_SIZE */ @@ -60,7 +61,7 @@ static int __pkvm_alloc_private_va_range(unsigned long start, size_t size) if (cur > __hyp_vmemmap) return -ENOMEM; - __io_map_base = cur; + __io_map_next = cur; return 0; } @@ -70,7 +71,7 @@ static int __pkvm_alloc_private_va_range(unsigned long start, size_t size) * @size: The size of the VA range to reserve. * @haddr: The hypervisor virtual start address of the allocation. * - * The private virtual address (VA) range is allocated above __io_map_base + * The private virtual address (VA) range is allocated above __io_map_next * and aligned based on the order of @size. * * Return: 0 on success or negative error code on failure. @@ -81,7 +82,7 @@ int pkvm_alloc_private_va_range(size_t size, unsigned long *haddr) int ret; hyp_spin_lock(&pkvm_pgd_lock); - addr = __io_map_base; + addr = __io_map_next; ret = __pkvm_alloc_private_va_range(addr, size); hyp_spin_unlock(&pkvm_pgd_lock); @@ -341,7 +342,7 @@ static int create_fixblock(void) return -EINVAL; hyp_spin_lock(&pkvm_pgd_lock); - addr = ALIGN(__io_map_base, PMD_SIZE); + addr = ALIGN(__io_map_next, PMD_SIZE); ret = __pkvm_alloc_private_va_range(addr, PMD_SIZE); if (ret) goto unlock; @@ -426,6 +427,7 @@ int hyp_create_idmap(u32 hyp_va_bits) */ __io_map_base = start & BIT(hyp_va_bits - 2); __io_map_base ^= BIT(hyp_va_bits - 2); + __io_map_next = __io_map_base; __hyp_vmemmap = __io_map_base | BIT(hyp_va_bits - 3); return __pkvm_create_mappings(start, end - start, start, PAGE_HYP_EXEC); @@ -433,19 +435,19 @@ int hyp_create_idmap(u32 hyp_va_bits) int pkvm_create_stack(phys_addr_t phys, unsigned long *haddr) { - unsigned long addr, prev_base; + unsigned long addr, prev_next; size_t size; int ret; hyp_spin_lock(&pkvm_pgd_lock); - prev_base = __io_map_base; + prev_next = __io_map_next; /* * Efficient stack verification using the NVHE_STACK_SHIFT bit implies * an alignment of our allocation on the order of the size. */ size = NVHE_STACK_SIZE * 2; - addr = ALIGN(__io_map_base, size); + addr = ALIGN(__io_map_next, size); ret = __pkvm_alloc_private_va_range(addr, size); if (!ret) { @@ -461,7 +463,7 @@ int pkvm_create_stack(phys_addr_t phys, unsigned long *haddr) ret = kvm_pgtable_hyp_map(&pkvm_pgtable, addr + NVHE_STACK_SIZE, NVHE_STACK_SIZE, phys, PAGE_HYP); if (ret) - __io_map_base = prev_base; + __io_map_next = prev_next; } hyp_spin_unlock(&pkvm_pgd_lock); From cfe80c3837f93202970b6d8f706f79c5c7396f17 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 8 Sep 2026 12:07:13 +0100 Subject: [PATCH 1767/2366] KVM: arm64: Check every private mapping is hyp-owned at pKVM init fix_host_ownership() transfers only what it walks, so a hyp mapping outside the linear map is not manipulated by the walk. Walk the quarter of the VA space holding the private range and the vmemmap once the transfer is done, and fail init unless every valid leaf is hyp-owned: in the vmemmap when the page is memory, and in the host stage-2, where hyp text may instead be mapped without write access. A leaf that is not memory has no vmemmap entry and is checked against the host stage-2 alone. Hyp text is matched by physical address, since the only executable mapping in the range is the Spectre-v3a vectors, whose VA is a private allocation, and an executable mapping of anything else must not be host-readable. The vmemmap can be block-mapped, so the walker checks each page of a leaf. Suggested-by: Will Deacon Signed-off-by: Fuad Tabba Reviewed-by: Vincent Donnefort Tested-by: Vincent Donnefort Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260908110713.1540304-5-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 1 + arch/arm64/kvm/hyp/include/nvhe/mm.h | 1 + arch/arm64/kvm/hyp/nvhe/mem_protect.c | 12 ++++ arch/arm64/kvm/hyp/nvhe/mm.c | 59 +++++++++++++++++++ arch/arm64/kvm/hyp/nvhe/setup.c | 4 ++ 5 files changed, 77 insertions(+) diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h index cab27f7bd423..ec85a9547120 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h +++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h @@ -55,6 +55,7 @@ bool addr_is_memory(phys_addr_t phys); bool addr_is_hyp_text(phys_addr_t phys); int host_stage2_idmap_locked(phys_addr_t addr, u64 size, enum kvm_pgtable_prot prot); int host_stage2_set_owner_locked(phys_addr_t addr, u64 size, u8 owner_id); +bool host_stage2_pte_is_hyp_owned(kvm_pte_t pte); int kvm_host_prepare_stage2(void *pgt_pool_base); int kvm_guest_prepare_stage2(struct pkvm_hyp_vm *vm, void *pgd); void kvm_guest_destroy_stage2(struct pkvm_hyp_vm *vm); diff --git a/arch/arm64/kvm/hyp/include/nvhe/mm.h b/arch/arm64/kvm/hyp/include/nvhe/mm.h index 6e83ce35c2f2..31cae95ddb71 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/mm.h +++ b/arch/arm64/kvm/hyp/include/nvhe/mm.h @@ -29,6 +29,7 @@ int __pkvm_create_private_mapping(phys_addr_t phys, size_t size, enum kvm_pgtable_prot prot, unsigned long *haddr); int pkvm_create_stack(phys_addr_t phys, unsigned long *haddr); +int pkvm_check_host_ownership(void); int pkvm_alloc_private_va_range(size_t size, unsigned long *haddr); #endif /* __KVM_HYP_MM_H */ diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c index d026f446bd8e..a6a47c1e058b 100644 --- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c +++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c @@ -641,6 +641,18 @@ int host_stage2_set_owner_locked(phys_addr_t addr, u64 size, u8 owner_id) return ret; } +bool host_stage2_pte_is_hyp_owned(kvm_pte_t pte) +{ + if (kvm_pte_valid(pte)) + return false; + + if (FIELD_GET(KVM_INVALID_PTE_TYPE_MASK, pte) != + KVM_HOST_INVALID_PTE_TYPE_DONATION) + return false; + + return FIELD_GET(KVM_HOST_DONATION_PTE_OWNER_MASK, pte) == PKVM_ID_HYP; +} + #define KVM_HOST_PTE_OWNER_GUEST_HANDLE_MASK GENMASK(15, 0) /* We need 40 bits for the GFN to cover a 52-bit IPA with 4k pages and LPA2 */ #define KVM_HOST_PTE_OWNER_GUEST_GFN_MASK GENMASK(55, 16) diff --git a/arch/arm64/kvm/hyp/nvhe/mm.c b/arch/arm64/kvm/hyp/nvhe/mm.c index 422ee57be956..29ab5ee9d57f 100644 --- a/arch/arm64/kvm/hyp/nvhe/mm.c +++ b/arch/arm64/kvm/hyp/nvhe/mm.c @@ -472,6 +472,65 @@ int pkvm_create_stack(phys_addr_t phys, unsigned long *haddr) return ret; } +static int check_page_ownership(phys_addr_t phys) +{ + kvm_pte_t pte; + bool host_ok; + int ret; + + if (addr_is_memory(phys)) { + struct hyp_page *page = hyp_phys_to_page(phys); + + if (get_hyp_state(page) != PKVM_PAGE_OWNED || + get_host_state(page) != PKVM_NOPAGE) + return -EPERM; + } + + ret = kvm_pgtable_get_leaf(&host_mmu.pgt, phys, &pte, NULL); + if (ret) + return ret; + + /* Hyp text may stay host-readable, see fix_host_ownership_walker(). */ + if (kvm_pte_valid(pte) && addr_is_hyp_text(phys)) + host_ok = !(kvm_pgtable_stage2_pte_prot(pte) & KVM_PGTABLE_PROT_W); + else + host_ok = host_stage2_pte_is_hyp_owned(pte); + + return host_ok ? 0 : -EPERM; +} + +static int check_host_ownership_walker(const struct kvm_pgtable_visit_ctx *ctx, + enum kvm_pgtable_walk_flags visit) +{ + phys_addr_t phys, end; + int ret; + + if (!kvm_pte_valid(ctx->old)) + return 0; + + phys = kvm_pte_to_phys(ctx->old); + end = phys + kvm_granule_size(ctx->level); + for (; phys < end; phys += PAGE_SIZE) { + ret = check_page_ownership(phys); + if (ret) + return ret; + } + + return 0; +} + +int pkvm_check_host_ownership(void) +{ + struct kvm_pgtable_walker walker = { + .cb = check_host_ownership_walker, + .flags = KVM_PGTABLE_WALK_LEAF, + }; + + /* The private range and the vmemmap share one quarter of the VA space. */ + return kvm_pgtable_walk(&pkvm_pgtable, __io_map_base, + BIT(pkvm_pgtable.ia_bits - 2), &walker); +} + static void *admit_host_page(void *arg) { struct kvm_hyp_memcache *host_mc = arg; diff --git a/arch/arm64/kvm/hyp/nvhe/setup.c b/arch/arm64/kvm/hyp/nvhe/setup.c index bb667cd7080b..45ac5f2ba4f7 100644 --- a/arch/arm64/kvm/hyp/nvhe/setup.c +++ b/arch/arm64/kvm/hyp/nvhe/setup.c @@ -334,6 +334,10 @@ void __noreturn __pkvm_init_finalise(void) if (ret) goto out; + ret = pkvm_check_host_ownership(); + if (ret) + goto out; + ret = hyp_ffa_init(ffa_proxy_pages); if (ret) goto out; From 33346f8960c7bb6a3b4e273b5cfe25c5a8be349f Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 11 Sep 2026 17:22:02 +0100 Subject: [PATCH 1768/2366] KVM: arm64: nv: Fix life cycle of the nested_mmus array The nested_mmus array holds the shadow page tables that are used when a guest is running a nested context. These structures are allocated on VCPU_INIT for whole guest, which implies that they may have to be relocated as the array grows. Should a VCPU_INIT occur whilst a vcpu is actively running an L2 and that the allocation requires relocation, that vcpu will still be running with a pointer to the previous structure, which will have been freed. Fix this by turning the array of structures to an array of pointers, which is now allocated at VM creation, sized to the absolute maximum that KVM can handle. In turn, each VCPU_INIT contributes S2_MMU_PER_VCPU to the pool. No reallocation is ever performed, and the life cycle of each object is much clearer: - the nested_mmus array is allocated in kvm_init_nested(), and freed in kvm_arch_destroy_vm() - s2_mmu structures are allocated in kvm_vcpu_init_nested(), and freed on kvm_arch_flush_shadow_all() Finally, the freeing of vcpu->arch.vncr_array is made consistent rather than being done on some failure paths, but not others. Fixes: 4f128f8e1aaa ("KVM: arm64: nv: Support multiple nested Stage-2 mmu structures") Reported-by: Shen Yongchao Reported-by: Karl Mehltretter Suggested-by: Karl Mehltretter Acked-by: Lorenzo Stoakes (ARM) Link: https://lore.kernel.org/r/20260803224405.41468-1-kmehltretter@gmail.com Signed-off-by: Marc Zyngier Cc: stable@vger.kernel.org Reviewed-by: Wei-Lin Chang Link: https://patch.msgid.link/20260911162203.1919330-2-maz@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/include/asm/kvm_host.h | 2 +- arch/arm64/include/asm/kvm_nested.h | 2 +- arch/arm64/kvm/arm.c | 8 ++- arch/arm64/kvm/nested.c | 93 +++++++++++++---------------- 4 files changed, 51 insertions(+), 54 deletions(-) diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h index 27fe0cd5b2d7..cd9b9d2462f9 100644 --- a/arch/arm64/include/asm/kvm_host.h +++ b/arch/arm64/include/asm/kvm_host.h @@ -322,7 +322,7 @@ struct kvm_arch { * Stage 2 paging state for VMs with nested S2 using a virtual * VMID. */ - struct kvm_s2_mmu *nested_mmus; + struct kvm_s2_mmu **nested_mmus; size_t nested_mmus_size; int nested_mmus_next; diff --git a/arch/arm64/include/asm/kvm_nested.h b/arch/arm64/include/asm/kvm_nested.h index 1ed708335809..5b8edb2e8a87 100644 --- a/arch/arm64/include/asm/kvm_nested.h +++ b/arch/arm64/include/asm/kvm_nested.h @@ -66,7 +66,7 @@ static inline u64 translate_ttbr0_el2_to_ttbr0_el1(u64 ttbr0) extern bool forward_smc_trap(struct kvm_vcpu *vcpu); extern bool forward_debug_exception(struct kvm_vcpu *vcpu); -extern void kvm_init_nested(struct kvm *kvm); +extern int kvm_init_nested(struct kvm *kvm); extern int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu); extern void kvm_init_nested_s2_mmu(struct kvm_s2_mmu *mmu); extern struct kvm_s2_mmu *lookup_s2_mmu(struct kvm_vcpu *vcpu); diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 8b080804bc90..b53219e048bd 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -236,8 +236,6 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) mutex_unlock(&kvm->lock); #endif - kvm_init_nested(kvm); - ret = kvm_share_hyp(kvm, kvm + 1); if (ret) return ret; @@ -252,6 +250,10 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) if (ret) goto err_free_cpumask; + ret = kvm_init_nested(kvm); + if (ret) + goto err_uninit_mmu; + if (is_protected_kvm_enabled()) { /* * If any failures occur after this is successful, make sure to @@ -280,6 +282,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) err_uninit_mmu: kvm_uninit_stage2_mmu(kvm); + kvfree(kvm->arch.nested_mmus); err_free_cpumask: free_cpumask_var(kvm->arch.supported_cpus); err_unshare_kvm: @@ -337,6 +340,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm) kvm_unshare_hyp(kvm, kvm + 1); + kvfree(kvm->arch.nested_mmus); kvm_arm_teardown_hypercalls(kvm); } diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index a0808391a456..2571f177e654 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -45,11 +45,15 @@ struct vncr_tlb { */ #define S2_MMU_PER_VCPU 2 -void kvm_init_nested(struct kvm *kvm) +int kvm_init_nested(struct kvm *kvm) { - kvm->arch.nested_mmus = NULL; + kvm->arch.nested_mmus = kvmalloc_objs(struct kvm_s2_mmu *, + KVM_MAX_VCPUS * S2_MMU_PER_VCPU, + GFP_KERNEL_ACCOUNT); kvm->arch.nested_mmus_size = 0; atomic_set(&kvm->arch.vncr_tlb_count, 0); + + return kvm->arch.nested_mmus ? 0 : -ENOMEM; } static int init_nested_s2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu) @@ -70,8 +74,9 @@ static int init_nested_s2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu) int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu) { struct kvm *kvm = vcpu->kvm; - struct kvm_s2_mmu *tmp; - int num_mmus, ret = 0; + int num_mmus; + + lockdep_assert_held(&kvm->arch.config_lock); if (test_bit(KVM_ARM_VCPU_HAS_EL2_E2H0, kvm->arch.vcpu_features) && !cpus_have_final_cap(ARM64_HAS_HCR_NV1)) @@ -84,51 +89,40 @@ int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu) if (!vcpu->arch.ctxt.vncr_array) return -ENOMEM; - /* - * Let's treat memory allocation failures as benign: If we fail to - * allocate anything, return an error and keep the allocated array - * alive. Userspace may try to recover by initializing the vcpu - * again, and there is no reason to affect the whole VM for this. - */ num_mmus = atomic_read(&kvm->online_vcpus) * S2_MMU_PER_VCPU; if (num_mmus > kvm->arch.nested_mmus_size) { - tmp = kvzalloc_objs(*tmp, num_mmus, GFP_KERNEL_ACCOUNT); + struct kvm_s2_mmu *tmp; + int i, ret = 0; + + tmp = kvzalloc_objs(*tmp, S2_MMU_PER_VCPU, GFP_KERNEL_ACCOUNT); if (!tmp) - return -ENOMEM; + ret = -ENOMEM; - write_lock(&kvm->mmu_lock); - - if (kvm->arch.nested_mmus_size) { - memcpy(tmp, kvm->arch.nested_mmus, - size_mul(sizeof(*tmp), kvm->arch.nested_mmus_size)); - - for (int i = 0; i < kvm->arch.nested_mmus_size; i++) - tmp[i].pgt->mmu = &tmp[i]; + for (i = 0; !ret && i < S2_MMU_PER_VCPU; i++) { + ret = init_nested_s2_mmu(kvm, &tmp[i]); + if (ret) + break; } - swap(kvm->arch.nested_mmus, tmp); + if (ret) { + while (--i >= 0) + kvm_free_stage2_pgd(&tmp[i]); - write_unlock(&kvm->mmu_lock); + kvfree(tmp); + free_page((unsigned long)vcpu->arch.ctxt.vncr_array); + vcpu->arch.ctxt.vncr_array = NULL; + return ret; + } - kvfree(tmp); + guard(write_lock)(&kvm->mmu_lock); + + for (i = 0; i < S2_MMU_PER_VCPU; i++) + kvm->arch.nested_mmus[i + kvm->arch.nested_mmus_size] = &tmp[i]; + + kvm->arch.nested_mmus_size += S2_MMU_PER_VCPU; } - for (int i = kvm->arch.nested_mmus_size; !ret && i < num_mmus; i++) - ret = init_nested_s2_mmu(kvm, &kvm->arch.nested_mmus[i]); - - if (ret) { - for (int i = kvm->arch.nested_mmus_size; i < num_mmus; i++) - kvm_free_stage2_pgd(&kvm->arch.nested_mmus[i]); - - free_page((unsigned long)vcpu->arch.ctxt.vncr_array); - vcpu->arch.ctxt.vncr_array = NULL; - - return ret; - } - - kvm->arch.nested_mmus_size = num_mmus; - return 0; } @@ -742,7 +736,7 @@ void kvm_s2_mmu_iterate_by_vmid(struct kvm *kvm, u16 vmid, write_lock(&kvm->mmu_lock); for (int i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (!kvm_s2_mmu_valid(mmu)) continue; @@ -784,7 +778,7 @@ struct kvm_s2_mmu *lookup_s2_mmu(struct kvm_vcpu *vcpu) * if S2 translation is disabled. */ for (int i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (!kvm_s2_mmu_valid(mmu)) continue; @@ -823,7 +817,7 @@ static struct kvm_s2_mmu *get_s2_mmu_nested(struct kvm_vcpu *vcpu) for (i = kvm->arch.nested_mmus_next; i < (kvm->arch.nested_mmus_size + kvm->arch.nested_mmus_next); i++) { - s2_mmu = &kvm->arch.nested_mmus[i % kvm->arch.nested_mmus_size]; + s2_mmu = kvm->arch.nested_mmus[i % kvm->arch.nested_mmus_size]; if (atomic_read(&s2_mmu->refcnt) == 0) break; @@ -1281,7 +1275,7 @@ void kvm_nested_s2_wp(struct kvm *kvm) return; for (i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (kvm_s2_mmu_valid(mmu)) kvm_stage2_wp_range(mmu, 0, kvm_phys_size(mmu)); @@ -1300,7 +1294,7 @@ void kvm_nested_s2_unmap(struct kvm *kvm, bool may_block) return; for (i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (kvm_s2_mmu_valid(mmu)) kvm_stage2_unmap_range(mmu, 0, kvm_phys_size(mmu), may_block); @@ -1319,7 +1313,7 @@ void kvm_nested_s2_flush(struct kvm *kvm) return; for (i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (kvm_s2_mmu_valid(mmu)) kvm_stage2_flush_range(mmu, 0, kvm_phys_size(mmu)); @@ -1328,16 +1322,15 @@ void kvm_nested_s2_flush(struct kvm *kvm) void kvm_arch_flush_shadow_all(struct kvm *kvm) { - int i; - - for (i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + for (int i = kvm->arch.nested_mmus_size - 1; i >= 0; i--) { + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (!WARN_ON(atomic_read(&mmu->refcnt))) kvm_free_stage2_pgd(mmu); + + if ((i % S2_MMU_PER_VCPU) == 0) + kvfree(mmu); } - kvfree(kvm->arch.nested_mmus); - kvm->arch.nested_mmus = NULL; kvm->arch.nested_mmus_size = 0; kvm_uninit_stage2_mmu(kvm); } From e5843f4effaa2ffac3e789ecd4456403564961d4 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 11 Sep 2026 17:22:03 +0100 Subject: [PATCH 1769/2366] KVM: arm64: nv: Delay freeing of shadow S2 structures until VM destruction We free the shadow S2 structures from kvm_arch_flush_shadow_all(), which is a Bad Idea(tm). Freeing the page tables is fair game (this is what this callback is for), but freeing the container that could still be referenced by another part of the system is not great. Instead, grow separate destructors that gets called when we tear the VM down for good. From there, we can nuke both the individual MMUs as well as the global array that points to them, safe in the knowledge that the vcpus themselves have been destroyed already. Fixes: 4f128f8e1aaac ("KVM: arm64: nv: Support multiple nested Stage-2 mmu structures") Reviewed-by: Lorenzo Stoakes (ARM) Signed-off-by: Marc Zyngier Cc: stable@vger.kernel.org Reviewed-by: Wei-Lin Chang Link: https://patch.msgid.link/20260911162203.1919330-3-maz@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/include/asm/kvm_nested.h | 1 + arch/arm64/kvm/arm.c | 4 ++-- arch/arm64/kvm/nested.c | 15 ++++++++++----- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/arch/arm64/include/asm/kvm_nested.h b/arch/arm64/include/asm/kvm_nested.h index 5b8edb2e8a87..586026e85903 100644 --- a/arch/arm64/include/asm/kvm_nested.h +++ b/arch/arm64/include/asm/kvm_nested.h @@ -67,6 +67,7 @@ static inline u64 translate_ttbr0_el2_to_ttbr0_el1(u64 ttbr0) extern bool forward_smc_trap(struct kvm_vcpu *vcpu); extern bool forward_debug_exception(struct kvm_vcpu *vcpu); extern int kvm_init_nested(struct kvm *kvm); +extern void kvm_destroy_nested(struct kvm *kvm); extern int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu); extern void kvm_init_nested_s2_mmu(struct kvm_s2_mmu *mmu); extern struct kvm_s2_mmu *lookup_s2_mmu(struct kvm_vcpu *vcpu); diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index b53219e048bd..eaf583b77193 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -282,7 +282,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) err_uninit_mmu: kvm_uninit_stage2_mmu(kvm); - kvfree(kvm->arch.nested_mmus); + kvm_destroy_nested(kvm); err_free_cpumask: free_cpumask_var(kvm->arch.supported_cpus); err_unshare_kvm: @@ -340,7 +340,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm) kvm_unshare_hyp(kvm, kvm + 1); - kvfree(kvm->arch.nested_mmus); + kvm_destroy_nested(kvm); kvm_arm_teardown_hypercalls(kvm); } diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index 2571f177e654..b191365d97cc 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -56,6 +56,15 @@ int kvm_init_nested(struct kvm *kvm) return kvm->arch.nested_mmus ? 0 : -ENOMEM; } +void kvm_destroy_nested(struct kvm *kvm) +{ + for (int i = 0; i < kvm->arch.nested_mmus_size; i+= S2_MMU_PER_VCPU) + kvfree(kvm->arch.nested_mmus[i]); + + kvm->arch.nested_mmus_size = 0; + kvfree(kvm->arch.nested_mmus); +} + static int init_nested_s2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu) { /* @@ -1322,16 +1331,12 @@ void kvm_nested_s2_flush(struct kvm *kvm) void kvm_arch_flush_shadow_all(struct kvm *kvm) { - for (int i = kvm->arch.nested_mmus_size - 1; i >= 0; i--) { + for (int i = 0; i < kvm->arch.nested_mmus_size; i++) { struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (!WARN_ON(atomic_read(&mmu->refcnt))) kvm_free_stage2_pgd(mmu); - - if ((i % S2_MMU_PER_VCPU) == 0) - kvfree(mmu); } - kvm->arch.nested_mmus_size = 0; kvm_uninit_stage2_mmu(kvm); } From 49d9d295d69d07e850cae35933ba8519e2915f26 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Mon, 14 Sep 2026 10:38:38 +0100 Subject: [PATCH 1770/2366] KVM: arm64: Don't WARN on an unknown VM ioctl in protected mode kvm_pkvm_ioctl_allowed() WARNs when kvm_get_cap_for_kvm_ioctl() doesn't find the ioctl number in vm_ioctl_caps[], and kvm_arch_vm_ioctl() calls it for every number the generic code doesn't handle, so ioctl(vm_fd, 0xdeadbeef) from userspace taints a pKVM host and panics it under panic_on_warn. The lookup is fed userspace input: return false, and userspace gets the -EINVAL kvm_arch_vm_ioctl() returns for that number on a host without pKVM. Fixes: b12b3b04f6ba0 ("KVM: arm64: Check whether a VM IOCTL is allowed in pKVM") Cc: stable@vger.kernel.org Signed-off-by: Fuad Tabba Reviewed-by: Suzuki K Poulose Link: https://patch.msgid.link/20260914093838.1082637-1-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/include/asm/kvm_pkvm.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h index beea00e693a0..cad60569f061 100644 --- a/arch/arm64/include/asm/kvm_pkvm.h +++ b/arch/arm64/include/asm/kvm_pkvm.h @@ -65,8 +65,7 @@ static inline bool kvm_pkvm_ioctl_allowed(struct kvm *kvm, unsigned int ioctl) int r; r = kvm_get_cap_for_kvm_ioctl(ioctl, &ext); - - if (WARN_ON_ONCE(r < 0)) + if (r < 0) return false; return kvm_pkvm_ext_allowed(kvm, ext); From 96e6757cb0674acb86ee8b558ee6bffe0eec0bb0 Mon Sep 17 00:00:00 2001 From: Sebastian Ott Date: Mon, 14 Sep 2026 15:10:13 +0200 Subject: [PATCH 1771/2366] KVM: selftests: fix steal_time for arm64 with host page size > 4K Fix the following failure when running with 16K host page size: ==== Test Assertion Failure ==== lib/kvm_util.c:991: vm_adjust_num_guest_pages(vm->mode, npages) == npages pid=873 tid=873 errno=0 - Success 1 0x0000000000405a27: vm_mem_add at kvm_util.c:991 2 0x000000000040241f: check_steal_time_uapi at steal_time.c:223 (discriminator 7) 3 (inlined by) main at steal_time.c:539 (discriminator 7) 4 0x00007fff8b57af3b: ?? ??:0 5 0x00007fff8b57b007: ?? ??:0 6 0x0000000000402b6f: _start at ??:? Number of guest pages is not compatible with the host. Try npages=4 Fixes: fc240715fc50 ("KVM: selftests: arm64: Fix steal_time test after UAPI refactoring") Reported-by: Zenghui Yu Link: https://lore.kernel.org/kvmarm/7575a845-a542-4b16-b512-aec3126f97f3@linux.dev/T/#u Signed-off-by: Sebastian Ott Reviewed-by: Zenghui Yu (Huawei) Link: https://patch.msgid.link/20260914131013.60334-1-sebott@redhat.com Signed-off-by: Oliver Upton --- tools/testing/selftests/kvm/steal_time.c | 30 ++++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/kvm/steal_time.c b/tools/testing/selftests/kvm/steal_time.c index bc3c62b72c58..785d19f9ee8f 100644 --- a/tools/testing/selftests/kvm/steal_time.c +++ b/tools/testing/selftests/kvm/steal_time.c @@ -27,6 +27,9 @@ static void *st_gva[NR_VCPUS]; static u64 guest_stolen_time[NR_VCPUS]; +static struct kvm_vm *vm_create_steal_time(u32 nr_vcpus, void *guest_code, + struct kvm_vcpu *vcpus[]); + #if defined(__x86_64__) /* steal_time must have 64-byte alignment */ @@ -210,17 +213,14 @@ static void check_steal_time_uapi(void) u64 st_ipa; int ret; - vm = vm_create_with_one_vcpu(&vcpu, NULL); - struct kvm_device_attr dev = { .group = KVM_ARM_VCPU_PVTIME_CTRL, .attr = KVM_ARM_VCPU_PVTIME_IPA, .addr = (u64)&st_ipa, }; + vm = vm_create_steal_time(1, NULL, &vcpu); vcpu_ioctl(vcpu, KVM_HAS_DEVICE_ATTR, &dev); - vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, 1, 0); - virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, 1); st_ipa = (ulong)ST_GPA_BASE | 1; ret = __vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &dev); @@ -500,13 +500,27 @@ static void run_vcpu(struct kvm_vcpu *vcpu) } } +static struct kvm_vm *vm_create_steal_time(u32 nr_vcpus, void *guest_code, + struct kvm_vcpu *vcpus[]) +{ + unsigned int gpages; + struct kvm_vm *vm; + + /* Create a VM and an identity mapped memslot for the steal time structure */ + vm = vm_create_with_vcpus(nr_vcpus, guest_code, vcpus); + gpages = vm_calc_num_guest_pages(VM_MODE_DEFAULT, STEAL_TIME_SIZE * nr_vcpus); + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, gpages, 0); + virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, gpages); + + return vm; +} + int main(int ac, char **av) { struct kvm_vcpu *vcpus[NR_VCPUS]; struct kvm_vm *vm; pthread_t thread; cpu_set_t cpuset; - unsigned int gpages; long stolen_time; long run_delay; bool verbose; @@ -517,11 +531,7 @@ int main(int ac, char **av) /* Set CPU affinity so we can force preemption of the VCPU */ cpu = pin_self_to_any_cpu(); - /* Create a VM and an identity mapped memslot for the steal time structure */ - vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus); - gpages = vm_calc_num_guest_pages(VM_MODE_DEFAULT, STEAL_TIME_SIZE * NR_VCPUS); - vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, gpages, 0); - virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, gpages); + vm = vm_create_steal_time(NR_VCPUS, guest_code, vcpus); ksft_print_header(); TEST_REQUIRE(is_steal_time_supported(vcpus[0])); From 089e4f3c4862ba3f29dff2361caa8084879194fd Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 1 Sep 2026 22:47:00 +0100 Subject: [PATCH 1772/2366] KVM: arm64: Fix FGT mapping for HFGITR_EL2.nGCSEPP The encoding to trap mapping currently maps a FGT on OP_GCSPOPX to HFGITR_EL2.nGCSEPP but as per DDI0601 2026-06 this FGT controls trapping of GCSPUSHX and GCSPOPCX, and not the separate GCSPOPX instruction. Update the mapping to reflect the architecture. Fixes: 863ac38984a82 ("KVM: arm64: Add missing HFGITR_EL2 FGT entries to nested virt") Reviewed-by: Leonardo Bras Signed-off-by: Mark Brown Reviewed-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260901-arm64-gcs-v20-2-f31750bdfadb@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/kvm/emulate-nested.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c index 625604019fb3..3806ff0920fe 100644 --- a/arch/arm64/kvm/emulate-nested.c +++ b/arch/arm64/kvm/emulate-nested.c @@ -1445,7 +1445,7 @@ static const struct encoding_to_trap_config encoding_to_fgt[] __initconst = { SR_FGT(OP_AT_S1E1A, HFGITR, ATS1E1A, 1), SR_FGT(OP_COSP_RCTX, HFGITR, COSPRCTX, 1), SR_FGT(OP_GCSPUSHX, HFGITR, nGCSEPP, 0), - SR_FGT(OP_GCSPOPX, HFGITR, nGCSEPP, 0), + SR_FGT(OP_GCSPOPCX, HFGITR, nGCSEPP, 0), SR_FGT(OP_GCSPUSHM, HFGITR, nGCSPUSHM_EL1, 0), SR_FGT(OP_BRB_IALL, HFGITR, nBRBIALL, 0), SR_FGT(OP_BRB_INJ, HFGITR, nBRBINJ, 0), From 7de9a6fb44eae4f05e68c805d58b9c618815adfa Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 15 Sep 2026 12:34:44 -1000 Subject: [PATCH 1773/2366] sched_ext: Wait for SCX_OPSS_DISPATCHING before reenqueueing a task ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") moved the final ops_state store in scx_dispatch_enqueue() after the DSQ unlock so that the custody update and ops.dequeue() precede it. A task can thus be found on a DSQ while still SCX_OPSS_DISPATCHING. The dequeue and core-sched pick paths wait for the state to clear in ops_dequeue() but the reenqueue paths don't. A reenqueue in that window runs ops.enqueue() and sets SCX_OPSS_QUEUED before the dispatch has completed. The dispatcher's final store then overwrites it with SCX_OPSS_NONE and finish_dispatch() drops every later dispatch of the task. Wait for SCX_OPSS_DISPATCHING to clear before dequeueing a task for reenqueue, the same way ops_dequeue() does. Fixes: ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 13 +++++++++++++ kernel/sched/ext/internal.h | 1 + kernel/sched/ext/sub.c | 1 + 3 files changed, 15 insertions(+) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 70b711c4de6e..725b890fcab9 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -4404,6 +4404,17 @@ static bool local_task_should_reenq(struct rq *rq, struct task_struct *p, return *reenq_flags & SCX_REENQ_ANY; } +/* + * The dispatcher stores the final ops_state after dropping the DSQ lock, so @p + * can be found on a DSQ while still %SCX_OPSS_DISPATCHING. Reenqueueing @p + * before that store lands would have it clobber the new %SCX_OPSS_QUEUED. + */ +void scx_reenq_wait_dispatching(struct task_struct *p) +{ + if (unlikely(atomic_long_read_acquire(&p->scx.ops_state) == SCX_OPSS_DISPATCHING)) + wait_ops_state(p, SCX_OPSS_DISPATCHING); +} + static u32 reenq_local(struct scx_sched *sch, struct rq *rq, u64 reenq_flags) { LIST_HEAD(tasks); @@ -4447,6 +4458,7 @@ static u32 reenq_local(struct scx_sched *sch, struct rq *rq, u64 reenq_flags) if (!local_task_should_reenq(rq, p, &reenq_flags, &reason)) continue; + scx_reenq_wait_dispatching(p); scx_dispatch_dequeue(rq, p); if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK)) @@ -4570,6 +4582,7 @@ static void reenq_user(struct rq *rq, struct scx_dispatch_q *dsq, u64 reenq_flag } /* @p is on @dsq, its rq and @dsq are locked */ + scx_reenq_wait_dispatching(p); dispatch_dequeue_locked(p, dsq); raw_spin_unlock(&dsq->lock); diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 3464e0f113c1..115c96fbf932 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -2078,6 +2078,7 @@ void scx_kick_cpu(struct scx_sched *sch, s32 cpu, u64 flags); u64 __scx_bpf_now(struct rq *rq); void schedule_dsq_reenq(struct scx_sched *sch, struct scx_dispatch_q *dsq, u64 reenq_flags, struct rq *locked_rq); +void scx_reenq_wait_dispatching(struct task_struct *p); int __scx_init_task(struct scx_sched *sch, struct task_struct *p, struct cgroup *cgrp, bool fork); void scx_enable_task(struct scx_sched *sch, struct task_struct *p); diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index f7aeb1488566..a30c965b175d 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -801,6 +801,7 @@ void scx_reenq_reject(struct rq *rq) if (WARN_ON_ONCE(p->migration_pending)) continue; + scx_reenq_wait_dispatching(p); scx_dispatch_dequeue(rq, p); if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK)) From 4a4263dfeabad72f95e8ab6e15146861fa4144dd Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 03:07:51 +0000 Subject: [PATCH 1774/2366] af_unix: Unify scc_index when finalising SCC in __unix_walk_scc(). Commit bfdb01283ee8 ("af_unix: Assign a unique index to SCC.") changed Tarjan's algorithm to update lowlink with lowlink, which is called lowpoint (unix_vertex.scc_index). unix_vertex_dead() assumes all vertices in an SCC share the same lowpoint, but this is not always true if an SCC has two or more back edges, depending on the order of DFS. For example, the graph below has two back edges from B to A and from C to B. A --> B --> C ^ | ^ | `----' `----' If DFS walks through A -> B -> C -> B (-> C -> B) -> A (-> B -> A), each index and scc_index will be updated as follows. A --> B --> C C = (3, 3) (index, scc_index) B = (2, 2) A = (1, 1) A ... B ... C C = (3, 2)<-. ^ | B = (2, 2) -' `----' A = (1, 1) A ... B ... C C = (3, 2) ^ | . . B = (2, 1)<-. `----' .... A = (1, 1) -' Then, unix_vertex_dead() thinks that B is passed to another SCC with scc_index 2, and the SCC is not garbage-collected. This does not happen if DFS walks in a different order below or starts from B. 1 3 A --> B --> C ^ | ^ | `----' `----' 2 4 Let's unify scc_index across the SCC when finalising it. Note that updating v->index was previously done in unix_scc_dead(), when called from __unix_walk_scc(), just to save one loop. Since __unix_walk_scc() now iterates over the SCC anyway, the update is moved back to __unix_walk_scc() and 'fast' argument is dropped. Fixes: 4090fa373f0e ("af_unix: Replace garbage collection algorithm.") Reported-by: James Burton Signed-off-by: Kuniyuki Iwashima Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260912030852.1467872-2-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/unix/garbage.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/net/unix/garbage.c b/net/unix/garbage.c index 9fcaaf55cba5..da774f56ca64 100644 --- a/net/unix/garbage.c +++ b/net/unix/garbage.c @@ -374,7 +374,7 @@ static bool unix_vertex_dead(struct unix_vertex *vertex) static LIST_HEAD(unix_visited_vertices); static unsigned long unix_vertex_grouped_index = UNIX_VERTEX_INDEX_MARK2; -static bool unix_scc_dead(struct list_head *scc, bool fast) +static bool unix_scc_dead(struct list_head *scc) { struct unix_vertex *vertex; bool scc_dead = true; @@ -386,10 +386,6 @@ static bool unix_scc_dead(struct list_head *scc, bool fast) /* Don't restart DFS from this vertex. */ list_move_tail(&vertex->entry, &unix_visited_vertices); - /* Mark vertex as off-stack for __unix_walk_scc(). */ - if (!fast) - vertex->index = unix_vertex_grouped_index; - if (scc_dead) scc_dead = unix_vertex_dead(vertex); } @@ -521,6 +517,7 @@ static unsigned long __unix_walk_scc(struct unix_vertex *vertex, } if (vertex->index == vertex->scc_index) { + struct unix_vertex *v; struct list_head scc; /* SCC finalised. @@ -530,7 +527,13 @@ static unsigned long __unix_walk_scc(struct unix_vertex *vertex, */ __list_cut_position(&scc, &vertex_stack, &vertex->scc_entry); - if (unix_scc_dead(&scc, false)) { + list_for_each_entry_reverse(v, &scc, scc_entry) { + /* Mark vertex as off-stack and assign a unique ID. */ + v->index = unix_vertex_grouped_index; + v->scc_index = vertex->scc_index; + } + + if (unix_scc_dead(&scc)) { unix_collect_skb(&scc, hitlist); } else { if (unix_vertex_max_scc_index < vertex->scc_index) @@ -588,7 +591,7 @@ static void unix_walk_scc_fast(struct sk_buff_head *hitlist) vertex = list_first_entry(&unix_unvisited_vertices, typeof(*vertex), entry); list_add(&scc, &vertex->scc_entry); - if (unix_scc_dead(&scc, true)) { + if (unix_scc_dead(&scc)) { cyclic_sccs--; unix_collect_skb(&scc, hitlist); } From b645ccd410547d0e0e4a9543f828119e24dc7635 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 03:07:52 +0000 Subject: [PATCH 1775/2366] selftest: af_unix: Add test case with mixed lowpoint in scm_rights.c. The new test case creates two SCCs so that each of them has multiple scc_index. Without patch, GC cannot free the sockets and the test fails. # RUN scm_rights.dgram.mixed_lowpoints ... # scm_rights.c:176:mixed_lowpoints:Expected 0 (0) == ret (12) # mixed_lowpoints: Test terminated by assertion # FAIL scm_rights.dgram.mixed_lowpoints not ok 5 scm_rights.dgram.mixed_lowpoints ... # FAILED: 45 / 50 tests passed. # Totals: pass:45 fail:5 xfail:0 xpass:0 skip:0 error:0 With the patch, all tests pass. # PASSED: 50 / 50 tests passed. # Totals: pass:50 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Kuniyuki Iwashima Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260912030852.1467872-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- .../testing/selftests/net/af_unix/scm_rights.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/testing/selftests/net/af_unix/scm_rights.c b/tools/testing/selftests/net/af_unix/scm_rights.c index d82a79c21c17..c165f250220a 100644 --- a/tools/testing/selftests/net/af_unix/scm_rights.c +++ b/tools/testing/selftests/net/af_unix/scm_rights.c @@ -378,4 +378,21 @@ TEST_F(scm_rights, backtrack_from_scc) close_sockets(10); } +TEST_F(scm_rights, mixed_lowpoint) +{ + create_sockets(6); + + send_fd(0, 1); + send_fd(1, 2); + send_fd(2, 1); + send_fd(1, 0); + + send_fd(3, 4); + send_fd(4, 5); + send_fd(5, 4); + send_fd(4, 3); + + close_sockets(6); +} + TEST_HARNESS_MAIN From 15989abd74f16f44bf953d056b95f1d2fda9b0cd Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 11 Sep 2026 11:20:15 +0200 Subject: [PATCH 1776/2366] net: stmmac: fix TSO header length truncation stmmac_tso_xmit() stores the protocol header length returned by stmmac_tso_header_size() in a u8. stmmac_tso_valid_packet() admits headers up to 1023 bytes, so a header longer than 255 bytes wraps modulo 256 (486 becomes 230, 256 becomes 0). A TCP over IPv6 socket carrying a few hundred bytes of sticky destination/hop-by-hop options makes skb_tcp_all_headers() exceed 255 while staying below the 1023-byte limit, so such an skb reaches stmmac_tso_xmit(). Widen proto_hdr_len to unsigned int, which is sufficient since the value is bounded by the hardware limit, and adjust the debug print specifier accordingly. Fixes: 9edfa7dab811 ("net: stmmac: enable TSO for IPv6") Signed-off-by: Lorenzo Bianconi Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260911-stmmac-fix-header-length-v1-1-8fc103334327@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 62c3441911e7..1fb5f804ea23 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4513,16 +4513,16 @@ static int stmmac_tso_get_num_desc(struct stmmac_tx_queue *tx_q, */ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) { + unsigned int first_entry, entry, tx_packets, proto_hdr_len; struct dma_desc *desc, *first, *mss_desc = NULL; struct stmmac_priv *priv = netdev_priv(dev); - unsigned int first_entry, entry, tx_packets; struct stmmac_txq_stats *txq_stats; int i, first_tx, nfrags, ndesc; struct stmmac_tx_queue *tx_q; bool set_ic, is_last_segment; u32 pay_len, mss, queue; - u8 proto_hdr_len, hdr; dma_addr_t des; + u8 hdr; nfrags = skb_shinfo(skb)->nr_frags; queue = skb_get_queue_mapping(skb); @@ -4570,7 +4570,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) } if (netif_msg_tx_queued(priv)) { - pr_info("%s: hdrlen %d, hdr_len %d, pay_len %d, mss %d\n", + pr_info("%s: hdrlen %d, hdr_len %u, pay_len %d, mss %d\n", __func__, hdr, proto_hdr_len, pay_len, mss); pr_info("\tskb->len %d, skb->data_len %d\n", skb->len, skb->data_len); From 6e05e46fa821a5c1b281355f1f622ac76cb6080a Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Thu, 10 Sep 2026 17:34:12 +0800 Subject: [PATCH 1777/2366] net/sched: act_api: release tail references on DELACTION failure A batched RTM_DELACTION request takes a temporary reference on each action before attempting any deletion. tcf_action_delete() clears each processed slot and drops its temporary reference before attempting the deletion. If deletion fails, tca_action_gd() calls tcf_action_put_many() to release the remaining references, but its tcf_act_for_each_action() iterator stops at the first NULL slot. When a batch stops at an action bound to a filter, this leaks a reference on each subsequent action. A later delete of an unbound action can then return success without removing it from the IDR. Walk the full array in tcf_action_put_many() and skip NULL slots to release the references held on the unprocessed actions. Fixes: a0e947c9ccff ("net/sched: act_api: avoid non-contiguous action array") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260910093413.34509-2-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 19501dc99464..3f653721c45f 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -1218,11 +1218,16 @@ static int tcf_action_put(struct tc_action *p) static void tcf_action_put_many(struct tc_action *actions[]) { - struct tc_action *a; int i; - tcf_act_for_each_action(i, a, actions) { - const struct tc_action_ops *ops = a->ops; + /* Deletion may have cleared entries before failing. */ + for (i = 0; i < TCA_ACT_MAX_PRIO; i++) { + struct tc_action *a = actions[i]; + const struct tc_action_ops *ops; + + if (!a) + continue; + ops = a->ops; if (tcf_action_put(a)) module_put(ops->owner); } From 14c5eb685cdefbd32e73d2723071ecbd8effbce9 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Thu, 10 Sep 2026 17:34:13 +0800 Subject: [PATCH 1778/2366] selftests: tc-testing: test action batch deletion failure cleanup Add tests for cleanup after a batched RTM_DELACTION request fails at a gact action bound to a filter. Check that subsequent actions retain their original reference counts and that earlier successful deletions are preserved. Cover failures at the first and middle entries. Verify that a remaining unbound action can be removed with one subsequent delete. Signed-off-by: Xuanqiang Luo Link: https://patch.msgid.link/20260910093413.34509-3-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- .../tc-tests/actions/batch-delete.json | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json diff --git a/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json b/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json new file mode 100644 index 000000000000..ef7ca4a6775b --- /dev/null +++ b/tools/testing/selftests/tc-testing/tc-tests/actions/batch-delete.json @@ -0,0 +1,115 @@ +[ + { + "id": "d710", + "name": "Release tail references after first action deletion fails", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ], + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC actions add action pass index 2", + "$TC actions add action pass index 3", + "$TC filter add dev $DEV1 protocol ip ingress u32 match u32 0 0 action gact index 1" + ], + "cmdUnderTest": "$TC actions del action gact index 1 action gact index 2 action gact index 3", + "expExitCode": "255", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 3\\b.*index 1 ref 2 bind 1\\b.*index 2 ref 1 bind 0\\b.*index 3 ref 1 bind 0\\b", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress", + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ] + ] + }, + { + "id": "d711", + "name": "Release tail references after middle action deletion fails", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ], + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC actions add action pass index 2", + "$TC actions add action pass index 3", + "$TC filter add dev $DEV1 protocol ip ingress u32 match u32 0 0 action gact index 2" + ], + "cmdUnderTest": "$TC actions del action gact index 1 action gact index 2 action gact index 3", + "expExitCode": "255", + "verifyCmd": "$TC actions ls action gact", + "matchPattern": "total acts 2\\b.*index 2 ref 2 bind 1\\b.*index 3 ref 1 bind 0\\b", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress", + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ] + ] + }, + { + "id": "d713", + "name": "Delete a tail action once after a failed batch", + "category": [ + "actions", + "gact" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ], + "$TC qdisc add dev $DEV1 ingress", + "$TC actions add action pass index 1", + "$TC actions add action pass index 2", + "$TC filter add dev $DEV1 protocol ip ingress u32 match u32 0 0 action gact index 1" + ], + "cmdUnderTest": "$TC actions del action gact index 1 action gact index 2", + "expExitCode": "255", + "verifyCmd": "sh -c '$TC actions del action gact index 2 && $TC actions ls action gact'", + "matchPattern": "total acts 1\\b.*index 1 ref 2 bind 1\\b", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress", + [ + "$TC actions flush action gact", + 0, + 1, + 255 + ] + ] + } +] From ecc7253683a3c55caa868ce0ee530fcb0044bd3c Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sat, 12 Sep 2026 23:30:48 +0000 Subject: [PATCH 1779/2366] pppoatm: ensure a writable skb header and linear data In pppoatm_send(), LLC encapsulation checks whether there is sufficient headroom for the 4-byte LLC header, but does not ensure that the skb header is writable. Normal transmit packets passing through ppp_start_xmit() have their header unshared via skb_cow_head(). However, packets can also reach pppoatm_send() via PPP channel bridging (PPPIOCBRIDGECHAN) without going through ppp_start_xmit(). Use skb_cow_head() to ensure both sufficient headroom and a writable header before pushing the LLC header. While at it: - Call pskb_may_pull(skb, 1) before inspecting skb->data[0] to prevent out-of-bounds reads on zero-length or non-linear frames (e.g. from bridging). - Defer SC_COMP_PROT protocol compression until after pppoatm_may_send() succeeds. This eliminates the temporary skb allocation on admission failure and completely removes the fragile "undo" heuristic at the nospace label, avoiding any risk of reading uninitialized headroom or performing an unbalanced skb_push(). Fixes: 4cf476ced45d ("ppp: add PPPIOCBRIDGECHAN and PPPIOCUNBRIDGECHAN ioctls") Signed-off-by: Eric Dumazet Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260912233048.3977192-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/atm/pppoatm.c | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/net/atm/pppoatm.c b/net/atm/pppoatm.c index 6da52d12df68..5214786e61d1 100644 --- a/net/atm/pppoatm.c +++ b/net/atm/pppoatm.c @@ -292,10 +292,13 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) struct atm_vcc *vcc; int ret; + if (!pskb_may_pull(skb, 1)) { + kfree_skb(skb); + return DROP_PACKET; + } + ATM_SKB(skb)->vcc = pvcc->atmvcc; pr_debug("(skb=0x%p, vcc=0x%p)\n", skb, pvcc->atmvcc); - if (skb->data[0] == '\0' && (pvcc->flags & SC_COMP_PROT)) - (void) skb_pull(skb, 1); vcc = ATM_SKB(skb)->vcc; bh_lock_sock(sk_atm(vcc)); @@ -317,23 +320,13 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) switch (pvcc->encaps) { /* LLC encapsulation needed */ case e_llc: - if (skb_headroom(skb) < LLC_LEN) { - struct sk_buff *n; - n = skb_realloc_headroom(skb, LLC_LEN); - if (n != NULL && - !pppoatm_may_send(pvcc, n->truesize)) { - kfree_skb(n); - goto nospace; - } - consume_skb(skb); - skb = n; - if (skb == NULL) { - bh_unlock_sock(sk_atm(vcc)); - return DROP_PACKET; - } - } else if (!pppoatm_may_send(pvcc, skb->truesize)) + if (skb_cow_head(skb, LLC_LEN)) { + bh_unlock_sock(sk_atm(vcc)); + kfree_skb(skb); + return DROP_PACKET; + } + if (!pppoatm_may_send(pvcc, skb->truesize)) goto nospace; - memcpy(skb_push(skb, LLC_LEN), pppllc, LLC_LEN); break; case e_vc: if (!pppoatm_may_send(pvcc, skb->truesize)) @@ -346,6 +339,12 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) return 1; } + if (skb->data[0] == '\0' && (pvcc->flags & SC_COMP_PROT)) + skb_pull(skb, 1); + + if (pvcc->encaps == e_llc) + memcpy(skb_push(skb, LLC_LEN), pppllc, LLC_LEN); + atm_account_tx(vcc, skb); pr_debug("atm_skb(%p)->vcc(%p)->dev(%p)\n", skb, ATM_SKB(skb)->vcc, ATM_SKB(skb)->vcc->dev); @@ -355,13 +354,6 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) return ret; nospace: bh_unlock_sock(sk_atm(vcc)); - /* - * We don't have space to send this SKB now, but we might have - * already applied SC_COMP_PROT compression, so may need to undo - */ - if ((pvcc->flags & SC_COMP_PROT) && skb_headroom(skb) > 0 && - skb->data[-1] == '\0') - (void) skb_push(skb, 1); return 0; } From 455ebeadf714f51e1dbbd6a022c74c9215b1cd76 Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Sun, 13 Sep 2026 17:08:50 +0800 Subject: [PATCH 1780/2366] net: ip_tunnel: initialize `options_len` before referencing options The following command triggers a kernel panic: ip link add d0 type dummy; ip link set d0 up ip route add 10.30.0.0/16 \ encap ip id 300 geneve_opts 4660:66:11223344 dev d0 memcpy: detected buffer overflow: 4 byte write of buffer size 0 kernel BUG at lib/string_helpers.c:1044! ... ip_tun_parse_opts.part.0.cold+0x10/0x10 ip_tun_build_state+0x116/0x2a0 On kernels built with GCC 15+ and `CONFIG_FORTIFY_SOURCE`, the fortified `memcpy()` got 0 sized destination with request of 4 bytes length: static int ip_tun_parse_opts_geneve(...) { ... attr = tb[LWTUNNEL_IP_OPT_GENEVE_DATA]; data_len = nla_len(attr); /* == 4 */ struct geneve_opt *opt = ip_tunnel_info_opts(info) + opts_len; memcpy(opt->opt_data, nla_data(attr), data_len); /* ^^^^^^^^^^^^^ 0 since options_len is assigned afterwards */ Fixed by initializing the counter before the options are referenced. Matching what `tunnel_key_opts_set()` already does. Fixes: bb5e62f2d547 ("net: Add options as a flexible array to struct ip_tunnel_info") Cc: stable@vger.kernel.org Signed-off-by: Gris Ge Reviewed-by: Hangbin Liu Reviewed-by: Gustavo A. R. Silva Link: https://patch.msgid.link/20260913090851.468216-1-cnfourt@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/ip_tunnel_core.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/net/ipv4/ip_tunnel_core.c b/net/ipv4/ip_tunnel_core.c index 5168d546ea2f..bab42b9e277f 100644 --- a/net/ipv4/ip_tunnel_core.c +++ b/net/ipv4/ip_tunnel_core.c @@ -680,8 +680,14 @@ static int ip_tun_get_optlen(struct nlattr *attr, } static int ip_tun_set_opts(struct nlattr *attr, struct ip_tunnel_info *info, - struct netlink_ext_ack *extack) + int opts_len, struct netlink_ext_ack *extack) { + /* `options_len` is the __counted_by() annotation of the `options` + * flexible array, it must be initialized before parsing writes + * into it. + */ + info->options_len = opts_len; + return ip_tun_parse_opts(attr, info, extack); } @@ -712,7 +718,8 @@ static int ip_tun_build_state(struct net *net, struct nlattr *attr, tun_info = lwt_tun_info(new_state); - err = ip_tun_set_opts(tb[LWTUNNEL_IP_OPTS], tun_info, extack); + err = ip_tun_set_opts(tb[LWTUNNEL_IP_OPTS], tun_info, opt_len, + extack); if (err < 0) { lwtstate_free(new_state); return err; @@ -753,7 +760,6 @@ static int ip_tun_build_state(struct net *net, struct nlattr *attr, } tun_info->mode = IP_TUNNEL_INFO_TX; - tun_info->options_len = opt_len; *ts = new_state; @@ -1006,7 +1012,8 @@ static int ip6_tun_build_state(struct net *net, struct nlattr *attr, tun_info = lwt_tun_info(new_state); - err = ip_tun_set_opts(tb[LWTUNNEL_IP6_OPTS], tun_info, extack); + err = ip_tun_set_opts(tb[LWTUNNEL_IP6_OPTS], tun_info, opt_len, + extack); if (err < 0) { lwtstate_free(new_state); return err; @@ -1040,7 +1047,6 @@ static int ip6_tun_build_state(struct net *net, struct nlattr *attr, } tun_info->mode = IP_TUNNEL_INFO_TX | IP_TUNNEL_INFO_IPV6; - tun_info->options_len = opt_len; *ts = new_state; From 6a038ef2b57922b6d9ca98ddac0df0681849b704 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 10 Sep 2026 20:46:09 +0000 Subject: [PATCH 1781/2366] drop_monitor: synchronize tracepoint unregistration on error path If register_trace_napi_poll() fails in net_dm_trace_on_set(), unregister_trace_kfree_skb() is called to roll back the kfree_skb tracepoint registration. However, tracepoint_synchronize_unregister() is omitted before calling cancel_work_sync() and module_put(). An in-flight probe executing concurrently on another CPU could call schedule_work() after cancel_work_sync() has already returned, leaving a pending work item scheduled after the module reference is dropped. If the module is then unloaded, executing the work item triggers a kernel panic. Add tracepoint_synchronize_unregister() after unregister_trace_kfree_skb() in the error path, matching net_dm_trace_off_set() and net_dm_hw_probe_unregister(). Fixes: 7c747838a558 ("drop_monitor: Split tracing enable / disable to different functions") Signed-off-by: Eric Dumazet Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260910204612.3762015-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index abaf108ac4db..018d19e3a71d 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -1173,6 +1173,7 @@ static int net_dm_trace_on_set(struct netlink_ext_ack *extack) err_unregister_trace: unregister_trace_kfree_skb(ops->kfree_skb_probe, NULL); + tracepoint_synchronize_unregister(); err_module_put: for_each_possible_cpu(cpu) { struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu); From c391a40f71886b28c082b47270f0e856fa3e1150 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 10 Sep 2026 20:46:10 +0000 Subject: [PATCH 1782/2366] drop_monitor: use timer_shutdown_sync() to prevent timer rearming during teardown In drop_monitor teardown paths (net_dm_trace_off_set(), net_dm_hw_monitor_stop(), and error unwind paths in net_dm_trace_on_set() and net_dm_hw_monitor_start()), per-CPU timers are stopped using timer_delete_sync() followed by cancel_work_sync(). However, there is a circular dependency between send_timer and dm_alert_work: 1) sched_send_work() (timer callback) schedules dm_alert_work. 2) send_dm_alert() / net_dm_hw_summary_work() calls reset_per_cpu_data() or net_dm_hw_reset_per_cpu_data(). 3) If memory allocation fails under memory pressure in the reset function, it re-arms the timer via mod_timer(&data->send_timer, ...). If dm_alert_work is running concurrently while timer_delete_sync() executes on another CPU, an allocation failure in the worker will re-arm the timer after timer_delete_sync() has already returned. Once cancel_work_sync() completes and module_put() is called, the timer remains active in the timer wheel. If the module is then unloaded, the timer will fire and execute sched_send_work() in freed memory, triggering a kernel panic / use-after-free. Switch from timer_delete_sync() to timer_shutdown_sync(). This guarantees that any in-flight timer handler has finished and prevents subsequent re-arming attempts from running workers from succeeding. When monitoring is restarted later, timer_setup() is invoked, which cleanly re-initializes the timer. Fixes: 9398e9c0b1d4 ("drop_monitor: Perform cleanup upon probe registration failure") Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260910204612.3762015-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 018d19e3a71d..873155ca7243 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -1083,7 +1083,7 @@ static int net_dm_hw_monitor_start(struct netlink_ext_ack *extack) struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu); struct sk_buff *skb; - timer_delete_sync(&hw_data->send_timer); + timer_shutdown_sync(&hw_data->send_timer); cancel_work_sync(&hw_data->dm_alert_work); while ((skb = __skb_dequeue(&hw_data->drop_queue))) { struct devlink_trap_metadata *hw_metadata; @@ -1117,7 +1117,7 @@ static void net_dm_hw_monitor_stop(struct netlink_ext_ack *extack) struct per_cpu_dm_data *hw_data = &per_cpu(dm_hw_cpu_data, cpu); struct sk_buff *skb; - timer_delete_sync(&hw_data->send_timer); + timer_shutdown_sync(&hw_data->send_timer); cancel_work_sync(&hw_data->dm_alert_work); while ((skb = __skb_dequeue(&hw_data->drop_queue))) { struct devlink_trap_metadata *hw_metadata; @@ -1179,7 +1179,7 @@ static int net_dm_trace_on_set(struct netlink_ext_ack *extack) struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu); struct sk_buff *skb; - timer_delete_sync(&data->send_timer); + timer_shutdown_sync(&data->send_timer); cancel_work_sync(&data->dm_alert_work); while ((skb = __skb_dequeue(&data->drop_queue))) consume_skb(skb); @@ -1207,7 +1207,7 @@ static void net_dm_trace_off_set(void) struct per_cpu_dm_data *data = &per_cpu(dm_cpu_data, cpu); struct sk_buff *skb; - timer_delete_sync(&data->send_timer); + timer_shutdown_sync(&data->send_timer); cancel_work_sync(&data->dm_alert_work); while ((skb = __skb_dequeue(&data->drop_queue))) consume_skb(skb); From c19b7d35086b7d240f1ca3088b0079d2bd39ffb9 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 10 Sep 2026 20:46:11 +0000 Subject: [PATCH 1783/2366] drop_monitor: use raw_cpu_ptr() in tracepoint probes syzbot reported a preemption warning in sk_skb_reason_drop(): BUG: using smp_processor_id() in preemptible [00000000] code: syz.0.17/5917 caller is net_dm_packet_trace_kfree_skb_hit+0x119/0x350 net/core/drop_monitor.c:519 In net_dm_packet_trace_kfree_skb_hit(), data = this_cpu_ptr(&dm_cpu_data) is evaluated before spin_lock_irqsave(&data->drop_queue.lock, flags). When kfree_skb() is called from preemptible context (e.g. process context during close() on /dev/net/tun), preemption is enabled, triggering the CONFIG_DEBUG_PREEMPT warning in smp_processor_id(). The same pattern exists in net_dm_hw_trap_summary_probe() and net_dm_hw_trap_packet_probe() for dm_hw_cpu_data. This is a false positive because each per-cpu structure is protected by its own spinlock. If the task migrates to another CPU right after reading the per-cpu pointer, the lock still safely synchronizes access to that queue. Use raw_cpu_ptr() instead of this_cpu_ptr() to silence CONFIG_DEBUG_PREEMPT without disturbing interrupt state or breaking PREEMPT_RT locking semantics. Fixes: ca30707dee2b ("drop_monitor: Add packet alert mode") Fixes: 5855357cd40e ("drop_monitor: Prepare probe functions for devlink tracepoint") Reported-by: syzbot+dc57fd6722deb17e92af@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6aa316b2.f81106d8.2ab401.0014.GAE@google.com/ Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260910204612.3762015-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 873155ca7243..795c15dd1771 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -448,7 +448,7 @@ net_dm_hw_trap_summary_probe(void *ignore, const struct devlink *devlink, if (metadata->trap_type == DEVLINK_TRAP_TYPE_CONTROL) return; - hw_data = this_cpu_ptr(&dm_hw_cpu_data); + hw_data = raw_cpu_ptr(&dm_hw_cpu_data); raw_spin_lock_irqsave(&hw_data->lock, flags); hw_entries = hw_data->hw_entries; @@ -516,7 +516,7 @@ static void net_dm_packet_trace_kfree_skb_hit(void *ignore, */ nskb->tstamp = tstamp; - data = this_cpu_ptr(&dm_cpu_data); + data = raw_cpu_ptr(&dm_cpu_data); spin_lock_irqsave(&data->drop_queue.lock, flags); if (skb_queue_len(&data->drop_queue) < net_dm_queue_len) @@ -983,7 +983,7 @@ net_dm_hw_trap_packet_probe(void *ignore, const struct devlink *devlink, NET_DM_SKB_CB(nskb)->hw_metadata = n_hw_metadata; nskb->tstamp = tstamp; - hw_data = this_cpu_ptr(&dm_hw_cpu_data); + hw_data = raw_cpu_ptr(&dm_hw_cpu_data); spin_lock_irqsave(&hw_data->drop_queue.lock, flags); if (skb_queue_len(&hw_data->drop_queue) < net_dm_queue_len) From 439f392084f8f7f59ab9d47a9579185accefe1d8 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 10 Sep 2026 20:46:12 +0000 Subject: [PATCH 1784/2366] drop_monitor: fix out-of-bounds write in reset_per_cpu_data() In reset_per_cpu_data(), al is computed as: al = sizeof(struct net_dm_alert_msg); al += dm_hit_limit * sizeof(struct net_dm_drop_point); al += sizeof(struct nlattr); skb = genlmsg_new(al, GFP_KERNEL); ... nla = nla_reserve(skb, NLA_UNSPEC, sizeof(struct net_dm_alert_msg)); ... msg = nla_data(nla); memset(msg, 0, al); Because al includes sizeof(struct nlattr) (the 4-byte attribute header), genlmsg_new() allocates al bytes of tailroom starting at nla. However, msg points to nla_data(nla), which is located sizeof(struct nlattr) bytes past nla. Calling memset(msg, 0, al) therefore writes al bytes starting from msg, exceeding the allocated buffer by sizeof(struct nlattr) (4 bytes) and corrupting skb_shared_info. Fix this by letting al represent only the payload length, allocating the skb with genlmsg_new(nla_total_size(al), GFP_KERNEL), and zeroing al bytes from msg. Fixes: 683703a26e46 ("drop_monitor: Update netlink protocol to include netlink attribute header in alert message") Signed-off-by: Eric Dumazet Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260910204612.3762015-5-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/drop_monitor.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index 795c15dd1771..edc660778408 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -141,9 +141,8 @@ static struct sk_buff *reset_per_cpu_data(struct per_cpu_dm_data *data) al = sizeof(struct net_dm_alert_msg); al += dm_hit_limit * sizeof(struct net_dm_drop_point); - al += sizeof(struct nlattr); - skb = genlmsg_new(al, GFP_KERNEL); + skb = genlmsg_new(nla_total_size(al), GFP_KERNEL); if (!skb) goto err; From 3f118c8217c109fd13ca61caa301d72c483897ef Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Sat, 12 Sep 2026 21:22:43 +0800 Subject: [PATCH 1785/2366] openvswitch: avoid reallocating confirmed conntrack labels ovs_ct_get_conn_labels() adds the labels extension when a conntrack entry does not have one. Confirmed conntracks can be read locklessly, so adding an extension may reallocate and free the extension block while another CPU accesses it. Only add the extension for unconfirmed conntracks. A confirmed conntrack without labels now fails the caller's label operation instead of reallocating its extension storage. Fixes: c2ac66735870 ("openvswitch: Allow matching on conntrack label") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Reviewed-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/372fbb062b40ae6723684f55484be86ff0064f8e.1789218015.git.zhilinz@nebusec.ai Signed-off-by: Jakub Kicinski --- net/openvswitch/conntrack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index 27115967e5d9..0f433688e17b 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -366,7 +366,7 @@ static struct nf_conn_labels *ovs_ct_get_conn_labels(struct nf_conn *ct) struct nf_conn_labels *cl; cl = nf_ct_labels_find(ct); - if (!cl) { + if (!cl && !nf_ct_is_confirmed(ct)) { nf_ct_labels_ext_add(ct); cl = nf_ct_labels_find(ct); } From f0ef4b1eaed000a304726a43091588e8426ba08a Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 14 Sep 2026 09:41:07 +0200 Subject: [PATCH 1786/2366] net: stmmac: do not overwrite phc_index when no PTP clock is registered stmmac_get_ts_info() reports phc_index as 0 when hardware timestamping is supported but no PTP clock has been registered yet (e.g. while the interface is down). Zero is a valid PHC index and would make userspace resolve the wrong clock; the absence of a clock should be reported as -1. The ethtool core already initializes phc_index to -1 before invoking the get_ts_info callback (ethtool_init_tsinfo()), so just drop the erroneous assignment. Fixes: 9364fa7fcf12 ("net: stmmac: Remove setting of RX software timestamp") Reviewed-by: Maxime Chevallier Reviewed-by: Rahul Rameshbabu Signed-off-by: Lorenzo Bianconi Reviewed-by: Gal Pressman Link: https://patch.msgid.link/20260914-stmmac-fix-phc_index-v2-1-bf3d90373fe4@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index 154cc0c7623d..1be5310ca766 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -1016,8 +1016,6 @@ static int stmmac_get_ts_info(struct net_device *dev, if (priv->ptp_clock) info->phc_index = ptp_clock_index(priv->ptp_clock); - else - info->phc_index = 0; info->tx_types = (1 << HWTSTAMP_TX_OFF) | (1 << HWTSTAMP_TX_ON); From 2842ce397dd09882530b42f7fdb0c855767eb24e Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Mon, 14 Sep 2026 13:52:58 +0300 Subject: [PATCH 1787/2366] net: bridge: vlan: fix bugs caused by switchdev deletion errors Allowing switchdev to prevent vlan deletion and error out in __vlan_del could cause multiple different issues - inconsistent state, memory leaks when flushing, NULL pointer dereference on bridge error when flushing. It doesn't make sense to allow it to stop __vlan_del, so log the error and continue with software vlan deletion. This is also consistent with 8021q behaviour. Suggested-by: Ido Schimmel Fixes: bf361ad38165 ("net: bridge: check __vlan_vid_del for error") Fixes: 5454f5c28eca ("net: bridge: vlan: check for errors from __vlan_del in __vlan_flush") Fixes: 2594e9064a57 ("bridge: vlan: add per-vlan struct and move to rhashtables") Fixes: 9c86ce2c1ae3 ("net: bridge: Notify about bridge VLANs") Signed-off-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260914105258.3436918-1-razor@blackwall.org Signed-off-by: Jakub Kicinski --- net/bridge/br_vlan.c | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c index 1e0e436629ec..92b3cb621a26 100644 --- a/net/bridge/br_vlan.c +++ b/net/bridge/br_vlan.c @@ -387,12 +387,12 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags, goto out; } -static int __vlan_del(struct net_bridge_vlan *v) +static void __vlan_del(struct net_bridge_vlan *v) { struct net_bridge_vlan *masterv = v; struct net_bridge_vlan_group *vg; struct net_bridge_port *p = NULL; - int err = 0; + int err; if (br_vlan_is_master(v)) { vg = br_vlan_group(v->br); @@ -406,12 +406,16 @@ static int __vlan_del(struct net_bridge_vlan *v) if (p) { err = __vlan_vid_del(p->dev, p->br, v); if (err) - goto out; + br_warn(p->br, + "port %u(%s) failed to delete vlan %u from switchdev: %pe\n", + (unsigned int)p->port_no, p->dev->name, + v->vid, ERR_PTR(err)); } else { err = br_switchdev_port_vlan_del(v->br->dev, v->vid); if (err && err != -EOPNOTSUPP) - goto out; - err = 0; + br_warn(v->br, + "failed to delete bridge vlan %u from switchdev: %pe\n", + v->vid, ERR_PTR(err)); } if (br_vlan_should_use(v)) { @@ -431,8 +435,6 @@ static int __vlan_del(struct net_bridge_vlan *v) } br_vlan_put_master(masterv); -out: - return err; } static void __vlan_group_free(struct net_bridge_vlan_group *vg) @@ -449,7 +451,6 @@ static void __vlan_flush(const struct net_bridge *br, { struct net_bridge_vlan *vlan, *tmp; u16 v_start = 0, v_end = 0; - int err; __vlan_delete_pvid(vg, vg->pvid); list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist) { @@ -463,13 +464,7 @@ static void __vlan_flush(const struct net_bridge *br, } v_end = vlan->vid; - err = __vlan_del(vlan); - if (err) { - br_err(br, - "port %u(%s) failed to delete vlan %d: %pe\n", - (unsigned int) p->port_no, p->dev->name, - vlan->vid, ERR_PTR(err)); - } + __vlan_del(vlan); } /* notify about the last/whole vlan range */ @@ -837,8 +832,9 @@ int br_vlan_delete(struct net_bridge *br, u16 vid) br_fdb_delete_by_port(br, NULL, vid, 0); vlan_tunnel_info_del(vg, v); + __vlan_del(v); - return __vlan_del(v); + return 0; } void br_vlan_flush(struct net_bridge *br) @@ -1368,8 +1364,9 @@ int nbp_vlan_delete(struct net_bridge_port *port, u16 vid) return -ENOENT; br_fdb_find_delete_local(port->br, port, port->dev->dev_addr, vid); br_fdb_delete_by_port(port->br, port, vid, 0); + __vlan_del(v); - return __vlan_del(v); + return 0; } void nbp_vlan_flush(struct net_bridge_port *port) From b52d695d062095327b944acf7daabbc816ab319b Mon Sep 17 00:00:00 2001 From: Stanley Jhu Date: Sat, 12 Sep 2026 21:16:25 +0800 Subject: [PATCH 1788/2366] scsi: ufs: core: Keep internal commands dispatchable during error handling Commit 08b12cda6c44 ("scsi: ufs: core: Switch to scsi_get_internal_cmd()") switched UFS internal commands to allocate requests on hba->host->pseudo_sdev->request_queue, which shares the host tagset with regular LUNs. During error recovery, ufshcd_err_handling_prepare() calls blk_mq_quiesce_tagset(&hba->host->tag_set), marking all queues in the tagset as quiesced, including pseudo_sdev->request_queue. When ufshcd_verify_dev_init() subsequently issues internal commands (e.g. NOP OUT UPIU) via blk_execute_rq(), blk_mq_run_hw_queue() skips running the quiesced queue, resulting in an unrecoverable circular wait deadlock. Keep quiescing the tagset and unquiesce the pseudo SCSI device on top of that, so internal commands stay dispatchable while the logical units remain quiesced. Re-quiesce the pseudo device before unquiescing the tagset so that quiesce_depth stays balanced. Clock scaling and ufshcd_pause_command_processing() are unaffected: they keep quiescing the whole tagset, internal commands included. Fixes: 08b12cda6c44 ("scsi: ufs: core: Switch to scsi_get_internal_cmd()") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/6f78c4bd-a70b-402d-abfd-599091b67674@acm.org/ Signed-off-by: Stanley Jhu Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260912131625.2301486-1-stanleyjhu@google.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 2ba244cf40ac..54f4e7d7de02 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -6817,11 +6817,17 @@ static void ufshcd_err_handling_prepare(struct ufs_hba *hba) } /* Wait for ongoing ufshcd_queuecommand() calls to finish. */ blk_mq_quiesce_tagset(&hba->host->tag_set); + /* + * Internal commands are submitted on the pseudo SCSI device. Let them + * through so that the error handler can recover the link. + */ + blk_mq_unquiesce_queue(hba->host->pseudo_sdev->request_queue); cancel_work_sync(&hba->eeh_work); } static void ufshcd_err_handling_unprepare(struct ufs_hba *hba) { + blk_mq_quiesce_queue_nowait(hba->host->pseudo_sdev->request_queue); blk_mq_unquiesce_tagset(&hba->host->tag_set); ufshcd_release(hba); if (ufshcd_is_clkscaling_supported(hba)) From ceac0de741bfb47ca255eee075257b3bb31f0651 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 11 Sep 2026 16:08:04 +0000 Subject: [PATCH 1789/2366] netlink: do not free nlk->groups while lockless readers can use it netlink_realloc_groups() uses krealloc() under netlink_table_grab(). Whenever NLGRPSZ(groups) lands in a different kmalloc bucket, the old bitmap is freed immediately. Two readers of nlk->groups / nlk->ngroups do not hold the netlink table lock: 1) sk_diag_dump_groups(). Hashed (bound) sockets are dumped from the rhashtable walk in __netlink_diag_dump(), which only holds RCU. Only the mc_list part of the dump takes nl_table_lock. 2) netlink_native_seq_show() (/proc/net/netlink), whose walk has been lockless since commit 21e4902aea80 ("netlink: Lockless lookup with RCU grace period in socket release"). Both can read a freed buffer, and sk_diag_dump_groups() can also read past the end of the old (smaller) buffer if it happens to load the old @groups pointer together with the new @ngroups value, copying the result into a NETLINK_DIAG_GROUPS attribute. This is the same class of bug that commit f773608026ee ("netlink: access nlk groups safely in netlink bind and getname") fixed for bind() and getname(); these two readers were missed. Simply grabbing the table lock in sk_diag_dump_groups() is not an option, because it is also called with nl_table_lock already held from the mc_list section of the dump. Make the lockless readers safe instead: - Allocate a new bitmap and free the old one after an RCU grace period, instead of relying on the implicit kfree() done by krealloc(). - Publish @groups before @ngroups, both with release semantics, and have the lockless readers load @ngroups first. A reader can then never pair the new (bigger) size with the old (smaller) buffer, and a reader picking up the new pointer while still seeing the old size is guaranteed to see the initialized bitmap. netlink_realloc_groups() is called from process context (bind() and setsockopt()), so kfree_rcu_mightsleep() can be used, once the table has been released. Fixes: 21e4902aea80 ("netlink: Lockless lookup with RCU grace period in socket release") Fixes: ad202074320c ("netlink: Use rhashtable walk interface in diag dump") Reported-by: James Burton Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260911160804.917099-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/netlink/af_netlink.c | 42 ++++++++++++++++++++++++++++++++-------- net/netlink/diag.c | 18 ++++++++++++++--- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index e6b1d9758c9c..9fdf964224ab 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -922,9 +922,9 @@ netlink_update_subscriptions(struct sock *sk, unsigned int subscriptions) static int netlink_realloc_groups(struct sock *sk) { + unsigned long *new_groups, *old_groups = NULL; struct netlink_sock *nlk = nlk_sk(sk); unsigned int groups; - unsigned long *new_groups; int err = 0; netlink_table_grab(); @@ -938,18 +938,37 @@ static int netlink_realloc_groups(struct sock *sk) if (nlk->ngroups >= groups) goto out_unlock; - new_groups = krealloc(nlk->groups, NLGRPSZ(groups), GFP_ATOMIC); - if (new_groups == NULL) { + /* Can not use krealloc(), because the old buffer might be freed + * immediately, while lockless readers (netlink diag dump and + * /proc/net/netlink) can still be looking at it. + */ + new_groups = kzalloc(NLGRPSZ(groups), GFP_ATOMIC); + if (!new_groups) { err = -ENOMEM; goto out_unlock; } - memset((char *)new_groups + NLGRPSZ(nlk->ngroups), 0, - NLGRPSZ(groups) - NLGRPSZ(nlk->ngroups)); + old_groups = nlk->groups; + if (old_groups) + memcpy(new_groups, old_groups, NLGRPSZ(nlk->ngroups)); + + /* Publish the new bitmap and its content: pairs with the address + * dependency in lockless readers, which can pick up the new pointer + * while still seeing the old (smaller) nlk->ngroups. + */ + smp_store_release(&nlk->groups, new_groups); + + /* Then publish the new size: pairs with smp_load_acquire() from + * lockless readers, so that they can not read NLGRPSZ(new ngroups) + * bytes from the old buffer. + */ + smp_store_release(&nlk->ngroups, groups); - nlk->groups = new_groups; - nlk->ngroups = groups; out_unlock: netlink_table_ungrab(); + + if (old_groups) + kfree_rcu_mightsleep(old_groups); + return err; } @@ -2705,12 +2724,19 @@ static int netlink_native_seq_show(struct seq_file *seq, void *v) } else { struct sock *s = v; struct netlink_sock *nlk = nlk_sk(s); + const unsigned long *groups; + + /* Lockless read : netlink_realloc_groups() can change + * nlk->groups under us. The old buffer is freed after an + * RCU grace period, and this walk is RCU protected. + */ + groups = READ_ONCE(nlk->groups); seq_printf(seq, "%pK %-3d %-10u %08x %-8d %-8d %-5d %-8d %-8u %-8llu\n", s, s->sk_protocol, nlk->portid, - nlk->groups ? (u32)nlk->groups[0] : 0, + groups ? (u32)groups[0] : 0, sk_rmem_alloc_get(s), sk_wmem_alloc_get(s), READ_ONCE(nlk->cb_running), diff --git a/net/netlink/diag.c b/net/netlink/diag.c index 0b3e021bd0ed..7979bd9b2606 100644 --- a/net/netlink/diag.c +++ b/net/netlink/diag.c @@ -12,12 +12,24 @@ static int sk_diag_dump_groups(struct sock *sk, struct sk_buff *nlskb) { struct netlink_sock *nlk = nlk_sk(sk); + unsigned long *groups; + unsigned int ngroups; - if (nlk->groups == NULL) + /* Hashed sockets are dumped from the rhashtable walk, which only + * holds rcu_read_lock(), while netlink_realloc_groups() can replace + * nlk->groups and nlk->ngroups at any time. + * + * Read nlk->ngroups first : this pairs with smp_store_release() + * from netlink_realloc_groups(), so that we can not use the new + * (bigger) size with the old (smaller) buffer. The old buffer is + * freed after an RCU grace period. + */ + ngroups = smp_load_acquire(&nlk->ngroups); + groups = READ_ONCE(nlk->groups); + if (!groups) return 0; - return nla_put(nlskb, NETLINK_DIAG_GROUPS, NLGRPSZ(nlk->ngroups), - nlk->groups); + return nla_put(nlskb, NETLINK_DIAG_GROUPS, NLGRPSZ(ngroups), groups); } static int sk_diag_put_flags(struct sock *sk, struct sk_buff *skb) From c9ee6511332687ea714ad8ab86a53cb837d86eea Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 14 Sep 2026 16:00:01 +0200 Subject: [PATCH 1790/2366] scsi: ufs: pltfrm: Add quirk for R-Car S4 lacking lanes-per-direction Since commit e72323f3b09f ("scsi: ufs: core: Configure only active lanes during link"), the following error is observed on R-Car S4: ufshcd-renesas e6860000.ufs: Tx lane mismatch [config,reported] [2,1] ufshcd-renesas e6860000.ufs: link startup failed -67 ufshcd-renesas e6860000.ufs: error -ENOLINK: Initialization failed with error -67 ufshcd-renesas e6860000.ufs: probe with driver ufshcd-renesas failed with error -67 R-Car S4 has one UFS lane per direction, as described in section 152.1 of its hardware manual. Without lanes-per-direction, the UFS platform driver defaults to two lanes. Previously, the core used PA_CONNECTEDRXDATALANES and PA_CONNECTEDTXDATALANES to configure the link without checking them against lanes-per-direction, so the missing property did not prevent initialization. While fixing the R-Car S4 DTS is the proper solution, doing only that would still break backwards compatibility with existing DTBs. Hence add a quirk to let lanes-per-direction default to one on R-Car S4. Fixes: e72323f3b09f9c89 ("scsi: ufs: core: Configure only active lanes during link") Reported-by: Koichiro Den Closes: https://lore.kernel.org/20260911073058.253000-1-den@valinux.co.jp Cc: stable@vger.kernel.org # 7.2+ Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/ae0cc2bd764e6dfffce99db3d8b44a55887c508c.1789394185.git.geert+renesas@glider.be Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/host/ufshcd-pltfrm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/ufs/host/ufshcd-pltfrm.c b/drivers/ufs/host/ufshcd-pltfrm.c index 5ac7afe75934..169dbc1a75fe 100644 --- a/drivers/ufs/host/ufshcd-pltfrm.c +++ b/drivers/ufs/host/ufshcd-pltfrm.c @@ -206,7 +206,11 @@ static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba) dev_dbg(hba->dev, "%s: failed to read lanes-per-direction, ret=%d\n", __func__, ret); - hba->lanes_per_direction = UFSHCD_DEFAULT_LANES_PER_DIRECTION; + /* Old R-Car S4 DTBs lack "lanes-per-direction = <1>" */ + if (of_device_is_compatible(dev->of_node, "renesas,r8a779f0-ufs")) + hba->lanes_per_direction = 1; + else + hba->lanes_per_direction = UFSHCD_DEFAULT_LANES_PER_DIRECTION; } } From bce07e2f37b5e4a427d36fd6b1c14067b27591db Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Sat, 1 Aug 2026 22:36:35 +0900 Subject: [PATCH 1791/2366] scsi: libiscsi_tcp: Check the data direction of a Data-In PDU The Data-In branch of iscsi_tcp_hdr_dissect() resolves the ITT to a task and copies the PDU's data segment into that command's scatterlist without asking whether the command was reading. iscsi_tcp_r2t_rsp() in the same file does ask, and rejects an R2T for a command that is not DMA_TO_DEVICE. A target that answers a WRITE command's ITT with a Data-In therefore has the initiator write target-supplied bytes into the pages that write was about to send. Those are the caller's own pinned pages for an O_DIRECT write, and page cache pages for a buffered one. Observed against a test target that emits one 512-byte Data-In naming a 128 KB write's ITT, after the R2T for that write. With O_DIRECT the caller's buffer ends up holding 512 bytes of the target's data while pwrite() returns 131072. Buffered is quieter: pwrite() and fsync() both succeed, nothing is logged, and reading those blocks back returns the target's bytes out of the page cache without a command going on the wire. Check the direction before using the scatterlist, the way the R2T path already does. Cc: stable@vger.kernel.org Signed-off-by: Yehyeong Lee Reviewed-by: Mike Christie Link: https://patch.msgid.link/20260801133635.1986706-1-yhlee@isslab.korea.ac.kr Fixes: a081c13e39b5 ("[SCSI] iscsi_tcp: split module into lib and lld") Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/libiscsi_tcp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/scsi/libiscsi_tcp.c b/drivers/scsi/libiscsi_tcp.c index 7223bb18b048..d35f93451ee9 100644 --- a/drivers/scsi/libiscsi_tcp.c +++ b/drivers/scsi/libiscsi_tcp.c @@ -480,6 +480,9 @@ static int iscsi_tcp_data_in(struct iscsi_conn *conn, struct iscsi_task *task) int datasn = be32_to_cpu(rhdr->datasn); unsigned total_in_length = task->sc->sdb.length; + if (task->sc->sc_data_direction != DMA_FROM_DEVICE) + return ISCSI_ERR_PROTO; + /* * lib iscsi will update this in the completion handling if there * is status. From f06a44e235ef188689ba23ffc72e9e89b10951a9 Mon Sep 17 00:00:00 2001 From: "Ewan D. Milne" Date: Tue, 15 Sep 2026 13:38:00 -0400 Subject: [PATCH 1792/2366] scsi: devinfo: Add BLIST_SKIP_IO_HINTS for EMC Symmetrix EMC Symmetrix returns an error on MODE SENSE for page 0Ah subpage 05h because it does not implement the SBC-5 I/O hints. These commands began to be sent as a result of commit 4f53138fffc2 ("scsi: sd: Translate data lifetime information"). Add BLIST_SKIP_IO_HINTS to avoid sending these commands because in large configurations the failed commands are displacing other useful information in internal error logs. Signed-off-by: Ewan D. Milne Link: https://patch.msgid.link/20260915173800.39117-1-emilne@redhat.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/scsi_devinfo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/scsi/scsi_devinfo.c b/drivers/scsi/scsi_devinfo.c index 15ffbe93ac72..88a911b35c94 100644 --- a/drivers/scsi/scsi_devinfo.c +++ b/drivers/scsi/scsi_devinfo.c @@ -161,7 +161,7 @@ static struct { {"DGC", "DISK", NULL, BLIST_SPARSELUN}, /* EMC CLARiiON, no storage on LUN 0 */ {"EMC", "Invista", "*", BLIST_SPARSELUN | BLIST_LARGELUN}, {"EMC", "SYMMETRIX", NULL, BLIST_SPARSELUN | BLIST_LARGELUN | - BLIST_REPORTLUN2 | BLIST_RETRY_ITF}, + BLIST_REPORTLUN2 | BLIST_RETRY_ITF | BLIST_SKIP_IO_HINTS}, {"EMULEX", "MD21/S2 ESDI", NULL, BLIST_SINGLELUN}, {"easyRAID", "16P", NULL, BLIST_NOREPORTLUN}, {"easyRAID", "X6P", NULL, BLIST_NOREPORTLUN}, From 278210c60c6f6958bd2eeaa2120c862683b83d09 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 15 Sep 2026 22:20:59 +0200 Subject: [PATCH 1793/2366] scsi: leapraid: Avoid -Wformat-security warning When extra warnings are enabled, the alloc_ordered_workqueue() function cannot be called with a variable name for the format string: drivers/scsi/leapraid/leapraid_os.c: In function 'leapraid_probe': drivers/scsi/leapraid/leapraid_os.c:2062:58: error: format not a string literal and no format arguments [-Werror=format-security] 2062 | alloc_ordered_workqueue(adapter->fw_evt_s.fw_evt_name, 0); | ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~ As the string is only assembled before the call and not used anywhere else, just fold the earlier snprintf() into the alloc_ordered_workqueue() call. Fixes: 5597088c9e79 ("scsi: leapraid: Add new SCSI driver") Signed-off-by: Arnd Bergmann Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/20260915202134.3534708-1-arnd@kernel.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/leapraid/leapraid_func.h | 2 -- drivers/scsi/leapraid/leapraid_os.c | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/scsi/leapraid/leapraid_func.h b/drivers/scsi/leapraid/leapraid_func.h index 4c0b9ca728d8..e8a0815bf95f 100644 --- a/drivers/scsi/leapraid/leapraid_func.h +++ b/drivers/scsi/leapraid/leapraid_func.h @@ -554,7 +554,6 @@ struct leapraid_fw_evt_work { /** * struct leapraid_fw_evt_struct - Firmware event handling structure * - * @fw_evt_name: Name of the firmware event. * @fw_evt_thread: Workqueue used for processing firmware events. * @fw_evt_lock: Spinlock protecting access to the firmware event list. * @fw_evt_list: Linked list of pending firmware events. @@ -565,7 +564,6 @@ struct leapraid_fw_evt_work { */ struct leapraid_fw_evt_struct { u32 leapraid_evt_masks[4]; - char fw_evt_name[48]; struct workqueue_struct *fw_evt_thread; spinlock_t fw_evt_lock; /* protects firmware event */ struct list_head fw_evt_list; diff --git a/drivers/scsi/leapraid/leapraid_os.c b/drivers/scsi/leapraid/leapraid_os.c index ee3242779dfd..507f11862276 100644 --- a/drivers/scsi/leapraid/leapraid_os.c +++ b/drivers/scsi/leapraid/leapraid_os.c @@ -2054,12 +2054,10 @@ static int leapraid_probe(struct pci_dev *pdev, const struct pci_device_id *id) shost->transportt = leapraid_transport_template; shost->unique_id = adapter->adapter_attr.id; - snprintf(adapter->fw_evt_s.fw_evt_name, - sizeof(adapter->fw_evt_s.fw_evt_name), - "fw_event_%s%d", LEAPRAID_DRIVER_NAME, - adapter->adapter_attr.id); adapter->fw_evt_s.fw_evt_thread = - alloc_ordered_workqueue(adapter->fw_evt_s.fw_evt_name, 0); + alloc_ordered_workqueue("fw_event_%s%d", 0, + LEAPRAID_DRIVER_NAME, + adapter->adapter_attr.id); if (!adapter->fw_evt_s.fw_evt_thread) { dev_err(&adapter->pdev->dev, "%s: Failed to create fw event workqueue\n", __func__); From 8c0c602202b9a4909b00bc3354e3c0355bc69e65 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 29 Aug 2026 23:32:55 +0200 Subject: [PATCH 1794/2366] selinux: preserve user SID across nested backing files SELinux saves the user file SID in a backing-file security blob so it remains available after mmap() replaces vma->vm_file with a backing file. For nested backing files (overlayfs over overlayfs, or FUSE passthrough backed by overlayfs), user_file may itself be a backing file. Its fsec->sid is the SID of the mounter that opened it, rather than the user that opened the top-level file. mprotect() then checks fd { use } against the mounter SID. This can incorrectly deny access without a domain transition, or check the wrong target SID after one. Copy the saved user SID when user_file is a backing file. Keep using the regular file SID for the first backing layer. With two nested overlayfs mounts and SELinux enforcing, mprotect(PROT_READ) returns EACCES with an fd { use } denial against the mounter SID. With this change, mprotect() succeeds. Tested on arm64 QEMU with a small BusyBox initramfs and a purpose-built SELinux policy. The original test was also repeated with Fedora Cloud Base 44 userspace and gave the same result. Cc: stable@vger.kernel.org Fixes: 82544d36b172 ("selinux: fix overlayfs mmap() and mprotect() access checks") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Amir Goldstein Reviewed-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 9 ++++++++- security/selinux/include/objsec.h | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index e5e17f100aae..b0bb5f8c90bf 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3843,13 +3843,20 @@ static int selinux_file_alloc_security(struct file *file) return 0; } +static inline u32 selinux_file_user_sid(const struct file *file) +{ + if (unlikely(file->f_mode & FMODE_BACKING)) + return selinux_backing_file(file)->uf_sid; + return selinux_file(file)->sid; +} + static int selinux_backing_file_alloc(struct file *backing_file, const struct file *user_file) { struct backing_file_security_struct *bfsec; bfsec = selinux_backing_file(backing_file); - bfsec->uf_sid = selinux_file(user_file)->sid; + bfsec->uf_sid = selinux_file_user_sid(user_file); return 0; } diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 3c0a16ec978b..853f7266ed18 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -87,7 +87,7 @@ struct file_security_struct { }; struct backing_file_security_struct { - u32 uf_sid; /* associated user file fsec->sid */ + u32 uf_sid; /* top-level user file fsec->sid */ }; struct superblock_security_struct { From 78fc54b934bfb2c18aad8154c7302067146946f9 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 29 Aug 2026 23:32:56 +0200 Subject: [PATCH 1795/2366] selinux: recheck intermediate backing files on mprotect() mprotect() can be used to bypass the SELinux checks that mmap() performs against the intermediate layers of a stacked filesystem. mmap() checks every backing layer as the request descends through the stack. mprotect() only has the lowest backing file in vma->vm_file, so it rechecks the top-level user and the lowest mounter, but skips the mounters of every layer in between. With two nested overlayfs mounts and a policy denying mounter_t -> middle_file_t:file { execute }, a direct mmap(PROT_EXEC) is denied: avc: denied { execute } for pid=71 comm="nested_exec" path="/payload" dev="overlay" ino=9 scontext=user_u:base_r:mounter_t tcontext=user_u:object_r:middle_file_t tclass=file permissive=0 while mmap(PROT_NONE) followed by mprotect(PROT_EXEC) succeeds. Preserve each intermediate path, mounter SID and file-description SID in the backing-file security blob, copying the saved entries when another backing layer is opened. Allocate the array only for nested backing files, and release it and the path references in the backing_file_free hook. During mprotect(), recheck fd { use } and the requested inode permissions for every saved mounter, and include the intermediate layers in the execmod checks. Policy for nested stacking may then need to grant intermediate mounters what a direct mmap() already requires, and execmod on intermediate labels for binaries using text relocations. Tested on arm64 QEMU with a small BusyBox initramfs and a purpose-built SELinux policy, on a mainline tree containing commit f2381b546e7e ("fs: fix user path of nested backing files"). Cc: stable@vger.kernel.org Fixes: 82544d36b172 ("selinux: fix overlayfs mmap() and mprotect() access checks") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Stephen Smalley [PM: subject tweak] Signed-off-by: Paul Moore --- security/selinux/hooks.c | 149 ++++++++++++++++++++++++++---- security/selinux/include/objsec.h | 8 ++ 2 files changed, 137 insertions(+), 20 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index b0bb5f8c90bf..3f4a6ddee322 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1674,24 +1674,30 @@ static int cred_has_capability(const struct cred *cred, return rc; } -/* Check whether a task has a particular permission to an inode. - The 'adp' parameter is optional and allows other audit - data to be passed (e.g. the dentry). */ +/* + * Check whether a SID has a particular permission to an inode. The 'adp' + * parameter is optional and allows other audit data to be passed (e.g. the + * dentry). + */ +static int inode_sid_has_perm(u32 sid, struct inode *inode, u32 perms, + struct common_audit_data *adp) +{ + struct inode_security_struct *isec; + + if (unlikely(IS_PRIVATE(inode))) + return 0; + + isec = selinux_inode(inode); + + return avc_has_perm(sid, isec->sid, isec->sclass, perms, adp); +} + static int inode_has_perm(const struct cred *cred, struct inode *inode, u32 perms, struct common_audit_data *adp) { - struct inode_security_struct *isec; - u32 sid; - - if (unlikely(IS_PRIVATE(inode))) - return 0; - - sid = cred_sid(cred); - isec = selinux_inode(inode); - - return avc_has_perm(sid, isec->sid, isec->sclass, perms, adp); + return inode_sid_has_perm(cred_sid(cred), inode, perms, adp); } /* Same as inode_has_perm, but pass explicit audit data containing @@ -3854,13 +3860,63 @@ static int selinux_backing_file_alloc(struct file *backing_file, const struct file *user_file) { struct backing_file_security_struct *bfsec; + const struct backing_file_security_struct *ubfsec; + struct backing_file_security_layer *layer; + u32 i; bfsec = selinux_backing_file(backing_file); bfsec->uf_sid = selinux_file_user_sid(user_file); + if (!(user_file->f_mode & FMODE_BACKING)) + return 0; + + ubfsec = selinux_backing_file(user_file); + /* a wrapped count would make kmalloc_array() return ZERO_SIZE_PTR */ + if (unlikely(ubfsec->layer_count == U32_MAX)) + return -EOVERFLOW; + + /* + * The final VMA only retains the lowest backing file, so record the + * whole chain here rather than in the mmap hook, where concurrent + * mappings would have to be serialized. Size it dynamically: erofs + * inode sharing adds a backing file without bumping s_stack_depth. + */ + bfsec->layers = kmalloc_array(ubfsec->layer_count + 1, + sizeof(*bfsec->layers), GFP_KERNEL); + if (!bfsec->layers) + return -ENOMEM; + + for (i = 0; i < ubfsec->layer_count; i++) { + layer = &bfsec->layers[i]; + *layer = ubfsec->layers[i]; + path_get(&layer->path); + } + + /* f_path, not file_user_path(): this layer, not the top-level file */ + layer = &bfsec->layers[i]; + layer->path = user_file->f_path; + layer->mounter_sid = cred_sid(user_file->f_cred); + layer->fd_sid = selinux_file(user_file)->sid; + path_get(&layer->path); + bfsec->layer_count = ubfsec->layer_count + 1; return 0; } +static void selinux_backing_file_free(struct file *backing_file) +{ + struct backing_file_security_struct *bfsec; + + /* security_backing_file_free() may be called twice after an error */ + if (!backing_file_security(backing_file)) + return; + + bfsec = selinux_backing_file(backing_file); + while (bfsec->layer_count) + path_put(&bfsec->layers[--bfsec->layer_count].path); + kfree(bfsec->layers); + bfsec->layers = NULL; +} + /* * Check whether a task has the ioctl permission and cmd * operation to an inode. @@ -3978,6 +4034,53 @@ static int selinux_file_ioctl_compat(struct file *file, unsigned int cmd, static int default_noexec __ro_after_init; +static u32 file_map_prot_to_av(unsigned long prot, bool shared) +{ + u32 av = FILE__READ; + + if (shared && (prot & PROT_WRITE)) + av |= FILE__WRITE; + if (prot & PROT_EXEC) + av |= FILE__EXECUTE; + + return av; +} + +static int backing_mounters_has_perm(const struct file *file, u32 av) +{ + const struct backing_file_security_struct *bfsec; + const struct backing_file_security_layer *layer; + struct common_audit_data ad; + struct inode *inode; + u32 i; + int rc; + + if (WARN_ON_ONCE(!(file->f_mode & FMODE_BACKING))) + return -EIO; + + bfsec = selinux_backing_file(file); + for (i = 0; i < bfsec->layer_count; i++) { + layer = &bfsec->layers[i]; + inode = d_inode(layer->path.dentry); + + ad.type = LSM_AUDIT_DATA_PATH; + ad.u.path = layer->path; + + if (layer->mounter_sid != layer->fd_sid) { + rc = avc_has_perm(layer->mounter_sid, layer->fd_sid, + SECCLASS_FD, FD__USE, &ad); + if (rc) + return rc; + } + + rc = inode_sid_has_perm(layer->mounter_sid, inode, av, &ad); + if (rc) + return rc; + } + + return 0; +} + static int __file_map_prot_check(const struct file *file, unsigned long prot, bool shared, bool mounter_check, bool bf_user_file) @@ -4011,14 +4114,10 @@ static int __file_map_prot_check(const struct file *file, unsigned long prot, if (file) { const struct cred *cred = mounter_check ? file->f_cred : current_cred(); - /* "read" always possible, "write" only if shared */ - u32 av = FILE__READ; - if (shared && prot_write) - av |= FILE__WRITE; - if (prot_exec) - av |= FILE__EXECUTE; - return __file_has_perm(cred, file, av, bf_user_file); + return __file_has_perm(cred, file, + file_map_prot_to_av(prot, shared), + bf_user_file); } return 0; @@ -4113,6 +4212,7 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, int rc; const struct cred *cred = current_cred(); u32 sid = cred_sid(cred); + u32 av; const struct file *file = vma->vm_file; bool backing_file; bool shared = vma->vm_flags & VM_SHARED; @@ -4156,6 +4256,10 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, if (rc) return rc; if (backing_file) { + rc = backing_mounters_has_perm(file, + FILE__EXECMOD); + if (rc) + return rc; rc = file_has_perm(file->f_cred, file, FILE__EXECMOD); if (rc) @@ -4168,6 +4272,10 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, if (rc) return rc; if (backing_file) { + av = file_map_prot_to_av(prot, shared); + rc = backing_mounters_has_perm(file, av); + if (rc) + return rc; rc = file_map_prot_check(file, prot, shared, true); if (rc) return rc; @@ -7626,6 +7734,7 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = { LSM_HOOK_INIT(file_permission, selinux_file_permission), LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security), LSM_HOOK_INIT(backing_file_alloc, selinux_backing_file_alloc), + LSM_HOOK_INIT(backing_file_free, selinux_backing_file_free), LSM_HOOK_INIT(file_ioctl, selinux_file_ioctl), LSM_HOOK_INIT(file_ioctl_compat, selinux_file_ioctl_compat), LSM_HOOK_INIT(mmap_file, selinux_mmap_file), diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 853f7266ed18..2f21568251ff 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -86,8 +86,16 @@ struct file_security_struct { u32 pseqno; /* Policy seqno at the time of file open */ }; +struct backing_file_security_layer { + struct path path; /* this layer's real path */ + u32 mounter_sid; /* SID of the mounter that opened it */ + u32 fd_sid; /* SID of its open file description */ +}; + struct backing_file_security_struct { u32 uf_sid; /* top-level user file fsec->sid */ + u32 layer_count; /* number of intermediate backing files */ + struct backing_file_security_layer *layers; }; struct superblock_security_struct { From e0c3e9d76adbe522dd420a766ce42d03ce887c29 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 14 Sep 2026 17:15:44 +0800 Subject: [PATCH 1796/2366] i2c: imx: disable autosuspend on remove i2c_imx_probe() enables runtime PM autosuspend with pm_runtime_use_autosuspend(). The probe error path correctly undoes this setting with pm_runtime_dont_use_autosuspend(), but the normal remove path only disables runtime PM. The runtime PM API requires pm_runtime_use_autosuspend() to be undone with pm_runtime_dont_use_autosuspend() at driver exit unless runtime PM was enabled with devm_pm_runtime_enable(). Leaving the autosuspend flag set therefore leaves the runtime PM state incompletely cleaned up after the driver is unbound. Add the missing pm_runtime_dont_use_autosuspend() call to the remove path. This issue was found by manual code inspection. Fixes: 588eb93ea49f ("i2c: imx: add runtime pm support to improve the performance") Signed-off-by: Guangshuo Li Cc: # v4.5+ Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260914091544.1667137-1-lgs201920130244@gmail.com --- drivers/i2c/busses/i2c-imx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index 19ec056b00af..4dc3df6a41a4 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -1920,6 +1920,7 @@ static void i2c_imx_remove(struct platform_device *pdev) pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); } static int i2c_imx_runtime_suspend(struct device *dev) From 0d1cb83337f13af082afb68b28d3fdfe29cde7fb Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Tue, 15 Sep 2026 06:59:33 +0000 Subject: [PATCH 1797/2366] ata: libahci_platform: Fix device reference leak in ahci_platform_get_resources() of_find_device_by_node() takes a reference on the port platform device, which is only used to look up its port regulator and is never released, neither on success nor on the error paths. Drop the reference with put_device() once the regulator has been obtained, which covers both the success and error paths. Fixes: c7d7ddee7e24 ("ata: libahci: Allow using multiple regulators") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://lore.kernel.org/r/20260915065933.1733061-1-vulab@iscas.ac.cn Reviewed-by: Damien Le Moal Signed-off-by: Niklas Cassel --- drivers/ata/libahci_platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ata/libahci_platform.c b/drivers/ata/libahci_platform.c index 6e072d681341..f6524135ea11 100644 --- a/drivers/ata/libahci_platform.c +++ b/drivers/ata/libahci_platform.c @@ -620,10 +620,10 @@ struct ahci_host_priv *ahci_platform_get_resources(struct platform_device *pdev, of_platform_device_create(child, NULL, NULL); port_dev = of_find_device_by_node(child); - if (port_dev) { rc = ahci_platform_get_regulator(hpriv, port, &port_dev->dev); + put_device(&port_dev->dev); if (rc == -EPROBE_DEFER) goto err_out; } From ad34235808b63a70ca4989b7a2852923193d06ef Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Mon, 7 Sep 2026 15:11:02 +0800 Subject: [PATCH 1798/2366] i2c: atr: fix dangling adapter pointer on add failure i2c_atr_add_adapter() stores atr->adapter[chan_id] before i2c_add_adapter() so that the I2C bus notifier can match child clients during registration. On failure the channel is freed but the slot was left pointing at freed memory, which can lead to use-after-free in i2c_atr_del_adapter() / cleanup and also block reuse with -EEXIST. Clear the slot on the i2c_add_adapter() error path before freeing chan. Fixes: a076a860acae ("media: i2c: add I2C Address Translator (ATR) support") Signed-off-by: Linkai Gong Cc: # v6.6+ Reviewed-by: Andy Shevchenko Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260907071102.1080840-1-gonglinkai@kylinos.cn --- drivers/i2c/i2c-atr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i2c/i2c-atr.c b/drivers/i2c/i2c-atr.c index e6d2af659d81..ca29633dcd62 100644 --- a/drivers/i2c/i2c-atr.c +++ b/drivers/i2c/i2c-atr.c @@ -855,6 +855,7 @@ int i2c_atr_add_adapter(struct i2c_atr *atr, struct i2c_atr_adap_desc *desc) ret = i2c_add_adapter(&chan->adap); if (ret) { + atr->adapter[chan_id] = NULL; dev_err(dev, "failed to add atr-adapter %u (error=%d)\n", chan_id, ret); goto err_free_alias_pool; From 2ab510e63197360945f915dd5631a77c63ac6b27 Mon Sep 17 00:00:00 2001 From: Tvrtko Ursulin Date: Tue, 15 Sep 2026 16:05:57 +0100 Subject: [PATCH 1799/2366] drm/sched: Fix virtual runtime race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevent pushing a new job to an entity seeing it being the first in the queue, and hence entering the drm_sched_rq_add_entity() path, if the pop side in drm_sched_entity_pop_job() has just de-queued the job but not yet updated the saved virtual time. Restoring the unsaved virtual time, which is at this point not a delta but still an absolute value, pushes the said entity to the rear of the run queue for a potentially very long time. We close this race by pulling the locked sections out to encompass both the queue push/pop and corresponding rbtree management. This is aligned with the future direction to replace the current lockless job queue with one of the fully locked standard list primitives. Signed-off-by: Tvrtko Ursulin Fixes: 2fa4d8e2c109 ("drm/sched: Add fair scheduling policy") Suggested-by: Luke.Wildhardt@proton.me # via Claude Opus Tested-by: Luke.Wildhardt@proton.me Cc: Christian König Cc: Danilo Krummrich Cc: Philipp Stanner Cc: Pierre-Eric Pelloux-Prayer Cc: Matthew Brost Cc: Vitaly Prosyak Cc: stable@vger.kernel.org # v7.2+ [phasta: commit title] Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20260915150557.62847-1-tvrtko.ursulin@igalia.com --- drivers/gpu/drm/scheduler/sched_entity.c | 8 +++++++- drivers/gpu/drm/scheduler/sched_rq.c | 20 +++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/scheduler/sched_entity.c b/drivers/gpu/drm/scheduler/sched_entity.c index a4a7efdbf229..673ca9cbf362 100644 --- a/drivers/gpu/drm/scheduler/sched_entity.c +++ b/drivers/gpu/drm/scheduler/sched_entity.c @@ -559,9 +559,10 @@ struct drm_sched_job *drm_sched_entity_pop_job(struct drm_sched_entity *entity) */ smp_wmb(); + spin_lock(&entity->lock); spsc_queue_pop(&entity->job_queue); - drm_sched_rq_pop_entity(entity); + spin_unlock(&entity->lock); /* Jobs and entities might have different lifecycles. Since we're * removing the job from the entities queue, set the jobs entity pointer @@ -647,6 +648,9 @@ void drm_sched_entity_push_job(struct drm_sched_job *sched_job) * Make sure to set the submit_ts first, to avoid a race. */ sched_job->submit_ts = submit_ts = ktime_get(); + + spin_lock(&entity->lock); + first = spsc_queue_push(&entity->job_queue, &sched_job->queue_node); /* first job wakes up scheduler */ @@ -657,5 +661,7 @@ void drm_sched_entity_push_job(struct drm_sched_job *sched_job) if (sched) drm_sched_wakeup(sched); } + + spin_unlock(&entity->lock); } EXPORT_SYMBOL(drm_sched_entity_push_job); diff --git a/drivers/gpu/drm/scheduler/sched_rq.c b/drivers/gpu/drm/scheduler/sched_rq.c index 0464d324d98d..23f46ec610e7 100644 --- a/drivers/gpu/drm/scheduler/sched_rq.c +++ b/drivers/gpu/drm/scheduler/sched_rq.c @@ -257,19 +257,17 @@ static ktime_t drm_sched_entity_get_job_ts(struct drm_sched_entity *entity) struct drm_gpu_scheduler * drm_sched_rq_add_entity(struct drm_sched_entity *entity, ktime_t ts) { + struct drm_sched_rq *rq = entity->rq; struct drm_gpu_scheduler *sched; - struct drm_sched_rq *rq; /* Add the entity to the run queue */ - spin_lock(&entity->lock); - if (entity->stopped) { - spin_unlock(&entity->lock); + lockdep_assert_held(&entity->lock); + if (entity->stopped) { DRM_ERROR("Trying to push to a killed entity\n"); return NULL; } - rq = entity->rq; spin_lock(&rq->lock); sched = rq->sched; @@ -289,7 +287,6 @@ drm_sched_rq_add_entity(struct drm_sched_entity *entity, ktime_t ts) drm_sched_rq_update_fifo_locked(entity, rq, ts); spin_unlock(&rq->lock); - spin_unlock(&entity->lock); return sched; } @@ -343,16 +340,17 @@ drm_sched_rq_next_rr_ts(struct drm_sched_rq *rq, */ void drm_sched_rq_pop_entity(struct drm_sched_entity *entity) { + struct drm_sched_rq *rq = entity->rq; struct drm_sched_job *next_job; - struct drm_sched_rq *rq; + + lockdep_assert_held(&entity->lock); + + spin_lock(&rq->lock); /* * Update the entity's location in the min heap according to * the timestamp of the next job, if any. */ - spin_lock(&entity->lock); - rq = entity->rq; - spin_lock(&rq->lock); next_job = drm_sched_entity_queue_peek(entity); if (next_job) { ktime_t ts; @@ -375,8 +373,8 @@ void drm_sched_rq_pop_entity(struct drm_sched_entity *entity) drm_sched_entity_save_vruntime(entity, min_vruntime); } } + spin_unlock(&rq->lock); - spin_unlock(&entity->lock); } /** From dbd9d1cbf9700528c8595ab1fa7ef832e79821fe Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Tue, 15 Sep 2026 23:31:10 +0700 Subject: [PATCH 1800/2366] ALSA: usb-audio: fix list_add double-add in push_back_to_ready_list stop_urbs() clears ep->ready_playback_urbs with a bare INIT_LIST_HEAD() instead of unlinking each queued snd_urb_ctx. If a URB survives past wait_clear_urbs()'s forced STOPPING->STOPPED timeout, its ctx is left looking "linked" (stale next/prev) even though the list head has forgotten it. When the endpoint later restarts and re-queues that same ctx onto the (now real) ready list, and the old URB's completion handler then calls push_back_to_ready_list() for it a second time, the ctx is still the list's own tail and list_add's double-add check trips: kernel BUG at lib/list_debug.c:35 (list_add double add) Guard push_back_to_ready_list() with a list_empty() check so a still-linked ctx isn't re-added, and make stop_urbs() actually unlink each ctx via list_del_init() instead of only resetting the head, so a dropped ctx doesn't keep looking linked to that guard. Reported-by: syzbot+9fe3b8d9f5c64ff410a7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9fe3b8d9f5c64ff410a7 Signed-off-by: Nguyen Ngoc Thang Link: https://patch.msgid.link/20260915163110.58124-1-ngocthang2710.1999@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/endpoint.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index b72ce1e9bfb1..879d0451536e 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -449,7 +449,9 @@ static void push_back_to_ready_list(struct snd_usb_endpoint *ep, struct snd_urb_ctx *ctx) { guard(spinlock_irqsave)(&ep->lock); - list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); + /* ctx may still be linked: a stale completion racing a stop/restart. */ + if (list_empty(&ctx->ready_list)) + list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); } /* @@ -1037,6 +1039,7 @@ void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep) */ static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) { + struct snd_urb_ctx *ctx, *n; unsigned int i; if (!force && atomic_read(&ep->running)) @@ -1046,7 +1049,9 @@ static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) return 0; scoped_guard(spinlock_irqsave, &ep->lock) { - INIT_LIST_HEAD(&ep->ready_playback_urbs); + /* Unlink each ctx; INIT_LIST_HEAD() alone would leave them looking linked. */ + list_for_each_entry_safe(ctx, n, &ep->ready_playback_urbs, ready_list) + list_del_init(&ctx->ready_list); ep->next_packet_head = 0; ep->next_packet_queued = 0; } From 51938dfa8a51a4f85328413fca9b6e21f9d2d088 Mon Sep 17 00:00:00 2001 From: Amit Machhiwal Date: Tue, 15 Sep 2026 22:04:15 +0530 Subject: [PATCH 1801/2366] KVM: PPC: Book3S HV: fix use-after-free in kvmhv_emulate_tlbie_all_lpid() kvmhv_emulate_tlbie_all_lpid() iterates the nested-guest IDR and drops mmu_lock before calling kvmhv_emulate_tlbie_lpid(), but does not hold a reference on the kvm_nested_guest pointer obtained from the IDR. A concurrent vCPU issuing a single-LPID tlbie (is=2, ric=2) can race through kvmhv_flush_nested() -> kvmhv_remove_nested() -> idr_remove / --refcnt -> kvmhv_release_nested() -> kfree(gp) in that window, leaving the iterating vCPU with a dangling pointer. The subsequent mutex_lock(&gp->tlb_lock) and accesses to gp->shadow_pgtable, gp->shadow_lpid and gp->l1_host all touch freed memory. The free path is fully L1-controlled. Fix this by incrementing gp->refcnt inside the loop before dropping mmu_lock, mirroring what kvmhv_get_nested() does, and releasing the reference with kvmhv_put_nested() after the per-guest work completes. This is the same get/put discipline already used at every other call site that drops mmu_lock while holding a nested-guest pointer. Fixes: e3b6b4661527 ("KVM: PPC: Book3S HV: Implement H_TLB_INVALIDATE hcall") Reviewed-by: Ritesh Harjani (IBM) Tested-by: R Nageswara Sastry Signed-off-by: Amit Machhiwal Signed-off-by: Gautam Menghani Signed-off-by: Madhavan Srinivasan --- arch/powerpc/kvm/book3s_hv_nested.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/powerpc/kvm/book3s_hv_nested.c b/arch/powerpc/kvm/book3s_hv_nested.c index 22e616662255..a6ff42d7666c 100644 --- a/arch/powerpc/kvm/book3s_hv_nested.c +++ b/arch/powerpc/kvm/book3s_hv_nested.c @@ -1204,8 +1204,10 @@ static void kvmhv_emulate_tlbie_all_lpid(struct kvm_vcpu *vcpu, int ric) spin_lock(&kvm->mmu_lock); idr_for_each_entry(&kvm->arch.kvm_nested_guest_idr, gp, lpid) { + ++gp->refcnt; spin_unlock(&kvm->mmu_lock); kvmhv_emulate_tlbie_lpid(vcpu, gp, ric); + kvmhv_put_nested(gp); spin_lock(&kvm->mmu_lock); } spin_unlock(&kvm->mmu_lock); From 0a416ee20bcccddf91ca5b63696a23b9d11d73aa Mon Sep 17 00:00:00 2001 From: Amit Machhiwal Date: Tue, 15 Sep 2026 22:04:16 +0530 Subject: [PATCH 1802/2366] KVM: PPC: Book3S HV: fix secure device page leak on uv_page_in() failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In kvmppc_svm_page_in(), if uv_page_in() fails after kvmppc_uvmem_get_page() has succeeded, the secure device page is never released. kvmppc_uvmem_get_page() sets a bit in kvmppc_uvmem_bitmap, allocates a kvmppc_uvmem_page_pvt struct, marks the GFN as KVMPPC_GFN_UVMEM_PFN, and calls zone_device_page_init() which sets refcount=1 and locks the page. The subsequent goto out_finalize skips the *mig.dst assignment, so migrate_vma_finalize() is a no-op for the page, and none of those resources are ever reclaimed. Each occurrence permanently consumes one entry from the firmware-bounded secure memory pool (kvmppc_uvmem_bitmap), leaks pvt, and leaves the GFN marked as secure — making it unusable for the lifetime of the VM. The twin __kvmppc_svm_page_out() already handles the analogous uv_page_out() failure correctly with unlock_page(dpage); __free_page(dpage). Apply the same pattern here: unlock_page() followed by put_page(), which chains through free_zone_device_folio() into kvmppc_uvmem_folio_free() to clear the bitmap bit, free pvt, and reset the GFN state. Reachable whenever uv_page_in() returns an error (e.g. UV pool exhaustion) on any POWER9/10 + Ultravisor/PEF system. Fixes: ca9f4942670c ("KVM: PPC: Book3S HV: Support for running secure guests") Reviewed-by: Ritesh Harjani (IBM) Tested-by: R Nageswara Sastry Signed-off-by: Amit Machhiwal Signed-off-by: Gautam Menghani Signed-off-by: Madhavan Srinivasan --- arch/powerpc/kvm/book3s_hv_uvmem.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/powerpc/kvm/book3s_hv_uvmem.c b/arch/powerpc/kvm/book3s_hv_uvmem.c index 5fbb95d90e99..463aef870c4e 100644 --- a/arch/powerpc/kvm/book3s_hv_uvmem.c +++ b/arch/powerpc/kvm/book3s_hv_uvmem.c @@ -779,8 +779,11 @@ static int kvmppc_svm_page_in(struct vm_area_struct *vma, if (spage) { ret = uv_page_in(kvm->arch.lpid, pfn << page_shift, gpa, 0, page_shift); - if (ret) + if (ret) { + unlock_page(dpage); + put_page(dpage); goto out_finalize; + } } } From 0b271f7d7f5ed45bc498a03ce0aa9cfd8402fc71 Mon Sep 17 00:00:00 2001 From: Shivaprasad G Bhat Date: Tue, 15 Sep 2026 22:04:17 +0530 Subject: [PATCH 1803/2366] powerpc/iommu: Fix the overflow validation in iommu_tce_check_ioba The commit b1af23d836f8 ("KVM: PPC: iommu: Unify TCE checking") unified IOBA parameter checking across KVM and VFIO into iommu_tce_check_ioba(). While doing so, the passed in argument npages is ignored and constant value '1' is used leaving out a possible overflow as the callers can legitimately be using npages > 1 for H_STUFF_TCE or H_PUT_TCE_INDIRECT cases. Fix this by accounting for 'npages', checking for arithmetic overflow, and verifying that the entire requested range (ioba - offset + npages) does not exceed the table capacity 'size'. Fixes: b1af23d836f8 ("KVM: PPC: iommu: Unify TCE checking") Reviewed-by: Ritesh Harjani (IBM) Tested-by: R Nageswara Sastry Signed-off-by: Shivaprasad G Bhat Signed-off-by: Gautam Menghani Signed-off-by: Madhavan Srinivasan --- arch/powerpc/kernel/iommu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/kernel/iommu.c b/arch/powerpc/kernel/iommu.c index ee1b5cb557c9..1ae8384637b5 100644 --- a/arch/powerpc/kernel/iommu.c +++ b/arch/powerpc/kernel/iommu.c @@ -1076,7 +1076,7 @@ int iommu_tce_check_ioba(unsigned long page_shift, if (ioba < offset) return -EINVAL; - if ((ioba + 1) > (offset + size)) + if ((ioba + npages < ioba) || (ioba - offset + npages > size)) return -EINVAL; return 0; From 1f7745fb3580152ca902ef181b605f33cabfb1d0 Mon Sep 17 00:00:00 2001 From: Ran Hongyun Date: Mon, 13 Jul 2026 19:55:25 +0800 Subject: [PATCH 1804/2366] squashfs: Add dictionary size range check to prevent shift-out-of-bounds When an abnormal SquashFS image (COMP_OPTS flag is 1 but dictionary size is 0) is mounted, and performs shift operations using dictionarysize, the shift exponent is -1, causing a shift-out-of-bounds. Detail as below: squashfs_comp_opts(msblk, buffer, length) squashfs_xz_comp_opts() if (comp_opts) n = ffs(opts->dict_size) - 1;<----opts->dict_size=0, n=-1 if (opts->dict_size != (1 << n) && opts->dict_size != (1 << n) + (1 << (n + 1))) <----shift-out-of-bounds Fix it by adding a dictionary size range check before the shift operation. Fixes: ff750311d30a ("Squashfs: add compression options support to xz decompressor") Signed-off-by: Ran Hongyun Link: https://patch.msgid.link/20260713115525.2661734-1-ranhongyun1@huawei.com Reviewed-by: Phillip Lougher Reviewed-by: Zhihao Cheng Signed-off-by: Christian Brauner (Amutable) --- fs/squashfs/xz_wrapper.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/squashfs/xz_wrapper.c b/fs/squashfs/xz_wrapper.c index 0a4ff3ec9c8c..6610af241449 100644 --- a/fs/squashfs/xz_wrapper.c +++ b/fs/squashfs/xz_wrapper.c @@ -57,10 +57,10 @@ static void *squashfs_xz_comp_opts(struct squashfs_sb_info *msblk, opts->dict_size = le32_to_cpu(comp_opts->dictionary_size); - /* the dictionary size should be 2^n or 2^n+2^(n+1) */ + /* the dictionary size should be positive and 2^n or 2^n+2^(n+1) */ n = ffs(opts->dict_size) - 1; - if (opts->dict_size != (1 << n) && opts->dict_size != (1 << n) + - (1 << (n + 1))) { + if (opts->dict_size <= 0 || (opts->dict_size != (1 << n) && + opts->dict_size != (1 << n) + (1 << (n + 1)))) { err = -EIO; goto out; } From 5a83606d9f31a38273ee803f3aae2e39247032c5 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Thu, 10 Sep 2026 17:49:04 +0800 Subject: [PATCH 1805/2366] drm/verisilicon: set blend mode for the cursor plane Blend mode properties are now required to expose pixel formats w/ alpha. Experiments show that the fixed blending mode for the cursor seems to be COVERAGE: - With a cursor plane filled with R=G=0, B=0xff, A=0x40, the cursor is visible on a pure-white background, which means the background is multiplied. - With a cursor plane filled with R=G=B=0xff, A=0x40, the cursor isn't pure white and non-white patterns can be see through, which means the cursor is multiplied. Add a fixed COVERAGE blend mode property for the cursor plane. Signed-off-by: Icenowy Zheng Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260910094904.3502741-1-zhengxingda@iscas.ac.cn --- drivers/gpu/drm/verisilicon/vs_cursor_plane.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/verisilicon/vs_cursor_plane.c b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c index fa4f601dd0c8..da456dced88a 100644 --- a/drivers/gpu/drm/verisilicon/vs_cursor_plane.c +++ b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -267,6 +268,7 @@ struct drm_plane *vs_cursor_plane_init(struct drm_device *drm_dev, return plane; drm_plane_helper_add(plane, &vs_cursor_plane_helper_funcs); + drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_COVERAGE)); return plane; } From e5d43d7e924d7e1d0c815a5c7407f2a40a6dd2f2 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Thu, 10 Sep 2026 17:49:59 +0800 Subject: [PATCH 1806/2366] drm/verisilicon: add primary modifier for format tables Currently the format tables are only used for the primary plane. Add primary modifiers to names related to the tables. Signed-off-by: Icenowy Zheng Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260910095000.3505878-1-zhengxingda@iscas.ac.cn --- drivers/gpu/drm/verisilicon/vs_hwdb.c | 12 ++++++------ drivers/gpu/drm/verisilicon/vs_hwdb.h | 4 ++-- drivers/gpu/drm/verisilicon/vs_primary_plane.c | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c index 2a0f7c59afa3..56aa45044306 100644 --- a/drivers/gpu/drm/verisilicon/vs_hwdb.c +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c @@ -10,7 +10,7 @@ #include "vs_dc_top_regs.h" #include "vs_hwdb.h" -static const u32 vs_formats_array_no_yuv444[] = { +static const u32 vs_primary_formats_array_no_yuv444[] = { DRM_FORMAT_XRGB4444, DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, @@ -44,7 +44,7 @@ static const u32 vs_formats_array_no_yuv444[] = { /* TODO: non-RGB formats */ }; -static const u32 vs_formats_array_with_yuv444[] = { +static const u32 vs_primary_formats_array_with_yuv444[] = { DRM_FORMAT_XRGB4444, DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, @@ -79,13 +79,13 @@ static const u32 vs_formats_array_with_yuv444[] = { }; static const struct vs_formats vs_formats_no_yuv444 = { - .array = vs_formats_array_no_yuv444, - .num = ARRAY_SIZE(vs_formats_array_no_yuv444) + .primary_array = vs_primary_formats_array_no_yuv444, + .primary_num = ARRAY_SIZE(vs_primary_formats_array_no_yuv444) }; static const struct vs_formats vs_formats_with_yuv444 = { - .array = vs_formats_array_with_yuv444, - .num = ARRAY_SIZE(vs_formats_array_with_yuv444) + .primary_array = vs_primary_formats_array_with_yuv444, + .primary_num = ARRAY_SIZE(vs_primary_formats_array_with_yuv444) }; static struct vs_chip_identity vs_chip_identities[] = { diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.h b/drivers/gpu/drm/verisilicon/vs_hwdb.h index 2065ecb73043..616076d931a5 100644 --- a/drivers/gpu/drm/verisilicon/vs_hwdb.h +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.h @@ -10,8 +10,8 @@ #include struct vs_formats { - const u32 *array; - unsigned int num; + const u32 *primary_array; + unsigned int primary_num; }; struct vs_chip_identity { diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane.c b/drivers/gpu/drm/verisilicon/vs_primary_plane.c index 1f2be41ae496..8e9449169301 100644 --- a/drivers/gpu/drm/verisilicon/vs_primary_plane.c +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c @@ -168,8 +168,8 @@ struct drm_plane *vs_primary_plane_init(struct drm_device *drm_dev, struct vs_dc plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0, &vs_primary_plane_funcs, - dc->identity.formats->array, - dc->identity.formats->num, + dc->identity.formats->primary_array, + dc->identity.formats->primary_num, NULL, DRM_PLANE_TYPE_PRIMARY, NULL); From 6c62dfd2820f0c32b0083668edbe0baca6e10b92 Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Thu, 10 Sep 2026 17:50:00 +0800 Subject: [PATCH 1807/2366] drm/verisilicon: remove ARGB formats from primary plane As the blending of the primary plane is currently explicitly disabled (and it's not possible on DC8000), remove the ARGB formats from the primary plane format tables. Signed-off-by: Icenowy Zheng Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20260910095000.3505878-2-zhengxingda@iscas.ac.cn --- drivers/gpu/drm/verisilicon/vs_hwdb.c | 32 --------------------------- 1 file changed, 32 deletions(-) diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c index 56aa45044306..ebf6f843bc88 100644 --- a/drivers/gpu/drm/verisilicon/vs_hwdb.c +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c @@ -15,32 +15,16 @@ static const u32 vs_primary_formats_array_no_yuv444[] = { DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, DRM_FORMAT_BGRX4444, - DRM_FORMAT_ARGB4444, - DRM_FORMAT_ABGR4444, - DRM_FORMAT_RGBA4444, - DRM_FORMAT_BGRA4444, DRM_FORMAT_XRGB1555, DRM_FORMAT_XBGR1555, DRM_FORMAT_RGBX5551, DRM_FORMAT_BGRX5551, - DRM_FORMAT_ARGB1555, - DRM_FORMAT_ABGR1555, - DRM_FORMAT_RGBA5551, - DRM_FORMAT_BGRA5551, DRM_FORMAT_RGB565, DRM_FORMAT_BGR565, DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888, - DRM_FORMAT_ARGB8888, - DRM_FORMAT_ABGR8888, - DRM_FORMAT_RGBA8888, - DRM_FORMAT_BGRA8888, - DRM_FORMAT_ARGB2101010, - DRM_FORMAT_ABGR2101010, - DRM_FORMAT_RGBA1010102, - DRM_FORMAT_BGRA1010102, /* TODO: non-RGB formats */ }; @@ -49,32 +33,16 @@ static const u32 vs_primary_formats_array_with_yuv444[] = { DRM_FORMAT_XBGR4444, DRM_FORMAT_RGBX4444, DRM_FORMAT_BGRX4444, - DRM_FORMAT_ARGB4444, - DRM_FORMAT_ABGR4444, - DRM_FORMAT_RGBA4444, - DRM_FORMAT_BGRA4444, DRM_FORMAT_XRGB1555, DRM_FORMAT_XBGR1555, DRM_FORMAT_RGBX5551, DRM_FORMAT_BGRX5551, - DRM_FORMAT_ARGB1555, - DRM_FORMAT_ABGR1555, - DRM_FORMAT_RGBA5551, - DRM_FORMAT_BGRA5551, DRM_FORMAT_RGB565, DRM_FORMAT_BGR565, DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888, - DRM_FORMAT_ARGB8888, - DRM_FORMAT_ABGR8888, - DRM_FORMAT_RGBA8888, - DRM_FORMAT_BGRA8888, - DRM_FORMAT_ARGB2101010, - DRM_FORMAT_ABGR2101010, - DRM_FORMAT_RGBA1010102, - DRM_FORMAT_BGRA1010102, /* TODO: non-RGB formats */ }; From 53cf0f26eece095a3752642151150404bc3351cc Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 1 Sep 2026 13:37:36 -0700 Subject: [PATCH 1808/2366] crypto: caam - map job ring registers without claiming region devm_platform_ioremap_resource() ends up calling request_mem_region(), which fails with -EBUSY on i.MX SoCs: There the job rings are modelled as sub-regions of their parent fsl,sec-v4.0 register window, and caam_probe() already reserves the whole window exclusively via devm_of_iomap() before the children are populated. Every job ring therefore collides with its own parent and fails to probe, taking the hardware RNG offline (seen on i.MX6, i.MX7 and i.MX8 boards such as colibri-imx7 and verdin-imx8mp). Map the job ring registers with devm_ioremap() instead, which does not claim the (already owned) region. Fixes: 9a955c0a7d11 ("crypto: caam - simplify probe resource and IRQ handling") Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Reported-by: Emanuele Ghidoli Tested-by: Emanuele Ghidoli Tested-by: Richard Leitner # i.MX8MP Tested-by: Sahil Malhotra # i.MX8MM-EVK Signed-off-by: Herbert Xu --- drivers/crypto/caam/jr.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/crypto/caam/jr.c b/drivers/crypto/caam/jr.c index 239469f6882e..cf725a6ac0fb 100644 --- a/drivers/crypto/caam/jr.c +++ b/drivers/crypto/caam/jr.c @@ -583,12 +583,29 @@ static int caam_jr_probe(struct platform_device *pdev) struct caam_drv_private_jr *jrpriv; static int total_jobrs; void __iomem *ctrl; + struct resource *r; int error; int irq; - ctrl = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(ctrl)) - return PTR_ERR(ctrl); + /* + * The job rings live inside the register window of their parent + * fsl,sec-v4.0 node, which caam_probe() already reserves (and maps) + * via devm_of_iomap(). A requested region that overlaps that + * reservation, e.g. from devm_platform_ioremap_resource(), would + * therefore fail with -EBUSY, so map the registers without claiming + * the region here. + */ + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "platform_get_resource() failed\n"); + return -EINVAL; + } + + ctrl = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (!ctrl) { + dev_err(&pdev->dev, "devm_ioremap() failed\n"); + return -ENOMEM; + } irq = platform_get_irq(pdev, 0); if (irq < 0) From a26204be587c57bd5c54fa513be26c4fd7bf252d Mon Sep 17 00:00:00 2001 From: Nemesa Garg Date: Wed, 9 Sep 2026 16:33:31 +0530 Subject: [PATCH 1809/2366] Revert "drm/i915/display: Clear SEL_FETCH_PLANE_CTL on plane disable" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7f1172a2ac0d7e50850785e2e65789c8aac8411a. This commit replaced the crtc_state->enable_psr2_sel_fetch guard in icl_plane_disable_sel_fetch_arm() and i9xx_cursor_disable_sel_fetch_arm() with HAS_PSR2_SEL_FETCH(). This is a display version check and says nothing about the pipe, so every plane and cursor disable on a display 12+ platform started writing SEL_FETCH_PLANE_CTL() / SEL_FETCH_CUR_CTL(), including on pipes that do not implement them. It shows up as an unclaimed register access on pipes driving HDMI where selective fetch was never enabled. The stale selective fetch enable bit that commit addressed is handled in the next patch. Fixes: 7f1172a2ac0d ("drm/i915/display: Clear SEL_FETCH_PLANE_CTL on plane disable") Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16876 Signed-off-by: Nemesa Garg Reviewed-by: Jouni Högander Signed-off-by: Suraj Kandpal Link: https://patch.msgid.link/20260909110332.3528029-2-nemesa.garg@intel.com (cherry picked from commit d393529394167e0f5f706657eebe84d8529ce4fc) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_cursor.c | 15 +++++---------- .../gpu/drm/i915/display/skl_universal_plane.c | 15 +++++---------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cursor.c b/drivers/gpu/drm/i915/display/intel_cursor.c index 86bb96ac449b..0673f16f6fd0 100644 --- a/drivers/gpu/drm/i915/display/intel_cursor.c +++ b/drivers/gpu/drm/i915/display/intel_cursor.c @@ -530,18 +530,13 @@ static int i9xx_check_cursor(struct intel_crtc_state *crtc_state, } static void i9xx_cursor_disable_sel_fetch_arm(struct intel_dsb *dsb, - struct intel_plane *plane) + struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - /* - * Clear this whenever the hardware has selective fetch, not just when - * the current state uses it. The cursor may have been enabled with - * selective fetch earlier and had its enable bit orphaned when the - * feature was switched off. - */ - if (!HAS_PSR2_SEL_FETCH(display)) + if (!crtc_state->enable_psr2_sel_fetch) return; intel_de_write_dsb(display, dsb, SEL_FETCH_CUR_CTL(pipe), 0); @@ -591,7 +586,7 @@ static void i9xx_cursor_update_sel_fetch_arm(struct intel_dsb *dsb, if (crtc_state->enable_psr2_su_region_et) wa_16021440873(dsb, plane, crtc_state, plane_state); else - i9xx_cursor_disable_sel_fetch_arm(dsb, plane); + i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); } } @@ -700,7 +695,7 @@ static void i9xx_cursor_update_arm(struct intel_dsb *dsb, if (plane_state) i9xx_cursor_update_sel_fetch_arm(dsb, plane, crtc_state, plane_state); else - i9xx_cursor_disable_sel_fetch_arm(dsb, plane); + i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); if (plane->cursor.base != base || plane->cursor.size != fbc_ctl || diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c index 5cda1ab90e40..07a683293352 100644 --- a/drivers/gpu/drm/i915/display/skl_universal_plane.c +++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c @@ -879,18 +879,13 @@ skl_plane_disable_arm(struct intel_dsb *dsb, } static void icl_plane_disable_sel_fetch_arm(struct intel_dsb *dsb, - struct intel_plane *plane) + struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) { struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - /* - * Clear this whenever the hardware has selective fetch, not just when - * the current state uses it. The plane may have been enabled with - * selective fetch earlier and had its enable bit orphaned when the - * feature was switched off. - */ - if (!HAS_PSR2_SEL_FETCH(display)) + if (!crtc_state->enable_psr2_sel_fetch) return; intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), 0); @@ -926,7 +921,7 @@ icl_plane_disable_arm(struct intel_dsb *dsb, skl_write_plane_wm(dsb, plane, crtc_state); - icl_plane_disable_sel_fetch_arm(dsb, plane); + icl_plane_disable_sel_fetch_arm(dsb, plane, crtc_state); if (plane_has_normalizer(plane)) intel_de_write_dsb(display, dsb, @@ -1646,7 +1641,7 @@ static void icl_plane_update_sel_fetch_arm(struct intel_dsb *dsb, intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), SEL_FETCH_PLANE_CTL_ENABLE); else - icl_plane_disable_sel_fetch_arm(dsb, plane); + icl_plane_disable_sel_fetch_arm(dsb, plane, crtc_state); } static void From b61b6f95d6722ddbbbd09e689fa41b55fd36f9a5 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Fri, 11 Sep 2026 11:04:47 +0200 Subject: [PATCH 1810/2366] futex: Also allocate private hash on vfork() As Jann demonstrated, it is entirely feasible to access the mm through vfork(). Therefore we need to allocate a private hash on vfork() as well as any other CLONE_VM user. Specifically, it must be avoided to have (private) futex waiters before allocating the private hash. Fixes: ee9dce44362b ("futex: Drop CLONE_THREAD requirement for private default hash alloc") Reported-by: Jann Horn Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260911090447.GT788244@noisy.programming.kicks-ass.net --- kernel/fork.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/fork.c b/kernel/fork.c index a5934a317634..5ef413368912 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1996,9 +1996,9 @@ static bool need_futex_hash_allocate_default(u64 clone_flags) { /* * Allocate a default futex hash for any sibling that will - * share the parent's mm, except vfork. + * share the parent's mm. */ - return (clone_flags & (CLONE_VM | CLONE_VFORK)) == CLONE_VM; + return clone_flags & CLONE_VM; } /* From f0e9f963a3d209d7dc7ddd61116118ab5da2797d Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Fri, 11 Sep 2026 17:45:47 +0530 Subject: [PATCH 1811/2366] drm/xe/i2c: Disable IRQ on unbind Currently, struct xe_i2c is freed before SGUnit IRQ is disabled in unbind path, leaving a potential UAF in case I2C IRQ is hit during this small window. Explicitly disable I2C IRQ in xe_i2c_remove() and fix this. Fixes: 0bb78ce09926 ("drm/xe/i2c: Wire up reset/postinstall for I2C IRQ") Signed-off-by: Raag Jadav Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260911121547.2407261-1-raag.jadav@intel.com Signed-off-by: Matt Roper (cherry picked from commit 8ba5c8b8ab3fd362267c11df2cd5a90ee46f6e24) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_i2c.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index 399099ff0dbc..f4f381988289 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -318,8 +318,10 @@ void xe_i2c_pm_resume(struct xe_device *xe, bool d3cold) static void xe_i2c_remove(void *data) { struct xe_i2c *i2c = data; + struct xe_device *xe = tile_to_xe(i2c->mmio->tile); unsigned int i; + xe_i2c_irq_reset(xe); xe_amc_exit(i2c); for (i = 0; i < XE_I2C_MAX_CLIENTS; i++) { @@ -329,6 +331,7 @@ static void xe_i2c_remove(void *data) bus_unregister_notifier(&i2c_bus_type, &i2c->bus_notifier); xe_i2c_unregister_adapter(i2c); + xe->i2c = NULL; } /** From a13f7f5d14af9baf34eb12c25962f8d3542b281d Mon Sep 17 00:00:00 2001 From: Ilya Titov Date: Thu, 3 Sep 2026 12:17:18 +0300 Subject: [PATCH 1812/2366] pinctrl: sunxi: keep a shadow copy of the data register output latches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Allwinner SoCs, reading a bank's data register returns the pin level, not the output latch, for pins that are muxed as inputs. Writing a GPIO therefore corrupts the output latches of all input-muxed pins in the same bank: the read-modify-write in sunxi_pinctrl_gpio_set() reads back their pin levels and writes those into their latches. This breaks emulated open-drain lines (e.g. a bit-banged I2C bus from i2c-gpio). Such a line is released high by muxing it as input and letting the pull-up raise it, so any concurrent GPIO write in the same bank stores 1 into its latch. Driving the line low afterwards is a non-atomic data-then-mux sequence in sunxi_pinctrl_gpio_direction_output(); if the poisoning write lands between the two steps, the pin actively drives high (push-pull) instead of low. Observed in practice as sporadic glitches on a T507 board bit-banging I2C on port E while other PE GPIOs are toggled. On a scope the failure is unmistakable: on a clock pulse where SCL should fall to GND, the line instead steps *above* its idle high level for the whole low phase — the pad drives a strong push-pull 3.3 V high, higher than the level the pull-up sustains on the loaded bus — before the next transition recovers it. The same can hit SDA, corrupting data instead of clocks. Steps to reproduce on any sunxi board with a bit-banged (i2c-gpio) bus: # background: toggle any other GPIO of the same bank, e.g. line 21 gpioset -c --toggle 100us 21=0 & # foreground: keep the bit-banged bus busy while :; do i2cdetect -y 0x50 0x57; done # watch SCL/SDA with a scope or logic analyzer: sporadic clock-low # phases driven high (above the pull-up level) instead of low The bank spinlock cannot help: the racing write is a perfectly valid whole-register RMW that faithfully writes back what the hardware returned. There are no set/clear registers on this IP to write a single bit atomically. Fix it the same way gpio-mmio handles hardware whose data register read does not return the output latch: keep a shadow copy of each bank's latches, base the read-modify-write on the shadow, and only write the register. The shadow is seeded from the hardware at probe time so pins left in output mode by the bootloader keep their state. Pins that reach output mode through the gpiolib paths write their value (and thereby their shadow bit) before the mux switch in sunxi_pinctrl_gpio_direction_output(); pins muxed to gpio_out directly through a pinmux node bypass that path, so sunxi_pmx_set() refreshes their shadow bit from the latch (readable once the pin is in output mode) to keep them driving their pre-existing level. Seeding the shadow reads the PIO registers at probe time, which requires the bus clock to be enabled. The clock was only requested at the very end of probe, after devm_pinctrl_register() had already claimed the pin hogs described in the device tree - which mux pins, and thus access registers, with the clock still gated. Move the request ahead of both. Boards whose bootloader leaves the PIO clock running are unaffected, which is why the pre-existing hog problem has gone unnoticed since commit 950707c0eb5c ("pinctrl: sunxi: add clock support"). Fixes: df7b34f4c3d2 ("pinctrl: sunxi: Fix gpio_set behaviour") Cc: stable@vger.kernel.org Signed-off-by: Ilya Titov Signed-off-by: Linus Walleij --- drivers/pinctrl/sunxi/pinctrl-sunxi.c | 76 +++++++++++++++++++++------ drivers/pinctrl/sunxi/pinctrl-sunxi.h | 7 +++ 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c index 25489beeb312..2881a83be99c 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c @@ -837,6 +837,21 @@ static void sunxi_pmx_set(struct pinctrl_dev *pctldev, writel((readl(pctl->membase + reg) & ~mask) | config << shift, pctl->membase + reg); + /* + * A pin muxed to gpio_out directly through a pinmux node bypasses + * sunxi_pinctrl_gpio_set() and drives whatever its output latch + * holds. Now that the pin is in output mode the data register + * reads back the latch, so refresh the shadow to keep such pins + * driving their pre-existing level. + */ + if (config == SUN4I_FUNC_OUTPUT) { + u32 *shadow = &pctl->dat_shadow[pin / PINS_PER_BANK]; + + sunxi_data_reg(pctl, pin, ®, &shift, &mask); + *shadow = (*shadow & ~mask) | + (readl(pctl->membase + reg) & mask); + } + raw_spin_unlock_irqrestore(&pctl->lock, flags); } @@ -1017,21 +1032,29 @@ static int sunxi_pinctrl_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) { struct sunxi_pinctrl *pctl = gpiochip_get_data(chip); - u32 reg, shift, mask, val; + u32 *shadow = &pctl->dat_shadow[offset / PINS_PER_BANK]; + u32 reg, shift, mask; unsigned long flags; sunxi_data_reg(pctl, offset, ®, &shift, &mask); raw_spin_lock_irqsave(&pctl->lock, flags); - val = readl(pctl->membase + reg); - + /* + * Reading the data register returns the pin level, not the output + * latch, for pins muxed as inputs. A read-modify-write based on + * the register would therefore corrupt the latches of input-muxed + * pins in the same bank (e.g. an emulated open-drain I2C line + * released high), making them drive the wrong level once switched + * to output. Base the read-modify-write on a shadow copy of the + * latches instead. + */ if (value) - val |= mask; + *shadow |= mask; else - val &= ~mask; + *shadow &= ~mask; - writel(val, pctl->membase + reg); + writel(*shadow, pctl->membase + reg); raw_spin_unlock_irqrestore(&pctl->lock, flags); @@ -1572,7 +1595,7 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, struct pinctrl_pin_desc *pins; struct sunxi_pinctrl *pctl; struct pinmux_ops *pmxops; - int i, ret, last_pin, pin_idx; + int i, ret, last_pin, pin_idx, nbanks; struct clk *clk; pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL); @@ -1610,6 +1633,37 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, if (!pctl->irq_array) return -ENOMEM; + /* + * The bus clock has to be enabled before the pinctrl device + * registers, as the pin hogs claimed from there access registers. + */ + ret = of_clk_get_parent_count(node); + clk = devm_clk_get_enabled(&pdev->dev, ret == 1 ? NULL : "apb"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + /* + * Seed the output latch shadow from the hardware so pins the + * bootloader left in output mode keep their state; see + * sunxi_pinctrl_gpio_set() for why a shadow is needed. This must + * happen before the pinctrl device registers, as pin hogs can mux + * pins to gpio_out and thereby update the shadow. + */ + last_pin = pctl->desc->pins[pctl->desc->npins - 1].pin.number; + nbanks = DIV_ROUND_UP(last_pin + 1 - pctl->desc->pin_base, + PINS_PER_BANK); + pctl->dat_shadow = devm_kcalloc(&pdev->dev, nbanks, + sizeof(*pctl->dat_shadow), GFP_KERNEL); + if (!pctl->dat_shadow) + return -ENOMEM; + + for (i = 0; i < nbanks; i++) { + u32 reg, shift, mask; + + sunxi_data_reg(pctl, i * PINS_PER_BANK, ®, &shift, &mask); + pctl->dat_shadow[i] = readl(pctl->membase + reg); + } + ret = sunxi_pinctrl_build_state(pdev); if (ret) { dev_err(&pdev->dev, "dt probe failed: %d\n", ret); @@ -1665,7 +1719,6 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, if (!pctl->chip) return -ENOMEM; - last_pin = pctl->desc->pins[pctl->desc->npins - 1].pin.number; pctl->chip->owner = THIS_MODULE; pctl->chip->request = gpiochip_generic_request; pctl->chip->free = gpiochip_generic_free; @@ -1699,13 +1752,6 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, goto gpiochip_error; } - ret = of_clk_get_parent_count(node); - clk = devm_clk_get_enabled(&pdev->dev, ret == 1 ? NULL : "apb"); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - goto gpiochip_error; - } - pctl->irq = devm_kcalloc(&pdev->dev, pctl->desc->irq_banks, sizeof(*pctl->irq), diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.h b/drivers/pinctrl/sunxi/pinctrl-sunxi.h index 0daf7600e2fb..498e88a19f71 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.h +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.h @@ -85,6 +85,7 @@ #define IO_BIAS_MASK GENMASK(3, 0) #define SUN4I_FUNC_INPUT 0 +#define SUN4I_FUNC_OUTPUT 1 #define SUN4I_FUNC_IRQ 6 #define SUN4I_FUNC_DISABLED_OLD 7 #define SUN4I_FUNC_DISABLED_NEW 15 @@ -175,6 +176,12 @@ struct sunxi_pinctrl { int *irq; unsigned *irq_array; raw_spinlock_t lock; + /* + * Output latch shadow, one word per bank. Seeded lockless at + * probe before the pinctrl device registers, protected by @lock + * afterwards. + */ + u32 *dat_shadow; struct pinctrl_dev *pctl_dev; unsigned long flags; u32 bank_mem_size; From cc337324cc6be1ce0ae7e5a068dcb7e99ae1b59c Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Mon, 14 Sep 2026 18:11:30 +0100 Subject: [PATCH 1813/2366] btrfs: fix creation of compressed inline extents that don't save space If the compressed data of an inline extent is larger than or equals to the size of the uncompressed data, we are still allowing the creation of the compressed inline extent, which does not result in any benefits, quite the contrary as we waste metadata space and have to decompress when reading. This is a recent regression introduced in commit 3eaf5f082c4c ("btrfs: extract inlined creation into a dedicated delalloc helper"). It happens because we are passing the block size to btrfs_compress_bio(), so we don't get -E2BIG from the compression code anymore, but we can not pass i_size either, because if i_size is smaller than sector size, we end up never creating lzo compressed inline extent for such small i_size values. So refuse the compressed result at run_delalloc_inline() if its size is not smaller than the uncompressed size (i_size). Reported-by: Hanabishi Link: https://lore.kernel.org/linux-btrfs/c97652a5-ac6b-4de6-aa23-3cdebc01d00b@gmail.com/ Fixes: 3eaf5f082c4c ("btrfs: extract inlined creation into a dedicated delalloc helper") CC: stable@vger.kernel.org # 7.1+ Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/inode.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 3668cbc7598e..4f976b52996d 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -2339,12 +2339,27 @@ static int run_delalloc_inline(struct btrfs_inode *inode, struct folio *locked_f } else if (inode->prop_compress) { compress_type = inode->prop_compress; } + /* + * We need to pass blocksize and not i_size, otherwise we can't + * create compressed inline extents for data smaller than sector + * size with lzo. + */ cb = btrfs_compress_bio(inode, 0, blocksize, compress_type, compress_level, 0); if (IS_ERR(cb)) { cb = NULL; /* Just fall back to non-compressed case. */ } else { compressed_size = cb->bbio.bio.bi_iter.bi_size; + /* + * If we did not save space, it's pointless and wasteful + * to have an inline compressed extent, so fallback to + * an uncompressed inline extent. + */ + if (compressed_size >= i_size) { + cleanup_compressed_bio(cb); + cb = NULL; + compressed_size = 0; + } } } if (!can_cow_file_range_inline(inode, 0, i_size, compressed_size)) { From 76bf149cd0298544631e756670b89c399c7acbca Mon Sep 17 00:00:00 2001 From: Daniel Linjama Date: Wed, 16 Sep 2026 09:15:56 +0300 Subject: [PATCH 1814/2366] btrfs: handle lack of space when cleaning up verity items When enable_verity() hits the qgroup limit, rollback_verity() needs its own metadata reservation. When the qgroup limit or lack of space refuses the rollback, the whole filesystem is forced read-only even though the qgroup limit was for one subvolume only. Also orphan cleanup at the next mount fails the same way, so the leftover items are never removed: with -EDQUOT the subvolume stays unreachable, and with -ENOSPC on a full filesystem the next read-write mount fails. Start transactions with btrfs_start_transaction_fallback_global_rsv() in btrfs_orphan_cleanup(), drop_verity_items() and rollback_verity(). Those calls only delete items and free the space in the end, so they may use the global reserve and skip the qgroup limit, which avoids -ENOSPC and -EDQUOT. Fixes: 146054090b08 ("btrfs: initial fsverity support") Reviewed-by: Qu Wenruo Signed-off-by: Daniel Linjama Signed-off-by: David Sterba --- fs/btrfs/inode.c | 3 ++- fs/btrfs/verity.c | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 4f976b52996d..e97446fc9e10 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -3892,7 +3892,8 @@ int btrfs_orphan_cleanup(struct btrfs_root *root) if (ret) goto out; } - trans = btrfs_start_transaction(root, 1); + /* Only deletes the orphan. */ + trans = btrfs_start_transaction_fallback_global_rsv(root, 1); if (IS_ERR(trans)) { ret = PTR_ERR(trans); goto out; diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c index 4e0ab5842274..600337a84fbe 100644 --- a/fs/btrfs/verity.c +++ b/fs/btrfs/verity.c @@ -93,6 +93,20 @@ static loff_t merkle_file_pos(const struct inode *inode) return rounded; } +/* + * Start a transaction for removing verity items or the verity orphan. + * + * Like unlink, this only deletes items and frees space in the end, so the + * reservation may come from the global reserve when the filesystem is full + * (-ENOSPC) and is not subject to the qgroup limit (-EDQUOT). Otherwise a + * failed enable could never be cleaned up in either situation. + */ +static struct btrfs_trans_handle *start_verity_cleanup_trans(struct btrfs_root *root, + unsigned int num_items) +{ + return btrfs_start_transaction_fallback_global_rsv(root, num_items); +} + /* * Drop all the items for this inode with this key_type. * @@ -120,7 +134,7 @@ static int drop_verity_items(struct btrfs_inode *inode, u8 key_type) while (1) { /* 1 for the item being dropped */ - trans = btrfs_start_transaction(root, 1); + trans = start_verity_cleanup_trans(root, 1); if (IS_ERR(trans)) return PTR_ERR(trans); @@ -466,7 +480,7 @@ static int rollback_verity(struct btrfs_inode *inode) * 1 for updating the inode flag * 1 for deleting the orphan */ - trans = btrfs_start_transaction(root, 2); + trans = start_verity_cleanup_trans(root, 2); if (IS_ERR(trans)) { ret = PTR_ERR(trans); trans = NULL; From 7c431d61b69a3fd0784c20aa4cd0b8fb501b5653 Mon Sep 17 00:00:00 2001 From: ZHOU Jiaxiang Date: Wed, 16 Sep 2026 21:58:21 +0800 Subject: [PATCH 1815/2366] scsi: block: Fix zones_cond out-of-bounds write on zone report blk_revalidate_disk_zones() sizes the zones_cond array from the disk capacity and zone size, but the index used by blk_revalidate_zone_cond() comes from the device-driven report_zones() walk and is never checked against the array size. A device reporting more zones than fit the array makes blk_zone_set_cond() write out of bounds. One way to reach this is a zone count exceeding 32 bits: both blk_revalidate_zone_args.nr_zones and struct zoned_disk_info.nr_zones are unsigned int, so a disk advertising more than UINT_MAX zones (e.g. 2^32 + 1024 zones of one 512-byte logical block) gets its zone count truncated to a small value, undersizing the array while the report walk keeps counting upward. Check the index against the array size before storing the zone condition, and refuse to revalidate when the zone count does not fit 32 bits. Fixes: 6e945ffb6555 ("block: use zone condition to determine conventional zones") Signed-off-by: ZHOU Jiaxiang Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/7815D1B293A8F55E+20260916135822.32584-2-me@fxti.xyz Signed-off-by: Martin K. Petersen (Oracle) --- block/blk-zoned.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/block/blk-zoned.c b/block/blk-zoned.c index a5afb842bf35..475aa16bc41a 100644 --- a/block/blk-zoned.c +++ b/block/blk-zoned.c @@ -2018,12 +2018,17 @@ static int disk_revalidate_zone_resources(struct gendisk *disk, struct blk_revalidate_zone_args *args) { struct queue_limits *lim = &disk->queue->limits; + unsigned long long nr_zones; unsigned int pool_size; int ret = 0; args->disk = disk; - args->nr_zones = - DIV_ROUND_UP_ULL(get_capacity(disk), lim->chunk_sectors); + nr_zones = DIV_ROUND_UP_ULL(get_capacity(disk), lim->chunk_sectors); + if (nr_zones > UINT_MAX) { + pr_warn("%s: Too many zones (%llu)\n", disk->disk_name, nr_zones); + return -EINVAL; + } + args->nr_zones = nr_zones; /* Cached zone conditions: 1 byte per zone */ args->zones_cond = kzalloc(args->nr_zones, GFP_NOIO); @@ -2131,6 +2136,12 @@ static int blk_revalidate_zone_cond(struct blk_zone *zone, unsigned int idx, { enum blk_zone_cond cond = zone->cond; + if (idx >= args->nr_zones) { + pr_warn("%s: Zone report index %u exceeds zone count %u\n", + args->disk->disk_name, idx, args->nr_zones); + return -EINVAL; + } + /* Check that the zone condition is consistent with the zone type. */ switch (cond) { case BLK_ZONE_COND_NOT_WP: From b6ec0f79745967c751c85df373062c8d15e45fc4 Mon Sep 17 00:00:00 2001 From: ZHOU Jiaxiang Date: Wed, 16 Sep 2026 21:58:22 +0800 Subject: [PATCH 1816/2366] scsi: sd_zbc: Reject disks with too many zones sd_zbc_read_zones() computes the number of zones with 64-bit arithmetic and stores the result in the unsigned int nr_zones field of struct zoned_disk_info, silently truncating counts that exceed 32 bits. The truncated count is later used to size per-zone resources, while the device may still report more zones than fit. Moreover, sd_zbc_report_zones() counts the reported zones with a signed int zone_idx, which overflows past INT_MAX. Reject devices reporting more than INT_MAX zones at scan time; such a device is not realistic for any medium that exists today, and accepting it produces inconsistent zone bookkeeping. Fixes: 89d947561077 ("sd: Implement support for ZBC devices") Signed-off-by: ZHOU Jiaxiang Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/C41798AB5AA6BF2B+20260916135822.32584-3-me@fxti.xyz Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/sd_zbc.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/sd_zbc.c b/drivers/scsi/sd_zbc.c index 56e455fb5add..456beaf2e769 100644 --- a/drivers/scsi/sd_zbc.c +++ b/drivers/scsi/sd_zbc.c @@ -589,7 +589,7 @@ int sd_zbc_revalidate_zones(struct scsi_disk *sdkp) int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim, u8 buf[SD_BUF_SIZE]) { - unsigned int nr_zones; + u64 nr_zones; u32 zone_blocks = 0; int ret; @@ -621,6 +621,12 @@ int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim, goto err; nr_zones = round_up(sdkp->capacity, zone_blocks) >> ilog2(zone_blocks); + if (nr_zones > INT_MAX) { + sd_printk(KERN_ERR, sdkp, "Too many zones (%llu)\n", + nr_zones); + ret = -EINVAL; + goto err; + } sdkp->early_zone_info.nr_zones = nr_zones; sdkp->early_zone_info.zone_blocks = zone_blocks; From 8d836581f9b1f57ceaa2b47654754ef1260b410b Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Wed, 16 Sep 2026 15:00:32 +0200 Subject: [PATCH 1817/2366] ata: libata-scsi: fix ata_dsm_trim_pages() kernel-doc make htmldocs fails with: Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301: ERROR: Unexpected indentation. [docutils] The Return: section of ata_dsm_trim_pages() ends its first sentence with a colon and continues with an indented bullet list. reStructuredText requires a blank line before an indented block, so docutils chokes on the list. Simply adding the missing blank line does not work either: Return: is a kernel-doc "special section", which is terminated by the first blank line, so the bullet list would end up in the Description section, detached from the sentence introducing it. Spell the two bounds out as prose instead, so that the Return: section stays self-contained. Documentation-only change. Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND") Reported-by: Thomas Huth Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/ Reviewed-by: Damien Le Moal Reviewed-by: Thomas Huth Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org Signed-off-by: Niklas Cassel --- drivers/ata/libata-scsi.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index b3666519b648..7e22bbc38238 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -2297,10 +2297,9 @@ static unsigned int ata_scsiop_inq_89(struct ata_device *dev, * logical block, so it can hold at most sector_size / 512 pages. * * Return: the maximum number of 512-byte pages a single translated WRITE SAME - * command may send to @dev (never less than one), that is the smaller of: - * - MAX PAGES PER DSM COMMAND (IDENTIFY DEVICE word 105), when the device - * reports a non-zero limit; and - * - the logical sector size expressed in 512-byte pages (see above). + * command may send to @dev, that is the smaller of MAX PAGES PER DSM COMMAND + * (IDENTIFY DEVICE word 105, when the device reports a non-zero limit) and + * the logical sector size expressed in 512-byte pages; never less than one. */ static unsigned int ata_dsm_trim_pages(struct ata_device *dev) { From e6cb0b4d4ecb8e71fd2200d907ab2e9663356f69 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Wed, 16 Sep 2026 05:29:26 +0500 Subject: [PATCH 1818/2366] hwmon: (hp-wmi-sensors) Fix use-after-free in fungible_show() nsensor->current_state is dynamically replaced as the sensor's state changes. update_numeric_sensor_from_wobj() does this by freeing the old string and installing a new one: if (strcmp(trimmed, nsensor->current_state)) { new_string = hp_wmi_strdup(dev, trimmed); if (new_string) { devm_kfree(dev, nsensor->current_state); nsensor->current_state = new_string; } } This function is only ever called from hp_wmi_update_info() while state->lock is held, so the free-and-replace itself is properly serialized against concurrent updates. fungible_show(), however, reads the same pointer after the lock has already been dropped: err = hp_wmi_update_info(state, info); if (err) return err; switch (prop) { ... case HP_WMI_PROPERTY_CURRENT_STATE: seq_printf(seqf, "%s\n", nsensor->current_state); break; hp_wmi_update_info() takes state->lock internally and releases it before returning, so by the time fungible_show() dereferences nsensor->current_state in seq_printf(), no lock is held. Two processes reading a sensor's current_state debugfs entry at overlapping times (or one reading it while another read of the same sensor triggers a refresh) can race: one thread's seq_printf() can be part-way through printing the string at the moment another thread's call into update_numeric_sensor_from_wobj() frees it with devm_kfree() and installs a new pointer, causing a use-after-free read. Take state->lock around the read in fungible_show() as well, so it can never run concurrently with the free-and-replace in update_numeric_sensor_from_wobj(). Fixes: 23902f98f8d4 ("hwmon: add HP WMI Sensors driver") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Acked-by: James Seo Link: https://patch.msgid.link/20260916002926.161595-1-meatuni001@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/hp-wmi-sensors.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c index 03c684ba83bd..55aee16df57d 100644 --- a/drivers/hwmon/hp-wmi-sensors.c +++ b/drivers/hwmon/hp-wmi-sensors.c @@ -1247,7 +1247,9 @@ static int fungible_show(struct seq_file *seqf, enum hp_wmi_property prop) break; case HP_WMI_PROPERTY_CURRENT_STATE: + mutex_lock(&state->lock); seq_printf(seqf, "%s\n", nsensor->current_state); + mutex_unlock(&state->lock); break; case HP_WMI_PROPERTY_UNIT_MODIFIER: From 1d12fb94ac0975566545871dda100df34df5f845 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Tue, 15 Sep 2026 16:48:35 +0000 Subject: [PATCH 1819/2366] hwmon: (pmbus/tps53679) Fix TPS53676 phase page decoding tps53676_identify() reads the USER_DATA_03 phase configuration to count the phases assigned to each channel and derive the number of PMBus pages. In each 16-bit phase descriptor the channel (PAGE) is encoded in bit 4 and the firing order in bits 3:0, but the code tested bit 3 (0x08), which is part of the firing-order field. TPS53676 supports up to seven phases, so firing-order bit 3 is never set. As a result the existing test classifies every enabled phase as channel A. On a dual-channel configuration the phases assigned to channel B are therefore miscounted as channel A and page 1 is not exposed. Test the PAGE field (bit 4) instead. Fixes: cb3d37b59012 ("hwmon: (pmbus/tps53679) Add support for TI TPS53676") Cc: stable@vger.kernel.org Signed-off-by: Sanman Pradhan Link: https://patch.msgid.link/20260915164823.160977-2-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/tps53679.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c index 31e54608b3c9..9f27832703ff 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -187,7 +187,7 @@ static int tps53676_identify(struct i2c_client *client, return -EIO; for (i = 0; i < 2 * TPS53676_MAX_PHASES; i += 2) { if (buf[i + 1] & 0x80) { - if (buf[i] & 0x08) + if (buf[i] & BIT(4)) phases_b++; else phases_a++; From d2710c8d938ae6a825a6463158e6e6f31eac792a Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:12 +0200 Subject: [PATCH 1820/2366] signal: Prevent exec() race Hyunwoo debugged the following KASAN UAF splat: BUG: KASAN: slab-use-after-free in __send_signal_locked+0xb27/0xba0 Write of size 8 at addr ffff888007ed80c8 by task poc/79 ... Call Trace: __send_signal_locked+0xb27/0xba0 do_send_sig_info+0xa7/0x160 do_send_specific+0x76/0xa0 __x64_sys_tgkill+0x193/0x270 ... Allocated by task 80: do_timer_create+0x1a4/0x1030 __x64_sys_timer_create+0x145/0x190 ... Freed by task 12: kmem_cache_free_bulk+0x1f8/0x4a0 kvfree_rcu_bulk+0x14f/0x1c0 kfree_rcu_work+0x128/0x1a0 ... Last potentially related work creation: kvfree_call_rcu+0x39/0x390 __flush_itimer_signals+0x211/0x320 flush_itimer_signals+0x47/0x90 begin_new_exec+0xa6b/0x28c0 It turned out that this happens with a non-leader exec() as Hyunwoo explained: de_thread() calls exchange_tids() before release_task(leader), so the struct pid held by a SIGEV_THREAD_ID timer created against the leader's tid now points to the thread which called execve(). pid_task() returns that thread and lock_task_sighand() on it succeeds. If the timer signal is blocked, its sigqueue stays queued on the leader's task::pending. The next expiry of that timer can then run while release_task() flushes the queue. posixtimer_send_sigqueue() checks whether the sigqueue is already queued with a plain list_empty(), which only reads list_head::next. list_del_init() is not atomic and INIT_LIST_HEAD() stores list_head::next before list_head::prev, so the check can pass in between. list_add_tail() queues the entry on the task::pending of the live thread, and the list_head::prev store from the flush then overwrites the list_head::prev link that list_add_tail() has just set. __flush_itimer_signals() does not undo that either. With list_head::prev pointing at the entry itself, its list_del_init() only stores the same values again, so the entry is not removed from the list. It is still there after the last reference is dropped and the timer is freed by RCU, and the list_add_tail() of a later tgkill() follows that list_head::prev into the freed timer. This problem surfaced with the recent commit which moved the sigqueue flush out of the sighand lock held region. Hyonwoo proposed to fix this by using list_del_init_careful(), but that just papers over the problem. After some disucssions and various attempts to solve it, Eric pointed out that there is no reason to flush task::pending late in release_task() and it should be done in exit_signals() already. As nothing can collect and deliver signals which are queued in a dying task's pending queue, there is no reason to delay it further. But it has to be ensured that no signals can be queued into it after that point. exit_signals() sets PF_EXITING in task::flags, which can be used as an indicator for this. Cure it by: - Preventing signal queueing for task private signals (PIDTYPE_PID) when the task has PF_EXITING set in __send_signal_locked() and in posixtimer_send_sigqueue(). - Protecting the unlocked setting of PF_EXITING in exit_signals() for the task group empty and the group exit case with sighand lock - Flushing task::pending signals right there. Optimize that by moving the whole pending list to an on-stack list head under sighand lock and free the signals without the lock held. There has been quite some discussion about the lockless flush and the non-leader exec case on weakly ordered systems. The problem is that a third party which tries to send a posix timer signal relies on the PID lookup to find the target task and that lookup might result in the new leader when the signal was originaly directed to the old leader. In case that the signal was queued on the old leader then the lockless flush raised a concern over the following situation: old_leader new_leader third party A: flush_list() // list_del_init() stores to sigqueue LOCK (tasklist) old_leader->exit_state = EXIT_ZOMBIE; B: UNLOCK (tasklist) C: LOCK (tasklist) if (old_leader->exit_state) transfer_tids() D: store PID posix_timer_send_sigqueue() // Observes #D so t = new_leader E: t = get_target() F: LOCK (sighand) G: if (list_empty(sigqueue)) list_add(sigqueue) The concern was that the third party might observe #D but not observe #A and therefore would proceed to #G while the list_del() stores (#A) in flush_list() are not visible yet, which could result in list corruption. That would be possible if looking at it solely from a RELEASE+ACQUIRE ordering point of view, but B-C is a UNLOCK+LOCK hand-over, which is not the same as RELEASE+ACQUIRE: RELEASE+ACQUIRE: RCpc, only the CPUs involved agree on the ordering UNLOCK+LOCK: RCtso, the hand-over is store-ordering As B-C is UNLOCK+LOCK, which is RCtso and that does impose store order, A stores must happen before the D store. Combine with E-F, which has a data dependency from the LOAD to the LOCK and thereby constraints later LOADs, those sigqueue loads in G that come after F must in fact observe the A stores. Fixes: fb3bbcfe344e ("exit: change the release_task() paths to call flush_sigqueue() lockless") Reported-by: Hyunwoo Kim Debugged-by: Hyunwoo Kim Suggested-by: "Eric W. Biederman" Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Oleg Nesterov Reviewed-by: Frederic Weisbecker Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260911090541.572536604@kernel.org Closes: https://patch.msgid.link/aok1rdkBgZsynHZB@v4bel --- kernel/exit.c | 11 ++--- kernel/signal.c | 117 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 92 insertions(+), 36 deletions(-) diff --git a/kernel/exit.c b/kernel/exit.c index 4e028f157597..424c44a42a4d 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -302,12 +302,13 @@ void release_task(struct task_struct *p) free_pids(post.pids); release_thread(p); /* - * This task was already removed from the process/thread/pid lists - * and lock_task_sighand(p) can't succeed. Nobody else can touch - * ->pending or, if group dead, signal->shared_pending. We can call - * flush_sigqueue() lockless. + * This task was already removed from the process/thread/pid lists and + * lock_task_sighand(p) can't succeed. If it's the group leader then + * flush tsk->signal->shared_pending. tsk->pending has been flushed + * already in exit_signals(). Nothing else can touch + * signal->shared_pending anymore, so flush_sigqueue() can be invoked + * lockless. */ - flush_sigqueue(&p->pending); if (thread_group_leader(p)) flush_sigqueue(&p->signal->shared_pending); diff --git a/kernel/signal.c b/kernel/signal.c index ec30550951ec..d31ebcb6ed4d 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -457,18 +457,42 @@ static void __sigqueue_free(struct sigqueue *q) kmem_cache_free(sigqueue_cachep, q); } -void flush_sigqueue(struct sigpending *queue) +/* + * flush_sigqueue_list() can only be invoked without holding sighand::siglock in + * the following cases: + * + * 1) When flushing task::pending _after_ setting task::flags PF_EXITING + * + * All functions which try to send a signal to @task will observe PF_EXITING + * and drop the signal. + * + * 2) When flushing task::signal::shared_pending _after_ the last task in a + * thread group was unhashed and task::sighand is NULL. + * + * Nothing can queue a signal anymore because sighand is NULL. + */ +static void flush_sigqueue_list(struct list_head *head) { - struct sigqueue *q; + struct sigqueue *q, *tmp; - sigemptyset(&queue->signal); - while (!list_empty(&queue->list)) { - q = list_entry(queue->list.next, struct sigqueue , list); + list_for_each_entry_safe(q, tmp, head, list) { list_del_init(&q->list); __sigqueue_free(q); } } +void flush_sigqueue(struct sigpending *queue) +{ + sigemptyset(&queue->signal); + flush_sigqueue_list(&queue->list); +} + +static void sigqueue_dequeue_pending(struct sigpending *queue, struct list_head *head) +{ + sigemptyset(&queue->signal); + list_splice_init(&queue->list, head); +} + /* * Flush all pending signals for this kthread. */ @@ -1019,6 +1043,21 @@ static inline bool legacy_queue(struct sigpending *signals, int sig) return (sig < SIGRTMIN) && sigismember(&signals->signal, sig); } +/* + * When PF_EXITING is set the task is on the way out and has t::pending + * flushed already. Prevent queueing of PIDTYPE_PID signals as they would + * be leaked. + */ +static inline bool task_can_queue_signal(struct task_struct *t, enum pid_type type) +{ + lockdep_assert_held(&t->sighand->siglock); + + if (!(t->flags & PF_EXITING)) + return true; + + return type != PIDTYPE_PID; +} + static int __send_signal_locked(int sig, struct kernel_siginfo *info, struct task_struct *t, enum pid_type type, bool force) { @@ -1030,6 +1069,10 @@ static int __send_signal_locked(int sig, struct kernel_siginfo *info, lockdep_assert_held(&t->sighand->siglock); result = TRACE_SIGNAL_IGNORED; + + if (!task_can_queue_signal(t, type)) + goto ret; + if (!prepare_signal(sig, t, force)) goto ret; @@ -1980,11 +2023,25 @@ static inline struct task_struct *posixtimer_get_target(struct k_itimer *tmr) struct task_struct *t = pid_task(tmr->it_pid, tmr->it_pid_type); if (t && tmr->it_pid_type != PIDTYPE_PID && - same_thread_group(t, current) && !current->exit_state) + same_thread_group(t, current) && !(current->flags & PF_EXITING)) t = current; return t; } +/* + * Find the target task for the POSIX timer signal and prevent that a + * PIDTYPE_PID signal is queued on a task which has PF_EXITING set. + */ +static inline struct task_struct *posixtimer_get_unignore_target(struct k_itimer *tmr) +{ + struct task_struct *t = posixtimer_get_target(tmr); + + if (t && task_can_queue_signal(t, tmr->it_pid_type)) + return t; + + return NULL; +} + void posixtimer_send_sigqueue(struct k_itimer *tmr) { struct sigqueue *q = &tmr->sigq; @@ -2002,6 +2059,9 @@ void posixtimer_send_sigqueue(struct k_itimer *tmr) if (!likely(lock_task_sighand(t, &flags))) return; + if (!task_can_queue_signal(t, tmr->it_pid_type)) + goto unlock; + /* * Update @tmr::sigqueue_seq for posix timer signals with sighand * locked to prevent a race against dequeue_signal(). @@ -2093,6 +2153,7 @@ void posixtimer_send_sigqueue(struct k_itimer *tmr) result = TRACE_SIGNAL_DELIVERED; out: trace_signal_generate(sig, &q->info, t, tmr->it_pid_type != PIDTYPE_PID, result); +unlock: unlock_task_sighand(t, &flags); } @@ -2148,7 +2209,7 @@ static void posixtimer_sig_unignore(struct task_struct *tsk, int sig) * has exited by now, drop the reference count. */ guard(rcu)(); - target = posixtimer_get_target(tmr); + target = posixtimer_get_unignore_target(tmr); if (target) posixtimer_queue_sigqueue(&tmr->sigq, target, tmr->it_pid_type); else @@ -3132,42 +3193,36 @@ static void retarget_shared_pending(struct task_struct *tsk, sigset_t *which) void exit_signals(struct task_struct *tsk) { + LIST_HEAD(sigq_list); int group_stop = 0; - sigset_t unblocked; /* * @tsk is about to have PF_EXITING set - lock out users which - * expect stable threadgroup. + * expect a stable threadgroup. */ cgroup_threadgroup_change_begin(tsk); - if (thread_group_empty(tsk) || (tsk->signal->flags & SIGNAL_GROUP_EXIT)) { + scoped_guard(spinlock_irq, &tsk->sighand->siglock) { tsk->flags |= PF_EXITING; - cgroup_threadgroup_change_end(tsk); - return; - } - spin_lock_irq(&tsk->sighand->siglock); - /* - * From now this task is not visible for group-wide signals, - * see wants_signal(), do_signal_stop(). - */ - tsk->flags |= PF_EXITING; + sigqueue_dequeue_pending(&tsk->pending, &sigq_list); + + if (task_sigpending(tsk) && !thread_group_empty(tsk) && + !(tsk->signal->flags & SIGNAL_GROUP_EXIT)) { + sigset_t unblocked = tsk->blocked; + + signotset(&unblocked); + retarget_shared_pending(tsk, &unblocked); + + if (unlikely(tsk->jobctl & JOBCTL_STOP_PENDING) && + task_participate_group_stop(tsk)) + group_stop = CLD_STOPPED; + } + } cgroup_threadgroup_change_end(tsk); - if (!task_sigpending(tsk)) - goto out; - - unblocked = tsk->blocked; - signotset(&unblocked); - retarget_shared_pending(tsk, &unblocked); - - if (unlikely(tsk->jobctl & JOBCTL_STOP_PENDING) && - task_participate_group_stop(tsk)) - group_stop = CLD_STOPPED; -out: - spin_unlock_irq(&tsk->sighand->siglock); + flush_sigqueue_list(&sigq_list); /* * If group stop has completed, deliver the notification. This From acb03d3881818581052924a9bbbe92b8741ed448 Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Fri, 11 Sep 2026 11:09:17 +0200 Subject: [PATCH 1821/2366] exec: Cleanup POSIX timers right after de_thread() A per-thread CPU timer holds a reference to the PID of the thread it is attached to and, while it is armed, its node is queued in that thread's posix_cputimers. The task is looked up by that PID. When a non-leader thread exec()s, de_thread() changes which task owns that PID. pid_task(timer->it.cpu.pid, PIDTYPE_PID) then returns NULL, but the node is still queued on tsk, which is alive. timer_lock_sighand() takes a failed lookup to mean that the node is already dequeued, so it has nothing to undo. begin_new_exec() calls posix_cpu_timers_exit(me) right after exec_task_namespaces() and that removes the leftover node, so the state normally stays invisible. But bprm->point_of_no_return is set before de_thread(), so if unshare_files(), set_mm_exe_file(), exec_mmap() or exec_task_namespaces() fails, the task dies before it gets there. exit_itimers() then frees the k_itimer while its node is still queued, and reaping tsk later erases that freed node from the rbtree. In short: the non-leader thread B the parent timer_create(CLOCK_THREAD_CPUTIME_ID) timer_settime() arm_timer() // the node is queued on B execve() de_thread(B) exchange_tids(B, leader) // B's PID now belongs to the leader release_task(leader) __exit_signal(leader) posix_cpu_timers_exit(leader) // cleans leader's queue, not B's __unhash_process(leader) // that PID has no task anymore exec_mmap() mmap_read_lock_killable(old_mm) kill(B, SIGKILL) // -EINTR get_signal() do_exit() exit_itimers() posix_timer_delete() posix_cpu_timer_del() posix_timer_unhash_and_free() // freed while still queued wait4() release_task(B) posix_cpu_timers_exit(B) cleanup_timerqueue() timerqueue_del() // use-after-free Move the POSIX timer cleanup right after de_thread() before any of the later failure conditions brings the task into do_exit(). [ tglx: Move the cleanup right after de_thread() ] Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") Signed-off-by: Hyunwoo Kim Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Oleg Nesterov Reviewed-by: Frederic Weisbecker Cc: stable@vger.kernel.org Link: https://patch.msgid.link/ao7Q8miiuLAPVnWv@v4bel Link: https://patch.msgid.link/20260911090541.627712075@kernel.org --- fs/exec.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index d3081c8f7c10..819643408e6d 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1115,6 +1115,17 @@ static struct file *bprm_identity_file(const struct linux_binprm *bprm) return bprm->file; } +static void posixtimer_exec(struct task_struct *me) +{ +#ifdef CONFIG_POSIX_TIMERS + spin_lock_irq(&me->sighand->siglock); + posix_cpu_timers_exit(me); + spin_unlock_irq(&me->sighand->siglock); + exit_itimers(me); + flush_itimer_signals(); +#endif +} + /* * Calling this is the point of no return. None of the failures will be * seen by userspace since either the process is already taking a fatal @@ -1152,6 +1163,16 @@ int begin_new_exec(struct linux_binprm * bprm) retval = de_thread(me); if (retval) goto out; + + /* + * This must be done here to ensure that POSIX CPU timers which were + * armed on the current task are dequeued from me::posix_cputimers. + * Otherwise in case of a TID switch the deletion of the related POSIX + * timer would not remove an enqueued timer because the TID lookup + * of the old TID fails. + */ + posixtimer_exec(me); + /* see the comment in check_unsafe_exec() */ current->fs->in_exec = 0; /* @@ -1206,14 +1227,6 @@ int begin_new_exec(struct linux_binprm * bprm) if (retval) goto out_unlock; -#ifdef CONFIG_POSIX_TIMERS - spin_lock_irq(&me->sighand->siglock); - posix_cpu_timers_exit(me); - spin_unlock_irq(&me->sighand->siglock); - exit_itimers(me); - flush_itimer_signals(); -#endif - /* * Make the signal table private. */ From ef56085dfd1df3a53ecce8a4ff440cf6d67f430d Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Mon, 14 Sep 2026 12:07:44 +0200 Subject: [PATCH 1822/2366] pinctrl: sunxi: A523: fix voltage withstand encoding The Allwinner A523 uses the same GPIO voltage "withstand" programming (setting the input level voltage thresholds) as the previous SoCs, but for some odd reason inverts the encoding of 1.8V vs. 3.3V. Add a new bias voltage type to note this difference, and select it for the A523. At the same time also use the newer "CTL" version, which in addition allows to turn off the withstand programming for I/O voltages other than exact 1.8V or 3.3V (for instance for 2.5V sometimes used for Ethernet PHYs). The A523 has that enable register, but didn't use it so far. This fixes eMMC and reportedly Ethernet operation on some A523 boards. Fixes: 648be4cd9517 ("pinctrl: sunxi: Add support for the Allwinner A523") Signed-off-by: Andre Przywara Tested-by: Per Larsson Tested-by: Juan Manuel Lopez Carrillo Reviewed-by: Chen-Yu Tsai Tested-by: Chen-Yu Tsai # Fixes eMMC on Orange Pi 4A Signed-off-by: Linus Walleij --- drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c | 2 +- drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c | 2 +- drivers/pinctrl/sunxi/pinctrl-sunxi.c | 6 ++++++ drivers/pinctrl/sunxi/pinctrl-sunxi.h | 2 ++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c index 462aa1c4a5fa..e27e4945def2 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c +++ b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c @@ -26,7 +26,7 @@ static const u8 a523_r_irq_bank_muxes[SUNXI_PINCTRL_MAX_BANKS] = static struct sunxi_pinctrl_desc a523_r_pinctrl_data = { .irq_banks = ARRAY_SIZE(a523_r_irq_bank_map), .irq_bank_map = a523_r_irq_bank_map, - .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_SEL, + .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV, .pin_base = PL_BASE, }; diff --git a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c index b6f78f1f30ac..88d8acd5bc24 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c +++ b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c @@ -26,7 +26,7 @@ static const u8 a523_irq_bank_muxes[SUNXI_PINCTRL_MAX_BANKS] = static struct sunxi_pinctrl_desc a523_pinctrl_data = { .irq_banks = ARRAY_SIZE(a523_irq_bank_map), .irq_bank_map = a523_irq_bank_map, - .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_SEL, + .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV, }; static int a523_pinctrl_probe(struct platform_device *pdev) diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c index 2881a83be99c..31cd142ce0f7 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c @@ -728,6 +728,7 @@ static int sunxi_pinctrl_set_io_bias_cfg(struct sunxi_pinctrl *pctl, { unsigned short bank; unsigned long flags; + bool inverted = false; u32 val, reg; int uV; @@ -766,6 +767,9 @@ static int sunxi_pinctrl_set_io_bias_cfg(struct sunxi_pinctrl *pctl, reg &= ~IO_BIAS_MASK; writel(reg | val, pctl->membase + sunxi_grp_config_reg(pin)); return 0; + case BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV: + inverted = true; + fallthrough; case BIAS_VOLTAGE_PIO_POW_MODE_CTL: val = uV > 1800000 && uV <= 2500000 ? BIT(bank) : 0; @@ -780,6 +784,8 @@ static int sunxi_pinctrl_set_io_bias_cfg(struct sunxi_pinctrl *pctl, fallthrough; case BIAS_VOLTAGE_PIO_POW_MODE_SEL: val = uV <= 1800000 ? 1 : 0; + if (inverted) + val = !val; raw_spin_lock_irqsave(&pctl->lock, flags); reg = readl(pctl->membase + pctl->pow_mod_sel_offset); diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.h b/drivers/pinctrl/sunxi/pinctrl-sunxi.h index 498e88a19f71..8bd00c6ff628 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.h +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.h @@ -117,8 +117,10 @@ enum sunxi_desc_bias_voltage { * Bias voltage is set through PIO_POW_MOD_SEL_REG * and PIO_POW_MOD_CTL_REG register, as seen on * A100 and D1 SoC, for example. + * Some SoCs invert the encoding for 1.8V vs. 3.3V. */ BIAS_VOLTAGE_PIO_POW_MODE_CTL, + BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV, }; struct sunxi_desc_function { From 1d9bb9c870632adea249cfdec49ac37e6f069164 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 00:03:12 -0400 Subject: [PATCH 1823/2366] pinctrl: single: free the IRQ on domain creation failure pcs_irq_init_chained_handler() requests a shared IRQ on affected SoCs, but its domain creation failure path only removes a chained handler. That does not release the action installed by request_irq(). The probe can continue without interrupt support while leaving the shared IRQ action registered. Use pcs_irq_free() to undo the appropriate type of handler registration. At this point pcs->domain is NULL, so the helper only releases the parent IRQ handler. Then mark the IRQ invalid, as the other initialization error paths already do, to prevent another release from a later probe unwind or remove. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 3e6cee1786a1 ("pinctrl: single: Add support for wake-up interrupts") Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Signed-off-by: Linus Walleij --- drivers/pinctrl/pinctrl-single.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/pinctrl/pinctrl-single.c b/drivers/pinctrl/pinctrl-single.c index 4d5f85b7e6bb..e0eb7240a985 100644 --- a/drivers/pinctrl/pinctrl-single.c +++ b/drivers/pinctrl/pinctrl-single.c @@ -1628,7 +1628,8 @@ static int pcs_irq_init_chained_handler(struct pcs_device *pcs, &pcs_irqdomain_ops, pcs_soc); if (!pcs->domain) { - irq_set_chained_handler(pcs_soc->irq, NULL); + pcs_irq_free(pcs); + pcs_soc->irq = -1; return -EINVAL; } From e922bad8b2d5028c51a096d083fea41cd0987154 Mon Sep 17 00:00:00 2001 From: Itai Handler Date: Thu, 10 Sep 2026 20:48:32 +0300 Subject: [PATCH 1824/2366] spi: spi-zynqmp-gqspi: stop the controller on shutdown The driver has no ->shutdown, and platform_drv_shutdown() has no fallback of its own. Unlike pci_device_shutdown(), which clears bus mastering when kexec_in_progress, nothing on the platform bus disarms a device that can still write to memory. The normal kexec path never calls ->suspend either, so the quiesce in zynqmp_qspi_suspend() is not reached. A controller that is still executing a DMA read may therefore keep writing to memory across a kexec. QSPIDMA_DST_ADDR still points at memory owned by the kernel that called kexec, DST_SIZE is non-zero and the flash is still clocked, so data can keep landing in RAM while the new kernel is being relocated, and after it has started executing. That destination is a physical address which means nothing to the new kernel, so the writes can corrupt whatever now occupies it: kernel text or data, page tables, or the initrd. Nothing reports an error and the resulting behaviour is undefined. This can be observed by reading GQSPI_EN (offset 0x114) and QSPIDMA_DST_ADDR/SIZE/STS/CTRL (offsets 0x800 to 0x80c) early in the new kernel, before the driver probes: without this patch GQSPI_EN reads 1 and QSPIDMA_DST_ADDR still points into the previous kernel's memory. Add a ->shutdown that stops the controller the way zynqmp_qspi_suspend() already does. spi_controller_suspend() stops the queue, waits for a message that is already executing and makes any later transfer fail with -ESHUTDOWN, so nothing can be cut short by the register write that follows. It may sleep, which is fine here: device_shutdown() runs in process context. Unlike ->suspend this cannot abort on error, because a controller left mastering the bus is worse than a truncated transfer, so a failure to drain is only logged. GQSPI_EN_OFST is then cleared, as zynqmp_qspi_remove() and zynqmp_qspi_suspend() already do. Skip that write only when pm_runtime_get_if_in_use() returns 0, i.e. runtime suspended: the clocks are gated, so the registers are unreachable and the controller cannot be mastering the bus. A negative return is not the same thing - it is what the CONFIG_PM=n stub always returns, and there probe() has enabled pclk and refclk for good, so the controller is running and must be stopped. Fixes: dfe11a11d523 ("spi: Add support for Zynq Ultrascale+ MPSoC GQSPI controller") Cc: stable@vger.kernel.org Signed-off-by: Itai Handler Link: https://patch.msgid.link/20260910174832.873352-1-itai.handler@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-zynqmp-gqspi.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/spi/spi-zynqmp-gqspi.c b/drivers/spi/spi-zynqmp-gqspi.c index 4d55090fa443..15e9d3ef8839 100644 --- a/drivers/spi/spi-zynqmp-gqspi.c +++ b/drivers/spi/spi-zynqmp-gqspi.c @@ -1373,11 +1373,45 @@ static void zynqmp_qspi_remove(struct platform_device *pdev) clk_disable_unprepare(xqspi->pclk); } +static void zynqmp_qspi_shutdown(struct platform_device *pdev) +{ + struct zynqmp_qspi *xqspi = platform_get_drvdata(pdev); + int ret; + + /* + * Stop the queue and reject any later transfer first, so the write + * below cannot cut into a message that is still being executed. + * Unlike ->suspend this cannot abort on error: a controller left + * mastering the bus is worse than a truncated transfer. + */ + ret = spi_controller_suspend(xqspi->ctlr); + if (ret) + dev_warn(&pdev->dev, "could not stop the queue: %d\n", ret); + + /* + * Only a runtime suspended controller can be left alone: its clocks + * are gated, so it cannot be mastering the bus, and its registers + * must not be accessed either. Any other answer means it may be + * running and has to be stopped. In particular, on a kernel built + * without runtime PM this returns -EINVAL, and there the clocks + * enabled in probe() are never gated at all. + */ + ret = pm_runtime_get_if_in_use(&pdev->dev); + if (!ret) + return; + + zynqmp_gqspi_write(xqspi, GQSPI_EN_OFST, 0x0); + + if (ret > 0) + pm_runtime_put_noidle(&pdev->dev); +} + MODULE_DEVICE_TABLE(of, zynqmp_qspi_of_match); static struct platform_driver zynqmp_qspi_driver = { .probe = zynqmp_qspi_probe, .remove = zynqmp_qspi_remove, + .shutdown = zynqmp_qspi_shutdown, .driver = { .name = "zynqmp-qspi", .of_match_table = zynqmp_qspi_of_match, From f033482d76a9f18080c7a40c5f9c678bd7adc8f3 Mon Sep 17 00:00:00 2001 From: Christiano Amora Date: Wed, 16 Sep 2026 10:46:22 -0300 Subject: [PATCH 1825/2366] Bluetooth: SMP: reject Security Request over BR/EDR Bose QC Ultra Headphones (dual-mode, same public address on both transports) occasionally send an SMP Security Request on the BR/EDR SMP fixed channel right after the ACL link is encrypted. The kernel handles it as if it were an LE link: smp_cmd_security_req() has no transport check, smp_ltk_encrypt() looks up an LTK with the ACL connection's dst_type, and hci_find_ltk() matches the peer's LE LTK because the LE public address type is stored as ADDR_LE_DEV_PUBLIC (0), the same value as BDADDR_BREDR. HCI_OP_LE_START_ENC is then issued on the ACL handle, the controller rejects it with Invalid HCI Command Parameters, and hci_cs_le_start_enc() disconnects the link with HCI_ERROR_AUTH_FAILURE. The headphones drop within a second of connecting, before any profile is up; a manual reconnect works. btmon (MediaTek MT7922, kernel 7.0.12): > HCI Event: Encryption Change (0x08) plen 4 Status: Success (0x00) Handle: 50 Address: BC:87:FA:47:73:5E (Bose Corporation) Encryption: Enabled with AES-CCM (0x02) > ACL Data RX: Handle 50 flags 0x02 dlen 6 BR/EDR SMP: Security Request (0x0b) len 1 Authentication requirement: No bonding, No MITM, SC (0x08) < HCI Command: LE Start Encryption (0x08|0x0019) plen 28 Handle: 50 Address: BC:87:FA:47:73:5E (Bose Corporation) > HCI Event: Command Status (0x0f) plen 4 LE Start Encryption (0x08|0x0019) ncmd 1 Status: Invalid HCI Command Parameters (0x12) < HCI Command: Disconnect (0x01|0x0006) plen 3 Handle: 50 Address: BC:87:FA:47:73:5E (Bose Corporation) Reason: Authentication Failure (0x05) SMP over BR/EDR is limited to cross-transport key derivation; the Security Request procedure (Core Specification Vol 3, Part H, Section 2.4.6, PDU in Section 3.6.7) has no BR/EDR counterpart. Reply with Pairing Failed / Command Not Supported on a non-LE link, before the PDU is parsed, and keep the connection. The reply is sent directly rather than through smp_failure(): rejecting a command on the wrong transport is not an authentication failure, and MGMT_EV_AUTH_FAILED would make bluetoothd disconnect the device. Tested on the affected host (kernel 7.0.12, MediaTek MT7922, Bose QC Ultra) with the patched module built out of tree: 7 days and 49 reconnects without a drop, against 2 drops in the 3 days before the patch. Every disconnect in that week had a userspace or remote reason. Fixes: b5ae344d4c0f ("Bluetooth: Add full SMP BR/EDR support") Assisted-by: LLM Signed-off-by: Christiano Amora Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/smp.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c index 6091c47cb002..d23f9d0729c4 100644 --- a/net/bluetooth/smp.c +++ b/net/bluetooth/smp.c @@ -2269,6 +2269,23 @@ static u8 smp_cmd_security_req(struct l2cap_conn *conn, struct sk_buff *skb) bt_dev_dbg(hdev, "conn %p", conn); + /* SMP over BR/EDR only covers cross-transport key derivation; the + * Security Request procedure has no BR/EDR counterpart. Reject it + * here, otherwise smp_ltk_encrypt() finds the peer's LE LTK + * (ADDR_LE_DEV_PUBLIC and BDADDR_BREDR are both 0) and issues + * HCI_OP_LE_START_ENC on the ACL handle, which the controller + * rejects and hci_cs_le_start_enc() turns into a disconnect. Reply + * without smp_failure(): this is not an authentication failure, and + * MGMT_EV_AUTH_FAILED would make bluetoothd drop the device. + */ + if (hcon->type != LE_LINK) { + u8 reason = SMP_CMD_NOTSUPP; + + smp_send_cmd(conn, SMP_CMD_PAIRING_FAIL, sizeof(reason), + &reason); + return 0; + } + if (skb->len < sizeof(*rp)) return SMP_INVALID_PARAMS; From f2bbb36426581045a8bf7793da5419b9375e4348 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Tue, 15 Sep 2026 19:17:18 -0700 Subject: [PATCH 1826/2366] Bluetooth: btnxpuart: Fix skb leak in nxp_process_fw_dump() When CONFIG_DEV_COREDUMP=n, hci_devcd_append() returns -EOPNOTSUPP without freeing its skb argument. This leaks the cloned skb and also prevents nxp_set_ind_reset() from being called to perform recovery. Fix by guarding the hci_devcd_append(hdev, skb_clone(skb, GFP_ATOMIC)) call with IS_ENABLED(CONFIG_DEV_COREDUMP). Fixes: 998e447f443f ("Bluetooth: btnxpuart: Add support for HCI coredump feature") Signed-off-by: Zijun Hu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btnxpuart.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c index 25e7b41b349f..4e23d71d00e8 100644 --- a/drivers/bluetooth/btnxpuart.c +++ b/drivers/bluetooth/btnxpuart.c @@ -1388,9 +1388,11 @@ static int nxp_process_fw_dump(struct hci_dev *hdev, struct sk_buff *skb) msecs_to_jiffies(20000)); } - err = hci_devcd_append(hdev, skb_clone(skb, GFP_ATOMIC)); - if (err < 0) - goto free_skb; + if (IS_ENABLED(CONFIG_DEV_COREDUMP)) { + err = hci_devcd_append(hdev, skb_clone(skb, GFP_ATOMIC)); + if (err < 0) + goto free_skb; + } if (buf_len == 0) { bt_dev_warn(hdev, "==== FW dump complete ==="); From 71af682ba4692c2ed9ace4c3d4ca462ae368c029 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Tue, 15 Sep 2026 12:08:22 +0000 Subject: [PATCH 1827/2366] Bluetooth: mgmt: Dequeue pending mesh_send_sync entries on cancel In send_cancel(), pending mesh_tx objects are removed from the hdev->mesh_pending list and freed via mesh_send_complete(). However, if a mesh transmission was already queued onto hdev->cmd_sync_work_list via mesh_next(), the queued entry retains a raw pointer to mesh_tx. When hci_cmd_sync_work later processes the entry, it attempts to execute mesh_send_sync and its destroy callback mesh_send_start_complete using the already freed mesh_tx pointer, leading to a use-after-free. Fix this by invoking hci_cmd_sync_dequeue() for mesh_send_sync on the target mesh_tx before completing it. If the entry is found and dequeued, its destroy callback will complete and free the object; otherwise, mesh_send_complete() is called directly. Additionally, ensure the transmission queue advances after cancellation or errors. In mesh_send_start_complete(), call mesh_next() on error unless err is -ECANCELED, because hci_cmd_sync_dequeue() holds hdev->cmd_sync_work_lock and calling mesh_next() synchronously would deadlock. Instead, advance the queue in send_cancel() once the lock is released and if no transmission is in progress. Fixes: b338d91703fa ("Bluetooth: Implement support for Mesh") Signed-off-by: Lee Jones Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index ac4864e56ec7..f740e745ae78 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -2316,6 +2316,8 @@ static void mesh_send_start_complete(struct hci_dev *hdev, void *data, int err) hci_dev_clear_flag(hdev, HCI_MESH_SENDING); /* Send Complete Error Code for handle */ mesh_send_complete(hdev, mesh_tx, false); + if (err != -ECANCELED) + mesh_next(hdev, NULL, 0); return; } @@ -2425,19 +2427,28 @@ static int send_cancel(struct hci_dev *hdev, void *data) do { mesh_tx = mgmt_mesh_next(hdev, cmd->sk); - if (mesh_tx) - mesh_send_complete(hdev, mesh_tx, false); + if (mesh_tx) { + if (!hci_cmd_sync_dequeue(hdev, mesh_send_sync, + mesh_tx, NULL)) + mesh_send_complete(hdev, mesh_tx, false); + } } while (mesh_tx); } else { mesh_tx = mgmt_mesh_find(hdev, cancel->handle); - if (mesh_tx && mesh_tx->sk == cmd->sk) - mesh_send_complete(hdev, mesh_tx, false); + if (mesh_tx && mesh_tx->sk == cmd->sk) { + if (!hci_cmd_sync_dequeue(hdev, mesh_send_sync, + mesh_tx, NULL)) + mesh_send_complete(hdev, mesh_tx, false); + } } mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_MESH_SEND_CANCEL, 0, NULL, 0); + if (!hci_dev_test_flag(hdev, HCI_MESH_SENDING)) + mesh_next(hdev, NULL, 0); + return 0; } From e06d549fcd4a0ba381ed67ddf1ab3c7a6ca4314c Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 13:04:29 -0300 Subject: [PATCH 1828/2366] Bluetooth: hci_conn: fix CIS hold ownership on reuse Commit 69997d50ec57 ("Bluetooth: ISO: handle bound CIS cleanup via hci_conn") made hci_bind_cis() and hci_connect_cis() return a connection with one hold for the ISO layer. hci_bind_cis() currently takes that hold only after configuring a CIS, so its BT_CONNECTED and matching BT_BOUND paths return a bare lookup result. Its configuration failure path can likewise call hci_conn_drop() before taking a hold. Take the hold before any state-dependent return or configuration error so every successful return follows the documented ownership contract and every error drop is balanced. hci_connect_cis() also assumes hci_conn_link() always takes a new CIS hold before dropping the one returned by hci_bind_cis(). However, the helper returns an existing link without taking another hold. In that case, preserve the CIS hold for the caller and drop the redundant LE hold because the existing link already owns its parent hold. Returning early also avoids changing an existing CIS back to BT_CONNECT. Fixes: 69997d50ec57 ("Bluetooth: ISO: handle bound CIS cleanup via hci_conn") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index fa72cf8aaa7a..e32bb9c342a5 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -2079,6 +2079,8 @@ struct hci_conn *hci_bind_cis(struct hci_dev *hdev, bdaddr_t *dst, cis->conn_timeout = timeout; } + hci_conn_hold(cis); + if (cis->state == BT_CONNECTED) return cis; @@ -2120,7 +2122,6 @@ struct hci_conn *hci_bind_cis(struct hci_dev *hdev, bdaddr_t *dst, return ERR_PTR(-EINVAL); } - hci_conn_hold(cis); cis->state = BT_BOUND; return cis; @@ -2497,6 +2498,12 @@ struct hci_conn *hci_connect_cis(struct hci_dev *hdev, bdaddr_t *dst, return cis; } + /* The existing link already owns the hold on its parent. */ + if (cis->link) { + hci_conn_drop(le); + return cis; + } + link = hci_conn_link(le, cis); hci_conn_drop(cis); if (!link) { From 0fcd4dad555c96e0bd3a1b8c569f989be85c7341 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 13:04:30 -0300 Subject: [PATCH 1829/2366] Bluetooth: ISO: release unused CIS holds after channel attach hci_bind_cis() and hci_connect_cis() return one hci_conn hold for the ISO layer. A new channel association consumes that hold, which is eventually released by iso_conn_free(). There are two cases where iso_chan_add() does not create an association: it returns success when the socket is already attached to the same iso_conn, and it returns -EBUSY when another socket is attached. The hold returned for the current call is unused in both cases. This occurs when deferred setup calls iso_connect_cis() again for its existing socket, or when another socket attempts to reuse the CIS. Detect the idempotent case while the connection is locked and release the unused hold after iso_chan_add(). Also release it on -EBUSY. Do not drop it for other errors: a newly allocated iso_conn releases the transferred hold when its last temporary reference is put. Fixes: 69997d50ec57 ("Bluetooth: ISO: handle bound CIS cleanup via hci_conn") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index eb99653f33f9..7657c2a0abbf 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -496,6 +496,7 @@ static int iso_connect_cis(struct sock *sk) struct hci_dev *hdev; bdaddr_t src, dst; u8 src_type; + bool already_attached; int err; lock_sock(sk); @@ -568,8 +569,14 @@ static int iso_connect_cis(struct sock *sk) goto unlock; } + iso_conn_lock(conn); + already_attached = iso_pi(sk)->conn == conn && conn->sk == sk; + iso_conn_unlock(conn); + err = iso_chan_add(conn, sk, NULL); iso_conn_put(conn); + if (already_attached || err == -EBUSY) + hci_conn_drop(hcon); if (err) goto unlock; From e93fad891c72deb84cae49430163b384ebcc92b1 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 13:03:58 -0300 Subject: [PATCH 1830/2366] Bluetooth: hci_sock: reject out-of-range OCF values The raw HCI socket security filter has 128 OCF bits per supported OGF, but masks the 10-bit OCF with 127 before looking up the command. An unprivileged socket can therefore submit a reserved OCF that aliases an allowlisted command modulo 128. A conforming controller should reject reserved opcodes. Nevertheless, the security decision must apply to the opcode that will actually be sent, especially since controller-specific behavior is outside the host stack's control. Reject OCF values that cannot be represented by the security filter instead of aliasing them onto an unrelated command. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sock.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 070ca388f9ac..6413593eee51 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -1881,7 +1881,8 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg, u16 ocf = hci_opcode_ocf(opcode); if (((ogf > HCI_SFLT_MAX_OGF) || - !hci_test_bit(ocf & HCI_FLT_OCF_BITS, + (ocf > HCI_FLT_OCF_BITS) || + !hci_test_bit(ocf, &hci_sec_filter.ocf_mask[ogf])) && !capable(CAP_NET_RAW)) { err = -EPERM; From b0a6cf99afd57a39598b1beca0e86ef5004980de Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 13:03:07 -0300 Subject: [PATCH 1831/2366] Bluetooth: hci_sock: validate event length before filtering is_filtered_packet() reads the event code from skb->data[0] without first checking that the skb is nonempty. When an opcode filter is configured, it also reads the command opcode at offsets 3 or 4 without checking that a Command Complete or Command Status event is long enough. hci_send_to_sock() invokes the filter before hci_event_packet() validates the event header. A malformed event supplied by a controller or a vhci device can therefore cause an out-of-bounds read. Keep the unmasked event code for the opcode checks. The masked value is needed for the 64-bit event bitmap, but using it to identify command events aliases event codes above 0x3f. In particular, Synchronous Train Complete (0x4f) was treated as Command Status (0x0f) even though its payload has no opcode. Reject actual command events that are too short for the field being inspected. A truncated command event cannot match a configured opcode. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sock.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 6413593eee51..6d56c77741e1 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -164,6 +164,7 @@ static bool is_filtered_packet(struct sock *sk, struct sk_buff *skb) { struct hci_filter *flt; int flt_type, flt_event; + u8 event; /* Apply filter */ flt = &hci_pi(sk)->filter; @@ -177,7 +178,11 @@ static bool is_filtered_packet(struct sock *sk, struct sk_buff *skb) if (hci_skb_pkt_type(skb) != HCI_EVENT_PKT) return false; - flt_event = (*(__u8 *)skb->data & HCI_FLT_EVENT_BITS); + if (skb->len < 1) + return true; + + event = *(__u8 *)skb->data; + flt_event = event & HCI_FLT_EVENT_BITS; if (!hci_test_bit(flt_event, &flt->event_mask)) return true; @@ -186,11 +191,17 @@ static bool is_filtered_packet(struct sock *sk, struct sk_buff *skb) if (!flt->opcode) return false; - if (flt_event == HCI_EV_CMD_COMPLETE && + if (event == HCI_EV_CMD_COMPLETE && skb->len < 5) + return true; + + if (event == HCI_EV_CMD_COMPLETE && flt->opcode != get_unaligned((__le16 *)(skb->data + 3))) return true; - if (flt_event == HCI_EV_CMD_STATUS && + if (event == HCI_EV_CMD_STATUS && skb->len < 6) + return true; + + if (event == HCI_EV_CMD_STATUS && flt->opcode != get_unaligned((__le16 *)(skb->data + 4))) return true; From 4c94557dd02569efa6c1072a0439addaef9a5224 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 13:03:32 -0300 Subject: [PATCH 1832/2366] Bluetooth: ISO: balance the parent hold in hci_bind_bis() hci_conn_link() takes a lifetime reference to its parent with hci_conn_get(), but only takes an operational hold on the child. hci_conn_unlink() later balances both a hold and a reference on the parent. The SCO and CIS paths pass a parent acquired from a connect helper, so it already has a hold. For an additional BIS, hci_bind_bis() obtains the parent from hci_conn_hash_lookup_big(), which returns a bare pointer. Unlinking the child then drops the parent's existing hold and can schedule it for disconnection while its socket is still using it. Take a hold on the parent before linking it and drop that hold if linking fails. A successful link transfers the hold to hci_conn_unlink(). Fixes: fa224d0c094a ("Bluetooth: ISO: Reassociate a socket with an active BIS") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index e32bb9c342a5..96195d2fd10f 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -2375,10 +2375,13 @@ struct hci_conn *hci_bind_bis(struct hci_dev *hdev, bdaddr_t *dst, __u8 sid, parent = hci_conn_hash_lookup_big(hdev, conn->iso_qos.bcast.big); if (parent && parent != conn) { + hci_conn_hold(parent); link = hci_conn_link(parent, conn); hci_conn_drop(conn); - if (!link) + if (!link) { + hci_conn_drop(parent); return ERR_PTR(-ENOLINK); + } } return conn; From 6c78a213d9070b610c7f418af2c25b66180b7e37 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 13:02:39 -0300 Subject: [PATCH 1833/2366] Bluetooth: L2CAP: validate frame length before control and FCS access l2cap_data_rcv() unpacks either a two-byte or four-byte control field without first ensuring that it is present. A short ERTM or streaming-mode frame can therefore cause an out-of-bounds read. There is a second short-frame case when CRC16 is enabled. After the control field is pulled, l2cap_check_fcs() subtracts two from skb->len without checking it. If fewer than two bytes remain, the subtraction wraps; skb_trim() leaves the buffer unchanged and the subsequent FCS load reads past the logical end of the frame. Validate that the frame contains both its control field and, when enabled, its FCS before either field is accessed. Fixes: 1c2acffb76d4 ("Bluetooth: Add initial support for ERTM packets transfers") Fixes: fcc203c30d72 ("Bluetooth: Add support for FCS option to L2CAP") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/l2cap_core.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 644e31160d55..aaa2a1cd489a 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -6702,9 +6702,17 @@ static int l2cap_stream_rx(struct l2cap_chan *chan, struct l2cap_ctrl *control, static int l2cap_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb) { struct l2cap_ctrl *control = &bt_cb(skb)->l2cap; - u16 len; + u16 len, min_len; u8 event; + min_len = test_bit(FLAG_EXT_CTRL, &chan->flags) ? + L2CAP_EXT_CTRL_SIZE : L2CAP_ENH_CTRL_SIZE; + if (chan->fcs == L2CAP_FCS_CRC16) + min_len += L2CAP_FCS_SIZE; + + if (skb->len < min_len) + goto drop; + __unpack_control(chan, skb); len = skb->len; From b5dbb41b212c50c095a4dbee3017a84fe94f033b Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 12:59:52 -0300 Subject: [PATCH 1834/2366] Bluetooth: mgmt: fix race in read_unconf_index_list() read_unconf_index_list() counts unconfigured controllers before allocating its response, then checks the device flags again while filling it. hci_dev_list_lock stabilizes list membership, but it does not serialize the per-device flags. During asynchronous controller setup, the worker can set HCI_UNCONFIGURED and clear HCI_SETUP between the two passes. A controller omitted from the allocation count can then become eligible for the fill pass, causing an out-of-bounds write to rp->index[]. Allocate space for every device on hci_dev_list. Since list membership cannot change while hci_dev_list_lock is held, the response remains large enough regardless of flag transitions. The reported count and response length still include only eligible unconfigured controllers. Fixes: 73d1df2a7a10 ("Bluetooth: Add support for Read Unconfigured Index List command") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index f740e745ae78..41956cdde982 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -496,13 +496,9 @@ static int read_unconf_index_list(struct sock *sk, struct hci_dev *hdev, read_lock(&hci_dev_list_lock); - count = 0; - list_for_each_entry(d, &hci_dev_list, list) { - if (hci_dev_test_flag(d, HCI_UNCONFIGURED)) - count++; - } + count = list_count_nodes(&hci_dev_list); - rp_len = sizeof(*rp) + (2 * count); + rp_len = sizeof(*rp) + (sizeof(__le16) * count); rp = kmalloc(rp_len, GFP_ATOMIC); if (!rp) { read_unlock(&hci_dev_list_lock); From d034e836eefd7ce75e588f7031cffbeec594f5ac Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Tue, 1 Sep 2026 09:04:13 +0800 Subject: [PATCH 1835/2366] smb: client: fix use-after-free of iface in cifs_try_adding_channels() cifs_try_adding_channels() iterates ses->iface_list with list_for_each_entry_safe_from(), which captures the next entry (niface) under iface_lock. The loop body then drops iface_lock for the whole duration of cifs_ses_add_channel(). A concurrent interface refresh (SMB3_request_interfaces() -> parse_server_interfaces()) marks all ifaces inactive and removes and frees any that are not re-advertised via list_del() + kref_put(), where release_iface() is a bare kfree(). Since niface typically has no channel holding a reference, the list reference is its last and it can be freed inside the unlocked window. On continue, the iterator advance step then dereferences niface->iface_head.next, and the loop body reads iface->rdma_capable/is_active, both on freed memory. Fix this by never keeping an unreferenced list pointer across the unlocked window. Each channel attempt now re-scans the list from the head under iface_lock, takes a kref on the selected candidate, and passes only that referenced candidate to cifs_ses_add_channel(). weight_fulfilled still tracks selection progress, so restarting the scan preserves the original weighted distribution and the weight_fulfilled-before-kref_put ordering on the failure path. Add a per-pass attempts cap so a flapping interface refresh cannot keep the inner loop spinning within a single tries increment. Fixes: aa45dadd34e4 ("cifs: change iface_list from array to sorted linked list") Cc: stable@vger.kernel.org Assisted-by: Qoder:Qwen3.8-Max Signed-off-by: Joseph Qi Acked-by: Shyam Prasad N Signed-off-by: Paulo Alcantara --- fs/smb/client/sess.c | 104 ++++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index 7cf7dd104f7c..e095f41b5882 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -149,9 +149,9 @@ int cifs_try_adding_channels(struct cifs_ses *ses) int old_chan_count, new_chan_count; int left; int rc = 0; - int tries = 0; + int tries = 0, attempts; size_t iface_weight = 0, iface_min_speed = 0; - struct cifs_server_iface *iface = NULL, *niface = NULL; + struct cifs_server_iface *iface = NULL, *candidate = NULL; struct cifs_server_iface *last_iface = NULL; spin_lock(&ses->chan_lock); @@ -197,67 +197,89 @@ int cifs_try_adding_channels(struct cifs_ses *ses) break; } - if (!iface) - iface = list_first_entry(&ses->iface_list, struct cifs_server_iface, - iface_head); last_iface = list_last_entry(&ses->iface_list, struct cifs_server_iface, iface_head); iface_min_speed = last_iface->speed; + spin_unlock(&ses->iface_lock); - list_for_each_entry_safe_from(iface, niface, &ses->iface_list, - iface_head) { - /* do not mix rdma and non-rdma interfaces */ - if (iface->rdma_capable != ses->server->rdma) - continue; + attempts = 0; + while (left > 0) { + spin_lock(&ses->iface_lock); - /* skip ifaces that are unusable */ - if (!iface->is_active || - (is_ses_using_iface(ses, iface) && - !iface->rss_capable)) - continue; + /* + * iface_lock must be dropped while opening a channel, + * and a concurrent interface refresh may remove and + * free entries during that window, so no list entry + * may be kept across it without a reference. Scan + * the list from the beginning each time and only pass + * a referenced candidate to cifs_ses_add_channel(); + * weight_fulfilled tracks the progress so that no + * iface is selected beyond its weight. + */ + candidate = NULL; + list_for_each_entry(iface, &ses->iface_list, iface_head) { + /* do not mix rdma and non-rdma interfaces */ + if (iface->rdma_capable != ses->server->rdma) + continue; - /* check if we already allocated enough channels */ - iface_weight = iface->speed / iface_min_speed; + /* skip ifaces that are unusable */ + if (!iface->is_active || + (is_ses_using_iface(ses, iface) && + !iface->rss_capable)) + continue; - if (iface->weight_fulfilled >= iface_weight) - continue; + /* check if we already allocated enough channels */ + iface_weight = iface->speed / iface_min_speed; - /* take ref before unlock */ - kref_get(&iface->refcount); + if (iface->weight_fulfilled >= iface_weight) + continue; + + /* take ref before unlock */ + kref_get(&iface->refcount); + candidate = iface; + break; + } + + if (!candidate) { + /* no usable iface. reset weight_fulfilled and start over */ + list_for_each_entry(iface, &ses->iface_list, iface_head) + iface->weight_fulfilled = 0; + spin_unlock(&ses->iface_lock); + break; + } + + attempts++; + if (attempts > 3 * ses->chan_max) { + kref_put(&candidate->refcount, release_iface); + spin_unlock(&ses->iface_lock); + break; + } spin_unlock(&ses->iface_lock); - rc = cifs_ses_add_channel(ses, iface); + rc = cifs_ses_add_channel(ses, candidate); spin_lock(&ses->iface_lock); if (rc) { cifs_dbg(VFS, "failed to open extra channel on iface:%pIS rc=%d\n", - &iface->sockaddr, + &candidate->sockaddr, rc); /* failure to add chan should increase weight */ - iface->weight_fulfilled++; - kref_put(&iface->refcount, release_iface); + candidate->weight_fulfilled++; + kref_put(&candidate->refcount, release_iface); + spin_unlock(&ses->iface_lock); continue; } - iface->num_channels++; - iface->weight_fulfilled++; + candidate->num_channels++; + candidate->weight_fulfilled++; cifs_info("successfully opened new channel on iface:%pIS\n", - &iface->sockaddr); + &candidate->sockaddr); + spin_unlock(&ses->iface_lock); + + left--; + new_chan_count++; break; } - - /* reached end of list. reset weight_fulfilled and start over */ - if (list_entry_is_head(iface, &ses->iface_list, iface_head)) { - list_for_each_entry(iface, &ses->iface_list, iface_head) - iface->weight_fulfilled = 0; - spin_unlock(&ses->iface_lock); - iface = NULL; - continue; - } - spin_unlock(&ses->iface_lock); - - left--; - new_chan_count++; } return new_chan_count - old_chan_count; From a5e22cba3549b3b9ca592a6bc62329c9b85ce285 Mon Sep 17 00:00:00 2001 From: Oder Chiou Date: Wed, 16 Sep 2026 18:18:03 +0800 Subject: [PATCH 1836/2366] ASoC: rt721: Reset codec to fix abnormal sound The audio output may become abnormal after a warm reboot from Windows. Reset the codec once during hardware initialization to restore it to a known state and prevent the issue. Signed-off-by: Oder Chiou Link: https://patch.msgid.link/20260916101803.2301508-1-oder_chiou@realtek.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt721-sdca-sdw.c | 3 +++ sound/soc/codecs/rt721-sdca.c | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c index eae7d662efae..910583162d3e 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.c +++ b/sound/soc/codecs/rt721-sdca-sdw.c @@ -70,6 +70,7 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re case 0x0310100: case 0x2000000 ... 0x2000003: case 0x2000013: + case 0x2000026: case 0x200002c: case 0x200003c: case 0x2000046: @@ -142,6 +143,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x200000d: case 0x2000019: case 0x2000020: + case 0x2000026: case 0x200002c: case 0x2000030: case 0x2000046: @@ -155,6 +157,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x5810039: case 0x5b10018: case 0x5b10019: + case 0x6100006: return true; default: return false; diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index a9479d0e4941..738644018fb9 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -1497,6 +1497,15 @@ int rt721_sdca_init(struct device *dev, struct regmap *regmap, &soc_sdca_dev_rt721, rt721_sdca_dai, ARRAY_SIZE(rt721_sdca_dai)); } +static void rt721_sdca_reset(struct rt721_sdca_priv *rt721) +{ + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_REG, + RT721_VD_HIDDEN_CTRL, RT721_HIDDEN_REG_SW_RESET, + RT721_HIDDEN_REG_SW_RESET); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_RESET_CTL, 0x1, 0x1); +} + int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) { struct rt721_sdca_priv *rt721 = dev_get_drvdata(dev); @@ -1530,9 +1539,17 @@ int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) } pm_runtime_get_noresume(&slave->dev); + + if (!rt721->first_hw_init) + rt721_sdca_reset(rt721); + rt721_sdca_dmic_preset(rt721); rt721_sdca_amp_preset(rt721); rt721_sdca_jack_preset(rt721); + + if (rt721->hs_jack && (!rt721->first_hw_init)) + rt721_sdca_jack_init(rt721); + if (rt721->first_hw_init) { regcache_cache_bypass(rt721->regmap, false); regcache_mark_dirty(rt721->regmap); From 11fc0048a6930f4fca44fe3bd16a0023e78846a2 Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Sun, 13 Sep 2026 13:31:32 -0400 Subject: [PATCH 1837/2366] ASoC: ux500: Parenthesize MSP_{RX,TX}_CLKPOL_BIT() arguments arm allmodconfig fails to build with gcc: In file included from sound/soc/ux500/ux500_msp_i2s.c:20: sound/soc/ux500/ux500_msp_i2s.h:151:38: error: suggest parentheses around arithmetic in operand of '^' [-Werror=parentheses] sound/soc/ux500/ux500_msp_i2s.c:204:21: note: in expansion of macro 'MSP_TX_CLKPOL_BIT' cc1: all warnings being treated as errors The macros never parenthesized their argument: #define MSP_TX_CLKPOL_BIT(n) ((n & TCKPOL_MASK) << TCKPOL_SHIFT) That went unnoticed while every caller passed a plain variable, but configure_protocol() now passes an XOR expression, which binds as "a ^ (b & MASK)" rather than "(a ^ b) & MASK", and gcc rightly complains. No functional change: tx_clk_pol and rx_clk_pol only ever hold MSP_FALLING_EDGE (0) or MSP_RISING_EDGE (1), and bclk_inverted is a bool, so masking before or after the XOR gives the same 0/1 result. Parenthesize the argument anyway - it fixes the build and stops the macros from silently mis-evaluating a future composite argument. Fixes: 9ccbacf5a012 ("ASoC: ux500: Validate MSP DAI configuration") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609051547.G9SJp8UQ-lkp@intel.com/ Assisted-by: LLM Signed-off-by: Sasha Levin Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260913173132.1172003-1-sashal@kernel.org Signed-off-by: Mark Brown --- sound/soc/ux500/ux500_msp_i2s.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h index 2bf2699bdc49..c66ef455e138 100644 --- a/sound/soc/ux500/ux500_msp_i2s.h +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -147,8 +147,8 @@ enum msp_direction { #define RCKPOL_MASK BIT(0) #define TCKPOL_MASK BIT(0) #define SPICKM_MASK (BIT(1) | BIT(0)) -#define MSP_RX_CLKPOL_BIT(n) ((n & RCKPOL_MASK) << RCKPOL_SHIFT) -#define MSP_TX_CLKPOL_BIT(n) ((n & TCKPOL_MASK) << TCKPOL_SHIFT) +#define MSP_RX_CLKPOL_BIT(n) (((n) & RCKPOL_MASK) << RCKPOL_SHIFT) +#define MSP_TX_CLKPOL_BIT(n) (((n) & TCKPOL_MASK) << TCKPOL_SHIFT) #define P1ELEN_SHIFT 0 #define P1FLEN_SHIFT 3 From c17ae8c26eac16ad244daef44044d714f68a2ddc Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 15 Sep 2026 18:25:15 +0900 Subject: [PATCH 1838/2366] ASoC: hdmi-codec: Report a change when the channel status moves The put() callback of "IEC958 Playback Default" stores all 24 channel status bytes and then returns 0. The core notifies userspace only on a positive return, so a write that changes what the get() callback hands back is never announced, and a mixer holding the control open keeps showing the old value. Compare the stored bytes and return 1 when they move, the way snd_hda_spdif_default_put() does. The same shape is in img-spdif-out and uniperif_player. No board with this codec was to hand. The change is a comparison of driver state with no hardware behaviour in it, and mixer-test counts the missing notification as event_missing. Fixes: 7a8e1d44211e ("ASoC: hdmi-codec: Add iec958 controls") Signed-off-by: HyeongJun An Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260915092515.2638542-1-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/hdmi-codec.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index bc2c22436ba6..7aa50c5bd3df 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -426,10 +426,14 @@ static int hdmi_codec_iec958_default_put(struct snd_kcontrol *kcontrol, struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(component); + if (!memcmp(hcp->iec_status, ucontrol->value.iec958.status, + sizeof(hcp->iec_status))) + return 0; + memcpy(hcp->iec_status, ucontrol->value.iec958.status, sizeof(hcp->iec_status)); - return 0; + return 1; } static int hdmi_codec_iec958_mask_get(struct snd_kcontrol *kcontrol, From 03a5699a0a04309c597683967aaaf25d1e555ea2 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Mon, 14 Sep 2026 18:47:12 +0800 Subject: [PATCH 1839/2366] ASoC: codecs: rt712-sdca-dmic: fix uninitialized stream_config->type stream_config is not initialized before being passed to sdw_stream_add_slave(). The type field may contain garbage and is later copied to stream->type by sdw_config_stream(). Zero-initialize stream_config so type defaults to SDW_STREAM_PCM. While at it, use snd_sdw_params_to_config() helper instead of open-coding the same logic. Fixes: 63a511284c9e ("ASoC: rt712-sdca: Add RT712 SDCA driver for Mic topology") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260914104712.379574-1-yijiangshan@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca-dmic.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c index 8860d81134e7..a9f3aa4e143a 100644 --- a/sound/soc/codecs/rt712-sdca-dmic.c +++ b/sound/soc/codecs/rt712-sdca-dmic.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "rt712-sdca.h" #include "rt712-sdca-dmic.h" @@ -632,10 +633,10 @@ static int rt712_sdca_dmic_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; struct sdw_stream_runtime *sdw_stream; - int retval, num_channels; + int retval; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s", __func__, dai->name); @@ -647,13 +648,8 @@ static int rt712_sdca_dmic_hw_params(struct snd_pcm_substream *substream, if (!rt712->slave) return -EINVAL; - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = params_channels(params); - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = SDW_DATA_DIR_TX; - - num_channels = params_channels(params); - port_config.ch_mask = GENMASK(num_channels - 1, 0); + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = 2; retval = sdw_stream_add_slave(rt712->slave, &stream_config, From 3482062c786ce4233f8ed3224d824184f53ec154 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Mon, 14 Sep 2026 13:26:11 +0100 Subject: [PATCH 1840/2366] ASoC: cs-amp-lib: Prevent NULL pointer if efi variable is zero length In cs_amp_alloc_get_efi_variable() the first call to cs_amp_get_efi_variable() might return EFI_SUCCESS if the variable exists with zero length. Trap this and return -ENOENT to prevent returning an unexpected NULL pointer. The first cs_amp_get_efi_variable() call was assumed to return EFI_BUFFER_TOO_SMALL if the variable existed, but if instead it returned EFI_SUCCESS this would be converted to 0 by cs_amp_convert_efi_status() and then be returned as a NULL pointer. Fixes: 00fd40bc7acec ("ASoC: cs-amp-lib: Support Dell SSIDExV2 UEFI variable") Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260914122611.2783563-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs-amp-lib.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c index 41a9a5b005c6..9bc19d2e1639 100644 --- a/sound/soc/codecs/cs-amp-lib.c +++ b/sound/soc/codecs/cs-amp-lib.c @@ -317,6 +317,8 @@ static void *cs_amp_alloc_get_efi_variable(efi_char16_t *name, unsigned long size = 0; status = cs_amp_get_efi_variable(name, guid, NULL, &size, NULL); + if (status == EFI_SUCCESS) + return ERR_PTR(-ENOENT); if (status != EFI_BUFFER_TOO_SMALL) return ERR_PTR(cs_amp_convert_efi_status(status)); From 29218a4d11a31a8157389bc2b9e62dd768d7ea42 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:46:46 +0530 Subject: [PATCH 1841/2366] ASoC: amd: acp: bounds-check SoundWire link ID in machine drivers Add a bounds check in create_sdw_dailink() to validate that the SoundWire link ID derived from link_mask does not exceed the maximum supported by the platform. If the link ID is out of range or link_mask is zero, log an error and return -EINVAL to prevent accessing invalid CPU pin ID tables. Applied to both acp-sdw-sof-mach.c and acp-sdw-legacy-mach.c. Fixes: 6d8348ddc56e ("ASoC: amd: acp: refactor SoundWire machine driver code") Signed-off-by: Vijendar Mukunda Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260910161728.1452808-2-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-legacy-mach.c | 10 ++++++++++ sound/soc/amd/acp/acp-sdw-sof-mach.c | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index 6eac42bac855..2ea226a195c3 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -205,6 +205,16 @@ static int create_sdw_dailink(struct snd_soc_card *card, return -EINVAL; } + if (!soc_end->link_mask) { + dev_err(dev, "invalid zero link_mask\n"); + return -EINVAL; + } + if ((ffs(soc_end->link_mask) - 1) >= amd_ctx->max_sdw_links) { + dev_err(dev, "link_id %d exceeds max_sdw_links %d\n", + ffs(soc_end->link_mask) - 1, amd_ctx->max_sdw_links); + return -EINVAL; + } + switch (amd_ctx->acp_rev) { case ACP63_PCI_REV: ret = get_acp63_cpu_pin_id(ffs(soc_end->link_mask - 1), diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index a9cd1f335167..6c74e67b134f 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -121,6 +121,15 @@ static int create_sdw_dailink(struct snd_soc_card *card, return -EINVAL; } + if (!sof_end->link_mask) { + dev_err(dev, "invalid zero link_mask\n"); + return -EINVAL; + } + if ((ffs(sof_end->link_mask) - 1) >= amd_ctx->max_sdw_links) { + dev_err(dev, "link_id %d exceeds max_sdw_links %d\n", + ffs(sof_end->link_mask) - 1, amd_ctx->max_sdw_links); + return -EINVAL; + } switch (amd_ctx->acp_rev) { case ACP63_PCI_REV: ret = get_acp63_cpu_pin_id(ffs(sof_end->link_mask - 1), From 0b7d55d3a91200f2b1ed710f525a944b0a7d6369 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:46:47 +0530 Subject: [PATCH 1842/2366] ASoC: amd: acp: refactor codec config count in SOF SoundWire machine driver num_devs was used both as the endpoint count and as the output for asoc_sdw_parse_sdw_endpoints(), which overwrites it with the codec configuration count. Introduce a separate num_confs variable to hold the codec conf count so the two values remain distinct across codec_conf allocation and card->num_configs assignment. Fixes: 6d8348ddc56e ("ASoC: amd: acp: refactor SoundWire machine driver code") Signed-off-by: Vijendar Mukunda Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260910161728.1452808-3-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index 6c74e67b134f..b7926967593f 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -286,6 +286,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) int num_devs = 0; int num_ends = 0; int num_aux = 0; + int num_confs; int num_links; int be_id = 0; int ret; @@ -296,6 +297,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) return ret; } + num_confs = num_ends; /* One per DAI link, worst case is a DAI link for every endpoint */ struct asoc_sdw_dailink *sof_dais __free(kfree) = kzalloc_objs(*sof_dais, num_ends); @@ -312,7 +314,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) if (!sof_aux) return -ENOMEM; - ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); + ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); if (ret < 0) return ret; @@ -324,7 +326,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) dev_dbg(dev, "sdw %d, dmic %d", sdw_be_num, dmic_num); - codec_conf = devm_kcalloc(dev, num_devs, sizeof(*codec_conf), GFP_KERNEL); + codec_conf = devm_kcalloc(dev, num_confs, sizeof(*codec_conf), GFP_KERNEL); if (!codec_conf) return -ENOMEM; @@ -335,7 +337,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) return -ENOMEM; card->codec_conf = codec_conf; - card->num_configs = num_devs; + card->num_configs = num_confs; card->dai_link = dai_links; card->num_links = num_links; card->aux_dev = sof_aux; From 27098aaf28b96ab4e6891709062c343566d4882b Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:46:48 +0530 Subject: [PATCH 1843/2366] ASoC: amd: acp: fix ffs() operator precedence for SoundWire link ID ffs(link_mask - 1) computes ffs on (link_mask - 1) instead of subtracting 1 from the result of ffs(link_mask). For a typical power-of-2 link_mask this returns the wrong link ID, causing cpu_pin_id lookup to select the incorrect SoundWire manager. Fix the operator precedence to ffs(link_mask) - 1 in both acp-sdw-sof-mach.c and acp-sdw-legacy-mach.c. Fixes: 6d8348ddc56e ("ASoC: amd: acp: refactor SoundWire machine driver code") Signed-off-by: Vijendar Mukunda Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260910161728.1452808-4-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-legacy-mach.c | 4 ++-- sound/soc/amd/acp/acp-sdw-sof-mach.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index 2ea226a195c3..1a05d4288a46 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -217,7 +217,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, switch (amd_ctx->acp_rev) { case ACP63_PCI_REV: - ret = get_acp63_cpu_pin_id(ffs(soc_end->link_mask - 1), + ret = get_acp63_cpu_pin_id(ffs(soc_end->link_mask) - 1, *be_id, &cpu_pin_id, dev); if (ret) return ret; @@ -225,7 +225,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, case ACP70_PCI_REV: case ACP71_PCI_REV: case ACP72_PCI_REV: - ret = get_acp70_cpu_pin_id(ffs(soc_end->link_mask - 1), + ret = get_acp70_cpu_pin_id(ffs(soc_end->link_mask) - 1, *be_id, &cpu_pin_id, dev); if (ret) return ret; diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index b7926967593f..e6d545fd665e 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -132,7 +132,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, } switch (amd_ctx->acp_rev) { case ACP63_PCI_REV: - ret = get_acp63_cpu_pin_id(ffs(sof_end->link_mask - 1), + ret = get_acp63_cpu_pin_id(ffs(sof_end->link_mask) - 1, *be_id, &cpu_pin_id, dev); if (ret) return ret; @@ -140,7 +140,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, case ACP70_PCI_REV: case ACP71_PCI_REV: case ACP72_PCI_REV: - ret = get_acp70_cpu_pin_id(ffs(sof_end->link_mask - 1), + ret = get_acp70_cpu_pin_id(ffs(sof_end->link_mask) - 1, *be_id, &cpu_pin_id, dev); if (ret) return ret; From d57616f8be5601d210bbb0f677b9cb88a5186c3c Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Thu, 10 Sep 2026 21:46:49 +0530 Subject: [PATCH 1844/2366] ASoC: amd: acp: fix card name length warning in SOF SoundWire machine driver The ALSA snd_card driver[] field is 16 bytes (including the NUL terminator), leaving 15 usable characters. The SOF framework prepends a "sof-" prefix when registering the card, so card->name = "amd-soundwire" becomes driver name "sof-amd-soundwire" which is 17 characters and overflows the driver[16] buffer, triggering a kernel warning. Fix by shortening the card name to "amd-sdw"; the resulting driver name "sof-amd-sdw" fits within the 15-character limit. Signed-off-by: Vijendar Mukunda Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20260910161728.1452808-5-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index e6d545fd665e..ec3e1f5f1052 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -390,7 +390,7 @@ static int mc_probe(struct platform_device *pdev) ctx->private = amd_ctx; card = &ctx->card; card->dev = &pdev->dev; - card->name = "amd-soundwire"; + card->name = "amd-sdw"; card->owner = THIS_MODULE; card->late_probe = asoc_sdw_card_late_probe; From 0030f62683d5061d43b80577b7ab27196f1adb4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Mon, 14 Sep 2026 12:12:35 +0200 Subject: [PATCH 1845/2366] ASoC: adau1977: make the Kconfig symbols user selectable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SND_SOC_ADAU1977_{SPI,I2C} are missing Kconfig text, so they don't show up in menuconfig and can't be selected by a user - only by another symbol such as a machine driver. Add the text to make these symbols selectable and usable with generic machine drivers like the simple audio card. Signed-off-by: Alvin Šipraga Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260914-asoc-adau1977-fixes-v1-1-aa2f0cabd728@analog.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f9a47e262a77..d88593c2bac8 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -524,13 +524,13 @@ config SND_SOC_ADAU1977 tristate config SND_SOC_ADAU1977_SPI - tristate + tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - SPI" depends on SPI_MASTER select SND_SOC_ADAU1977 select REGMAP_SPI config SND_SOC_ADAU1977_I2C - tristate + tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - I2C" depends on I2C select SND_SOC_ADAU1977 select REGMAP_I2C From 528a0da3e55b24d1113b3658e94cf432e0020913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Mon, 14 Sep 2026 12:12:36 +0200 Subject: [PATCH 1846/2366] ASoC: adau1977-spi: drop __maybe_unused and of_match_ptr() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 5ab23c7923a1 ("modpost: Create modalias for builtin modules") MODULE_DEVICE_TABLE() is enough to reference a match table and the data isn't discarded by the linker even when the driver is built-in and CONFIG_OF is disabled. Drop the of_match_ptr() wrapping so that OF matching keeps working regardless of CONFIG_OF. This also means we can drop __maybe_unused since it's always used. The entries in adau1977_spi_of_match were also erroneously indented with spaces - replace the indentation with tabs to conform with coding style. Signed-off-by: Alvin Šipraga Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260914-asoc-adau1977-fixes-v1-2-aa2f0cabd728@analog.com Signed-off-by: Mark Brown --- sound/soc/codecs/adau1977-spi.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/soc/codecs/adau1977-spi.c b/sound/soc/codecs/adau1977-spi.c index 878cde9d1014..c98da5ba9e9e 100644 --- a/sound/soc/codecs/adau1977-spi.c +++ b/sound/soc/codecs/adau1977-spi.c @@ -53,18 +53,18 @@ static const struct spi_device_id adau1977_spi_ids[] = { }; MODULE_DEVICE_TABLE(spi, adau1977_spi_ids); -static const struct of_device_id adau1977_spi_of_match[] __maybe_unused = { - { .compatible = "adi,adau1977" }, - { .compatible = "adi,adau1978" }, - { .compatible = "adi,adau1979" }, - { }, +static const struct of_device_id adau1977_spi_of_match[] = { + { .compatible = "adi,adau1977" }, + { .compatible = "adi,adau1978" }, + { .compatible = "adi,adau1979" }, + { }, }; MODULE_DEVICE_TABLE(of, adau1977_spi_of_match); static struct spi_driver adau1977_spi_driver = { .driver = { .name = "adau1977", - .of_match_table = of_match_ptr(adau1977_spi_of_match), + .of_match_table = adau1977_spi_of_match, }, .probe = adau1977_spi_probe, .id_table = adau1977_spi_ids, From 76a8fe25b97881223976363044924d5cf0511749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alvin=20=C5=A0ipraga?= Date: Mon, 14 Sep 2026 12:12:37 +0200 Subject: [PATCH 1847/2366] ASoC: adau1977-i2c: add OF match table for I2C MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Like for SPI, the I2C driver needs an OF match table for the kernel to be able to automatically load the driver when built as a module. Add one. Signed-off-by: Alvin Šipraga Reviewed-by: Nuno Sá Link: https://patch.msgid.link/20260914-asoc-adau1977-fixes-v1-3-aa2f0cabd728@analog.com Signed-off-by: Mark Brown --- sound/soc/codecs/adau1977-i2c.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/codecs/adau1977-i2c.c b/sound/soc/codecs/adau1977-i2c.c index d1c6c4ddf506..5a11cafdff36 100644 --- a/sound/soc/codecs/adau1977-i2c.c +++ b/sound/soc/codecs/adau1977-i2c.c @@ -34,9 +34,18 @@ static const struct i2c_device_id adau1977_i2c_ids[] = { }; MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids); +static const struct of_device_id adau1977_i2c_of_match[] = { + { .compatible = "adi,adau1977" }, + { .compatible = "adi,adau1978" }, + { .compatible = "adi,adau1979" }, + { }, +}; +MODULE_DEVICE_TABLE(of, adau1977_i2c_of_match); + static struct i2c_driver adau1977_i2c_driver = { .driver = { .name = "adau1977", + .of_match_table = adau1977_i2c_of_match, }, .probe = adau1977_i2c_probe, .id_table = adau1977_i2c_ids, From c774ec8f0a5d02a06d34c27f5a7de7e333b91265 Mon Sep 17 00:00:00 2001 From: Eva Kurchatova Date: Wed, 16 Sep 2026 23:44:31 +0300 Subject: [PATCH 1848/2366] selftests: cgroup: give the O_TMPFILE open in get_temp_fd() a mode O_TMPFILE, like O_CREAT, needs the third argument. Without it glibc refuses the call at compile time as soon as fortification is on: In function 'open', inlined from 'get_temp_fd' at test_memcontrol.c:33:9: /usr/include/bits/fcntl2.h:52:11: error: call to '__open_missing_mode' declared with attribute error: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments The fortify checks take effect only once the compiler optimises, and cgroup/Makefile builds with "-Wall -pthread" alone, so this goes unnoticed in a plain build. Building the tests with the flags distributions commonly use, -O2 -D_FORTIFY_SOURCE=3, loses test_memcontrol entirely. Fixes: 84092dbcf901 ("selftests: cgroup: add memory controller self-tests") Signed-off-by: Eva Kurchatova Signed-off-by: Tejun Heo --- tools/testing/selftests/cgroup/test_memcontrol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testing/selftests/cgroup/test_memcontrol.c index 3a84d068fbf3..0ed82347044e 100644 --- a/tools/testing/selftests/cgroup/test_memcontrol.c +++ b/tools/testing/selftests/cgroup/test_memcontrol.c @@ -30,7 +30,7 @@ static int page_size; int get_temp_fd(void) { - return open(".", O_TMPFILE | O_RDWR | O_EXCL); + return open(".", O_TMPFILE | O_RDWR | O_EXCL, 0600); } int alloc_pagecache(int fd, size_t size) From 089070b51ccbac411462a30a454690274c6e4270 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Wed, 16 Sep 2026 23:54:17 +0000 Subject: [PATCH 1849/2366] hwmon: (pmbus/tps53679) Select page 0 for single-page TPS53676 tps53676_identify() derives the number of PMBus pages but does not ensure that page 0 is selected for single-page configurations. pmbus_set_page() does not update the PAGE register when info->pages is 1, so if boot firmware leaves PAGE set to another value subsequent register accesses may target the wrong page. For single-page devices, select page 0 explicitly. Fixes: cb3d37b59012 ("hwmon: (pmbus/tps53679) Add support for TI TPS53676") Cc: stable@vger.kernel.org Signed-off-by: Sanman Pradhan Link: https://patch.msgid.link/20260916235406.681131-2-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/tps53679.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c index 9f27832703ff..6c25701b36fc 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -200,6 +200,15 @@ static int tps53676_identify(struct i2c_client *client, if (phases_b > 0) { info->pages = 2; info->phases[1] = phases_b; + } else { + /* + * pmbus_set_page() does not update the PAGE register on + * single-page devices, so select page 0 explicitly in case + * the boot firmware left the device on another page. + */ + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + if (ret < 0) + return ret; } return 0; } From 92b68492eae701e5b0e9d142ffe229921af7b1fa Mon Sep 17 00:00:00 2001 From: James Seo Date: Wed, 16 Sep 2026 15:19:15 -0700 Subject: [PATCH 1850/2366] hwmon: (hp-wmi-sensors) Improve raw WMI string handling Commit c9ba59258094 ("hwmon: (hp-wmi-sensors) Fix failure to load on EliteDesk 800 G6") left out some logic for recognizing raw WMI strings in check_numeric_sensor_wobj(). This issue was reported by a user along with an incomplete and unsuitable proposed solution [1]. Add the missing logic and properly remedy the issue. Also slightly refactor how raw WMI strings are recognized elsewhere to make the intent that they should be treated as regular ACPI strings clearer. Reported-by: Muhammad Bilal Link: https://lore.kernel.org/linux-hwmon/20260916002907.161210-1-meatuni001@gmail.com/ [1] Fixes: c9ba59258094 ("hwmon: (hp-wmi-sensors) Fix failure to load on EliteDesk 800 G6") Signed-off-by: James Seo Link: https://patch.msgid.link/20260916221912.434119-5-james@equiv.tech Signed-off-by: Guenter Roeck --- drivers/hwmon/hp-wmi-sensors.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/hp-wmi-sensors.c b/drivers/hwmon/hp-wmi-sensors.c index 55aee16df57d..cfa0d6ad0fb3 100644 --- a/drivers/hwmon/hp-wmi-sensors.c +++ b/drivers/hwmon/hp-wmi-sensors.c @@ -526,14 +526,12 @@ static int check_wobj(const union acpi_object *wobj, for (prop = 0; prop <= last_prop; prop++) { type = elements[prop].type; valid_type = property_map[prop]; - if (type != valid_type) { - if (type == ACPI_TYPE_BUFFER && - valid_type == ACPI_TYPE_STRING && - is_raw_wmi_string(elements[prop].buffer.pointer, - elements[prop].buffer.length)) - continue; + if (type == ACPI_TYPE_BUFFER && + is_raw_wmi_string(elements[prop].buffer.pointer, + elements[prop].buffer.length)) + type = ACPI_TYPE_STRING; + if (type != valid_type) return -EINVAL; - } } return 0; @@ -579,6 +577,7 @@ static int check_numeric_sensor_wobj(const union acpi_object *wobj, int prop = HP_WMI_PROPERTY_NAME; acpi_object_type valid_type; union acpi_object *elements; + union acpi_object *element; u32 elem_count; int last_prop; bool is_new; @@ -602,13 +601,20 @@ static int check_numeric_sensor_wobj(const union acpi_object *wobj, elem_count > HP_WMI_MAX_PROPERTIES) return -EINVAL; - type = elements[HP_WMI_PROPERTY_SIZE].type; + element = &elements[HP_WMI_PROPERTY_SIZE]; + type = element->type; switch (type) { case ACPI_TYPE_INTEGER: is_new = true; last_prop = HP_WMI_PROPERTY_RATE_UNITS; break; + case ACPI_TYPE_BUFFER: + if (!is_raw_wmi_string(element->buffer.pointer, + element->buffer.length)) + return -EINVAL; + fallthrough; + case ACPI_TYPE_STRING: is_new = false; last_prop = HP_WMI_PROPERTY_CURRENT_READING; @@ -631,6 +637,10 @@ static int check_numeric_sensor_wobj(const union acpi_object *wobj, for (i = 0; i < elem_count && prop <= last_prop; i++, prop++) { type = elements[i].type; valid_type = hp_wmi_property_map[prop]; + if (type == ACPI_TYPE_BUFFER && + is_raw_wmi_string(elements[i].buffer.pointer, + elements[i].buffer.length)) + type = ACPI_TYPE_STRING; if (type != valid_type) return -EINVAL; @@ -651,6 +661,10 @@ static int check_numeric_sensor_wobj(const union acpi_object *wobj, /* PossibleStates[0] has already been type-checked. */ for (j = 0; i + 1 < elem_count && j + 1 < count; j++) { type = elements[++i].type; + if (type == ACPI_TYPE_BUFFER && + is_raw_wmi_string(elements[i].buffer.pointer, + elements[i].buffer.length)) + type = ACPI_TYPE_STRING; if (type != valid_type) return -EINVAL; } From 7f4a5ec6258fd7c92633ec4b0493fc51166d9398 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 12 Sep 2026 14:08:30 -0400 Subject: [PATCH 1851/2366] net/sched: codel: bound the dropping loop per dequeue call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CoDel control law schedules the next drop one interval/sqrt(count) after the previous drop, using the configured interval (codel_params.interval). For very small intervals the scheduled step rounds down to zero, so the dropping loop in codel_dequeue() never advances and drains the entire backlog under the qdisc lock in one call - an unprivileged user can trigger a soft lockup this way. Fix in the shared codel code used by both codel and fq_codel: 1. Make the control-law step at least 1 tick so the dropping loop always moves forward. 2. Cap the dropping loop at CODEL_MAX_DROPS_PER_DEQUEUE (256) drops per codel_dequeue() call, resyncing drop_next to now when the cap is hit: the catch-up owed to the loop grows with the idle gap and the backlog, which no interval threshold can bound. This is a deliberate behaviour change after long idle gaps. The cap applies to fq_codel (4b549a2ef4be) and the mac80211 TXQ path (fixed interval, cap only). The target sojourn delay (codel_params.target) is not validated: it does not feed the control law, so a sub-tick value is aggressive rather than deadlock-prone. Conditions to recreate the bug: - tc qdisc add dev lo root handle 1: tbf rate 1kbit burst 2kb limit 1000000 - tc qdisc add dev lo parent 1:1 handle 10: codel interval 2us target 1ms noecn limit 1000000 (same for fq_codel) - unpatched kernel: tc accepts it; a UDP flood under the 1kbit tbf soft-lockups (watchdog: BUG: soft lockup) while one codel_dequeue() call drops the backlog under the qdisc lock - patched kernel: same setup, at most 256 drops per dequeue call, no soft lockup Testing: claim reproducer and interval 2us/3us variants run clean; tdc qdisc category passes (see the selftests patch). Fixes: 76e3cc126bb2 ("codel: Controlled Delay AQM") Reported-by: Vega Reviewed-by: Eric Dumazet Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim Reviewed-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/QDISC-1L5H.v1.20260912080102@mojatatu.com Signed-off-by: Jakub Kicinski --- include/net/codel.h | 5 +++++ include/net/codel_impl.h | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/net/codel.h b/include/net/codel.h index aa80f744826c..183d43c2bd43 100644 --- a/include/net/codel.h +++ b/include/net/codel.h @@ -140,6 +140,11 @@ struct codel_vars { /* needed shift to get a Q0.32 number from rec_inv_sqrt */ #define REC_INV_SQRT_SHIFT (32 - REC_INV_SQRT_BITS) +/* Cap on drops per codel_dequeue() call: the loop's work depends on the + * idle gap and backlog, both outside our control; resync when exceeded. + */ +#define CODEL_MAX_DROPS_PER_DEQUEUE 256 + /** * struct codel_stats - contains codel shared variables and stats * @maxpacket: largest packet we've seen so far diff --git a/include/net/codel_impl.h b/include/net/codel_impl.h index 2c1f0ec309e9..8f26132d45b7 100644 --- a/include/net/codel_impl.h +++ b/include/net/codel_impl.h @@ -93,12 +93,17 @@ static void codel_Newton_step(struct codel_vars *vars) * CoDel control_law is t + interval/sqrt(count) * We maintain in rec_inv_sqrt the reciprocal value of sqrt(count) to avoid * both sqrt() and divide operation. + * + * Clamp the increment to at least 1 tick: a very small interval (or a + * large count) can truncate it to zero, stalling the dropping loop. */ static codel_time_t codel_control_law(codel_time_t t, codel_time_t interval, u32 rec_inv_sqrt) { - return t + reciprocal_scale(interval, rec_inv_sqrt << REC_INV_SQRT_SHIFT); + return t + max_t(u32, 1, + reciprocal_scale(interval, + rec_inv_sqrt << REC_INV_SQRT_SHIFT)); } static bool codel_should_drop(const struct sk_buff *skb, @@ -154,6 +159,7 @@ static struct sk_buff *codel_dequeue(void *ctx, codel_skb_dequeue_t dequeue_func) { struct sk_buff *skb = dequeue_func(vars, ctx); + unsigned int drops = 0; codel_time_t now; bool drop; @@ -180,6 +186,14 @@ static struct sk_buff *codel_dequeue(void *ctx, */ while (vars->dropping && codel_time_after_eq(now, vars->drop_next)) { + if (++drops > CODEL_MAX_DROPS_PER_DEQUEUE) { + /* fell far behind the schedule */ + WRITE_ONCE(vars->drop_next, + codel_control_law(now, + params->interval, + vars->rec_inv_sqrt)); + break; + } /* dont care of possible wrap * since there is no more divide. */ From f6fb2ac5e19ae4b66112a698050db80e51f841c3 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 12 Sep 2026 14:08:31 -0400 Subject: [PATCH 1852/2366] selftests/tc-testing: add codel/fq_codel interval boundary cases Add tdc cases locking the codel/fq_codel small-interval uAPI after the dropping-loop bound (previous patch): sub-tick and two-tick intervals are ACCEPTED (the loop bound makes them safe), the 1024us boundary is accepted, and a sub-tick target sojourn delay is accepted (it does not participate in the control law): codel: 6e44/a8c3/a695/9793 - interval 1us/3us/1024us and target 1us accepted (rendered 0us/2us/1.02ms/0us by tc) fq_codel: 1b4d/3540/49c5/3e0f - interval 1us/3us/1024us and target 1us accepted The positive cases match the full rendered qdisc line (tc renders interval 1us as 0us, 3us as 2us, 1024us as 1.02ms), mirroring the existing tests in these files. These cases do not test the dropping-loop bound itself: tdc cannot observe per-dequeue drop counts. c797 (fq_codel target 1 interval 1) passes unmodified on the patched kernel, which is the uAPI evidence for the previous patch. Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-1L5H.v1.20260912080102@mojatatu.com.2 Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/qdiscs/codel.json | 72 +++++++++++++++++++ .../tc-testing/tc-tests/qdiscs/fq_codel.json | 72 +++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/codel.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/codel.json index 6d515d0e5ed6..a894e6f0e267 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/codel.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/codel.json @@ -213,5 +213,77 @@ "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1p target 5ms interval 100ms", "matchCount": "1", "teardown": ["$TC qdisc del dev $DEV1 handle 1: root"] + }, + { + "id": "6e44", + "name": "Create CODEL with 1us interval, accepted (sub-tick, uAPI locked)", + "category": [ + "qdisc", + "codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root codel interval 1us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1000p target 5ms interval 0us", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "a8c3", + "name": "Create CODEL with 3us interval, accepted (two ticks)", + "category": [ + "qdisc", + "codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root codel interval 3us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1000p target 5ms interval 2us", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "a695", + "name": "Create CODEL with 1024us interval boundary accepted", + "category": [ + "qdisc", + "codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root codel interval 1024us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1000p target 5ms interval 1.02ms", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "9793", + "name": "Create CODEL with 1us target, accepted (target not in control law)", + "category": [ + "qdisc", + "codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root codel target 1us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc codel 1: root refcnt [0-9]+ limit 1000p target 0us interval 100ms", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] } ] diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/fq_codel.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/fq_codel.json index 4ce62b857fd7..de6a1b8d954a 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/fq_codel.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/fq_codel.json @@ -316,5 +316,77 @@ "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 1p flows 1024 quantum.*target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64", "matchCount": "1", "teardown": ["$TC qdisc del dev $DEV1 handle 1: root"] + }, + { + "id": "1b4d", + "name": "Create FQ_CODEL with 1us interval, accepted (sub-tick, uAPI locked)", + "category": [ + "qdisc", + "fq_codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root fq_codel interval 1us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 10240p flows 1024 quantum [0-9]+ target 5ms interval 0us memory_limit 32Mb ecn drop_batch 64", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "3540", + "name": "Create FQ_CODEL with 3us interval, accepted (two ticks)", + "category": [ + "qdisc", + "fq_codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root fq_codel interval 3us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 10240p flows 1024 quantum [0-9]+ target 5ms interval 2us memory_limit 32Mb ecn drop_batch 64", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "49c5", + "name": "Create FQ_CODEL with 1024us interval boundary accepted", + "category": [ + "qdisc", + "fq_codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root fq_codel interval 1024us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 10240p flows 1024 quantum [0-9]+ target 5ms interval 1.02ms memory_limit 32Mb ecn drop_batch 64", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] + }, + { + "id": "3e0f", + "name": "Create FQ_CODEL with 1us target, accepted (target not in control law)", + "category": [ + "qdisc", + "fq_codel" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root fq_codel target 1us", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc fq_codel 1: root refcnt [0-9]+ limit 10240p flows 1024 quantum [0-9]+ target 0us interval 100ms memory_limit 32Mb ecn drop_batch 64", + "matchCount": "1", + "teardown": ["$TC qdisc del dev $DUMMY handle 1: root"] } ] From 88f113634028ca90a857031837d8061d1a9e1a7b Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:05:11 +0000 Subject: [PATCH 1853/2366] watchdog: sp5100_tco: Fix pci_dev reference leak in sp5100_tco_init() sp5100_tco_init() stores the PCI device matched by for_each_pci_dev() in the global sp5100_tco_pci and keeps its reference for the lifetime of the driver, but neither sp5100_tco_exit() nor the error paths of sp5100_tco_init() call pci_dev_put(), leaking the reference on driver registration failure and on every module load/unload cycle. Drop the reference when the platform driver or device registration fails and when the module is unloaded. Fixes: 15e28bf13008 ("watchdog: Add support for sp5100 chipset TCO") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260916170511.2086199-1-vulab@iscas.ac.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/sp5100_tco.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 7e99c3b1f367..72ad59649ccc 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -605,8 +605,10 @@ static int __init sp5100_tco_init(void) pr_info("SP5100/SB800 TCO WatchDog Timer Driver\n"); err = platform_driver_register(&sp5100_tco_driver); - if (err) + if (err) { + pci_dev_put(sp5100_tco_pci); return err; + } sp5100_tco_platform_device = platform_device_register_simple(TCO_DRIVER_NAME, -1, NULL, 0); @@ -619,6 +621,7 @@ static int __init sp5100_tco_init(void) unreg_platform_driver: platform_driver_unregister(&sp5100_tco_driver); + pci_dev_put(sp5100_tco_pci); return err; } @@ -626,6 +629,7 @@ static void __exit sp5100_tco_exit(void) { platform_device_unregister(sp5100_tco_platform_device); platform_driver_unregister(&sp5100_tco_driver); + pci_dev_put(sp5100_tco_pci); } module_init(sp5100_tco_init); From a9ce4053dc945c5372dedba5017ee675b30dc0c5 Mon Sep 17 00:00:00 2001 From: Mark Amirkan Date: Sun, 13 Sep 2026 17:14:09 -0700 Subject: [PATCH 1854/2366] net: lan743x: fix RX checksum use-after-free lan743x_rx_process_buffer() adds each non-first receive buffer to the head skb's frag_list. On the last descriptor, lan743x_rx_trim_skb() linearizes the head and frees the fragment skb metadata. The checksum-success path then writes ip_summed through the local skb pointer, which still points to the final fragment. This causes a use-after-free write when a packet spans more than one receive buffer. Set ip_summed on the surviving head skb instead. Multi-buffer receive can occur after a live MTU increase because existing ring entries keep their old buffer size until they are replenished. A KUnit test invoking lan743x_rx_process_buffer() with a two-buffer packet produced a one-byte KASAN use-after-free write before this change. The same test passed after the change. The driver object also builds with W=1. This was not tested on physical LAN743x hardware. Fixes: cd6910501cfd ("net: lan743x: Add support for Rx IP & TCP checksum offload") Cc: stable@vger.kernel.org Signed-off-by: Mark Amirkan Reviewed-by: Chenguang Zhao Link: https://patch.msgid.link/20260913-b4-send-lan743x-uaf-v1-1-73d563d08ba9@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/lan743x_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microchip/lan743x_main.c b/drivers/net/ethernet/microchip/lan743x_main.c index 24ae56a3c9ed..82d3ec20ba25 100644 --- a/drivers/net/ethernet/microchip/lan743x_main.c +++ b/drivers/net/ethernet/microchip/lan743x_main.c @@ -2604,7 +2604,7 @@ static int lan743x_rx_process_buffer(struct lan743x_rx *rx) rx->adapter->netdev); if (rx->adapter->netdev->features & NETIF_F_RXCSUM) { if (!is_ice && !is_tce && !is_icsm) - skb->ip_summed = CHECKSUM_UNNECESSARY; + rx->skb_head->ip_summed = CHECKSUM_UNNECESSARY; } netdev_dbg(netdev, "sending %d byte frame to OS", rx->skb_head->len); From 33ff111d7ba3beb86e28938d6382bb5beabd865a Mon Sep 17 00:00:00 2001 From: Mark Amirkan Date: Sun, 13 Sep 2026 10:28:08 +0000 Subject: [PATCH 1855/2366] net/packet: clear RX owner on VNET header error Commit 61fad6816fc1 ("net/packet: tpacket_rcv: avoid a producer race condition") added rx_owner_map and made tpacket_rcv() claim a V1 or V2 ring slot before converting the virtio-net header. If the conversion fails, the drop path leaves the slot claimed. With a one-frame TPACKET_V2 ring, an unsupported UDP GSO packet leaves the only slot unavailable, so the ring also drops the next valid packet. Clear the ownership bit on this error path. TPACKET_V3 already clears its block state here. Fixes: 61fad6816fc1 ("net/packet: tpacket_rcv: avoid a producer race condition") Cc: stable@vger.kernel.org Signed-off-by: Mark Amirkan Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260913-b4-send-packet-vnet-v1-1-5545ffb528ae@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 76bde7906d49..50cae32ae269 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -2384,7 +2384,9 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev, virtio_net_hdr_from_skb(skb, h.raw + macoff - sizeof(struct virtio_net_hdr), vio_le(), true, 0)) { - if (po->tp_version == TPACKET_V3) + if (po->tp_version <= TPACKET_V2) + __clear_bit(slot_id, po->rx_ring.rx_owner_map); + else prb_clear_blk_fill_status(&po->rx_ring); goto drop_n_account; } From 60404266ef3e0a1cd8f7a164060e0c83efb72f4b Mon Sep 17 00:00:00 2001 From: Mark Amirkan Date: Sun, 13 Sep 2026 10:30:05 +0000 Subject: [PATCH 1856/2366] mptcp: return sk_wait_data() errors from recvmsg() Commit 581302298524 ("mptcp: error out earlier on disconnect") made mptcp_recvmsg() stop when sk_wait_data() returns an error. The error is stored in err, but the function then jumps to a path which returns copied. When no data was copied, recvmsg() therefore returns zero and reports a false EOF. Store the result in copied, which is the value returned by the function. This also keeps the usual partial-read result when data was copied before the error. A recvmsg() blocked in one thread reproduces the issue when another thread disconnects the same MPTCP socket with connect(AF_UNSPEC). Before this change recvmsg() returns zero; afterwards it returns -EPIPE. Fixes: 581302298524 ("mptcp: error out earlier on disconnect") Cc: stable@vger.kernel.org Signed-off-by: Mark Amirkan Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260913-b4-send-mptcp-recv-error-v1-1-4eaa3684a8b8@gmail.com Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 0098e2830931..8dc25ef1542c 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2459,7 +2459,7 @@ static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, mptcp_cleanup_rbuf(msk, copied); err = sk_wait_data(sk, &timeo, last); if (err < 0) { - err = copied ? : err; + copied = copied ? : err; goto out_err; } } From 37213e61120297920ae4c937fcb326a360da5084 Mon Sep 17 00:00:00 2001 From: Mark Amirkan Date: Sun, 13 Sep 2026 10:31:08 +0000 Subject: [PATCH 1857/2366] net/packet: avoid truncating TPACKET_V3 private size tpacket_req3.tp_sizeof_priv is an unsigned int, and packet_set_ring() validates the full value against the block size. init_prb_bdqc() then stores it in the unsigned short blk_sizeof_priv field. Commit 2b6867c2ce76 ("net/packet: fix overflow in check for priv area size") fixed the validation arithmetic, but an accepted value above USHRT_MAX still narrows when it is stored. For a 131072-byte block, tp_sizeof_priv=65536 is valid. The narrowing makes offset_to_first_pkt 48 instead of 65584, so packet records can be placed in the private area that userspace asked the kernel to preserve. blk_sizeof_priv is internal state, so widen it to hold the validated UAPI value. Fixes: f6fb8f100b80 ("af-packet: TPACKET_V3 flexible buffer implementation.") Cc: stable@vger.kernel.org Signed-off-by: Mark Amirkan Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260913-b4-send-packet-private-v1-1-925eab2cd388@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/packet/internal.h b/net/packet/internal.h index b76e645cd78d..f5c8cd0eed2c 100644 --- a/net/packet/internal.h +++ b/net/packet/internal.h @@ -21,7 +21,7 @@ struct tpacket_kbdq_core { unsigned int hdrlen; unsigned char reset_pending_on_curr_blk; unsigned short kactive_blk_num; - unsigned short blk_sizeof_priv; + unsigned int blk_sizeof_priv; unsigned short version; From 150dba2c69e93302af24a0c868eebe4871e2e107 Mon Sep 17 00:00:00 2001 From: Farhad Alemi Date: Sat, 12 Sep 2026 07:40:09 +0000 Subject: [PATCH 1858/2366] net: remove WARN_ON_ONCE() from the dev_fill_forward_path() loop check ipip_fill_forward_path() and ip6_tnl_fill_forward_path() look up the route to the tunnel's remote endpoint and set ctx->dev to its device, which is the tunnel itself when that route resolves back to the tunnel. dev_fill_forward_path() then makes no progress and trips WARN_ON_ONCE(last_dev == ctx->dev) as soon as a flowtable tries to offload a flow through the tunnel. That routing loop is a configuration any CAP_NET_ADMIN user can set up, and ip_tunnel_xmit() and ip6_tnl_xmit() already treat it as a tx error, so remove the warning and just fail the walk, as commit 008e7a7c293b ("net: remove WARN_ON_ONCE when accessing forward path array") did for the path stack overflow. Fixes: ab427db17885 ("netfilter: flowtable: Add IPIP rx sw acceleration") Fixes: d98103575dcd ("netfilter: flowtable: Add IP6IP6 rx sw acceleration") Closes: https://lore.kernel.org/all/CA+0ovCgaRvbd0Udj70b2xxG8Cx3CaCpNhnf1V4RWQuDveZYZhA@mail.gmail.com/ Suggested-by: Pablo Neira Ayuso Signed-off-by: Farhad Alemi Reviewed-by: Xuanqiang Luo Link: https://patch.msgid.link/CA+0ovCgKDOk+Bg6Gh5Lwx94u_jJjQ30-vY1JcY2BYfhnWJJbPA@mail.gmail.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index ecfbd72d5d1a..c67900354fa6 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -789,7 +789,7 @@ int dev_fill_forward_path(struct net_device_path_ctx *ctx, goto err_out; stack->num_paths++; - if (WARN_ON_ONCE(last_dev == ctx->dev)) + if (last_dev == ctx->dev) goto err_out; } From 5ae916fabca141b79b32e2e57f3c915c0f1e1b2e Mon Sep 17 00:00:00 2001 From: Yige Jiang Date: Sun, 13 Sep 2026 14:41:02 +0800 Subject: [PATCH 1859/2366] net: netsec: fix device_node reference leak on phy_np netsec_of_probe() takes a reference on the PHY device_node with of_parse_phandle() and stores it in priv->phy_np, but the driver never drops it. One device_node reference is leaked per probe, on the success path as well as on every error path reached after netsec_of_probe(). Neither consumer takes ownership. of_mdio_parse_addr() is a static inline taking a const struct device_node * that only reads the "reg" property. of_phy_connect() borrows as well: of_phy_get_and_connect() in drivers/net/mdio/of_mdio.c brackets its own call with of_node_get() at :364 and of_node_put() at :373, which would be a double put if of_phy_connect() consumed the reference. The node is still in use at netsec_netdev_open() time, where it is passed to of_phy_connect(), so it has device lifetime. Release it at the probe error label, which every failure path after the acquire funnels through, and in netsec_remove(). Both releases precede free_netdev(), since priv is netdev_priv(ndev). The ACPI probe path leaves priv->phy_np NULL and of_node_put(NULL) is a no-op. There is no end-user visible symptom on currently supported platforms: a device_node is only freed once OF_DYNAMIC is enabled and the node has been detached, so on a static device tree the imbalance is inert. It is observable as a refcount that grows across bind/unbind cycles, and would matter under device tree overlays. Found by static analysis of reference acquire/release pairing rather than from a runtime report. No reproducer was produced and the change has not been runtime tested; it is compile-tested only (arm64, CONFIG_SNI_NETSEC=m via COMPILE_TEST). Fixes: 533dd11a12f6 ("net: socionext: Add Synquacer NetSec driver") Signed-off-by: Yige Jiang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260913064102.37452-1-yigejiang86@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/socionext/netsec.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/socionext/netsec.c b/drivers/net/ethernet/socionext/netsec.c index d14a6584473c..79a0a324c921 100644 --- a/drivers/net/ethernet/socionext/netsec.c +++ b/drivers/net/ethernet/socionext/netsec.c @@ -2149,6 +2149,7 @@ static int netsec_probe(struct platform_device *pdev) pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); free_ndev: + of_node_put(priv->phy_np); free_netdev(ndev); dev_err(&pdev->dev, "init failed\n"); @@ -2166,6 +2167,7 @@ static void netsec_remove(struct platform_device *pdev) netif_napi_del(&priv->napi); pm_runtime_disable(&pdev->dev); + of_node_put(priv->phy_np); free_netdev(priv->ndev); } From 490599ab23134962a6d18a024e84541d77bdb999 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 14 Sep 2026 19:23:27 -0700 Subject: [PATCH 1860/2366] eth: fbnic: ring the doorbell if a burst ends in a drop fbnic_tx_map() skips the doorbell write, and the completion request, for every packet handed to it with xmit_more set, counting on the packet which ends the burst to publish them all. When that packet is dropped instead - skb_put_padto(), skb_cow_head() or a DMA mapping failure - nothing rings. The descriptors of the preceding packets stay invisible to the HW until the next transmit on that queue, which for a burst-then-idle workload may never come. Remember the meta descriptor of the last packet left without a doorbell and flush it from the error paths. The completion request has to be set on that descriptor rather than simply writing the tail, otherwise the HW would transmit the packets but never report a head, and the ring would fill up and stall for good. This is very similar to Joe's recent series of fixes for bnxt. Not seen in real life, reproduced under QEMU with failure injection. Fixes: 9a57bacd574b ("eth: fbnic: Add basic Tx handling") Reviewed-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260915022327.913218-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 42 +++++++++++++++----- drivers/net/ethernet/meta/fbnic/fbnic_txrx.h | 9 ++++- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index 401f8b8ae1ca..e7918d3f6aba 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -311,6 +311,29 @@ fbnic_rx_csum(u64 rcd, struct sk_buff *skb, struct fbnic_ring *rcq, } } +static void fbnic_tx_doorbell(struct fbnic_ring *ring, __le64 *meta) +{ + *meta |= cpu_to_le64(FBNIC_TWD_FLAG_REQ_COMPLETION); + ring->deferred_meta = -1; + + /* Force DMA writes to flush before writing to tail */ + dma_wmb(); + + writel(ring->tail, ring->doorbell); +} + +/* Packets handed to us with xmit_more set are left in the ring without a + * doorbell, and without a completion request, in the expectation that the + * packet ending the burst will ring for all of them. If that packet gets + * dropped instead we have to ring here, otherwise the descriptors sit in + * the ring until the next transmit, which may never come. + */ +static void fbnic_tx_flush_doorbell(struct fbnic_ring *ring) +{ + if (ring->deferred_meta >= 0) + fbnic_tx_doorbell(ring, &ring->desc[ring->deferred_meta]); +} + static bool fbnic_tx_map(struct fbnic_ring *ring, struct sk_buff *skb, __le64 *meta) { @@ -378,14 +401,10 @@ fbnic_tx_map(struct fbnic_ring *ring, struct sk_buff *skb, __le64 *meta) /* Verify there is room for another packet */ fbnic_maybe_stop_tx(skb->dev, ring, FBNIC_MAX_SKB_DESC); - if (fbnic_tx_sent_queue(skb, ring)) { - *meta |= cpu_to_le64(FBNIC_TWD_FLAG_REQ_COMPLETION); - - /* Force DMA writes to flush before writing to tail */ - dma_wmb(); - - writel(tail, ring->doorbell); - } + if (fbnic_tx_sent_queue(skb, ring)) + fbnic_tx_doorbell(ring, meta); + else + ring->deferred_meta = meta - ring->desc; return false; dma_error: @@ -425,8 +444,10 @@ fbnic_xmit_frame_ring(struct sk_buff *skb, struct fbnic_ring *ring) * otherwise try next time */ desc_needed = skb_shinfo(skb)->nr_frags + 10; - if (fbnic_maybe_stop_tx(skb->dev, ring, desc_needed)) + if (fbnic_maybe_stop_tx(skb->dev, ring, desc_needed)) { + fbnic_tx_flush_doorbell(ring); return NETDEV_TX_BUSY; + } *meta = cpu_to_le64(FBNIC_TWD_FLAG_DEST_MAC); @@ -447,6 +468,8 @@ fbnic_xmit_frame_ring(struct sk_buff *skb, struct fbnic_ring *ring) err_free: dev_kfree_skb_any(skb); err_count: + fbnic_tx_flush_doorbell(ring); + u64_stats_update_begin(&ring->stats.syncp); ring->stats.dropped++; u64_stats_update_end(&ring->stats.syncp); @@ -2491,6 +2514,7 @@ static void fbnic_enable_twq0(struct fbnic_ring *twq) fbnic_ring_wr32(twq, FBNIC_QUEUE_TWQ0_CTL, FBNIC_QUEUE_TWQ_CTL_RESET); twq->tail = 0; twq->head = 0; + twq->deferred_meta = -1; /* Store descriptor ring address and size */ fbnic_ring_wr32(twq, FBNIC_QUEUE_TWQ0_BAL, lower_32_bits(twq->dma)); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h index e03c9d2c38dc..f5899446dcc5 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h @@ -128,9 +128,14 @@ struct fbnic_ring { /* Rx BDQs only */ struct page_pool *page_pool; - /* Deferred_head is used to cache the head for TWQ1 if + /* TWQ0 only, index of the meta descriptor of the last packet + * placed in the ring without ringing the doorbell, -1 if the + * doorbell is in sync with the tail. + */ + s32 deferred_meta; + + /* TCQ only, used to cache the head for TWQ1 if * an attempt is made to clean TWQ1 with zero napi_budget. - * We do not use it for any other ring. */ s32 deferred_head; }; From 9ed55f3dbef4f4adfe65eb03b0c35c53229a8490 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 15 Sep 2026 04:30:54 +0000 Subject: [PATCH 1861/2366] net: lock the socket in sock_gettstamp() sk->sk_flags must only be changed while holding the socket lock, because sock_set_flag() and sock_reset_flag() use non atomic operations (__set_bit() and __clear_bit()). sock_gettstamp() is one of the last places where a bit of sk->sk_flags is changed from a syscall without owning the socket lock, through sock_enable_timestamp(sk, SOCK_TIMESTAMP). sk_set_memalloc() and sk_clear_memalloc() also change sk->sk_flags without the socket lock, but their callers (nbd, iscsi_tcp, nvme-tcp, sunrpc, wireguard) need a careful audit, this will be addressed in a separate patch. Jungwoo Lee and Wongi Lee reported an UDP socket use-after-free caused by this bug: a SIOCGSTAMPNS_NEW ioctl racing with bind() can cancel the SOCK_RCU_FREE bit that udp_lib_get_port() just set, because both threads perform a read-modify-write on the same word. CPU 0 (bind) CPU 1 (SIOCGSTAMPNS_NEW) -------------------------------- ---------------------------- read sk_flags = F read sk_flags = F compute F | BIT(SOCK_RCU_FREE) compute F | BIT(SOCK_TIMESTAMP) store F | BIT(SOCK_RCU_FREE) sk_add_node_rcu(sk, ...) store F | BIT(SOCK_TIMESTAMP) After the lost update, SOCK_RCU_FREE is clear while the socket is visible to lockless UDP receive lookups. sk_destruct() then frees the socket immediately instead of waiting for a RCU grace period, while the receive path still holds a reference-less pointer to it: BUG: KASAN: slab-use-after-free in ipv4_pktinfo_prepare+0x30/0x410 Read of size 8 at addr ffff888008806610 by task exploit/207 CPU: 0 UID: 1000 PID: 207 Comm: exploit Not tainted 6.12.95+ #1 ipv4_pktinfo_prepare+0x30/0x410 udp_queue_rcv_one_skb+0x51c/0x1180 udp_unicast_rcv_skb+0x109/0x350 ip_protocol_deliver_rcu+0x14b/0x310 ip_local_deliver_finish+0x29d/0x390 ip_local_deliver+0x24d/0x2a0 Only grab the socket lock when SOCK_TIMESTAMP has to be set, to keep the common case lockless. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Jungwoo Lee Reported-by: Wongi Lee Signed-off-by: Eric Dumazet Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260915043055.3441600-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/sock.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/core/sock.c b/net/core/sock.c index fa60b7494c58..d5e302e21e85 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -3911,7 +3911,14 @@ int sock_gettstamp(struct socket *sock, void __user *userstamp, struct sock *sk = sock->sk; struct timespec64 ts; - sock_enable_timestamp(sk, SOCK_TIMESTAMP); + /* sk->sk_flags must only be changed under the socket lock, + * because sock_set_flag() uses non atomic operations. + */ + if (!sock_flag(sk, SOCK_TIMESTAMP)) { + lock_sock(sk); + sock_enable_timestamp(sk, SOCK_TIMESTAMP); + release_sock(sk); + } ts = ktime_to_timespec64(sock_read_timestamp(sk)); if (ts.tv_sec == -1) return -ENOENT; From 400cb663ca019bae6eb878f06f1094ddf7c0b0df Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 14:48:49 +0800 Subject: [PATCH 1862/2366] watchdog: digicolor: Avoid division by zero clk_get_rate() could return 0. Avoid a division by zero panic. Since get_timeleft() cannot propagate errors, check the clock rate early in probe() and cache the rate in the driver data as it is unlikely to change at runtime. Fixes: 336694a01dae ("watchdog: digicolor: driver for Conexant Digicolor CX92755 SoC") Cc: stable@vger.kernel.org Signed-off-by: Tzung-Bi Shih Acked-by: Baruch Siach Link: https://patch.msgid.link/20260913064851.8239-2-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/digicolor_wdt.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/watchdog/digicolor_wdt.c b/drivers/watchdog/digicolor_wdt.c index 073d37867f47..de1a3267a972 100644 --- a/drivers/watchdog/digicolor_wdt.c +++ b/drivers/watchdog/digicolor_wdt.c @@ -25,6 +25,7 @@ struct dc_wdt { void __iomem *base; struct clk *clk; spinlock_t lock; + unsigned long rate; }; static unsigned timeout; @@ -61,7 +62,7 @@ static int dc_wdt_start(struct watchdog_device *wdog) { struct dc_wdt *wdt = watchdog_get_drvdata(wdog); - dc_wdt_set(wdt, wdog->timeout * clk_get_rate(wdt->clk)); + dc_wdt_set(wdt, wdog->timeout * wdt->rate); return 0; } @@ -79,7 +80,7 @@ static int dc_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t) { struct dc_wdt *wdt = watchdog_get_drvdata(wdog); - dc_wdt_set(wdt, t * clk_get_rate(wdt->clk)); + dc_wdt_set(wdt, t * wdt->rate); wdog->timeout = t; return 0; @@ -90,7 +91,7 @@ static unsigned int dc_wdt_get_timeleft(struct watchdog_device *wdog) struct dc_wdt *wdt = watchdog_get_drvdata(wdog); uint32_t count = readl_relaxed(wdt->base + TIMER_A_COUNT); - return count / clk_get_rate(wdt->clk); + return count / wdt->rate; } static const struct watchdog_ops dc_wdt_ops = { @@ -130,7 +131,11 @@ static int dc_wdt_probe(struct platform_device *pdev) wdt->clk = devm_clk_get(dev, NULL); if (IS_ERR(wdt->clk)) return PTR_ERR(wdt->clk); - dc_wdt_wdd.max_timeout = U32_MAX / clk_get_rate(wdt->clk); + + wdt->rate = clk_get_rate(wdt->clk); + if (!wdt->rate) + return -EINVAL; + dc_wdt_wdd.max_timeout = U32_MAX / wdt->rate; dc_wdt_wdd.timeout = dc_wdt_wdd.max_timeout; dc_wdt_wdd.parent = dev; From 5af7d2cbd20f893def03c8310a460ade66a5d822 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 14:48:50 +0800 Subject: [PATCH 1863/2366] watchdog: rtd119x: Avoid division by zero clk_get_rate() could return 0. Avoid a division by zero panic. Fixes: 2bdf6acbfead ("watchdog: Add Realtek RTD1295") Cc: stable@vger.kernel.org Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260913064851.8239-3-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/rtd119x_wdt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/rtd119x_wdt.c b/drivers/watchdog/rtd119x_wdt.c index 984905695dde..0bfadb58917b 100644 --- a/drivers/watchdog/rtd119x_wdt.c +++ b/drivers/watchdog/rtd119x_wdt.c @@ -98,6 +98,7 @@ static int rtd119x_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rtd119x_watchdog_device *data; + unsigned long rate; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -111,10 +112,14 @@ static int rtd119x_wdt_probe(struct platform_device *pdev) if (IS_ERR(data->clk)) return PTR_ERR(data->clk); + rate = clk_get_rate(data->clk); + if (!rate) + return -EINVAL; + data->wdt_dev.info = &rtd119x_wdt_info; data->wdt_dev.ops = &rtd119x_wdt_ops; data->wdt_dev.timeout = 120; - data->wdt_dev.max_timeout = 0xffffffff / clk_get_rate(data->clk); + data->wdt_dev.max_timeout = 0xffffffff / rate; data->wdt_dev.min_timeout = 1; data->wdt_dev.parent = dev; From 6274281c41efa8dd1aa5234c59ad904ff89d7af4 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 14:48:51 +0800 Subject: [PATCH 1864/2366] watchdog: rzv2h: Avoid division by zero clk_get_rate() could return 0. Avoid a division by zero panic. Fixes: f6febd0a30b6 ("watchdog: Add Watchdog Timer driver for RZ/V2H(P)") Cc: stable@vger.kernel.org Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260913064851.8239-4-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/rzv2h_wdt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/rzv2h_wdt.c b/drivers/watchdog/rzv2h_wdt.c index 3b6abb66a1da..83540dd9a37b 100644 --- a/drivers/watchdog/rzv2h_wdt.c +++ b/drivers/watchdog/rzv2h_wdt.c @@ -278,6 +278,7 @@ static int rzv2h_wdt_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct rzv2h_wdt_priv *priv; struct clk *count_clk; + unsigned long rate; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -314,8 +315,12 @@ static int rzv2h_wdt_probe(struct platform_device *pdev) return dev_err_probe(dev, -EINVAL, "Invalid count source\n"); } + rate = clk_get_rate(count_clk); + if (!rate) + return dev_err_probe(dev, -EINVAL, "Invalid clock rate\n"); + priv->wdev.max_hw_heartbeat_ms = (MILLI * priv->of_data->timeout_cycles * - priv->of_data->cks_div) / clk_get_rate(count_clk); + priv->of_data->cks_div) / rate; dev_dbg(dev, "max hw timeout of %dms\n", priv->wdev.max_hw_heartbeat_ms); ret = devm_pm_runtime_enable(dev); From 1d9763f34a85680db1e8233d654fdb85e5f897cc Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 00:33:34 +0800 Subject: [PATCH 1865/2366] watchdog: msc313e: Propagate error code in resume() If msc313e_wdt_start() fails during system resume, the error is currently ignored. Consequently, the watchdog isn't running without the user's knowledge. Propagate the error code and print a message if msc313e_wdt_start() fails. Signed-off-by: Tzung-Bi Shih Fixes: e9800b7994642 ("watchdog: Add Mstar MSC313e WDT driver") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260912163334.28636-1-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index 4a5cce2a16b1..9a400562d1ae 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -191,11 +191,15 @@ static int __maybe_unused msc313e_wdt_suspend(struct device *dev) static int __maybe_unused msc313e_wdt_resume(struct device *dev) { struct msc313e_wdt_priv *priv = dev_get_drvdata(dev); + int ret = 0; - if (watchdog_active(&priv->wdev) || watchdog_hw_running(&priv->wdev)) - msc313e_wdt_start(&priv->wdev); + if (watchdog_active(&priv->wdev) || watchdog_hw_running(&priv->wdev)) { + ret = msc313e_wdt_start(&priv->wdev); + if (ret) + dev_err(dev, "Failed to restart watchdog (err=%d)\n", ret); + } - return 0; + return ret; } static SIMPLE_DEV_PM_OPS(msc313e_wdt_pm_ops, msc313e_wdt_suspend, msc313e_wdt_resume); From 22737cfced627ffcb4b5c36d63bb3d4476f63213 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Sun, 13 Sep 2026 14:51:26 +0800 Subject: [PATCH 1866/2366] watchdog: msc313e: Fix premature reset during timeout update Updating the 32-bit hardware timeout requires writing to two 16-bit registers sequentially. If the watchdog is actively running, this non-atomic update might trigger a premature system reset. Clear the watchdog counter before updating the registers to prevent the timer from timing out prematurely against an intermediate threshold. Fixes: e9800b799464 ("watchdog: Add Mstar MSC313e WDT driver") Cc: stable@vger.kernel.org Signed-off-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260913065126.8350-1-tzungbi@kernel.org Signed-off-by: Guenter Roeck --- drivers/watchdog/msc313e_wdt.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c index 9a400562d1ae..d62586f6e09d 100644 --- a/drivers/watchdog/msc313e_wdt.c +++ b/drivers/watchdog/msc313e_wdt.c @@ -46,6 +46,9 @@ static void msc313e_wdt_set_hw_timeout(struct msc313e_wdt_priv *priv, { u32 t = timeout * clk_get_rate(priv->clk); + /* Clear before to prevent premature reset during non-atomic updates. */ + writew(1, priv->base + REG_WDT_CLR); + writew(t & 0xffff, priv->base + REG_WDT_MAX_PRD_L); writew((t >> 16) & 0xffff, priv->base + REG_WDT_MAX_PRD_H); writew(1, priv->base + REG_WDT_CLR); @@ -76,6 +79,9 @@ static int msc313e_wdt_stop(struct watchdog_device *wdev) { struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev); + /* Clear before to prevent premature reset during non-atomic updates. */ + writew(1, priv->base + REG_WDT_CLR); + writew(0, priv->base + REG_WDT_MAX_PRD_L); writew(0, priv->base + REG_WDT_MAX_PRD_H); writew(0, priv->base + REG_WDT_CLR); From 5071122bf5a628494db16d98d253f622a5aab074 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Mon, 14 Sep 2026 14:23:53 +0800 Subject: [PATCH 1867/2366] watchdog: da9062: fix suspend/resume handling of HW_RUNNING watchdog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit da9062_wdt_suspend() and da9062_wdt_resume() only check watchdog_active(), when the watchdog is left running by the driver sets WDOG_HW_RUNNING in da9062_wdt_probe() but userspace never opens the device, so WDOG_ACTIVE remains cleared, the wdt_disable() will not be executed in da9062_wdt_suspend. In this case, the suspend callback is a no-op and the watchdog keeps counting during system suspend, leading to an unexpected system reset. Check WDOG_HW_RUNNING and wdt->wdd,can fix this issue. Fixes: f6c98b08381c7 ("watchdog: da9062: add power management ops") Cs: stable@vger.kernel.org Signed-off-by: Li Jun Link: https://patch.msgid.link/20260914062353.582205-1-lijun01@kylinos.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/da9062_wdt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/da9062_wdt.c b/drivers/watchdog/da9062_wdt.c index 426962547df1..4d558652e9e7 100644 --- a/drivers/watchdog/da9062_wdt.c +++ b/drivers/watchdog/da9062_wdt.c @@ -256,7 +256,7 @@ static int __maybe_unused da9062_wdt_suspend(struct device *dev) if (!wdt->use_sw_pm) return 0; - if (watchdog_active(wdd)) + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) return da9062_wdt_stop(wdd); return 0; @@ -270,7 +270,7 @@ static int __maybe_unused da9062_wdt_resume(struct device *dev) if (!wdt->use_sw_pm) return 0; - if (watchdog_active(wdd)) + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) return da9062_wdt_start(wdd); return 0; From 1dd85662fee6e2ac580b1c4f9a0c0a7ae6e31f0e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 14 Sep 2026 23:26:41 +0200 Subject: [PATCH 1868/2366] net: ethernet: cortina: Ack RX overrun interrupt correctly The RX overrun interrupt is reported in interrupt status register 4, but gmac_irq() acknowledges it using the RX descriptor error bit from status register 0. For GMAC0 this writes the GMAC1 overrun bit, while for GMAC1 the shift leaves no bit in the 32-bit register. Acknowledge the same per-port RX overrun bit that was detected. Fixes: 4d5ae32f5e1e ("net: ethernet: Add a driver for Gemini gigabit ethernet") Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260914-b4-gemini-ethernet-fixes-2-v2-1-5ab39a047b90@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cortina/gemini.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c index f08de623e6f7..2dd2fa801829 100644 --- a/drivers/net/ethernet/cortina/gemini.c +++ b/drivers/net/ethernet/cortina/gemini.c @@ -1798,7 +1798,7 @@ static irqreturn_t gmac_irq(int irq, void *data) if (val & (GMAC0_RX_OVERRUN_INT_BIT << (netdev->dev_id * 8))) { spin_lock(&geth->irq_lock); - writel(GMAC0_RXDERR_INT_BIT << (netdev->dev_id * 8), + writel(GMAC0_RX_OVERRUN_INT_BIT << (netdev->dev_id * 8), geth->base + GLOBAL_INTERRUPT_STATUS_4_REG); u64_stats_update_begin(&port->ir_stats_syncp); ++port->stats.rx_fifo_errors; From 8f0ca55016a7647109ae2bc91bcb346fc8b13785 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:07:04 +0000 Subject: [PATCH 1869/2366] watchdog: starfive-wdt: Fix runtime PM leak in starfive_wdt_pm_start() starfive_wdt_pm_start() takes a runtime PM reference with pm_runtime_get_sync(), which increments the usage counter even when it fails, and returns the error without dropping it again. The watchdog core does not invoke the stop callback when start fails, so the reference taken on the error path is leaked. Use pm_runtime_resume_and_get() instead, which keeps the usage counter balanced when the resume fails. Fixes: db728ea9c7be ("drivers: watchdog: Add StarFive Watchdog driver") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260916170704.2086331-1-vulab@iscas.ac.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/starfive-wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/starfive-wdt.c b/drivers/watchdog/starfive-wdt.c index af55adc4a3c6..5a3254c83d3b 100644 --- a/drivers/watchdog/starfive-wdt.c +++ b/drivers/watchdog/starfive-wdt.c @@ -371,7 +371,7 @@ static void starfive_wdt_stop(struct starfive_wdt *wdt) static int starfive_wdt_pm_start(struct watchdog_device *wdd) { struct starfive_wdt *wdt = watchdog_get_drvdata(wdd); - int ret = pm_runtime_get_sync(wdd->parent); + int ret = pm_runtime_resume_and_get(wdd->parent); if (ret < 0) return ret; From 42d1221d321e55afc7bba9109a77aaf5a817c8a3 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 31 Aug 2026 12:27:20 -0700 Subject: [PATCH 1870/2366] scsi: megaraid_sas: Protect megasas_get_ctrl_info() in megasas_resume() Protect the megasas_get_ctrl_info() call in megasas_resume() with instance->reset_mutex using scoped_guard(). megasas_get_ctrl_info() may release and reacquire instance->reset_mutex. Hence, calling this function without holding instance->reset_mutex is not safe. Fixes: c3b10a55abc9 ("scsi: megaraid_sas: Update controller info during resume") Cc: Kashyap Desai Cc: Sumit Saxena Cc: Shivasharan S Cc: Chandrakanth patil Signed-off-by: Bart Van Assche Link: https://patch.msgid.link/f06b5ee432b21cf293f0663e15b64f75a84b9fd5.1788204406.git.bvanassche@acm.org Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/megaraid/megaraid_sas_base.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/scsi/megaraid/megaraid_sas_base.c b/drivers/scsi/megaraid/megaraid_sas_base.c index b95f187297ae..4f9a53769966 100644 --- a/drivers/scsi/megaraid/megaraid_sas_base.c +++ b/drivers/scsi/megaraid/megaraid_sas_base.c @@ -7886,7 +7886,9 @@ megasas_resume(struct device *dev) goto fail_init_mfi; } - if (megasas_get_ctrl_info(instance) != DCMD_SUCCESS) + scoped_guard(mutex, &instance->reset_mutex) + rval = megasas_get_ctrl_info(instance); + if (rval != DCMD_SUCCESS) goto fail_init_mfi; tasklet_init(&instance->isr_tasklet, instance->instancet->tasklet, From 5d063822ac5184939c1ed377a339a01d8ae814e8 Mon Sep 17 00:00:00 2001 From: Guanglei Zhu Date: Fri, 11 Sep 2026 10:17:32 +0800 Subject: [PATCH 1871/2366] net: wwan: mhi_wwan_mbim: guard against a cyclic NDP chain The NDP traversal in mhi_mbim_rx() only stops when wNextNdpIndex is zero. Nothing requires the offsets to advance, so a modem that points an NDP at itself, or at an earlier NDP, keeps the loop spinning forever on one CPU. Break out when the next NDP offset is not larger than the current one. Fixes: aa730a9905b7 ("net: wwan: Add MHI MBIM network driver") Cc: stable@vger.kernel.org Suggested-by: Loic Poulain Signed-off-by: Guanglei Zhu Verified in a QEMU guest with a fault injector feeding the driver's receive callback an NTB whose single NDP points at itself: the unpatched driver spins in mhi_mbim_rx() with one CPU pinned at 100% and the thread never returns. With this check the loop terminates within one iteration. Changes in v2: move the non-increasing check to the wNextNdpIndex retrieval site, as suggested by Loic Poulain, instead of tracking the previous offset in a separate variable. Link: https://patch.msgid.link/20260911021734.1396599-1-zhugl3@xiaopeng.com Signed-off-by: Jakub Kicinski --- drivers/net/wwan/mhi_wwan_mbim.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/wwan/mhi_wwan_mbim.c b/drivers/net/wwan/mhi_wwan_mbim.c index a94998712597..5679948546a5 100644 --- a/drivers/net/wwan/mhi_wwan_mbim.c +++ b/drivers/net/wwan/mhi_wwan_mbim.c @@ -349,9 +349,13 @@ static void mhi_mbim_rx(struct mhi_mbim_context *mbim, struct sk_buff *skb) unlock: rcu_read_unlock(); next_ndp: - /* Other NDP to process? */ - ndpoffset = (int)le16_to_cpu(ndp16.wNextNdpIndex); - if (!ndpoffset) + /* Other NDP to process? The offsets must advance, or a + * self-referencing NDP keeps the loop spinning forever. + */ + n = (int)le16_to_cpu(ndp16.wNextNdpIndex); + if (n > ndpoffset) + ndpoffset = n; + else break; } From 31550d585589fde1ae95bf7f7a8188b2d2fdf1c7 Mon Sep 17 00:00:00 2001 From: Guanglei Zhu Date: Fri, 11 Sep 2026 10:17:33 +0800 Subject: [PATCH 1872/2366] net: wwan: mhi_wwan_mbim: check skb_copy_bits() return value mhi_mbim_rx() ignores the return value of skb_copy_bits() when it copies each datagram out of the NTB. The datagram offset and length come from the DPE, which is only checked to lie within the NTB itself, so a modem can point a datagram outside the received skb. The copy then fails and the freshly allocated skbn is passed to netif_rx() with its uninitialized contents still in place, leaking kernel heap memory into the network stack. Free the skb and account an error when the copy fails. Fixes: aa730a9905b7 ("net: wwan: Add MHI MBIM network driver") Cc: stable@vger.kernel.org Suggested-by: Loic Poulain Signed-off-by: Guanglei Zhu Verified in a QEMU guest with a fault injector pointing a DPE outside the received NTB: the copy fails, and the unpatched driver hands the uninitialized skbn to the network stack (observed as "unknown protocol" on bytes that were never written). With this check the failed datagram is dropped and counted as an rx error. Changes in v2: factor the free-and-count sequence out into mhi_mbim_rx_drop(), shared with the unknown-protocol path, as suggested by Loic Poulain. Link: https://patch.msgid.link/20260911021734.1396599-2-zhugl3@xiaopeng.com Signed-off-by: Jakub Kicinski --- drivers/net/wwan/mhi_wwan_mbim.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/net/wwan/mhi_wwan_mbim.c b/drivers/net/wwan/mhi_wwan_mbim.c index 5679948546a5..336f89756f10 100644 --- a/drivers/net/wwan/mhi_wwan_mbim.c +++ b/drivers/net/wwan/mhi_wwan_mbim.c @@ -251,6 +251,14 @@ static int mbim_rx_verify_ndp16(struct sk_buff *skb, struct usb_cdc_ncm_ndp16 *n return ret; } +static void mhi_mbim_rx_drop(struct mhi_mbim_link *link, struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + u64_stats_update_begin(&link->rx_syncp); + u64_stats_inc(&link->rx_errors); + u64_stats_update_end(&link->rx_syncp); +} + static void mhi_mbim_rx(struct mhi_mbim_context *mbim, struct sk_buff *skb) { int ndpoffset; @@ -320,7 +328,10 @@ static void mhi_mbim_rx(struct mhi_mbim_context *mbim, struct sk_buff *skb) continue; skb_put(skbn, dgram_len); - skb_copy_bits(skb, dgram_offset, skbn->data, dgram_len); + if (skb_copy_bits(skb, dgram_offset, skbn->data, dgram_len)) { + mhi_mbim_rx_drop(link, skbn); + continue; + } switch (skbn->data[0] & 0xf0) { case 0x40: @@ -332,10 +343,7 @@ static void mhi_mbim_rx(struct mhi_mbim_context *mbim, struct sk_buff *skb) default: net_err_ratelimited("%s: unknown protocol\n", link->ndev->name); - dev_kfree_skb_any(skbn); - u64_stats_update_begin(&link->rx_syncp); - u64_stats_inc(&link->rx_errors); - u64_stats_update_end(&link->rx_syncp); + mhi_mbim_rx_drop(link, skbn); continue; } From c7ead9704249d57d4693a04697e3bbd285138fa9 Mon Sep 17 00:00:00 2001 From: Guanglei Zhu Date: Fri, 11 Sep 2026 10:17:34 +0800 Subject: [PATCH 1873/2366] net: wwan: t7xx: validate the netif index in t7xx_ccmni_recv_skb() The netif index carried in the DPMAIF PIT header is five bits wide, but ccmni_inst[] only has room for NIC_DEV_MAX (21) entries. t7xx_ccmni_recv_skb() indexes the array without a bounds check, so indexes 21 to 31 read past it. The out-of-bounds value lands in the callback table that follows the array, which is never NULL, so the existing !ccmni check does not catch it and the driver dereferences whatever sits there as a struct t7xx_ccmni. Drop the skb when the index is out of range. Fixes: 05d19bf500f8 ("net: wwan: t7xx: Add WWAN network interface") Cc: stable@vger.kernel.org Signed-off-by: Guanglei Zhu Verified in a QEMU guest with a fault injector setting the netif index to 25: the unpatched driver reads a value past ccmni_inst[], which lands in the callback table, and dereferences it far enough to queue the skb. With this check the packet is dropped. Well-formed traffic on index 0 is unaffected. Changes in v2: none. Link: https://patch.msgid.link/20260911021734.1396599-3-zhugl3@xiaopeng.com Signed-off-by: Jakub Kicinski --- drivers/net/wwan/t7xx/t7xx_netdev.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wwan/t7xx/t7xx_netdev.c b/drivers/net/wwan/t7xx/t7xx_netdev.c index fc0a7cb181df..8f32c2d26931 100644 --- a/drivers/net/wwan/t7xx/t7xx_netdev.c +++ b/drivers/net/wwan/t7xx/t7xx_netdev.c @@ -420,6 +420,10 @@ static void t7xx_ccmni_recv_skb(struct t7xx_ccmni_ctrl *ccmni_ctlb, struct sk_bu skb_cb = T7XX_SKB_CB(skb); netif_id = skb_cb->netif_idx; + if (netif_id >= NIC_DEV_MAX) { + dev_kfree_skb(skb); + return; + } ccmni = READ_ONCE(ccmni_ctlb->ccmni_inst[netif_id]); if (!ccmni) { dev_kfree_skb(skb); From 90e4b849dfa6fc8e6c050bcfe1b331b69c015d28 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 11 Sep 2026 10:58:29 +0200 Subject: [PATCH 1874/2366] net: stmmac: propagate FPE preemption-class mapping errors stmmac_fpe_map_preemption_class() dispatches through the stmmac_do_void_callback() helper, which forces the callback's return value to 0 whenever the op pointer is populated. As a result the -EINVAL returned by dwmac5_fpe_map_preemption_class() (e.g. when a preemptible TC owns more than one TXQ under SP scheduling) is silently swallowed by every caller. Switch the dispatch macro to stmmac_do_callback() so the callback's real result is propagated, and honour it in the taprio and mqprio qdisc offload. Note that the taprio "if (ret)" check in tc_taprio_configure() used to be dead code and now becomes live: a preemptible TC spanning more than one TXQ under SP scheduling cannot be programmed in hardware, so a taprio or mqprio configuration that previously returned success while leaving the preemption-class register unprogrammed now fails with -EINVAL. For taprio, the failure also runs the disable path, tearing down the schedule that was just installed; this is the intended behaviour. Fixes: 195e4f409a40 ("net: stmmac: support fp parameter of tc-mqprio") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260911-stmmac-tc_setup_dwmac510_mqprio-error-path-v3-1-a76b1e2547c1@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- .../net/ethernet/stmicro/stmmac/stmmac_tc.c | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 04dafec021b4..9314bcb85c22 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -494,7 +494,7 @@ struct stmmac_ops { #define stmmac_set_arp_offload(__priv, __args...) \ stmmac_do_void_callback(__priv, mac, set_arp_offload, __args) #define stmmac_fpe_map_preemption_class(__priv, __args...) \ - stmmac_do_void_callback(__priv, mac, fpe_map_preemption_class, __args) + stmmac_do_callback(__priv, mac, fpe_map_preemption_class, __args) /* PTP and HW Timer helpers */ struct stmmac_hwtimestamp { diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c index 14cabe76e53e..5398616fcdfe 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c @@ -970,7 +970,7 @@ static int tc_taprio_configure(struct stmmac_priv *priv, struct netlink_ext_ack *extack = qopt->mqprio.extack; struct timespec64 time, current_time, qopt_time; ktime_t current_time_ns; - int i, ret = 0; + int err, i, ret = 0; u64 ctr; if (qopt->base_time < 0) @@ -1120,9 +1120,9 @@ static int tc_taprio_configure(struct stmmac_priv *priv, mutex_unlock(&priv->est_lock); } - stmmac_fpe_map_preemption_class(priv, priv->dev, extack, 0); + err = stmmac_fpe_map_preemption_class(priv, priv->dev, extack, 0); - return ret; + return qopt->cmd == TAPRIO_CMD_DESTROY ? err : ret; } static void tc_taprio_stats(struct stmmac_priv *priv, @@ -1237,14 +1237,15 @@ static int tc_query_caps(struct stmmac_priv *priv, } } -static void stmmac_reset_tc_mqprio(struct net_device *ndev, - struct netlink_ext_ack *extack) +static int stmmac_reset_tc_mqprio(struct net_device *ndev, + struct netlink_ext_ack *extack) { struct stmmac_priv *priv = netdev_priv(ndev); netdev_reset_tc(ndev); netif_set_real_num_tx_queues(ndev, priv->plat->tx_queues_to_use); - stmmac_fpe_map_preemption_class(priv, ndev, extack, 0); + + return stmmac_fpe_map_preemption_class(priv, ndev, extack, 0); } static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv, @@ -1257,10 +1258,8 @@ static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv, u32 num_tc = qopt->num_tc; int err; - if (!num_tc) { - stmmac_reset_tc_mqprio(ndev, extack); - return 0; - } + if (!num_tc) + return stmmac_reset_tc_mqprio(ndev, extack); err = netdev_set_num_tc(ndev, num_tc); if (err) From 02fffd1939f6b45892f61822459953ce95e42948 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 11 Sep 2026 10:58:30 +0200 Subject: [PATCH 1875/2366] net: stmmac: preserve real_num_tx_queues on mqprio setup failure With the FPE preemption-class mapping error now propagated from stmmac_fpe_map_preemption_class(), tc_setup_dwmac510_mqprio() can fail on the mapping step. The error path used to call stmmac_reset_tc_mqprio(), which resets the number of real TX queues to priv->plat->tx_queues_to_use (the platform maximum), overwriting the value that was active before the offload was attempted (for example a lower count left over from a previous mqprio configuration). The issue can be triggered using the following configuration: # First mqprio config lowers the hw queue count below the platform # default (e.g. 8 TX queues). $tc qdisc add dev eth0 root handle 1: mqprio queues 2@0 2@2 # Replace mqprio configuration with a second one that fails FPE # preemption-class mapping. stmmac driver resets the real_num_tx_queues # to the platform maximum, losing the previous configuration. $tc qdisc replace dev eth0 root handle 2: mqprio queues 2@0 2@2 fp E P Save ndev->real_num_tx_queues before lowering it and restore it, together with the TC-to-queue and priority-to-TC mappings, when the FPE preemption-class mapping fails, instead of resetting the queue count to the platform maximum. Note that a failed setup makes the qdisc layer run mqprio_destroy() on the new qdisc. Because priv->hw_offload is only assigned after ndo_setup_tc() succeeds, mqprio_destroy() calls netdev_set_num_tc(dev, 0), so dev->num_tc ends up 0 regardless of the driver-side restore and the previous qdisc is not reactivated. The restore is still needed to keep real_num_tx_queues and to avoid leaving the failed configuration's TC-to-queue and priority-to-TC mappings in place. Fixes: 195e4f409a40 ("net: stmmac: support fp parameter of tc-mqprio") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260911-stmmac-tc_setup_dwmac510_mqprio-error-path-v3-2-a76b1e2547c1@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_tc.c | 80 ++++++++++++++----- 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c index 5398616fcdfe..42a00446e9b4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c @@ -1237,6 +1237,30 @@ static int tc_query_caps(struct stmmac_priv *priv, } } +static int stmmac_set_ndev_tcs(struct net_device *ndev, u8 ntc, + struct netdev_tc_txq *tc_to_txq) +{ + int i, err; + + netdev_reset_tc(ndev); + if (!ntc) + return 0; + + err = netdev_set_num_tc(ndev, ntc); + if (err) + return err; + + for (i = 0; i < ntc; i++) { + u16 count, offset; + + count = tc_to_txq[i].count; + offset = tc_to_txq[i].offset; + netdev_set_tc_queue(ndev, i, count, offset); + } + + return 0; +} + static int stmmac_reset_tc_mqprio(struct net_device *ndev, struct netlink_ext_ack *extack) { @@ -1251,43 +1275,61 @@ static int stmmac_reset_tc_mqprio(struct net_device *ndev, static int tc_setup_dwmac510_mqprio(struct stmmac_priv *priv, struct tc_mqprio_qopt_offload *mqprio) { + unsigned int ndev_num_tx_queues, num_tx_queues = 0; + struct netdev_tc_txq ndev_tc_to_txq[TC_MAX_QUEUE]; + struct netdev_tc_txq tc_to_txq[TC_MAX_QUEUE] = {}; struct netlink_ext_ack *extack = mqprio->extack; struct tc_mqprio_qopt *qopt = &mqprio->qopt; - u32 offset, count, num_stack_tx_queues = 0; struct net_device *ndev = priv->dev; - u32 num_tc = qopt->num_tc; - int err; + u8 ndev_prio_tc_map[TC_BITMASK + 1]; + int i, err, ndev_ntc; - if (!num_tc) + if (!qopt->num_tc) return stmmac_reset_tc_mqprio(ndev, extack); - err = netdev_set_num_tc(ndev, num_tc); - if (err) - return err; + if (qopt->num_tc > ARRAY_SIZE(tc_to_txq)) + return -EINVAL; - for (u32 tc = 0; tc < num_tc; tc++) { - offset = qopt->offset[tc]; - count = qopt->count[tc]; - num_stack_tx_queues += count; + /* save current tc values for reset */ + ndev_ntc = netdev_get_num_tc(ndev); + for (i = 0; i < ARRAY_SIZE(ndev->tc_to_txq); i++) + ndev_tc_to_txq[i].combined = + READ_ONCE(ndev->tc_to_txq[i].combined); + for (i = 0; i < ARRAY_SIZE(ndev_prio_tc_map); i++) + ndev_prio_tc_map[i] = READ_ONCE(ndev->prio_tc_map[i]); - err = netdev_set_tc_queue(ndev, tc, count, offset); - if (err) - goto err_reset_tc; + for (i = 0; i < qopt->num_tc; i++) { + tc_to_txq[i] = (struct netdev_tc_txq) { + .count = qopt->count[i], + .offset = qopt->offset[i], + }; + num_tx_queues += qopt->count[i]; } - err = netif_set_real_num_tx_queues(ndev, num_stack_tx_queues); + err = stmmac_set_ndev_tcs(ndev, qopt->num_tc, tc_to_txq); if (err) - goto err_reset_tc; + goto error_reset_tc; + + ndev_num_tx_queues = ndev->real_num_tx_queues; + err = netif_set_real_num_tx_queues(ndev, num_tx_queues); + if (err) + goto error_reset_tc; err = stmmac_fpe_map_preemption_class(priv, ndev, extack, mqprio->preemptible_tcs); if (err) - goto err_reset_tc; + goto error_reset_num_tx_queues; return 0; -err_reset_tc: - stmmac_reset_tc_mqprio(ndev, extack); +error_reset_num_tx_queues: + if (netif_set_real_num_tx_queues(ndev, ndev_num_tx_queues)) + netdev_warn(ndev, "Failed to restore %u TX queues\n", + ndev_num_tx_queues); +error_reset_tc: + stmmac_set_ndev_tcs(ndev, ndev_ntc, ndev_tc_to_txq); + for (i = 0; i < ARRAY_SIZE(ndev_prio_tc_map); i++) + netdev_set_prio_tc_map(ndev, i, ndev_prio_tc_map[i]); return err; } From a41f24c612c3f5139a3143307eb85bbcf1bd4d07 Mon Sep 17 00:00:00 2001 From: Daniel Zahka Date: Tue, 15 Sep 2026 16:11:37 -0700 Subject: [PATCH 1876/2366] net: psp: avoid conflicts with skb->decrypted and sk_validate_xmit_skb() PSP conflicts with TLS ULP in its usage of both skb->decrypted and sk->sk_validate_xmit_skb(). Make PSP mutually exclusive with TLS ULP, the only other user of either of these. As other users of skb->decrypted come along, they can be added to sk_has_decrypt_user(). It would make sense to also assert that sk->sk_validate_xmit_skb() is also NULL in both of these setup paths for similar future proofing, but the PSP listener/sk_clone() path is still broken and it could be seen as a regression to not allow rx assoc to run on a child of a listener socket with PSP tx assoc state. Include all TCP ULPs in the sk_has_decrypt_user() check, even though TLS is the only one that conflicts with PSP via the decrypted bit. This is intentional because PSP was not designed to be used with ULPs. It is best to close off surface area that may make bugs reachable, until someone wishes to design and test an actual user of PSP with ULPs. Fixes: 6b46ca260e22 ("net: psp: add socket security association code") Signed-off-by: Daniel Zahka Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260915-psp-ktls-fix-v2-1-0eedc3b148ec@gmail.com Signed-off-by: Jakub Kicinski --- include/net/sock.h | 2 ++ net/core/sock.c | 7 +++++++ net/ipv4/tcp_ulp.c | 4 ++++ net/psp/psp_sock.c | 4 ++++ 4 files changed, 17 insertions(+) diff --git a/include/net/sock.h b/include/net/sock.h index 51185222aac2..60ea55dc1885 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -2312,6 +2312,8 @@ static inline void sk_gso_disable(struct sock *sk) sk->sk_route_caps &= ~NETIF_F_GSO_MASK; } +bool sk_has_decrypt_user(const struct sock *sk); + static inline int skb_do_copy_data_nocache(struct sock *sk, struct sk_buff *skb, struct iov_iter *from, char *to, int copy, int offset) diff --git a/net/core/sock.c b/net/core/sock.c index d5e302e21e85..d23333bb4f3f 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -142,6 +142,7 @@ #include +#include #include #include #include @@ -2670,6 +2671,12 @@ void sk_setup_caps(struct sock *sk, struct dst_entry *dst) } EXPORT_SYMBOL_GPL(sk_setup_caps); +bool sk_has_decrypt_user(const struct sock *sk) +{ + return psp_sk_assoc(sk) || + (sk_is_inet(sk) && inet_csk_has_ulp(sk)); /* for tls */ +} + /* * Simple resource managers for sockets. */ diff --git a/net/ipv4/tcp_ulp.c b/net/ipv4/tcp_ulp.c index 2aa442128630..b58045df101e 100644 --- a/net/ipv4/tcp_ulp.c +++ b/net/ipv4/tcp_ulp.c @@ -136,6 +136,10 @@ static int __tcp_set_ulp(struct sock *sk, const struct tcp_ulp_ops *ulp_ops) if (icsk->icsk_ulp_ops) goto out_err; + err = -EINVAL; + if (sk_has_decrypt_user(sk)) + goto out_err; + if (sk->sk_socket) clear_bit(SOCK_SUPPORT_ZC, &sk->sk_socket->flags); diff --git a/net/psp/psp_sock.c b/net/psp/psp_sock.c index 1a2a6b7516b0..e9b53eedf8db 100644 --- a/net/psp/psp_sock.c +++ b/net/psp/psp_sock.c @@ -143,6 +143,10 @@ int psp_sock_assoc_set_rx(struct sock *sk, struct psp_assoc *pas, NL_SET_ERR_MSG(extack, "Socket already has PSP state"); err = -EBUSY; goto exit_unlock; + } else if (sk_has_decrypt_user(sk)) { + NL_SET_ERR_MSG(extack, "Socket has incompatible state"); + err = -EINVAL; + goto exit_unlock; } refcount_inc(&pas->refcnt); From b4288c59bda883b0e5cd95099dc3b0b7b7fc50f6 Mon Sep 17 00:00:00 2001 From: Daniel Zahka Date: Tue, 15 Sep 2026 16:11:38 -0700 Subject: [PATCH 1877/2366] selftests: drv-net: psp: test PSP and TCP ULP mutual exclusion Test both setting PSP after TLS ULP, and TLS ULP after PSP. Add CONFIG_TLS=y to the drivers/net/config. Signed-off-by: Daniel Zahka Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260915-psp-ktls-fix-v2-2-0eedc3b148ec@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/drivers/net/config | 1 + tools/testing/selftests/drivers/net/psp.py | 46 ++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config index b6989c7d3d9d..4838adf27fa1 100644 --- a/tools/testing/selftests/drivers/net/config +++ b/tools/testing/selftests/drivers/net/config @@ -21,5 +21,6 @@ CONFIG_NET_SCH_INGRESS=y CONFIG_NET_SCH_PRIO=m CONFIG_PPP=y CONFIG_PPPOE=y +CONFIG_TLS=y CONFIG_VLAN_8021Q=m CONFIG_XDP_SOCKETS=y diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py index 315648a770d0..a5b1e14f120f 100755 --- a/tools/testing/selftests/drivers/net/psp.py +++ b/tools/testing/selftests/drivers/net/psp.py @@ -23,6 +23,8 @@ from lib.py import NetNSEnter from lib.py import bkg, rand_port, wait_port_listen from lib.py import ip +TCP_ULP = 31 + def _get_outq(s): one = b'\0' * 4 @@ -333,6 +335,50 @@ def assoc_version_mismatch(cfg): ksft_eq(the_exception.nl_msg.error, -errno.EINVAL) +def _require_tls_ulp(): + with socket.create_server(("localhost", 0)) as srv, \ + socket.create_connection(srv.getsockname()) as s: + try: + s.setsockopt(socket.SOL_TCP, TCP_ULP, b"tls") + except OSError as exc: + raise KsftSkipEx("kTLS not available") from exc + + +def assoc_psp_ulp_exclusive(cfg): + """ Test that a TCP ULP cannot be attached to a PSP socket """ + _init_psp_dev(cfg) + _require_tls_ulp() + + with _make_clr_conn(cfg) as s: + try: + cfg.pspnl.rx_assoc({"version": 0, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + with ksft_raises(OSError) as cm: + s.setsockopt(socket.SOL_TCP, TCP_ULP, b"tls") + ksft_eq(cm.exception.errno, errno.EINVAL) + finally: + _close_conn(cfg, s) + + +def assoc_ulp_psp_exclusive(cfg): + """ Test that a PSP assoc cannot be added to a socket with a TCP ULP """ + _init_psp_dev(cfg) + _require_tls_ulp() + + with _make_clr_conn(cfg) as s: + try: + s.setsockopt(socket.SOL_TCP, TCP_ULP, b"tls") + with ksft_raises(NlError) as cm: + cfg.pspnl.rx_assoc({"version": 0, + "dev-id": cfg.psp_dev_id, + "sock-fd": s.fileno()}) + ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL) + ksft_eq(cm.exception.nl_msg.extack['bad-attr'], ".sock-fd") + finally: + _close_conn(cfg, s) + + def assoc_twice(cfg): """ Test reusing Tx assoc for two sockets """ _init_psp_dev(cfg) From 2f91fc9a96cdab6c03d436246056552e04677636 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:08:56 +0530 Subject: [PATCH 1878/2366] s390: Fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Heiko Carstens --- drivers/s390/block/dasd_3990_erp.c | 6 +++--- drivers/s390/block/dasd_eckd.c | 4 ++-- drivers/s390/char/raw3270.c | 2 +- drivers/s390/char/vmlogrdr.c | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/s390/block/dasd_3990_erp.c b/drivers/s390/block/dasd_3990_erp.c index 736459477c19..121cb9ae5c67 100644 --- a/drivers/s390/block/dasd_3990_erp.c +++ b/drivers/s390/block/dasd_3990_erp.c @@ -103,7 +103,7 @@ dasd_3990_erp_int_req(struct dasd_ccw_req * erp) /* first time set initial retry counter and erp_function */ /* and retry once without blocking queue */ - /* (this enables easier enqueing of the cqr) */ + /* (this enables easier enqueuing of the cqr) */ if (erp->function != dasd_3990_erp_int_req) { erp->retries = 256; @@ -302,7 +302,7 @@ dasd_3990_erp_action_4(struct dasd_ccw_req * erp, char *sense) /* first time set initial retry counter and erp_function */ /* and retry once without waiting for state change pending */ - /* interrupt (this enables easier enqueing of the cqr) */ + /* interrupt (this enables easier enqueuing of the cqr) */ if (erp->function != dasd_3990_erp_action_4) { DBF_DEV_EVENT(DBF_INFO, device, "%s", @@ -1078,7 +1078,7 @@ dasd_3990_erp_bus_out(struct dasd_ccw_req * erp) /* first time set initial retry counter and erp_function */ /* and retry once without blocking queue */ - /* (this enables easier enqueing of the cqr) */ + /* (this enables easier enqueuing of the cqr) */ if (erp->function != dasd_3990_erp_bus_out) { erp->retries = 256; erp->function = dasd_3990_erp_bus_out; diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 8d976dd58a6c..9d1b11020b4e 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -2060,7 +2060,7 @@ dasd_eckd_psf_ssc(struct dasd_device *device, int enable_pav, } /* - * Valide storage server of current device. + * Valid storage server of current device. */ static int dasd_eckd_validate_server(struct dasd_device *device, unsigned long flags) @@ -5672,7 +5672,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_raw(struct dasd_device *startdev, char *dst; /* - * raw track access needs to be mutiple of 64k and on 64k boundary + * raw track access needs to be multiple of 64k and on 64k boundary * For read requests we can fix an incorrect alignment by padding * the request with dummy pages. */ diff --git a/drivers/s390/char/raw3270.c b/drivers/s390/char/raw3270.c index aa9c4d81225c..6861bf27d15f 100644 --- a/drivers/s390/char/raw3270.c +++ b/drivers/s390/char/raw3270.c @@ -420,7 +420,7 @@ struct raw3270_ua { /* Query Reply structure for Usable Area */ char flags0; char flags1; short w; /* Width of usable area */ - short h; /* Heigth of usavle area */ + short h; /* Height of usavle area */ char units; /* 0x00:in; 0x01:mm */ int xr; int yr; diff --git a/drivers/s390/char/vmlogrdr.c b/drivers/s390/char/vmlogrdr.c index 383e7e2bd69f..005735a89920 100644 --- a/drivers/s390/char/vmlogrdr.c +++ b/drivers/s390/char/vmlogrdr.c @@ -444,7 +444,7 @@ static int vmlogrdr_receive_data(struct vmlogrdr_priv_t *priv) spin_unlock_bh(&priv->priv_lock); /* An rc of 5 indicates that the record was bigger than * the buffer, which is OK for us. A 9 indicates that the - * record was purged befor we could receive it. + * record was purged before we could receive it. */ if (rc == 5) rc = 0; From 4525a911049543c23885a540a788d13be318a486 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 7 Sep 2026 07:58:47 +0200 Subject: [PATCH 1879/2366] s390/pci/docs: Fix sriov_numvfs attribute name The attribute is sriov_numvfs (drivers/pci/iov.c); the document names it sriov_numvf, which does not exist. Use sriov_numvfs. Fixes: de267a7c71ba ("s390/pci: Documentation for zPCI") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Signed-off-by: Heiko Carstens --- Documentation/arch/s390/pci.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/arch/s390/pci.rst b/Documentation/arch/s390/pci.rst index 80f4ba193159..565434626fb5 100644 --- a/Documentation/arch/s390/pci.rst +++ b/Documentation/arch/s390/pci.rst @@ -67,7 +67,7 @@ Entries specific to zPCI functions and entries that hold zPCI information. A physical function that currently supports a virtual function cannot be powered off until all virtual functions are removed with: - echo 0 > /sys/bus/pci/devices/DDDD:BB:dd.f/sriov_numvf + echo 0 > /sys/bus/pci/devices/DDDD:BB:dd.f/sriov_numvfs * /sys/bus/pci/devices/DDDD:BB:dd.f/: From 29d9e5835d89223aa913dcf7b942cc1c148bdd25 Mon Sep 17 00:00:00 2001 From: Vineeth Vijayan Date: Thu, 10 Sep 2026 11:32:01 +0200 Subject: [PATCH 1880/2366] s390/cio: Fix cio_update_schib() to not cache invalid schib When pmcw.dnv is 0, the contents of all SCHIB fields are unpredictable. Zero sch->schib in that case to prevent subsequent code from making decisions based on unpredictable data. Reported-by: William Bezenah Signed-off-by: Vineeth Vijayan Reviewed-by: Peter Oberparleiter Fixes: 8c58a229688c ("s390/cio: Do not unregister the subchannel based on DNV") Signed-off-by: Heiko Carstens --- drivers/s390/cio/cio.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/s390/cio/cio.c b/drivers/s390/cio/cio.c index 70dc8cc76594..e1c62eb60cca 100644 --- a/drivers/s390/cio/cio.c +++ b/drivers/s390/cio/cio.c @@ -453,7 +453,8 @@ EXPORT_SYMBOL_GPL(cio_commit_config); /** * cio_update_schib - Perform stsch and update schib if subchannel is valid. * @sch: subchannel on which to perform stsch - * Return zero on success, -ENODEV otherwise. + * Return zero on success, -ENODEV if the subchannel is not operational, + * -EACCES if the subchannel has no valid device. */ int cio_update_schib(struct subchannel *sch) { @@ -462,10 +463,12 @@ int cio_update_schib(struct subchannel *sch) if (stsch(sch->schid, &schib)) return -ENODEV; - memcpy(&sch->schib, &schib, sizeof(schib)); - - if (!css_sch_is_valid(&schib)) + if (!css_sch_is_valid(&schib)) { + memset(&sch->schib, 0, sizeof(sch->schib)); return -EACCES; + } + + memcpy(&sch->schib, &schib, sizeof(schib)); return 0; } From f6f2985eabdb2bfdc82ce90a1ea3ec53ba795f34 Mon Sep 17 00:00:00 2001 From: Vineeth Vijayan Date: Thu, 10 Sep 2026 11:32:02 +0200 Subject: [PATCH 1881/2366] s390/cio: Check pmcw.dnv before pmcw.ena in I/O entry points The device number valid (dnv) bit in the PMCW must be checked before acting on any other PMCW fields for IO-type subchannels. A subchannel with dnv=0 has no valid device number associated, making it meaningless to evaluate the enabled (ena) state or issue any I/O instruction against it. Reported-by: William Bezenah Signed-off-by: Vineeth Vijayan Reviewed-by: Peter Oberparleiter Fixes: 8c58a229688c ("s390/cio: Do not unregister the subchannel based on DNV") Signed-off-by: Heiko Carstens --- drivers/s390/cio/device_ops.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c index 61c07b4a0fe8..c1ba4a19368f 100644 --- a/drivers/s390/cio/device_ops.c +++ b/drivers/s390/cio/device_ops.c @@ -142,6 +142,8 @@ int ccw_device_clear(struct ccw_device *cdev, unsigned long intparm) if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) @@ -198,6 +200,8 @@ int ccw_device_start_timeout_key(struct ccw_device *cdev, struct ccw1 *cpa, if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) @@ -379,6 +383,8 @@ int ccw_device_halt(struct ccw_device *cdev, unsigned long intparm) if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) @@ -413,6 +419,8 @@ int ccw_device_resume(struct ccw_device *cdev) if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) @@ -548,6 +556,8 @@ int ccw_device_tm_start_timeout_key(struct ccw_device *cdev, struct tcw *tcw, int rc; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_VERIFY) { @@ -694,6 +704,8 @@ int ccw_device_tm_intrg(struct ccw_device *cdev) { struct subchannel *sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state != DEV_STATE_ONLINE) From 9590f4d83880dfb5a81906e48e72779248fbe8f0 Mon Sep 17 00:00:00 2001 From: Vineeth Vijayan Date: Thu, 10 Sep 2026 11:32:03 +0200 Subject: [PATCH 1882/2366] s390/cio: Guard PMCW field accesses with dnv check When PMCW.DNV is 0, no I/O device is associated with the subchannel. However, several code paths access PMCW fields directly from the cached sch->schib without first invoking the update helper. Add explicit DNV validation before accessing PMCW fields from the cached SCHIB to avoid using invalid data. Reported-by: William Bezenah Signed-off-by: Vineeth Vijayan Reviewed-by: Peter Oberparleiter Fixes: 8c58a229688c ("s390/cio: Do not unregister the subchannel based on DNV") Signed-off-by: Heiko Carstens --- drivers/s390/cio/chp.c | 3 +++ drivers/s390/cio/device.c | 9 +++++---- drivers/s390/cio/device_fsm.c | 3 +++ drivers/s390/cio/device_ops.c | 9 +++++++++ drivers/s390/cio/vfio_ccw_fsm.c | 2 +- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/s390/cio/chp.c b/drivers/s390/cio/chp.c index c890f21a82ce..eaf0527bff6c 100644 --- a/drivers/s390/cio/chp.c +++ b/drivers/s390/cio/chp.c @@ -78,6 +78,9 @@ u8 chp_get_sch_opm(struct subchannel *sch) int opm; int i; + if (!sch->schib.pmcw.dnv) + return 0; + opm = 0; chp_id_init(&chpid); for (i = 0; i < 8; i++) { diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index fb591118ecb2..68dd4a62975d 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -922,7 +922,7 @@ static int ccw_device_move_to_sch(struct ccw_device *cdev, if (!sch_is_pseudo_sch(old_sch)) { spin_lock_irq(&old_sch->lock); - old_enabled = old_sch->schib.pmcw.ena; + old_enabled = old_sch->schib.pmcw.dnv && old_sch->schib.pmcw.ena; rc = 0; if (old_enabled) rc = cio_disable_subchannel(old_sch); @@ -941,7 +941,7 @@ static int ccw_device_move_to_sch(struct ccw_device *cdev, CIO_MSG_EVENT(0, "device_move(0.%x.%04x,0.%x.%04x)=%d\n", cdev->private->dev_id.ssid, cdev->private->dev_id.devno, sch->schid.ssid, - sch->schib.pmcw.dev, rc); + sch->schid.sch_no, rc); if (old_enabled) { /* Try to re-enable the old subchannel. */ spin_lock_irq(&old_sch->lock); @@ -1207,7 +1207,7 @@ static void io_subchannel_quiesce(struct subchannel *sch) cdev = sch_get_cdev(sch); if (cio_is_console(sch->schid)) goto out_unlock; - if (!sch->schib.pmcw.ena) + if (!sch->schib.pmcw.dnv || !sch->schib.pmcw.ena) goto out_unlock; ret = cio_disable_subchannel(sch); if (ret != -EBUSY) @@ -1254,7 +1254,8 @@ static int recovery_check(struct device *dev, void *data) switch (cdev->private->state) { case DEV_STATE_ONLINE: sch = to_subchannel(cdev->dev.parent); - if ((sch->schib.pmcw.pam & sch->opm) == sch->vpm) + if (sch->schib.pmcw.dnv && + (sch->schib.pmcw.pam & sch->opm) == sch->vpm) break; fallthrough; case DEV_STATE_DISCONNECTED: diff --git a/drivers/s390/cio/device_fsm.c b/drivers/s390/cio/device_fsm.c index ab419d40a8a7..b5686c25c83c 100644 --- a/drivers/s390/cio/device_fsm.c +++ b/drivers/s390/cio/device_fsm.c @@ -170,6 +170,9 @@ __recover_lost_chpids(struct subchannel *sch, int old_lpm) int mask, i; struct chp_id chpid; + if (!sch->schib.pmcw.dnv) + return; + chp_id_init(&chpid); for (i = 0; i<8; i++) { mask = 0x80 >> i; diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c index c1ba4a19368f..f2f7f8cba410 100644 --- a/drivers/s390/cio/device_ops.c +++ b/drivers/s390/cio/device_ops.c @@ -490,6 +490,8 @@ struct channel_path_desc_fmt0 *ccw_device_get_chp_desc(struct ccw_device *cdev, struct chp_id chpid; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return NULL; chp_id_init(&chpid); chpid.id = sch->schib.pmcw.chpid[chp_idx]; return chp_get_chp_desc(chpid); @@ -510,6 +512,8 @@ u8 *ccw_device_get_util_str(struct ccw_device *cdev, int chp_idx) struct chp_id chpid; u8 *util_str; + if (!sch->schib.pmcw.dnv) + return NULL; chp_id_init(&chpid); chpid.id = sch->schib.pmcw.chpid[chp_idx]; chp = chpid_to_chp(chpid); @@ -662,6 +666,9 @@ int ccw_device_get_mdc(struct ccw_device *cdev, u8 mask) struct chp_id chpid; int mdc = 0, i; + if (!sch->schib.pmcw.dnv) + return 0; + /* Adjust requested path mask to excluded varied off paths. */ if (mask) mask &= sch->lpm; @@ -798,6 +805,8 @@ int ccw_device_get_chpid(struct ccw_device *cdev, int chp_idx, u8 *chpid) if ((chp_idx < 0) || (chp_idx > 7)) return -EINVAL; + if (!sch->schib.pmcw.dnv) + return -ENODEV; mask = 0x80 >> chp_idx; if (!(sch->schib.pmcw.pim & mask)) return -ENODEV; diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c index 5fd94e9d5c61..9a000b0231d6 100644 --- a/drivers/s390/cio/vfio_ccw_fsm.c +++ b/drivers/s390/cio/vfio_ccw_fsm.c @@ -399,7 +399,7 @@ static void fsm_close(struct vfio_ccw_private *private, spin_lock_irq(&sch->lock); - if (!sch->schib.pmcw.ena) + if (!sch->schib.pmcw.dnv || !sch->schib.pmcw.ena) goto err_unlock; ret = cio_disable_subchannel(sch); From 5535d5e61a77ad79118ea7665ce1c14f857f0f12 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Thu, 3 Sep 2026 16:42:12 +0800 Subject: [PATCH 1883/2366] drm/loongson: Create blend mode property for cursor plane After commit 860e748bddcc929 ("drm: ensure blend mode supported if pixel format with alpha exposed") we get warnings at boot: loongson 0000:00:06.1: [drm] [PLANE:41:ls-cursor-plane-0] pixel format with alpha exposed but blend mode not setup. Please fix. loongson 0000:00:06.1: [drm] [PLANE:46:ls-cursor-plane-1] pixel format with alpha exposed but blend mode not setup. Please fix. The reason is the cursor plane supports color formats with alpha but the driver doesn't create blend mode property, which triggers the warning in validate_blend_mode_for_alpha_formats(). The loongson DC HW doesn't support DRM_MODE_BLEND_PREMULTI, so create blend mode property with DRM_MODE_BLEND_COVERAGE for cursor planes since it is the only one implemented in the driver. Signed-off-by: Huacai Chen Reviewed-by: Jianmin Lv Signed-off-by: Icenowy Zheng Link: https://patch.msgid.link/20260903084212.3621540-1-chenhuacai@loongson.cn --- drivers/gpu/drm/loongson/lsdc_plane.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/loongson/lsdc_plane.c b/drivers/gpu/drm/loongson/lsdc_plane.c index bea42215796d..bcc0ffa17bdf 100644 --- a/drivers/gpu/drm/loongson/lsdc_plane.c +++ b/drivers/gpu/drm/loongson/lsdc_plane.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -765,7 +766,7 @@ int ls7a1000_cursor_plane_init(struct drm_device *ddev, drm_plane_helper_add(plane, &ls7a1000_cursor_plane_helper_funcs); - return 0; + return drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_COVERAGE)); } int ls7a2000_cursor_plane_init(struct drm_device *ddev, @@ -790,5 +791,5 @@ int ls7a2000_cursor_plane_init(struct drm_device *ddev, drm_plane_helper_add(plane, &ls7a2000_cursor_plane_helper_funcs); - return 0; + return drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_COVERAGE)); } From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: [PATCH 1884/2366] x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- arch/x86/include/asm/text-patching.h | 4 ++-- arch/x86/kernel/alternative.c | 6 ++++-- arch/x86/kernel/kprobes/core.c | 5 ++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/arch/x86/include/asm/text-patching.h b/arch/x86/include/asm/text-patching.h index f2d142a0a862..ea09381070e8 100644 --- a/arch/x86/include/asm/text-patching.h +++ b/arch/x86/include/asm/text-patching.h @@ -164,9 +164,9 @@ unsigned long int3_emulate_pop(struct pt_regs *regs) } static __always_inline -void int3_emulate_call(struct pt_regs *regs, unsigned long func) +void int3_emulate_call(struct pt_regs *regs, unsigned long ip, unsigned long func) { - int3_emulate_push(regs, regs->ip - INT3_INSN_SIZE + CALL_INSN_SIZE); + int3_emulate_push(regs, ip); int3_emulate_jmp(regs, func); } diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index 741d8767ddf8..582c6d8307d1 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c @@ -2176,6 +2176,7 @@ int3_exception_notify(struct notifier_block *self, unsigned long val, void *data unsigned long selftest = (unsigned long)&int3_selftest_asm; struct die_args *args = data; struct pt_regs *regs = args->regs; + unsigned long ip; OPTIMIZER_HIDE_VAR(selftest); @@ -2188,7 +2189,8 @@ int3_exception_notify(struct notifier_block *self, unsigned long val, void *data if (regs->ip - INT3_INSN_SIZE != selftest) return NOTIFY_DONE; - int3_emulate_call(regs, (unsigned long)&int3_selftest_callee); + ip = regs->ip - INT3_INSN_SIZE + CALL_INSN_SIZE; + int3_emulate_call(regs, ip, (unsigned long)&int3_selftest_callee); return NOTIFY_STOP; } @@ -2758,7 +2760,7 @@ noinstr int smp_text_poke_int3_handler(struct pt_regs *regs) break; case CALL_INSN_OPCODE: - int3_emulate_call(regs, (long)ip + tpl->disp); + int3_emulate_call(regs, (long)ip, (long)ip + tpl->disp); break; case JMP32_INSN_OPCODE: diff --git a/arch/x86/kernel/kprobes/core.c b/arch/x86/kernel/kprobes/core.c index 4e5f8c1736ec..133ff20caccd 100644 --- a/arch/x86/kernel/kprobes/core.c +++ b/arch/x86/kernel/kprobes/core.c @@ -510,10 +510,9 @@ NOKPROBE_SYMBOL(kprobe_emulate_ret); static void kprobe_emulate_call(struct kprobe *p, struct pt_regs *regs) { - unsigned long func = regs->ip - INT3_INSN_SIZE + p->ainsn.size; + unsigned long ip = regs->ip - INT3_INSN_SIZE + p->ainsn.size; - func += p->ainsn.rel32; - int3_emulate_call(regs, func); + int3_emulate_call(regs, ip, ip + p->ainsn.rel32); } NOKPROBE_SYMBOL(kprobe_emulate_call); From fc3ae66514ca5e87251f79044236e3e8babd24a3 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 14 Sep 2026 16:20:27 +0100 Subject: [PATCH 1885/2366] netfs: Fix netfs_read_gaps() to use separate sink folios Fix netfs_read_gaps() to use separate folios rather than re-using a single sink folio to discard the unwanted data so that cifs checksum checking sees all the data that was fetched. Fixes: 7f84a7b9892d ("netfs: Make netfs_read_folio() handle streaming-write pages") Reported-by: Frank Sorenson Closes: https://lore.kernel.org/r/a385053c-1c4a-4060-a3bb-befa007ddb33@redhat.com/ Signed-off-by: David Howells Link: https://patch.msgid.link/3228134.1789399227@warthog.procyon.org.uk Tested-by: Frank Sorenson Reviewed-by: Paulo Alcantara cc: Paulo Alcantara cc: Namjae Jeon cc: netfs@lists.linux.dev cc: linux-cifs@vger.kernel.org cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 424df70a5c30..105194de6e13 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -482,15 +482,14 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) struct netfs_group *group = netfs_folio_group(folio); struct netfs_folio *finfo = netfs_folio_info(folio); struct netfs_inode *ctx = netfs_inode(mapping->host); - struct folio *sink = NULL; - struct bio_vec *bvec; + struct bio_vec *bvec = NULL; unsigned int from = finfo->dirty_offset; unsigned int to = from + finfo->dirty_len; - unsigned int off = 0, i = 0; + unsigned int off = 0; size_t flen = folio_size(folio); size_t nr_bvec = flen / PAGE_SIZE + 2; size_t part; - int ret; + int ret, i = 0, sink_from = -1, sink_to = -1; _enter("%lx", folio->index); @@ -515,24 +514,23 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) if (!bvec) goto discard; - sink = folio_alloc(GFP_KERNEL, 0); - if (!sink) { - kfree(bvec); - goto discard; - } - trace_netfs_folio(folio, netfs_folio_trace_read_gaps); - rreq->direct_bv = bvec; - rreq->direct_bv_count = nr_bvec; if (from > 0) { bvec_set_folio(&bvec[i++], folio, from, 0); off = from; } + sink_from = i; while (off < to) { + struct folio *sink = folio_alloc(GFP_KERNEL, 0); + + if (!sink) + goto discard; part = min_t(size_t, to - off, PAGE_SIZE); - bvec_set_folio(&bvec[i++], sink, part, 0); + bvec_set_folio(&bvec[i], sink, part, 0); off += part; + sink_to = i; + i++; } if (to < flen) bvec_set_folio(&bvec[i++], folio, flen - to, to); @@ -553,8 +551,10 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) folio_mark_uptodate(folio); } - if (sink) - folio_put(sink); + if (sink_to >= 0) + for (; sink_from <= sink_to; sink_from++) + folio_put(bvec_folio(&bvec[sink_from])); + kfree(bvec); folio_unlock(folio); netfs_put_request(rreq, netfs_rreq_trace_put_return); return ret < 0 ? ret : 0; @@ -563,6 +563,10 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) netfs_put_failed_request(rreq); alloc_error: folio_unlock(folio); + if (sink_to >= 0) + for (; sink_from <= sink_to; sink_from++) + folio_put(bvec_folio(&bvec[sink_from])); + kfree(bvec); return ret; } From 065f3ce5936e68da75f3dc18201d290073d78f3c Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:37:05 -0700 Subject: [PATCH 1886/2366] xfs: call xfs_dquot_set_prealloc_limits if we installed default rtb limits Now that we have quotas for the realtime volume, we also have precomputed watermark limits for the realtime block counts. These precomputations should be done any time we change the rtb limits, which means that xfs_qm_adjust_dqlimits needs to ensure that if we installed a default rtb limit. Cc: stable@vger.kernel.org # v6.13 Fixes: 5dd70852b03901 ("xfs: create quota preallocation watermarks for realtime quota") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_dquot.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_dquot.c b/fs/xfs/xfs_dquot.c index b4f6c594808c..e696ee36c2e8 100644 --- a/fs/xfs/xfs_dquot.c +++ b/fs/xfs/xfs_dquot.c @@ -139,10 +139,14 @@ xfs_qm_adjust_dqlimits( dq->q_ino.softlimit = defq->ino.soft; if (!dq->q_ino.hardlimit) dq->q_ino.hardlimit = defq->ino.hard; - if (!dq->q_rtb.softlimit) + if (!dq->q_rtb.softlimit) { dq->q_rtb.softlimit = defq->rtb.soft; - if (!dq->q_rtb.hardlimit) + prealloc = 1; + } + if (!dq->q_rtb.hardlimit) { dq->q_rtb.hardlimit = defq->rtb.hard; + prealloc = 1; + } if (prealloc) xfs_dquot_set_prealloc_limits(dq); From 41c4c41cf6c44f98db2916e1781f537d9ba6461a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:37:20 -0700 Subject: [PATCH 1887/2366] xfs: fix rtgroup repair estimations When I added online fsck for realtime reflink, I forgot to update xrep_calc_rtgroup_resblks to factor in the size of the refcount btree when it guesses how much space we need to start a repair. This hasn't been a huge problem in practice because there are few filesystems with (a) realtime, (b) rtgroups, (c) reflink, and (d) no rmap. But let's fix this before someone stumbles upon it, especially since LOLLM flagged this for me. Cc: stable@vger.kernel.org # v6.14 Fixes: 83ccffc489975d ("xfs: online repair of the realtime refcount btree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/repair.c | 21 ++++++++++++++++++--- fs/xfs/scrub/trace.h | 12 ++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c index 11697a8b2a1d..c2a437416227 100644 --- a/fs/xfs/scrub/repair.c +++ b/fs/xfs/scrub/repair.c @@ -399,6 +399,7 @@ xrep_calc_rtgroup_resblks( struct xfs_mount *mp = sc->mp; struct xfs_scrub_metadata *sm = sc->sm; uint64_t usedlen; + xfs_extlen_t refcbt_sz = 0; xfs_extlen_t rmapbt_sz = 0; if (!(sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR)) @@ -411,13 +412,27 @@ xrep_calc_rtgroup_resblks( usedlen = xfs_rtbxlen_to_blen(mp, xfs_rtgroup_extents(mp, sm->sm_agno)); ASSERT(usedlen <= XFS_MAX_RGBLOCKS); + if (xfs_has_reflink(mp)) + refcbt_sz = xfs_rtrefcountbt_calc_size(mp, usedlen); + if (xfs_has_rmapbt(mp)) rmapbt_sz = xfs_rtrmapbt_calc_size(mp, usedlen); - trace_xrep_calc_rtgroup_resblks_btsize(mp, sm->sm_agno, usedlen, - rmapbt_sz); + /* + * Guess how many blocks we need to rebuild the rmapbt. For + * non-reflink filesystems we can't have more records than used blocks. + * However, with reflink it's possible to have more than one rmap + * record per rtgroup block. We don't know how many rmaps there could + * be in the rtgroup, so we start off with what we hope is an generous + * over-estimation. + */ + if (refcbt_sz > 0 && rmapbt_sz > 0) + rmapbt_sz *= 2; - return rmapbt_sz; + trace_xrep_calc_rtgroup_resblks_btsize(mp, sm->sm_agno, usedlen, + rmapbt_sz, refcbt_sz); + + return max(rmapbt_sz, refcbt_sz); } #endif /* CONFIG_XFS_RT */ diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 0f5adc293962..cb85f75ce101 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -2376,25 +2376,29 @@ TRACE_EVENT(xrep_calc_ag_resblks_btsize, #ifdef CONFIG_XFS_RT TRACE_EVENT(xrep_calc_rtgroup_resblks_btsize, TP_PROTO(struct xfs_mount *mp, xfs_rgnumber_t rgno, - xfs_rgblock_t usedlen, xfs_rgblock_t rmapbt_sz), - TP_ARGS(mp, rgno, usedlen, rmapbt_sz), + xfs_rgblock_t usedlen, xfs_rgblock_t rmapbt_sz, + xfs_rgblock_t refcbt_sz), + TP_ARGS(mp, rgno, usedlen, rmapbt_sz, refcbt_sz), TP_STRUCT__entry( __field(dev_t, dev) __field(xfs_rgnumber_t, rgno) __field(xfs_rgblock_t, usedlen) __field(xfs_rgblock_t, rmapbt_sz) + __field(xfs_rgblock_t, refcbt_sz) ), TP_fast_assign( __entry->dev = mp->m_super->s_dev; __entry->rgno = rgno; __entry->usedlen = usedlen; __entry->rmapbt_sz = rmapbt_sz; + __entry->refcbt_sz = refcbt_sz; ), - TP_printk("dev %d:%d rgno 0x%x usedlen %u rmapbt %u", + TP_printk("dev %d:%d rgno 0x%x usedlen %u rmapbt %u refcountbt %u", MAJOR(__entry->dev), MINOR(__entry->dev), __entry->rgno, __entry->usedlen, - __entry->rmapbt_sz) + __entry->rmapbt_sz, + __entry->refcbt_sz) ); #endif /* CONFIG_XFS_RT */ From d7b92cbe566f6fe54368f62e4b515d9f80c43a18 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:37:36 -0700 Subject: [PATCH 1888/2366] xfs: don't cross reference rmapbt with bitmaps if they're incomplete LOLLM points out that runtime errors (e.g. ENOMEM) when we're trying to compute space usag bitmaps are silently dropped by the rmapbt scrubber. We ought to flag that as an incomplete scrub instead of reporting cross-referencing errors based on faulty data. Cc: stable@vger.kernel.org # v6.4 Fixes: fed050f3452da0 ("xfs: cross-reference rmap records with ag btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rmap.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/fs/xfs/scrub/rmap.c b/fs/xfs/scrub/rmap.c index 0cd3eecd2ca5..68e2847c962b 100644 --- a/fs/xfs/scrub/rmap.c +++ b/fs/xfs/scrub/rmap.c @@ -493,11 +493,18 @@ xchk_rmapbt_walk_ag_metadata( * If there's an error, set XFAIL and disable the bitmap * cross-referencing checks, but proceed with the scrub anyway. */ - if (error) - xchk_btree_xref_process_error(sc, sc->sa.rmap_cur, - sc->sa.rmap_cur->bc_nlevels - 1, &error); - else - cr->bitmaps_complete = true; + if (error) { + if (!xchk_btree_xref_process_error(sc, sc->sa.rmap_cur, + sc->sa.rmap_cur->bc_nlevels - 1, &error)) { + /* only set incomplete if we didn't set xfail */ + if (error) + xchk_set_incomplete(sc); + } + + return 0; + } + + cr->bitmaps_complete = true; return 0; } @@ -567,7 +574,8 @@ xchk_rmapbt( if (error) goto out; - xchk_rmapbt_check_bitmaps(sc, cr); + if (cr->bitmaps_complete) + xchk_rmapbt_check_bitmaps(sc, cr); out: xagb_bitmap_destroy(&cr->refcbt_owned); From ab1c416d2377cdc16123ef521aac4da1c468c3d4 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:37:52 -0700 Subject: [PATCH 1889/2366] xfs: don't let memory failures leak blocks and kill repairs LOLLM complains that a memory allocation failure in xrep_newbt_add_blocks results in online repair leaking blocks that were previously allocated to write a new btree, but the problem is worse than that -- a limitation of the codebase is that the callers cannot undo the transaction /and/ return the error -- either you undo all changes and commit the transaction, or you error out and the filesystem goes down. However, the new btree space reservation object isn't that big (~48 bytes). Let's just do a NOFAIL allocation and the problem goes away. Cc: stable@vger.kernel.org # v6.8 Fixes: be408417630427 ("xfs: implement block reservation accounting for btrees we're staging") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/newbt.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/xfs/scrub/newbt.c b/fs/xfs/scrub/newbt.c index c82f4631fd9c..584076b2a6ee 100644 --- a/fs/xfs/scrub/newbt.c +++ b/fs/xfs/scrub/newbt.c @@ -193,9 +193,11 @@ xrep_newbt_add_blocks( struct xrep_newbt_resv *resv; int error; - resv = kmalloc_obj(struct xrep_newbt_resv, XCHK_GFP_FLAGS); - if (!resv) - return -ENOMEM; + /* + * We have no way to clean up the allocated space *and* return an + * ENOMEM if we fail to allocate this control structure. + */ + resv = kmalloc_obj(struct xrep_newbt_resv, GFP_KERNEL | __GFP_NOFAIL); INIT_LIST_HEAD(&resv->list); resv->agbno = XFS_FSB_TO_AGBNO(mp, args->fsbno); From d80993655f7be2a461c0a63e2022da5757f47dac Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:38:07 -0700 Subject: [PATCH 1890/2366] xfs: don't merge different file IO error types LOLLM noticed that we can accidentally merge file range health monitoring events even if they have different errors. We shouldn't do that. Cc: stable@vger.kernel.org # v7.0 Fixes: dfa8bad3a8796c ("xfs: convey file I/O errors to the health monitor") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index c3749675ef19..2bdd747f1ead 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -247,7 +247,9 @@ xfs_healthmon_merge_events( case XFS_HEALTHMON_DIOWRITE: case XFS_HEALTHMON_DATALOST: /* logically adjacent file ranges can merge */ - if (existing->fino != new->fino || existing->fgen != new->fgen) + if (existing->fino != new->fino || + existing->fgen != new->fgen || + existing->error != new->error) return false; if (existing->fpos + existing->flen == new->fpos) { From f8f6382ff13109e19d0fc1d0224ff7d29641e56a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:38:23 -0700 Subject: [PATCH 1891/2366] xfs: fix blockgc group quota scanning when usrquota isn't enforced LOLLM noticed the copy-paste error here -- if user quotas aren't enforced but we're near the group quota limit, we fail to set FLAG_GID and hence we might not actually free any preallocations, causing unnecessary EDQUOT. Fix that. Cc: stable@vger.kernel.org # v5.12 Fixes: c237dd7c709432 ("xfs: flush eof/cowblocks if we can't reserve quota for inode creation") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_icache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c index 82dac88e3c4c..de8be344e987 100644 --- a/fs/xfs/xfs_icache.c +++ b/fs/xfs/xfs_icache.c @@ -1653,7 +1653,7 @@ xfs_blockgc_free_dquots( do_work = true; } - if (XFS_IS_UQUOTA_ENFORCED(mp) && gdqp && xfs_dquot_lowsp(gdqp)) { + if (XFS_IS_GQUOTA_ENFORCED(mp) && gdqp && xfs_dquot_lowsp(gdqp)) { icw.icw_gid = make_kgid(mp->m_super->s_user_ns, gdqp->q_id); icw.icw_flags |= XFS_ICWALK_FLAG_GID; do_work = true; From 65f39d09d73718611cee40399179323b5d4ead00 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:38:38 -0700 Subject: [PATCH 1892/2366] xfs: fix cursor and pointer handling when recovering iunlink buckets LOLLM pointed out a bug in xlog_recover_iunlink_bucket: 1. We don't null out prev_ip after releasing it, which can lead to UAF problems if the inodegc flush call in the loop fails. at which point I noticed even more bugs: 2. If the inodegc flush inside the loop fails, we also leak @ip. 3. We set prev_agino to agino having already advanced agino, which results in inodes with i_prev_unlinked set to itself. 4. If we exit the bottom of the loop with prev_ip set, then prev_ip aliases ip and we also set its i_prev_unlinked to itself. Bugs 3 and 4 introduce loops into the unlinked list, though these loops don't surface because we immediately flush each unlinked inode after loading it. Fix all of these issues. Cc: stable@vger.kernel.org # v6.0 Fixes: 04755d2e5821b3 ("xfs: refactor xlog_recover_process_iunlinks()") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log_recover.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c index e7e49529658b..cf0d610265fe 100644 --- a/fs/xfs/xfs_log_recover.c +++ b/fs/xfs/xfs_log_recover.c @@ -2736,12 +2736,13 @@ xlog_recover_iunlink_bucket( { struct xfs_mount *mp = pag_mount(pag); struct xfs_inode *prev_ip = NULL; - struct xfs_inode *ip; xfs_agino_t prev_agino, agino; int error = 0; agino = be32_to_cpu(agi->agi_unlinked[bucket]); while (agino != NULLAGINO) { + struct xfs_inode *ip; + error = xfs_iget(mp, NULL, xfs_agino_to_ino(pag, agino), 0, 0, &ip); if (error) @@ -2750,11 +2751,11 @@ xlog_recover_iunlink_bucket( ASSERT(VFS_I(ip)->i_nlink == 0); ASSERT(VFS_I(ip)->i_mode != 0); xfs_iflags_clear(ip, XFS_IRECOVERY); - agino = ip->i_next_unlinked; if (prev_ip) { ip->i_prev_unlinked = prev_agino; xfs_irele(prev_ip); + prev_ip = NULL; /* * Ensure the inode is removed from the unlinked list @@ -2766,18 +2767,20 @@ xlog_recover_iunlink_bucket( * complete. */ error = xfs_inodegc_flush(mp); - if (error) - break; + if (error) { + xfs_irele(ip); + return error; + } } prev_agino = agino; + agino = ip->i_next_unlinked; prev_ip = ip; } if (prev_ip) { int error2; - ip->i_prev_unlinked = prev_agino; xfs_irele(prev_ip); error2 = xfs_inodegc_flush(mp); From ffb48dccce1960a9ea24463a2f3c21d124d6b672 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:38:54 -0700 Subject: [PATCH 1893/2366] xfs: drop dquot flush lock when we can't find a buffer to flush LOLLM noticed that xfs_qm_flush_one fails to drop the dquot flush lock if it can't grab the buffer associated with the dquot. Since there's no buffer, nobody else is going to drop the dqflock, so we need to do it ourselves. Cc: stable@vger.kernel.org # v6.13 Fixes: ca378189fdfa89 ("xfs: convert quotacheck to attach dquot buffers") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_qm.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c index 99a82107b8e6..54d00d543b51 100644 --- a/fs/xfs/xfs_qm.c +++ b/fs/xfs/xfs_qm.c @@ -1432,16 +1432,22 @@ xfs_qm_flush_one( error = xfs_dquot_use_attached_buf(dqp, &bp); if (error) - goto out_unlock; + goto out_dqflock; if (!bp) { error = -EFSCORRUPTED; - goto out_unlock; + goto out_dqflock; } error = xfs_qm_dqflush(dqp, bp); if (!error) xfs_buf_delwri_queue(bp, buffer_list); xfs_buf_relse(bp); + mutex_unlock(&dqp->q_qlock); + xfs_qm_dqrele(dqp); + return error; + +out_dqflock: + xfs_dqfunlock(dqp); out_unlock: mutex_unlock(&dqp->q_qlock); xfs_qm_dqrele(dqp); From 476582d754cdc5110f806001417fea6c77824c13 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:39:10 -0700 Subject: [PATCH 1894/2366] xfs: don't let hidden_space go negative in xfs_metafile_resv_init LOLLM points out that if the amount of fdblocks that we can reserve for a metadata btree file goes below the space already used by that file, then the hidden_space subtraction can underflow, causing xfs_dec_fdblocks to subtract a huge amount of space. We never want the target to be less than the used sapce, so fix the logic that adjusts dblocks_avail downwards. Also fix an error in the adjacent comment. Cc: stable@vger.kernel.org # v6.15 Fixes: 1df8d75030b787 ("xfs: make metabtree reservations global") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_metafile.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/xfs/libxfs/xfs_metafile.c b/fs/xfs/libxfs/xfs_metafile.c index 71f004e9dc64..1f54d39003c2 100644 --- a/fs/xfs/libxfs/xfs_metafile.c +++ b/fs/xfs/libxfs/xfs_metafile.c @@ -297,14 +297,14 @@ xfs_metafile_resv_init( goto out_unlock; /* - * Space taken by the per-AG metadata btrees are accounted on-disk as - * used space. We therefore only hide the space that is reserved but - * not used by the trees. + * Space taken by metadata btrees are accounted on-disk as used space. + * We therefore only hide the space that is reserved but not used by + * the trees. */ if (used > target) target = used; else if (target > dblocks_avail) - target = dblocks_avail; + target = max(dblocks_avail, used); hidden_space = target - used; error = xfs_dec_fdblocks(mp, hidden_space, true); From fe2f9135df43db849e74f03956d322ac20b59af7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 14 Sep 2026 22:39:25 -0700 Subject: [PATCH 1895/2366] xfs: fix wild memcpy access when formatting ondisk rtrefcount btree roots LOLLM noticed that the inode btree root formatting methods copy too many bytes -- there's only one set of keys in node blocks, not two. This causes memory corruption of whatever's beyond the buffers. Cc: stable@vger.kernel.org # v6.14 Fixes: f0415af60f482a ("xfs: wire up a new metafile type for the realtime refcount") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtrefcount_btree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c index e2950dbe2068..dcc89b8e149b 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c @@ -617,7 +617,7 @@ xfs_rtrefcountbt_from_disk( fpp = xfs_rtrefcount_droot_ptr_addr(dblock, 1, maxrecs); tpp = xfs_rtrefcount_broot_ptr_addr(mp, rblock, 1, rblocklen); numrecs = be16_to_cpu(dblock->bb_numrecs); - memcpy(tkp, fkp, 2 * sizeof(*fkp) * numrecs); + memcpy(tkp, fkp, sizeof(*fkp) * numrecs); memcpy(tpp, fpp, sizeof(*fpp) * numrecs); } else { frp = xfs_rtrefcount_droot_rec_addr(dblock, 1); @@ -703,7 +703,7 @@ xfs_rtrefcountbt_to_disk( fpp = xfs_rtrefcount_broot_ptr_addr(mp, rblock, 1, rblocklen); tpp = xfs_rtrefcount_droot_ptr_addr(dblock, 1, maxrecs); numrecs = be16_to_cpu(rblock->bb_numrecs); - memcpy(tkp, fkp, 2 * sizeof(*fkp) * numrecs); + memcpy(tkp, fkp, sizeof(*fkp) * numrecs); memcpy(tpp, fpp, sizeof(*fpp) * numrecs); } else { frp = xfs_rtrefcount_rec_addr(rblock, 1); From e9d810279f84b30738f7790c0ed15f8dd5b9024a Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 09:47:01 +0000 Subject: [PATCH 1896/2366] gpio: arizona: Fix runtime PM leak in arizona_gpio_direction_out() Switching a persistent GPIO line from input to output acquires a runtime PM reference on the parent device, but if the subsequent regmap_update_bits() fails the reference is never dropped and no later direction_in() can balance it since the direction was never changed. Drop the reference on the update failure path. Fixes: 27a49ed17e22 ("gpio: arizona: Add support for GPIOs that need to be maintained") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260916094701.2007509-1-vulab@iscas.ac.cn Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-arizona.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-arizona.c b/drivers/gpio/gpio-arizona.c index a7e98d395d8e..88cf013f0d90 100644 --- a/drivers/gpio/gpio-arizona.c +++ b/drivers/gpio/gpio-arizona.c @@ -115,8 +115,12 @@ static int arizona_gpio_direction_out(struct gpio_chip *chip, if (value) value = ARIZONA_GPN_LVL; - return regmap_update_bits(arizona->regmap, ARIZONA_GPIO1_CTRL + offset, - ARIZONA_GPN_DIR | ARIZONA_GPN_LVL, value); + ret = regmap_update_bits(arizona->regmap, ARIZONA_GPIO1_CTRL + offset, + ARIZONA_GPN_DIR | ARIZONA_GPN_LVL, value); + if (ret < 0 && (val & ARIZONA_GPN_DIR) && persistent) + pm_runtime_put_autosuspend(chip->parent); + + return ret; } static int arizona_gpio_set(struct gpio_chip *chip, unsigned int offset, From 02af7eac17bc62a72335c1fc8c4af4471654f6cc Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 14 Sep 2026 17:44:06 -0700 Subject: [PATCH 1897/2366] gpio: mvebu: keep resume masks within the irqchip cache mvebu_gpio_resume() writes the edge/level mask registers saved at suspend time straight back to hardware, bypassing the irqchip's mask_cache_priv. genirq skips mask_irq() for a line it already considers masked, so restoring a bit in hardware that genirq thinks is still masked leaves that line unmasked behind genirq's back. An asserted level line then has nobody to ack it, and the moment interrupts are re-enabled the chained handler storms, hanging resume. AND the restored mask values with the matching irqchip mask cache so only lines genirq currently considers unmasked are unmasked again. Read the caches under gc->lock to keep them consistent with the mask/unmask handlers. Tested on Helios4 (armhf): 5 suspend cycles woken by magic packet, no hang; mvebu_gpio_resume() returns in 6 usecs. Assisted-by: LLM Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260915004406.115230-1-rosenp@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mvebu.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c index 93b8a08b04b9..a2796240fe14 100644 --- a/drivers/gpio/gpio-mvebu.c +++ b/drivers/gpio/gpio-mvebu.c @@ -1034,6 +1034,8 @@ static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state) static int mvebu_gpio_resume(struct platform_device *pdev) { struct mvebu_gpio_chip *mvchip = platform_get_drvdata(pdev); + u32 edge_cache = ~0U, level_cache = ~0U; + unsigned long flags; int i; regmap_write(mvchip->regs, GPIO_OUT_OFF + mvchip->offset, @@ -1045,32 +1047,51 @@ static int mvebu_gpio_resume(struct platform_device *pdev) regmap_write(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset, mvchip->in_pol_reg); + /* + * genirq skips mask_irq() for a line it already considers masked, so + * unmasking one behind its back leaves an asserted level line that + * nobody masks. Restore only bits the irqchip cache still has set. + * + * Snapshot the caches under the raw spinlock, but release it before + * the regmap writes below: regmap_write() takes a sleepable lock on + * PREEMPT_RT. + */ + if (mvchip->domain) { + struct irq_chip_generic *gc; + + gc = irq_get_domain_generic_chip(mvchip->domain, 0); + raw_spin_lock_irqsave(&gc->lock, flags); + level_cache = gc->chip_types[0].mask_cache_priv; + edge_cache = gc->chip_types[1].mask_cache_priv; + raw_spin_unlock_irqrestore(&gc->lock, flags); + } + switch (mvchip->soc_variant) { case MVEBU_GPIO_SOC_VARIANT_ORION: case MVEBU_GPIO_SOC_VARIANT_A8K: regmap_write(mvchip->regs, GPIO_EDGE_MASK_OFF + mvchip->offset, - mvchip->edge_mask_regs[0]); + mvchip->edge_mask_regs[0] & edge_cache); regmap_write(mvchip->regs, GPIO_LEVEL_MASK_OFF + mvchip->offset, - mvchip->level_mask_regs[0]); + mvchip->level_mask_regs[0] & level_cache); break; case MVEBU_GPIO_SOC_VARIANT_MV78200: for (i = 0; i < 2; i++) { regmap_write(mvchip->regs, GPIO_EDGE_MASK_MV78200_OFF(i), - mvchip->edge_mask_regs[i]); + mvchip->edge_mask_regs[i] & edge_cache); regmap_write(mvchip->regs, GPIO_LEVEL_MASK_MV78200_OFF(i), - mvchip->level_mask_regs[i]); + mvchip->level_mask_regs[i] & level_cache); } break; case MVEBU_GPIO_SOC_VARIANT_ARMADAXP: for (i = 0; i < 4; i++) { regmap_write(mvchip->regs, GPIO_EDGE_MASK_ARMADAXP_OFF(i), - mvchip->edge_mask_regs[i]); + mvchip->edge_mask_regs[i] & edge_cache); regmap_write(mvchip->regs, GPIO_LEVEL_MASK_ARMADAXP_OFF(i), - mvchip->level_mask_regs[i]); + mvchip->level_mask_regs[i] & level_cache); } break; default: From c51e89c5b5db3e1927738fad7cd18b66dde68aed Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 15 Sep 2026 17:21:59 +0100 Subject: [PATCH 1898/2366] netfs, afs: Fix symlink reading Fix the reading of symlinks from the cache in afs by making netfslib trim the amount read down to i_size. The problem is that afs sets the size of the iterator to the size of the buffer (PAGE_SIZE) so that the cache can round the read size up to the cache's DIO size. Note that this also impacts the reading of AFS mountpoints as they're just stored as symlinks with an odd file mode. Link: https://patch.msgid.link/3912795.1789489319@warthog.procyon.org.uk Fixes: c0410adf3da6 ("afs: Fix the locking used by afs_get_link()") Reviewed-by: Paulo Alcantara cc: Paulo Alcantara cc: Marc Dionne cc: linux-afs@lists.infradead.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/read_collect.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 5cf22087d243..a94197ef0181 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -435,6 +435,11 @@ static void netfs_rreq_assess_single(struct netfs_io_request *rreq) netfs_single_mark_inode_dirty(rreq->inode); } + /* To do DIO, the cache has to round the size up, so we need to undo + * the rounding. + */ + rreq->transferred = min(rreq->transferred, rreq->i_size); + if (rreq->iocb) { rreq->iocb->ki_pos += rreq->transferred; if (rreq->iocb->ki_complete) { From 7459c021874246c196f397686e100702f059b9e7 Mon Sep 17 00:00:00 2001 From: Julian Sun Date: Tue, 15 Sep 2026 12:49:12 +0800 Subject: [PATCH 1899/2366] fs: avoid repeated scans in evict_inodes() We observed hung tasks when users attempted to unmount a filesystem after its disk had been removed while still in use. During device removal, fs_bdev_mark_dead() calls evict_inodes() while holding s_umount. Each time evict_inodes() drops s_inode_list_lock to reschedule, it restarts the walk from the head of s_inodes. With many referenced inodes at the head of the list, these restarts repeatedly scan the same inodes without reclaiming them. This can keep s_umount held for a long time, blocking concurrent umount attempts and triggering hung-task reports. Keep the current inode, already marked I_FREEING, out of the disposal batch until s_inode_list_lock is reacquired. Resume the walk from this inode and dispose of it in a later batch or at the end of the walk. The zero-refcount and state checks under i_lock allow this walker to claim the inode by setting I_FREEING and removing it from the LRU. Other reclaimers skip the inode, leaving this walker responsible for eviction. Only evict() removes it from s_inodes, so keeping it out of the disposal batch ensures that it remains on the list while the lock is dropped. After reacquiring the lock, reading its current next pointer accounts for concurrent removal of following inodes. The existing inode lifetime rules prohibit acquiring a reference to an inode marked I_FREEING or I_WILL_FREE. __iget() requires its caller to hold i_lock and establish that taking a reference is valid. Inode lookup and igrab() check these flags under i_lock when acquiring a reference from zero. ihold() requires an existing reference, which would keep i_count nonzero and prevent this walker from claiming the inode. These rules already allow iput_final() and the inode shrinker to release i_lock after setting I_FREEING and before eviction completes. A temporary __iget() reference would also keep the inode on the list, but its release must preserve last-reference handling. Another user can acquire a reference, update lazy timestamps and drop its reference while the pin is held. If the pin becomes the last reference, dropping it with atomic_dec_and_test() and evicting directly bypasses iput()'s lazytime handling and can lose those timestamp updates. Releasing the pin with iput() preserves that handling, but does not guarantee eviction. fs_bdev_mark_dead() runs with SB_ACTIVE set, so iput() may retain the inode in cache, whereas evict_inodes() must evict eligible zero-reference inodes. The inode may also have been freed when iput() returns, so the walker cannot then use it to force eviction. Using I_FREEING preserves the existing eviction behavior without introducing an additional last-reference transition. The xfstests auto group passed on ext4 and XFS with known unrelated failures excluded. No new issues were observed, and the previously reproducible hung task no longer occurs with this patch. Fixes: ac05fbb40062 ("inode: don't softlockup when evicting inodes") Signed-off-by: Julian Sun Link: https://patch.msgid.link/20260915044912.3183440-1-sunjunchao@bytedance.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/inode.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/fs/inode.c b/fs/inode.c index ba7da39be4a3..a9d37be390a1 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -880,7 +880,6 @@ void evict_inodes(struct super_block *sb) struct inode *inode; LIST_HEAD(dispose); -again: spin_lock(&sb->s_inode_list_lock); list_for_each_entry(inode, &sb->s_inodes, i_sb_list) { if (icount_read_once(inode)) @@ -899,19 +898,19 @@ void evict_inodes(struct super_block *sb) inode_state_set(inode, I_FREEING); inode_lru_list_del(inode); spin_unlock(&inode->i_lock); - list_add(&inode->i_lru, &dispose); /* - * We can have a ton of inodes to evict at unmount time given - * enough memory, check to see if we need to go to sleep for a - * bit so we don't livelock. + * Keep this inode out of dispose so it stays on s_inodes while + * the list lock is dropped. I_FREEING prevents new references + * and leaves eviction to us, so we can resume the walk from it. */ if (need_resched()) { spin_unlock(&sb->s_inode_list_lock); cond_resched(); dispose_list(&dispose); - goto again; + spin_lock(&sb->s_inode_list_lock); } + list_add(&inode->i_lru, &dispose); } spin_unlock(&sb->s_inode_list_lock); From f6988c90671e83db79df1b7b9d6fdb0e5947fd84 Mon Sep 17 00:00:00 2001 From: "Patrick Lu (Anthropic)" Date: Fri, 11 Sep 2026 18:49:49 +0000 Subject: [PATCH 1900/2366] writeback: bound cleanup_offline_cgwb() rescans by rotating scanned inodes cleanup_offline_cgwb() prepares at most WB_MAX_INODES_PER_ISW inodes per call and is called again until the dying wb is drained, but every call walks wb->b_attached and then wb->b_dirty_time from the same end. Inodes already prepared (they stay on the list with I_WB_SWITCH set until the switch worker runs) and inodes that cannot be switched (I_FREEING, I_WILL_FREE, !SB_ACTIVE, DAX, already on the target wb) stay where they are, so each pass rescans a growing run of them under wb->list_lock and a full drain is quadratic in the number of inodes on the list. With ~17M inodes attached to one dying cgwb we saw this end in soft lockups, with CPUs reported stuck for 21-48s. Walk both lists from the oldest end and move every scanned inode to the newest end, so the next pass starts where the previous one stopped and the drain becomes linear. b_attached is unordered, so nobody sees the reorder there. b_dirty_time is ordered by dirtied_when, but the oldest unscanned inode stays at the end move_expired_inodes() picks from, sync takes the whole list regardless of order, and prepared inodes leave the list as soon as the switch work runs and get a new dirtied_time_when on the new wb anyway, so the only inodes left out of order are the ones that can never switch (DAX), and only on the dying wb. Fixes: c22d70a162d3 ("writeback, cgroup: release dying cgwbs by switching attached inodes") Cc: stable@vger.kernel.org Acked-by: Tejun Heo Acked-by: Roman Gushchin Signed-off-by: Patrick Lu (Anthropic) Link: https://patch.msgid.link/20260911-wb-cgwb-rotate-v2-1-a9ab253a1295@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/fs-writeback.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index e744f9f9d43f..ea3eb40bf828 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -727,19 +727,34 @@ static bool isw_prepare_wbs_switch(struct bdi_writeback *new_wb, struct inode_switch_wbs_context *isw, struct list_head *list, int *nr) { - struct inode *inode; + struct inode *inode, *tmp; + LIST_HEAD(scanned); + bool full = false; + + /* + * Walk from the oldest end and move scanned inodes to the newest + * end, so the next scan resumes at unscanned inodes instead of + * re-walking an ever-growing run of prepared and skipped ones. + * For b_dirty_time this keeps the oldest unscanned inode at the + * end move_expired_inodes() picks from; b_attached is unordered. + */ + list_for_each_entry_safe_reverse(inode, tmp, list, i_io_list) { + list_move(&inode->i_io_list, &scanned); - list_for_each_entry(inode, list, i_io_list) { if (!inode_prepare_wbs_switch(inode, new_wb)) continue; isw->inodes[*nr] = inode; (*nr)++; - if (*nr >= WB_MAX_INODES_PER_ISW - 1) - return true; + if (*nr >= WB_MAX_INODES_PER_ISW - 1) { + full = true; + break; + } } - return false; + list_splice(&scanned, list); + + return full; } /** From 9ca4ba24259183ce15665be86b2956cd896c4687 Mon Sep 17 00:00:00 2001 From: James Clark Date: Tue, 15 Sep 2026 11:58:17 +0700 Subject: [PATCH 1901/2366] net: macb: fix ordering around PTP timestamp read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PTP_SYS_OFFSET_EXTENDED returns system timestamps that do not correctly bracket the PHC register read on MACB/GEM. On a Raspberry Pi 5, the returned interval can be as short as 37 ns, while an ordered register read takes approximately 1 us. This biases the midpoint used by phc2sys, causing CLOCK_REALTIME to run approximately 0.5 us ahead when synchronized to the PHC. gem_tsu_get_time() reads the nanoseconds register using the driver's relaxed MMIO accessor. On weakly ordered systems, the subsequent system timestamp can be taken before the register read completes. The internal smp_rmb() in the pre-timestamp path also does not guarantee ordering against the subsequent MMIO read. Add rmb() before and after the bracketed nanoseconds read in both the normal and seconds rollover paths so the system timestamps bracket the PHC read. Adding the post-read barrier increases the minimum interval on the same Raspberry Pi 5 to approximately 1 us. Fixes: e51bb5c2784c ("net: macb: ptp: Switch to gettimex64() interface") Tested-by: Nicolai Buchwitz # Raspberry Pi CM5, min bracket 37 ns -> 981 ns Reviewed-by: Nicolai Buchwitz Reviewed-by: Théo Lebrun Assisted-by: LLM Signed-off-by: James Clark Link: https://patch.msgid.link/20260915045823.76100-1-jjc@jclark.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cadence/macb_ptp.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/ethernet/cadence/macb_ptp.c b/drivers/net/ethernet/cadence/macb_ptp.c index 6d9166389988..14ae57fa00cb 100644 --- a/drivers/net/ethernet/cadence/macb_ptp.c +++ b/drivers/net/ethernet/cadence/macb_ptp.c @@ -50,7 +50,12 @@ static int gem_tsu_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts, spin_lock_irqsave(&bp->tsu_clk_lock, flags); ptp_read_system_prets(sts); + /* explicit barriers are needed because gem_readl() is relaxed */ + if (sts) + rmb(); first = gem_readl(bp, TN); + if (sts) + rmb(); ptp_read_system_postts(sts); secl = gem_readl(bp, TSL); sech = gem_readl(bp, TSH); @@ -62,7 +67,11 @@ static int gem_tsu_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts, * (assume all done within 1s) */ ptp_read_system_prets(sts); + if (sts) + rmb(); ts->tv_nsec = gem_readl(bp, TN); + if (sts) + rmb(); ptp_read_system_postts(sts); secl = gem_readl(bp, TSL); sech = gem_readl(bp, TSH); From 14cb1e7702e5cb3c58888f6aed498381a73927d2 Mon Sep 17 00:00:00 2001 From: Dmitriy Okunev Date: Mon, 14 Sep 2026 12:15:57 +0300 Subject: [PATCH 1902/2366] net: mvpp2: prevent buffer overflow in page_pool allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per‑processor buffering scheme is supported only if the number of pools (nrxqs * 2) does not exceed MVPP2_BM_MAX_POOLS (8). This is already checked in mvpp2_probe() during the initial activation of percpu_pools. However, mvpp2_change_mtu() may later call mvpp2_bm_switch_buffers(priv, true) without this check, which can lead to an out-of-bounds access in the priv->page_pool array in mvpp2_bm_init(). The array is sized to hold MVPP2_PORT_MAX_RXQ entries, and mvpp2_get_nrxqs() may return exactly that value. The per-CPU scheme then doubles it to nrxqs * 2, exceeding the array bounds. Check that the hardware version is MVPP22 or newer and that the number of pools (nrxqs * 2) does not exceed MVPP2_BM_MAX_POOLS before switching to per-CPU mode. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 7d04b0b13b11 ("mvpp2: percpu buffers") Signed-off-by: Dmitriy Okunev Link: https://patch.msgid.link/20260914091557.71769-1-dokunevdmitriy@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c index ccc24a1301f2..848ee655c6ea 100644 --- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c +++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c @@ -5086,7 +5086,8 @@ static int mvpp2_change_mtu(struct net_device *dev, int mtu) netdev_warn(dev, "mtu %d too high, switching to shared buffers", mtu); mvpp2_bm_switch_buffers(priv, false); } - } else { + } else if (priv->hw_version >= MVPP22 && + mvpp2_get_nrxqs(priv) * 2 <= MVPP2_BM_MAX_POOLS) { bool jumbo = false; int i; From d798162eb364df2e77a56fdbe5bae54440152d3b Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 15 Sep 2026 14:30:47 -0700 Subject: [PATCH 1903/2366] dpll: reject a reference sync pin which is not on the pin's dpll dpll_pin_ref_sync_state_set() resolves the partner's driver private data with dpll_pin_on_dpll_priv() and passes the result to ref_sync_get() and ref_sync_set() without looking at it. The helper returns NULL when the partner holds no ref on that dpll. Of the two drivers implementing the feature only zl3073x dereferences the pointer (sync_pin->id); ice ignores it, so ice cannot fault here. The NULL is a teardown race, not a steady state - zl3073x registers every input pin with every channel, so the partner is normally present on the dpll the base pin resolves to. zl3073x_dev_stop() unregisters pins one at a time, taking and dropping dpll_lock for each, and between the partner's turn and the base pin's the partner is out of that dpll's pin_refs while still registered with the channels not yet torn down, so dpll_pin_available() keeps passing. That path is not only driver removal: devlink reload and devlink dev flash both run zl3073x_dev_stop(). Reproduced by holding that state open with a mock dpll device, which is where the frame name comes from: BUG: kernel NULL pointer dereference, address: 0000000000000000 Oops: Oops: 0000 [#1] SMP NOPTI RIP: 0010:mock_ref_sync_get+0x5/0x30 Call Trace: dpll_pin_ref_sync_set+0x19f/0x4a0 dpll_nl_pin_set_doit+0x17d/0x840 genl_family_rcv_msg_doit+0xd6/0x130 genl_rcv_msg+0x181/0x2b0 netlink_rcv_skb+0x55/0x100 genl_rcv+0x23/0x30 netlink_unicast+0x24d/0x370 netlink_sendmsg+0x1e2/0x420 __sys_sendto+0x1db/0x1f0 __x64_sys_sendto+0x1f/0x30 do_syscall_64+0xe1/0x490 Commit d2e914a4a0d0 ("dpll: fix NULL pointer dereference in dpll_msg_add_pin_ref_sync()") added the same guard to the read side, which the kernel walks into by itself because the delete notification is emitted from inside the unregister; the write side needs a pin-set to land in the window and was left alone. Test the priv rather than look up pin_refs directly, so that the two halves key off the same condition. Fixes: 58256a26bfb3 ("dpll: add reference sync get/set") Signed-off-by: Jakub Kicinski Reviewed-by: Ivan Vecera Link: https://patch.msgid.link/20260915213047.1352286-1-kuba@kernel.org Signed-off-by: Paolo Abeni --- drivers/dpll/dpll_netlink.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 523d76a5fd49..45365214fbef 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -1202,6 +1202,7 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, const enum dpll_pin_state state, struct netlink_ext_ack *extack) { + void *pin_priv, *ref_sync_pin_priv; const struct dpll_pin_ops *ops; enum dpll_pin_state old_state; struct dpll_pin *ref_sync_pin; @@ -1230,9 +1231,15 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, return -EOPNOTSUPP; } dpll = ref->dpll; - ret = ops->ref_sync_get(pin, dpll_pin_on_dpll_priv(dpll, pin), - ref_sync_pin, - dpll_pin_on_dpll_priv(dpll, ref_sync_pin), + pin_priv = dpll_pin_on_dpll_priv(dpll, pin); + ref_sync_pin_priv = dpll_pin_on_dpll_priv(dpll, ref_sync_pin); + /* Pin may have been unregistered from this dpll already */ + if (!ref_sync_pin_priv) { + NL_SET_ERR_MSG(extack, + "reference sync pin not registered with the dpll"); + return -ENODEV; + } + ret = ops->ref_sync_get(pin, pin_priv, ref_sync_pin, ref_sync_pin_priv, &old_state, extack); if (ret) { NL_SET_ERR_MSG(extack, "unable to get old reference sync state"); @@ -1241,9 +1248,7 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, if (state == old_state) return 0; - ret = ops->ref_sync_set(pin, dpll_pin_on_dpll_priv(dpll, pin), - ref_sync_pin, - dpll_pin_on_dpll_priv(dpll, ref_sync_pin), + ret = ops->ref_sync_set(pin, pin_priv, ref_sync_pin, ref_sync_pin_priv, state, extack); if (ret) { NL_SET_ERR_MSG_FMT(extack, From f81e6c3fb06327bc49cdd6e559845293ba06a704 Mon Sep 17 00:00:00 2001 From: Inbal Schussheim Date: Mon, 14 Sep 2026 12:04:07 +0300 Subject: [PATCH 1904/2366] tcp: exclude old ACKs from tcp fast path Exclude old ACKs before SND.UNA from the tcp fast path as well as ACKs after SND.NXT. Such ACKs will fall through to the slow path, where tcp_ack() performs the appropriate validation and challenge ACK handling according to RFC5961 and Commit 3d501dd326fb1c7 ("tcp: do not accept ACK of bytes we never sent"). This prevents old ACKs from being accepted or modifying connection state as part of the fast path before appropriate ACK validation is applied. In particular, this prevents payload carried by a segment with an excessively old ACK from advancing RCV.NXT before the ACK is rejected. Fixes: 31770e34e43d ("tcp: Revert "tcp: remove header prediction"") Reported-by: Amit Klein Reported-by: Tamir Shahar Reported-by: Inbal Schussheim Suggested-by: Eric Dumazet Cc: stable@vger.kernel.org Signed-off-by: Inbal Schussheim Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260914090408.1435080-2-inbal.lipshtat@mail.huji.ac.il Signed-off-by: Paolo Abeni --- net/ipv4/tcp_input.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 0f60a1dbf927..92bc60716f33 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -6490,6 +6490,7 @@ static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb, * or pure receivers (this means either the sequence number or the ack * value must stay constant) * - Unexpected TCP option. + * - ACK sequence number is outside [SND.UNA, SND.NXT]. * * When these conditions are not satisfied it drops into a standard * receive procedure patterned after RFC793 to handle all cases. @@ -6539,7 +6540,7 @@ void tcp_rcv_established(struct sock *sk, struct sk_buff *skb) if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags && TCP_SKB_CB(skb)->seq == tp->rcv_nxt && - !after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) { + between(TCP_SKB_CB(skb)->ack_seq, tp->snd_una, tp->snd_nxt)) { int tcp_header_len = tp->tcp_header_len; s32 delta = 0; int flag = 0; From d841cd7513f3d48018175ecb1fb972cfd3c3c10b Mon Sep 17 00:00:00 2001 From: Inbal Schussheim Date: Mon, 14 Sep 2026 12:04:08 +0300 Subject: [PATCH 1905/2366] selftests: net: packetdrill: test exclusion of old ACK from TCP fast path Add a packetdrill test for an in-sequence data segment carrying an excessively old ACK. Verify that the segment falls through from the TCP fast path to the slow path, where the existing ACK validation rejects it and sends a challenge ACK. The payload is not accepted and RCV.NXT remains unchanged. Based on the reproducer from Commit 3d501dd326fb ("tcp: do not accept ACK of bytes we never sent"). Signed-off-by: Inbal Schussheim Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260914090408.1435080-3-inbal.lipshtat@mail.huji.ac.il Signed-off-by: Paolo Abeni --- .../tcp_rfc5961_reject-old-ack.pkt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tools/testing/selftests/net/packetdrill/tcp_rfc5961_reject-old-ack.pkt diff --git a/tools/testing/selftests/net/packetdrill/tcp_rfc5961_reject-old-ack.pkt b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_reject-old-ack.pkt new file mode 100644 index 000000000000..32dd9de1d366 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_reject-old-ack.pkt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 + +`./defaults.sh +sysctl -q net.ipv4.tcp_invalid_ratelimit=0 +` + +// Test rejection of data segments carrying excessively old ACKs + +0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 ++0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 ++0 bind(3, ..., ...) = 0 ++0 listen(3, 1024) = 0 + +// ---------------- Handshake ------------------- // ++0 < S 0:0(0) win 65535 ++0 > S. 0:0(0) ack 1 <...> ++0 < . 1:1(0) ack 1 win 65535 ++0 accept(3, ..., ...) = 4 + +// Populate receive memory so the following segment can use +// header prediction. ++0 < P. 1:501(500) ack 1 win 65535 ++0 > . 1:1(0) ack 501 + +// Send an in-sequence data segment carrying an excessively old ACK. ++0 < P. 501:1501(1000) ack 2794967397 win 65535 + +// Challenge ACK; RCV.NXT must remain 501. ++0 > . 1:1(0) ack 501 From a5117e1eccac6ee3bd4aed7cacf8ebcb6b3eb309 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 15 Sep 2026 13:04:23 +0000 Subject: [PATCH 1906/2366] net: skbuff: do not leave stale header offsets after pskb_carve() pskb_carve_inside_header() and pskb_carve_inside_nonlinear() remove the first bytes of a packet and reallocate skb->head. All the headers that were present before the operation are gone, but both functions call skb_headers_offset_update(skb, 0), which is a no-op : skb->mac_header, skb->network_header, skb->transport_header and skb->csum_start keep their old values and now describe bytes which are no longer there. Both helpers size the new head from the old skb_end_offset(), so the stale offsets still land inside the new allocation. They point past skb_tail_pointer() though, to bytes that were never initialized. pskb_carve_inside_nonlinear() is the worst case, because it leaves a zombie skb with an empty linear part (skb->data == skb_tail_pointer(skb), skb_headlen(skb) == 0), while skb_mac_header_was_set() is still true and skb->mac_header is way ahead of skb->data. The only user of pskb_extract() is rds_tcp_data_recv(), and the carved skb is queued on tinc->ti_skb_list. When the RDS incoming message is released, rds_tcp_inc_free() calls skb_queue_purge(), which frees the skbs with SKB_DROP_REASON_QUEUE_PURGE. This is visible from drop_monitor, which then tries to pull back to the (bogus) mac header : skbuff: __skb_pull(len=234) skb len=6968 data_len=6968 headroom=0 headlen=0 tailroom=0 end-tail=384 mac=(234,14) mac_len=14 net=(248,40) trans=288 shinfo(txflags=0 nr_frags=1 gso(size=1428 type=16 segs=5)) csum(0x100120 start=288 offset=16 ip_summed=3 complete_sw=0 valid=1 level=0) hash(0x7b446c6c sw=0 l4=1) proto=0x86dd pkttype=0 iif=60 kernel BUG at ./include/linux/skbuff.h:2847! Add skb_carve_reset_headers() to mark the mac and transport headers as not set, reset the network header, clear skb->mac_len, and drop a now meaningless CHECKSUM_PARTIAL (csum_start no longer describes anything). Invalidate the inner offsets as well. Unlike mac_header and transport_header they have no "unset" sentinel, so a leftover non-zero value still looks like a real header. Zero skb->inner_mac_header, skb->inner_network_header, skb->inner_transport_header, skb->inner_protocol and skb->encapsulation, so that all the header state is invalidated in one place. v2: fixed an inaccurate changelog. The stale offsets stay inside the new skb->head, which is never smaller than the old one, they simply point past skb_tail_pointer() to bytes that are gone. Thanks to Xuanqiang Luo for insisting on this. Also invalidate the inner header state, as suggested by the netdev AI review : https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260911114922.621937-1-edumazet%40google.com Fixes: 6fa01ccd8830 ("skbuff: Add pskb_extract() helper function") Reported-by: syzbot+586af68eb819833c2d91@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6aa3e9d3.f2639fcc.29487d.0028.GAE@google.com/ Cc: Xuanqiang Luo Cc: Allison Henderson Cc: rds-devel@oss.oracle.com Signed-off-by: Eric Dumazet Reviewed-by: Xuanqiang Luo Link: https://patch.msgid.link/20260915130423.3956471-1-edumazet@google.com Signed-off-by: Paolo Abeni --- net/core/skbuff.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index cc3b4b70288b..609f2c7f4a47 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -6832,6 +6832,34 @@ struct sk_buff *alloc_skb_with_frags(unsigned long header_len, } EXPORT_SYMBOL(alloc_skb_with_frags); +/* pskb_carve_inside_header() and pskb_carve_inside_nonlinear() + * remove the first bytes of a packet and reallocate skb->head. + * + * Whatever headers were present before the operation are gone, + * we must not leave stale offsets, otherwise users of this skb + * (skb_dump(), drop_monitor, taps, ...) would read or pull garbage. + */ +static void skb_carve_reset_headers(struct sk_buff *skb) +{ + skb_unset_mac_header(skb); + skb_unset_transport_header(skb); + skb_reset_network_header(skb); + skb->mac_len = 0; + + /* Inner offsets have no "unset" marker, zero them so that + * skb_inner_network_header_was_set() becomes false and no + * consumer mistakes them for a real (and long gone) header. + */ + skb->inner_mac_header = 0; + skb->inner_network_header = 0; + skb->inner_transport_header = 0; + skb->inner_protocol = 0; + skb->encapsulation = 0; + + if (skb->ip_summed == CHECKSUM_PARTIAL) + skb->ip_summed = CHECKSUM_NONE; +} + /* carve out the first off bytes from skb when off < headlen */ static int pskb_carve_inside_header(struct sk_buff *skb, const u32 off, const int headlen, gfp_t gfp_mask) @@ -6887,7 +6915,7 @@ static int pskb_carve_inside_header(struct sk_buff *skb, const u32 off, skb->head_frag = 0; skb_set_end_offset(skb, size); skb_set_tail_pointer(skb, skb_headlen(skb)); - skb_headers_offset_update(skb, 0); + skb_carve_reset_headers(skb); skb->cloned = 0; skb->hdr_len = 0; skb->nohdr = 0; @@ -7027,7 +7055,7 @@ static int pskb_carve_inside_nonlinear(struct sk_buff *skb, const u32 off, skb->data = data; skb_set_end_offset(skb, size); skb_reset_tail_pointer(skb); - skb_headers_offset_update(skb, 0); + skb_carve_reset_headers(skb); skb->cloned = 0; skb->hdr_len = 0; skb->nohdr = 0; From 1fca688e9443003e33cf30453e7a7560367656c9 Mon Sep 17 00:00:00 2001 From: shechenglong Date: Mon, 7 Sep 2026 11:51:47 +0800 Subject: [PATCH 1907/2366] drm/client: fix restore of partially initialized client I got a null-ptr-deref report when closing a DRM file descriptor: WARNING: drivers/gpu/drm/drm_atomic.c:2031 at __drm_atomic_helper_set_config+0x18e/0x1b0 [drm] Call Trace: drm_client_modeset_commit_atomic+0x16b/0x220 [drm] drm_client_modeset_commit_locked+0x56/0x160 [drm] drm_client_modeset_commit+0x21/0x40 [drm] __drm_fb_helper_restore_fbdev_mode_unlocked.part.0+0x7b/0x80 drm_fbdev_client_restore+0xe/0x20 [drm_client_lib] drm_client_dev_restore+0x9f/0xc0 [drm] drm_release+0xc5/0xe0 [drm] The warning is followed by a NULL pointer dereference: BUG: kernel NULL pointer dereference, address: 0000000000000008 RIP: __drm_fb_helper_restore_fbdev_mode_unlocked.part.0+0x41/0x80 [drm_kms_helper] Call Trace: drm_fbdev_client_restore+0xe/0x20 [drm_client_lib] drm_client_dev_restore+0x9f/0xc0 [drm] drm_release+0xc5/0xe0 [drm] __fput+0xdc/0x2b0 __x64_sys_close+0x39/0x80 do_syscall_64+0x8d/0x460 entry_SYSCALL_64_after_hwframe+0x76/0x7e drm_client_register() adds the DRM client to the device client list before invoking the initial hotplug callback. If the hotplug callback fails, the client remains registered. For the fbdev client, a failure during drm_fb_helper_initial_config() causes the partially initialized fbdev helper to be cleaned up. drm_fb_helper_fini() releases fb_helper->info and leaves it NULL. The fbdev client therefore remains registered even though there is no fully initialized framebuffer device. Later, when userspace closes the DRM file descriptor, drm_release() can invoke the restore callbacks of registered DRM clients: drm_release() drm_client_dev_restore() drm_fbdev_client_restore() drm_fb_helper_restore_fbdev_mode_unlocked() drm_fbdev_client_restore() currently restores the fbdev state unconditionally. For a partially initialized fbdev client this can submit an incomplete modeset state and subsequently access fbdev state which has not been initialized, resulting in the warning and NULL pointer dereference above. drm_fbdev_client_unregister() already uses fb_helper->info to distinguish a fully probed framebuffer device from a partially initialized client. Use the same condition in drm_fbdev_client_restore() and skip restore if no framebuffer device has been successfully initialized. Signed-off-by: shechenglong Reviewed-by: Thomas Zimmermann Fixes: 5d08c44e47b9 ("drm/fbdev: Add memory-agnostic fbdev client") Signed-off-by: Thomas Zimmermann Cc: # v6.13+ Link: https://patch.msgid.link/20260907035147.1339-1-shechenglong@xfusion.com --- drivers/gpu/drm/clients/drm_fbdev_client.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/clients/drm_fbdev_client.c b/drivers/gpu/drm/clients/drm_fbdev_client.c index 91d196a397cf..1c16bc1084c4 100644 --- a/drivers/gpu/drm/clients/drm_fbdev_client.c +++ b/drivers/gpu/drm/clients/drm_fbdev_client.c @@ -42,6 +42,14 @@ static int drm_fbdev_client_restore(struct drm_client_dev *client, bool force) { struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); + /* + * The client is registered before the initial fbdev probe. + * If probing failed, the client remains registered but there + * is no valid fbdev framebuffer to restore. + */ + if (!fb_helper->info || !fb_helper->fb) + return 0; + drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper, force); return 0; From 2b0f561f21b27c40c91ea4975268a06092bd7e9c Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Thu, 17 Sep 2026 15:05:57 +0200 Subject: [PATCH 1908/2366] mptcp: avoid unneeded actions on subflow reset Once in a blue moon, the mptcp receive path can recursively call mptcp_data_ready() via state change under unlucky error conditions, and then try to hold the data lock again. Break the recursion loop explicitly checking for the exceptional condition. Add a new flag instead of using an existing one like 'closing', to exit early in subflow_state_change(), and explicitly flush the RX queue at reset time. This avoids unneeded processing to check for available data -- calling get_mapping_status() and more on a dying subflow -- but also in error reporting and worker scheduling. Note that we must consume the currently peeked skb before invoking mptcp_dss_corruption to avoid consuming it again after the eventual reset has freed it. Fixes: e32d262c89e2 ("mptcp: handle consistently DSS corruption") Cc: stable@vger.kernel.org Reported-by: Xinyang Ge Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260917-net-mptcp-misc-fixes-7-3-rc4-v2-1-0cf5c72667c8@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 4 ++-- net/mptcp/protocol.h | 3 ++- net/mptcp/subflow.c | 11 +++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 8dc25ef1542c..d9fc3be9d2db 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -856,12 +856,12 @@ static bool __mptcp_move_skbs_from_subflow(struct mptcp_sock *msk, mptcp_dss_corruption(msk, ssk); } } else { + sk_eat_skb(ssk, skb); + if (unlikely(!fin)) { DEBUG_NET_WARN_ON_ONCE(1); mptcp_dss_corruption(msk, ssk); } - - sk_eat_skb(ssk, skb); } WRITE_ONCE(tp->copied_seq, seq); diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 2b4c27426477..0384d6a023f9 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -585,7 +585,8 @@ struct mptcp_subflow_context { is_mptfo : 1, /* subflow is doing TFO */ close_event_done : 1, /* has done the post-closed part */ mpc_drop : 1, /* the MPC option has been dropped in a rtx */ - __unused : 9; + resetting : 1, /* subflow is resetting */ + __unused : 8; bool data_avail; bool scheduled; bool pm_listener; /* a listener managed by the kernel PM? */ diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c index 01db7edce18a..f0a6725d2c37 100644 --- a/net/mptcp/subflow.c +++ b/net/mptcp/subflow.c @@ -438,6 +438,10 @@ void mptcp_subflow_reset(struct sock *ssk) /* must hold: tcp_done() could drop last reference on parent */ sock_hold(sk); + subflow->resetting = 1; + + /* No need to delay the actual close for to-be discarded data. */ + __skb_queue_purge(&ssk->sk_receive_queue); mptcp_send_active_reset_reason(ssk); tcp_done(ssk); if (!test_and_set_bit(MPTCP_WORK_CLOSE_SUBFLOW, &mptcp_sk(sk)->flags)) @@ -1883,6 +1887,13 @@ static void subflow_state_change(struct sock *sk) __subflow_state_change(sk); + /* Rx queue processing is unneeded, error reporting will take place at + * __mptcp_close_ssk() time and subflow reset can't happen in case of + * fallback: subflow_sched_work_if_closed() would be a no-op. + */ + if (subflow->resetting) + return; + /* as recvmsg() does not acquire the subflow socket for ssk selection * a fin packet carrying a DSS can be unnoticed if we don't trigger * the data available machinery here. From 42064de57fb83231fcc89663a94885f228a1ee53 Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Thu, 17 Sep 2026 15:05:58 +0200 Subject: [PATCH 1909/2366] mptcp: close race between scheduler and state change The mptcp scheduler may race with subflow sockets state change: data transmission on the selected socket may fail and a later release could try to use mss_now reset to 0 for a divide operation. Address the issue by explicitly checking for the critical scenario. Fixes: c886d70286bf ("mptcp: do not queue data on closed subflows") Cc: stable@vger.kernel.org Reported-by: Shardul Bankar Reported-by: Xinyang Ge Closes: https://lore.kernel.org/20260525194828.1137119-1-shardul.b@mpiricsoftware.com Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260917-net-mptcp-misc-fixes-7-3-rc4-v2-2-0cf5c72667c8@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index d9fc3be9d2db..577d0134b9ec 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -1664,7 +1664,9 @@ struct sock *mptcp_subflow_get_send(struct mptcp_sock *msk) static void mptcp_push_release(struct sock *ssk, struct mptcp_sendmsg_info *info) { - tcp_push(ssk, 0, info->mss_now, tcp_sk(ssk)->nonagle, info->size_goal); + if (info->mss_now) + tcp_push(ssk, 0, info->mss_now, tcp_sk(ssk)->nonagle, + info->size_goal); release_sock(ssk); } From f3ef03357396d4b147d8e76c75fb612c2f264ffc Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Thu, 17 Sep 2026 15:05:59 +0200 Subject: [PATCH 1910/2366] mptcp: fix bad accounting in __mptcp_subflow_push_pending() If __subflow_push_pending() errors out we should avoid updating the copied byte counters, to avoid mismatch push call later on. Fixes: 0fa1b3783a17 ("mptcp: use get_send wrapper") Cc: stable@vger.kernel.org Signed-off-by: Paolo Abeni Reviewed-by: Matthieu Baerts (NGI0) Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260917-net-mptcp-misc-fixes-7-3-rc4-v2-3-0cf5c72667c8@kernel.org Signed-off-by: Jakub Kicinski --- net/mptcp/protocol.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 577d0134b9ec..e89a69ab927c 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -1854,7 +1854,8 @@ static void __mptcp_subflow_push_pending(struct sock *sk, struct sock *ssk, bool ret = __subflow_push_pending(sk, ssk, &info); if (ret <= 0) keep_pushing = false; - copied += ret; + else + copied += ret; } mptcp_for_each_subflow(msk, subflow) { From 9413959fa9fe2d94d4814f8cc2b60409f4cd46b5 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 25 Aug 2026 23:09:32 +0800 Subject: [PATCH 1911/2366] drm/amd/pm: report energy accumulator for smu 14.0.3 add energy accumulator on pmfw 0x00685000 and above version. Signed-off-by: Kevin Wang Reviewed-by: Kenneth Feng Signed-off-by: Alex Deucher (cherry picked from commit 4aa733ab15b303e2a40e2985ac21a0e01f24cc4a) --- drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c index 56a5c11bc196..ed99e61f18e1 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_2_ppt.c @@ -2142,6 +2142,7 @@ static void smu_v14_0_2_init_msg_ctl(struct smu_context *smu) static ssize_t smu_v14_0_2_get_gpu_metrics(struct smu_context *smu, void **table) { + uint32_t mp1_ver = amdgpu_ip_version(smu->adev, MP1_HWIP, 0); struct gpu_metrics_v1_3 *gpu_metrics = (struct gpu_metrics_v1_3 *)smu_driver_table_ptr( smu, SMU_DRIVER_TABLE_GPU_METRICS); @@ -2171,6 +2172,8 @@ static ssize_t smu_v14_0_2_get_gpu_metrics(struct smu_context *smu, metrics->Vcn1ActivityPercentage); gpu_metrics->average_socket_power = metrics->AverageSocketPower; + if (mp1_ver == IP_VERSION(14, 0, 3) && smu->smc_fw_version >= 0x00685000) + gpu_metrics->energy_accumulator = metrics->EnergyAccumulator; if (metrics->AverageGfxActivity <= SMU_14_0_2_BUSY_THRESHOLD) gpu_metrics->average_gfxclk_frequency = metrics->AverageGfxclkFrequencyPostDs; From 63e19ef3ddab806c472748c825f4dc88dcd994e8 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Tue, 25 Aug 2026 23:21:53 -0400 Subject: [PATCH 1912/2366] drm/amd/display: Atomize IRQ register read/modify/write ops [Why] The OTG_GLOBAL_SYNC_STATUS register controls various HW IRQ sources for the output timing generator (OTG). VUPDATE_NO_LOCK is one of them. To enable the IRQ, driver sets the VUPDATE_NO_LOCK_EN bit in the GLOBAL_SYNC_STATUS register. To ack the IRQ after it fires, the driver sets the VUPDATE_NO_LOCK_CLEAR bit in the same GLOBAL_SYNC_STATUS register. The bit sets are done through read/modify/write operations, which are not atomic. Thus, the following race is possible: Thread A: IRQ handler: *HW IRQ fires* # IRQ disable val = read(GLOBAL_SYNC_STATUS) unset(val, VUPDATE_NO_LOCK_EN) write(val, GLOBAL_SYNC_STATUS) # ACK reads VUPDATE_NO_LOCK_EN unset val1 = read(GLOBAL_SYNC_STATUS) set(val1, VUPDATE_NO_LOCK_CLEAR) # IRQ enable val = read(GLOBAL_SYNC_STATUS) set(val, VUPDATE_NO_LOCK_EN) write(val, GLOBAL_SYNC_STATUS) # BAD! clears VUPDATE_NO_LOCK_EN write(val1, GLOBAL_SYNC_STATUS) Regarding the tagged Fixes: change, it appears the change made this race more likely to occur. Since VUPDATE_NO_LOCK is now the sole IRQ source for vblank handling, a single race on high refresh panels can lead to a time out. [How] The GLOBAL_SYNC_STATUS register is only one example, other IRQ control registers also share the same scheme. On top of GLOBAL_SYNC_STATUS, let's clean up those as well. To keep things simple, Let's atomize the IRQ rmw ops via a single driver-wide spinlock. Due to the small scope of this lock, it is unlikely to cause noticeable overhead on top of all the existing locking within the IRQ set/handle paths. Since DM is responsible for locking, wrap dc_interrupt_set/ack with the spinlock in the new amdgpu_dm_irq_set/ack functions. Migrate/drop all references in DM to dc_interrupt_set/ack to use amdgpu_dm_irq_set/ack instead. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5616 Fixes: c87e6635d2db ("drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock") Reviewed-by: Mario Limonciello Signed-off-by: Leo Li Signed-off-by: Chenyu Chen Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 70de0a0216583a53c946155f8c8adedfdca6b4e7) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 12 ++++ .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 3 +- .../amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 3 +- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c | 64 +++++++++++-------- .../drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h | 28 ++++++++ .../amdgpu_dm/tests/amdgpu_dm_crtc_test.c | 16 +++-- .../amdgpu_dm/tests/amdgpu_dm_helpers_test.c | 8 ++- .../amdgpu_dm/tests/amdgpu_dm_irq_test.c | 34 +++++----- 9 files changed, 117 insertions(+), 55 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 08b8605029ab..36d2f86f000a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1420,7 +1420,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, if (acrtc && state->stream_status[i].plane_count != 0 && amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst; - rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; + rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; if (rc) drm_warn(adev_to_drm(adev), "Failed to %s pflip interrupts\n", enable ? "enable" : "disable"); @@ -1444,7 +1444,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, /* During gpu-reset we disable and then enable vblank irq, so * don't use amdgpu_irq_get/put() to avoid refcount change. */ - if (!dc_interrupt_set(adev->dm.dc, irq_source, enable)) + if (!amdgpu_dm_irq_set(adev, irq_source, enable)) drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis"); } else if (acrtc && state->stream_status[i].plane_count != 0) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 3524931451c8..881c8d1c3cc0 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -552,6 +552,18 @@ struct amdgpu_display_manager { struct common_irq_params vupdate_params[DC_IRQ_SOURCE_VUPDATE6 - DC_IRQ_SOURCE_VUPDATE1 + 1]; + /** + * @irq_reg_lock: + * + * Serializes the read-modify-writes of the HW interrupt control + * registers. Several interrupt sources share one register - e.g. the + * enable and clear bits of both VSTARTUP (vblank) and VUPDATE_NO_LOCK + * live in OTG_GLOBAL_SYNC_STATUS. Therefore, enabling one source must + * not race with acking another. Held only across amdgpu_dm_irq_set() + * and amdgpu_dm_irq_ack(). + */ + spinlock_t irq_reg_lock; + /** * @dmub_trace_params: * diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c index 62eac6e65334..1d941be73561 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c @@ -31,6 +31,7 @@ #include "amdgpu_dm_psr.h" #include "amdgpu_dm_replay.h" #include "amdgpu_dm_crtc.h" +#include "amdgpu_dm_irq.h" #include "amdgpu_dm_plane.h" #include "amdgpu_dm_trace.h" #include "amdgpu_dm_debugfs.h" @@ -91,7 +92,7 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable) irq_source = IRQ_TYPE_VUPDATE + acrtc->otg_inst; - rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; + rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; DRM_DEBUG_VBL("crtc %d - vupdate irq %sabling: r=%d\n", acrtc->crtc_id, enable ? "en" : "dis", rc); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index 298de7b75ca8..ced8b3d2d762 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -1439,12 +1439,13 @@ void dm_helpers_free_gpu_mem( bool dm_helpers_dmub_outbox_interrupt_control(struct dc_context *ctx, bool enable) { + struct amdgpu_device *adev = ctx->driver_context; enum dc_irq_source irq_source; bool ret; irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; - ret = dc_interrupt_set(ctx->dc, irq_source, enable); + ret = amdgpu_dm_irq_set(adev, irq_source, enable); DRM_DEBUG_DRIVER("Dmub trace irq %sabling: r=%d\n", enable ? "en" : "dis", ret); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c index d0239a3de2e1..74a8735168aa 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c @@ -396,6 +396,7 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev) DRM_DEBUG_KMS("DM_IRQ\n"); spin_lock_init(&adev->dm.irq_handler_list_table_lock); + spin_lock_init(&adev->dm.irq_reg_lock); adev->dm.irq_wq = alloc_workqueue("amdgpu_dm_irq", WQ_UNBOUND | WQ_HIGHPRI, 0); @@ -530,7 +531,7 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) */ for (src = DC_IRQ_SOURCE_HPD1; src <= DC_IRQ_SOURCE_HPD6RX; src++) { hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; - dc_interrupt_set(adev->dm.dc, src, false); + amdgpu_dm_irq_set(adev, src, false); DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -568,7 +569,7 @@ void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) - dc_interrupt_set(adev->dm.dc, src, true); + amdgpu_dm_irq_set(adev, src, true); } DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -594,7 +595,7 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) - dc_interrupt_set(adev->dm.dc, src, true); + amdgpu_dm_irq_set(adev, src, true); } DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); @@ -690,6 +691,23 @@ STATIC_IFN_KUNIT void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, } EXPORT_IF_KUNIT(amdgpu_dm_irq_immediate_work); +bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, + bool enable) +{ + guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); + + return dc_interrupt_set(adev->dm.dc, src, enable); +} +EXPORT_IF_KUNIT(amdgpu_dm_irq_set); + +void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src) +{ + guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); + + dc_interrupt_ack(adev->dm.dc, src); +} +EXPORT_IF_KUNIT(amdgpu_dm_irq_ack); + /** * amdgpu_dm_irq_handler - Generic DM IRQ handler * @adev: amdgpu base driver device containing the DM device @@ -710,7 +728,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_irq_handler(struct amdgpu_device *adev, entry->src_id, entry->src_data[0]); - dc_interrupt_ack(adev->dm.dc, src); + amdgpu_dm_irq_ack(adev, src); /* Call high irq work immediately */ amdgpu_dm_irq_immediate_work(adev, src); @@ -750,7 +768,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, enum dc_irq_source src = amdgpu_dm_hpd_to_dal_irq_source(type); bool st = (state == AMDGPU_IRQ_STATE_ENABLE); - dc_interrupt_set(adev->dm.dc, src, st); + amdgpu_dm_irq_set(adev, src, st); return 0; } EXPORT_IF_KUNIT(amdgpu_dm_set_hpd_irq_state); @@ -785,7 +803,7 @@ static inline int dm_irq_state(struct amdgpu_device *adev, if (dc && dc->caps.ips_support && dc->idle_optimizations_allowed) dc_allow_idle_optimizations(dc, false); - dc_interrupt_set(adev->dm.dc, irq_source, st); + amdgpu_dm_irq_set(adev, irq_source, st); return 0; } @@ -842,7 +860,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *a enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; bool st = (state == AMDGPU_IRQ_STATE_ENABLE); - dc_interrupt_set(adev->dm.dc, irq_source, st); + amdgpu_dm_irq_set(adev, irq_source, st); return 0; } EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_outbox_irq_state); @@ -870,7 +888,7 @@ STATIC_IFN_KUNIT int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *ad enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX0; bool st = (state == AMDGPU_IRQ_STATE_ENABLE); - dc_interrupt_set(adev->dm.dc, irq_source, st); + amdgpu_dm_irq_set(adev, irq_source, st); return 0; } EXPORT_IF_KUNIT(amdgpu_dm_set_dmub_trace_irq_state); @@ -937,9 +955,7 @@ EXPORT_IF_KUNIT(amdgpu_dm_set_irq_funcs); void amdgpu_dm_outbox_init(struct amdgpu_device *adev) { - dc_interrupt_set(adev->dm.dc, - DC_IRQ_SOURCE_DMCUB_OUTBOX, - true); + amdgpu_dm_irq_set(adev, DC_IRQ_SOURCE_DMCUB_OUTBOX, true); } EXPORT_IF_KUNIT(amdgpu_dm_outbox_init); @@ -962,7 +978,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) /* First, clear all hpd and hpdrx interrupts */ for (i = DC_IRQ_SOURCE_HPD1; i <= DC_IRQ_SOURCE_HPD6RX; i++) { - if (!dc_interrupt_set(adev->dm.dc, i, false)) + if (!amdgpu_dm_irq_set(adev, i, false)) drm_err(dev, "Failed to clear hpd(rx) source=%d on init\n", i); } @@ -991,7 +1007,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) * of dm. Note that only hpd interrupt types are registered with * base driver; hpd_rx types aren't. IOW, amdgpu_irq_get/put on * hpd_rx isn't available. DM currently controls hpd_rx - * explicitly with dc_interrupt_set() + * explicitly with amdgpu_dm_irq_set() */ if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { irq_type = dc_link->irq_source_hpd - DC_IRQ_SOURCE_HPD1; @@ -1000,23 +1016,21 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) * and what bios reports as the # of connectors with hpd * sources. Since the # of hpd source types registered * with base driver == mode_info.num_hpd, we have to - * fallback to dc_interrupt_set for the remaining types. + * fallback to amdgpu_dm_irq_set for the remaining types. */ if (irq_type < adev->mode_info.num_hpd) { if (amdgpu_irq_get(adev, &adev->hpd_irq, irq_type)) drm_err(dev, "DM_IRQ: Failed get HPD for source=%d)!\n", dc_link->irq_source_hpd); } else { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd, - true); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, + true); } } if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd_rx, - true); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, + true); } } drm_connector_list_iter_end(&iter); @@ -1061,16 +1075,14 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) drm_err(dev, "DM_IRQ: Failed put HPD for source=%d!\n", dc_link->irq_source_hpd); } else { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd, - false); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, + false); } } if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { - dc_interrupt_set(adev->dm.dc, - dc_link->irq_source_hpd_rx, - false); + amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, + false); } } drm_connector_list_iter_end(&iter); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h index 4c200a9614a7..bc16ecc67329 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h @@ -89,6 +89,34 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, enum dc_irq_source irq_source, void *ih_index); +/** + * amdgpu_dm_irq_set - enable or disable a DC interrupt source. + * + * @adev: AMD DRM device + * @src: DC interrupt source to toggle + * @enable: true to enable the source, false to disable it + * + * DM-wide replacement for dc_interrupt_set(). As locking is DM's + * responsibility, this is a thin wrapper serializes the underlying + * read-modify-write against the other interrupt sources sharing HW control + * registers with @src, so DM must never call dc_interrupt_set() directly. + * + * Returns: true if the source was toggled. + */ +bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, + bool enable); + +/** + * amdgpu_dm_irq_ack - acknowledge a DC interrupt source. + * + * @adev: AMD DRM device + * @src: DC interrupt source to acknowledge + * + * DM-wide replacement for dc_interrupt_ack(), serialized the same way as + * amdgpu_dm_irq_set(). + */ +void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src); + void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev); void amdgpu_dm_outbox_init(struct amdgpu_device *adev); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c index 0d998f204250..ae0f4da96252 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_crtc_test.c @@ -436,7 +436,7 @@ static void dm_test_crtc_set_vupdate_irq_no_otg(struct kunit *test) * dm_test_crtc_set_vupdate_irq_dc_busy - Test vupdate irq when DC rejects request * @test: The KUnit test context * - * With an OTG instance assigned but no DC attached, dc_interrupt_set() returns + * With an OTG instance assigned but no DC attached, amdgpu_dm_irq_set() returns * false and the function must report the request as busy (-EBUSY). */ static void dm_test_crtc_set_vupdate_irq_dc_busy(struct kunit *test) @@ -453,12 +453,12 @@ static void dm_test_crtc_set_vupdate_irq_dc_busy(struct kunit *test) acrtc->base.dev = &adev->ddev; acrtc->otg_inst = 0; - /* adev->dm.dc is NULL, so dc_interrupt_set() returns false. */ + /* adev->dm.dc is NULL, so amdgpu_dm_irq_set() returns false. */ KUNIT_EXPECT_EQ(test, amdgpu_dm_crtc_set_vupdate_irq(&acrtc->base, true), -EBUSY); } -/* Per-source funcs let dc_interrupt_set() succeed without register access. */ +/* Per-source funcs let amdgpu_dm_irq_set() succeed without register access. */ static bool dm_test_vupdate_irq_src_set(struct irq_service *irq_service, const struct irq_source_info *info, bool enable) @@ -477,7 +477,7 @@ static struct irq_source_info_funcs dm_test_vupdate_irq_src_funcs = { .ack = dm_test_vupdate_irq_src_ack, }; -/* A .set that fails so dc_interrupt_set() reports the source as busy. */ +/* A .set that fails so amdgpu_dm_irq_set() reports the source as busy. */ static bool dm_test_vupdate_irq_src_set_busy(struct irq_service *irq_service, const struct irq_source_info *info, bool enable) @@ -519,7 +519,9 @@ static void dm_test_crtc_set_vupdate_irq_enable(struct kunit *test) irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); - /* Populate the per-source info table so dc_interrupt_set() succeeds. */ + /* + * Populate the per-source info table so amdgpu_dm_irq_set() succeeds. + */ info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, info); @@ -1018,7 +1020,9 @@ static void dm_test_crtc_enable_vblank_vupdate_busy(struct kunit *test) irqs = kunit_kzalloc(test, sizeof(*irqs), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); - /* Per-source .set fails so dc_interrupt_set() reports the source busy. */ + /* + * Per-source .set fails so amdgpu_dm_irq_set() reports the source busy. + */ info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, info); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c index 82e0c984693c..639512bea275 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_helpers_test.c @@ -2442,17 +2442,21 @@ static void dm_test_is_dp_sink_present_null_priv(struct kunit *test) * dm_test_dmub_outbox_interrupt_control_null_dc - Test outbox irq control with NULL dc * @test: The KUnit test context * - * dc_interrupt_set() is NULL-safe and returns false when dc is NULL, so the + * amdgpu_dm_irq_set() is NULL-safe and returns false when dc is NULL, so the * helper returns false without touching real interrupt hardware. */ static void dm_test_dmub_outbox_interrupt_control_null_dc(struct kunit *test) { + struct amdgpu_device *adev; struct dc_context *ctx; + adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, adev); ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx); + ctx->driver_context = adev; - /* ctx->dc is NULL → dc_interrupt_set returns false */ + /* adev->dm.dc is NULL → amdgpu_dm_irq_set returns false */ KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, true)); KUNIT_EXPECT_FALSE(test, dm_helpers_dmub_outbox_interrupt_control(ctx, false)); } diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c index 861ee9eaa032..95322d8c8613 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_irq_test.c @@ -268,7 +268,7 @@ static bool dm_test_irq_src_ack(struct irq_service *irq_service, return true; } -/* Per-source funcs let dc_interrupt_set() succeed without register access. */ +/* Per-source funcs let amdgpu_dm_irq_set() succeed without register access. */ static struct irq_source_info_funcs dm_test_irq_src_funcs = { .set = dm_test_irq_src_set, .ack = dm_test_irq_src_ack, @@ -290,7 +290,7 @@ static struct dc *dm_test_alloc_dc_with_irq_service(struct kunit *test, KUNIT_ASSERT_NOT_ERR_OR_NULL(test, irqs); /* - * Populate the per-source info table so dc_interrupt_set()/_ack() + * Populate the per-source info table so amdgpu_dm_irq_set()/_ack() * succeed without touching hardware registers. */ info = kunit_kzalloc(test, sizeof(*info) * DAL_IRQ_SOURCES_NUMBER, @@ -1227,7 +1227,7 @@ static void dm_test_irq_suspend_empty(struct kunit *test) KUNIT_ASSERT_EQ(test, amdgpu_dm_irq_init(adev), 0); /* - * With no registered handlers the HW dc_interrupt_set() calls are + * With no registered handlers the amdgpu_dm_irq_set() calls are * skipped, so suspend must complete without touching the (absent) DC. */ amdgpu_dm_irq_suspend(adev); @@ -1275,11 +1275,11 @@ static void dm_test_irq_resume_late_empty(struct kunit *test) } /** - * dm_test_irq_suspend_registered - Test suspend reaches the dc_interrupt_set path + * dm_test_irq_suspend_registered - Test suspend reaches the irq set path * @test: The KUnit test context * * Registers a low-context HPD handler so the handler list is non-empty, - * forcing amdgpu_dm_irq_suspend() to call dc_interrupt_set() (NULL-safe with + * forcing amdgpu_dm_irq_suspend() to call amdgpu_dm_irq_set() (NULL-safe with * no DC) and flush_work() on the registered handler. */ static void dm_test_irq_suspend_registered(struct kunit *test) @@ -1330,11 +1330,11 @@ static void dm_test_irq_suspend_disables_polling(struct kunit *test) } /** - * dm_test_irq_resume_early_registered - Test early resume reaches dc_interrupt_set + * dm_test_irq_resume_early_registered - Test early resume reaches irq set * @test: The KUnit test context * * Registers a low-context HPD RX handler so early resume calls - * dc_interrupt_set() for the short-pulse interrupt source. + * amdgpu_dm_irq_set() for the short-pulse interrupt source. */ static void dm_test_irq_resume_early_registered(struct kunit *test) { @@ -1358,10 +1358,10 @@ static void dm_test_irq_resume_early_registered(struct kunit *test) } /** - * dm_test_irq_resume_late_registered - Test late resume reaches dc_interrupt_set + * dm_test_irq_resume_late_registered - Test late resume reaches irq set * @test: The KUnit test context * - * Registers a low-context HPD handler so late resume calls dc_interrupt_set() + * Registers a low-context HPD handler so late resume calls amdgpu_dm_irq_set() * for the HPD interrupt source. */ static void dm_test_irq_resume_late_registered(struct kunit *test) @@ -1592,7 +1592,7 @@ static void dm_test_set_crtc_irq_state_enable(struct kunit *test) /* * otg_inst >= 0 computes the irq source and reaches the NULL-safe - * dc_interrupt_set(); the ips_support branch is skipped (dc == NULL). + * amdgpu_dm_irq_set(); the ips_support branch is skipped (dc == NULL). */ acrtc->otg_inst = 3; adev->mode_info.crtcs[0] = acrtc; @@ -1671,8 +1671,8 @@ static void dm_test_set_vupdate_irq_state_enable(struct kunit *test) * * With a non-NULL DC that advertises IPS support and currently allows idle * optimizations, dm_irq_state() must call dc_allow_idle_optimizations() before - * dc_interrupt_set(). disable_idle_power_optimizations makes that call a safe - * early return, and per-source stub funcs let dc_interrupt_set() succeed. + * amdgpu_dm_irq_set(). disable_idle_power_optimizations makes that call a safe + * early return, and per-source stub funcs let amdgpu_dm_irq_set() succeed. */ static void dm_test_set_crtc_irq_state_allows_idle(struct kunit *test) { @@ -1891,7 +1891,7 @@ static void dm_test_set_hpd_irq_state_null_dc(struct kunit *test) adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - /* dc_interrupt_set() is a no-op when dc is NULL, so both states + /* amdgpu_dm_irq_set() is a no-op when dc is NULL, so both states * return 0 without dereferencing the (absent) DC. */ KUNIT_EXPECT_EQ(test, amdgpu_dm_set_hpd_irq_state(adev, NULL, AMDGPU_HPD_1, @@ -1951,7 +1951,7 @@ static void dm_test_outbox_init_null_dc(struct kunit *test) adev = kunit_kzalloc(test, sizeof(*adev), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, adev); - /* Single dc_interrupt_set() call must be skipped when dc is NULL. */ + /* Single amdgpu_dm_irq_set() call must be skipped when dc is NULL. */ amdgpu_dm_outbox_init(adev); } @@ -1969,7 +1969,7 @@ static void dm_test_hpd_init_empty_connectors(struct kunit *test) /* * With an empty connector list the per-connector loop is skipped and - * the initial clear loop relies on dc_interrupt_set() being a no-op + * the initial clear loop relies on amdgpu_dm_irq_set() being a no-op * for a NULL dc, so init must complete without touching the DC. */ amdgpu_dm_hpd_init(adev); @@ -2004,7 +2004,7 @@ static void dm_test_hpd_init_fini_with_connectors(struct kunit *test) /* * num_hpd = 0 forces irq_type >= num_hpd so the loop takes the HW - * fallback (dc_interrupt_set()) instead of amdgpu_irq_get(); with a + * fallback (amdgpu_dm_irq_set()) instead of amdgpu_irq_get(); with a * NULL dc that fallback is a safe no-op. */ adev->mode_info.num_hpd = 0; @@ -2090,7 +2090,7 @@ static void dm_test_hpd_init_fini_irq_ref(struct kunit *test) /* * num_hpd >= 1 makes irq_type (0) < num_hpd, so the loop takes the * amdgpu_irq_get()/amdgpu_irq_put() branch instead of the - * dc_interrupt_set() fallback. The mock device has irq.installed == + * amdgpu_dm_irq_set() fallback. The mock device has irq.installed == * false, so both calls fail early with -ENOENT (logging an error) * without touching the base-driver irq state. */ From 5f28bb1c2cd9dcdb76a20d61b3ea069b85893c59 Mon Sep 17 00:00:00 2001 From: Vladimir Marioukhine Date: Wed, 12 Aug 2026 13:19:46 -0400 Subject: [PATCH 1913/2366] drm/amdkfd: implement restore_mqd callbacks for GFX12/12.1 kfd_mqd_manager_v12.c (GFX 12.0) and kfd_mqd_manager_v12_1.c (GFX 12.1) do not implement restore_mqd callbacks, leaving the function pointers NULL and causing CRIU restore to return -EOPNOTSUPP on GFX12. Implement restore_mqd for both compute and SDMA queues in kfd_mqd_manager_v12.c and kfd_mqd_manager_v12_1.c, modeled after the GFX 11 implementation with the following improvements: - update cp_mqd_base_addr_lo/hi to the newly allocated MQD address, fixing a pre-existing gap shared with v11 where the in-MQD copy still pointed at the old checkpoint-time address after restore - memset the full allocation before memcpy for compute queues to avoid stale data in the GTT sub-allocator tail; SDMA MQDs use sizeof(*m) since they are packed at mqd_size stride in a shared BO checkpoint_mqd registration is deferred to a follow-up patch that also implements get_checkpoint_info, so that checkpoint and restore are enabled together as a complete and testable unit. Note: GFX12.1 restore handles XCC0 only. Multi-XCC CRIU restore is currently unreachable due to a separate validation issue in kfd_criu_restore_queue(). A pr_warn_once() is emitted if a multi-XCC device is encountered. Signed-off-by: Vladimir Marioukhine Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit b1f9601237d050f5df478464cf51bf1fff29a256) Cc: stable@vger.kernel.org --- .../drm/amd/amdkfd/kfd_device_queue_manager.c | 7 +- .../gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c | 59 ++++++++++++++++ .../drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 68 +++++++++++++++++++ 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 9811e4e10291..2f78395a0c31 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -770,10 +770,12 @@ static int create_queue_nocpsch(struct device_queue_manager *dqm, mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( q->properties.type)]; if (qd && !mqd_mgr->restore_mqd) { - pr_debug("restore_mqd not implemented for this GPU\n"); + pr_debug("restore_mqd not implemented for queue type %d\n", + q->properties.type); retval = -EOPNOTSUPP; goto deallocate_vmid; } + if (q->properties.type == KFD_QUEUE_TYPE_COMPUTE) { retval = allocate_hqd(dqm, q); if (retval) @@ -2250,7 +2252,8 @@ static int create_queue_cpsch(struct device_queue_manager *dqm, struct queue *q, mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( q->properties.type)]; if (qd && !mqd_mgr->restore_mqd) { - pr_debug("restore_mqd not implemented for this GPU\n"); + pr_debug("restore_mqd not implemented for queue type %d\n", + q->properties.type); retval = -EOPNOTSUPP; goto out_deallocate_doorbell; } diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c index 7c387fa90076..63f25a60baa5 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c @@ -380,6 +380,63 @@ static int debugfs_show_mqd_sdma(struct seq_file *m, void *data) #endif +static void restore_mqd(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, const void *mqd_src, + const void *ctl_stack_src, const u32 ctl_stack_size) +{ + u64 addr; + struct v12_compute_mqd *m; + + m = (struct v12_compute_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); + memcpy(m, mqd_src, sizeof(*m)); + + /* Update MQD base address to the newly allocated location */ + m->cp_mqd_base_addr_lo = lower_32_bits(addr); + m->cp_mqd_base_addr_hi = upper_32_bits(addr); + + m->cp_hqd_pq_doorbell_control &= + ~CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK; + m->cp_hqd_pq_doorbell_control |= + qp->doorbell_off << CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + pr_debug("cp_hqd_pq_doorbell_control 0x%x\n", m->cp_hqd_pq_doorbell_control); + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + +static void restore_mqd_sdma(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, + const void *mqd_src, + const void *ctl_stack_src, + const u32 ctl_stack_size) +{ + u64 addr; + struct v12_sdma_mqd *m; + + m = (struct v12_sdma_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); + memcpy(m, mqd_src, sizeof(*m)); + + m->sdmax_rlcx_doorbell_offset = + qp->doorbell_off << SDMA0_QUEUE0_DOORBELL_OFFSET__OFFSET__SHIFT; + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + struct mqd_manager *mqd_manager_init_v12(enum KFD_MQD_TYPE type, struct kfd_node *dev) { @@ -407,6 +464,7 @@ struct mqd_manager *mqd_manager_init_v12(enum KFD_MQD_TYPE type, mqd->mqd_size = sizeof(struct v12_compute_mqd); mqd->get_wave_state = get_wave_state; mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd; #endif @@ -453,6 +511,7 @@ struct mqd_manager *mqd_manager_init_v12(enum KFD_MQD_TYPE type, mqd->is_occupied = kfd_is_occupied_sdma; mqd->mqd_size = sizeof(struct v12_sdma_mqd); mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd_sdma; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd_sdma; #endif diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c index 431a940f91f3..708bbb08c089 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c @@ -641,6 +641,72 @@ static int debugfs_show_mqd_sdma(struct seq_file *m, void *data) #endif +static void restore_mqd_v12_1(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, const void *mqd_src, + const void *ctl_stack_src, const u32 ctl_stack_size) +{ + u64 addr; + struct v12_1_compute_mqd *m; + + /* + * GFX12.1 is multi-XCC capable but this restore handles XCC0 only. + * Multi-XCC CRIU restore is currently unreachable because + * kfd_criu_restore_queue() validates against unscaled mqd_size. + */ + if (NUM_XCC(mm->dev->xcc_mask) > 1) + pr_warn_once("GFX12.1 multi-XCC CRIU restore not fully supported\n"); + + m = (struct v12_1_compute_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size) * + NUM_XCC(mm->dev->xcc_mask)); + memcpy(m, mqd_src, sizeof(*m)); + + /* Update MQD base address to the newly allocated location */ + m->cp_mqd_base_addr_lo = lower_32_bits(addr); + m->cp_mqd_base_addr_hi = upper_32_bits(addr); + + m->cp_hqd_pq_doorbell_control &= + ~CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET_MASK; + m->cp_hqd_pq_doorbell_control |= + qp->doorbell_off << CP_HQD_PQ_DOORBELL_CONTROL__DOORBELL_OFFSET__SHIFT; + pr_debug("cp_hqd_pq_doorbell_control 0x%x\n", m->cp_hqd_pq_doorbell_control); + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + +static void restore_mqd_sdma_v12_1(struct mqd_manager *mm, void **mqd, + struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, + struct queue_properties *qp, + const void *mqd_src, + const void *ctl_stack_src, + const u32 ctl_stack_size) +{ + u64 addr; + struct v12_sdma_mqd *m; + + m = (struct v12_sdma_mqd *)mqd_mem_obj->cpu_ptr; + addr = mqd_mem_obj->gpu_addr; + + memset(m, 0, AMDGPU_MQD_SIZE_ALIGN(mm->mqd_size)); + memcpy(m, mqd_src, sizeof(*m)); + + m->sdmax_rlcx_doorbell_offset = + qp->doorbell_off << SDMA0_SDMA_QUEUE0_DOORBELL_OFFSET__OFFSET__SHIFT; + + *mqd = m; + if (gart_addr) + *gart_addr = addr; + + qp->is_active = 0; +} + struct mqd_manager *mqd_manager_init_v12_1(enum KFD_MQD_TYPE type, struct kfd_node *dev) { @@ -668,6 +734,7 @@ struct mqd_manager *mqd_manager_init_v12_1(enum KFD_MQD_TYPE type, mqd->mqd_size = sizeof(struct v12_1_compute_mqd); mqd->get_wave_state = get_wave_state_v12_1; mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd_v12_1; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd; #endif @@ -714,6 +781,7 @@ struct mqd_manager *mqd_manager_init_v12_1(enum KFD_MQD_TYPE type, mqd->is_occupied = kfd_is_occupied_sdma; mqd->mqd_size = sizeof(struct v12_sdma_mqd); mqd->mqd_stride = kfd_mqd_stride; + mqd->restore_mqd = restore_mqd_sdma_v12_1; #if defined(CONFIG_DEBUG_FS) mqd->debugfs_show_mqd = debugfs_show_mqd_sdma; #endif From 636139603b99d2e3a18a46cf3f8d39313ce8042e Mon Sep 17 00:00:00 2001 From: Mike Lothian Date: Sat, 12 Sep 2026 00:29:08 +0100 Subject: [PATCH 1914/2366] drm/amdgpu: hold a runtime PM reference for P2P dma-buf attachments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit amdgpu_dma_buf_map() adds VRAM to the allowed domains for a peer2peer attachment. GTT is only a fallback placement when VRAM is preferred, so ttm_bo_validate() migrates the buffer from GTT into VRAM. While the exporting device is runtime suspended its SDMA rings are down and the move fails: amdgpu: Move buffer fallback to memcpy unavailable An importer on a second GPU reaches this holding no runtime PM reference on the exporter, e.g. a compositor on the APU submitting a frame that references a buffer exported by an idle dGPU: amdgpu_cs_ioctl -> amdgpu_cs_parser_bos -> amdgpu_cs_bo_validate -> ttm_bo_validate -> amdgpu_bo_move -> dma_buf_map_attachment -> amdgpu_dma_buf_map -> ttm_bo_validate -> amdgpu_bo_move Pinning a dma-buf into VRAM has the same requirement, which commit 030631e97b20 ("drm/amdgpu: revert "take runtime pm reference when we attach a buffer" v2") called out as the one case that would need the reference back. Take it in attach and drop it in detach. pm_runtime_get_if_active() never resumes the device, so it cannot deadlock against the reservation taken during resume, which is why the old pm_runtime_get_sync() had to go. If the device is not active, clear peer2peer instead: the buffer then stays in GTT, which remains accessible while the GPU is powered down. If runtime PM is disabled, take a plain reference so the put in detach stays balanced. Fixes: 030631e97b20 ("drm/amdgpu: revert "take runtime pm reference when we attach a buffer" v2") Suggested-by: Christian König Reviewed-by: Christian König Signed-off-by: Mike Lothian Assisted-by: Claude:Opus-5 [Claude Code] Signed-off-by: Alex Deucher (cherry picked from commit 062ff15e30a48d14fb7d7558eba84f8dc97197f0) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c | 43 ++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c index b33c300e26e2..9adf3eed8822 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_dma_buf.c @@ -43,6 +43,7 @@ #include #include #include +#include static const struct dma_buf_attach_ops amdgpu_dma_buf_attach_ops; @@ -100,15 +101,54 @@ static int amdgpu_dma_buf_attach(struct dma_buf *dmabuf, pci_p2pdma_distance(adev->pdev, attach->dev, false) < 0) attach->peer2peer = false; + /* + * Only allow P2P while the exporter is active, and keep it active + * until detach. With runtime PM disabled take a plain reference so + * the put in detach stays balanced. + */ + if (attach->peer2peer) { + struct device *dev = adev_to_drm(adev)->dev; + int ret = pm_runtime_get_if_active(dev); + + if (!ret) + attach->peer2peer = false; + else if (ret < 0) + pm_runtime_get_noresume(dev); + } + r = dma_resv_lock(bo->tbo.base.resv, NULL); if (r) - return r; + goto err_pm_put; amdgpu_vm_bo_update_shared(bo); dma_resv_unlock(bo->tbo.base.resv); return 0; + +err_pm_put: + if (attach->peer2peer) + pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); + return r; +} + +/** + * amdgpu_dma_buf_detach - &dma_buf_ops.detach implementation + * + * @dmabuf: DMA-buf where we remove the attachment from + * @attach: the attachment to remove + * + * Drop the runtime PM reference taken in amdgpu_dma_buf_attach(). + */ +static void amdgpu_dma_buf_detach(struct dma_buf *dmabuf, + struct dma_buf_attachment *attach) +{ + struct drm_gem_object *obj = dmabuf->priv; + struct amdgpu_bo *bo = gem_to_amdgpu_bo(obj); + struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev); + + if (attach->peer2peer) + pm_runtime_put_autosuspend(adev_to_drm(adev)->dev); } /** @@ -350,6 +390,7 @@ static void amdgpu_dma_buf_vunmap(struct dma_buf *dma_buf, struct iosys_map *map const struct dma_buf_ops amdgpu_dmabuf_ops = { .attach = amdgpu_dma_buf_attach, + .detach = amdgpu_dma_buf_detach, .pin = amdgpu_dma_buf_pin, .unpin = amdgpu_dma_buf_unpin, .map_dma_buf = amdgpu_dma_buf_map, From 723d4dc628d764b19cf9efca14b82cca5ff020c9 Mon Sep 17 00:00:00 2001 From: Dmitriy Chumachenko Date: Mon, 14 Sep 2026 17:33:03 +0300 Subject: [PATCH 1915/2366] drm/amdgpu: check ras and obj before dereference nbio_v7_9_handle_ras_controller_intr_no_bifring() dereferences ras and obj without checking either for NULL. Both amdgpu_ras_get_context() and amdgpu_ras_find_obj() can return NULL, e.g. during the window between adev->nbio.ras being set (early in amdgpu_ras_init(), by design, to enable the fatal-error interrupt as soon as possible) and the PCIE_BIF ras object actually being created in RAS late_init. Any interrupt in that window crashes in hard-IRQ context. This is analogous to commit d190b459b2a4 ("drm/amdgpu: the warning dereferencing obj for nbio_v7_4"), which fixed the same issue in the nbio_v7_4 handler. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 7692e1ee2446 ("drm/amdgpu: add RAS fatal error handler for NBIO v7.9") Reviewed-by: Tao Zhou Signed-off-by: Dmitriy Chumachenko Signed-off-by: Alex Deucher (cherry picked from commit c7071767a50a32ed727cf800ac84372429e3b4b3) --- drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c b/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c index bdfd2917e3ca..def02993b7cf 100644 --- a/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c +++ b/drivers/gpu/drm/amd/amdgpu/nbio_v7_9.c @@ -535,7 +535,7 @@ static void nbio_v7_9_handle_ras_controller_intr_no_bifring(struct amdgpu_device RAS_CNTLR_INTERRUPT_CLEAR, 1); WREG32_SOC15(NBIO, 0, regBIF_BX0_BIF_DOORBELL_INT_CNTL, bif_doorbell_intr_cntl); - if (!ras->disable_ras_err_cnt_harvest) { + if (ras && !ras->disable_ras_err_cnt_harvest && obj) { /* * clear error status after ras_controller_intr * according to hw team and count ue number From 04de4007d32385b8b6a5dd72bff3146dfdc592c3 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 15 Sep 2026 10:59:45 -0500 Subject: [PATCH 1916/2366] drm/amdgpu: Fix GPU PCIe link capability reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit eb53125a7ad9 ("drm/amd: Add dedicated helper for amdgpu_device_find_parent()") made amdgpu_device_gpu_bandwidth() query the first device outside the dGPU. That is the host side of the physical link, not the GPU side. As a result, the ASIC and platform capability masks can both be based on the host port. drm_amdgpu_info_device then exposes the host capabilities to userspace, such as Gen5 x16 for a Gen4 x8 GPU. Cache both ends of the physical link during device initialization. Use link_dev for the GPU capability and link_partner for the platform capability and _PR3 detection. Reported-by: "Marek Olšák" Closes: https://lore.kernel.org/amd-gfx/CAAxE2A4VhsAzzO1QjBjUg+NgnbD04ZzMyN6xsUJxjKJHH6hxiw@mail.gmail.com/ Suggested-by: Lijo Lazar Fixes: eb53125a7ad9 ("drm/amd: Add dedicated helper for amdgpu_device_find_parent()") Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 7ea6a47224e2c6e89a3a682d7fbaace4817a55aa) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 3 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 48 ++++++++-------------- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 7974f9b7944f..a9c6f5d4a639 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -621,6 +621,9 @@ enum amdgpu_enforce_isolation_mode { struct amdgpu_device { struct device *dev; struct pci_dev *pdev; + /* The two ends of the physical PCIe link outside the device. */ + struct pci_dev *link_dev; + struct pci_dev *link_partner; struct drm_device ddev; #ifdef CONFIG_DRM_AMD_ACP diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 104d1d2cbad9..933804349dbf 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -1954,18 +1954,17 @@ static void amdgpu_uid_fini(struct amdgpu_device *adev) adev->uid_info = NULL; } -static struct pci_dev *amdgpu_device_find_parent(struct amdgpu_device *adev) +static void amdgpu_device_init_pcie_links(struct amdgpu_device *adev) { - struct pci_dev *parent = adev->pdev; + adev->link_dev = adev->pdev; + adev->link_partner = pci_upstream_bridge(adev->link_dev); - /* skip upstream/downstream switches internal to dGPU */ - while ((parent = pci_upstream_bridge(parent))) { - if (parent->vendor == PCI_VENDOR_ID_ATI) - continue; - break; + /* Skip upstream/downstream switches internal to the dGPU. */ + while (adev->link_partner && + adev->link_partner->vendor == PCI_VENDOR_ID_ATI) { + adev->link_dev = adev->link_partner; + adev->link_partner = pci_upstream_bridge(adev->link_dev); } - - return parent; } /** @@ -1981,7 +1980,6 @@ static struct pci_dev *amdgpu_device_find_parent(struct amdgpu_device *adev) static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) { struct amdgpu_ip_block *ip_block; - struct pci_dev *parent; bool total, skip_bios, early_full_gpu_access = false; uint32_t bios_flags; int i, r; @@ -2077,10 +2075,9 @@ static int amdgpu_device_ip_early_init(struct amdgpu_device *adev) !dev_is_removable(&adev->pdev->dev)) adev->flags |= AMD_IS_PX; - if (!(adev->flags & AMD_IS_APU)) { - parent = amdgpu_device_find_parent(adev); - adev->has_pr3 = parent ? pci_pr3_present(parent) : false; - } + if (!(adev->flags & AMD_IS_APU)) + adev->has_pr3 = adev->link_partner && + pci_pr3_present(adev->link_partner); adev->pm.pp_feature = amdgpu_pp_feature_mask; if (amdgpu_sriov_vf(adev) || sched_policy == KFD_SCHED_POLICY_NO_HWS) @@ -3776,6 +3773,7 @@ int amdgpu_device_init(struct amdgpu_device *adev, adev->shutdown = false; adev->flags = flags; + amdgpu_device_init_pcie_links(adev); if (amdgpu_force_asic_type >= 0 && amdgpu_force_asic_type < CHIP_LAST) adev->asic_type = amdgpu_force_asic_type; @@ -5872,11 +5870,9 @@ static void amdgpu_device_partner_bandwidth(struct amdgpu_device *adev, *width = PCIE_LNK_WIDTH_UNKNOWN; if (amdgpu_device_pcie_dynamic_switching_supported(adev)) { - struct pci_dev *parent = amdgpu_device_find_parent(adev); - - if (parent) { - *speed = pcie_get_speed_cap(parent); - *width = pcie_get_width_cap(parent); + if (adev->link_partner) { + *speed = pcie_get_speed_cap(adev->link_partner); + *width = pcie_get_width_cap(adev->link_partner); } } else { /* use the current speeds rather than max if switching is not supported */ @@ -5898,21 +5894,11 @@ static void amdgpu_device_gpu_bandwidth(struct amdgpu_device *adev, enum pci_bus_speed *speed, enum pcie_link_width *width) { - struct pci_dev *parent = adev->pdev; - if (!speed || !width) return; - /* use the device itself */ - *speed = pcie_get_speed_cap(adev->pdev); - *width = pcie_get_width_cap(adev->pdev); - - /* use the link outside the device */ - parent = amdgpu_device_find_parent(adev); - if (parent) { - *speed = pcie_get_speed_cap(parent); - *width = pcie_get_width_cap(parent); - } + *speed = pcie_get_speed_cap(adev->link_dev); + *width = pcie_get_width_cap(adev->link_dev); } /** From c883d0a132d430ef7ebb23fd94323be94d0fbdb8 Mon Sep 17 00:00:00 2001 From: David Francis Date: Wed, 5 Aug 2026 09:51:35 -0400 Subject: [PATCH 1917/2366] drm/amdkfd: Avoid integer underflow with ffs in EOP ring size calc The low 6 bits of cp_hqd_eop_control store the base-2 logarithm of the EOP ring size. This was calculated as ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1 But ffs can in theory return 1 or 0, so this could underflow (although in practice the ring buffer size cannot be less than 4096). Change this to ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4) using properties of logarithms. Reviewed-by: Kent Russell Signed-off-by: David Francis Signed-off-by: Alex Deucher (cherry picked from commit 4f18c56630383c14bfc6b2d65f88f2f895d2121a) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c index e034da638c07..4f8a8a1a6186 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v10.c @@ -204,7 +204,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c index 350fcbbba4b2..bf015dc5b868 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v11.c @@ -242,7 +242,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c index 63f25a60baa5..6ea09b031caf 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12.c @@ -217,7 +217,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c index 708bbb08c089..c709db0210ce 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c @@ -295,7 +295,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? min(0xA, - ffs(q->eop_ring_buffer_size / sizeof(unsigned int)) - 1 - 1) : 0; + ffs(q->eop_ring_buffer_size / sizeof(unsigned int) / 4)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = From 8ee521b8b189799e361d4233c5180ba56656d4d4 Mon Sep 17 00:00:00 2001 From: David Francis Date: Wed, 5 Aug 2026 09:16:51 -0400 Subject: [PATCH 1918/2366] drm/amdkfd: Avoid integer underflow in EOP ring size calculation. The low 6 bits of cp_hqd_eop_control store the base-2 logarithm of the EOP ring size. This was calculated as order_base_2(q->eop_ring_buffer_size / 4) - 1 But order_base_2 can in theory return 0, so this could underflow (although in practice the ring buffer size cannot be less than 4096). Change this to order_base_2(q->eop_ring_buffer_size / 8) using properties of logarithms. Also add to the above comment to make the mathematics more clear. Reviewed-by: Kent Russell Signed-off-by: David Francis Signed-off-by: Alex Deucher (cherry picked from commit f0f43fcf8b2b3a924cad9444340921c96ed5f634) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c | 6 +++++- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c index b95720198e28..6e6bc1ec0b64 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c @@ -285,6 +285,10 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, 1 << CP_HQD_IB_CONTROL__IB_EXE_DISABLE__SHIFT; /* + * The lowest 6 bits of eop_control store the EOP ring size. If + * their value is X, the ring size is 2^(X + 1) dwords, or + * 2^(X + 3) bytes. + * * HW does not clamp this field correctly. Maximum EOP queue size * is constrained by per-SE EOP done signal count, which is 8-bit. * Limit is 0xFF EOP entries (= 0x7F8 dwords). CP will not submit @@ -296,7 +300,7 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, * */ m->cp_hqd_eop_control = q->eop_ring_buffer_size ? - min(0xA, order_base_2(q->eop_ring_buffer_size / 4) - 1) : 0; + min(0xA, order_base_2(q->eop_ring_buffer_size / 8)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c index 60b87a500698..029572548c14 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c @@ -208,6 +208,9 @@ static void __update_mqd(struct mqd_manager *mm, void *mqd, mtype << CP_HQD_IB_CONTROL__MTYPE__SHIFT; /* + * The lowest 6 bits of eop_control store the EOP ring size. If + * their value is X, the ring size is 2^(X + 1) dwords, or + * 2^(X + 3) bytes. * HW does not clamp this field correctly. Maximum EOP queue size * is constrained by per-SE EOP done signal count, which is 8-bit. * Limit is 0xFF EOP entries (= 0x7F8 dwords). CP will not submit @@ -215,7 +218,7 @@ static void __update_mqd(struct mqd_manager *mm, void *mqd, * is safe, giving a maximum field value of 0xA. */ m->cp_hqd_eop_control |= q->eop_ring_buffer_size ? min(0xA, - order_base_2(q->eop_ring_buffer_size / 4) - 1) : 0; + order_base_2(q->eop_ring_buffer_size / 8)) : 0; m->cp_hqd_eop_base_addr_lo = lower_32_bits(q->eop_ring_buffer_address >> 8); m->cp_hqd_eop_base_addr_hi = From 0d2f4cfa564355fcbbc71498fd8ec09243036109 Mon Sep 17 00:00:00 2001 From: Srinivasan Shanmugam Date: Tue, 15 Sep 2026 14:07:39 +0530 Subject: [PATCH 1919/2366] drm/amd/display: Fix NULL dereference in dcn50/dcn60 init_hw dc->clk_mgr is checked for NULL earlier in dcn50_init_hw() and dcn60_init_hw(), but dcn50_initialize_min_clocks() and dcn401_initialize_min_clocks() are called without any guard, causing Smatch to report potential NULL dereferences. Guard both call sites with the same pattern used throughout both functions: if (dc->clk_mgr && dc->clk_mgr->funcs) Also fix dcn50_initialize_min_clocks() which calls get_dispclk_from_dentist without checking the function pointer, unlike the dcn401 equivalent which guards that call. Fix kernel-doc in dcn60_hwseq.c by adding missing parameter descriptions for @probe in dcn60_update_probe_status() and @type in is_probe_measurement_type_for_hubbub(). Fixes: 7f7d7ea1fa51 ("drm/amd/display: Add new sources for DCN6") Reported-by: Dan Carpenter Cc: Aurabindo Pillai Cc: Ivan Lipski Cc: Dan Wheeler Cc: Roman Li Cc: Alex Hung Cc: Tom Chung Signed-off-by: Srinivasan Shanmugam Reviewed-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit 325c9a827cdd748e126eafeadaffc556204773d2) --- drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c | 6 ++++-- drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c index a7f8fd03faea..e549556b9679 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn50/dcn50_hwseq.c @@ -67,7 +67,8 @@ static void dcn50_initialize_min_clocks(struct dc *dc) * audio corruption. Read current DISPCLK from DENTIST and request the same * freq to ensure that the timing is valid and unchanged. */ - clocks->dispclk_khz = dc->clk_mgr->funcs->get_dispclk_from_dentist(dc->clk_mgr); + if (dc->clk_mgr->funcs->get_dispclk_from_dentist) + clocks->dispclk_khz = dc->clk_mgr->funcs->get_dispclk_from_dentist(dc->clk_mgr); } clocks->ref_dtbclk_khz = dc->clk_mgr->bw_params->clk_table.entries[0].dtbclk_mhz * 1000; clocks->fclk_p_state_change_support = true; @@ -639,7 +640,8 @@ void dcn50_init_hw(struct dc *dc) dc->res_pool->hubbub->funcs->allow_self_refresh_control(dc->res_pool->hubbub, !dc->res_pool->hubbub->ctx->dc->debug.disable_stutter); - dcn50_initialize_min_clocks(dc); + if (dc->clk_mgr && dc->clk_mgr->funcs) + dcn50_initialize_min_clocks(dc); /* On HW init, allow idle optimizations after pipes have been turned off. * diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c index 72c2d3ca52f6..61ad6efa7a74 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn60/dcn60_hwseq.c @@ -643,7 +643,8 @@ void dcn60_init_hw(struct dc *dc) dc->res_pool->hubbub->funcs->allow_self_refresh_control(dc->res_pool->hubbub, !dc->res_pool->hubbub->ctx->dc->debug.disable_stutter); - dcn401_initialize_min_clocks(dc); + if (dc->clk_mgr && dc->clk_mgr->funcs) + dcn401_initialize_min_clocks(dc); /* On HW init, allow idle optimizations after pipes have been turned off. * @@ -1001,6 +1002,7 @@ static void dcn60_build_hubbub_perfmon_sequence( /** * dcn60_update_probe_status - Set the valid flag on a latched probe result. * @status: result sink whose u was written by the GET BLS step during execute + * @probe: current probe state used to determine measurement type and validity */ static void dcn60_update_probe_status(struct dc_probe_status *status) { @@ -1024,6 +1026,7 @@ static void dcn60_update_probe_status(struct dc_probe_status *status) /** * is_probe_measurement_type_for_hubbub - Returns true if the probe type is * served by the hubbub perfmon block on DCN60. + * @type: the probe measurement type to classify */ static bool is_probe_measurement_type_for_hubbub(enum dc_probe_type type) { From 7f9caa70aef0950e06d395ca0035831214d88187 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 15 Sep 2026 12:51:24 -0500 Subject: [PATCH 1920/2366] drm/amdgpu: Skip KFD mapping clear before initialization amdgpu_amdkfd_clear_kfd_mapping() assumes that a non-NULL kfd_dev has a fully populated node array. This is not true when KFD device initialization fails after probe. For example, kgd2kfd_device_init() sets num_nodes before checking PCIe atomics support. On Polaris systems without the required atomics, it returns before allocating nodes[0], but the kfd_dev remains attached to the amdgpu device. A later GPU reset then dereferences nodes[0]->id. Require the authoritative KFD initialization flag before walking the node array, matching the existing KFD reset and teardown paths. Fixes: 70cadefcc616 ("drm/amdgpu: unmap all user mappings of framebuffer and doorbell before mode1 reset") Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5833 Reviewed-by: Alex Deucher Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 4ac1835823c47903fbb278bbf474773c46f59edc) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index 816d8817f0b2..054870e9078d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -330,7 +330,7 @@ void amdgpu_amdkfd_clear_kfd_mapping(struct amdgpu_device *adev) struct kfd_dev *kfd = adev->kfd.dev; unsigned int i; - if (!kfd) + if (!kfd || !kfd->init_complete) return; for (i = 0; i < kfd->num_nodes; i++) { From 5155002b03b24ba3ef91c5c313b8cf0171b24904 Mon Sep 17 00:00:00 2001 From: Chengjun Yao Date: Tue, 8 Sep 2026 10:15:43 +0800 Subject: [PATCH 1921/2366] drm/amdgpu: fix rmmio iounmap skipped on device removal amdgpu_pci_remove() calls drm_dev_unplug() before fini_sw(), so drm_dev_enter() is already false there and the iounmap() guarded by it is skipped. This .remove path runs on both hot-unplug and plain rmmod, so the register BAR ioremap mapping leaks one instance per unload. Unmap rmmio unconditionally (guard only on non-NULL) and drop the now unused idx. Fixes: 62d5f9f7110a ("drm/amdgpu: Unmap MMIO mappings when device is not unplugged") Signed-off-by: Chengjun Yao Reviewed-by: Asad Kamal Signed-off-by: Alex Deucher (cherry picked from commit dd6f86a97260e5207d3329ad03aa89fdad61b1e6) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 933804349dbf..9269e780feb7 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -4335,7 +4335,7 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev) void amdgpu_device_fini_sw(struct amdgpu_device *adev) { - int i, idx; + int i; bool px; amdgpu_device_ip_fini(adev); @@ -4377,11 +4377,9 @@ void amdgpu_device_fini_sw(struct amdgpu_device *adev) if ((adev->pdev->class >> 8) == PCI_CLASS_DISPLAY_VGA) vga_client_unregister(adev->pdev); - if (drm_dev_enter(adev_to_drm(adev), &idx)) { - + if (adev->rmmio) { iounmap(adev->rmmio); adev->rmmio = NULL; - drm_dev_exit(idx); } if (IS_ENABLED(CONFIG_PERF_EVENTS)) From 2ac2fe765ef475f409616ac0b57c4a3922749b0f Mon Sep 17 00:00:00 2001 From: Francis Marlou Pacaro Date: Fri, 4 Sep 2026 08:20:21 +0800 Subject: [PATCH 1922/2366] drm/amd/display: fix MALL hysteresis timer underflow at high refresh rates dcn30_apply_idle_power_optimizations() derives the MALL frame cache hysteresis timer with tmr_delay = (uint32_t)(div_u64(..., denom) - 64LL); div_u64() returns a u64, so when the quotient is smaller than 64 the subtraction wraps instead of going negative and tmr_delay ends up huge. The loop that follows tries to squeeze it into the 6 bit register field by doubling denom, but that only makes the quotient smaller, so tmr_delay can never converge. tmr_scale is bumped past 3 and the function gives up with /* Delay exceeds range of hysteresis timer */ ASSERT(false); even though the requested delay is too *short* to encode, not too long. With mall_additional_timer_percent left at its default of 0, the quotient drops below 64 once the refresh rate used for the calculation goes above ~243 Hz. Every DCN 3.0 display above that loses MALL static screen entirely and splats a WARN once per boot. Reproduced on Navi 23 (RX 6600) driving 1920x1080, resetting /sys/kernel/debug/clear_warn_once between modes: refresh MALL ASSERT 144 Hz enabled no 240 Hz enabled no 280 Hz skipped yes 360 Hz skipped yes Commit 3bb68cec4db8 ("drm/amd/display: Add Overflow check to skip MALL") already covered the other end of the range, where a large stutter period makes the delay too long to encode. Cover the short end by clamping to 0, which selects the shortest hysteresis the register can express, 65.28us * 64 = ~4.18ms. That is marginally longer than what the formula asks for at these refresh rates, and erring long is the safe direction: it only delays MALL entry, it can never enter early. The numerator does not change between iterations, only denom does, so compute it once and keep both call sites inside 100 columns. The genuinely out of range case at very low refresh rates still reaches the ASSERT, which is where it belongs. Fixes: 52f2e83e2fe5 ("drm/amdgpu/display: add MALL support (v2)") Signed-off-by: Francis Marlou Pacaro Reviewed-by: Leo Li Signed-off-by: Alex Deucher (cherry picked from commit 387550e53e1405f1f960b62b22f8783db17c8e1d) --- .../gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c index cb163902e12e..d669b47af120 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn30/dcn30_hwseq.c @@ -1064,10 +1064,12 @@ bool dcn30_apply_idle_power_optimizations(struct dc *dc, bool enable) */ unsigned int denom = refresh_hz * 6528; unsigned int stutter_period = dc->current_state->perf_params.stutter_period_us; + uint64_t num = (1000000LL + 2 * stutter_period * refresh_hz) * + (100LL + dc->debug.mall_additional_timer_percent); + uint64_t tmr_ticks; - tmr_delay = (uint32_t)(div_u64(((1000000LL + 2 * stutter_period * refresh_hz) * - (100LL + dc->debug.mall_additional_timer_percent) + denom - 1), - denom) - 64LL); + tmr_ticks = div_u64(num + denom - 1, denom); + tmr_delay = tmr_ticks > 64 ? (uint32_t)(tmr_ticks - 64) : 0; /* In some cases the stutter period is really big (tiny modes) in these * cases MALL cant be enabled, So skip these cases to avoid a ASSERT() @@ -1089,9 +1091,8 @@ bool dcn30_apply_idle_power_optimizations(struct dc *dc, bool enable) } denom *= 2; - tmr_delay = (uint32_t)(div_u64(((1000000LL + 2 * stutter_period * refresh_hz) * - (100LL + dc->debug.mall_additional_timer_percent) + denom - 1), - denom) - 64LL); + tmr_ticks = div_u64(num + denom - 1, denom); + tmr_delay = tmr_ticks > 64 ? (uint32_t)(tmr_ticks - 64) : 0; } /* Copy HW cursor */ From df5cdc2c832ca4e8a6d774596b9005558761a403 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 16 Sep 2026 12:00:24 -1000 Subject: [PATCH 1923/2366] sched_ext: Derive SCX_RQ_IN_WAKEUP from the core enqueue flags schedule_deferred_locked() skips scheduling a deferred action while SCX_RQ_IN_WAKEUP is set and relies on the task_woken_scx() call that follows a wakeup enqueue to run it. enqueue_task_scx() sets the flag from the merged enqueue flags, which include the flags stashed for a remote activation. move_remote_task_to_local_dsq() thus sets SCX_RQ_IN_WAKEUP on the destination rq when the moved task was woken up, although no task_woken_scx() follows that activation. An IMMED insert into a busy destination requests a local reenqueue during that enqueue. The request gets linked but not scheduled and stays pending until an unrelated wakeup or preemption on that CPU runs the deferred actions. The IMMED task sits behind the running task in the meantime. If nothing runs them before the scheduler is disabled, the request outlives the scheduler and points into its freed per-cpu area, which the next scheduler dereferences from run_deferred(). Test the core enqueue flags for the wakeup bit. Only the core's wakeup path is followed by task_woken_scx(). Fixes: 57ccf5ccdc56 ("sched_ext: Fix enqueue_task_scx() truncation of upper enqueue flags") Cc: stable@vger.kernel.org # v7.1+ Reported-by: Andrea Righi Link: https://lore.kernel.org/all/20260916145807.3250167-1-arighi@nvidia.com/ Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 725b890fcab9..e207ccd17164 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2141,7 +2141,12 @@ static void enqueue_task_scx(struct rq *rq, struct task_struct *p, int core_enq_ int sticky_cpu = p->scx.sticky_cpu; u64 enq_flags = core_enq_flags | rq->scx.remote_activate_enq_flags; - if (enq_flags & ENQUEUE_WAKEUP) + /* + * SCX_RQ_IN_WAKEUP promises a task_woken_scx() call once this enqueue + * returns. Only the core's wakeup path delivers one. The flags stashed + * for a remote activation may carry the wakeup bit without it. + */ + if (core_enq_flags & ENQUEUE_WAKEUP) rq->scx.flags |= SCX_RQ_IN_WAKEUP; /* From 67b4411538c8341692548429d43256f25be99f7a Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 18:00:36 +0000 Subject: [PATCH 1924/2366] drm/nouveau: Fix bridge reference leak in nv1a_ram_new() pci_get_domain_bus_and_slot() takes a reference to the PCI device, which is never released once the memory size has been read from its config space. Drop the reference before returning. Fixes: 2fa6d6cdaf283c05 ("drm/nouveau: deprecate pci_get_bus_and_slot()") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260916180036.2090118-1-vulab@iscas.ac.cn --- drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c index 18241c6ba5fa..4d52a158f320 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c @@ -51,6 +51,8 @@ nv1a_ram_new(struct nvkm_fb *fb, struct nvkm_ram **pram) mib = ((mem >> 4) & 127) + 1; } + pci_dev_put(bridge); + return nvkm_ram_new_(&nv04_ram_func, fb, NVKM_RAM_TYPE_STOLEN, mib * 1024 * 1024, pram); } From cb86607ada73185f321baa7f9d94a08a3a40bbf5 Mon Sep 17 00:00:00 2001 From: fangqiurong Date: Thu, 17 Sep 2026 15:52:41 +0800 Subject: [PATCH 1925/2366] sched_ext: Don't run ops.dequeue() with a DSQ lock held ops.dequeue() is invoked with the source user DSQ's lock still held on the consume and move paths (scx_consume_dispatch_q(), move_task_between_dsqs()). A BPF scheduler which locks the source user DSQ from ops.dequeue() - e.g. by iterating it with bpf_iter_scx_dsq - self-deadlocks. ops.dequeue() can only call the "any" kfuncs and none of them can lock a builtin DSQ, so the global and bypass paths can't deadlock; however, all DSQ locks share one lockdep class, so iterating any user DSQ from ops.dequeue() on those paths trips the recursion check. Move the invocation after the DSQ unlock on all three paths. SCX_TASK_IN_CUSTODY is cleared under the lock serializing the transfer so that the callback is invoked exactly once. Fixes: ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") Cc: stable@vger.kernel.org # v7.1+ Acked-by: Andrea Righi Signed-off-by: fangqiurong Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 44 ++++++++++++++++++------------------- kernel/sched/ext/internal.h | 3 +-- kernel/sched/ext/sub.c | 10 ++++----- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index e207ccd17164..f568fd9973f6 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -1499,27 +1499,22 @@ static inline bool task_scx_migrating(struct task_struct *p) return p->scx.sticky_cpu >= 0; } -/* - * Call ops.dequeue() if the task is in BPF custody and not migrating. - * Clears %SCX_TASK_IN_CUSTODY when the callback is invoked. - */ -static void call_task_dequeue(struct scx_sched *sch, struct rq *rq, - struct task_struct *p, u64 deq_flags) +/* Must be called under the lock serializing @p's custody transfers. */ +static bool task_leave_custody(struct task_struct *p) { if (!(p->scx.flags & SCX_TASK_IN_CUSTODY) || task_scx_migrating(p)) - return; - - if (SCX_HAS_OP(sch, dequeue)) - SCX_CALL_OP_TASK(sch, dequeue, rq, p, deq_flags); + return false; p->scx.flags &= ~SCX_TASK_IN_CUSTODY; + return true; } static void rq_owned_post_enq(struct scx_sched *sch, struct rq *rq, struct scx_dispatch_q *dsq, struct task_struct *p, u64 enq_flags) { - call_task_dequeue(sch, rq, p, 0); + if (task_leave_custody(p) && SCX_HAS_OP(sch, dequeue)) + SCX_CALL_OP_TASK(sch, dequeue, rq, p, 0); /* * Only local inserts get the wakeup treatment below. Rejects kick the @@ -1705,20 +1700,28 @@ static void scx_dispatch_enqueue(struct scx_sched *sch, struct rq *rq, if (is_rq_owned) { rq_owned_post_enq(sch, rq, dsq, p, enq_flags); } else { + bool call_dequeue = false; + /* * Global and bypass DSQs are terminal - the task leaves the - * scheduler's custody, so ops.dequeue() fires here. It can run + * scheduler's custody, so ops.dequeue() fires. It can run * without @p's rq lock (finish_dispatch() passes the dispatch * rq); that's safe because dequeue_task_scx() waits on * SCX_OPSS_DISPATCHING (see the ops_state note above) and so * can't race it. A non-terminal DSQ keeps the task in custody. + * The custody transfer happens under @dsq->lock so that later + * consumers see the flag clear; the callback runs after + * @dsq->lock is dropped because it may lock a DSQ itself. */ if (dsq->id == SCX_DSQ_GLOBAL || dsq->id == SCX_DSQ_BYPASS) - call_task_dequeue(sch, rq, p, 0); + call_dequeue = task_leave_custody(p); else p->scx.flags |= SCX_TASK_IN_CUSTODY; raw_spin_unlock(&dsq->lock); + + if (call_dequeue && SCX_HAS_OP(sch, dequeue)) + SCX_CALL_OP_TASK(sch, dequeue, rq, p, 0); } /* @@ -2215,7 +2218,7 @@ static void ops_dequeue(struct rq *rq, struct task_struct *p, u64 deq_flags) /* * A queued task must always be in BPF scheduler's custody. If * SCX_TASK_IN_CUSTODY is clear, finish_dispatch() on another - * CPU has already passed call_task_dequeue() (which clears the + * CPU has already passed task_leave_custody() (which clears the * flag), but has not yet written SCX_OPSS_NONE. That final * store does not require this rq's lock, so retrying with * cpu_relax() is bounded: we will observe NONE (or DISPATCHING, @@ -2263,7 +2266,8 @@ static void ops_dequeue(struct rq *rq, struct task_struct *p, u64 deq_flags) * NONE but the task may still have %SCX_TASK_IN_CUSTODY set until * it is enqueued on the destination. */ - call_task_dequeue(sch, rq, p, deq_flags); + if (task_leave_custody(p) && SCX_HAS_OP(sch, dequeue)) + SCX_CALL_OP_TASK(sch, dequeue, rq, p, deq_flags); } static bool dequeue_task_scx(struct rq *rq, struct task_struct *p, int core_deq_flags) @@ -2379,14 +2383,10 @@ static void wakeup_preempt_scx(struct rq *rq, struct task_struct *p, int wake_fl } void scx_move_local_task_to_local_dsq(struct scx_sched *sch, struct task_struct *p, - u64 enq_flags, struct scx_dispatch_q *src_dsq, - struct rq *dst_rq) + u64 enq_flags, struct rq *dst_rq) { struct scx_dispatch_q *dst_dsq = scx_resolve_local_dsq(sch, dst_rq, p, &enq_flags); - /* @p is on @dst_rq, an rq-owned @src_dsq is covered by the rq lock */ - if (!dsq_is_rq_owned(src_dsq)) - lockdep_assert_held(&src_dsq->lock); lockdep_assert_rq_held(dst_rq); WARN_ON_ONCE(p->scx.holding_cpu >= 0); @@ -2634,8 +2634,8 @@ static struct rq *move_task_between_dsqs(struct scx_sched *sch, /* @p is going from a non-local DSQ to a local DSQ */ if (src_rq == dst_rq) { scx_task_unlink_from_dsq(p, src_dsq); - scx_move_local_task_to_local_dsq(sch, p, enq_flags, src_dsq, dst_rq); raw_spin_unlock(&src_dsq->lock); + scx_move_local_task_to_local_dsq(sch, p, enq_flags, dst_rq); } else { raw_spin_unlock(&src_dsq->lock); move_remote_task_to_local_dsq(sch, p, enq_flags, src_rq, dst_rq); @@ -2685,8 +2685,8 @@ bool scx_consume_dispatch_q(struct scx_sched *sch, struct rq *rq, if (rq == task_rq) { scx_task_unlink_from_dsq(p, dsq); - scx_move_local_task_to_local_dsq(sch, p, enq_flags, dsq, rq); raw_spin_unlock(&dsq->lock); + scx_move_local_task_to_local_dsq(sch, p, enq_flags, rq); return true; } diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 115c96fbf932..d150de10a5c9 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -2065,8 +2065,7 @@ void scx_dispatch_dequeue(struct rq *rq, struct task_struct *p); void scx_do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags, int sticky_cpu); void scx_move_local_task_to_local_dsq(struct scx_sched *sch, struct task_struct *p, - u64 enq_flags, struct scx_dispatch_q *src_dsq, - struct rq *dst_rq); + u64 enq_flags, struct rq *dst_rq); bool scx_consume_dispatch_q(struct scx_sched *sch, struct rq *rq, struct scx_dispatch_q *dsq, u64 enq_flags); bool scx_consume_global_dsq(struct scx_sched *sch, struct rq *rq); diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index a30c965b175d..3036190b0662 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -555,8 +555,8 @@ static void scx_rescue_timerfn(struct timer_list *timer) scx.dsq_list.node); scx_task_unlink_from_dsq(p, &rq->scx.rescue.dsq); scx_rescue_admit(rq, p, slice); - scx_move_local_task_to_local_dsq(scx_task_sched(p), p, SCX_ENQ_IGNORE_CAPS, - &rq->scx.rescue.dsq, rq); + scx_move_local_task_to_local_dsq(scx_task_sched(p), p, + SCX_ENQ_IGNORE_CAPS, rq); if (sched_class_above(&ext_sched_class, rq->curr->sched_class)) resched_curr(rq); } else if (p->scx.dsq && rq->scx.rescue.budget > 2 * scx_rescue_quantum_ns) { @@ -572,7 +572,7 @@ static void scx_rescue_timerfn(struct timer_list *timer) scx_task_unlink_from_dsq(p, &rq->scx.local_dsq); scx_move_local_task_to_local_dsq(scx_task_sched(p), p, SCX_ENQ_HEAD | SCX_ENQ_PREEMPT | SCX_ENQ_IGNORE_CAPS, - &rq->scx.local_dsq, rq); + rq); } out_arm: scx_rescue_timer_arm(rq); @@ -596,8 +596,8 @@ void scx_rescue_flush(struct rq *rq) /* and flush out all pending ones */ list_for_each_entry_safe(p, n, &rq->scx.rescue.dsq.list, scx.dsq_list.node) { scx_task_unlink_from_dsq(p, &rq->scx.rescue.dsq); - scx_move_local_task_to_local_dsq(scx_task_sched(p), p, SCX_ENQ_IGNORE_CAPS, - &rq->scx.rescue.dsq, rq); + scx_move_local_task_to_local_dsq(scx_task_sched(p), p, + SCX_ENQ_IGNORE_CAPS, rq); } timer_delete(&rq->scx.rescue.timer); From 9ec7ba20c97d92fa59b351832aeeb934b3b16177 Mon Sep 17 00:00:00 2001 From: fangqiurong Date: Thu, 17 Sep 2026 15:52:42 +0800 Subject: [PATCH 1926/2366] selftests/sched_ext: Test that ops.dequeue() can iterate the consumed DSQ Add a scheduler whose ops.dequeue() iterates the source user DSQ with bpf_iter_scx_dsq. The iteration takes the DSQ's raw spinlock; on a kernel that runs ops.dequeue() while the consume path still holds that lock, the first task consumed self-deadlocks the CPU with IRQs disabled. The watchdog cannot recover from that state, so on an unfixed kernel this test wedges the system instead of failing cleanly. On a fixed kernel the scheduler runs clean and the test passes. Signed-off-by: fangqiurong Signed-off-by: Tejun Heo --- tools/testing/selftests/sched_ext/Makefile | 1 + .../selftests/sched_ext/dequeue_iter.bpf.c | 73 +++++++++++++++++ .../selftests/sched_ext/dequeue_iter.c | 79 +++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 tools/testing/selftests/sched_ext/dequeue_iter.bpf.c create mode 100644 tools/testing/selftests/sched_ext/dequeue_iter.c diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile index 3cfe90e0f34f..49897727f535 100644 --- a/tools/testing/selftests/sched_ext/Makefile +++ b/tools/testing/selftests/sched_ext/Makefile @@ -164,6 +164,7 @@ all_test_bpfprogs := $(foreach prog,$(wildcard *.bpf.c),$(INCLUDE_DIR)/$(patsubs auto-test-targets := \ create_dsq \ dequeue \ + dequeue_iter \ enq_last_no_enq_fails \ ddsp_bogus_dsq_fail \ ddsp_vtimelocal_fail \ diff --git a/tools/testing/selftests/sched_ext/dequeue_iter.bpf.c b/tools/testing/selftests/sched_ext/dequeue_iter.bpf.c new file mode 100644 index 000000000000..76c2c71a90b6 --- /dev/null +++ b/tools/testing/selftests/sched_ext/dequeue_iter.bpf.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ops.dequeue() of this scheduler iterates the user DSQ it consumes + * tasks from with bpf_iter_scx_dsq, which takes the DSQ lock. + * On a kernel that still runs ops.dequeue() with that lock held, the + * iteration self-deadlocks the CPU - this test wedges the system on + * unfixed kernels instead of failing cleanly. + * + * Copyright (c) 2026 fangqiurong + */ + +#include + +char _license[] SEC("license") = "GPL"; + +UEI_DEFINE(uei); + +#define TEST_DSQ_ID 1000 + +u64 dq_count; + +s32 BPF_STRUCT_OPS_SLEEPABLE(dequeue_iter_init) +{ + return scx_bpf_create_dsq(TEST_DSQ_ID, -1); +} + +s32 BPF_STRUCT_OPS(dequeue_iter_select_cpu, struct task_struct *p, + s32 prev_cpu, u64 wake_flags) +{ + return prev_cpu; +} + +void BPF_STRUCT_OPS(dequeue_iter_enqueue, struct task_struct *p, u64 enq_flags) +{ + scx_bpf_dsq_insert(p, TEST_DSQ_ID, SCX_SLICE_DFL, enq_flags); +} + +void BPF_STRUCT_OPS(dequeue_iter_dispatch, s32 cpu, struct task_struct *task) +{ + scx_bpf_dsq_move_to_local(TEST_DSQ_ID, 0); +} + +void BPF_STRUCT_OPS(dequeue_iter_dequeue, struct task_struct *p, u64 deq_flags) +{ + struct bpf_iter_scx_dsq it; + struct task_struct *t; + + if (!bpf_iter_scx_dsq_new(&it, TEST_DSQ_ID, 0)) { + while ((t = bpf_iter_scx_dsq_next(&it))) + ; + } + bpf_iter_scx_dsq_destroy(&it); + + __sync_fetch_and_add(&dq_count, 1); +} + +void BPF_STRUCT_OPS(dequeue_iter_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); + scx_bpf_destroy_dsq(TEST_DSQ_ID); +} + +SEC(".struct_ops.link") +struct sched_ext_ops dequeue_iter_ops = { + .init = (void *)dequeue_iter_init, + .select_cpu = (void *)dequeue_iter_select_cpu, + .enqueue = (void *)dequeue_iter_enqueue, + .dispatch = (void *)dequeue_iter_dispatch, + .dequeue = (void *)dequeue_iter_dequeue, + .exit = (void *)dequeue_iter_exit, + .timeout_ms = 1000U, + .name = "dequeue_iter", +}; diff --git a/tools/testing/selftests/sched_ext/dequeue_iter.c b/tools/testing/selftests/sched_ext/dequeue_iter.c new file mode 100644 index 000000000000..f60711bf3b39 --- /dev/null +++ b/tools/testing/selftests/sched_ext/dequeue_iter.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 fangqiurong + */ +#include +#include +#include +#include +#include "dequeue_iter.bpf.skel.h" +#include "scx_test.h" + +#define DQ_TARGET 10 +#define DQ_DEADLINE_MS 3000 + +static unsigned long long now_ms(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + return ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000; +} + +static enum scx_test_status setup(void **ctx) +{ + struct dequeue_iter *skel; + + skel = dequeue_iter__open(); + SCX_FAIL_IF(!skel, "Failed to open"); + SCX_ENUM_INIT(skel); + SCX_FAIL_IF(dequeue_iter__load(skel), "Failed to load skel"); + + *ctx = skel; + + return SCX_TEST_PASS; +} + +static enum scx_test_status run(void *ctx) +{ + struct dequeue_iter *skel = ctx; + struct bpf_link *link; + unsigned long long end; + + link = bpf_map__attach_struct_ops(skel->maps.dequeue_iter_ops); + SCX_FAIL_IF(!link, "Failed to attach scheduler"); + + end = now_ms() + DQ_DEADLINE_MS; + while (skel->bss->dq_count < DQ_TARGET && !UEI_EXITED(skel, uei) && + now_ms() < end) + usleep(100); + + bpf_link__destroy(link); + + SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG)); + + if (skel->bss->dq_count < DQ_TARGET) { + SCX_ERR("ops.dequeue() fired only %llu times", + (unsigned long long)skel->bss->dq_count); + return SCX_TEST_FAIL; + } + + return SCX_TEST_PASS; +} + +static void cleanup(void *ctx) +{ + struct dequeue_iter *skel = ctx; + + dequeue_iter__destroy(skel); +} + +struct scx_test dequeue_iter = { + .name = "dequeue_iter", + .description = "Verify ops.dequeue() can iterate its source user DSQ", + .setup = setup, + .run = run, + .cleanup = cleanup, +}; +REGISTER_SCX_TEST(&dequeue_iter) From 3565893cc72cdf6b795cf6a33e7ff9605322334d Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Wed, 16 Sep 2026 05:16:38 +0000 Subject: [PATCH 1927/2366] btrfs: clear free space tree creation state on rebuild failure btrfs_rebuild_free_space_tree() sets BTRFS_FS_CREATING_FREE_SPACE_TREE before rebuilding the free space tree. Several error paths return without clearing this flag. The transaction restart failure path can leave the flag set on a live filesystem, causing delayed reference processing to be skipped. Clear it on all free space tree rebuild failure paths. Keep BTRFS_FS_FREE_SPACE_TREE_UNTRUSTED set, since a failed rebuild leaves the free space tree untrusted. Callers must fall back to extent-tree caching. Fixes: 882af9f13e83 ("btrfs: handle free space tree rebuild in multiple transactions") CC: stable@vger.kernel.org # 6.14+ Assisted-by: LLM Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Guanghui Yang <3497809730@qq.com> Signed-off-by: David Sterba --- fs/btrfs/free-space-tree.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/free-space-tree.c b/fs/btrfs/free-space-tree.c index 1b3d82ae3de8..b7a4a6ade30f 100644 --- a/fs/btrfs/free-space-tree.c +++ b/fs/btrfs/free-space-tree.c @@ -1353,7 +1353,7 @@ int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info) if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); btrfs_end_transaction(trans); - return ret; + goto out_clear; } node = rb_first_cached(&fs_info->block_group_cache_tree); @@ -1371,14 +1371,16 @@ int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info) if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); btrfs_end_transaction(trans); - return ret; + goto out_clear; } next: if (btrfs_should_end_transaction(trans)) { btrfs_end_transaction(trans); trans = btrfs_start_transaction(free_space_root, 1); - if (IS_ERR(trans)) - return PTR_ERR(trans); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + goto out_clear; + } } node = rb_next(node); } @@ -1390,6 +1392,10 @@ int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info) ret = btrfs_commit_transaction(trans); clear_bit(BTRFS_FS_FREE_SPACE_TREE_UNTRUSTED, &fs_info->flags); return ret; + +out_clear: + clear_bit(BTRFS_FS_CREATING_FREE_SPACE_TREE, &fs_info->flags); + return ret; } static int __add_block_group_free_space(struct btrfs_trans_handle *trans, From 97fcd34aa9fd73cefe3120ac9a82ca9d7763922f Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 16 Sep 2026 15:43:41 +0100 Subject: [PATCH 1928/2366] btrfs: abort transaction on failure to update inode for hole punching and reflinking If we fail to update the inode we error out without aborting the transaction, which can result in a persistent inconsistency if after the failure the transaction is committed, as we have dropped file extent items from a range and either punched a hole or insert a new file extent item for that range (for reflinks). So add the missing transaction abort. Fixes: 2aaa66558172 ("Btrfs: add hole punching") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/file.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c index 20e15dc30bfb..f978c6524aa0 100644 --- a/fs/btrfs/file.c +++ b/fs/btrfs/file.c @@ -2509,8 +2509,10 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode, inode_set_ctime_current(&inode->vfs_inode)); ret = btrfs_update_inode(trans, inode); - if (ret) + if (unlikely(ret)) { + btrfs_abort_transaction(trans, ret); break; + } btrfs_end_transaction(trans); btrfs_btree_balance_dirty(fs_info); From aeab4c62875748ecfd390a47ac1d91ea7c9a6abb Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 16 Sep 2026 16:49:37 +0100 Subject: [PATCH 1929/2366] btrfs: check if there is space for chunk item when validating sys chunk array We checked if have enough remaining space for a key before dereferencing a key, but we then dereference a chunk item, to get the number of stripes, without checking if there is space for the item. So add a check to see if there is enough space for a chunk item before dereferencing the item to extract the stripe count. Fixes: 2a9bb78cfd36 ("btrfs: validate system chunk array at btrfs_validate_super()") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 819727460bcf..dc7ad92876c0 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -2357,6 +2357,10 @@ static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, key.type, cur); return -EUCLEAN; } + + if (unlikely(cur + sizeof(*chunk) > sys_array_size)) + goto short_read; + chunk = (struct btrfs_chunk *)(sb->sys_chunk_array + cur); num_stripes = btrfs_stack_chunk_num_stripes(chunk); if (unlikely(cur + btrfs_chunk_item_size(num_stripes) > sys_array_size)) From b797b52e88a66598a972111b02ef13edd8d03ed2 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sat, 12 Sep 2026 18:12:06 +0930 Subject: [PATCH 1930/2366] btrfs: add "/dev/root" exception for device path update [BEHAVIOR CHANGE] Since commit 108cc8733989 ("btrfs: fix a lockdep caused by path resolution during device scan"), users with btrfs rootfs but without an initramfs are complaining that grub2 can no longer detect the rootfs device: /usr/sbin/grub-probe: error: cannot find a device for / (is /dev mounted?). [CAUSE] Although using btrfs without an initramfs is not recommended (if a new device is added to the rootfs, the system can no longer boot, as there is no way to register all devices), there is still a minority of users doing this. If there is no initramfs but the rootfs is on a block-device-based filesystem, the kernel boot sequence initializes a minimal ramfs/tmpfs, creates "/dev/root" with the proper device number for the rootfs, and then invokes mount using "/dev/root". That's why the end user will get the mount output: /dev/root on / rw To be honest, this is a user space problem: no one should trust the device path shown in mount, only the device number. E.g. one can even use "/proc/self/fd/*" to mount an fs, and that proc path will be registered, and no one else can mount that fs using that path. Before commit 108cc8733989 ("btrfs: fix a lockdep caused by path resolution during device scan"), btrfs had an internal path lookup workaround to address such weird paths, it works by checking if the existing device path can still resolve to the device number. But that path resolution is deadlock prone, thus it's replaced by a simple devt check. This works fine in most cases, as a btrfs device is registered by udev at boot time, thus all paths are sane. However this will not work for systems without an initramfs, causing the unreachable "/dev/root" path to exist forever without a way to rename it. [WORKAROUND] Add an exception to the device path rename requirement. If the device has the name "/dev/root", we know it's booted without an initramfs, and only for that case we allow device path update. And if someone intentionally created "/dev/root" after boot, the existing devt checks will reject that weird name as usual. This should satisfy the minority of users, and still keep most of the existing guards preventing unexpected/unnecessary device path updates. Fixes: 108cc8733989 ("btrfs: fix a lockdep caused by path resolution during device scan") Link: https://lore.kernel.org/linux-btrfs/CAKLYgeL7nrA4nXcewdv9Fqg_s=3GS=vmoypnEiZBKQ7rySZFuQ@mail.gmail.com/ Link: https://lore.kernel.org/linux-btrfs/dfbe1e27-dab8-4d55-8cf3-0b28eeac5df4@gmail.com/ Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/volumes.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 427aa8e24fc3..7bbc8e745e6b 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -740,6 +740,36 @@ const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb) return has_metadata_uuid ? sb->metadata_uuid : sb->fsid; } +static bool should_rename_device(const struct btrfs_device *dev) +{ + bool ret; + const char *old_name; + + rcu_read_lock(); + old_name = rcu_dereference(dev->name); + /* + * For systems booted without an initramfs, the rootfs has the device + * name "/dev/root". + * + * Although using btrfs without an initramfs is not recommended (if a + * new device is added to the rootfs, the system can no longer boot, as + * there is no way to register all devices), there is still a minority + * of users doing this. + * + * And after the system is up, a later device scan on the real block + * device file will never get this device's name updated, as the + * device->devt is still the same. + * + * Here we add one and only one exception for "/dev/root", to allow the + * device name to be updated even if the new path points to the same + * block device. + */ + ret = (strcmp(old_name, "/dev/root") == 0); + rcu_read_unlock(); + + return ret; +} + /* * Add new device to list of registered devices * @@ -860,7 +890,8 @@ static noinline struct btrfs_device *device_list_add(const char *path, MAJOR(path_devt), MINOR(path_devt), current->comm, task_pid_nr(current)); - } else if (!device->name || device->devt != path_devt) { + } else if (!device->name || device->devt != path_devt || + should_rename_device(device)) { const char *old_name; /* From 72de4807ba84da485dda1a91572d66da9149e95a Mon Sep 17 00:00:00 2001 From: Anand Jain Date: Sun, 13 Sep 2026 02:06:28 +0800 Subject: [PATCH 1931/2366] btrfs: derive f_fsid with dev_t only when temp_fsid is active Commit c2a74ed0494c ("btrfs: derive f_fsid from on-disk fsid and dev_t") mixed dev_t into f_fsid for all single-device setups to avoid f_fsid collisions with cloned filesystems. However, doing this unconditionally breaks backward compatibility. statfs(2) f_fsid changes after a kernel upgrade, and also can shift across reboots or dev re-attaches as dev_t values change. Fix this by only mixing dev_t when temp_fsid is active. This means for non-temp_fsid setups or the original mount, we use the old method of deriving fsid based on the UUID. So in the case of a cloned Btrfs filesystem, we won't be able to maintain the same fsid across mount recycle if the mount order changes. Reported-by: Dave Hansen Link: https://lore.kernel.org/linux-btrfs/be0c08f5-2f31-40f5-8a3b-f2f58b3e00ff@intel.com Fixes: c2a74ed0494c ("btrfs: derive f_fsid from on-disk fsid and dev_t") CC: stable@vger.kernel.org # 7.2 Signed-off-by: Anand Jain Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/super.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index 464129b1b0d4..ddb620ac241b 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c @@ -1836,8 +1836,12 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf) f_fsid.val[0] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root) >> 32; f_fsid.val[1] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root); - /* Hash dev_t to avoid f_fsid collision with cloned filesystems. */ - if (fs_info->fs_devices->total_devices == 1) { + /* + * Hash dev_t to avoid f_fsid collisions with cloned filesystems. + * Only do this when a clone is present so the original filesystem + * (mounted first) maintains backward-compatible f_fsid behavior. + */ + if (fs_info->fs_devices->temp_fsid) { __kernel_fsid_t dev_fsid = u64_to_fsid(huge_encode_dev(fs_info->fs_devices->latest_dev->bdev->bd_dev)); From 40c2096961b4e8f48d1fc17406a90c5a36ac0a8d Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 14 Sep 2026 15:19:20 +0200 Subject: [PATCH 1932/2366] bpf: Verify global subprogs in each sleepability context Global subprograms are verified independently with a fresh verifier root. do_check_common() currently seeds that root's in_sleepable state from the program, even though a global subprogram can also run from callbacks whose execution context differs from the program's main entry point. In particular, workqueue and task-work callbacks are sleepable even when the containing program is not. A global subprogram of that program is therefore verified as non-sleepable, making in_rcu_cs() true and allowing loads of RCU-protected kptrs to produce trusted MEM_RCU pointers. The same subprogram can then be called from a sleepable callback without a classic RCU reader. It can retain such a pointer while the object is freed and use it after free. The verifier's execution-context predicates are complementary. A state is sleepable only when in_sleepable is set and no RCU, preemption, IRQ, or lock region is active. Each condition which prevents sleeping also provides RCU protection, while in_rcu_cs() treats a non-sleepable state as implicitly protected. Use this relationship to represent a global subprogram caller with only the result of in_sleepable_context(). A protected sleepable caller is normalized to in_sleepable=false at the independent verification root. This both prevents sleepable operations and makes in_rcu_cs() true without copying caller-owned lock state. Track only the contexts in which each global subprogram is actually reached. Verify it once if all reachable calls use the same context, and twice only if both sleepable and non-sleepable calls reach it. Calls found while verifying globals or asynchronous callbacks mark further contexts for checking. Repeat the existing subprogram walk until all called contexts have been verified; unreachable global calls remain unchecked. Accumulate instruction counts over those verification passes. Preserve the total recorded before each pass, since path accounting has already added this pass's synchronous instructions and its root total must also include asynchronous subprograms. This makes an unprotected callback verify the global subprogram as sleepable, turning its RCU-protected kptr load into an untrusted pointer. Protected callers and global subprograms which do not depend on implicit RCU protection remain valid. Fixes: 81f1d7a583fa ("bpf: wq: add bpf_wq_set_callback_impl") Fixes: 38aa7003e369 ("bpf: task work scheduling kfuncs") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260914131923.2544250-2-memxor@gmail.com Signed-off-by: Eduard Zingerman --- include/linux/bpf.h | 5 +-- kernel/bpf/verifier.c | 71 ++++++++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index e57af902560c..1d2676782d70 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1651,8 +1651,9 @@ static inline void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags struct bpf_func_info_aux { u16 linkage; bool unreliable; - bool called : 1; - bool verified : 1; + /* Indexed by in_sleepable. */ + bool called[2]; + bool verified[2]; }; enum bpf_jit_poke_reason { diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index ddba53eaa333..5d7080c260d8 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9931,6 +9931,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn, if (err == -EFAULT) return err; if (bpf_subprog_is_global(env, subprog)) { + struct bpf_func_info_aux *sub_aux = subprog_aux(env, subprog); const char *sub_name = bpf_subprog_name(env, subprog); const char *operation; bool returns_void; @@ -9962,11 +9963,10 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn, if (env->log.level & BPF_LOG_LEVEL) verbose(env, "Func#%d ('%s') is global and assumed valid.\n", subprog, sub_name); + sub_aux->called[in_sleepable_context(env)] = true; returns_void = subprog_returns_void(env, subprog); if (env->subprog_info[subprog].changes_pkt_data) clear_all_pkt_pointers(env); - /* mark global subprog for verifying after main prog */ - subprog_aux(env, subprog)->called = true; if (returns_void) bpf_diag_record_scrub(env, &caller->regs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED); else @@ -10784,11 +10784,7 @@ int bpf_get_helper_proto(struct bpf_verifier_env *env, int func_id, /* Check if we're in a sleepable context. */ static inline bool in_sleepable_context(struct bpf_verifier_env *env) { - return !env->cur_state->active_rcu_locks && - !env->cur_state->active_preempt_locks && - !env->cur_state->active_locks && - !env->cur_state->active_irq_id && - in_sleepable(env); + return !in_rcu_cs(env); } static const char *non_sleepable_context_description(struct bpf_verifier_env *env) @@ -19447,13 +19443,14 @@ static void free_states(struct bpf_verifier_env *env) } } -static int do_check_common(struct bpf_verifier_env *env, int subprog) +static int do_check_common(struct bpf_verifier_env *env, int subprog, bool is_sleepable) { bool pop_log = !(env->log.level & BPF_LOG_LEVEL2); struct bpf_subprog_info *sub = subprog_info(env, subprog); struct bpf_prog_aux *aux = env->prog->aux; struct bpf_verifier_state *state; struct bpf_reg_state *regs; + u32 old_insns_total = sub->insns_total; u32 insn_processed = env->insn_processed; int ret, i; @@ -19466,7 +19463,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog) state->curframe = 0; state->speculative = false; state->branches = 1; - state->in_sleepable = env->prog->sleepable; + state->in_sleepable = is_sleepable; state->frame[0] = kzalloc_obj(struct bpf_func_state, GFP_KERNEL_ACCOUNT); if (!state->frame[0]) { kfree(state); @@ -19607,8 +19604,10 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog) * not accounted as callees by account_current_path(). * Accumulate their total counts as total counts of the main or * global subprog hosting the async call. + * Start from the saved total of earlier contexts: adding to the current + * total would count this pass's synchronous paths twice. */ - env->subprog_info[subprog].insns_total = env->insn_processed - insn_processed; + sub->insns_total = old_insns_total + (env->insn_processed - insn_processed); return ret; } @@ -19636,14 +19635,19 @@ static int do_check_subprogs(struct bpf_verifier_env *env) { struct bpf_prog_aux *aux = env->prog->aux; struct bpf_func_info_aux *sub_aux; - int i, ret, new_cnt; + int context, i, ret, new_cnt; if (!aux->func_info) return 0; - /* exception callback is presumed to be always called */ - if (env->exception_callback_subprog) - subprog_aux(env, env->exception_callback_subprog)->called = true; + /* + * Callbacks cannot throw, so the exception callback always runs in the + * main program's context. It is presumed to be always called. + */ + if (env->exception_callback_subprog) { + sub_aux = subprog_aux(env, env->exception_callback_subprog); + sub_aux->called[env->prog->sleepable] = true; + } again: new_cnt = 0; @@ -19652,29 +19656,28 @@ static int do_check_subprogs(struct bpf_verifier_env *env) continue; sub_aux = subprog_aux(env, i); - if (!sub_aux->called || sub_aux->verified) - continue; + for (context = 0; context < ARRAY_SIZE(sub_aux->called); context++) { + if (!sub_aux->called[context] || sub_aux->verified[context]) + continue; - env->insn_idx = env->subprog_info[i].start; - WARN_ON_ONCE(env->insn_idx == 0); - ret = do_check_common(env, i); - if (ret) { - return ret; - } else if (env->log.level & BPF_LOG_LEVEL) { - verbose(env, "Func#%d ('%s') is safe for any args that match its prototype\n", - i, bpf_subprog_name(env, i)); + env->insn_idx = env->subprog_info[i].start; + WARN_ON_ONCE(env->insn_idx == 0); + ret = do_check_common(env, i, context); + if (ret) + return ret; + if (env->log.level & BPF_LOG_LEVEL) + verbose(env, "Func#%d ('%s') is safe for any args " + "that match its prototype\n", + i, bpf_subprog_name(env, i)); + + sub_aux->verified[context] = true; + new_cnt++; } - - /* We verified new global subprog, it might have called some - * more global subprogs that we haven't verified yet, so we - * need to do another pass over subprogs to verify those. - */ - sub_aux->verified = true; - new_cnt++; } - /* We can't loop forever as we verify at least one global subprog on - * each pass. + /* + * We can't loop forever as each pass verifies at least one new context, + * and there are only two contexts per global subprog. */ if (new_cnt) goto again; @@ -19687,7 +19690,7 @@ static int do_check_main(struct bpf_verifier_env *env) int ret; env->insn_idx = 0; - ret = do_check_common(env, 0); + ret = do_check_common(env, 0, env->prog->sleepable); if (!ret) env->prog->aux->stack_depth = env->subprog_info[0].stack_depth; return ret; From a452e729b7be317837bb2157d5d88aa3de9873e6 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 14 Sep 2026 15:19:21 +0200 Subject: [PATCH 1933/2366] selftests/bpf: Test global subprog callback contexts Exercise global subprogram verification from workqueue callbacks, which can run in a sleepable context even when the containing program is not sleepable. An unprotected callback must not let the global subprogram use implicit RCU protection inherited from the program. Add a negative case which loads an RCU-protected task kptr in a global subprogram reached from a workqueue callback. It fails on an unfixed kernel because the program is incorrectly accepted. Also cover a workqueue callback protected by an explicit RCU read-side critical section, where the same global subprogram remains valid. Call the same harmless global subprogram directly from the main program and from an unprotected callback. Mark it __weak __noinline so both calls survive optimization, and check that its instruction statistics account for both verification contexts. This also verifies that global calls from callbacks are not rejected wholesale. Workqueue callbacks return zero explicitly after the global call, as required by their contract. Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260914131923.2544250-3-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../bpf/progs/verifier_async_cb_context.c | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c index e0926767bbd3..1b653bfb63eb 100644 --- a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c +++ b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c @@ -9,6 +9,11 @@ char _license[] SEC("license") = "GPL"; +struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym; +void bpf_task_release(struct task_struct *p) __ksym; +void bpf_rcu_read_lock(void) __ksym; +void bpf_rcu_read_unlock(void) __ksym; + /* Timer tests */ struct timer_elem { @@ -164,6 +169,7 @@ int syscall_btf_find_prog(void *ctx) struct wq_elem { struct bpf_wq w; + struct task_struct __kptr *task; }; struct { @@ -217,6 +223,106 @@ int wq_sleepable_prog(void *ctx) return 0; } +__noinline int wq_global_acquire(void) +{ + struct task_struct *task, *acquired; + struct wq_elem *val; + int key = 0; + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + task = val->task; + if (!task) + return 0; + + acquired = bpf_task_acquire(task); + if (acquired) + bpf_task_release(acquired); + return 0; +} + +static int wq_global_rcu_cb(void *map, int *key, void *value) +{ + wq_global_acquire(); + return 0; +} + +SEC("fentry/bpf_fentry_test1") +__failure __msg("R1 must be a rcu pointer") +int wq_global_rcu_prog(void *ctx) +{ + struct wq_elem *val; + int key = 0; + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + bpf_wq_init(&val->w, &wq_map, 0); + bpf_wq_set_callback(&val->w, wq_global_rcu_cb, 0); + return 0; +} + +static int wq_global_rcu_lock_cb(void *map, int *key, void *value) +{ + bpf_rcu_read_lock(); + wq_global_acquire(); + bpf_rcu_read_unlock(); + return 0; +} + +SEC("fentry/bpf_fentry_test1") +__success +int wq_global_rcu_lock_prog(void *ctx) +{ + struct wq_elem *val; + int key = 0; + + /* Verify the same global subprog in non-sleepable and protected contexts. */ + wq_global_acquire(); + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + bpf_wq_init(&val->w, &wq_map, 0); + bpf_wq_set_callback(&val->w, wq_global_rcu_lock_cb, 0); + return 0; +} + +__weak __noinline int wq_global_no_rcu(void) +{ + return 0; +} + +static int wq_global_no_rcu_cb(void *map, int *key, void *value) +{ + wq_global_no_rcu(); + return 0; +} + +SEC("fentry/bpf_fentry_test1") +__success __log_level(4) +__msg("subprog {{[0-9]+}} (wq_global_no_rcu) global insns_self 4 insns_total 4 stack 0") +int wq_global_no_rcu_prog(void *ctx) +{ + struct wq_elem *val; + int key = 0; + + /* Verify the same global in non-sleepable and unprotected contexts. */ + wq_global_no_rcu(); + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + bpf_wq_init(&val->w, &wq_map, 0); + bpf_wq_set_callback(&val->w, wq_global_no_rcu_cb, 0); + return 0; +} + /* Task work tests */ struct task_work_elem { From 70504de0bb627848667207bec7ccfd647deb8814 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Fri, 11 Sep 2026 00:18:24 +0800 Subject: [PATCH 1934/2366] xsk: Use a 32-bit compare in xsk_map_gen_lookup xsk_map_gen_lookup() loads a u32 key and compares it with max_entries using BPF_JMP_IMM. BPF immediates are sign-extended to 64 bits, so a max_entries value of 0x80000000 or higher becomes a threshold larger than every zero-extended 32-bit key. An out-of-range index then skips the bounds check and the generated lookup reads past xsk_map[]. Compare with BPF_JMP32_IMM so the check stays in 32-bit unsigned range. Fixes: e65650f291ee ("bpf: Implement map_gen_lookup() callback for XSKMAP") Reported-by: Vega Signed-off-by: Zhiling Zou Signed-off-by: Alexei Starovoitov Reviewed-by: Emil Tsalapatis Link: https://patch.msgid.link/7d2cb8e8dfaa9eb8fdff85156987a60960787dc3.1789056660.git.zhilinz@nebusec.ai Signed-off-by: Eduard Zingerman --- net/xdp/xskmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/xdp/xskmap.c b/net/xdp/xskmap.c index 3bff346308d0..bf00d6463c19 100644 --- a/net/xdp/xskmap.c +++ b/net/xdp/xskmap.c @@ -124,7 +124,7 @@ static int xsk_map_gen_lookup(struct bpf_map *map, struct bpf_insn *insn_buf) struct bpf_insn *insn = insn_buf; *insn++ = BPF_LDX_MEM(BPF_W, ret, index, 0); - *insn++ = BPF_JMP_IMM(BPF_JGE, ret, map->max_entries, 5); + *insn++ = BPF_JMP32_IMM(BPF_JGE, ret, map->max_entries, 5); *insn++ = BPF_ALU64_IMM(BPF_LSH, ret, ilog2(sizeof(struct xsk_sock *))); *insn++ = BPF_ALU64_IMM(BPF_ADD, mp, offsetof(struct xsk_map, xsk_map)); *insn++ = BPF_ALU64_REG(BPF_ADD, ret, mp); From 05762c5bc1cfdcac36747994fde2c04387a457f1 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:52 -0500 Subject: [PATCH 1935/2366] smb: client: fix next_buffer UAF and NextCommand bounds in compound PDUs Fix several related bounds checking and pointer lifecycle issues in receive_encrypted_standard()'s handling of compound encrypted frames: - Clear next_buffer after assigning it to server->bigbuf. A stale next_buffer pointer can lead to a use-after-free on subsequent error paths. - Update pdu_length to the decrypted plaintext size (buf_size). Using the pre-decryption length allows NextCommand to point into stale ciphertext residue. - Reject next_cmd values smaller than MID_HEADER_SIZE(server). - Fix an integer overflow in the upper bound check by verifying pdu_length - next_cmd < MID_HEADER_SIZE(server), ensuring the trailing slice is large enough for a header. Fixes: b24df3e30cbf ("cifs: update receive_encrypted_standard to handle compounded responses") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index cb4fd09f996e..fcf7033889c7 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -5365,6 +5365,7 @@ receive_encrypted_standard(struct TCP_Server_Info *server, length = decrypt_raw_data(server, buf, buf_size, NULL, false); if (length) return length; + pdu_length = buf_size; next_is_large = server->large_buf; one_more: @@ -5377,8 +5378,15 @@ receive_encrypted_standard(struct TCP_Server_Info *server, } if (next_cmd) { - if (WARN_ON_ONCE(next_cmd > pdu_length)) + if (next_cmd < MID_HEADER_SIZE(server) || + next_cmd > pdu_length || + pdu_length - next_cmd < MID_HEADER_SIZE(server)) { + unsigned int max_next = pdu_length > (unsigned int)MID_HEADER_SIZE(server) ? + pdu_length - (unsigned int)MID_HEADER_SIZE(server) : 0; + cifs_server_dbg(VFS, "invalid NextCommand offset %u out of range [%zu, %u]\n", + next_cmd, MID_HEADER_SIZE(server), max_next); return -1; + } if (next_is_large) next_buffer = (char *)cifs_buf_get(); else @@ -5414,6 +5422,7 @@ receive_encrypted_standard(struct TCP_Server_Info *server, server->bigbuf = buf = next_buffer; else server->smallbuf = buf = next_buffer; + next_buffer = NULL; goto one_more; } else if (ret != 0) { /* From b4694f269e66dfcd66991446375285723b6957bd Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:53 -0500 Subject: [PATCH 1936/2366] smb: client: validate minimum PDU size before smb2_get_data_area_len() __smb2_calc_size() calls smb2_get_data_area_len(), which reads command-specific struct fields to locate the data area. However, smb2_check_message() only validates StructureSize2, meaning a truncated response could cause smb2_get_data_area_len() to read out-of-bounds. Replace has_smb2_data_area[] with smb2_min_pdu_len[], which is now used to indicate both whether a command's response has a data area and the size of that fixed response struct. A non-zero entry means the command has a data area, and is the minimum length required before the struct is read. For each command with a data area, PDUs shorter than this minimum size are rejected instead of parsed. The minimum is not applied to SMB2 error responses, which carry only the 9-byte error body, the same exemption the StructureSize2 check above it already makes. STATUS_MORE_PROCESSING_REQUIRED is treated as a normal reply, since an in-progress SESSION_SETUP response carries a full body and a security blob. Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2misc.c | 69 ++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c index 9068175e57cd..0cfe60ae42c3 100644 --- a/fs/smb/client/smb2misc.c +++ b/fs/smb/client/smb2misc.c @@ -85,6 +85,36 @@ static const __le16 smb2_rsp_struct_sizes[NUMBER_OF_SMB2_COMMANDS] = { /* SMB2_OPLOCK_BREAK */ cpu_to_le16(24) }; +/* + * Minimum received PDU size for commands whose response carries a + * variable-length data area. A non-zero entry marks the command as + * having one, and gives the length smb2_check_message() requires + * before smb2_get_data_area_len() reads the offset and length fields + * out of the fixed response struct. + */ +static const size_t smb2_min_pdu_len[NUMBER_OF_SMB2_COMMANDS] = { + /* SMB2_NEGOTIATE */ sizeof(struct smb2_negotiate_rsp), + /* SMB2_SESSION_SETUP */ sizeof(struct smb2_sess_setup_rsp), + /* SMB2_LOGOFF */ 0, + /* SMB2_TREE_CONNECT */ 0, + /* SMB2_TREE_DISCONNECT */ 0, + /* SMB2_CREATE */ sizeof(struct smb2_create_rsp), + /* SMB2_CLOSE */ 0, + /* SMB2_FLUSH */ 0, + /* SMB2_READ */ sizeof(struct smb2_read_rsp), + /* SMB2_WRITE */ 0, + /* SMB2_LOCK */ 0, + /* SMB2_IOCTL */ sizeof(struct smb2_ioctl_rsp), + /* SMB2_CANCEL */ 0, + /* SMB2_ECHO */ 0, + /* SMB2_QUERY_DIRECTORY */ sizeof(struct smb2_query_directory_rsp), + /* SMB2_CHANGE_NOTIFY */ sizeof(struct smb2_change_notify_rsp), + /* SMB2_QUERY_INFO */ sizeof(struct smb2_query_info_rsp), + /* SMB2_SET_INFO */ 0, + /* SMB2_OPLOCK_BREAK */ 0, +}; + +#define smb2_has_data_area(cmd) (smb2_min_pdu_len[cmd] != 0) #define SMB311_NEGPROT_BASE_SIZE (sizeof(struct smb2_hdr) + sizeof(struct smb2_negotiate_rsp)) static __u32 get_neg_ctxt_len(struct smb2_hdr *hdr, __u32 len, @@ -233,6 +263,16 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len, } } + if ((shdr->Status == STATUS_SUCCESS || + shdr->Status == STATUS_MORE_PROCESSING_REQUIRED || + pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2_LE) && + smb2_has_data_area(command) && + len < smb2_min_pdu_len[command]) { + cifs_server_dbg(VFS, "SMB2 command %d response too short: %u < %zu\n", + command, len, smb2_min_pdu_len[command]); + return 1; + } + have_data = false; data_area_overlap = false; calc_len = __smb2_calc_size(buf, &have_data, &data_area_overlap); @@ -298,33 +338,6 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len, return 0; } -/* - * The size of the variable area depends on the offset and length fields - * located in different fields for various SMB2 responses. SMB2 responses - * with no variable length info, show an offset of zero for the offset field. - */ -static const bool has_smb2_data_area[NUMBER_OF_SMB2_COMMANDS] = { - /* SMB2_NEGOTIATE */ true, - /* SMB2_SESSION_SETUP */ true, - /* SMB2_LOGOFF */ false, - /* SMB2_TREE_CONNECT */ false, - /* SMB2_TREE_DISCONNECT */ false, - /* SMB2_CREATE */ true, - /* SMB2_CLOSE */ false, - /* SMB2_FLUSH */ false, - /* SMB2_READ */ true, - /* SMB2_WRITE */ false, - /* SMB2_LOCK */ false, - /* SMB2_IOCTL */ true, - /* SMB2_CANCEL */ false, /* BB CHECK this not listed in documentation */ - /* SMB2_ECHO */ false, - /* SMB2_QUERY_DIRECTORY */ true, - /* SMB2_CHANGE_NOTIFY */ true, - /* SMB2_QUERY_INFO */ true, - /* SMB2_SET_INFO */ false, - /* SMB2_OPLOCK_BREAK */ false -}; - /* * Returns the pointer to the beginning of the data area. Length of the data * area and the offset to it (from the beginning of the smb are also returned. @@ -451,7 +464,7 @@ __smb2_calc_size(void *buf, bool *have_data, bool *data_area_overlap) */ len += le16_to_cpu(pdu->StructureSize2); - if (has_smb2_data_area[le16_to_cpu(shdr->Command)] == false) + if (!smb2_has_data_area(le16_to_cpu(shdr->Command))) goto calc_size_exit; smb2_get_data_area_len(&offset, &data_length, shdr); From f73726b83e4756fdaa099e1bc1143293bd57ad79 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:54 -0500 Subject: [PATCH 1937/2366] smb: client: fix server->total_read for compound encrypted PDUs In receive_encrypted_standard(), server->total_read is left at the full decrypted frame size when walking sub-PDUs of a compound encrypted frame. As a result, cifs_handle_standard() passes this full size to smb2_check_message(), causing the PDU length guards to incorrectly validate the entire compound frame instead of the current sub-PDU. This allows truncated non-last sub-PDUs to bypass length validation, leading to out-of-bounds reads in smb2_get_data_area_len(). Fix this by setting server->total_read to the true length of the current sub-PDU: next_cmd for non-last sub-PDUs, and the remaining pdu_length for the last one. Fixes: b24df3e30cbf ("cifs: update receive_encrypted_standard to handle compounded responses") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index fcf7033889c7..7f2177f6fc01 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -5371,6 +5371,7 @@ receive_encrypted_standard(struct TCP_Server_Info *server, one_more: shdr = (struct smb2_hdr *)buf; next_cmd = le32_to_cpu(shdr->NextCommand); + server->total_read = next_cmd ? next_cmd : pdu_length; if (*num_mids >= MAX_COMPOUND) { cifs_server_dbg(VFS, "too many PDUs in compound\n"); From e83330c55edc0c3ac08aa6c95e49e4694c65523b Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:55 -0500 Subject: [PATCH 1938/2366] smb: client: fix missing lower-bound check on DFS referral string offsets parse_dfs_referrals() checks that DfsPathOffset and NetworkAddressOffset do not exceed the buffer end, but fails to check that they don't point inside the referral header itself. If a server provides an offset smaller than sizeof(struct dfs_referral_level_3), the derived string pointer overlaps with the struct fields, causing cifs_strndup_from_utf16() to interpret header data as UTF-16 strings. Fix this by enforcing that string offsets are at least sizeof(*ref). Fixes: 4ecce920e13a ("CIFS: move DFS response parsing out of SMB1 code") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/misc.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index 945194fe7a97..05168284f205 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -788,7 +788,11 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size, node->ref_flag = le16_to_cpu(ref->ReferralEntryFlags); /* copy DfsPath */ - if (le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) { + if (le16_to_cpu(ref->DfsPathOffset) < sizeof(*ref) || + le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) { + cifs_dbg(VFS, "%s: DfsPathOffset %u out of range [%zu, %td]\n", + __func__, le16_to_cpu(ref->DfsPathOffset), + sizeof(*ref), data_end - (char *)ref); rc = -EINVAL; goto parse_DFS_referrals_exit; } @@ -802,7 +806,11 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size, } /* copy link target UNC */ - if (le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) { + if (le16_to_cpu(ref->NetworkAddressOffset) < sizeof(*ref) || + le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) { + cifs_dbg(VFS, "%s: NetworkAddressOffset %u out of range [%zu, %td]\n", + __func__, le16_to_cpu(ref->NetworkAddressOffset), + sizeof(*ref), data_end - (char *)ref); rc = -EINVAL; goto parse_DFS_referrals_exit; } From 1b3221bb121079ad79a1f3c3aa360ba649832e7a Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:56 -0500 Subject: [PATCH 1939/2366] smb: client: reject short Next offsets in parse_server_interfaces() In parse_server_interfaces(), the server-supplied Next offset is validated against bytes_left, but not against the size of the interface structure itself. A small, non-zero Next value can pass the bounds check but advance the pointer by less than sizeof(*p). This causes the next iteration of the loop to read misaligned, overlapping structure fields. Fix this by ensuring the Next offset is at least sizeof(*p). Fixes: 7d34ec36abb8 ("smb3: fix for slab out of bounds on mount to ksmbd") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 7f2177f6fc01..bda940cb3784 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -785,9 +785,9 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, break; } /* Validate that Next doesn't point beyond the buffer */ - if (next > bytes_left) { - cifs_dbg(VFS, "%s: invalid Next pointer %zu > %zd\n", - __func__, next, bytes_left); + if (next < sizeof(*p) || next > bytes_left) { + cifs_dbg(VFS, "%s: invalid Next pointer %zu out of range [%zu, %zd]\n", + __func__, next, sizeof(*p), bytes_left); rc = -EINVAL; goto out; } From eeb5ef6083e1cefa2ef75041b5597ff228b8d7bb Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:57 -0500 Subject: [PATCH 1940/2366] smb: client: fix OOB struct field reads in move_smb2_ea_to_cifs() In move_smb2_ea_to_cifs(), the while (src_size > 0) loop condition is insufficient. It allows iteration to continue even if the remaining src_size is too small to contain a complete smb2_ea_info structure. Consequently, reads of ea_name_length and ea_value_length can occur out-of-bounds. Fix this by ensuring src_size >= sizeof(*src) before attempting to read any structure fields. Additionally, reject any next_entry_offset that is smaller than sizeof(*src) or that would advance the pointer beyond the available buffer. Note that for calls where the server returns a malformed EA list, the error returned to userspace changes from -ENODATA (getxattr) or -ERANGE (listxattr) to -EIO. This correctly signals a server protocol error rather than misleadingly indicating "attribute not present" or "output buffer too small". Fixes: 95907fea4fd8 ("cifs: Add support for reading attributes on SMB2+") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 25 +++++++++++++++++-------- fs/smb/client/trace.h | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index bda940cb3784..ee3c98e3f316 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -1053,8 +1053,9 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size, char *name, *value; size_t buf_size = dst_size; size_t name_len, value_len, user_name_len; + u32 next_off; - while (src_size > 0) { + while (src_size >= sizeof(*src)) { name_len = (size_t)src->ea_name_length; value_len = (size_t)le16_to_cpu(src->ea_value_length); @@ -1110,14 +1111,22 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size, if (!src->next_entry_offset) break; - if (src_size < le32_to_cpu(src->next_entry_offset)) { - /* stop before overrun buffer */ - rc = -ERANGE; - break; + next_off = le32_to_cpu(src->next_entry_offset); + if (next_off < sizeof(*src) || src_size < next_off) { + cifs_dbg(FYI, "EA next_entry_offset %u out of range [%zu, %zu]\n", + next_off, sizeof(*src), src_size); + rc = smb_EIO2(smb_eio_trace_ea_next_offset, + next_off, src_size); + goto out; + } + src_size -= next_off; + src = (void *)((char *)src + next_off); + if (src_size > 0 && src_size < sizeof(*src)) { + cifs_dbg(FYI, "EA next_entry_offset %u left truncated entry (%zu bytes)\n", + next_off, src_size); + rc = smb_EIO2(smb_eio_trace_ea_next_offset, next_off, src_size); + goto out; } - src_size -= le32_to_cpu(src->next_entry_offset); - src = (void *)((char *)src + - le32_to_cpu(src->next_entry_offset)); } /* didn't find the named attribute */ diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index b442cccd1530..bb8d0197cb54 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -27,6 +27,7 @@ EM(smb_eio_trace_copychunk_overcopy_c, "copychunk_overcopy_c") \ EM(smb_eio_trace_create_rsp_too_small, "create_rsp_too_small") \ EM(smb_eio_trace_dfsref_no_rsp, "dfsref_no_rsp") \ + EM(smb_eio_trace_ea_next_offset, "ea_next_offset") \ EM(smb_eio_trace_ea_overrun, "ea_overrun") \ EM(smb_eio_trace_extract_will_pin, "extract_will_pin") \ EM(smb_eio_trace_forced_shutdown, "forced_shutdown") \ From b09d092eb24ad0110f16a9b7c1ed5d2a0c1733dc Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:58 -0500 Subject: [PATCH 1941/2366] smb: client: fix missing iov bounds check in parse_posix_sids() In parse_posix_sids(), sidsbuf_end is calculated using the server-supplied out_len without being validated against the actual length of the received iov (iov_len). If a server provides an inflated out_len, sidsbuf_end will point past the end of the iov. This defeats the bounds guards in posix_info_sid_size(), allowing out-of-bounds reads into adjacent kernel memory. Fix this by rejecting responses where the calculated sidsbuf_end would exceed the received iov boundaries or cause pointer wraparound. Fixes: a90f37e3d7ac ("smb: client: parse owner/group when creating reparse points") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2inode.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 96063e355186..13fe8e3b48f3 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -77,6 +77,17 @@ static int parse_posix_sids(struct cifs_open_info_data *data, sidsbuf = (u8 *)qi + le16_to_cpu(qi->OutputBufferOffset) + qi_len; sidsbuf_end = sidsbuf + out_len - qi_len; + if (sidsbuf_end < sidsbuf) { + cifs_dbg(VFS, "%s: server-supplied out_len %u caused pointer wraparound\n", + __func__, out_len); + return -EINVAL; + } + if (sidsbuf_end > (u8 *)rsp_iov->iov_base + rsp_iov->iov_len) { + cifs_dbg(VFS, "%s: server-supplied out_len %u overruns iov by %td bytes\n", + __func__, out_len, + sidsbuf_end - ((u8 *)rsp_iov->iov_base + rsp_iov->iov_len)); + return -EINVAL; + } owner_len = posix_info_sid_size(sidsbuf, sidsbuf_end); if (owner_len == -1) From 4775c3b7a597907e0b97556c7986fda238a377ae Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:33:59 -0500 Subject: [PATCH 1942/2366] smb: client: fix potential OOB read in smb3_enum_snapshots() If snapshot_array_size is smaller than GMT_TOKEN_SIZE, smb3_enum_snapshots() sets ret_data_len to sizeof(struct smb_snapshot_array) without verifying the actual length of the server's reply. Because SMB2_ioctl() places no lower bound on the server-supplied OutputCount and allocates retbuf to exactly that length, a short reply results in ret_data_len exceeding the size of retbuf. The subsequent copy_to_user() then reads past the end of retbuf, leaking adjacent slab memory to userspace. The subsequent clamp check is ineffective as it only reduces ret_data_len. Fix this by rejecting replies shorter than sizeof(struct smb_snapshot_array) with -EIO. Note that the bound is set to the 12-byte struct size rather than the 16-byte MIN_SNAPSHOT_ARRAY_SIZE defined in MS-SMB2 3.3.5.15.1, because 12 bytes is exactly what copy_to_user() attempts to read. Fixes: e02789a53d71 ("smb3: enumerating snapshots was leaving part of the data off end") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index ee3c98e3f316..3464470d3297 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -2463,8 +2463,14 @@ smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon, * and retry the ioctl again with larger array size sufficient * to hold all of the snapshot GMT tokens on the second try. */ - if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE) + if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE) { + if (ret_data_len < sizeof(struct smb_snapshot_array)) { + rc = -EIO; + kfree(retbuf); + return rc; + } ret_data_len = sizeof(struct smb_snapshot_array); + } /* * We return struct SRV_SNAPSHOT_ARRAY, followed by From 5f0306e731e2f46e91419eae57eee3a241c055e0 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 16 Sep 2026 16:34:00 -0500 Subject: [PATCH 1943/2366] smb: client: fix reparse buffer bounds in cifs_query_reparse_point() In cifs_query_reparse_point(), the start >= end check before casting to struct reparse_data_buffer * only ensures the start pointer is within the response. It fails to verify that there is enough space remaining for the fixed 8-byte header of the structure. If a server provides a DataOffset that leaves less than 8 bytes remaining, the check passes, but subsequent reads of ReparseTag and ReparseDataLength will occur out-of-bounds. Fix this by ensuring the remaining space is at least the size of the reparse_data_buffer structure before accessing its fields. Fixes: 56e84c64fc25 ("cifs: Fix validation of SMB1 query reparse point response") Cc: stable@vger.kernel.org Signed-off-by: Frank Sorenson Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index f9aff0712794..6dddbd84b93b 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -3080,7 +3080,7 @@ int cifs_query_reparse_point(const unsigned int xid, end = 2 + get_bcc(&io_rsp->hdr) + (__u8 *)&io_rsp->ByteCount; start = (__u8 *)&io_rsp->hdr.Protocol + data_offset; - if (start >= end) { + if (start >= end || (size_t)(end - start) < sizeof(*buf)) { rc = smb_EIO2(smb_eio_trace_qreparse_data_area, (unsigned long)start - (unsigned long)io_rsp, (unsigned long)end - (unsigned long)io_rsp); From 5ea72f7b7139b123713a7983448f910bc4514d9e Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 18:02:02 +0000 Subject: [PATCH 1944/2366] drm/nouveau: Fix gem reference leak in validate_init() On the ttm_bo_reserve() failure and "vma not found" error paths, the loop breaks without adding the looked-up object to any validate list, so the reference taken by drm_gem_object_lookup() is never released; validate_fini() only walks the spliced lists. Drop the reference before breaking out on both paths. Fixes: 19ca10d82e33bcfe ("drm/nouveau/gem: lookup VMAs for buffers referenced by pushbuf ioctl") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260916180202.2090231-1-vulab@iscas.ac.cn --- drivers/gpu/drm/nouveau/nouveau_gem.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c index 0b7123b163e4..51188be57221 100644 --- a/drivers/gpu/drm/nouveau/nouveau_gem.c +++ b/drivers/gpu/drm/nouveau/nouveau_gem.c @@ -522,6 +522,7 @@ validate_init(struct nouveau_channel *chan, struct drm_file *file_priv, if (unlikely(ret)) { if (ret != -ERESTARTSYS) NV_PRINTK(err, cli, "fail reserve\n"); + drm_gem_object_put(gem); break; } } @@ -531,6 +532,7 @@ validate_init(struct nouveau_channel *chan, struct drm_file *file_priv, struct nouveau_vma *vma = nouveau_vma_find(nvbo, vmm); if (!vma) { NV_PRINTK(err, cli, "vma not found!\n"); + drm_gem_object_put(gem); ret = -EINVAL; break; } From 1e04611d3735543bd80a67d9d13dc13f503746fb Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 18:03:42 +0000 Subject: [PATCH 1945/2366] drm/nouveau: Fix runtime PM leak in nouveau_connector_detect() If nvif_outp_edid_get() fails, nouveau_connector_detect() returns early without dropping the runtime PM reference taken at the start of the function, keeping the device powered on until the next successful detect. Balance the reference on the error path like the other exit paths do. Fixes: 0cd7e0718139 ("drm/nouveau/disp: add output method to fetch edid") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260916180342.2090360-1-vulab@iscas.ac.cn --- drivers/gpu/drm/nouveau/nouveau_connector.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c index b0b0ad9a0c24..4cfc9c7c2ae0 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.c +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -600,8 +600,11 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) new_edid = drm_get_edid(connector, nv_encoder->i2c); } else { ret = nvif_outp_edid_get(&nv_encoder->outp, (u8 **)&new_edid); - if (ret < 0) + if (ret < 0) { + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put_autosuspend(dev->dev); return connector_status_disconnected; + } } nouveau_connector_set_edid(nv_connector, new_edid); From 3d743adf090cd4c9a2120c1e02b0482e88aa0d2d Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Thu, 17 Sep 2026 16:10:15 +0200 Subject: [PATCH 1946/2366] spi: fsl-qspi: Reprogram the clock rate when the operation frequency changes fsl_qspi_select_mem() returns early when the chip select has not changed, which happens before it reaches clk_set_rate(). Since the rate is now taken from the spi-mem operation rather than from the SPI device, the controller honours op->max_freq exactly once per chip select and ignores it for every operation after that. q->selected is only reset to -1 in fsl_qspi_default_setup(), i.e. at probe and on resume, so on the common single chip select board the very first operation latches a rate that all subsequent operations inherit, whatever frequency they asked for. This results in operations being issued with the wrong frequency. Cache the operation frequency the clock was programmed for next to the selected chip select, and redo the clock setup when either changes. Fixes: 2438db5253eb ("spi: fsl-qspi: Support per spi-mem operation frequency switches") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Frieder Schrempf Acked-by: Han Xu Link: https://patch.msgid.link/20260917-fsl-qspi-freq-op-fix-v1-1-5fbe6b02f738@kontron.de Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-qspi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/spi/spi-fsl-qspi.c b/drivers/spi/spi-fsl-qspi.c index 57358851029b..d2c2090442f8 100644 --- a/drivers/spi/spi-fsl-qspi.c +++ b/drivers/spi/spi-fsl-qspi.c @@ -289,6 +289,7 @@ struct fsl_qspi { struct pm_qos_request pm_qos_req; struct device *dev; int selected; + u32 selected_freq; u32 memmap_phy; }; @@ -551,7 +552,8 @@ static void fsl_qspi_select_mem(struct fsl_qspi *q, struct spi_device *spi, unsigned long rate = op->max_freq; int ret; - if (q->selected == spi_get_chipselect(spi, 0)) + if (q->selected == spi_get_chipselect(spi, 0) && + q->selected_freq == op->max_freq) return; if (needs_4x_clock(q)) @@ -571,6 +573,7 @@ static void fsl_qspi_select_mem(struct fsl_qspi *q, struct spi_device *spi, } q->selected = spi_get_chipselect(spi, 0); + q->selected_freq = op->max_freq; fsl_qspi_invalidate(q); } From 97077ac87afe9e91ec074ef0be64454e7ccbf344 Mon Sep 17 00:00:00 2001 From: Peiyang He Date: Wed, 16 Sep 2026 18:31:38 +0800 Subject: [PATCH 1947/2366] drm/nouveau: fix double-free in nvif_vmm_dtor On failure, nouveau_cli_init() calls nouveau_cli_fini() to tear the client down. Then, nouveau_drm_open() also enters into its cleanup path and calls nouveau_cli_fini() AGAIN. nouveau_cli_fini() calls nouveau_vmm_fini(): void nouveau_vmm_fini(struct nouveau_vmm *vmm) { nouveau_svmm_fini(&vmm->svmm); nvif_vmm_dtor(&vmm->vmm); vmm->cli = NULL; } Inside nvif_vmm_dtor(), vmm->page is freed unconditionally: void nvif_vmm_dtor(struct nvif_vmm *vmm) { kfree(vmm->page); nvif_object_dtor(&vmm->object); } vmm->page is never cleared after being freed, so the second call of nvif_vmm_dtor() will cause a double-free. Found by fuzzing the nouveau driver with a modified Syzkaller: BUG: KASAN: double-free in nvif_vmm_dtor+0x31/0x50 drivers/gpu/drm/nouveau/nvif/vmm.c:194 Free of addr ffff888010fcdc30 by task syz.0.173/2567 CPU: 1 UID: 0 PID: 2567 Comm: syz.0.173 Not tainted 7.2.0 #24 PREEMPT(lazy) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: __dump_stack lib/dump_stack.c:94 [inline] dump_stack_lvl+0x95/0xe0 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0xcb/0x5a0 mm/kasan/report.c:482 kasan_report_invalid_free+0xaa/0xd0 mm/kasan/report.c:557 check_slab_allocation+0xe4/0x110 mm/kasan/common.c:235 kasan_slab_pre_free include/linux/kasan.h:199 [inline] slab_free_hook mm/slub.c:2622 [inline] slab_free mm/slub.c:6377 [inline] kfree+0x192/0x590 mm/slub.c:6692 nvif_vmm_dtor+0x31/0x50 drivers/gpu/drm/nouveau/nvif/vmm.c:194 nouveau_vmm_fini+0x16/0x50 drivers/gpu/drm/nouveau/nouveau_vmm.c:127 nouveau_cli_fini+0x10e/0x210 drivers/gpu/drm/nouveau/nouveau_drm.c:225 nouveau_drm_open+0x24e/0x740 drivers/gpu/drm/nouveau/nouveau_drm.c:1255 drm_file_alloc+0x5f2/0xad0 drivers/gpu/drm/drm_file.c:176 drm_open_helper+0x1d7/0x4a0 drivers/gpu/drm/drm_file.c:335 drm_open+0x190/0x3d0 drivers/gpu/drm/drm_file.c:388 drm_stub_open+0x1f2/0x460 drivers/gpu/drm/drm_drv.c:1211 chrdev_open+0x21c/0x660 fs/char_dev.c:411 do_dentry_open+0x59d/0x12b0 fs/open.c:947 vfs_open+0x82/0x390 fs/open.c:1052 do_open fs/namei.c:4700 [inline] path_openat+0x2345/0x3420 fs/namei.c:4863 do_file_open+0x207/0x460 fs/namei.c:4892 do_sys_openat2+0xd1/0x1d0 fs/open.c:1368 do_sys_open fs/open.c:1374 [inline] __do_sys_openat fs/open.c:1390 [inline] __se_sys_openat fs/open.c:1385 [inline] __x64_sys_openat+0x144/0x200 fs/open.c:1385 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x115/0x690 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7fc6d687594d Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b0 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007fc6d5295008 EFLAGS: 00000246 ORIG_RAX: 0000000000000101 RAX: ffffffffffffffda RBX: 00007fc6d6b06180 RCX: 00007fc6d687594d RDX: 0000000000022501 RSI: 0000200000000000 RDI: ffffffffffffff9c RBP: 00007fc6d691c303 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007fc6d6b06218 R14: 00007fc6d6b06180 R15: 00007ffd9451d760 Allocated by task 2567 on cpu 1 at 163.593900s: kasan_save_stack+0x24/0x50 mm/kasan/common.c:57 kasan_save_track+0x17/0x60 mm/kasan/common.c:78 poison_kmalloc_redzone mm/kasan/common.c:398 [inline] __kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415 kasan_kmalloc include/linux/kasan.h:263 [inline] __do_kmalloc_node mm/slub.c:5334 [inline] __kmalloc_noprof+0x304/0x7c0 mm/slub.c:5359 _kmalloc_noprof include/linux/slab.h:992 [inline] nvif_vmm_ctor+0x3c0/0x7e0 drivers/gpu/drm/nouveau/nvif/vmm.c:237 nouveau_vmm_init+0x40/0x90 drivers/gpu/drm/nouveau/nouveau_vmm.c:134 nouveau_cli_init+0x7b9/0xe10 drivers/gpu/drm/nouveau/nouveau_drm.c:293 nouveau_drm_open+0x236/0x740 drivers/gpu/drm/nouveau/nouveau_drm.c:1243 drm_file_alloc+0x5f2/0xad0 drivers/gpu/drm/drm_file.c:176 drm_open_helper+0x1d7/0x4a0 drivers/gpu/drm/drm_file.c:335 drm_open+0x190/0x3d0 drivers/gpu/drm/drm_file.c:388 drm_stub_open+0x1f2/0x460 drivers/gpu/drm/drm_drv.c:1211 chrdev_open+0x21c/0x660 fs/char_dev.c:411 do_dentry_open+0x59d/0x12b0 fs/open.c:947 vfs_open+0x82/0x390 fs/open.c:1052 do_open fs/namei.c:4700 [inline] path_openat+0x2345/0x3420 fs/namei.c:4863 do_file_open+0x207/0x460 fs/namei.c:4892 do_sys_openat2+0xd1/0x1d0 fs/open.c:1368 do_sys_open fs/open.c:1374 [inline] __do_sys_openat fs/open.c:1390 [inline] __se_sys_openat fs/open.c:1385 [inline] __x64_sys_openat+0x144/0x200 fs/open.c:1385 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x115/0x690 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 2567 on cpu 1 at 163.601355s: kasan_save_stack+0x24/0x50 mm/kasan/common.c:57 kasan_save_track+0x17/0x60 mm/kasan/common.c:78 kasan_save_free_info+0x3b/0x60 mm/kasan/generic.c:584 poison_slab_object mm/kasan/common.c:253 [inline] __kasan_slab_free+0x61/0x80 mm/kasan/common.c:285 kasan_slab_free include/linux/kasan.h:235 [inline] slab_free_hook mm/slub.c:2677 [inline] slab_free mm/slub.c:6377 [inline] kfree+0x383/0x590 mm/slub.c:6692 nvif_vmm_dtor+0x31/0x50 drivers/gpu/drm/nouveau/nvif/vmm.c:194 nouveau_vmm_fini+0x16/0x50 drivers/gpu/drm/nouveau/nouveau_vmm.c:127 nouveau_cli_fini+0x10e/0x210 drivers/gpu/drm/nouveau/nouveau_drm.c:225 nouveau_cli_init+0x593/0xe10 drivers/gpu/drm/nouveau/nouveau_drm.c:324 nouveau_drm_open+0x236/0x740 drivers/gpu/drm/nouveau/nouveau_drm.c:1243 drm_file_alloc+0x5f2/0xad0 drivers/gpu/drm/drm_file.c:176 drm_open_helper+0x1d7/0x4a0 drivers/gpu/drm/drm_file.c:335 drm_open+0x190/0x3d0 drivers/gpu/drm/drm_file.c:388 drm_stub_open+0x1f2/0x460 drivers/gpu/drm/drm_drv.c:1211 chrdev_open+0x21c/0x660 fs/char_dev.c:411 do_dentry_open+0x59d/0x12b0 fs/open.c:947 vfs_open+0x82/0x390 fs/open.c:1052 do_open fs/namei.c:4700 [inline] path_openat+0x2345/0x3420 fs/namei.c:4863 do_file_open+0x207/0x460 fs/namei.c:4892 do_sys_openat2+0xd1/0x1d0 fs/open.c:1368 do_sys_open fs/open.c:1374 [inline] __do_sys_openat fs/open.c:1390 [inline] __se_sys_openat fs/open.c:1385 [inline] __x64_sys_openat+0x144/0x200 fs/open.c:1385 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x115/0x690 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff888010fcdc30 which belongs to the cache kmalloc-16 of size 16 The buggy address is located 0 bytes inside of 16-byte region [ffff888010fcdc30, ffff888010fcdc40) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10fcd flags: 0x100000000000000(node=0|zone=1) page_type: f5(slab) raw: 0100000000000000 ffff88800d441640 dead000000000100 dead000000000122 raw: 0000000000000000 0000000000550055 00000000f5000000 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff888010fcdb00: fc fc 00 04 fc fc fc fc fa fb fc fc fc fc fa fb ffff888010fcdb80: fc fc fc fc fa fb fc fc fc fc 00 07 fc fc fc fc >ffff888010fcdc00: fa fb fc fc fc fc fa fb fc fc fc fc fa fb fc fc ^ ffff888010fcdc80: fc fc fa fb fc fc fc fc 00 04 fc fc fc fc fa fb ffff888010fcdd00: fc fc fc fc 00 00 fc fc fc fc fa fb fc fc fc fc Fix by removing the redundant teardown in nouveau_drm_open(), since nouveau_cli_init() already does the cleanup work. Also clear vmm->page after its freeing. Cc: stable@vger.kernel.org Fixes: 20d8a88e557a ("drm/nouveau: tidy up the client init/fini interfaces") Signed-off-by: Peiyang He Assisted-by: LLM Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/03BA723D9E5FF725+20260916103138.2651605-1-peiyang_he@smail.nju.edu.cn --- drivers/gpu/drm/nouveau/nouveau_drm.c | 4 +--- drivers/gpu/drm/nouveau/nvif/vmm.c | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 4d1ad718e09b..b4dfaf70762a 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -1250,10 +1250,8 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv) mutex_unlock(&drm->clients_lock); done: - if (ret && cli) { - nouveau_cli_fini(cli); + if (ret && cli) kfree(cli); - } pm_runtime_mark_last_busy(dev->dev); pm_runtime_put_autosuspend(dev->dev); diff --git a/drivers/gpu/drm/nouveau/nvif/vmm.c b/drivers/gpu/drm/nouveau/nvif/vmm.c index 65c3e883b119..579af70766f2 100644 --- a/drivers/gpu/drm/nouveau/nvif/vmm.c +++ b/drivers/gpu/drm/nouveau/nvif/vmm.c @@ -192,6 +192,7 @@ void nvif_vmm_dtor(struct nvif_vmm *vmm) { kfree(vmm->page); + vmm->page = NULL; nvif_object_dtor(&vmm->object); } From 64ca4cdd1031206424e6455f0ae4fb0560d8f46a Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Mon, 17 Aug 2026 14:50:40 +0800 Subject: [PATCH 1948/2366] drm/nouveau/dmem: pin VRAM for the whole registered range Commit c32287471077 ("gpu/drm/nouveau: enable THP support for GPU memory migration") grew the device-private region that nouveau_dmem_chunk_alloc() registers from DMEM_CHUNK_SIZE to DMEM_CHUNK_SIZE * NR_CHUNKS, but left the VRAM buffer object backing that region at DMEM_CHUNK_SIZE. nouveau_dmem_page_addr() returns chunk->bo->offset plus the page's offset within the registered region, so every page past the first chunk resolves to VRAM outside the buffer object. Size the buffer object to the region it backs. Fixes: c32287471077 ("gpu/drm/nouveau: enable THP support for GPU memory migration") Reported-by: Yuhao Jiang Assisted-by: LLM Cc: stable@vger.kernel.org Signed-off-by: Junrui Luo Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260817-nouveau-fixes-v1-1-f518d0c735f3@outlook.com --- drivers/gpu/drm/nouveau/nouveau_dmem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_dmem.c b/drivers/gpu/drm/nouveau/nouveau_dmem.c index ad4570c50be7..e74d7bb975a8 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dmem.c +++ b/drivers/gpu/drm/nouveau/nouveau_dmem.c @@ -339,8 +339,8 @@ nouveau_dmem_chunk_alloc(struct nouveau_drm *drm, struct page **ppage, chunk->pagemap.ops = &nouveau_dmem_pagemap_ops; chunk->pagemap.owner = drm->dev; - ret = nouveau_bo_new_pin(&drm->client, NOUVEAU_GEM_DOMAIN_VRAM, DMEM_CHUNK_SIZE, - &chunk->bo); + ret = nouveau_bo_new_pin(&drm->client, NOUVEAU_GEM_DOMAIN_VRAM, + DMEM_CHUNK_SIZE * NR_CHUNKS, &chunk->bo); if (ret) goto out_release; From cb4c7603678ccef4c52b38159f2aaad867586dbc Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 17 Sep 2026 14:54:25 -0400 Subject: [PATCH 1949/2366] drm/nouveau/gsp/r570: Add support for INTERNAL_GCX_ENTRY_PREREQUISITE OpenRM's runtime PM handling looks a bit different then nouveau's, one part in particular that differs from us: OpenRM actually consults GSP to ask whether the GPU should be allowed to enter Gc6 and/or GcOff before runtime suspending the GPU. In the event the card isn't ready, runtime suspend is simply delayed for a few seconds before retrying. Implement the command used for querying GSP about this, NV2080_CTRL_CMD_INTERNAL_GCX_ENTRY_PREREQUISITE, and check to ensure that the GPU is ready for runtime suspend in nouveau_pmops_runtime_suspend() using this query. If the GPU can't be runtime suspended, update the last busy counter of the device and then return -EBUSY from nouveau_pmops_runtime_suspend() - essentially delaying the runtime suspend process by whatever autosuspend_delay_ms is set to. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260917185916.1089621-2-lyude@redhat.com --- drivers/gpu/drm/nouveau/include/nvif/cl0080.h | 10 ++++++ drivers/gpu/drm/nouveau/include/nvif/device.h | 1 + .../gpu/drm/nouveau/include/nvkm/subdev/gsp.h | 2 ++ drivers/gpu/drm/nouveau/nouveau_drm.c | 13 +++++++ drivers/gpu/drm/nouveau/nvif/device.c | 13 +++++++ .../gpu/drm/nouveau/nvkm/engine/device/user.c | 35 +++++++++++++++++++ .../gpu/drm/nouveau/nvkm/subdev/gsp/base.c | 10 ++++++ .../gpu/drm/nouveau/nvkm/subdev/gsp/priv.h | 1 + .../drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c | 28 +++++++++++++++ .../nvkm/subdev/gsp/rm/r570/nvrm/gsp.h | 7 ++++ .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h | 2 ++ 11 files changed, 122 insertions(+) diff --git a/drivers/gpu/drm/nouveau/include/nvif/cl0080.h b/drivers/gpu/drm/nouveau/include/nvif/cl0080.h index ea8267e0d8da..9e639df1da46 100644 --- a/drivers/gpu/drm/nouveau/include/nvif/cl0080.h +++ b/drivers/gpu/drm/nouveau/include/nvif/cl0080.h @@ -4,6 +4,7 @@ #define NV_DEVICE_V0_INFO 0x00 #define NV_DEVICE_V0_TIME 0x01 +#define NV_DEVICE_V0_GCX_READY 0x02 struct nv_device_info_v0 { __u8 version; @@ -55,6 +56,15 @@ struct nv_device_time_v0 { __u64 time; }; +#define NV_DEVICE_GC6_READY BIT(0) +#define NV_DEVICE_GCOFF_READY BIT(1) + +struct nv_device_gcx_ready_v0 { + __u8 version; + __u8 pad01[6]; + __u8 ready; +}; + #define NV_DEVICE_INFO_UNIT (0xffffffffULL << 32) #define NV_DEVICE_INFO(n) ((n) | (0x00000000ULL << 32)) #define NV_DEVICE_HOST(n) ((n) | (0x00000001ULL << 32)) diff --git a/drivers/gpu/drm/nouveau/include/nvif/device.h b/drivers/gpu/drm/nouveau/include/nvif/device.h index 7877a2a79da9..ce2fadcb05d9 100644 --- a/drivers/gpu/drm/nouveau/include/nvif/device.h +++ b/drivers/gpu/drm/nouveau/include/nvif/device.h @@ -22,4 +22,5 @@ int nvif_device_ctor(struct nvif_client *, const char *name, struct nvif_device void nvif_device_dtor(struct nvif_device *); int nvif_device_map(struct nvif_device *); u64 nvif_device_time(struct nvif_device *); +int nvif_device_gcx_ready(struct nvif_device *); #endif diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h index 64fed208e4cf..cd10c3705369 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h @@ -495,6 +495,8 @@ nvkm_gsp_event_dtor(struct nvkm_gsp_event *event) int nvkm_gsp_intr_stall(struct nvkm_gsp *, enum nvkm_subdev_type, int); int nvkm_gsp_intr_nonstall(struct nvkm_gsp *, enum nvkm_subdev_type, int); +int nvkm_gsp_gcx_ready(struct nvkm_gsp *gsp); + int gv100_gsp_new(struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); int tu102_gsp_new(struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); int tu116_gsp_new(struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index b4dfaf70762a..d0eb29583cb1 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -1148,6 +1148,7 @@ nouveau_pmops_runtime_suspend(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); struct nouveau_drm *drm = pci_get_drvdata(pdev); + struct nvif_device *nvif = &drm->client.device; int ret; if (!nouveau_pmops_runtime()) { @@ -1155,6 +1156,18 @@ nouveau_pmops_runtime_suspend(struct device *dev) return -EBUSY; } + // Check if the GPU itself is ready for runtime suspend, otherwise mark as busy and check + // again in a bit. + ret = nvif_device_gcx_ready(nvif); + if (ret < 0) { + NV_ERROR(drm, "Failed to query GCX readiness (returned %d)\n", ret); + return -EBUSY; + } else if (!(ret & NV_DEVICE_GCOFF_READY)) { + NV_DEBUG(drm, "GPU isn't ready for suspend yet, delaying...\n"); + pm_runtime_mark_last_busy(dev); + return -EBUSY; + } + nouveau_switcheroo_optimus_dsm(); ret = nouveau_do_suspend(drm, true); pci_save_state(pdev); diff --git a/drivers/gpu/drm/nouveau/nvif/device.c b/drivers/gpu/drm/nouveau/nvif/device.c index 24880931039f..1be9fbe6cb70 100644 --- a/drivers/gpu/drm/nouveau/nvif/device.c +++ b/drivers/gpu/drm/nouveau/nvif/device.c @@ -38,6 +38,19 @@ nvif_device_time(struct nvif_device *device) return device->user.func->time(&device->user); } +int +nvif_device_gcx_ready(struct nvif_device *device) +{ + struct nv_device_gcx_ready_v0 args = {}; + int ret; + + ret = nvif_object_mthd(&device->object, NV_DEVICE_V0_GCX_READY, &args, sizeof(args)); + if (ret) + return ret; + + return args.ready; +} + int nvif_device_map(struct nvif_device *device) { diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c index 23d11d8221cb..f78e6b9b4292 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -189,6 +190,38 @@ nvkm_udevice_time(struct nvkm_udevice *udev, void *data, u32 size) return ret; } +static int +nvkm_udevice_gcx_ready(struct nvkm_udevice *udev, void *data, u32 size) +{ + struct nvkm_object *object = &udev->object; + struct nvkm_device *device = udev->device; + struct nvkm_gsp *gsp = device->gsp; + union { + struct nv_device_gcx_ready_v0 v0; + } *args = data; + int ret = -ENOSYS; + + if (!gsp) { + args->v0.ready = NV_DEVICE_GC6_READY | NV_DEVICE_GCOFF_READY; + return 0; + } + + nvif_ioctl(object, "device gcx ready size %d\n", size); + ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false); + if (!ret) { + nvif_ioctl(object, "device gcx ready vers %d\n", args->v0.version); + + ret = nvkm_gsp_gcx_ready(gsp); + if (ret < 0) + return ret; + + args->v0.ready = ret; + ret = 0; + } + + return ret; +} + static int nvkm_udevice_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size) { @@ -199,6 +232,8 @@ nvkm_udevice_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size) return nvkm_udevice_info(udev, data, size); case NV_DEVICE_V0_TIME: return nvkm_udevice_time(udev, data, size); + case NV_DEVICE_V0_GCX_READY: + return nvkm_udevice_gcx_ready(udev, data, size); default: break; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c index 9ba1316831e7..e475d0e8fa7b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c @@ -20,6 +20,7 @@ * OTHER DEALINGS IN THE SOFTWARE. */ #include "priv.h" +#include int nvkm_gsp_intr_nonstall(struct nvkm_gsp *gsp, enum nvkm_subdev_type type, int inst) @@ -47,6 +48,15 @@ nvkm_gsp_intr_stall(struct nvkm_gsp *gsp, enum nvkm_subdev_type type, int inst) return -ENOENT; } +int +nvkm_gsp_gcx_ready(struct nvkm_gsp *gsp) +{ + if (!gsp->rm->api->gsp->gcx_ready) + return NV_DEVICE_GC6_READY | NV_DEVICE_GCOFF_READY; + + return gsp->rm->api->gsp->gcx_ready(gsp); +} + static int nvkm_gsp_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h index 71b7203bef50..b07797813b04 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h @@ -93,6 +93,7 @@ void r535_gsp_dtor(struct nvkm_gsp *); int r535_gsp_oneinit(struct nvkm_gsp *); int r535_gsp_init(struct nvkm_gsp *); int r535_gsp_fini(struct nvkm_gsp *, enum nvkm_suspend_state suspend); +int r535_gsp_gcx_ready(struct nvkm_gsp *gsp); int nvkm_gsp_new_(const struct nvkm_gsp_fwif *, struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c index b45781cd0dfd..89b801c1e609 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c @@ -10,6 +10,7 @@ #include "nvrm/gsp.h" #include "nvrm/rpcfn.h" #include "nvrm/msgfn.h" +#include "nvif/cl0080.h" #include #include @@ -215,6 +216,32 @@ r570_gsp_set_rmargs(struct nvkm_gsp *gsp, bool resume) args->bDmemStack = 1; } +int +r570_gsp_gcx_ready(struct nvkm_gsp *gsp) +{ + NV2080_CTRL_INTERNAL_GCX_ENTRY_PREREQUISITE_PARAMS *ctrl; + int ret = 0; + + ctrl = nvkm_gsp_rm_ctrl_rd(&gsp->internal.device.subdevice, + NV2080_CTRL_CMD_INTERNAL_GCX_ENTRY_PREREQUISITE, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + if (ctrl->bIsGC6Satisfied) + ret |= NV_DEVICE_GC6_READY; + if (ctrl->bIsGCOFFSatisfied) + ret |= NV_DEVICE_GCOFF_READY; + + nvkm_debug(&gsp->subdev, + "GCX ready status: GC6=%s GCOFF=%s\n", + str_yes_no(ctrl->bIsGC6Satisfied), str_yes_no(ctrl->bIsGCOFFSatisfied)); + + nvkm_gsp_rm_ctrl_done(&gsp->internal.device.subdevice, ctrl); + return ret; +} + + const struct nvkm_rm_api_gsp r570_gsp = { .set_rmargs = r570_gsp_set_rmargs, @@ -223,4 +250,5 @@ r570_gsp = { .xlat_mc_engine_idx = r570_gsp_xlat_mc_engine_idx, .drop_post_nocat_record = r570_gsp_drop_post_nocat_record, .sr_data_size = r570_gsp_sr_data_size, + .gcx_ready = r570_gsp_gcx_ready, }; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h index c458569af9d7..2814629fddd2 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h @@ -639,4 +639,11 @@ typedef struct GSP_FMC_BOOT_PARAMS } GSP_FMC_BOOT_PARAMS; #define GSP_FW_HEAP_PARAM_BASE_RM_SIZE_GH100 (14 << 20) // Hopper+ + +#define NV2080_CTRL_CMD_INTERNAL_GCX_ENTRY_PREREQUISITE (0x2080a7d7) + +typedef struct NV2080_CTRL_INTERNAL_GCX_ENTRY_PREREQUISITE_PARAMS { + NvBool bIsGC6Satisfied; + NvBool bIsGCOFFSatisfied; +} NV2080_CTRL_INTERNAL_GCX_ENTRY_PREREQUISITE_PARAMS; #endif diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h index e9ac47d86b69..86970129ad96 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h @@ -40,6 +40,7 @@ struct nvkm_rm_api { void (*drop_send_user_shared_data)(struct nvkm_gsp *); void (*drop_post_nocat_record)(struct nvkm_gsp *); u32 (*sr_data_size)(struct nvkm_gsp *); + int (*gcx_ready)(struct nvkm_gsp *gsp); } *gsp; const struct nvkm_rm_api_rpc { @@ -174,6 +175,7 @@ int r535_gr_chan_new(struct nvkm_gr *, struct nvkm_chan *, const struct nvkm_ocl int r535_gr_promote_ctx(struct r535_gr *, bool golden, struct nvkm_vmm *, struct nvkm_memory **pctxbuf_mem, struct nvkm_vma **pctxbuf_vma, struct nvkm_gsp_object *chan); +int r570_gsp_gcx_ready(struct nvkm_gsp *gsp); extern const struct nvkm_rm_api_engine r535_nvdec; extern const struct nvkm_rm_api_engine r535_nvenc; extern const struct nvkm_rm_api_engine r535_nvjpg; From 3217000f0b7e4f67e5b386d5b3491ec1b9b584b8 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 17 Sep 2026 14:54:26 -0400 Subject: [PATCH 1950/2366] drm/nouveau/gsp/r535: Add support for MEMSYS_GET_STATIC_CONFIG This is a GSP structure describing various characteristics of the memory management system that GSP provides. Start by fetching it during driver load, but don't do anything with the information we get from it just yet. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260917185916.1089621-3-lyude@redhat.com --- .../gpu/drm/nouveau/include/nvkm/subdev/gsp.h | 4 ++ .../drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c | 17 +++++++ .../nvkm/subdev/gsp/rm/r535/nvrm/gsp.h | 45 +++++++++++++++++++ .../drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c | 8 ++++ .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h | 1 + 5 files changed, 75 insertions(+) diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h index cd10c3705369..ed5c6e0e68d3 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h @@ -156,6 +156,10 @@ struct nvkm_gsp { struct sg_table fbsr; } sr; + struct { + bool use_raw_mode_comptagline_alloc; + } memsys; + struct { struct nvkm_gsp_mem mem; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index 94925f1590ea..63aa30f94747 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1782,6 +1782,23 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) return 0; } +int +r535_gsp_get_static_memsys_info(struct nvkm_gsp *gsp) +{ + NV2080_CTRL_INTERNAL_MEMSYS_GET_STATIC_CONFIG_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_rd(&gsp->internal.device.subdevice, + NV2080_CTRL_CMD_INTERNAL_MEMSYS_GET_STATIC_CONFIG, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + gsp->memsys.use_raw_mode_comptagline_alloc = ctrl->bUseRawModeComptaglineAllocation; + + nvkm_gsp_rm_ctrl_done(&gsp->internal.device.subdevice, ctrl); + return 0; +} + int r535_gsp_init(struct nvkm_gsp *gsp) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/gsp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/gsp.h index b6683a5bf870..7b10b7548c57 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/gsp.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/gsp.h @@ -782,6 +782,51 @@ typedef struct NV2080_CTRL_INTERNAL_INTR_GET_KERNEL_TABLE_PARAMS { #define GSP_FW_HEAP_PARAM_CLIENT_ALLOC_SIZE ((48 << 10) * 2048) // Support 2048 channels +typedef struct NV2080_CTRL_INTERNAL_MEMSYS_GET_STATIC_CONFIG_PARAMS { + /*! Determines if RM should use 1 to 1 Comptagline allocation policy */ + NvBool bOneToOneComptagLineAllocation; + + /*! Determines if RM should use 1 to 4 Comptagline allocation policy */ + NvBool bUseOneToFourComptagLineAllocation; + + /*! Determines if RM should use raw Comptagline allocation policy */ + NvBool bUseRawModeComptaglineAllocation; + + /*! Has COMPBIT_BACKING_SIZE been overridden to zero (i.e. disabled)? */ + NvBool bDisableCompbitBacking; + + /*! Determine if we need to disable post L2 compression */ + NvBool bDisablePostL2Compression; + + /*! Is ECC DRAM feature supported? */ + NvBool bEnabledEccFBPA; + + NvBool bL2PreFill; + + /*! L2 cache size */ + NV_DECLARE_ALIGNED(NvU64 l2CacheSize, 8); + + /*! Indicate whether fpba is present or not */ + NvBool bFbpaPresent; + + /*! Size covered by one comptag */ + NvU32 comprPageSize; + + /*! log32(comprPageSize) */ + NvU32 comprPageShift; + + /*! RAM type */ + NvU32 ramType; + + /*! LTC count */ + NvU32 ltcCount; + + /*! LTS per LTC count */ + NvU32 ltsPerLtcCount; +} NV2080_CTRL_INTERNAL_MEMSYS_GET_STATIC_CONFIG_PARAMS; + +#define NV2080_CTRL_CMD_INTERNAL_MEMSYS_GET_STATIC_CONFIG (0x20800a1c) /* finn: Evaluated from "(FINN_NV20_SUBDEVICE_0_INTERNAL_INTERFACE_ID << 8) | NV2080_CTRL_INTERNAL_MEMSYS_GET_STATIC_CONFIG_PARAMS_MESSAGE_ID" */ + typedef union rpc_message_rpc_union_field_v03_00 { NvU32 spare; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c index 89b801c1e609..ea38a94211f4 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c @@ -138,6 +138,14 @@ r570_gsp_get_static_info(struct nvkm_gsp *gsp) } } + ret = r535_gsp_get_static_memsys_info(gsp); + if (ret) { + nvkm_error(&gsp->subdev, "Retrieving static memsys info failed\n"); + return ret; + } + nvkm_debug(&gsp->subdev, "memsys: Use raw mode for comptag allocations? %s\n", + str_yes_no(gsp->memsys.use_raw_mode_comptagline_alloc)); + return 0; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h index 86970129ad96..17480d4e527a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h @@ -176,6 +176,7 @@ int r535_gr_promote_ctx(struct r535_gr *, bool golden, struct nvkm_vmm *, struct nvkm_memory **pctxbuf_mem, struct nvkm_vma **pctxbuf_vma, struct nvkm_gsp_object *chan); int r570_gsp_gcx_ready(struct nvkm_gsp *gsp); +int r535_gsp_get_static_memsys_info(struct nvkm_gsp *gsp); extern const struct nvkm_rm_api_engine r535_nvdec; extern const struct nvkm_rm_api_engine r535_nvenc; extern const struct nvkm_rm_api_engine r535_nvjpg; From c7ef611a43bb3ab7e7738349a860418336d507db Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 17 Sep 2026 14:54:27 -0400 Subject: [PATCH 1951/2366] drm/nouveau/gsp/r570: Add comp mode workaround from issue #3172217 One of the things that OpenRM does right before initiating fbsr is apply a special workaround (nvidia issue #3172217) which temporarily disables raw compression mode on the GPU. It is later re-enabled after resuming with fbsr completes. Since we don't currently save the compbit backing with fbsr, this shouldn't currently make any functional difference in the suspend/resume process. But it will be required for implementing support for saving and restoring compbit backings from the GPU. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260917185916.1089621-4-lyude@redhat.com --- .../nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 48 +++++++++++++++++++ .../nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h | 6 +++ 2 files changed, 54 insertions(+) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index af5aa5065c3d..f73d9b29e891 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -26,6 +26,35 @@ r570_fbsr_suspend_channels(struct nvkm_gsp *gsp, bool suspend) return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); } +static int +r570_memsys_enable_raw_comp_mode(struct nvkm_gsp *gsp, bool enable) +{ + NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS *ctrl; + int ret; + + ctrl = nvkm_gsp_rm_ctrl_get(&gsp->internal.device.subdevice, + NV2080_CTRL_CMD_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->bRawMode = enable; + + ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); + if (!ret) + nvkm_debug(&gsp->subdev, "memsys: Raw compression mode %s\n", + str_enabled_disabled(enable)); + + return ret; +} + +static bool +r570_need_raw_comp_war(struct nvkm_gsp *gsp, struct nvkm_device *device) +{ + return (device->card_type == GA100 || device->card_type == AD100) && + gsp->memsys.use_raw_mode_comptagline_alloc; +} + static void r570_fbsr_resume(struct nvkm_gsp *gsp) { @@ -33,6 +62,7 @@ r570_fbsr_resume(struct nvkm_gsp *gsp) struct nvkm_instmem *imem = device->imem; struct nvkm_instobj *iobj; struct nvkm_vmm *vmm; + int ret; /* Restore BAR2 page tables via BAR0 window, and re-enable BAR2. */ list_for_each_entry(iobj, &imem->boot, head) { @@ -54,6 +84,13 @@ r570_fbsr_resume(struct nvkm_gsp *gsp) vmm = nvkm_bar_bar1_vmm(device); vmm->func->flush(vmm, 0); + /* Re-enable raw mode if it was previously disabled */ + if (r570_need_raw_comp_war(gsp, device)) { + ret = r570_memsys_enable_raw_comp_mode(gsp, true); + if (ret) + nvkm_error(&gsp->subdev, "Failed to re-enable raw comp mode\n"); + } + /* Resume channel scheduling. */ r570_fbsr_suspend_channels(device->gsp, false); @@ -104,6 +141,17 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp) /* Stop channel scheduling. */ r570_fbsr_suspend_channels(gsp, true); + /* Temporarily disable raw mode to prevent FBSR restore operations from corrupting + * compressed surfaces. Required for ampere and ada. + * + * Nvidia bug #3172217 + */ + if (r570_need_raw_comp_war(gsp, device)) { + ret = r570_memsys_enable_raw_comp_mode(gsp, false); + if (ret) + return ret; + } + /* Save BAR2 allocations to system memory. */ list_for_each_entry(iobj, &imem->list, head) { if (iobj->preserve) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h index 8af432375f7a..9050a8274b27 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h @@ -16,4 +16,10 @@ typedef struct NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS { NV_DECLARE_ALIGNED(NvU64 sysmemAddrOfSuspendResumeData, 8); } NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS; +#define NV2080_CTRL_CMD_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE (0x20800a6f) /* finn: Evaluated from "(FINN_NV20_SUBDEVICE_0_INTERNAL_INTERFACE_ID << 8) | NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS_MESSAGE_ID" */ + +typedef struct NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS { + NvBool bRawMode; +} NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS; + #endif From 82f4394bbe223fda560153257e5cf21bdb12b6a3 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 17 Sep 2026 14:54:28 -0400 Subject: [PATCH 1952/2366] drm/nouveau/gsp/r570: Start saving comptag backing stores One of the portions of OpenRM's fbsr process that we never implemented is the saving and restoring of comptag backing stores. This isn't strictly necessary for fbsr to work (as long as we don't specify bEnteringGcOff = 1), but implementing it brings us much closer to matching OpenRM's fbsr process - which means we can rely on things being well tested on Nvidia's side. Now that we have the required driver workarounds in place and fetch the required information from GSP's memsys on driver load, let's implement support for this by fetching the required space for the compbit backing stores and adding it to the amount of memory that we allocate for fbsr. With this, we should be able to safely enable bEnteringGcOff in fbsr. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260917185916.1089621-5-lyude@redhat.com --- .../nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 27 ++++++++++++++++++- .../nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h | 23 ++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index f73d9b29e891..a93a39ef7c69 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -26,6 +26,23 @@ r570_fbsr_suspend_channels(struct nvkm_gsp *gsp, bool suspend) return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); } +static int +r570_fb_get_compbit_store_size(struct nvkm_gsp *gsp, u64 *size) +{ + NV0080_CTRL_FB_GET_COMPBIT_STORE_INFO_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_rd(&gsp->internal.device.object, + NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + *size = ctrl->Size; + + nvkm_gsp_rm_ctrl_done(&gsp->internal.device.object, ctrl); + return 0; +} + static int r570_memsys_enable_raw_comp_mode(struct nvkm_gsp *gsp, bool enable) { @@ -135,7 +152,7 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp) struct nvkm_device *device = subdev->device; struct nvkm_instmem *imem = device->imem; struct nvkm_instobj *iobj; - u64 size; + u64 size, compbit_store_size; int ret; /* Stop channel scheduling. */ @@ -152,6 +169,12 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp) return ret; } + ret = r570_fb_get_compbit_store_size(gsp, &compbit_store_size); + if (ret < 0) + return ret; + nvkm_debug(&gsp->subdev, "fbsr: Compbit backing store size: 0x%llx bytes\n", + compbit_store_size); + /* Save BAR2 allocations to system memory. */ list_for_each_entry(iobj, &imem->list, head) { if (iobj->preserve) { @@ -174,6 +197,8 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp) size = gsp->fb.heap.size; size += gsp->fb.rsvd_size; size += gsp->fb.bios.vga_workspace.size; + size += compbit_store_size; + nvkm_debug(subdev, "fbsr: size: 0x%llx bytes\n", size); ret = nvkm_gsp_sg(device, size, &gsp->sr.fbsr); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h index 9050a8274b27..cb3e448415b4 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h @@ -16,6 +16,29 @@ typedef struct NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS { NV_DECLARE_ALIGNED(NvU64 sysmemAddrOfSuspendResumeData, 8); } NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS; +#define NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO (0x801306) /* finn: Evaluated from "(FINN_NV01_DEVICE_0_FB_INTERFACE_ID << 8) | NV0080_CTRL_FB_GET_COMPBIT_STORE_INFO_PARAMS_MESSAGE_ID" */ + +typedef struct NV0080_CTRL_FB_GET_COMPBIT_STORE_INFO_PARAMS { + NV_DECLARE_ALIGNED(NvU64 Size, 8); + NV_DECLARE_ALIGNED(NvU64 Address, 8); + NvU32 AddressSpace; + NvU32 MaxCompbitLine; + NvU32 comptagsPerCacheLine; + NvU32 cacheLineSize; + NvU32 cacheLineSizePerSlice; + NvU32 cacheLineFetchAlignment; + NV_DECLARE_ALIGNED(NvU64 backingStoreBase, 8); + NvU32 gobsPerComptagPerSlice; + NvU32 backingStoreCbcBase; + NvU32 comptaglineAllocationPolicy; + NV_DECLARE_ALIGNED(NvU64 privRegionStartOffset, 8); + NvU32 cbcCoveragePerSlice; +} NV0080_CTRL_FB_GET_COMPBIT_STORE_INFO_PARAMS; + +#define NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO_ADDRESS_SPACE_UNKNOWN 0 // ADDR_UNKNOWN +#define NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO_ADDRESS_SPACE_SYSMEM 1 // ADDR_SYSMEM +#define NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO_ADDRESS_SPACE_FBMEM 2 // ADDR_FBMEM + #define NV2080_CTRL_CMD_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE (0x20800a6f) /* finn: Evaluated from "(FINN_NV20_SUBDEVICE_0_INTERNAL_INTERFACE_ID << 8) | NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS_MESSAGE_ID" */ typedef struct NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS { From adb87c20081e5ed5b6eef1270fc08b28bd77e865 Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Thu, 17 Sep 2026 14:54:29 -0400 Subject: [PATCH 1953/2366] drm/nouveau/gsp/r570: Enable Gcoff in fbsr again Now that we're properly saving the compbit backing stores on fbsr init, we can start setting bEnteringGcOff = 1 again without things breaking, which brings us closer to following the exact same code-paths OpenRM does for fbsr. Signed-off-by: Lyude Paul Reviewed-by: Dave Airlie Link: https://patch.msgid.link/20260917185916.1089621-6-lyude@redhat.com --- drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index a93a39ef7c69..469e7eed1d6f 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -135,7 +135,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) ctrl->hClient = gsp->internal.client.object.handle; ctrl->hSysMem = memlist.handle; ctrl->sysmemAddrOfSuspendResumeData = gsp->sr.meta.addr; - ctrl->bEnteringGcoffState = 0; + ctrl->bEnteringGcoffState = 1; ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); if (ret) From 3359a372efb6d585c97019ee1b7f1874442bcebe Mon Sep 17 00:00:00 2001 From: Peiyang He Date: Mon, 7 Sep 2026 13:22:04 +0800 Subject: [PATCH 1954/2366] drm/nouveau/uvmm: fix UAF in nouveau_uvmm_sm when BO is in TTM_PL_SYSTEM nouveau_uvmm_sm() calls op_map(), which passes bo->resource through nouveau_mem() to nouveau_uvma_map(). nouveau_uvmm_vmm_map() then reads mem->mem.type. But this is only valid when bo->resource is backed by struct nouveau_mem, as is the case for VRAM and TT resources. If the BO is left in TTM_PL_SYSTEM, bo->resource is only a struct ttm_resource. Treating it as struct nouveau_mem makes the mem->mem.type read past the end of the resource, causing a KASAN: slab-use-after-free Read in nouveau_uvmm_sm report: BUG: KASAN: slab-use-after-free in nouveau_uvmm_vmm_map drivers/gpu/drm/nouveau/nouveau_uvmm.c:152 [inline] BUG: KASAN: slab-use-after-free in nouveau_uvma_map drivers/gpu/drm/nouveau/nouveau_uvmm.c:199 [inline] BUG: KASAN: slab-use-after-free in op_map drivers/gpu/drm/nouveau/nouveau_uvmm.c:849 [inline] BUG: KASAN: slab-use-after-free in nouveau_uvmm_sm.constprop.0+0x6ab/0x900 drivers/gpu/drm/nouveau/nouveau_uvmm.c:903 Read of size 1 at addr ffff888127d3e3a0 by task kworker/0:1/11 CPU: 0 UID: 0 PID: 11 Comm: kworker/0:1 Not tainted 7.2.0 #5 PREEMPT(lazy) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: nouveau_sched_wq_2224 drm_sched_run_job_work Call Trace: __dump_stack lib/dump_stack.c:94 [inline] dump_stack_lvl+0x95/0xe0 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0xcb/0x5a0 mm/kasan/report.c:482 kasan_report+0xca/0x100 mm/kasan/report.c:595 nouveau_uvmm_vmm_map drivers/gpu/drm/nouveau/nouveau_uvmm.c:152 [inline] nouveau_uvma_map drivers/gpu/drm/nouveau/nouveau_uvmm.c:199 [inline] op_map drivers/gpu/drm/nouveau/nouveau_uvmm.c:849 [inline] nouveau_uvmm_sm.constprop.0+0x6ab/0x900 drivers/gpu/drm/nouveau/nouveau_uvmm.c:903 nouveau_uvmm_sm_unmap drivers/gpu/drm/nouveau/nouveau_uvmm.c:932 [inline] nouveau_uvmm_bind_job_run+0xd6/0x250 drivers/gpu/drm/nouveau/nouveau_uvmm.c:1532 nouveau_job_run drivers/gpu/drm/nouveau/nouveau_sched.c:350 [inline] nouveau_sched_run_job+0x62/0xd0 drivers/gpu/drm/nouveau/nouveau_sched.c:364 drm_sched_run_job_work+0x356/0xa10 drivers/gpu/drm/scheduler/sched_main.c:1061 process_one_work+0x8a5/0x1900 kernel/workqueue.c:3322 process_scheduled_works kernel/workqueue.c:3405 [inline] worker_thread+0x5dd/0xd80 kernel/workqueue.c:3486 kthread+0x31d/0x420 kernel/kthread.c:436 ret_from_fork+0x662/0x940 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Allocated by task 2224 on cpu 0 at 66.550027s: kasan_save_stack+0x24/0x50 mm/kasan/common.c:57 kasan_save_track+0x17/0x60 mm/kasan/common.c:78 poison_kmalloc_redzone mm/kasan/common.c:398 [inline] __kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415 kasan_kmalloc include/linux/kasan.h:263 [inline] __do_kmalloc_node mm/slub.c:5334 [inline] __kmalloc_noprof+0x304/0x7c0 mm/slub.c:5359 _kmalloc_noprof include/linux/slab.h:992 [inline] dma_resv_list_alloc+0x27/0x90 drivers/dma-buf/dma-resv.c:106 dma_resv_reserve_fences+0x60e/0xa30 drivers/dma-buf/dma-resv.c:205 ttm_bo_alloc_resource+0x12c/0xbd0 drivers/gpu/drm/ttm/ttm_bo.c:721 ttm_bo_validate+0x1bc/0x4a0 drivers/gpu/drm/ttm/ttm_bo.c:856 ttm_bo_init_reserved+0x2c3/0x570 drivers/gpu/drm/ttm/ttm_bo.c:970 nouveau_bo_init+0x159/0x2c0 drivers/gpu/drm/nouveau/nouveau_bo.c:359 nouveau_gem_new+0x234/0x5f0 drivers/gpu/drm/nouveau/nouveau_gem.c:272 nouveau_gem_ioctl_new+0x1eb/0x420 drivers/gpu/drm/nouveau/nouveau_gem.c:352 drm_ioctl_kernel+0x192/0x350 drivers/gpu/drm/drm_ioctl.c:817 drm_ioctl+0x4f8/0xb40 drivers/gpu/drm/drm_ioctl.c:914 nouveau_drm_ioctl+0xea/0x2c0 drivers/gpu/drm/nouveau/nouveau_drm.c:1338 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x180/0x1d0 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x115/0x690 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 2223 on cpu 0 at 66.554063s: kasan_save_stack+0x24/0x50 mm/kasan/common.c:57 kasan_save_track+0x17/0x60 mm/kasan/common.c:78 kasan_save_free_info+0x3b/0x60 mm/kasan/generic.c:584 poison_slab_object mm/kasan/common.c:253 [inline] __kasan_slab_free+0x61/0x80 mm/kasan/common.c:285 kasan_slab_free include/linux/kasan.h:235 [inline] slab_free_hook mm/slub.c:2677 [inline] __rcu_free_sheaf_prepare+0xb6/0x2e0 mm/slub.c:2928 rcu_free_sheaf+0x1b/0x120 mm/slub.c:5978 rcu_do_batch kernel/rcu/tree.c:2645 [inline] rcu_core+0x521/0x1490 kernel/rcu/tree.c:2897 handle_softirqs+0x1b1/0x8a0 kernel/softirq.c:622 __do_softirq kernel/softirq.c:656 [inline] invoke_softirq kernel/softirq.c:496 [inline] __irq_exit_rcu+0x137/0x1c0 kernel/softirq.c:735 irq_exit_rcu+0x9/0x20 kernel/softirq.c:752 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline] sysvec_apic_timer_interrupt+0x70/0x80 arch/x86/kernel/apic/apic.c:1062 asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674 The buggy address belongs to the object at ffff888127d3e380 which belongs to the cache kmalloc-96 of size 96 The buggy address is located 32 bytes inside of freed 96-byte region [ffff888127d3e380, ffff888127d3e3e0) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x127d3e flags: 0x200000000000000(node=0|zone=2) page_type: f5(slab) raw: 0200000000000000 ffff888100041280 dead000000000122 0000000000000000 raw: 0000000000000000 0000000000200020 00000000f5000000 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff888127d3e280: 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc ffff888127d3e300: 00 00 00 00 00 00 00 00 00 00 00 fc fc fc fc fc >ffff888127d3e380: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc ^ ffff888127d3e400: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc ffff888127d3e480: 00 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc Fix by resetting the placement to the BO's valid domains before calling nouveau_bo_validate(), matching the handling in nouveau_uvmm_bo_validate(), so map jobs do not run for SYSTEM resources; Reject BO that cannot reside in VRAM or GART; Also skip op_map() when the GPUVA has been invalidated, matching the handling in the unmap and remap paths. Found when fuzzing the nouveau driver with a modified Syzkaller. Fixes: b88baab82871 ("drm/nouveau: implement new VM_BIND uAPI") Cc: stable@vger.kernel.org Signed-off-by: Peiyang He Assisted-by: Codex:gpt-5.5 Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/0D77BEC410CE0129+20260907052204.1431488-1-peiyang_he@smail.nju.edu.cn --- drivers/gpu/drm/nouveau/nouveau_uvmm.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c index fc125fd44a9b..2026fe6b48c6 100644 --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c @@ -846,6 +846,9 @@ op_map(struct nouveau_uvma *uvma) { struct nouveau_bo *nvbo = nouveau_gem_object(uvma->va.gem.obj); + if (drm_gpuva_invalidated(&uvma->va)) + return; + nouveau_uvma_map(uvma, nouveau_mem(nvbo->bo.resource)); } @@ -1232,6 +1235,7 @@ bind_lock_validate(struct nouveau_job *job, struct drm_exec *exec, drm_gpuva_for_each_op(va_op, op->ops) { struct drm_gem_object *obj = op_gem_obj(va_op); + struct nouveau_bo *nvbo; if (unlikely(!obj)) continue; @@ -1246,8 +1250,13 @@ bind_lock_validate(struct nouveau_job *job, struct drm_exec *exec, if (va_op->op == DRM_GPUVA_OP_UNMAP) continue; - ret = nouveau_bo_validate(nouveau_gem_object(obj), - true, false); + nvbo = nouveau_gem_object(obj); + if (!(nvbo->valid_domains & + (NOUVEAU_GEM_DOMAIN_VRAM | NOUVEAU_GEM_DOMAIN_GART))) + return -EINVAL; + + nouveau_bo_placement_set(nvbo, nvbo->valid_domains, 0); + ret = nouveau_bo_validate(nvbo, true, false); if (ret) return ret; } From c0078f4d8c8fcd8edfe8c53ffe77d48565c1957e Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Tue, 15 Sep 2026 20:14:12 +0000 Subject: [PATCH 1955/2366] mailmap: add entry for Wei Wang My Meta email address is no longer active. Map it to my current address so that git and get_maintainer.pl stop pointing at a dead address for my contributions. Signed-off-by: Wei Wang Link: https://patch.msgid.link/20260915201412.2201757-1-weiwan@google.com Signed-off-by: Jakub Kicinski --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 1f5540bc33f1..526217c88495 100644 --- a/.mailmap +++ b/.mailmap @@ -975,6 +975,7 @@ Vladimir Davydov Vlastimil Babka WangYuli WangYuli +Wei Wang Weiwen Hu WeiXiong Liao Wen Gong From c9dc7d730319ad64b51570c5387f1fee7b07b510 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Thu, 13 Aug 2026 17:50:03 +0800 Subject: [PATCH 1956/2366] PCI: imx6: Move clock enable after core reset assertion Commit 610fa91d9863 ("PCI: imx6: Assert PERST# before enabling regulators") inadvertently moved clock enablement before core reset assertion, breaking PCI device initialization on i.MX6Q Apalis platforms with ASM1061/ASM1062 SATA controllers connected: imx6q-pcie 1ffc000.pcie: host bridge /soc/pcie@1ffc000 ranges: imx6q-pcie 1ffc000.pcie: IO 0x0001f80000..0x0001f8ffff -> 0x0000000000 imx6q-pcie 1ffc000.pcie: MEM 0x0001000000..0x0001efffff -> 0x0001000000 imx6q-pcie 1ffc000.pcie: config reg[1] 0x01f00000 == cpu 0x01f00000 imx6q-pcie 1ffc000.pcie: iATU: unroll F, 4 ob, 4 ib, align 64K, limit 4G imx6q-pcie 1ffc000.pcie: Link: Only Gen1 is enabled imx6q-pcie 1ffc000.pcie: Link failed to come up. LTSSM: POLL_CONFIG imx6q-pcie 1ffc000.pcie: probe with driver imx6q-pcie failed with error -110 NOTE: It is not 100% clear if the issue is specific to the ASM1061/ASM1062 device or on the specific power-up sequence (reset vs cold-power-on). To fix this regression, restore the original sequence where clocks are enabled after asserting core reset and configuring the controller type. Fixes: 610fa91d9863 ("PCI: imx6: Assert PERST# before enabling regulators") Reported-by: Leonardo Costa Closes: https://lore.kernel.org/all/bl7i3obu2clzsgeoct2a4mtfhv6typcjdqmgneropf3hpgwve6@n2m5uhlduw57/T/#u Reported-by: Franz Schnyder Closes: https://lore.kernel.org/all/t65y5d54axtksbfs7r4olcefqhwm6m4dz3njgnrnf7fcotj74i@o7avoznlafbj/ Signed-off-by: Richard Zhu Signed-off-by: Manivannan Sadhasivam [bhelgaas: move to pci/for-linus for v7.3] Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org # 7.2+ Link: https://patch.msgid.link/20260813095003.356062-1-hongxing.zhu@oss.nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 39790e66b98d..f7a2eb257c16 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1394,12 +1394,6 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) } } - ret = imx_pcie_clk_enable(imx_pcie); - if (ret) { - dev_err(dev, "unable to enable pcie clocks: %d\n", ret); - goto err_pwrctrl_power_off; - } - if (pp->bridge && imx_check_flag(imx_pcie, IMX_PCIE_FLAG_HAS_LUT)) { pp->bridge->enable_device = imx_pcie_enable_device; pp->bridge->disable_device = imx_pcie_disable_device; @@ -1415,6 +1409,12 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) imx_pcie_configure_type(imx_pcie); + ret = imx_pcie_clk_enable(imx_pcie); + if (ret) { + dev_err(dev, "unable to enable pcie clocks: %d\n", ret); + goto err_pwrctrl_power_off; + } + if (imx_pcie->phy) { ret = phy_init(imx_pcie->phy); if (ret) { From f7eae6d8d768fabd6b59779ca7da79e02c74e113 Mon Sep 17 00:00:00 2001 From: "Jonghyuk Kim(MalHyuk)" Date: Wed, 2 Sep 2026 10:27:16 +0900 Subject: [PATCH 1957/2366] drm/nouveau: RCU-free the scheduler-containing nouveau_sched struct nouveau_sched embeds a struct drm_gpu_scheduler (base). nouveau_sched_destroy() calls nouveau_sched_fini() (which does drm_sched_fini(&sched->base)) and then frees the object with plain kfree(sched). drm_sched_fence_get_timeline_name() returns fence->sched->name, and the scheduler fence keeps a .release callback so it is not ops-detached on signalling. A finished fence exported to userspace via drm_syncobj / sync_file therefore keeps pointing at &sched->base after nouveau_sched_destroy(), and a later get_timeline_name() -- reachable unprivileged through SYNC_IOC_FILE_INFO -- dereferences freed memory (KASAN slab-use-after-free read). Per the dma-fence lifetime contract the exporter must keep the data backing a signalled fence alive for an RCU grace period. Free the scheduler-containing object with kfree_rcu() instead of kfree(). Fixes: 5f03a507b29e ("drm/nouveau: implement 1:1 scheduler - entity relationship") Cc: stable@vger.kernel.org Signed-off-by: Jonghyuk Kim(MalHyuk) Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260902012717.880724-1-malhyuk97@gmail.com --- drivers/gpu/drm/nouveau/nouveau_sched.c | 2 +- drivers/gpu/drm/nouveau/nouveau_sched.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.c b/drivers/gpu/drm/nouveau/nouveau_sched.c index 8b9f935afe09..b3f02c490ecb 100644 --- a/drivers/gpu/drm/nouveau/nouveau_sched.c +++ b/drivers/gpu/drm/nouveau/nouveau_sched.c @@ -517,7 +517,7 @@ nouveau_sched_destroy(struct nouveau_sched **psched) struct nouveau_sched *sched = *psched; nouveau_sched_fini(sched); - kfree(sched); + kfree_rcu(sched, rcu); *psched = NULL; } diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.h b/drivers/gpu/drm/nouveau/nouveau_sched.h index 20cd1da8db73..51ce8dcf6285 100644 --- a/drivers/gpu/drm/nouveau/nouveau_sched.h +++ b/drivers/gpu/drm/nouveau/nouveau_sched.h @@ -98,6 +98,7 @@ void nouveau_job_free(struct nouveau_job *job); struct nouveau_sched { struct drm_gpu_scheduler base; + struct rcu_head rcu; struct drm_sched_entity entity; struct workqueue_struct *wq; struct mutex mutex; From 50e80e2bb5e2be8515205b9c496b9640ddefa434 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Tue, 15 Sep 2026 01:02:07 +0800 Subject: [PATCH 1958/2366] bpf: Skip unsettled links in link iterator bpf_link_prime() inserts a link into link_idr before anon_inode_getfile() succeeds and before bpf_link_settle() publishes the ID in link->id. bpf_link_by_id() treats such an ID-zero link as unsettled, but the link iterator takes a reference without this check. If anon_inode_getfile() then fails, the creator removes the ID and frees its still-private link directly. The iterator is left with a dangling reference and its next bpf_link_put() accesses freed memory. Treat ID-zero entries as transient in bpf_link_get_curr_or_next(), just as bpf_link_by_id() does. BUG: KASAN: slab-use-after-free in bpf_link_put Write of size 8 by task exp/384 Call Trace: bpf_link_put kernel/bpf/syscall.c:3372 bpf_link_seq_next kernel/bpf/link_iter.c:33 bpf_seq_read kernel/bpf/bpf_iter.c:158 vfs_read fs/read_write.c:572 ksys_read fs/read_write.c:716 do_syscall_64 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe arch/x86/entry/entry_64.S:121 Kernel panic - not syncing: KASAN: panic_on_warn set ... Fixes: 9f8836127308 ("bpf: Add bpf_link iterator") Reported-by: Xiang Mei Signed-off-by: Weiming Shi Signed-off-by: Andrii Nakryiko Link: https://lore.kernel.org/bpf/20260914170206.170723-2-bestswngs@gmail.com --- kernel/bpf/syscall.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 853b47f81384..244a939b9d2d 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -6042,7 +6042,10 @@ struct bpf_link *bpf_link_get_curr_or_next(u32 *id) again: link = idr_get_next(&link_idr, id); if (link) { - link = bpf_link_inc_not_zero(link); + if (link->id) + link = bpf_link_inc_not_zero(link); + else + link = ERR_PTR(-EAGAIN); if (IS_ERR(link)) { (*id)++; goto again; From fefd9480ec361969f1a836df46326a1801062c26 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sat, 8 Aug 2026 21:41:37 +0800 Subject: [PATCH 1959/2366] drm/nouveau: fix autosuspend cleanup during teardown nouveau_drm_device_init() calls pm_runtime_use_autosuspend(), but nouveau_drm_device_fini() does not call the matching pm_runtime_dont_use_autosuspend(). If the autosuspend delay is set to a negative value while autosuspend is enabled, the runtime PM core increments usage_count to prevent runtime suspend. Without calling pm_runtime_dont_use_autosuspend() during teardown, this reference is not dropped and usage_count remains unbalanced. The documentation for pm_runtime_use_autosuspend() also notes that it is important to undo it with pm_runtime_dont_use_autosuspend() at driver exit time, unless runtime PM was initially enabled with devm_pm_runtime_enable(). Add the missing pm_runtime_dont_use_autosuspend() call to the common device teardown path. This issue was found by manual code inspection. Fixes: 5addcf0a5f0f ("nouveau: add runtime PM support (v0.9)") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260808134137.2864847-1-lgs201920130244@gmail.com --- drivers/gpu/drm/nouveau/nouveau_drm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index d0eb29583cb1..2c7077a49888 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -585,6 +585,7 @@ nouveau_drm_device_fini(struct nouveau_drm *drm) if (nouveau_pmops_runtime()) { pm_runtime_get_sync(dev->dev); pm_runtime_forbid(dev->dev); + pm_runtime_dont_use_autosuspend(dev->dev); } nouveau_led_fini(dev); From 717e0a25036b6c92cecace30913b2d874a4c22b8 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 16:34:39 +0000 Subject: [PATCH 1960/2366] cifs: Fix server use-after-free in cifs_chan_skip_or_disable() When a secondary channel is no longer supported by the server, cifs_chan_skip_or_disable() drops the channel reference with cifs_put_tcp_session() and then continues to use the server pointer by calling cifs_signal_cifsd_for_reconnect() on it and reading its primary_server pointer. cifs_put_tcp_session() can drop the last reference of the channel and tear it down, so both the channel and the primary server (whose reference is also dropped by cifs_put_tcp_session()) can be freed before they are signaled for reconnect. Signal the channel and the primary server and capture the primary server pointer before dropping the channel reference with cifs_put_tcp_session(). Fixes: f591062bdbf4 ("cifs: handle servers that still advertise multichannel after disabling") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2pdu.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index dea05aeb53a1..880ce12f50c4 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -189,18 +189,19 @@ cifs_chan_skip_or_disable(struct cifs_ses *ses, spin_unlock(&ses->chan_lock); /* - * the above reference of server by channel - * needs to be dropped without holding chan_lock - * as cifs_put_tcp_session takes a higher lock - * i.e. cifs_tcp_ses_lock + * signal the channel and its primary server to + * reconnect before dropping the above reference of + * server by channel, which is done without holding + * chan_lock as cifs_put_tcp_session takes a higher + * lock i.e. cifs_tcp_ses_lock */ - cifs_put_tcp_session(server, from_reconnect); - cifs_signal_cifsd_for_reconnect(server, false); /* mark primary server as needing reconnect */ pserver = server->primary_server; cifs_signal_cifsd_for_reconnect(pserver, false); + + cifs_put_tcp_session(server, from_reconnect); skip_terminate: return -EHOSTDOWN; } From 8e0b235bd918d06f54ba8fddd2c3ddc36ca59c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20T=C3=B6pel?= Date: Tue, 15 Sep 2026 12:49:15 +0200 Subject: [PATCH 1961/2366] eth: fbnic: Fix payload page pool error cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The payload page pool pointer contains an error pointer when its allocation fails. The cleanup path passes that error pointer to page_pool_destroy() instead of destroying the header page pool. This can dereference the error pointer and leave the header page pool allocated. Destroy the header page pool instead. Fixes: 8a11010fdd96 ("eth: fbnic: allocate unreadable page pool for the payloads") Reported-by: Sashiko Link: https://lore.kernel.org/netdev/178915061000.219967.7726187707862333281@kernel.org/ Signed-off-by: Björn Töpel Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260915104917.3978113-1-bjorn@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index e7918d3f6aba..661dee1661af 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -1622,7 +1622,7 @@ fbnic_alloc_qt_page_pools(struct fbnic_net *fbn, struct fbnic_q_triad *qt, return 0; err_destroy_sub0: - page_pool_destroy(pp); + page_pool_destroy(qt->sub0.page_pool); return PTR_ERR(pp); } From e7d3e2f46dd5a69046e6d95a0f189155a5516b93 Mon Sep 17 00:00:00 2001 From: "Chang S. Bae" Date: Wed, 16 Sep 2026 22:59:39 +0000 Subject: [PATCH 1962/2366] x86/microcode/intel: Reject problematic loading on Granite Rapids systems Microcode updates can usually jump revisions. However, there is an erratum on Granite Rapids systems. If they "jump over" revision 0x1000405, they result in an #MC. Avoid it. Signed-off-by: Chang S. Bae Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Dave Hansen Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260916225939.1144524-1-chang.seok.bae@intel.com --- arch/x86/kernel/cpu/microcode/intel.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/arch/x86/kernel/cpu/microcode/intel.c b/arch/x86/kernel/cpu/microcode/intel.c index 1142183c950c..9f09d388ed20 100644 --- a/arch/x86/kernel/cpu/microcode/intel.c +++ b/arch/x86/kernel/cpu/microcode/intel.c @@ -309,6 +309,26 @@ static void save_microcode_patch(struct microcode_intel *patch) pr_err("Unable to allocate microcode memory size: %u\n", size); } +static bool revision_is_safe(struct cpu_signature *sig, u32 rev) +{ + u32 vfm = IFM(x86_family(sig->sig), x86_model(sig->sig)); + + /* + * Erratum GNR98 can cause #MCs if "jumping over" revision 0x1000405. + * Avoid the jumps. + */ + if (vfm == INTEL_GRANITERAPIDS_X && + x86_stepping(sig->sig) == 1 && + sig->pf & 0x95 && + sig->rev < 0x1000405 && + rev > 0x1000405) { + pr_err_once("Erratum GNR98: skipping revision 0x%x.\n", rev); + return false; + } + + return true; +} + /* Scan blob for microcode matching the boot CPUs family, model, stepping */ static __init struct microcode_intel *scan_microcode(void *data, size_t size, struct ucode_cpu_info *uci, @@ -330,6 +350,9 @@ static __init struct microcode_intel *scan_microcode(void *data, size_t size, if (!intel_find_matching_signature(data, &uci->cpu_sig)) continue; + if (!revision_is_safe(&uci->cpu_sig, mc_header->rev)) + continue; + /* * For saving the early microcode, find the matching revision which * was loaded on the BSP. @@ -878,6 +901,9 @@ static enum ucode_state parse_microcode_blobs(int cpu, struct iov_iter *iter) if (!intel_find_matching_signature(mc, &uci->cpu_sig)) continue; + if (!revision_is_safe(&uci->cpu_sig, mc_header.rev)) + continue; + is_safe = ucode_validate_minrev(&mc_header); if (force_minrev && !is_safe) continue; From 0a5f5d9e94dead312d32c366b917c64e552b72f7 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 16 Sep 2026 06:01:14 -0400 Subject: [PATCH 1963/2366] net/sched: cls_u32: fix manual hash table handle IDR aliasing A u32 hash table created with an explicit handle ('tc filter add ... handle 801: u32 divisor N') keys its IDR entry on the raw handle, while the destroy paths free it under handle2id(handle). The two key domains disagree for handles in the 0x800..0xFFF htid range: handle2id() folds them back into the auto-allocated id space (1..0x7FF). A manual table therefore leaves its raw-keyed IDR entry unreachable on delete (a permanent leak), and its delete can drop the idr entry of an unrelated live auto table. A later auto allocation can then hand out a handle that aliases the live manual table; u32_lookup_ht() first-match routes lookups and TCA_U32_LINK for that htid to the wrong table. Key the divisor-path alloc on handle2id(handle) so allocation and removal share one key domain. A manual handle that maps onto an id already in use is rejected with -ENOSPC, and auto allocation skips ids held by live manual tables. Conditions to recreate: ip link add test0 type dummy tc qdisc add dev test0 clsact tc filter add dev test0 ingress protocol ip pref 1 \ handle 801: u32 divisor 16 tc filter add dev test0 ingress protocol ip pref 2 u32 divisor 16 tc -d filter show dev test0 ingress | grep 'fh 801:' # unpatched: two live tables with handle 0x80100000 (the pref 2 root # hnode is auto-allocated id 1); patched: the auto hnode takes id 2. Also tested with a poc with a live u32 table on the block, add/delete a manual table 'handle 901: u32 divisor 1' twice; unpatched, the re-add fails with -ENOSPC because the raw key leaked on the first delete. Fixes: 73af53d82076 ("net: sched: cls_u32: Fix u32's systematic failure to free IDR entries for hnodes.") Reported-by: Sashiko (gemini + nipa) Closes: https://sashiko.dev/#/patchset/20260822222049.114526-1-jhs@mojatatu.com Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-LQFE.v1.20260911041746.1@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/cls_u32.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c index a3e65c8cf29e..76ce2d124079 100644 --- a/net/sched/cls_u32.c +++ b/net/sched/cls_u32.c @@ -1003,8 +1003,16 @@ static int u32_change(struct net *net, struct sk_buff *in_skb, return -ENOMEM; } } else { - err = idr_alloc_u32(&tp_c->handle_idr, ht, &handle, - handle, GFP_KERNEL); + /* The IDR is keyed on the mapped id, and that is + * what the destroy paths remove. Ask for it here, + * so a manual handle colliding with the + * auto-allocated id space is rejected (-ENOSPC) + * instead of aliasing a future auto id. + */ + u32 id = handle2id(handle); + + err = idr_alloc_u32(&tp_c->handle_idr, ht, &id, id, + GFP_KERNEL); if (err) { kfree(ht); return err; From 960ab631f3d8789586db71b9914b361059ddcb6e Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 16 Sep 2026 06:01:15 -0400 Subject: [PATCH 1964/2366] selftests/tc-testing: add u32 manual table handle IDR tests 35fc: create a manual table with handle 801:, then add an auto-allocated table. Before the fix, the auto allocation reuses id 1 and hands out the same handle 0x80100000, aliasing the manual table; the test requires the manual 801: handle to keep exactly one entry in the dump. a6e8: with a live u32 table keeping the tc_u_common alive, add and delete a manual table with handle 901:, then re-add it. Unpatched, the delete leaks the raw-keyed IDR entry and the re-add fails with -ENOSPC; the test requires the re-add to succeed. Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-LQFE.v1.20260911041746.2@mojatatu.com Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/filters/u32.json | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json b/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json index e2b03f2b5e89..edc5148a8d97 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json +++ b/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json @@ -376,5 +376,53 @@ "teardown": [ "$TC qdisc del dev $DUMMY clsact" ] + }, + { + "id": "35fc", + "name": "u32 manual table then auto table: auto allocation must not alias a live manual handle", + "category": [ + "filter", + "u32" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 ingress protocol ip pref 1 handle 801: u32 divisor 16" + ], + "cmdUnderTest": "$TC filter add dev $DEV1 ingress protocol ip pref 2 u32 divisor 16", + "expExitCode": "0", + "verifyCmd": "$TC -d filter show dev $DEV1 ingress", + "matchPattern": "fh 801:", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "a6e8", + "name": "u32 manual table add/del does not leak its idr entry (re-adding the same handle succeeds)", + "category": [ + "filter", + "u32" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 ingress protocol ip pref 1 u32 divisor 16", + "$TC filter add dev $DEV1 ingress protocol ip pref 5 handle 901: u32 divisor 1", + "$TC filter del dev $DEV1 ingress protocol ip pref 5 handle 901: u32" + ], + "cmdUnderTest": "$TC filter add dev $DEV1 ingress protocol ip pref 6 handle 901: u32 divisor 1", + "expExitCode": "0", + "verifyCmd": "$TC -d filter show dev $DEV1 ingress", + "matchPattern": "fh 901: ht divisor 1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] } ] From daf677c2c6449011ee695d55b48b5b2977a36f88 Mon Sep 17 00:00:00 2001 From: Kyle Hendry Date: Tue, 15 Sep 2026 10:39:20 -0700 Subject: [PATCH 1965/2366] net: pcs: rzn1-miic: Fix config array initialization Fix memset parameters to initialize the entire DT value array Fixes: f39e968dc168a7bd ("net: pcs: rzn1-miic: Move configuration data to SoC-specific struct") Reviewed-by: Geert Uytterhoeven Signed-off-by: Kyle Hendry Reviewed-by: Lad Prabhakar Link: https://patch.msgid.link/20260915-rzn1-miic-fix-array-v5-1-b7173fd5b97d@reliablecontrols.com Signed-off-by: Jakub Kicinski --- drivers/net/pcs/pcs-rzn1-miic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/pcs/pcs-rzn1-miic.c b/drivers/net/pcs/pcs-rzn1-miic.c index 2b72fa98ddf1..cb74861e823c 100644 --- a/drivers/net/pcs/pcs-rzn1-miic.c +++ b/drivers/net/pcs/pcs-rzn1-miic.c @@ -683,7 +683,8 @@ static int miic_parse_dt(struct miic *miic, u32 *mode_cfg) if (!dt_val) return -ENOMEM; - memset(dt_val, MIIC_MODCTRL_CONF_NONE, sizeof(*dt_val)); + memset(dt_val, MIIC_MODCTRL_CONF_NONE, + sizeof(*dt_val) * miic->of_data->conf_conv_count); if (of_property_read_u32(np, "renesas,miic-switch-portin", &conf) == 0) dt_val[0] = conf; From 39c6580765dad6477fb2637f6f616e0d276aae65 Mon Sep 17 00:00:00 2001 From: Heyang Tan Date: Mon, 14 Sep 2026 10:05:21 +0800 Subject: [PATCH 1966/2366] octeontx2-af: use seq_file for rsrc_alloc debugfs The rsrc_alloc debugfs reader writes rows directly to userspace without respecting the caller's read count. It also uses the current row length as the userspace stride, which can corrupt output when rows have different widths. Use seq_file to handle userspace buffer sizes, offsets, and partial reads, and write output columns directly to the seq_file buffer. Fixes: 23205e6d06d4 ("octeontx2-af: Dump current resource provisioning status") Signed-off-by: Heyang Tan Reviewed-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260914020521.146-1-thy15333007817@163.com Signed-off-by: Jakub Kicinski --- .../marvell/octeontx2/af/rvu_debugfs.c | 104 ++++++------------ 1 file changed, 33 insertions(+), 71 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c index 904374baae6f..2927633465d9 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c @@ -714,110 +714,72 @@ static int get_max_column_width(struct rvu *rvu) } /* Dumps current provisioning status of all RVU block LFs */ -static ssize_t rvu_dbg_rsrc_attach_status(struct file *filp, - char __user *buffer, - size_t count, loff_t *ppos) +static int rvu_dbg_rsrc_attach_status(struct seq_file *filp, void *unused) { - int index, off = 0, flag = 0, len = 0, i = 0; - struct rvu *rvu = filp->private_data; - int bytes_not_copied = 0; + struct rvu *rvu = filp->private; + int index, pf, vf, pcifunc; struct rvu_block block; - int pf, vf, pcifunc; - int buf_size = 2048; int lf_str_size; char *lfs; - char *buf; - /* don't allow partial reads */ - if (*ppos != 0) - return 0; - - buf = kzalloc(buf_size, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - /* Get the maximum width of a column */ lf_str_size = get_max_column_width(rvu); + if (lf_str_size < 0) + return lf_str_size; lfs = kzalloc(lf_str_size, GFP_KERNEL); - if (!lfs) { - kfree(buf); + if (!lfs) return -ENOMEM; - } - off += scnprintf(&buf[off], buf_size - 1 - off, "%-*s", lf_str_size, - "pcifunc"); + + seq_printf(filp, "%-*s", lf_str_size, "pcifunc"); for (index = 0; index < BLK_COUNT; index++) - if (strlen(rvu->hw->block[index].name)) { - off += scnprintf(&buf[off], buf_size - 1 - off, - "%-*s", lf_str_size, - rvu->hw->block[index].name); - } + if (strlen(rvu->hw->block[index].name)) + seq_printf(filp, "%-*s", lf_str_size, + rvu->hw->block[index].name); - off += scnprintf(&buf[off], buf_size - 1 - off, "\n"); - bytes_not_copied = copy_to_user(buffer + (i * off), buf, off); - if (bytes_not_copied) - goto out; - - i++; - *ppos += off; + seq_putc(filp, '\n'); for (pf = 0; pf < rvu->hw->total_pfs; pf++) { for (vf = 0; vf <= rvu->hw->total_vfs; vf++) { - off = 0; - flag = 0; pcifunc = rvu_make_pcifunc(rvu->pdev, pf, vf); if (!pcifunc) continue; - if (vf) { - sprintf(lfs, "PF%d:VF%d", pf, vf - 1); - off = scnprintf(&buf[off], - buf_size - 1 - off, - "%-*s", lf_str_size, lfs); - } else { - sprintf(lfs, "PF%d", pf); - off = scnprintf(&buf[off], - buf_size - 1 - off, - "%-*s", lf_str_size, lfs); - } - for (index = 0; index < BLK_COUNT; index++) { block = rvu->hw->block[index]; if (!strlen(block.name)) continue; - len = 0; - lfs[len] = '\0'; + lfs[0] = '\0'; get_lf_str_list(&block, pcifunc, lfs); if (strlen(lfs)) - flag = 1; - - off += scnprintf(&buf[off], buf_size - 1 - off, - "%-*s", lf_str_size, lfs); + break; } - if (flag) { - off += scnprintf(&buf[off], - buf_size - 1 - off, "\n"); - bytes_not_copied = copy_to_user(buffer + - (i * off), - buf, off); - if (bytes_not_copied) - goto out; + if (index == BLK_COUNT) + continue; - i++; - *ppos += off; + if (vf) + sprintf(lfs, "PF%d:VF%d", pf, vf - 1); + else + sprintf(lfs, "PF%d", pf); + seq_printf(filp, "%-*s", lf_str_size, lfs); + + for (index = 0; index < BLK_COUNT; index++) { + block = rvu->hw->block[index]; + if (!strlen(block.name)) + continue; + + lfs[0] = '\0'; + get_lf_str_list(&block, pcifunc, lfs); + seq_printf(filp, "%-*s", lf_str_size, lfs); } + seq_putc(filp, '\n'); } } -out: kfree(lfs); - kfree(buf); - if (bytes_not_copied) - return -EFAULT; - return *ppos; + return 0; } -RVU_DEBUG_FOPS(rsrc_status, rsrc_attach_status, NULL); +RVU_DEBUG_SEQ_FOPS(rsrc_status, rsrc_attach_status, NULL); static int rvu_dbg_rvu_pf_cgx_map_display(struct seq_file *filp, void *unused) { From 1d653a183973f5283a3db5a38cd5e195eb152244 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 17 Sep 2026 22:24:07 +0100 Subject: [PATCH 1967/2366] fprobe: Terminate the fgraph_data list when the reservation is not filled fprobe_fgraph_entry() reserves shadow stack space for every fprobe with an exit handler, but only fills it for those whose entry handler returns 0. fgraph_reserve_data() does not clear the area, so fprobe_return() parses the unused tail as headers left over from an earlier call, and an exit handler can run twice or despite its entry handler asking to skip it. Write a zero word after the last entry to terminate the walk. A zeroed slot does not decode to a NULL fprobe on the arches that encode the header into one unsigned long, since arch_decode_fprobe_header_fp() ORs in FPROBE_HEADER_MSB_PATTERN, so make read_fprobe_header() return NULL for a zeroed slot. Link: https://lore.kernel.org/all/20260917212407.384468-1-devnexen@gmail.com/ Fixes: e0a384434ae1 ("tracing: fprobe: do not zero out unused fgraph_data") Cc: stable@vger.kernel.org Suggested-by: Masami Hiramatsu (Google) Signed-off-by: David Carlier Signed-off-by: Masami Hiramatsu (Google) --- kernel/trace/fprobe.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c index 1e9b00997ff2..9f2d98181779 100644 --- a/kernel/trace/fprobe.c +++ b/kernel/trace/fprobe.c @@ -171,6 +171,11 @@ static inline bool write_fprobe_header(unsigned long *stack, static inline void read_fprobe_header(unsigned long *stack, struct fprobe **fp, unsigned int *size_words) { + if (!*stack) { + *fp = NULL; + *size_words = 0; + return; + } *fp = arch_decode_fprobe_header_fp(*stack); *size_words = arch_decode_fprobe_header_size(*stack); } @@ -203,6 +208,12 @@ static inline void read_fprobe_header(unsigned long *stack, { struct __fprobe_header *fph = (struct __fprobe_header *)stack; + if (!*stack) { + *fp = NULL; + *size_words = 0; + return; + } + *fp = fph->fp; *size_words = fph->size_words; } @@ -635,6 +646,10 @@ static int fprobe_fgraph_entry(struct ftrace_graph_ent *trace, struct fgraph_ops } } + /* Terminate the list, fgraph_reserve_data() does not clear it. */ + if (used && used < reserved_words) + fgraph_data[used] = 0; + /* If any exit_handler is set, data must be used. */ return used != 0; } From d09e8f64653c93da5793c16be19330968f2a32e6 Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Tue, 15 Sep 2026 14:34:57 +0300 Subject: [PATCH 1968/2366] net/mlx5: devcom, Base component size on linked devices mlx5_devcom_comp_get_size() returns the component's kref count. That kref is bumped in mlx5_devcom_register_component() under comp_list_lock, before the comp_dev is linked onto comp_dev_list_head under comp->sem. The event broadcast (mlx5_devcom_locked_send_event()) walks that list. Hence, a caller can read the expected size, but send_event won't be sent to all peers. In the SD group registration path, this lets a member broadcast its role-election event over an incomplete list, electing a primary that never completes the group, is never marked ready, and leaves the group with a stale primary. Track the number of linked comp_devs in a dedicated counter, maintained under comp->sem together with the list add/remove, and return it from mlx5_devcom_comp_get_size(). Fixes: 9bb1ac80738a ("net/mlx5: devcom, Add component size getter") Signed-off-by: Shay Drory Reviewed-by: Akiva Goldberger Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260915113459.3934760-2-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c index 64f92427602d..75855481522b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c @@ -37,6 +37,7 @@ struct mlx5_devcom_comp { struct mlx5_devcom_key key; mlx5_devcom_event_handler_t handler; struct kref ref; + int nr_devs; bool ready; struct rw_semaphore sem; struct lock_class_key lock_key; @@ -170,6 +171,7 @@ devcom_alloc_comp_dev(struct mlx5_devcom_dev *devc, down_write(&comp->sem); list_add_tail(&devcom->list, &comp->comp_dev_list_head); + WRITE_ONCE(comp->nr_devs, comp->nr_devs + 1); up_write(&comp->sem); return devcom; @@ -182,6 +184,7 @@ devcom_free_comp_dev(struct mlx5_devcom_comp_dev *devcom) down_write(&comp->sem); list_del(&devcom->list); + WRITE_ONCE(comp->nr_devs, comp->nr_devs - 1); up_write(&comp->sem); kref_put(&devcom->devc->ref, mlx5_devcom_dev_release); @@ -284,7 +287,7 @@ int mlx5_devcom_comp_get_size(struct mlx5_devcom_comp_dev *devcom) { struct mlx5_devcom_comp *comp = devcom->comp; - return kref_read(&comp->ref); + return READ_ONCE(comp->nr_devs); } int mlx5_devcom_locked_send_event(struct mlx5_devcom_comp_dev *devcom, From e1e29ada2b938b13ba689a06a8bd8604564da2b3 Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Tue, 15 Sep 2026 14:34:58 +0300 Subject: [PATCH 1969/2366] net/mlx5: SD, unload reps on shared FDB create error path mlx5_lag_shared_fdb_create() sets sd_fdb_active on every group member before reloading the representors, so mlx5_lag_is_active() is already true and the guard in mlx5_esw_offloads_rep_load() does not skip the VF/SF reps. If the reload then fails, the error path clears sd_fdb_active and destroys the shared FDB, leaving the reps loaded while SD LAG is inactive - the state cited commit was written to prevent. Unload the reps in the error path as well. Fixes: 68c2dd59a6c7 ("net/mlx5: E-Switch, Tie rep load/unload to SD LAG state") Signed-off-by: Shay Drory Reviewed-by: Akiva Goldberger Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260915113459.3934760-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c index 6b4ad3c53f2f..424040918fa3 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c @@ -270,6 +270,7 @@ int mlx5_lag_shared_fdb_create(struct mlx5_lag *ldev, pf->sd_fdb_active = false; } mlx5_lag_destroy_single_fdb_filter(ldev, group_id); + mlx5_lag_unload_reps_from_locked(ldev, filter); } err_add_devices: mlx5_lag_add_devices_filter(ldev, filter); From bae23d1ae62092c7f0ec6d5f7e1be5d164822638 Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Tue, 15 Sep 2026 14:34:59 +0300 Subject: [PATCH 1970/2366] net/mlx5: LAG, reload IB reps of LAG master before the rest In a shared-FDB LAG the master device creates the bond IB device; the other LAG members do not create their own, they populate a port inside the master's IB device. mlx5_lag_reload_ib_reps_unlocked() reloaded the members' IB reps in iteration order, with no guarantee the master is reloaded first. When a non-master member is reloaded before the master, it tries to populate its port in an IB device that has not been recreated yet. Hence, reload the master's IB reps first, then every other member. Fixes: 2b204cdb1206 ("net/mlx5: LAG, use xa_alloc to manage LAG device indices") Signed-off-by: Shay Drory Reviewed-by: Akiva Goldberger Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260915113459.3934760-4-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/lag/lag.c | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index c655f6e32e9b..dd14cdc378de 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -1266,25 +1266,45 @@ void mlx5_lag_remove_devices(struct mlx5_lag *ldev) mlx5_lag_remove_devices_filter(ldev, MLX5_LAG_FILTER_PORTS); } +static int mlx5_lag_reload_ib_reps_idx(struct mlx5_lag *ldev, int idx, + u32 flags) +{ + struct lag_func *pf = mlx5_lag_pf(ldev, idx); + struct mlx5_eswitch *esw; + int ret; + + if (pf->dev->priv.flags & flags) + return 0; + + esw = pf->dev->priv.eswitch; + mlx5_esw_reps_block(esw); + ret = mlx5_eswitch_reload_ib_reps(esw); + mlx5_esw_reps_unblock(esw); + + return ret; +} + static int mlx5_lag_reload_ib_reps_unlocked(struct mlx5_lag *ldev, u32 flags, u32 filter, bool cont_on_fail) { - struct lag_func *pf; + int master_idx = mlx5_lag_get_dev_index_by_seq_filter(ldev, MLX5_LAG_P1, + filter); int ret; int i; - mlx5_lag_for_each(i, 0, ldev, filter) { - pf = mlx5_lag_pf(ldev, i); - if (!(pf->dev->priv.flags & flags)) { - struct mlx5_eswitch *esw; + if (master_idx < 0) + return -EINVAL; - esw = pf->dev->priv.eswitch; - mlx5_esw_reps_block(esw); - ret = mlx5_eswitch_reload_ib_reps(esw); - mlx5_esw_reps_unblock(esw); - if (ret && !cont_on_fail) - return ret; - } + ret = mlx5_lag_reload_ib_reps_idx(ldev, master_idx, flags); + if (ret && !cont_on_fail) + return ret; + + mlx5_lag_for_each(i, 0, ldev, filter) { + if (i == master_idx) + continue; + ret = mlx5_lag_reload_ib_reps_idx(ldev, i, flags); + if (ret && !cont_on_fail) + return ret; } return 0; From 261b61d3735b042ae25634f795c4540be0fc140c Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:09 +0200 Subject: [PATCH 1971/2366] bpf: Make post-verification instruction rewrites killable After do_check() returns, the verifier runs several instruction rewrite passes. Some of them patch or remove one instruction at a time. Each operation moves the remaining instruction and auxiliary-data arrays and adjusts all branch offsets, making the overall work quadratic in the program length. A privileged loader can submit 131072 unconditional jumps by zero followed by a valid return. Verification finishes quickly, but bpf_opt_remove_nops() then spends a long time removing each jump separately. Since this post-verification work neither checks for signals nor reschedules, a pending SIGKILL cannot terminate the task until the rewrite finishes. Make bpf_patch_insn_data() and verifier_remove_insns() common cancellation and rescheduling points. These helpers run from BPF_PROG_LOAD process context, and bpf_patch_insn_data() can already sleep while reallocating auxiliary data. Report interrupted constant blinding as -EINTR and propagate it through both JIT paths, including kernels that permit interpreter fallback. Other blinding failures retain the existing fallback behavior. This does not reduce the quadratic cost of the rewrite passes, but it makes the work preemptible and allows a killed loader to be torn down promptly. Fixes: 52875a04f4b2 ("bpf: verifier: remove dead code") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-2-memxor@gmail.com Signed-off-by: Eduard Zingerman --- kernel/bpf/core.c | 21 +++++++++++++++++---- kernel/bpf/fixups.c | 24 ++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 8b294dfc1ad4..2e3bf8113ae9 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -1619,6 +1620,8 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp * fix it up here on error. */ bpf_jit_prog_release_other(prog, clone); + if (env && fatal_signal_pending(current)) + return ERR_PTR(-EINTR); return IS_ERR(tmp) ? tmp : ERR_PTR(-ENOMEM); } @@ -2636,11 +2639,14 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc orig_prog = prog; prog = bpf_jit_blind_constants(env, prog); /* - * If blinding was requested and we failed during blinding, we must fall - * back to the interpreter. + * Fall back to the interpreter after blinding failures, except when + * the loader was killed. */ - if (IS_ERR(prog)) + if (IS_ERR(prog)) { + if (PTR_ERR(prog) == -EINTR) + return prog; goto out_restore; + } prog = bpf_int_jit_compile(env, prog); if (prog->jited) { @@ -2659,6 +2665,8 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct bpf_prog *fp, int *err) { + struct bpf_prog *jit_prog; + /* In case of BPF to BPF calls, verifier did all the prep * work with regards to JITing, etc. */ @@ -2681,7 +2689,12 @@ struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct if (*err) return fp; - fp = bpf_prog_jit_compile(env, fp); + jit_prog = bpf_prog_jit_compile(env, fp); + if (IS_ERR(jit_prog)) { + *err = PTR_ERR(jit_prog); + return fp; + } + fp = jit_prog; bpf_prog_jit_attempt_done(fp); if (!fp->jited && jit_needed) { *err = -ENOTSUPP; diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c index 52d3cec33672..d6f83521fc78 100644 --- a/kernel/bpf/fixups.c +++ b/kernel/bpf/fixups.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "disasm.h" @@ -306,12 +307,28 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len) } } +/* + * Some post-verification instruction rewriting passes require an + * O(prog->len) operation per instruction. Keep their shared primitives + * killable and preemptible. + */ +static bool bpf_rewrite_must_abort(void) +{ + if (fatal_signal_pending(current)) + return true; + cond_resched(); + return false; +} + struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, const struct bpf_insn *patch, u32 len) { struct bpf_prog *new_prog; struct bpf_insn_aux_data *new_data = NULL; + if (bpf_rewrite_must_abort()) + return NULL; + if (len > 1) { new_data = vrealloc(env->insn_aux_data, array_size(env->prog->len + len - 1, @@ -523,6 +540,9 @@ static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt) unsigned int orig_prog_len = env->prog->len; int err; + if (bpf_rewrite_must_abort()) + return -EINTR; + if (bpf_prog_is_offloaded(env->prog->aux)) bpf_prog_offload_remove_insns(env, off, cnt); @@ -1356,7 +1376,7 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env) } prog = bpf_jit_blind_constants(env, prog); if (IS_ERR(prog)) { - err = -ENOMEM; + err = PTR_ERR(prog); prog = orig_prog; goto out_restore; } @@ -1433,7 +1453,7 @@ int bpf_fixup_call_args(struct bpf_verifier_env *env) err = bpf_jit_subprogs(env); if (err == 0) return 0; - if (err == -EFAULT) + if (err == -EFAULT || err == -EINTR) return err; } #ifndef CONFIG_BPF_JIT_ALWAYS_ON From fd16449a9b3b31a8f18944c2f0e29e4e218ca2cf Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:10 +0200 Subject: [PATCH 1972/2366] bpf: Preserve packet pointer class displacement in regsafe() regsafe() maps packet pointer IDs between states and checks that each current register range is a subset of the corresponding explored register range. It does not, however, preserve the displacement between registers that share a packet pointer ID. This is unsound because packet range is shared by ID. A bounds check on one class member updates every member, and a later access can consume the range through another member. Commit 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") folded the fixed pointer offset into r64 and removed the old off equality check, so two individually narrower registers can prune even when their displacement has changed. The explored path can then license an out-of-bounds packet access on the pruned path. Require matching range bases for packet pointers with an ID. Together with the existing ID mapping, this preserves the displacement between members of each packet-pointer class without adding per-ID state. Packet pointers without an ID remain unaffected. Fixes: 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-3-memxor@gmail.com Signed-off-by: Eduard Zingerman --- kernel/bpf/states.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c index 66fb11b6c6a7..6c88ad95b63b 100644 --- a/kernel/bpf/states.c +++ b/kernel/bpf/states.c @@ -635,6 +635,9 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, /* id relations must be preserved */ if (!check_ids(rold->id, rcur->id, idmap)) return false; + /* Preserve displacements between pointers sharing an ID. */ + if (rold->id && rold->r64.base != rcur->r64.base) + return false; /* new val must satisfy old val knowledge */ return range_within(rold, rcur) && tnum_in(rold->var_off, rcur->var_off); From 2059d9af54f0d66deb237aa75d2ed28648df05a1 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:11 +0200 Subject: [PATCH 1973/2366] selftests/bpf: Test packet pointer class displacement pruning Add two paths whose packet pointer ranges are individually compatible at a join but whose members have different relative displacements. The first path proves an eight-byte access through one member. On the second path, the same guard only proves that the access starts before data_end. An affected verifier prunes the second path and accepts the program. With packet pointer class displacement preserved, it explores that path and rejects the out-of-bounds access. Read the unknown offset and branch selector directly from XDP context fields, and force state checkpoints so the pruning attempt does not depend on the verifier checkpoint heuristics. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://patch.msgid.link/20260917233222.2542500-4-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../progs/verifier_xdp_direct_packet_access.c | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c index 0b86d95a4133..9866bc154194 100644 --- a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c +++ b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c @@ -1719,4 +1719,39 @@ l0_%=: r0 = 0; \ : __clobber_all); } +SEC("xdp") +__description("XDP pkt regsafe preserves packet pointer class displacement") +__failure __msg("R2 min value is outside of the allowed memory range") +__flag(BPF_F_ANY_ALIGNMENT) __flag(BPF_F_TEST_STATE_FREQ) +__naked void pkt_regsafe_class_displacement(void) +{ + asm volatile (" \ + r8 = *(u32 *)(r1 + %[xdp_md_data_end]); \ + r9 = *(u32 *)(r1 + %[xdp_md_data]); \ + r4 = *(u32 *)(r1 + %[xdp_md_rx_queue_index]); \ + r4 &= 15; \ + r0 = *(u32 *)(r1 + %[xdp_md_ingress_ifindex]); \ + if r0 != 0 goto l0_%=; \ + r2 = r9; \ + r2 += r4; \ + r3 = r2; \ + r3 += 8; \ + goto l1_%=; \ +l0_%=: r4 &= 3; \ + r4 += 8; \ + r2 = r9; \ + r2 += r4; \ + r3 = r2; \ +l1_%=: if r3 > r8 goto l2_%=; \ + r0 = *(u64 *)(r2 + 0); \ +l2_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(xdp_md_data, offsetof(struct xdp_md, data)), + __imm_const(xdp_md_data_end, offsetof(struct xdp_md, data_end)), + __imm_const(xdp_md_rx_queue_index, offsetof(struct xdp_md, rx_queue_index)), + __imm_const(xdp_md_ingress_ifindex, offsetof(struct xdp_md, ingress_ifindex)) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; From c26e97721b172163042b98572fead234797f2c3c Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:12 +0200 Subject: [PATCH 1974/2366] bpf: Apply CO-RE relocations before subprogram validation check_subprogs() verifies that each subprogram ends in an exit or an unconditional jump before in-kernel CO-RE relocations are applied. An unresolved relocation can then replace that terminal instruction with an invalid helper call. The resulting fall-through into another subprogram breaks the CFG invariant used by postorder and stack liveness analysis, which can write past their per-subprogram arrays. Apply CO-RE relocations immediately after preparing the program BTF, before subprogram discovery and validation. Keep func_info and line_info validation after subprogram discovery because those records depend on the complete subprogram layout. Reject an ldimm64 first slot at the end of the instruction stream before CO-RE can inspect its missing second slot. check_subprogs() previously rejected this form before relocation processing because it is not a valid subprogram terminator. Moving CO-RE ahead of check_subprogs() removes that implicit protection, so perform an explicit check before applying relocations. Include core_relo_cnt when deciding whether to prepare program BTF. A load that supplied only CO-RE relocation metadata previously skipped both BTF setup and relocation processing. Fixes: fbd94c7afcf9 ("bpf: Pass a set of bpf_core_relo-s to prog_load command.") Suggested-by: Andrii Nakryiko Suggested-by: Alexei Starovoitov Suggested-by: Eduard Zingerman Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-5-memxor@gmail.com Signed-off-by: Eduard Zingerman --- include/linux/bpf_verifier.h | 2 ++ kernel/bpf/check_btf.c | 12 ++++-------- kernel/bpf/verifier.c | 12 +++++++++++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 36b65797877d..bba5a727c651 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -1197,6 +1197,8 @@ static inline void bpf_trampoline_unpack_key(u64 key, u32 *obj_id, u32 *btf_id) int bpf_prepare_btf_info(struct bpf_verifier_env *env, const union bpf_attr *attr, bpfptr_t uattr); +int bpf_check_core_relo(struct bpf_verifier_env *env, + const union bpf_attr *attr, bpfptr_t uattr); int bpf_check_btf_info(struct bpf_verifier_env *env, const union bpf_attr *attr, bpfptr_t uattr); diff --git a/kernel/bpf/check_btf.c b/kernel/bpf/check_btf.c index 0e8b3ccc7a5b..4c1ed842f661 100644 --- a/kernel/bpf/check_btf.c +++ b/kernel/bpf/check_btf.c @@ -338,9 +338,9 @@ static int check_btf_line(struct bpf_verifier_env *env, #define MIN_CORE_RELO_SIZE sizeof(struct bpf_core_relo) #define MAX_CORE_RELO_SIZE MAX_FUNCINFO_REC_SIZE -static int check_core_relo(struct bpf_verifier_env *env, - const union bpf_attr *attr, - bpfptr_t uattr) +int bpf_check_core_relo(struct bpf_verifier_env *env, + const union bpf_attr *attr, + bpfptr_t uattr) { u32 i, nr_core_relo, ncopy, expected_size, rec_size; struct bpf_core_relo core_relo = {}; @@ -414,7 +414,7 @@ int bpf_prepare_btf_info(struct bpf_verifier_env *env, struct btf *btf; int err; - if (!attr->func_info_cnt && !attr->line_info_cnt) { + if (!attr->func_info_cnt && !attr->line_info_cnt && !attr->core_relo_cnt) { if (check_abnormal_return(env)) return -EINVAL; return 0; @@ -455,9 +455,5 @@ int bpf_check_btf_info(struct bpf_verifier_env *env, if (err) return err; - err = check_core_relo(env, attr, uattr); - if (err) - return err; - return 0; } diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 5d7080c260d8..33161dc64568 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -21199,6 +21199,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, ret = bpf_diag_init(env); if (ret) goto err_prep; + if (env->prog->insnsi[env->prog->len - 1].code == (BPF_LD | BPF_IMM | BPF_DW)) { + verbose(env, "invalid bpf_ld_imm64 insn\n"); + ret = -EINVAL; + goto err_prep; + } if (env->signature) { ret = bpf_prog_calc_tag(env->prog); if (ret < 0) @@ -21274,6 +21279,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, if (ret < 0) goto skip_full_check; + /* Apply CO-RE before validating the program's instruction layout. */ + ret = bpf_check_core_relo(env, attr, uattr); + if (ret < 0) + goto skip_full_check; + /* Discover all subprograms before validating their layout and BTF. */ ret = add_subprogs(env); if (ret < 0) @@ -21283,7 +21293,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, if (ret < 0) goto skip_full_check; - /* Validate BTF against the complete subprogram layout and apply CO-RE. */ + /* Validate BTF against the complete subprogram layout. */ ret = bpf_check_btf_info(env, attr, uattr); if (ret < 0) goto skip_full_check; From 968ee7c06b62f1ac4597c351a45c2219a678a458 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:13 +0200 Subject: [PATCH 1975/2366] selftests/bpf: Test early in-kernel CO-RE relocation Add a raw program load with CO-RE relocation metadata but no func_info or line_info. Place the relocation in dead code and require the poisoning log, proving that the kernel processes standalone CO-RE metadata instead of silently skipping it. Also give a subprogram a relocatable immediate as its terminal instruction. Require the relocation's poisoning log before check_subprogs() rejects the resulting fall-through. With the old ordering, check_subprogs() rejects the original terminal instruction before CO-RE can emit the substitution log, so the test continues to distinguish the ordering after relocation target validation is tightened. Submit a trailing ldimm64 first slot with CO-RE metadata and require the early structural diagnostic. This exercises the check that protects relocation processing instead of the later regular instruction validation. Load the standalone instruction stream without relocation metadata first to ensure that CO-RE processing causes its poisoning diagnostic. Encode the fixed BTF metadata directly with the selftest BTF helpers. Suggested-by: Eduard Zingerman Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-6-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../selftests/bpf/prog_tests/core_reloc_raw.c | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c index a18d3680fb16..bb19e49dd87d 100644 --- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c +++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c @@ -14,6 +14,138 @@ static char log[16 * 1024]; +static int load_core_relo_insns(int btf_fd, struct bpf_insn *insns, int insn_cnt, + struct bpf_func_info *funcs, int func_cnt, + int enum_id, int access_str_off, int insn_idx, + bool relocate) +{ + struct bpf_core_relo relo = { + .insn_off = insn_idx * sizeof(struct bpf_insn), + .type_id = enum_id, + .access_str_off = access_str_off, + .kind = BPF_CORE_ENUMVAL_VALUE, + }; + union bpf_attr attr = { + .prog_type = BPF_PROG_TYPE_SOCKET_FILTER, + .insn_cnt = insn_cnt, + .insns = (__u64)insns, + .license = (__u64)"GPL", + .log_buf = (__u64)log, + .log_size = sizeof(log), + .log_level = 2, + .prog_btf_fd = btf_fd, + .func_info_rec_size = sizeof(struct bpf_func_info), + .func_info = (__u64)funcs, + .func_info_cnt = func_cnt, + }; + + if (relocate) { + attr.core_relo_cnt = 1; + attr.core_relos = (__u64)&relo; + attr.core_relo_rec_size = sizeof(relo); + } + memset(log, 0, sizeof(log)); + return sys_bpf_prog_load(&attr, sizeof(attr), 1); +} + +static void test_early_core_relo(void) +{ + struct test_btf { + struct btf_header hdr; + __u32 types[18]; + char strings[64]; + } raw_btf = { + .hdr = { + .magic = BTF_MAGIC, + .version = BTF_VERSION, + .hdr_len = sizeof(struct btf_header), + .type_off = 0, + .type_len = sizeof(raw_btf.types), + .str_off = offsetof(struct test_btf, strings) - + offsetof(struct test_btf, types), + .str_len = sizeof(raw_btf.strings), + }, + .types = { + BTF_TYPE_INT_ENC(1, BTF_INT_SIGNED, 0, 32, 4), /* [1] int */ + BTF_FUNC_PROTO_ENC(1, 0), /* [2] int (*)(void) */ + BTF_FUNC_ENC(5, 2), /* [3] main_fn */ + BTF_FUNC_ENC(13, 2), /* [4] sub_fn */ + BTF_TYPE_ENC(20, BTF_INFO_ENC(BTF_KIND_ENUM, 0, 1), 4), /* [5] enum */ + BTF_ENUM_ENC(45, 0), /* value = 0 */ + }, + .strings = "\0int\0main_fn\0sub_fn\0core_relo_poison_missing\0value\0" "0", + }; + struct bpf_func_info funcs[] = { + { .insn_off = 0, .type_id = 3 }, + { .insn_off = 3, .type_id = 4 }, + }; + struct bpf_insn core_only[] = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + struct bpf_insn subprog[] = { + BPF_CALL_REL(2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + struct bpf_insn truncated_ldimm64[] = { + BPF_RAW_INSN(BPF_LD | BPF_IMM | BPF_DW, 0, 0, 0, 0), + }; + int access_str_off = 51; /* offset of "0" */ + int enum_id = 5; + int btf_fd, prog_fd = -1; + + btf_fd = bpf_btf_load(&raw_btf, sizeof(raw_btf), NULL); + if (!ASSERT_GE(btf_fd, 0, "btf_load")) + goto cleanup; + + if (test__start_subtest("without_func_info")) { + prog_fd = load_core_relo_insns(btf_fd, core_only, ARRAY_SIZE(core_only), NULL, 0, + enum_id, access_str_off, 2, false); + if (!ASSERT_GE(prog_fd, 0, "control_load")) + goto cleanup; + close(prog_fd); + prog_fd = load_core_relo_insns(btf_fd, core_only, ARRAY_SIZE(core_only), NULL, 0, + enum_id, access_str_off, 2, true); + if (!ASSERT_GE(prog_fd, 0, "poisoned_load")) + goto cleanup; + ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log"); + close(prog_fd); + prog_fd = -1; + } + + if (test__start_subtest("before_subprog_validation")) { + prog_fd = load_core_relo_insns(btf_fd, subprog, ARRAY_SIZE(subprog), funcs, 2, + enum_id, access_str_off, 2, true); + if (!ASSERT_LT(prog_fd, 0, "poisoned_load")) + goto cleanup; + ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log"); + ASSERT_HAS_SUBSTR(log, "last insn is not an exit or jmp", "poisoned_load_log"); + } + + if (test__start_subtest("truncated_ldimm64")) { + prog_fd = load_core_relo_insns(btf_fd, truncated_ldimm64, + ARRAY_SIZE(truncated_ldimm64), NULL, 0, + enum_id, access_str_off, 0, true); + if (!ASSERT_LT(prog_fd, 0, "truncated_load")) + goto cleanup; + ASSERT_HAS_SUBSTR(log, "invalid bpf_ld_imm64 insn", "truncated_load_log"); + } + +cleanup: + if (env.verbosity > VERBOSE_NORMAL && log[0]) { + printf("-------- program load log start --------\n"); + printf("%s", log); + printf("-------- program load log end ----------\n"); + } + close(prog_fd); + close(btf_fd); +} + /* Check that verifier rejects BPF program containing relocation * pointing to non-existent BTF type. */ @@ -120,6 +252,7 @@ static void test_bad_local_id(void) void test_core_reloc_raw(void) { + test_early_core_relo(); if (test__start_subtest("bad_local_id")) test_bad_local_id(); } From 394ae398337c5f87e567f6cd63b937fc2b2f6ddc Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:14 +0200 Subject: [PATCH 1976/2366] bpf: Restrict CO-RE poisoning to relocatable instructions CO-RE relocation records can name any instruction offset. When a relocation cannot be resolved, bpf_core_patch_insn() currently poisons its target before checking whether that instruction is a valid relocation target. Malformed metadata can therefore replace jumps, calls, exits, register-source arithmetic, or non-immediate loads instead of failing at the relocation step. Handle poisoning only after the instruction has passed the same class and operand-form checks used for a resolved relocation. Route invalid forms through the existing diagnostic and return a hard error. Keep poisoning supported instructions, including both halves of a plain ldimm64, so an unresolved relocation in dead code remains valid. Extend bpf_core_poison_insn() to poison both halves of ldimm64, and return its status directly from each validated instruction case. This avoids routing the success path through a common label and leaves the helper free to report errors. The shared relocation code applies this restriction to both libbpf and in-kernel CO-RE. Fixes: d7a252708dbc ("libbpf: Improve handling of failed CO-RE relocations") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-7-memxor@gmail.com Signed-off-by: Eduard Zingerman --- tools/lib/bpf/relo_core.c | 58 +++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/tools/lib/bpf/relo_core.c b/tools/lib/bpf/relo_core.c index 8ad2715721cf..2672623a4198 100644 --- a/tools/lib/bpf/relo_core.c +++ b/tools/lib/bpf/relo_core.c @@ -980,23 +980,30 @@ static int bpf_core_calc_relo(const char *prog_name, } /* - * Turn instruction for which CO_RE relocation failed into invalid one with + * Turn instruction for which CO-RE relocation failed into invalid one with * distinct signature. */ -static void bpf_core_poison_insn(const char *prog_name, int relo_idx, - int insn_idx, struct bpf_insn *insn) +static int bpf_core_poison_insn(const char *prog_name, int relo_idx, + struct bpf_insn *insn, int insn_idx) { - pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n", - prog_name, relo_idx, insn_idx); - insn->code = BPF_JMP | BPF_CALL; - insn->dst_reg = 0; - insn->src_reg = 0; - insn->off = 0; - /* if this instruction is reachable (not a dead code), - * verifier will complain with the following message: - * invalid func unknown#195896080 - */ - insn->imm = 195896080; /* => 0xbad2310 => "bad relo" */ + int insn_cnt = is_ldimm64_insn(insn) ? 2 : 1; + int i; + + for (i = 0; i < insn_cnt; i++) { + pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n", + prog_name, relo_idx, insn_idx + i); + insn[i].code = BPF_JMP | BPF_CALL; + insn[i].dst_reg = 0; + insn[i].src_reg = 0; + insn[i].off = 0; + /* + * If this instruction is reachable (not dead code), the verifier + * will complain with "invalid func unknown#195896080". + */ + insn[i].imm = 195896080; /* => 0xbad2310 => "bad relo" */ + } + + return 0; } static int insn_bpf_size_to_bytes(struct bpf_insn *insn) @@ -1047,17 +1054,6 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, class = BPF_CLASS(insn->code); - if (res->poison) { -poison: - /* poison second part of ldimm64 to avoid confusing error from - * verifier about "unknown opcode 00" - */ - if (is_ldimm64_insn(insn)) - bpf_core_poison_insn(prog_name, relo_idx, insn_idx + 1, insn + 1); - bpf_core_poison_insn(prog_name, relo_idx, insn_idx, insn); - return 0; - } - orig_val = res->orig_val; new_val = res->new_val; @@ -1065,7 +1061,9 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, case BPF_ALU: case BPF_ALU64: if (BPF_SRC(insn->code) != BPF_K) - return -EINVAL; + goto bad_insn; + if (res->poison) + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); if (res->validate && insn->imm != orig_val) { pr_warn("prog '%s': relo #%d: unexpected insn #%d (ALU/ALU64) value: got %d, exp %llu -> %llu\n", prog_name, relo_idx, @@ -1082,6 +1080,8 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, case BPF_LDX: case BPF_ST: case BPF_STX: + if (res->poison) + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); if (res->validate && insn->off != orig_val) { pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDX/ST/STX) value: got %d, exp %llu -> %llu\n", prog_name, relo_idx, insn_idx, insn->off, (unsigned long long)orig_val, @@ -1097,7 +1097,7 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, pr_warn("prog '%s': relo #%d: insn #%d (LDX/ST/STX) accesses field incorrectly. " "Make sure you are accessing pointers, unsigned integers, or fields of matching type and size.\n", prog_name, relo_idx, insn_idx); - goto poison; + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); } orig_val = insn->off; @@ -1140,6 +1140,9 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, return -EINVAL; } + if (res->poison) + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); + imm = (__u32)insn[0].imm | ((__u64)insn[1].imm << 32); if (res->validate && imm != orig_val) { pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDIMM64) value: got %llu, exp %llu -> %llu\n", @@ -1157,6 +1160,7 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, break; } default: +bad_insn: pr_warn("prog '%s': relo #%d: trying to relocate unrecognized insn #%d, code:0x%x, src:0x%x, dst:0x%x, off:0x%x, imm:0x%x\n", prog_name, relo_idx, insn_idx, insn->code, (unsigned)insn->src_reg, (unsigned)insn->dst_reg, (unsigned)insn->off, (unsigned)insn->imm); From 3440505aca926e726a26c0fd30356454334322bd Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:15 +0200 Subject: [PATCH 1977/2366] selftests/bpf: Test CO-RE instruction poisoning restrictions Add raw CO-RE relocations that fail to resolve their target enum value. Place each supported and unsupported instruction form in dead code. Unsupported targets must fail relocation with a diagnostic even when they are unreachable. Supported ALU immediates, memory accesses, and ldimm64 instructions must still be poisoned and removed as dead code, allowing the program to load. Check that both halves of ldimm64 are poisoned. Load every instruction stream without relocations first to ensure that rejection is caused by the relocation rather than the original program. Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-8-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../selftests/bpf/prog_tests/core_reloc_raw.c | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c index bb19e49dd87d..51f42b02a267 100644 --- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c +++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c @@ -50,6 +50,28 @@ static int load_core_relo_insns(int btf_fd, struct bpf_insn *insns, int insn_cnt static void test_early_core_relo(void) { + static const char unrecognized[] = "trying to relocate unrecognized insn #2"; + static const struct { + const char *name; + struct bpf_insn insns[2]; + const char *err_msg; + } tests[] = { + { "poison_exit", { BPF_EXIT_INSN() }, unrecognized }, + { "poison_ja", { BPF_JMP_A(1) }, unrecognized }, + { "poison_jmp", { BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1) }, unrecognized }, + { "poison_jmp32", { BPF_JMP32_IMM(BPF_JEQ, BPF_REG_0, 0, 1) }, unrecognized }, + { "poison_call", { BPF_EMIT_CALL(BPF_FUNC_get_prandom_u32) }, unrecognized }, + { "poison_alu_reg", { BPF_MOV32_REG(BPF_REG_0, BPF_REG_1) }, unrecognized }, + { "poison_alu64_reg", { BPF_MOV64_REG(BPF_REG_0, BPF_REG_1) }, unrecognized }, + { "poison_ld_abs", { BPF_LD_ABS(BPF_W, 0) }, + "insn #2 (LDIMM64) has unexpected form" }, + { "poison_alu_imm", { BPF_MOV32_IMM(BPF_REG_0, 0) } }, + { "poison_alu64_imm", { BPF_MOV64_IMM(BPF_REG_0, 0) } }, + { "poison_ldx", { BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0) } }, + { "poison_st", { BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0) } }, + { "poison_stx", { BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4) } }, + { "poison_ldimm64", { BPF_LD_IMM64(BPF_REG_0, 0) } }, + }; struct test_btf { struct btf_header hdr; __u32 types[18]; @@ -97,7 +119,7 @@ static void test_early_core_relo(void) }; int access_str_off = 51; /* offset of "0" */ int enum_id = 5; - int btf_fd, prog_fd = -1; + int btf_fd, prog_fd = -1, i; btf_fd = bpf_btf_load(&raw_btf, sizeof(raw_btf), NULL); if (!ASSERT_GE(btf_fd, 0, "btf_load")) @@ -136,6 +158,43 @@ static void test_early_core_relo(void) ASSERT_HAS_SUBSTR(log, "invalid bpf_ld_imm64 insn", "truncated_load_log"); } + for (i = 0; i < ARRAY_SIZE(tests); i++) { + struct bpf_insn insns[] = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1), + tests[i].insns[0], + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + bool is_ldimm64 = insns[2].code == (BPF_LD | BPF_DW | BPF_IMM); + + if (!test__start_subtest(tests[i].name)) + continue; + if (is_ldimm64) { + insns[1].off = 2; + insns[3] = tests[i].insns[1]; + } + prog_fd = load_core_relo_insns(btf_fd, insns, ARRAY_SIZE(insns), funcs, 1, + enum_id, access_str_off, 2, false); + if (!ASSERT_GE(prog_fd, 0, "control_load")) + goto cleanup; + close(prog_fd); + prog_fd = load_core_relo_insns(btf_fd, insns, ARRAY_SIZE(insns), funcs, 1, + enum_id, access_str_off, 2, true); + if (!tests[i].err_msg) { + ASSERT_GE(prog_fd, 0, "dead_poison_load"); + ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log"); + if (is_ldimm64) + ASSERT_HAS_SUBSTR(log, "substituting insn #3", "poison_ldimm64_log"); + } else { + ASSERT_LT(prog_fd, 0, "invalid_poison_load"); + ASSERT_HAS_SUBSTR(log, tests[i].err_msg, "invalid_poison_log"); + ASSERT_NULL(strstr(log, "substituting insn"), "invalid_poison_substitution"); + } + close(prog_fd); + prog_fd = -1; + } + cleanup: if (env.verbosity > VERBOSE_NORMAL && log[0]) { printf("-------- program load log start --------\n"); From 71919742c83c32afcccf06a7a28e28f3f55f21a2 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:16 +0200 Subject: [PATCH 1978/2366] bpf: Assign lock identity to callback map values A nested bpf_for_each_map_elem() callback can unlock a different element of the same map: static long inner(void *map, int *key, struct value *v, struct value **outer_value) { bpf_spin_lock(&v->lock); bpf_spin_unlock(&(*outer_value)->lock); return 0; } static long outer(void *map, int *key, struct value *v, void *ctx) { bpf_for_each_map_elem(map, inner, &v, 0); return 0; } Both callback values currently have ID zero and the same map_ptr. process_spin_lock() compares those two fields, so it accepts the unlock even though the two callbacks can receive different map elements. Assign a fresh ID to every callback map value in the for-each, timer/workqueue, and task-work constructors. Copies of one callback argument retain its ID, so locking and unlocking through that argument continues to work. Distinct callbacks also get distinct IDs for single-element arrays, including inner arrays sharing inner_map_meta. Preserve map_uid for every inner-map lookup and compare it through check_ids() during state pruning. This preserves relationships between maps, keys, and values while allowing equivalent states with different lookup IDs to match. It avoids field-specific rules for when an inner map needs an identity. Move map_uid out of the metadata union and next to the other IDs, so register comparisons can use the existing memcmp() ranges and remap the IDs separately. Clear it when resetting a register or converting a map lookup result to a socket pointer. Shrink frameno to u8, which is enough for MAX_CALL_FRAMES, to make room without growing bpf_reg_state. Fixes: d0d78c1df9b1 ("bpf: Allow locking bpf_spin_lock global variables") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260917233222.2542500-9-memxor@gmail.com Signed-off-by: Eduard Zingerman --- include/linux/bpf_verifier.h | 25 +++++++++++++------------ kernel/bpf/states.c | 6 ++++-- kernel/bpf/verifier.c | 13 +++++++++---- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index bba5a727c651..a7202b44ab10 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -46,17 +46,11 @@ struct bpf_reg_state { /* valid when type == PTR_TO_PACKET */ int range; - /* valid when type == CONST_PTR_TO_MAP | PTR_TO_MAP_VALUE | - * PTR_TO_MAP_VALUE_OR_NULL + /* + * For CONST_PTR_TO_MAP, PTR_TO_MAP_KEY, PTR_TO_MAP_VALUE and + * PTR_TO_INSN. */ - struct { - struct bpf_map *map_ptr; - /* To distinguish map lookups from outer map - * the map_uid is non-zero for registers - * pointing to inner maps. - */ - u32 map_uid; - }; + struct bpf_map *map_ptr; /* for PTR_TO_BTF_ID */ struct { @@ -155,13 +149,20 @@ struct bpf_reg_state { * gets parent_id set to the dynptr's id. */ u32 parent_id; - /* Inside the callee two registers can be both PTR_TO_STACK like + /* + * Distinguishes inner-map lookups and their keys and values. Zero for + * other registers. Kept outside the metadata union for ID remapping + * during state comparisons. + */ + u32 map_uid; + /* + * Inside the callee two registers can be both PTR_TO_STACK like * R1=fp-8 and R2=fp-8, but one of them points to this function stack * while another to the caller's stack. To differentiate them 'frameno' * is used which is an index in bpf_verifier_state->frame[] array * pointing to bpf_func_state. */ - u32 frameno; + u8 frameno; /* if (!precise && SCALAR_VALUE) min/max/tnum don't affect safety */ bool precise; }; diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c index 6c88ad95b63b..e4ec007f7fa6 100644 --- a/kernel/bpf/states.c +++ b/kernel/bpf/states.c @@ -491,7 +491,8 @@ static bool regs_exact(const struct bpf_reg_state *rold, { return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 && check_ids(rold->id, rcur->id, idmap) && - check_ids(rold->parent_id, rcur->parent_id, idmap); + check_ids(rold->parent_id, rcur->parent_id, idmap) && + check_ids(rold->map_uid, rcur->map_uid, idmap); } enum exact_level { @@ -616,7 +617,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, range_within(rold, rcur) && tnum_in(rold->var_off, rcur->var_off) && check_ids(rold->id, rcur->id, idmap) && - check_ids(rold->parent_id, rcur->parent_id, idmap); + check_ids(rold->parent_id, rcur->parent_id, idmap) && + check_ids(rold->map_uid, rcur->map_uid, idmap); case PTR_TO_PACKET_META: case PTR_TO_PACKET: /* We must have at least as much range as the old ptr diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 33161dc64568..02be326f235c 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -1864,6 +1864,7 @@ static void __mark_reg_known(struct bpf_reg_state *reg, u64 imm) offsetof(struct bpf_reg_state, var_off) - sizeof(reg->type)); reg->id = 0; reg->parent_id = 0; + reg->map_uid = 0; ___mark_reg_known(reg, imm); } @@ -1925,17 +1926,18 @@ static void refine_map_lookup_value(struct bpf_reg_state *reg) if (map->inner_map_meta) { reg->type = CONST_PTR_TO_MAP | maybe_null; reg->map_ptr = map->inner_map_meta; - /* transfer reg's id which is unique for every map_lookup_elem + /* + * transfer reg's id which is unique for every map_lookup_elem * as UID of the inner map. */ - if (btf_record_has_field(map->inner_map_meta->record, - BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK)) - reg->map_uid = reg->id; + reg->map_uid = reg->id; } else if (map->map_type == BPF_MAP_TYPE_XSKMAP) { reg->type = PTR_TO_XDP_SOCK | maybe_null; + reg->map_uid = 0; } else if (map->map_type == BPF_MAP_TYPE_SOCKMAP || map->map_type == BPF_MAP_TYPE_SOCKHASH) { reg->type = PTR_TO_SOCKET | maybe_null; + reg->map_uid = 0; } } @@ -10048,6 +10050,7 @@ int map_set_for_each_callback_args(struct bpf_verifier_env *env, __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = caller->regs[BPF_REG_1].map_ptr; callee->regs[BPF_REG_3].map_uid = caller->regs[BPF_REG_1].map_uid; + callee->regs[BPF_REG_3].id = ++env->id_gen; /* pointer to stack or null */ callee->regs[BPF_REG_4] = caller->regs[BPF_REG_3]; @@ -10144,6 +10147,7 @@ static int set_timer_callback_state(struct bpf_verifier_env *env, __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = map_ptr; callee->regs[BPF_REG_3].map_uid = map_uid; + callee->regs[BPF_REG_3].id = ++env->id_gen; /* unused */ bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]); @@ -10262,6 +10266,7 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env, __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = map_ptr; callee->regs[BPF_REG_3].map_uid = map_uid; + callee->regs[BPF_REG_3].id = ++env->id_gen; /* unused */ bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]); From 04ae4ffc57a6b7a8215779f0e9c536cacda9f761 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:17 +0200 Subject: [PATCH 1979/2366] selftests/bpf: Check callback map value lock identity Add a verifier test which retains a map value from an outer callback and then acquires a lock through an inner callback value before attempting to release the outer callback value. Both values can denote different elements, so the verifier must reject the mismatched unlock. Also exercise callbacks reached through two inner-map lookups. The lookup results share inner_map_meta but may refer to different one-element arrays, so their callback values must retain distinct lock identities. Extend the existing spin_lock failure table and reuse its array and inner-map fixtures to keep these cases alongside the other lock identity tests. Update the nested callback reference-leak expectation for the extra callback value ID. Signed-off-by: Kumar Kartikeya Dwivedi Link: https://patch.msgid.link/20260917233222.2542500-10-memxor@gmail.com Signed-off-by: Eduard Zingerman --- .../selftests/bpf/prog_tests/cb_refs.c | 2 +- .../selftests/bpf/prog_tests/spin_lock.c | 2 + .../selftests/bpf/progs/test_spin_lock_fail.c | 67 ++++++++++++++++++- 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/cb_refs.c b/tools/testing/selftests/bpf/prog_tests/cb_refs.c index 78566b817fd7..490e15e7126d 100644 --- a/tools/testing/selftests/bpf/prog_tests/cb_refs.c +++ b/tools/testing/selftests/bpf/prog_tests/cb_refs.c @@ -13,7 +13,7 @@ struct { } cb_refs_tests[] = { { "underflow_prog", "release kfunc bpf_kfunc_call_test_release expects referenced PTR_TO_BTF_ID passed to R1" }, { "leak_prog", "Possibly NULL pointer passed to helper R2" }, - { "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */ + { "nested_cb", "Unreleased reference id=5 alloc_insn=2" }, /* alloc_insn=2{4,5} */ { "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */ }; diff --git a/tools/testing/selftests/bpf/prog_tests/spin_lock.c b/tools/testing/selftests/bpf/prog_tests/spin_lock.c index 5c3579438427..e368370262c8 100644 --- a/tools/testing/selftests/bpf/prog_tests/spin_lock.c +++ b/tools/testing/selftests/bpf/prog_tests/spin_lock.c @@ -54,6 +54,8 @@ static struct { { "lock_global_sleepable_helper_subprog", "global function calls are not allowed while holding a lock" }, { "lock_global_sleepable_kfunc_subprog", "global function calls are not allowed while holding a lock" }, { "lock_global_sleepable_subprog_indirect", "global function calls are not allowed while holding a lock" }, + { "callback_value_lock_identity", "bpf_spin_unlock of different lock" }, + { "callback_inner_map_value_lock_identity", "bpf_spin_unlock of different lock" }, }; static int match_regex(const char *pattern, const char *string) diff --git a/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c b/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c index f678ee6bd7ea..55282f20fa32 100644 --- a/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c +++ b/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c @@ -14,17 +14,18 @@ struct array_map { __type(key, int); __type(value, struct foo); __uint(max_entries, 1); -} array_map SEC(".maps"); +} array_map SEC(".maps"), array_map_b SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS); - __uint(max_entries, 1); + __uint(max_entries, 2); __type(key, int); __type(value, int); __array(values, struct array_map); } map_of_maps SEC(".maps") = { .values = { [0] = &array_map, + [1] = &array_map_b, }, }; @@ -314,4 +315,66 @@ int lock_global_sleepable_subprog_indirect(struct __sk_buff *ctx) return ret; } +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 2); + __type(key, int); + __type(value, struct foo); +} callback_array_map SEC(".maps"); + +struct callback_ctx { + struct foo *value; +}; + +static long lock_different_value(struct bpf_map *map, int *key, + struct foo *value, struct callback_ctx *ctx) +{ + bpf_spin_lock(&value->lock); + bpf_spin_unlock(&ctx->value->lock); + return 0; +} + +static long nest_lock_different_value(struct bpf_map *map, int *key, + struct foo *value, void *data) +{ + struct callback_ctx ctx = { .value = value }; + + bpf_for_each_map_elem(&callback_array_map, lock_different_value, &ctx, 0); + return 0; +} + +SEC("?tc") +int callback_value_lock_identity(void *ctx) +{ + bpf_for_each_map_elem(&callback_array_map, nest_lock_different_value, NULL, 0); + return 0; +} + +static long nest_lock_different_inner_value(struct bpf_map *map, int *key, + struct foo *value, void *data) +{ + struct callback_ctx ctx = { .value = value }; + int inner_key = 1; + void *inner_map; + + inner_map = bpf_map_lookup_elem(&map_of_maps, &inner_key); + if (!inner_map) + return 0; + bpf_for_each_map_elem(inner_map, lock_different_value, &ctx, 0); + return 0; +} + +SEC("?tc") +int callback_inner_map_value_lock_identity(void *ctx) +{ + int inner_key = 0; + void *inner_map; + + inner_map = bpf_map_lookup_elem(&map_of_maps, &inner_key); + if (!inner_map) + return 0; + bpf_for_each_map_elem(inner_map, nest_lock_different_inner_value, NULL, 0); + return 0; +} + char _license[] SEC("license") = "GPL"; From b4e875d397da451fb4e9c573ff4b86db53caba05 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 18 Sep 2026 01:32:18 +0200 Subject: [PATCH 1980/2366] libbpf: Reject truncated ldimm64 CO-RE relocations CO-RE relocation of an ldimm64 instruction operates on two instruction slots. A malformed BPF ELF can end a function after the first slot and attach a CO-RE relocation to it. libbpf allocates the instruction array according to the function symbol size, so the shared relocation code would then access beyond the allocation. Reject a terminal ldimm64 in libbpf's relocation loop, where the program length is available, before resolving or applying the relocation. Both resolved and unresolved relocations validate the absent second slot, and unresolved relocation poisoning would additionally write past the array. The in-kernel caller is protected by the verifier's early instruction-stream check before it applies CO-RE relocations. Fixes: eacaaed784e2 ("libbpf: Implement enum value-based CO-RE relocations") Reported-by: Sashiko Signed-off-by: Kumar Kartikeya Dwivedi Link: https://lore.kernel.org/20260914140852.03DA21F0089B@smtp.kernel.org Link: https://patch.msgid.link/20260917233222.2542500-11-memxor@gmail.com Signed-off-by: Eduard Zingerman --- tools/lib/bpf/libbpf.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index b749c01742ee..bfa64ae6c94d 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -6206,6 +6206,13 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path) return -EINVAL; insn = &prog->insns[insn_idx]; + if (is_ldimm64_insn(insn) && (size_t)insn_idx + 1 >= prog->insns_cnt) { + pr_warn("prog '%s': relo #%d: insn #%d (LDIMM64) is truncated\n", + prog->name, i, insn_idx); + err = -EINVAL; + goto out; + } + err = record_relo_core(prog, rec, insn_idx); if (err) { pr_warn("prog '%s': relo #%d: failed to record relocation: %s\n", From 2ec28c09b320ba241bea8a70ee5cb9ccf4a099e8 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 14:30:50 -0300 Subject: [PATCH 1981/2366] vsock: ignore empty child namespace mode writes __vsock_net_mode_string() returns success without updating new_mode when the transfer length is zero. Its caller then reads the uninitialized enum and may permanently store a stack-derived value in the write-once child mode. Return before calling __vsock_net_mode_string() when *lenp is zero so that the helper is never invoked with nothing to parse and new_mode is never read uninitialized. This also prevents an empty write from locking the current mode. Fixes: eafb64f40ca4 ("vsock: add netns to vsock core") Cc: stable@vger.kernel.org Reviewed-by: Luigi Leonardi Signed-off-by: Aldo Ariel Panzardo Reviewed-by: Stefano Garzarella Reviewed-by: Bobby Eshleman Link: https://patch.msgid.link/20260915173050.3176344-1-qwe.aldo@gmail.com Signed-off-by: Jakub Kicinski --- net/vmw_vsock/af_vsock.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index f840498b58af..9b71479a2b29 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -2889,6 +2889,9 @@ static int vsock_net_child_mode_string(const struct ctl_table *table, int write, net = container_of(table->data, struct net, vsock.child_ns_mode); + if (!*lenp) + return 0; + ret = __vsock_net_mode_string(table, write, buffer, lenp, ppos, vsock_net_child_mode(net), &new_mode); if (ret) From 651010592bdce7005c1179498327e51bfc4fe1a5 Mon Sep 17 00:00:00 2001 From: Zhang Yunfei Date: Fri, 11 Sep 2026 17:11:23 +0800 Subject: [PATCH 1982/2366] net: txgbe: fix FDIR filter restore for VF rules txgbe_fdir_filter_restore() reprograms every filter from txgbe->fdir_filter_list after a reset. It extracts the ring part of filter->action with ethtool_get_flow_spec_ring() and maps it onto a PF rx ring, silently dropping the VF part of the cookie that txgbe_add_ethtool_fdir_entry() stores there (input->action = fsp->ring_cookie). For a rule directed at a VF, restore therefore reprograms the filter to the PF queue with the same ring index: after any down/up or txgbe_reinit_locked(), traffic matching the rule is steered to the PF instead of the VF. Handle VF rules the same way txgbe_add_ethtool_fdir_entry() does: validate vf against wx->num_vfs and ring against wx->num_rx_queues_per_pool, and map the ring onto the absolute queue index ((vf - 1) * wx->num_rx_queues_per_pool) + ring. Fixes: 7a91722e0dd4 ("net: txgbe: Support the FDIR rules assigned to VFs") Cc: stable@vger.kernel.org Signed-off-by: Zhang Yunfei Link: https://patch.msgid.link/20260911091123.798931-1-zhangyunfei1@kylinos.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c index a84010828551..59a47532618c 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c @@ -591,15 +591,24 @@ static void txgbe_fdir_filter_restore(struct wx *wx) queue = TXGBE_RDB_FDIR_DROP_QUEUE; } else { u32 ring = ethtool_get_flow_spec_ring(filter->action); + u8 vf = ethtool_get_flow_spec_ring_vf(filter->action); - if (ring >= wx->num_rx_queues) { + if (!vf && ring >= wx->num_rx_queues) { wx_err(wx, "FDIR restore failed, ring:%u\n", ring); continue; + } else if (vf && (vf > wx->num_vfs || + ring >= wx->num_rx_queues_per_pool)) { + wx_err(wx, "FDIR restore failed, vf:%u, ring:%u\n", + vf, ring); + continue; } /* Map the ring onto the absolute queue index */ - queue = wx->rx_ring[ring]->reg_idx; + if (!vf) + queue = wx->rx_ring[ring]->reg_idx; + else + queue = ((vf - 1) * wx->num_rx_queues_per_pool) + ring; } ret = txgbe_fdir_write_perfect_filter(wx, From 46bc52d13594848023e681860df8700c8db14354 Mon Sep 17 00:00:00 2001 From: Linkui Xiao Date: Wed, 16 Sep 2026 20:53:16 +0800 Subject: [PATCH 1983/2366] ipv4: fib: fix data-race and stale genid check around nh->nh_saddr fib_select_multipath() compares nexthop_nh->nh_saddr against the flow source address with no lock held, while fib_info_update_nhc_saddr() stores a new value from another CPU as soon as the preferred source address of the egress device changes. Commit 195374d89368 ("ipv4: fib: annotate races around nh->nh_saddr_genid and nh->nh_saddr") added WRITE_ONCE() on the store side and READ_ONCE() in fib_result_prefsrc() after syzbot reported BUG: KCSAN: data-race in fib_select_path / fib_select_path but it only covered that reader. fib_select_multipath(), reached from fib_select_path(), is a second lockless reader of nh->nh_saddr and was left bare. Moreover, nh_saddr is only meaningful when nh_saddr_genid matches dev_addr_genid, as established by commit 436c3b66ec98 ("ipv4: Invalidate nexthop cache nh_saddr more correctly."). fib_select_multipath() skips that validation, so it can score a nexthop using a stale source address and skew the ECMP selection. Annotate both reads with READ_ONCE() and refresh the cached source address via fib_info_update_nhc_saddr() when the genid does not match, mirroring fib_result_prefsrc(). Fixes: 32607a332cfe ("ipv4: prefer multipath nexthop that matches source address") Signed-off-by: Linkui Xiao Reviewed-by: Ido Schimmel Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260916125316.988044-1-xiaolinkui@126.com Signed-off-by: Jakub Kicinski --- net/ipv4/fib_semantics.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c index 7a362f2e2c2b..5c9021ea3a79 100644 --- a/net/ipv4/fib_semantics.c +++ b/net/ipv4/fib_semantics.c @@ -2176,6 +2176,15 @@ static bool fib_good_nh(const struct fib_nh *nh) return !!(state & NUD_VALID); } +static __be32 fib_nh_saddr(struct net *net, const struct fib_info *fi, + struct fib_nh *nh, int genid) +{ + if (READ_ONCE(nh->nh_saddr_genid) == genid) + return READ_ONCE(nh->nh_saddr); + + return fib_info_update_nhc_saddr(net, &nh->nh_common, fi->fib_scope); +} + void fib_select_multipath(struct fib_result *res, int hash, const struct flowi4 *fl4) { @@ -2184,6 +2193,7 @@ void fib_select_multipath(struct fib_result *res, int hash, bool use_neigh; int score = -1; __be32 saddr; + int genid; if (unlikely(res->fi->nh)) { nexthop_path_fib_result(res, hash); @@ -2192,6 +2202,7 @@ void fib_select_multipath(struct fib_result *res, int hash, use_neigh = READ_ONCE(net->ipv4.sysctl_fib_multipath_use_neigh); saddr = fl4 ? fl4->saddr : 0; + genid = saddr ? atomic_read(&net->ipv4.dev_addr_genid) : 0; change_nexthops(fi) { int nh_upper_bound, nh_score = 0; @@ -2204,7 +2215,7 @@ void fib_select_multipath(struct fib_result *res, int hash, (use_neigh && !fib_good_nh(nexthop_nh))) continue; - if (saddr && nexthop_nh->nh_saddr == saddr) + if (saddr && fib_nh_saddr(net, fi, nexthop_nh, genid) == saddr) nh_score += 2; if (hash <= nh_upper_bound) nh_score++; From a363c62a653cc8b3e21da9545fa4e028ef50f9c3 Mon Sep 17 00:00:00 2001 From: Longlong Xia Date: Sun, 23 Aug 2026 12:40:51 +0800 Subject: [PATCH 1984/2366] mm/hugetlb: do not dissolve gigantic pages without runtime support dissolve_free_hugetlb_folio() doesn't check hstate_is_gigantic_no_runtime(h) though remove_hugetlb_folio()/ update_and_free_hugetlb_folio() silently bail for such folios, so it frees a still-listed folio and, on vmemmap restore failure, the add_hugetlb_folio() rollback corrupts the free list. Link: https://lore.kernel.org/20260823044118.1097121-2-xialonglong2025@163.com Fixes: 6eb4e88a6d27 ("hugetlb: create remove_hugetlb_page() to separate functionality") Signed-off-by: Longlong Xia Signed-off-by: Andrew Morton Assisted-by: Codex:gpt-5.6-sol Acked-by: Muchun Song Cc: David Hildenbrand Cc: Miaohe Lin Cc: Michal Hocko Cc: Oscar Salvador Cc: --- mm/hugetlb.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 4f6f58bf3db6..d28972cd33f5 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -1967,6 +1967,15 @@ int dissolve_free_hugetlb_folio(struct folio *folio) struct hstate *h = folio_hstate(folio); bool adjust_surplus = false; + /* + * remove_hugetlb_folio()/update_and_free_hugetlb_folio() bail + * for gigantic hstates without runtime support, so dissolving one + * here would leave it on the free list and, on vmemmap restore + * failure, the add_hugetlb_folio() rollback corrupts that list. + */ + if (hstate_is_gigantic_no_runtime(h)) + goto out; + if (!available_huge_pages(h)) goto out; From 5179241521401ef364294128bb43cdbce7252457 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 17 Sep 2026 13:20:25 +0200 Subject: [PATCH 1985/2366] fs: don't create the private nullfs mount under namespace_sem init_mount_tree() mounts the mutable rootfs on top of nullfs via LOCK_MOUNT_EXACT(). That declares a pinned mountpoint with a cleanup attribute in the scope of the whole function so the nullfs root inode lock and namespace_sem are only dropped when init_mount_tree() returns. This became a problem when the private nullfs instance for kthreads was added. kern_mount() allocates a new superblock and alloc_super() takes the new s_umount with SINGLE_DEPTH_NESTING and then shrinker_mutex via shrinker_alloc(). Doing that with namespace_sem held teaches lockdep the dependency namespace_sem -> s_umount/1 -> shrinker_mutex With CONFIG_SHRINKER_DEBUG shrinker_debugfs_rename() takes the debugfs directory inode lock under shrinker_mutex every time a block device is mounted and lock_mount_exact() takes namespace_sem under the inode lock of the mountpoint for every mount. So mounting anything on debugfs, e.g. the tracefs automount on /sys/kernel/debug/tracing, closes the cycle: WARNING: possible circular locking dependency detected 7.3.0-rc3+ #17 Not tainted ------------------------------------------------------ rasdaemon/4449 is trying to acquire lock: (namespace_sem){++++}-{4:4}, at: lock_mount_exact+0x4c/0x308 but task is already holding lock: (&sb->s_type->i_mutex_key#17){++++}-{4:4}, at: lock_mount_exact+0x3c/0x308 which lock already depends on the new lock. ... Chain exists of: namespace_sem --> shrinker_mutex --> &sb->s_type->i_mutex_key#17 This can't actually deadlock. init_mount_tree() runs single-threaded during early boot before any other task exists and nothing allocates a superblock under namespace_sem after that. But lockdep can't know that and disables itself for the rest of the boot. Move mounting the rootfs on top of nullfs into a helper so the locks are dropped when it returns. Fixes: 32750c77e811 ("fs: start all kthreads in nullfs") Reported-by: Zenghui Yu Closes: https://lore.kernel.org/15174353-3f4a-a1ca-5bd1-ea2a4c77828e@huawei.com Link: https://patch.msgid.link/20260917-atemtechnik-bleichen-befassen-9a57db01baf0@brauner Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index ae5dc64f8b45..5e41021eaa63 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -6184,6 +6184,21 @@ struct mnt_namespace init_mnt_ns = { .poll = __WAIT_QUEUE_HEAD_INITIALIZER(init_mnt_ns.poll), }; +static void __init mount_rootfs_on_nullfs(struct vfsmount *mnt, + struct vfsmount *nullfs_mnt) +{ + struct path root = { + .mnt = nullfs_mnt, + .dentry = nullfs_mnt->mnt_root, + }; + + LOCK_MOUNT_EXACT(mp, &root); + if (unlikely(IS_ERR(mp.parent))) + panic("VFS: Failed to mount rootfs on nullfs"); + scoped_guard(mount_writer) + attach_mnt(real_mount(mnt), mp.parent, mp.mp); +} + static void __init init_mount_tree(void) { struct vfsmount *mnt, *nullfs_mnt; @@ -6215,15 +6230,7 @@ static void __init init_mount_tree(void) mnt_root = real_mount(nullfs_mnt); init_mnt_ns.root = mnt_root; - /* Mount mutable rootfs on top of nullfs. */ - root.mnt = nullfs_mnt; - root.dentry = nullfs_mnt->mnt_root; - - LOCK_MOUNT_EXACT(mp, &root); - if (unlikely(IS_ERR(mp.parent))) - panic("VFS: Failed to mount rootfs on nullfs"); - scoped_guard(mount_writer) - attach_mnt(real_mount(mnt), mp.parent, mp.mp); + mount_rootfs_on_nullfs(mnt, nullfs_mnt); pr_info("VFS: Finished mounting rootfs on nullfs\n"); From d54a489c8c4b627775445d54a6cbd32f209bda8f Mon Sep 17 00:00:00 2001 From: Frank Wunderlich Date: Thu, 17 Sep 2026 17:37:11 +0200 Subject: [PATCH 1986/2366] gpiolib: use of_node_name if line-name is missing Until v7.0, GPIO hogs inherited the DT node name when no line-name property was specified. This was implemented as a fallback in of_parse_own_gpio(). Commit d1d564ec4992 ("gpio: move hogs into GPIO core") moved hog parsing into the GPIO core and removed this fallback. Consequently, GPIO hogs without a line-name property are now displayed with a ? in /sys/kernel/debug/gpio. Restore the old fallback. Fixes: d1d564ec4992 ("gpio: move hogs into GPIO core") Signed-off-by: Frank Wunderlich Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260917153712.134367-1-linux@fw-web.de Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index ef8ccaf17c9c..28f7265c5d56 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1029,6 +1029,13 @@ int gpiochip_add_hog(struct gpio_chip *gc, struct fwnode_handle *fwnode) ret = of_gpiochip_get_lflags(gc, &gpiospec, &lflags); if (ret) return ret; + + /* + * If no line-name property is present, fall back to the OF + * node name as in the previous implementation. + */ + if (!name) + name = to_of_node(fwnode)->name; } else { /* * GPIO_ACTIVE_LOW is currently the only lookup flag From d644b23afe1ef509c9961a6d84a093c2587edf02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Tue, 18 Aug 2026 20:00:15 +0000 Subject: [PATCH 1987/2366] netfilter: flowtable: publish HW_DEAD after worker is done MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit flow_offload_work_del() sets NF_FLOW_HW_DEAD before the work handler clears NF_FLOW_HW_PENDING. Once a flow is both HW_DYING and HW_DEAD, a concurrent garbage collection pass can remove it and schedule it for RCU freeing. The offload worker holds neither an RCU read lock nor a reference to the flow. If it is preempted after publishing HW_DEAD, the RCU callback can free the flow before the worker resumes and clears HW_PENDING, resulting in a use-after-free. Move HW_DEAD publication to the common worker epilogue after the pending bit is cleared, making it the final flow access by destroy work. Order all preceding flow accesses before publishing the bit that allows garbage collection to free the object. Fixes: 2c8897953f3b ("netfilter: flowtable: Add pending bit for offload work") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_flow_table_offload.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c index 801a3dd9ceea..6757fd89c1f1 100644 --- a/net/netfilter/nf_flow_table_offload.c +++ b/net/netfilter/nf_flow_table_offload.c @@ -995,7 +995,6 @@ static void flow_offload_work_del(struct flow_offload_work *offload) flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_ORIGINAL); if (test_bit(NF_FLOW_HW_BIDIRECTIONAL, &offload->flow->flags)) flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_REPLY); - set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags); } static void flow_offload_tuple_stats(struct flow_offload_work *offload, @@ -1059,6 +1058,12 @@ static void flow_offload_work_handler(struct work_struct *work) } clear_bit(NF_FLOW_HW_PENDING, &offload->flow->flags); + if (offload->cmd == FLOW_CLS_DESTROY) { + /* Publish after the worker's last flow access. */ + smp_mb__before_atomic(); + set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags); + } + kfree(offload); } From 9461613afc59acef44a0071b0dd5075f6e993ffe Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 3 Sep 2026 02:41:46 +0200 Subject: [PATCH 1988/2366] netfilter: nfnetlink_queue: hold nfnl mutex in event notifier We must serialize the release notifier and the config netlink function. A concurrent thread can issue close() which can call the release function while unrelated socket processes UNBIND request for same portid: Oops: general protection fault, [..] RIP: 0010:__instance_destroy+0x60/0x210 [nfnetlink_queue] Call Trace: nfqnl_recv_config+0x9b0/0xdc0 [nfnetlink_queue] nfnetlink_rcv_msg+0x7c2/0xeb0 ? __pfx_nfnetlink_rcv_msg+0x10/0x10 After this, parallel UNBIND and URELEASE events are impossible. This change isn't nice, but its the shortest fix given instances are not refcounted and the nfnetlink config callback drops the rcu read lock early due to need for sleeping allocations. Fixes: 7af4cc3fa158 ("[NETFILTER]: Add "nfnetlink_queue" netfilter queue handler over nfnetlink") Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nfnetlink_queue.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index c727668b0c5b..a3bc00280051 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -1593,6 +1593,7 @@ nfqnl_rcv_nl_event(struct notifier_block *this, if (event == NETLINK_URELEASE && n->protocol == NETLINK_NETFILTER) { int i; + nfnl_lock(NFNL_SUBSYS_QUEUE); /* destroy all instances for this portid */ spin_lock(&q->instances_lock); for (i = 0; i < INSTANCE_BUCKETS; i++) { @@ -1606,6 +1607,7 @@ nfqnl_rcv_nl_event(struct notifier_block *this, } } spin_unlock(&q->instances_lock); + nfnl_unlock(NFNL_SUBSYS_QUEUE); } return NOTIFY_DONE; } @@ -1925,9 +1927,9 @@ static int nfqnl_recv_config(struct sk_buff *skb, const struct nfnl_info *info, /* Lookup queue under RCU. After peer_portid check (or for new queue * in BIND case), the queue is owned by the socket sending this message. - * A socket cannot simultaneously send a message and close, so while - * processing this CONFIG message, nfqnl_rcv_nl_event() (triggered by - * socket close) cannot destroy this queue. Safe to use without RCU. + * nfqnl_rcv_nl_event() will block on the nfnl subsys mutex that is + * held by the caller, so the queue cannot be destroyed in parallel, + * even after we drop the RCU read lock. */ rcu_read_lock(); queue = instance_lookup(q, queue_num); From 1b9b5323725e458906c7620a3bc10398b51ad954 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 6 Sep 2026 16:44:10 +0800 Subject: [PATCH 1989/2366] netfilter: ip6t_rpfilter: reject routes without inet6_dev ip6_route_lookup() can return an error-free route whose rt6i_idev is NULL. Lowering an external nexthop device's MTU below IPV6_MIN_MTU tears down its inet6_dev while fib6_ifdown() leaves routes using nexthop objects in the FIB. An unprivileged user can construct this state with rtnetlink in a private user and network namespace, then trigger a NULL dereference through an IPv6 rpfilter lookup: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000 KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: rpfilter_mt (net/ipv6/netfilter/ip6t_rpfilter.c:75) Call Trace: ip6t_do_table (net/ipv6/netfilter/ip6_tables.c:316) nf_hook_slow (net/netfilter/core.c:619) ipv6_rcv (net/ipv6/ip6_input.c:351) __netif_receive_skb_one_core (net/core/dev.c:6216) process_backlog (net/core/dev.c:6680) __napi_poll (net/core/dev.c:7739) net_rx_action (net/core/dev.c:7959) handle_softirqs (kernel/softirq.c:622) do_softirq.part.0 (kernel/softirq.c:523) __local_bh_enable_ip (kernel/softirq.c:450) __dev_queue_xmit (net/core/dev.c:4913) packet_sendmsg (net/packet/af_packet.c:3139) __sys_sendto (net/socket.c:2252) __x64_sys_sendto (net/socket.c:2259) do_syscall_64 (arch/x86/entry/syscall_64.c:94) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Kernel panic - not syncing: Fatal exception in interrupt Reject routes without an inet6_dev immediately after lookup. Such routes are not eligible for reverse-path filtering, and the check protects all later rt6i_idev dereferences. Fixes: e26f9a480fb6 ("netfilter: add ipv6 reverse path filter match") Reported-by: co+459f67f4d8af8ce6@bugs.sh Closes: https://lore.kernel.org/all/VtWUkE8QzJt5CroTj2V2v3ZQ0gwbXZ7nq7I3@bugs.sh/ Suggested-by: Florian Westphal Assisted-by: Claude:gpt-5 Cc: stable@vger.kernel.org Signed-off-by: Weiming Shi Signed-off-by: Pablo Neira Ayuso --- net/ipv6/netfilter/ip6t_rpfilter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/netfilter/ip6t_rpfilter.c b/net/ipv6/netfilter/ip6t_rpfilter.c index 67c87a88cde4..b5def30c3127 100644 --- a/net/ipv6/netfilter/ip6t_rpfilter.c +++ b/net/ipv6/netfilter/ip6t_rpfilter.c @@ -61,7 +61,7 @@ static bool rpfilter_lookup_reverse6(struct net *net, const struct sk_buff *skb, fl6.flowi6_oif = dev->ifindex; rt = (void *)ip6_route_lookup(net, &fl6, skb, lookup_flags); - if (rt->dst.error) + if (rt->dst.error || !rt->rt6i_idev) goto out; if (rt->rt6i_flags & (RTF_REJECT|RTF_ANYCAST)) From 82313c169eddc02b1bf5ba6b427803e272d3ec42 Mon Sep 17 00:00:00 2001 From: Luxiao Xu Date: Sun, 6 Sep 2026 21:29:55 +0800 Subject: [PATCH 1990/2366] netfilter: ip6t_rt: fix zero-address non-strict match out-of-bounds read rt_mt6_check() permits rules to be configured with rtinfo->addrnr == 0 even when address matching (IP6T_RT_FST_MASK) is requested. In the IP6T_RT_FST_NSTRICT path, rt_mt6() evaluates packet routing addresses against rtinfo->addrs[i] and terminates backwards at the bottom of the loop: if (ipv6_addr_equal(ap, &rtinfo->addrs[i])) { i++; } if (i == rtinfo->addrnr) break; When addrnr is 0, if the first packet address matches rtinfo->addrs[0], i is incremented to 1. Because i is now strictly greater than addrnr (0), the loop termination condition (i == rtinfo->addrnr) is bypassed and will never be satisfied. If a crafted IPv6 packet contains matching routing addresses, i will advance past IP6T_RT_HOPS (16). The subsequent call to ipv6_addr_equal() reads beyond struct ip6t_rt, triggering UBSAN/KASAN out-of-bounds warnings or kernel panics. Fix this by: 1. Rejecting rules in rt_mt6_check() where IP6T_RT_FST_MASK is set but rtinfo->addrnr is zero. 2. In rt_mt6(), moving the termination condition (i < rtinfo->addrnr) into the for-loop header condition and removing the backwards break at the end of the loop body. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega Suggested-by: Florian Westphal Assisted-by: LLM Signed-off-by: Luxiao Xu Signed-off-by: Ren Wei Signed-off-by: Pablo Neira Ayuso --- net/ipv6/netfilter/ip6t_rt.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/net/ipv6/netfilter/ip6t_rt.c b/net/ipv6/netfilter/ip6t_rt.c index 8051425213dd..9880faf3cc7d 100644 --- a/net/ipv6/netfilter/ip6t_rt.c +++ b/net/ipv6/netfilter/ip6t_rt.c @@ -96,7 +96,8 @@ static bool rt_mt6(const struct sk_buff *skb, struct xt_action_param *par) unsigned int i = 0; for (temp = 0; - temp < (unsigned int)((hdrlen - 8) / 16); + temp < (unsigned int)((hdrlen - 8) / 16) && + i < rtinfo->addrnr; temp++) { ap = skb_header_pointer(skb, ptr @@ -112,8 +113,6 @@ static bool rt_mt6(const struct sk_buff *skb, struct xt_action_param *par) if (ipv6_addr_equal(ap, &rtinfo->addrs[i])) i++; - if (i == rtinfo->addrnr) - break; } if (i == rtinfo->addrnr) return ret; @@ -162,6 +161,12 @@ static int rt_mt6_check(const struct xt_mtchk_param *par) pr_info_ratelimited("too many addresses specified\n"); return -EINVAL; } + + if ((rtinfo->flags & IP6T_RT_FST_MASK) && !rtinfo->addrnr) { + pr_info_ratelimited("address list match requested but addrnr is 0\n"); + return -EINVAL; + } + if ((rtinfo->flags & (IP6T_RT_RES | IP6T_RT_FST_MASK)) && (!(rtinfo->flags & IP6T_RT_TYP) || (rtinfo->rt_type != 0) || From a311a898172743558b82f6035ef2aa8c310a4223 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 22:02:28 +0200 Subject: [PATCH 1991/2366] netfilter: nft_synproxy: use the family-aware checksum helper nft_synproxy_do_eval() verifies the TCP checksum before it switches on skb->protocol. It uses nf_ip_checksum(), which constructs an IPv4 pseudo header and relies on the IPv4 header checksum when folding the whole skb. Neither operation is valid for an IPv6 packet. A correctly checksummed IPv6 segment can therefore fail verification when it reaches the hook as CHECKSUM_NONE or, at NF_INET_LOCAL_IN, CHECKSUM_COMPLETE. nft_synproxy_do_eval() returns NF_DROP before nft_synproxy_eval_v6() can send a SYN-ACK. nft_synproxy_validate() deliberately admits NFPROTO_IPV6 and NFPROTO_INET, and the xtables counterpart ip6t_SYNPROXY.c already calls nf_ip6_checksum(). Use nf_checksum() with nft_pf() so the checksum helper dispatches to the packet family's implementation. Fixes: ad49d86e07a4 ("netfilter: nf_tables: Add synproxy support") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nft_synproxy.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nft_synproxy.c b/net/netfilter/nft_synproxy.c index 9ed288c9d168..554a96a000f4 100644 --- a/net/netfilter/nft_synproxy.c +++ b/net/netfilter/nft_synproxy.c @@ -118,7 +118,8 @@ static void nft_synproxy_do_eval(const struct nft_synproxy *priv, return; } - if (nf_ip_checksum(skb, nft_hook(pkt), thoff, IPPROTO_TCP)) { + if (nf_checksum(skb, nft_hook(pkt), thoff, IPPROTO_TCP, + nft_pf(pkt))) { regs->verdict.code = NF_DROP; return; } From e290145564886d6a3038810c621f738c1fe9fa51 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Fri, 11 Sep 2026 14:43:15 +0300 Subject: [PATCH 1992/2366] ipvs: revalidate ihl before icmp_send While the outer IP header is already pulled into the skb head, we must be careful and revalidate the embedded headers after reading them from the skb frags to prevent possible out-of-bounds access. One such place reported by Sashiko is ip_vs_in_icmp() where local process can change the ihl field and after pskb_may_pull() we can see larger value. Even if icmp_send() has checks to prevent out-of-bounds access, play safe and add check to drop the packet if the ihl field is changed. As the outer headers are pulled, make sure the transport header is updated too, it was used before commit 7fcc2fe39fed ("net: icmp: avoid invalid transport header access in icmp_send tracepoint") Fixes: f2edb9f7706d ("ipvs: implement passive PMTUD for IPIP packets") Link: https://sashiko.dev/#/patchset/20260806105211.34622-1-ja%40ssi.bg Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_core.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index ba0957798bad..fd503f0efb57 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -1960,6 +1960,12 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* Ensure the IP header is present in headroom */ if (!pskb_may_pull(skb, hlen_orig)) goto ignore_tunnel; + skb_set_transport_header(skb, hlen_orig); + /* Before now we may used ihl from skb frag, revalidate it after + * copying it into skb head to prevent out-of-bounds access + */ + if (ip_hdr(skb)->ihl * 4 != hlen_orig) + goto ignore_tunnel; IP_VS_DBG(12, "Sending ICMP for %pI4->%pI4: t=%u, c=%u, i=%u\n", &ip_hdr(skb)->saddr, &ip_hdr(skb)->daddr, type, code, ntohl(info)); From 207d591c353201f3bd3e0c89bb7d44a849c8fd59 Mon Sep 17 00:00:00 2001 From: Naman Gulati Date: Sat, 12 Sep 2026 01:10:51 +0000 Subject: [PATCH 1993/2366] netfilter: ctnetlink: fix suspicious RCU usage in expect_iter_name expect_iter_name() is invoked by nf_ct_expect_iterate_net() under spin_lock_bh(&nf_conntrack_expect_lock). It does not hold rcu_read_lock(). When accessing exp->helper with rcu_dereference() in syzbot's report, lockdep warns: ============================= WARNING: suspicious RCU usage syzkaller #0 Not tainted ----------------------------- net/netfilter/nf_conntrack_netlink.c:3393 suspicious rcu_dereference_check() usage! locks held by syz-executor381/5628: 2, last CPU#1: #0: ffffffff9aee42a0 (nfnl_subsys_ctnetlink_exp){+.+.}-{4:4}, at: nfnetlink_rcv_msg+0xa69/0x12b0 #1: ffffffff8ea74d58 (nf_conntrack_expect_lock){+...}-{3:3}, at: nf_ct_expect_iterate_net+0x38/0x180 Call Trace: dump_stack_lvl+0xe8/0x150 lockdep_rcu_suspicious+0x140/0x1d0 expect_iter_name+0xfb/0x100 nf_ct_expect_iterate_net+0xf2/0x180 ctnetlink_del_expect+0x45d/0x640 nfnetlink_rcv_msg+0xcc2/0x12b0 netlink_rcv_skb+0x226/0x4a0 nfnetlink_rcv+0x2b9/0x28c0 netlink_unicast+0x7bd/0x940 netlink_sendmsg+0x813/0xb40 ____sys_sendmsg+0x54e/0x850 ___sys_sendmsg+0x2a5/0x360 __sys_sendmsg+0x2a5/0x360 do_syscall_64+0x166/0x520 entry_SYSCALL_64_after_hwframe+0x77/0x7f Use rcu_dereference_protected() with lockdep_is_held() on nf_conntrack_expect_lock instead, similar to expect_iter_me() in nf_conntrack_helper.c. Fixes: f01794106042 ("netfilter: nf_conntrack_expect: use expect->helper") Reported-by: syzbot+4bd730aede2791e40bdf@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6aa4a377.f81106d8.2ab401.0024.GAE@google.com/T/#u Signed-off-by: Naman Gulati Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_conntrack_netlink.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index 579ada063b1b..4e5d7c701436 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -3392,7 +3392,8 @@ static bool expect_iter_name(struct nf_conntrack_expect *exp, void *data) struct nf_conntrack_helper *helper; const char *name = data; - helper = rcu_dereference(exp->helper); + helper = rcu_dereference_protected(exp->helper, + lockdep_is_held(&nf_conntrack_expect_lock)); if (!helper) return false; From 4f948b5949d2e418b4506752e7c514fe91bd408b Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Fri, 18 Sep 2026 11:19:59 +0200 Subject: [PATCH 1994/2366] binfmt_misc: fix OOB read in bpf_binprm_select_interp() bpf_binprm_select_interp() checks the name its load program passes with strnlen(name, name__sz) and then hands the same buffer to binfmt_misc_find_interp(), which compares it with an unbounded strcmp(). The buffer can be a BPF map value that another CPU rewrites between the two reads. If the terminating NUL is overwritten in that window, strcmp() reads past the name__sz bytes the verifier checked. That is an out-of-bounds read of up to 31 bytes of whatever follows the checked name__sz bytes. The verifier checks the name and name__sz pair with BPF_READ | BPF_WRITE, so a writable array map value is an accepted argument. bpf(BPF_MAP_UPDATE_ELEM) on an array map copies the new value over the old one in place and takes no lock. The NUL that strnlen() finds can be overwritten before strcmp() reads the buffer again: CPU0 CPU1 bpf_binprm_select_interp() strnlen(name, name__sz) finds the NUL inside name__sz bpf(BPF_MAP_UPDATE_ELEM) array_map_update_elem() copy_map_value() overwrites the NUL binfmt_misc_find_interp() strcmp(interp->name, name) reads past name__sz strnlen() proves that a NUL lies inside name__sz only at the moment it runs. The map update on CPU1 takes no lock, so it can store over the NUL right after. The lookup on CPU0 then walks the live buffer again, once per bound interpreter: fs/binfmt_misc.c:binfmt_misc_find_interp list_for_each_entry(interp, interps, list) if (!strcmp(interp->name, name)) return interp; strcmp() stops at the first mismatch or at the end of interp->name. bm_entry_add_interp() caps a bound name at BINFMT_MISC_INTERP_NAME_MAX (32) bytes, so strcmp() reads at most 33 bytes of name. The smallest name__sz the kfunc accepts is 2, which leaves up to 31 bytes read beyond the checked extent. The handler's own load program has to pass a writable map value, and something has to store into it while the kfunc runs. The window between strnlen() and strcmp() is short, but with a BPF_F_MMAPABLE array the store is a plain user space write into the mapped value, so a loop can hit it without a single bpf() call. Copy the name into a stack buffer of BINFMT_MISC_INTERP_NAME_MAX + 1 bytes, terminate it, and look up the copy. The memcpy() length is below name__sz, so the copy stays inside the extent the verifier checked, and the BPF buffer is not read again afterwards. Return -ENOENT first for a name longer than BINFMT_MISC_INTERP_NAME_MAX. bm_entry_add_interp() rejects a longer name, and the only other binding site attaches the empty name. No entry can bind such a name, so that lookup already ended in -ENOENT and no result changes. Check the first byte of the copy and return -EINVAL if it is NUL, as the existing "!len" test does for an empty name. Only an 'F' entry binds the empty name and a 'B' entry cannot carry 'F', so without that check a racing store of NUL to byte 0 would look up a name no entry binds and end in -ENOENT rather than -EINVAL. A NUL stored further into the name only shortens it to another name the program could have passed anyway. binfmt_misc_find_interp() itself is left alone: entry_attach_interpreter() calls it with a kernel string, and this kfunc now calls it with a private copy. Fixes: 6ec7c96bee30 ("binfmt_misc: let a 'B' entry bind its interpreters") Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260918-work-binfmt_misc-fixes-v1-1-647b24bc1c46@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc_bpf.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c index 91576ff05911..ce1bc78e8511 100644 --- a/fs/binfmt_misc_bpf.c +++ b/fs/binfmt_misc_bpf.c @@ -176,6 +176,7 @@ __bpf_kfunc int bpf_binprm_select_interp(struct linux_binprm *bprm, const char *name, size_t name__sz) { const struct binfmt_misc_interp *interp; + char buf[BINFMT_MISC_INTERP_NAME_MAX + 1]; size_t len; char *path; @@ -184,8 +185,20 @@ __bpf_kfunc int bpf_binprm_select_interp(struct linux_binprm *bprm, len = strnlen(name, name__sz); if (len == name__sz || !len) return -EINVAL; + /* No entry binds a longer name, so it cannot be found. */ + if (len > BINFMT_MISC_INTERP_NAME_MAX) + return -ENOENT; - interp = binfmt_misc_find_interp(bprm->bpf_interps, name); + /* + * The program may pass memory that is written to while this runs, + * so look the name up in a private copy and check that instead. + */ + memcpy(buf, name, len); + buf[len] = '\0'; + if (!buf[0]) + return -EINVAL; + + interp = binfmt_misc_find_interp(bprm->bpf_interps, buf); if (!interp) return -ENOENT; From 1970fc4ecb52dabce2e57f9be721964b936a052d Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Fri, 18 Sep 2026 11:20:00 +0200 Subject: [PATCH 1995/2366] binfmt_misc: fix racy checks in bpf set_interp kfuncs bpf_binprm_set_interp() tests path[0] != '/' on the buffer its load program passes and then reads the same buffer again to copy it with kmemdup_nul(). The buffer can be a BPF map value that another CPU rewrites between the two reads. If byte 0 is overwritten in that window, the kfunc stages a relative or empty interpreter path. The staged path is not checked again, so open_exec() resolves a relative path against the working directory of the task doing the exec. bpf_binprm_set_interp_arg() has the same pattern for its "!len" test and can stage an empty argument, which the interpreter then receives as an empty argv entry. The verifier checks the path and path__sz pair with BPF_READ | BPF_WRITE, so a writable array map value is an accepted argument. bpf(BPF_MAP_UPDATE_ELEM) on an array map copies the new value over the old one in place and takes no lock. Both kfuncs are KF_SLEEPABLE and allocate with GFP_KERNEL between the test and the copy, so the task can sleep inside the window: load program bpf(BPF_MAP_UPDATE_ELEM) bpf_binprm_set_interp() strnlen(path, path__sz) path[0] != '/' is false kmemdup_nul(path, len, GFP_KERNEL) allocation may sleep array_map_update_elem() copy_map_value() rewrites byte 0 copy reads path again bm_bpf_stage_selection() The test in the load program's column proves what byte 0 held only at the moment the test ran. The map update takes no lock, so it can store to byte 0 right after. kmemdup_nul() then copies the rewritten bytes, and bm_bpf_stage_selection() publishes them as bprm->bpf_interp. The staged path is not checked again on its way to open_exec(): load_misc_binary() entry_select_interpreter() returns bprm->bpf_interp unchanged build_interp_argv() copy_string_kernel() copies it as argv[0] bprm_change_interp() kstrdup() entry_open_interpreter() open_exec() unless a bound file is staged or the entry is an 'F' entry None of these functions tests the first byte, and load_misc_binary() hands the pointer to nothing else. In bpf_binprm_set_interp_arg(), strnlen() finds a non-zero len, a NUL is then stored to byte 0, and build_interp_argv() later copies the empty bprm->bpf_interp_arg with copy_string_kernel(). The handler's own load program has to pass a writable map value, and something has to store into it while the kfunc runs. The allocation can sleep inside the window, and with a BPF_F_MMAPABLE array the store is a plain user space write into the mapped value, so a loop can hit it without a single bpf() call. Check the private copy in both kfuncs, so that the string that gets staged is the string that was checked. bpf_binprm_select_interp() already looks its name up in a private copy for the same reason. The remaining tests work on path__sz, arg__sz or the local len, and the copy length is len, so the copy stays inside the extent the verifier checked. Results of bpf_binprm_set_interp() with the check on the copy: - A NUL stored to byte 0 fails interp[0] != '/' and gets -EINVAL. - For len == 0, kmemdup_nul() returns an empty string, so an empty path still gets -EINVAL. - A NUL stored further into the string only shortens it to another absolute path, or another non-empty argument, that the program could have passed anyway. - A path that both lacks the leading '/' and is PATH_MAX or longer now gets -ENAMETOOLONG instead of -EINVAL. - A path that is empty or lacks the leading '/' is now rejected after the copy rather than before it, so such a call makes an allocation and returns -ENOMEM instead of -EINVAL if that allocation fails. bpf_binprm_set_interp_arg() still rejects an empty argument before allocating, so its results are unchanged apart from the raced case fixed here. Both new checks run before the previously staged string is freed or replaced. A failing call frees only its own allocation and leaves the earlier selection in place, as the -ENOMEM path already does. Fixes: b4bfe2f6b011 ("binfmt_misc: add binfmt_misc_ops bpf struct_ops") Signed-off-by: Chris Mason Link: https://patch.msgid.link/20260918-work-binfmt_misc-fixes-v1-2-647b24bc1c46@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc_bpf.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c index ce1bc78e8511..a3e26e8a4027 100644 --- a/fs/binfmt_misc_bpf.c +++ b/fs/binfmt_misc_bpf.c @@ -141,8 +141,6 @@ __bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm, len = strnlen(path, path__sz); if (len == path__sz) return -EINVAL; - if (path[0] != '/') - return -EINVAL; if (len >= PATH_MAX) return -ENAMETOOLONG; @@ -150,6 +148,15 @@ __bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm, if (!interp) return -ENOMEM; + /* + * The program may pass memory that is written to while this runs, + * so check the private copy and not the buffer it was made from. + */ + if (interp[0] != '/') { + kfree(interp); + return -EINVAL; + } + bm_bpf_stage_selection(bprm, interp, NULL); return 0; } @@ -241,6 +248,15 @@ __bpf_kfunc int bpf_binprm_set_interp_arg(struct linux_binprm *bprm, if (!val) return -ENOMEM; + /* + * The program may pass memory that is written to while this runs, + * so check the private copy and not the buffer it was made from. + */ + if (!val[0]) { + kfree(val); + return -EINVAL; + } + kfree(bprm->bpf_interp_arg); bprm->bpf_interp_arg = val; return 0; From 70194dc37670bd08e44b471389861cc01bd3a3c9 Mon Sep 17 00:00:00 2001 From: Aohan Mei Date: Mon, 14 Sep 2026 19:51:47 +0800 Subject: [PATCH 1996/2366] netfilter: nf_tables: skip expired catchall elements on insert and delete nft_setelem_catchall_insert() looks up duplicates with nft_set_elem_active() only, while nft_set_catchall_lookup() and the dump path additionally skip expired elements. Once a catchall element with a timeout expires, this predicate drift makes it invisible to userspace dumps, yet it still blocks re-insertion: with NLM_F_EXCL the request fails with -EEXIST, and without it the request reports success but silently inserts nothing. The stale entry only goes away when the (user-tunable) gc interval elapses, so the catchall rule may silently stop matching for an arbitrarily long time after its first expiration. The delete path shows the same drift: nft_setelem_catchall_deactivate() picks the first active-next entry in the catchall list, so with an expired entry still pending GC it retires the stale entry instead of the fresh one, and it deactivates an element that userspace no longer sees instead of failing with -ENOENT. Align both walks with the lookup and dump predicates: only an element that is active and not expired counts as a duplicate or delete candidate, using the per-netns timestamp taken at transaction start, in line with the set backend .insert/.deactivate and catchall GC sync paths. Reported-by: TencentOS Corvus AI Cc: stable@vger.kernel.org Fixes: aaa31047a6d2 ("netfilter: nftables: add catch-all set element support") Assisted-by: CodeBuddy:Kimi-K3 Signed-off-by: Aohan Mei Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_tables_api.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index c0b754a2d45b..b59628e6240c 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -6995,11 +6995,14 @@ static int nft_setelem_catchall_insert(const struct net *net, { struct nft_set_elem_catchall *catchall; u8 genmask = nft_genmask_next(net); + u64 tstamp = nft_net_tstamp(net); struct nft_set_ext *ext; list_for_each_entry(catchall, &set->catchall_list, list) { ext = nft_set_elem_ext(set, catchall->elem); - if (nft_set_elem_active(ext, genmask)) { + if (nft_set_elem_active(ext, genmask) && + !__nft_set_elem_expired(ext, tstamp) && + !nft_set_elem_is_dead(ext)) { *priv = catchall->elem; return -EEXIST; } @@ -7092,11 +7095,14 @@ static int nft_setelem_catchall_deactivate(const struct net *net, struct nft_set_elem *elem) { struct nft_set_elem_catchall *catchall; + u64 tstamp = nft_net_tstamp(net); struct nft_set_ext *ext; list_for_each_entry(catchall, &set->catchall_list, list) { ext = nft_set_elem_ext(set, catchall->elem); - if (!nft_is_active_next(net, ext)) + if (!nft_is_active_next(net, ext) || + __nft_set_elem_expired(ext, tstamp) || + nft_set_elem_is_dead(ext)) continue; kfree(elem->priv); From 88aed0422f39b22406f35f1e758cea25e7bbcfb5 Mon Sep 17 00:00:00 2001 From: Vinay Belgaumkar Date: Fri, 4 Sep 2026 11:16:24 -0700 Subject: [PATCH 1997/2366] perf: Fix null pointer access in is_include_guest_event() A typical module unload occurring event when there is an active perf connection leads to freeing of the pmu pointer. The call log is something like: .. __pmu_detach_event pmu_detach_event pmu_detach_events perf_pmu_unregister .. __pmu_detach_event() sets event->pmu to null. When the perf connection finally is closed, the following stack trace is observed: Oops: general protection fault, kernel NULL pointer dereference ... RIP: 0010:_free_event+0x3e/0x370 ... Call Trace: ... perf_event_release_kernel+0x260/0x2d0 perf_release+0x12/0x20 A call to mediated_pmu_unaccount_event() inside _free_event() is the root cause of this crash. Adding a check inside is_include_guest_event() ensures we don't accidentally access a null pmu ptr. In addition to this, we will now call mediated_pmu_unaccount_event() before clearing the pmu ptr so that nr_include_guest_events counts are maintained correctly. Fixes: eff95e170275 ("perf: Add APIs to create/release mediated guest vPMUs") Assisted-by: Claude:Claude-Sonnet-5 Signed-off-by: Vinay Belgaumkar Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260904181625.1394082-1-vinay.belgaumkar@intel.com --- kernel/events/core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/events/core.c b/kernel/events/core.c index fe33fe15689d..db7b76d6b68a 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -6350,6 +6350,9 @@ static DEFINE_MUTEX(perf_mediated_pmu_mutex); /* !exclude_guest event of PMU with PERF_PMU_CAP_MEDIATED_VPMU */ static inline bool is_include_guest_event(struct perf_event *event) { + if (!event->pmu) + return false; + if ((event->pmu->capabilities & PERF_PMU_CAP_MEDIATED_VPMU) && !event->attr.exclude_guest) return true; @@ -13002,6 +13005,7 @@ static void __pmu_detach_event(struct pmu *pmu, struct perf_event *event, exclusive_event_destroy(event); module_put(pmu->module); + mediated_pmu_unaccount_event(event); event->pmu = NULL; /* force fault instead of UAF */ } From fe3c73d7bc769e7afc252f867a3421fe168b898d Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 15 Sep 2026 20:41:01 +0200 Subject: [PATCH 1998/2366] sched/core: Avoid false migration warning for proxy donors Proxy execution can move a blocked donor's scheduling context to the lock owner's CPU even when the donor is migration-disabled. The donor does not execute there, and its original execution CPU remains recorded in wake_cpu. set_task_cpu() warns unconditionally for migration-disabled tasks, so a subsequent proxy migration or the wakeup path returning the donor home triggers a false positive: moving a blocked scheduling context does not violate the migration-disabled execution context. For example, creating a mutex owner on CPU1 and a migration-disabled waiter on CPU0 can trigger the following warning: proxy_migrate_repro: donor blocking on CPU0 with migration disabled proxy_migrate_repro: donor moved from CPU0 to CPU1 WARNING: kernel/sched/core.c:3389 at set_task_cpu+0x1d3/0x280 ... Call Trace: try_to_wake_up+0x43f/0x780 __mutex_unlock_slowpath+0x330/0x540 owner_fn+0x9f/0xc0 [proxy_migrate_repro] ... proxy_migrate_repro: donor woke on CPU0, task_cpu=0 proxy_migrate_repro: completed Exclude blocked proxy donors from the warning. The proxy wakeup path restores an executable placement before clearing the blocked state. Fixes: b049b81bdff6 ("sched: Handle blocked-waiter migration (and return migration)") Signed-off-by: Andrea Righi Signed-off-by: Peter Zijlstra (Intel) Acked-by: John Stultz Link: https://patch.msgid.link/20260915184101.2621252-1-arighi@nvidia.com --- kernel/sched/core.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 7885ff76e69f..0b846a13c628 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -3351,6 +3351,8 @@ void relax_compatible_cpus_allowed_ptr(struct task_struct *p) void set_task_cpu(struct task_struct *p, unsigned int new_cpu) { unsigned int state = READ_ONCE(p->__state); + bool proxy_migrated = sched_proxy_exec() && p->is_blocked && + task_cpu(p) != p->wake_cpu; /* * We should never call set_task_cpu() on a blocked task, @@ -3386,7 +3388,12 @@ void set_task_cpu(struct task_struct *p, unsigned int new_cpu) */ WARN_ON_ONCE(!cpu_online(new_cpu)); - WARN_ON_ONCE(is_migration_disabled(p)); + /* + * Proxy execution can move a blocked task's scheduling context to any + * CPU without moving its migration-disabled execution context. The + * wakeup path will return the task to a CPU where it can execute. + */ + WARN_ON_ONCE(is_migration_disabled(p) && !proxy_migrated); trace_sched_migrate_task(p, new_cpu); From 93f53499d0b945e8ae447f497faf743d60069f61 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Thu, 17 Sep 2026 16:09:06 -0700 Subject: [PATCH 1999/2366] x86/fred: Reconstruct the #GP context for rejected INT instructions FRED event delivery does not use the IDT, so the gate DPL check that rejects a user INT n falls to software (Intel FRED specification [1], section 8.3). fred_intx() rejects the same vectors as IDT delivery, but reports a zero error code and the IP after the INT. This breaks the signal ABI. Wine uses the error code to recognize INT 0x2d, so the changed context turns a handled breakpoint into an access violation in Elden Ring. Rewind IP using the instruction length in the augmented SS and synthesize the IDT selector error code, (vector << 3) | 2. Set RF in the saved flags, as the CPU does for a #GP fault. Section 5.2.1 defines the saved vector, instruction length and RF state. The supplied length handles prefixes without reading user memory. Limit the changes to already-rejected software interrupts, preserving the accepted INT3, INT4 and enabled INT80 paths and hardware exceptions. With IA32 emulation disabled, INT 0x80 now reports the same #GP as the DPL 0 gate IDT installs there. The rewound IP also stops fixup_iopl_exception() from inspecting the byte after the INT. Also clear the software event flag. Section 6.2.3 specifies that ERETU with this flag and TF set traps before executing any user instruction. A tracer that suppresses SIGSEGV and resumes with TF set expects the next instruction to run first, as after IRET. The sigreturn path clears the same flag for this reason in prevent_single_step_upon_eretu(). [1] Intel Flexible Return and Event Delivery (FRED) Specification, revision 9.0 (346446-009US), sections 5.2.1, 6.2.3 and 8.3. Fixes: 14619d912b65 ("x86/fred: FRED entry/exit and dispatch code") Closes: https://gitlab.freedesktop.org/mesa/mesa/-/work_items/15745 Closes: https://gitlab.freedesktop.org/mesa/mesa/-/work_items/16132 Reported-by: Paul Gofman Signed-off-by: Matthew Schwartz Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: H. Peter Anvin Link: https://cdrdv2.intel.com/v1/dl/getContent/678938 # [1] Link: https://patch.msgid.link/20260917230907.2080792-2-matthew.schwartz@linux.dev --- arch/x86/entry/entry_fred.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/x86/entry/entry_fred.c b/arch/x86/entry/entry_fred.c index fb3594ddf731..854899bfec5d 100644 --- a/arch/x86/entry/entry_fred.c +++ b/arch/x86/entry/entry_fred.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -71,7 +72,15 @@ static noinstr void fred_intx(struct pt_regs *regs) #endif default: - return exc_general_protection(regs, 0); + /* + * Reconstruct the #GP fault state that IDT delivery would produce. + * Clear the software event flag so ERETU with TF set does not trap + * before the resumed instruction. See prevent_single_step_upon_eretu(). + */ + regs->ip -= regs->fred_ss.insnlen; + regs->flags |= X86_EFLAGS_RF; + regs->fred_ss.swevent = 0; + return exc_general_protection(regs, (regs->fred_ss.vector << 3) | 2); } } From 96443a53bc3ef4b67dab0c497878fc8d56f799f3 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Thu, 17 Sep 2026 16:09:07 -0700 Subject: [PATCH 2000/2366] selftests/x86: Check signal state for rejected software interrupts Add a test of the signal ABI for INT instructions in both 32-bit and 64-bit processes. Check the signal number, trap number, error code, si_code, si_addr, instruction pointer and RF/TF state against legacy IDT behavior. Include a 15-byte prefixed INT to check that IP uses the hardware instruction length. Exercise both INT3 encodings, INT4, UD2 and HLT to cover the unchanged trap and fault paths. Run each instruction with TF clear and set. Resume at a known NOP after handling the signal and check that single-stepping traps after the NOP. Also drive INT 0x2d under ptrace, which resumes through the fault frame rather than sigreturn and so exposes a stale FRED software event flag. Start from an INT3 stop, whose FRED frame has no software event flag, instead of the syscall frame of raise(SIGSTOP). Single-step into the INT and check that the fault reports its address. Then suppress SIGSEGV and resume at the NOP, once with PTRACE_SINGLESTEP and once with PTRACE_CONT and TF set. Section 6.2.3 of the Intel FRED specification [1] specifies the immediate single-step trap caused by returning with both that flag and TF set. Check that each trap occurs after the NOP, rather than at its address. Report whether the CPU supports FRED, since a pass looks the same on either entry path. INT 0x80 with IA32 emulation disabled and a 64-bit tracer of a 32-bit tracee are not covered. Both variants pass all 29 checks on a non-FRED AMD host and on Panther Lake with FRED enabled and the fix applied. With the same binaries on unpatched Panther Lake, 16 signal-context checks fail and the first ptrace check reports the IP after the INT. The two dependent ptrace resume checks are not reached. [1] Intel Flexible Return and Event Delivery (FRED) Specification, revision 9.0 (346446-009US), section 6.2.3. Signed-off-by: Matthew Schwartz Signed-off-by: Peter Zijlstra (Intel) Link: https://cdrdv2.intel.com/v1/dl/getContent/678938 # [1] Link: https://patch.msgid.link/20260917230907.2080792-3-matthew.schwartz@linux.dev --- tools/testing/selftests/x86/Makefile | 2 +- tools/testing/selftests/x86/int_signal.c | 311 +++++++++++++++++++++++ 2 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/x86/int_signal.c diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile index 434065215d12..d478b13cc8d5 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile @@ -13,7 +13,7 @@ CAN_BUILD_WITH_NOPIE := $(shell ./check_cc.sh "$(CC)" trivial_program.c -no-pie) TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt test_mremap_vdso \ check_initial_reg_state sigreturn iopl ioperm \ test_vsyscall mov_ss_trap sigtrap_loop \ - syscall_arg_fault fsgsbase_restore sigaltstack + syscall_arg_fault fsgsbase_restore sigaltstack int_signal TARGETS_C_BOTHBITS += nx_stack TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \ test_FCMOV test_FCOMI test_FISTTP \ diff --git a/tools/testing/selftests/x86/int_signal.c b/tools/testing/selftests/x86/int_signal.c new file mode 100644 index 000000000000..22676dac72b5 --- /dev/null +++ b/tools/testing/selftests/x86/int_signal.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Check the signal context for INT instructions with IDT and FRED entry. */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "helpers.h" + +#ifdef __x86_64__ +#define REG_IP REG_RIP +#define USER_IP rip +#define STACK_PTR "%rsp" +#else +#define REG_IP REG_EIP +#define USER_IP eip +#define STACK_PTR "%esp" +#endif + +/* + * Each instruction has normal and single-step entry points. Resume at the + * NOP after handling its signal, then expect a trace trap after that NOP + * when TF is set. Explicit labels avoid assuming the kernel's saved IP. + */ +#define PROBE(name, insn) \ + extern void name(void); \ + extern void name##_tf(void); \ + extern const char name##_end[], name##_step[]; \ + asm(".pushsection .text\n" \ + ".globl " #name "_tf\n" \ + ".type " #name "_tf, @function\n" \ + #name "_tf:\n" \ + "pushf\n" \ + "orl $0x100, (" STACK_PTR ")\n" \ + "popf\n" \ + ".globl " #name "\n" \ + ".type " #name ", @function\n" \ + #name ":\n" insn "\n" \ + ".globl " #name "_end\n" \ + #name "_end:\nnop\n" \ + ".globl " #name "_step\n" \ + #name "_step:\nret\n" \ + ".size " #name ", .-" #name "\n" \ + ".size " #name "_tf, .-" #name "_tf\n" \ + ".popsection\n") + +PROBE(int1, ".byte 0xcd, 0x01"); +PROBE(int29, ".byte 0xcd, 0x29"); +PROBE(int2c, ".byte 0xcd, 0x2c"); +PROBE(int2d, ".byte 0xcd, 0x2d"); +PROBE(prefixed_int2d, ".byte 0x66, 0xcd, 0x2d"); +PROBE(long_int2d, ".fill 13, 1, 0x2e\n.byte 0xcd, 0x2d"); +PROBE(int81, ".byte 0xcd, 0x81"); +PROBE(intff, ".byte 0xcd, 0xff"); +PROBE(short_int3, ".byte 0xcc"); +PROBE(long_int3, ".byte 0xcd, 0x03"); +PROBE(int4, ".byte 0xcd, 0x04"); +PROBE(ud2, ".byte 0x0f, 0x0b"); +PROBE(hlt, ".byte 0xf4"); + +struct test { + const char *name; + void (*run)(void); + void (*run_tf)(void); + const char *end, *step; + int signo, trap, error, ip_offset, flags, code; +}; + +#define TEST(name, sig, trap, error, offset, flags, code) \ + { #name, name, name##_tf, name##_end, name##_step, \ + sig, trap, error, offset, flags, code } + +#define GP(name, error) \ + TEST(name, SIGSEGV, 13, error, 0, X86_EFLAGS_RF, SI_KERNEL) + +static const struct test tests[] = { + GP(int1, 0x00a), + GP(int29, 0x14a), + GP(int2c, 0x162), + GP(int2d, 0x16a), + GP(prefixed_int2d, 0x16a), + GP(long_int2d, 0x16a), + GP(int81, 0x40a), + GP(intff, 0x7fa), + GP(hlt, 0), + TEST(short_int3, SIGTRAP, 3, 0, 1, 0, SI_KERNEL), + TEST(long_int3, SIGTRAP, 3, 0, 2, 0, SI_KERNEL), + TEST(int4, SIGSEGV, 4, 0, 2, 0, SI_KERNEL), + TEST(ud2, SIGILL, 6, 0, 0, X86_EFLAGS_RF, ILL_ILLOPN), +}; + +static const struct test *active; +static volatile sig_atomic_t seen, signo, trap, error, ip_offset, flags; +static volatile sig_atomic_t code, addr_ok, single_step, stepped, step_ok; + +static void handler(int sig, siginfo_t *info, void *context) +{ + ucontext_t *uc = context; + uintptr_t ip = uc->uc_mcontext.gregs[REG_IP]; + uintptr_t start = (uintptr_t)active->run; + uintptr_t end = (uintptr_t)active->end; + + if (seen && single_step && sig == SIGTRAP) { + if (stepped++) { + ksft_print_msg("%s: second trace trap at %#lx\n", + active->name, (unsigned long)ip); + _exit(KSFT_FAIL); + } + step_ok = ip == (uintptr_t)active->step && + uc->uc_mcontext.gregs[REG_TRAPNO] == 1 && + info->si_code == TRAP_TRACE; + uc->uc_mcontext.gregs[REG_EFL] &= ~X86_EFLAGS_TF; + return; + } + + if (seen || ip < start || ip > end) { + ksft_print_msg("%s: unexpected signal %d at %#lx\n", + active->name, sig, (unsigned long)ip); + _exit(KSFT_FAIL); + } + + signo = sig; + trap = uc->uc_mcontext.gregs[REG_TRAPNO]; + error = uc->uc_mcontext.gregs[REG_ERR]; + ip_offset = ip - start; + flags = uc->uc_mcontext.gregs[REG_EFL] & (X86_EFLAGS_RF | X86_EFLAGS_TF); + code = info->si_code; + /* force_sig() reports no address, force_sig_fault() reports the IP. */ + addr_ok = info->si_addr == (code == SI_KERNEL ? NULL : (void *)ip); + seen = 1; + uc->uc_mcontext.gregs[REG_IP] = end; +} + +static void wait_for_child(pid_t child, int *status) +{ + pid_t ret; + + do { + ret = waitpid(child, status, 0); + } while (ret < 0 && errno == EINTR); + if (ret != child) + ksft_exit_fail_perror("waitpid"); +} + +/* Resume the tracee and check where the next stop lands. */ +static bool resume_to(pid_t child, int *status, int request, int sig, + const void *ip, const char *what) +{ + struct user_regs_struct regs; + + if (ptrace(request, child, 0, 0)) + return false; + wait_for_child(child, status); + if (!WIFSTOPPED(*status)) { + ksft_print_msg("%s: tracee did not stop\n", what); + return false; + } + if (WSTOPSIG(*status) != sig) { + ksft_print_msg("%s: stopped with signal %d, expected %d\n", + what, WSTOPSIG(*status), sig); + return false; + } + if (ptrace(PTRACE_GETREGS, child, 0, ®s)) + return false; + if ((unsigned long)regs.USER_IP != (unsigned long)ip) { + ksft_print_msg("%s: stopped at %#lx, expected %#lx\n", what, + (unsigned long)regs.USER_IP, (unsigned long)ip); + return false; + } + return true; +} + +static bool set_ip(pid_t child, const void *ip, bool tf) +{ + struct user_regs_struct regs; + + if (ptrace(PTRACE_GETREGS, child, 0, ®s)) + return false; + regs.USER_IP = (unsigned long)ip; + if (tf) + regs.eflags |= X86_EFLAGS_TF; + return !ptrace(PTRACE_SETREGS, child, 0, ®s); +} + +/* + * Exercise the tracer paths that resume through the fault frame rather than + * sigreturn. A stale FRED software event flag on that frame traps before the + * NOP executes instead of after it. + */ +static void test_ptrace(void) +{ + bool into = false, step = false, cont = false; + pid_t child; + int status; + + child = fork(); + if (child < 0) + ksft_exit_fail_perror("fork"); + if (!child) { + if (ptrace(PTRACE_TRACEME, 0, 0, 0)) + _exit(KSFT_FAIL); + /* Start from a breakpoint frame, not the syscall frame of raise(). */ + asm volatile("int3"); + _exit(KSFT_FAIL); + } + + wait_for_child(child, &status); + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) + goto out; + if (ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_EXITKILL)) + goto out; + + /* Single-step into the INT. The fault must report the INT's address. */ + if (!set_ip(child, int2d, false)) + goto out; + into = resume_to(child, &status, PTRACE_SINGLESTEP, SIGSEGV, int2d, + "single-step into INT"); + if (!into) + goto out; + + /* Suppress SIGSEGV and single-step the NOP. */ + if (!set_ip(child, int2d_end, false)) + goto out; + step = resume_to(child, &status, PTRACE_SINGLESTEP, SIGTRAP, int2d_step, + "single-step after INT"); + if (!step) + goto out; + + /* Fault again, then suppress SIGSEGV and continue with TF set. */ + if (!set_ip(child, int2d, false)) + goto out; + if (!resume_to(child, &status, PTRACE_CONT, SIGSEGV, int2d, + "continue to INT")) + goto out; + if (!set_ip(child, int2d_end, true)) + goto out; + cont = resume_to(child, &status, PTRACE_CONT, SIGTRAP, int2d_step, + "continue with TF after INT"); +out: + if (WIFSTOPPED(status)) { + kill(child, SIGKILL); + wait_for_child(child, &status); + } + ksft_test_result(into, "ptrace single-step into INT faults at the INT\n"); + ksft_test_result(step, "ptrace single-step after suppressing SIGSEGV\n"); + ksft_test_result(cont, "ptrace continue with TF after suppressing SIGSEGV\n"); +} + +static bool cpu_has_fred(void) +{ + unsigned int eax, ebx, ecx, edx; + + if (__get_cpuid_max(0, NULL) < 7) + return false; + __cpuid_count(7, 1, eax, ebx, ecx, edx); + return eax & (1 << 17); +} + +int main(void) +{ + unsigned int i, tf; + int expected_flags, ok; + + ksft_print_header(); + ksft_set_plan(2 * ARRAY_SIZE(tests) + 3); + ksft_print_msg("CPU %s FRED\n", cpu_has_fred() ? "supports" : "lacks"); + sethandler(SIGSEGV, handler, 0); + sethandler(SIGTRAP, handler, 0); + sethandler(SIGILL, handler, 0); + + for (tf = 0; tf < 2; tf++) { + for (i = 0; i < ARRAY_SIZE(tests); i++) { + active = &tests[i]; + single_step = tf; + seen = signo = trap = error = ip_offset = flags = 0; + code = addr_ok = stepped = step_ok = 0; + expected_flags = active->flags | (tf ? X86_EFLAGS_TF : 0); + if (tf) + active->run_tf(); + else + active->run(); + + ok = seen && signo == active->signo && trap == active->trap && + error == active->error && ip_offset == active->ip_offset && + flags == expected_flags && code == active->code && addr_ok && + (!tf || (stepped && step_ok)); + ksft_test_result(ok, "%s%s\n", active->name, tf ? " with TF" : ""); + if (!ok) { + ksft_print_msg("got signal=%d trap=%d error=%#x ip=%d\n", + signo, trap, error, ip_offset); + ksft_print_msg("got flags=%#x code=%d addr_ok=%d step_ok=%d\n", + flags, code, addr_ok, step_ok); + ksft_print_msg("expected signal=%d trap=%d error=%#x ip=%d\n", + active->signo, active->trap, active->error, + active->ip_offset); + ksft_print_msg("expected flags=%#x code=%d\n", + expected_flags, active->code); + } + } + } + test_ptrace(); + ksft_finished(); +} From 406aa2b186d3f13a35bc1ad6aff4274917851bc4 Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Tue, 15 Sep 2026 13:24:22 +0100 Subject: [PATCH 2001/2366] perf/arm-cmn: Fix multi-filter encoding The current special-case for EVICT_STATE_SEL filtering effectively assigns the "filter" and "filter2" controls in the opposite order from how the CMN S3 r2 TRM states "Filtering is programmed in pmu_hbt_lbt_sel and pmu_evict_state_sel". On reflection, not only does this seem unnecessarily non-obvious to users, but it's also likely to be a problem for scaling to a full multi-filter abstraction in future. There is a logical order to filters based on their bitfield positions in the pmu_event_sel register, which the TRM descriptions allude to, and the cmn_filter_select enum already (almost) follows, so let's fix the UABI to follow suit while it's still unreleased. Fixes: 09178f536bb9 ("perf/arm-cmn: Plumb in new filter types") Signed-off-by: Robin Murphy Reviewed-by: Ilkka Koskinen Reviewed-by: Leo Yan Signed-off-by: Will Deacon --- drivers/perf/arm-cmn.c | 58 +++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c index 33ee2be9b386..5378fba916cf 100644 --- a/drivers/perf/arm-cmn.c +++ b/drivers/perf/arm-cmn.c @@ -305,9 +305,9 @@ enum cmn_filter_select { SEL_NONE, SEL_OCCUP1_ID, SEL_CLASS_OCCUP_ID, - SEL_CBUSY_SNTHROTTLE_SEL, SEL_HBT_LBT_SEL, SEL_SN_HOME_SEL, + SEL_CBUSY_SNTHROTTLE_SEL, SEL_SNP_VC_SEL, SEL_ENHANCED_HBT_LBT_SEL, SEL_EVICT_STATE_SEL, @@ -978,10 +978,14 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj, _CMN_EVENT_HNS(_model, _name##_all, _event, _sel, 0), \ _CMN_EVENT_HNS(_model, _name##_hbt, _event, _sel, 1), \ _CMN_EVENT_HNS(_model, _name##_lbt, _event, _sel, 2) -#define _CMN_EVENT_HNS_HBT2(_model, _name, _event, _fsel1, f1) \ - _CMN_EVENT_HNS(_model, _name##_all, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 0), \ - _CMN_EVENT_HNS(_model, _name##_hbt, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 1), \ - _CMN_EVENT_HNS(_model, _name##_lbt, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 2) +#define _CMN_EVENT_HNS_EVICT(_model, _name, _event, _fsel1, f1) \ + _CMN_EVENT_HNS(_model, _name##_all, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 0), \ + _CMN_EVENT_HNS(_model, _name##_eu, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 1), \ + _CMN_EVENT_HNS(_model, _name##_en, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 2), \ + _CMN_EVENT_HNS(_model, _name##_su, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 3), \ + _CMN_EVENT_HNS(_model, _name##_sn, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 4), \ + _CMN_EVENT_HNS(_model, _name##_mu, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 5), \ + _CMN_EVENT_HNS(_model, _name##_mn, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 6) #define CMN_EVENT_HNS_OCC(_model, _name, _event) \ CMN_EVENT_HN_OCC(_model, hns_##_name, CMN_TYPE_HNS, _event), \ @@ -1014,13 +1018,9 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj, _CMN_EVENT_HNS(CMNS3R2, _name##_ccglcn, _event, SEL_ENHANCED_HBT_LBT_SEL, 5), \ _CMN_EVENT_HNS(CMNS3R2, _name##_ccgrn, _event, SEL_ENHANCED_HBT_LBT_SEL, 6) #define CMN_EVENT_HNS_EVICT(_model, _name, _event) \ - _CMN_EVENT_HNS_HBT2(_model, _name##_all, _event, SEL_EVICT_STATE_SEL, 0), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_eu, _event, SEL_EVICT_STATE_SEL, 1), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_en, _event, SEL_EVICT_STATE_SEL, 2), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_su, _event, SEL_EVICT_STATE_SEL, 3), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_sn, _event, SEL_EVICT_STATE_SEL, 4), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_mu, _event, SEL_EVICT_STATE_SEL, 5), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_mn, _event, SEL_EVICT_STATE_SEL, 6) + _CMN_EVENT_HNS_EVICT(_model, _name##_all, _event, SEL_HBT_LBT_SEL, 0), \ + _CMN_EVENT_HNS_EVICT(_model, _name##_hbt, _event, SEL_HBT_LBT_SEL, 1), \ + _CMN_EVENT_HNS_EVICT(_model, _name##_lbt, _event, SEL_HBT_LBT_SEL, 2) #define CMN_EVENT_HNSR0_HBT(_name, _event) \ _CMN_EVENT_HNS_HBT(CMN700 | CMNS3R01, _name, _event, SEL_HBT_LBT_SEL) @@ -1860,10 +1860,12 @@ static void arm_cmn_val_add_event(struct arm_cmn *cmn, struct arm_cmn_val *val, val->dtm_count[dtm]++; + if (sel == SEL_EVICT_STATE_SEL) { + val->filter[dtm][sel] = CMN_EVENT_FILTER2(event) + 1; + sel = SEL_HBT_LBT_SEL; + } if (sel) val->filter[dtm][sel] = CMN_EVENT_FILTER(event) + 1; - if (sel == SEL_EVICT_STATE_SEL) - val->filter[dtm][SEL_HBT_LBT_SEL] = CMN_EVENT_FILTER2(event) + 1; if (type != CMN_TYPE_WP) continue; @@ -1914,14 +1916,17 @@ static int arm_cmn_validate_group(struct arm_cmn *cmn, struct perf_event *event) if (val->dtm_count[dtm] == CMN_DTM_NUM_COUNTERS) goto done; + if (sel == SEL_EVICT_STATE_SEL) { + if (val->filter[dtm][sel] && + val->filter[dtm][sel] != CMN_EVENT_FILTER2(event) + 1) + goto done; + sel = SEL_HBT_LBT_SEL; + } + if (sel && val->filter[dtm][sel] && val->filter[dtm][sel] != CMN_EVENT_FILTER(event) + 1) goto done; - if (sel == SEL_EVICT_STATE_SEL && val->filter[dtm][SEL_HBT_LBT_SEL] && - val->filter[dtm][SEL_HBT_LBT_SEL] != CMN_EVENT_FILTER2(event) + 1) - goto done; - if (type != CMN_TYPE_WP) continue; @@ -1950,7 +1955,7 @@ static enum cmn_filter_select arm_cmn_event_filter(const struct arm_cmn *cmn, for (int i = 0; i < ARRAY_SIZE(arm_cmn_event_attrs) - 1; i++) { e = container_of(arm_cmn_event_attrs[i], typeof(*e), attr.attr); if (e->model & model && e->type == type && e->eventid == eventid) - return e->filter[0].sel; + return e->filter[1].sel ?: e->filter[0].sel; } return SEL_NONE; } @@ -2081,16 +2086,21 @@ static void arm_cmn_event_clear(struct arm_cmn *cmn, struct perf_event *event, static int arm_cmn_set_event_filter(struct arm_cmn_node *dn, struct perf_event *event) { enum cmn_filter_select fsel = to_cmn_hw(event)->filter_sel; + bool evict_state = fsel == SEL_EVICT_STATE_SEL; int ret = 0; + if (evict_state) { + ret = arm_cmn_set_event_sel_hi(dn, fsel, CMN_EVENT_FILTER2(event)); + if (ret) + return ret; + fsel = SEL_HBT_LBT_SEL; + } if (fsel) ret = arm_cmn_set_event_sel_hi(dn, fsel, CMN_EVENT_FILTER(event)); - if (fsel == SEL_EVICT_STATE_SEL && !ret) { - ret = arm_cmn_set_event_sel_hi(dn, SEL_HBT_LBT_SEL, CMN_EVENT_FILTER2(event)); - if (ret) - dn->filter[fsel].count--; - } + if (ret && evict_state) + dn->filter[SEL_EVICT_STATE_SEL].count--; + return ret; } From bb756b11ad63832ebee58caf9e8f9381eaecff9f Mon Sep 17 00:00:00 2001 From: Zeng Heng Date: Fri, 11 Sep 2026 09:58:59 +0800 Subject: [PATCH 2002/2366] arm64: io: Reject non-user protection in ioremap_prot() Mapping a stack-top page via /dev/mem with PROT_NONE and then reading that process's /proc//cmdline triggers a spurious WARN in ioremap_prot() through generic_access_phys(): WARNING: ./arch/arm64/include/asm/io.h:275 at generic_access_phys Call trace: generic_access_phys+0x1c8/0x228 (P) __access_remote_vm+0x2b4/0x398 access_remote_vm+0x14/0x30 get_mm_cmdline+0xf8/0x2a0 proc_pid_cmdline_read+0x68/0x120 generic_access_phys() passes the protection derived from the user PTE to ioremap_prot(). On arm64, a PROT_NONE mapping is represented by a present-invalid PTE, so pte_present() still returns true and the protection reaches ioremap_prot(). A PROT_NONE mapping does not have PTE_USER, causing the existing WARN_ON_ONCE() in ioremap_prot() to fire even though this is a valid user mapping. Execute-only mappings have the same issue and must not be readable through this path either. ioremap_prot() should therefore reject protection values without PTE_USER without warning. This makes the access fail cleanly for PROT_NONE and execute-only mappings while retaining the existing user-protection contract. Fixes: 8f098037139b ("arm64: io: Extract user memory type in ioremap_prot()") Signed-off-by: Zeng Heng Reviewed-by: Catalin Marinas Signed-off-by: Will Deacon --- arch/arm64/include/asm/io.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h index 49a7002661a9..9aa0bb08ab60 100644 --- a/arch/arm64/include/asm/io.h +++ b/arch/arm64/include/asm/io.h @@ -280,7 +280,8 @@ static inline void __iomem *ioremap_prot(phys_addr_t phys, size_t size, pgprot_t prot; ptval_t user_prot_val = pgprot_val(user_prot); - if (WARN_ON_ONCE(!(user_prot_val & PTE_USER))) + /* Reject PROT_NONE and exec-only */ + if (!(user_prot_val & PTE_USER)) return NULL; prot = __pgprot_modify(PAGE_KERNEL, PTE_ATTRINDX_MASK, From baaa9b126b875b40ffd9356da52c7c871917e5bb Mon Sep 17 00:00:00 2001 From: Yureka Lilian Date: Fri, 11 Sep 2026 19:02:10 +0200 Subject: [PATCH 2003/2366] arm64: Add override for WFxT Add an override for WFxT support within ID_AA64ISAR2_EL1 to allow it to be disabled using the new arm64.nowfxt command line parameter. This accompanies the idle=nop param introduced in a previous patch series [1] in dealing with misbehaving WFI and WFIT instructions on Apple Silicon SoCs, and eases debugging of other quirky WFxT implementations. Link[1]: https://lore.kernel.org/all/20260804-arm64-idle-param-v3-1-d10f8159062b@cyberchaos.dev/ Suggested-by: Will Deacon Signed-off-by: Yureka Lilian Signed-off-by: Will Deacon --- Documentation/admin-guide/kernel-parameters.txt | 3 +++ arch/arm64/kernel/pi/idreg-override.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 68647ff4bdd2..0fe9e36ce61a 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -589,6 +589,9 @@ Kernel parameters arm64.nosve [ARM64] Unconditionally disable Scalable Vector Extension support + arm64.nowfxt [ARM64] Unconditionally disable Wait For Event with + Timeout and Wait For Interrupt with Timeout support + ataflop= [HW,M68k] atarimouse= [HW,MOUSE] Atari Mouse diff --git a/arch/arm64/kernel/pi/idreg-override.c b/arch/arm64/kernel/pi/idreg-override.c index 274bc72ba98d..acaf56234973 100644 --- a/arch/arm64/kernel/pi/idreg-override.c +++ b/arch/arm64/kernel/pi/idreg-override.c @@ -186,6 +186,7 @@ static const struct ftr_set_desc isar2 __prel64_initconst = { .name = "id_aa64isar2", .override = &id_aa64isar2_override, .fields = { + FIELD("wfxt", ID_AA64ISAR2_EL1_WFxT_SHIFT, NULL), FIELD("gpa3", ID_AA64ISAR2_EL1_GPA3_SHIFT, NULL), FIELD("apa3", ID_AA64ISAR2_EL1_APA3_SHIFT, NULL), FIELD("mops", ID_AA64ISAR2_EL1_MOPS_SHIFT, NULL), @@ -254,6 +255,7 @@ static const struct { "id_aa64isar1.api=0 id_aa64isar1.apa=0 " "id_aa64isar2.gpa3=0 id_aa64isar2.apa3=0" }, { "arm64.nomops", "id_aa64isar2.mops=0" }, + { "arm64.nowfxt", "id_aa64isar2.wfxt=0" }, { "arm64.nomte", "id_aa64pfr1.mte=0" }, { "nokaslr", "arm64_sw.nokaslr=1" }, { "rodata=off", "arm64_sw.rodataoff=1" }, From b7403afb7a5f85073243df10238b3483958ad69e Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 6 Sep 2026 13:14:16 +0100 Subject: [PATCH 2004/2366] arm64: errata: match the target implementation CPU's own MIDR __is_affected_midr_range() is handed the MIDR and REVIDR of one target implementation CPU, but tests the erratum's range with is_midr_in_range(), which re-scans all of target_impl_cpus[] and ignores the @midr argument. The range test is thus constant across the per-CPU loop in is_affected_midr_range() and only answers "is any target CPU in range". Since just the fixed_revs REVIDR check uses the iteration's own registers, an out-of-range target CPU can decide whether a MIDR_FIXED() exemption applies. A VM then enables a workaround whose only in-range CPU is fixed silicon, e.g. erratum 2658417 on a Cortex-A510 r1p1 with REVIDR_EL1[25] set. Factor the range test into __is_midr_in_range(), which takes an explicit MIDR, and use it in __is_affected_midr_range(). Fixes: 86edf6bdcf05 ("smccc/kvm_guest: Enable errata based on implementation CPUs") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: David Carlier Signed-off-by: Will Deacon --- arch/arm64/kernel/cpu_errata.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c index b33dccfafaf8..8ec47d89b45b 100644 --- a/arch/arm64/kernel/cpu_errata.c +++ b/arch/arm64/kernel/cpu_errata.c @@ -28,18 +28,21 @@ bool cpu_errata_set_target_impl(u64 num, void *impl_cpus) return true; } +static inline bool __is_midr_in_range(u32 midr, struct midr_range const *range) +{ + return midr_is_cpu_model_range(midr, range->model, + range->rv_min, range->rv_max); +} + static inline bool is_midr_in_range(struct midr_range const *range) { int i; if (!target_impl_cpu_num) - return midr_is_cpu_model_range(read_cpuid_id(), range->model, - range->rv_min, range->rv_max); + return __is_midr_in_range(read_cpuid_id(), range); for (i = 0; i < target_impl_cpu_num; i++) { - if (midr_is_cpu_model_range(target_impl_cpus[i].midr, - range->model, - range->rv_min, range->rv_max)) + if (__is_midr_in_range(target_impl_cpus[i].midr, range)) return true; } return false; @@ -59,7 +62,7 @@ __is_affected_midr_range(const struct arm64_cpu_capabilities *entry, u32 midr, u32 revidr) { const struct arm64_midr_revidr *fix; - if (!is_midr_in_range(&entry->midr_range)) + if (!__is_midr_in_range(midr, &entry->midr_range)) return false; midr &= MIDR_REVISION_MASK | MIDR_VARIANT_MASK; From 4485a01f4df1c9683d8ffe3e4ade6c33c9572d3c Mon Sep 17 00:00:00 2001 From: Sean Young Date: Sun, 13 Sep 2026 15:12:27 +0100 Subject: [PATCH 2005/2366] parisc: unwind: Replace open-coded binary search with bsearch() There is a bug in the binary search where hi can underflow. If addr is less than the first entry, then "hi = mid - 1" will underflow to ULONG_MAX. Then we have an out-of-bounds read. Replace the open-coded binary search with bsearch(). Issue found by an LLM. Signed-off-by: Sean Young Signed-off-by: Helge Deller --- arch/parisc/kernel/unwind.c | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/arch/parisc/kernel/unwind.c b/arch/parisc/kernel/unwind.c index 32103a270a8e..fab9ae22191a 100644 --- a/arch/parisc/kernel/unwind.c +++ b/arch/parisc/kernel/unwind.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -49,27 +50,23 @@ static DEFINE_SPINLOCK(unwind_lock); static struct unwind_table kernel_unwind_table __ro_after_init; static LIST_HEAD(unwind_tables); +static int cmp_unwind_entry(const void *key, const void *elt) +{ + unsigned long addr = (unsigned long)key; + const struct unwind_table_entry *e = elt; + + if (addr < e->region_start) + return -1; + if (addr > e->region_end) + return 1; + return 0; +} + static inline const struct unwind_table_entry * find_unwind_entry_in_table(const struct unwind_table *table, unsigned long addr) { - const struct unwind_table_entry *e = NULL; - unsigned long lo, hi, mid; - - lo = 0; - hi = table->length - 1; - - while (lo <= hi) { - mid = (hi - lo) / 2 + lo; - e = &table->table[mid]; - if (addr < e->region_start) - hi = mid - 1; - else if (addr > e->region_end) - lo = mid + 1; - else - return e; - } - - return NULL; + return bsearch((void *)addr, table->table, table->length, + sizeof(*table->table), cmp_unwind_entry); } static const struct unwind_table_entry * From cb917b1e1c23f6f9b73802cd576b48bd606458c0 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Thu, 17 Sep 2026 20:04:14 -0700 Subject: [PATCH 2006/2366] parisc: remove unused header The last use of arch/parisc/include/asm/compat_ucontext.h was removed in commit d313d4e72df3 ("parisc: remove unused compat_rt_sigframe.h header") but it was left behind. Remove it. Signed-off-by: Ethan Nelson-Moore Signed-off-by: Helge Deller --- arch/parisc/include/asm/compat_ucontext.h | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 arch/parisc/include/asm/compat_ucontext.h diff --git a/arch/parisc/include/asm/compat_ucontext.h b/arch/parisc/include/asm/compat_ucontext.h deleted file mode 100644 index c606f1bc891d..000000000000 --- a/arch/parisc/include/asm/compat_ucontext.h +++ /dev/null @@ -1,18 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _ASM_PARISC_COMPAT_UCONTEXT_H -#define _ASM_PARISC_COMPAT_UCONTEXT_H - -#include - -/* 32-bit ucontext as seen from an 64-bit kernel */ -struct compat_ucontext { - compat_uint_t uc_flags; - compat_uptr_t uc_link; - compat_stack_t uc_stack; /* struct compat_sigaltstack (12 bytes)*/ - /* FIXME: Pad out to get uc_mcontext to start at an 8-byte aligned boundary */ - compat_uint_t pad[1]; - struct compat_sigcontext uc_mcontext; - compat_sigset_t uc_sigmask; /* mask last for extensibility */ -}; - -#endif /* !_ASM_PARISC_COMPAT_UCONTEXT_H */ From 289e99e7a263c6fd6a5d07d6d8f2156b70f3a9d5 Mon Sep 17 00:00:00 2001 From: Zhenghui Hao Date: Fri, 18 Sep 2026 13:46:49 +0800 Subject: [PATCH 2007/2366] parisc: parse early parameters in setup_arch() parisc is one of the few architectures that does not call parse_early_param() from setup_arch(). That was mostly harmless until commit d49004c5f0c1 ("arch, mm: consolidate initialization of nodes, zones and memory map") moved the consumer of several hugetlb command line parameters into mm_core_init_early(), which runs before the generic parse_early_param() call in start_kernel(). As a result hugepages=, hugepagesz=, default_hugepagesz=, hugetlb_cma= and hugetlb_free_vmemmap= are recorded after they have already been consumed and are silently dropped on parisc. Call parse_early_param() from setup_arch(), after the command line has been set up and the memory inventory has been taken. jump_label_init() must be called first because early parameter handlers may enable or disable static keys. Both functions are safe to call more than once: the generic calls in start_kernel() remain in place and turn into no-ops. Suggested-by: Mike Rapoport (Microsoft) Fixes: d49004c5f0c1 ("arch, mm: consolidate initialization of nodes, zones and memory map") Cc: Signed-off-by: Zhenghui Hao Tested-by: Helge Deller Signed-off-by: Helge Deller --- arch/parisc/kernel/setup.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/arch/parisc/kernel/setup.c b/arch/parisc/kernel/setup.c index d3e17a7a8901..4d3015a411d6 100644 --- a/arch/parisc/kernel/setup.c +++ b/arch/parisc/kernel/setup.c @@ -132,6 +132,18 @@ void __init setup_arch(char **cmdline_p) parisc_cache_init(); paging_init(); + /* + * Parse early parameters before mm_core_init_early() runs. + * Several early_param() handlers only record data that is consumed + * from there - for example hugepages=, hugepagesz=, + * default_hugepagesz=, hugetlb_cma= and hugetlb_free_vmemmap= - so + * the generic parse_early_param() call in start_kernel() is too late + * for them. jump_label_init() must come first, since early param + * handlers may enable or disable static keys. + */ + jump_label_init(); + parse_early_param(); + #ifdef CONFIG_PA11 dma_ops_init(); #endif From 4dd1999783d7d12434006289338373e49492dc96 Mon Sep 17 00:00:00 2001 From: Alexey Klimov Date: Thu, 17 Sep 2026 10:16:42 +0200 Subject: [PATCH 2008/2366] soc: samsung: exynos-pmu: fix use-after-free of interrupt generator node The setup_cpuhp_and_cpuidle() parses the device tree node for the interrupt generation block via of_parse_phandle() and decrements its reference count using of_node_put() immediately after fetching the resource address. However, later the intr_gen_node pointer is passed into of_syscon_register_regmap(). Fix this by declaring intr_gen_node with __free() and removing of_node_put(). Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260513-exynos850-cpuhotplug-v4-0-54fec5f65362@linaro.org?part=3 Fixes: 78b72897a5c8 ("soc: samsung: exynos-pmu: Enable CPU Idle for gs101") Cc: stable@vger.kernel.org Signed-off-by: Alexey Klimov Link: https://patch.msgid.link/20260828-exynos-pmu-cpuhp-idle-fixes-v2-1-06bce6107bd6@linaro.org Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260917081641.72291-2-krzk@kernel.org Signed-off-by: Arnd Bergmann --- drivers/soc/samsung/exynos-pmu.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/soc/samsung/exynos-pmu.c b/drivers/soc/samsung/exynos-pmu.c index f5fcdde9750e..efccdd63e40e 100644 --- a/drivers/soc/samsung/exynos-pmu.c +++ b/drivers/soc/samsung/exynos-pmu.c @@ -409,13 +409,12 @@ static struct notifier_block exynos_cpupm_reboot_nb = { static int setup_cpuhp_and_cpuidle(struct device *dev) { - struct device_node *intr_gen_node; + struct device_node *intr_gen_node __free(device_node) = + of_parse_phandle(dev->of_node, "google,pmu-intr-gen-syscon", 0); struct resource intrgen_res; void __iomem *virt_addr; int ret, cpu; - intr_gen_node = of_parse_phandle(dev->of_node, - "google,pmu-intr-gen-syscon", 0); if (!intr_gen_node) { /* * To maintain support for older DTs that didn't specify syscon @@ -431,8 +430,6 @@ static int setup_cpuhp_and_cpuidle(struct device *dev) * syscon provided regmap. */ ret = of_address_to_resource(intr_gen_node, 0, &intrgen_res); - of_node_put(intr_gen_node); - virt_addr = devm_ioremap(dev, intrgen_res.start, resource_size(&intrgen_res)); if (!virt_addr) From 7cb575b71ab98194d2e040bded3a7281e089c5ed Mon Sep 17 00:00:00 2001 From: Li Jun Date: Thu, 17 Sep 2026 09:37:10 +0800 Subject: [PATCH 2009/2366] watchdog: da9063: fix suspend/resume handling of HW_RUNNING watchdog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit da9063_wdt_suspend() and da9063_wdt_resume() only check watchdog_active(), when the watchdog is left running by the driver sets WDOG_HW_RUNNING in da9063_wdt_probe() but userspace never opens the device, so WDOG_ACTIVE remains cleared, the wdt_disable() will not be executed in da9063_wdt_suspend. In this case, the suspend callback is a no-op and the watchdog keeps counting during system suspend, leading to an unexpected system reset. Check WDOG_HW_RUNNING and wdd,can fix this issue. Fixes: a7ceca4398bc8 ("watchdog: da9063: optionally disable watchdog during suspend") Cc: stable@vger.kernel.org Signed-off-by: Li Jun Link: https://patch.msgid.link/20260917013710.2754679-1-lijun01@kylinos.cn Signed-off-by: Guenter Roeck --- drivers/watchdog/da9063_wdt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/da9063_wdt.c b/drivers/watchdog/da9063_wdt.c index 92e1b78ff481..3703110e82fc 100644 --- a/drivers/watchdog/da9063_wdt.c +++ b/drivers/watchdog/da9063_wdt.c @@ -271,7 +271,7 @@ static int da9063_wdt_suspend(struct device *dev) if (!da9063->use_sw_pm) return 0; - if (watchdog_active(wdd)) + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) return da9063_wdt_stop(wdd); return 0; @@ -285,7 +285,7 @@ static int da9063_wdt_resume(struct device *dev) if (!da9063->use_sw_pm) return 0; - if (watchdog_active(wdd)) + if (watchdog_active(wdd) || watchdog_hw_running(wdd)) return da9063_wdt_start(wdd); return 0; From 8c7fdc0b4c64d6583fff3e8f7696237a16ff7c29 Mon Sep 17 00:00:00 2001 From: Joshua Hahn Date: Wed, 2 Sep 2026 12:45:20 -0700 Subject: [PATCH 2010/2366] selftests/cgroup: account for zswap shrinker writeback The test_no_invasive_cgroup_shrink selftest checks that when a cgroup has zswapped out more memory than memory.zswap.max, it does not trigger writeback for other cgroups. To do this, it compares the writeback count in a control cgroup and makes sure that it is 0, and then checks the writeback count in an aggressor cgroup who does expect to see writeback. However, when the zswap shrinker is enabled, the victim cgroup can see legitimate writebacks not triggered by the aggressor. In some Meta CI tests, we have seen this failure mode happen. Instead of checking that the victim cgroup has 0 writeback, compare the writeback values before and after the aggressor runs and check that the victim cgroup did not perform any additional writeback. Note that this can still lead to probabilistic failures if writebacks take longer than 5 seconds, but this should fix the systematic failure case and make "not ok test_no_invasive_cgroup_shrink" less likely. Link: https://lore.kernel.org/20260902194521.3652178-1-joshua.hahnjy@gmail.com Fixes: b5ba474f3f51 ("zswap: shrink zswap pool based on memory pressure") Signed-off-by: Joshua Hahn Signed-off-by: Andrew Morton Reported-by: Krush Chavan Suggested-by: Nhat Pham Cc: --- tools/testing/selftests/cgroup/test_zswap.c | 28 +++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/cgroup/test_zswap.c b/tools/testing/selftests/cgroup/test_zswap.c index 609c48f38524..8df54b59513a 100644 --- a/tools/testing/selftests/cgroup/test_zswap.c +++ b/tools/testing/selftests/cgroup/test_zswap.c @@ -20,6 +20,7 @@ static int page_size; #define PATH_ZSWAP "/sys/module/zswap" #define PATH_ZSWAP_ENABLED "/sys/module/zswap/parameters/enabled" +#define PATH_ZSWAP_SHRINKER_ENABLED "/sys/module/zswap/parameters/shrinker_enabled" #define PATH_ZSWAP_STORED_PAGES "/sys/kernel/debug/zswap/stored_pages" static int read_int(const char *path, size_t *value) @@ -444,6 +445,16 @@ static int test_zswap_writeback_disabled(const char *root) return test_zswap_writeback(root, false); } +static bool zswap_shrinker_enabled(void) +{ + char value[2]; + + if (read_text(PATH_ZSWAP_SHRINKER_ENABLED, value, sizeof(value)) <= 0) + return 0; + + return value[0] == 'Y'; +} + /* * When trying to store a memcg page in zswap, if the memcg hits its memory * limit in zswap, writeback should affect only the zswapped pages of that @@ -453,6 +464,7 @@ static int test_no_invasive_cgroup_shrink(const char *root) { int ret = KSFT_FAIL; unsigned int off; + long zswpwb_before, zswpwb_after, zswpwb_target; size_t allocation_size = page_size * 1024; unsigned int nr_pages = allocation_size / page_size; char zswap_max_buf[32], mem_max_buf[32]; @@ -488,6 +500,14 @@ static int test_no_invasive_cgroup_shrink(const char *root) if (cg_read_key_long(zw_group, "memory.stat", "zswapped") < 1) goto out; + /* If the shrinker is enabled, try to let the writebacks finish first */ + if (zswap_shrinker_enabled()) + sleep(5); + + zswpwb_before = get_cg_wb_count(zw_group); + if (zswpwb_before < 0) + goto out; + /* Push wb_group memory into zswap with hard-to-compress data to trigger wb */ if (cg_enter_current(wb_group)) goto out; @@ -500,9 +520,13 @@ static int test_no_invasive_cgroup_shrink(const char *root) getrandom(&wb_allocation[off], page_size/4, 0); } - /* Verify that only zswapped memory from gwb_group has been written back */ - if (wait_for_writeback(wb_group, 5000) > 0 && get_cg_wb_count(zw_group) == 0) + /* Verify that only zswapped memory from wb_group has been written back */ + zswpwb_target = wait_for_writeback(wb_group, 5000); + zswpwb_after = get_cg_wb_count(zw_group); + + if (zswpwb_target > 0 && zswpwb_before == zswpwb_after) ret = KSFT_PASS; + out: cg_enter_current(root); if (zw_group) { From 8d50c2f37bcc766cd5ecde9bbb14c9c27c609f33 Mon Sep 17 00:00:00 2001 From: Haowen Bai Date: Thu, 3 Sep 2026 21:28:54 +0800 Subject: [PATCH 2011/2366] mailmap: update Haowen Bai's email address Map Haowen Bai's former Meizu and current Ugreen addresses to baihaowen88@gmail.com as the canonical public address. Link: https://lore.kernel.org/20260903132854.1930923-1-calvin.bai@ugreen.com Signed-off-by: Haowen Bai Signed-off-by: Andrew Morton --- .mailmap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index 90ff4831f592..59518b814b4f 100644 --- a/.mailmap +++ b/.mailmap @@ -355,6 +355,8 @@ Hans Verkuil Hans Verkuil Hans Verkuil Hao Ge +Haowen Bai +Haowen Bai Harry Yoo <42.hyeyoo@gmail.com> Harry Yoo Heiko Carstens From 525c0edc032b3297d0c1056cf1fa20cf1f9e6184 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Fri, 4 Sep 2026 10:37:51 +0800 Subject: [PATCH 2012/2366] ocfs2: make ocfs2_calc_xattr_init() return void ocfs2_calc_xattr_init() used to read the default ACL off the parent inode itself, so it could return an error from ocfs2_xattr_get_nolock(). Commit bd7c05fb4a47 ("ocfs2: fix circular locking dependency in ocfs2_init_acl()") moved that lookup before the transaction starts and deleted the error path, but left the now vestigial 'int ret = 0' declaration and both 'return ret' statements behind, along with an unreachable error branch in ocfs2_mknod(). Drop the leftover variable and convert the return type to void, so the callee states that it always succeeds and the caller no longer carries a check that can never trigger. No functional change. Link: https://lore.kernel.org/20260904023751.3703334-1-joseph.qi@linux.alibaba.com Fixes: bd7c05fb4a47 ("ocfs2: fix circular locking dependency in ocfs2_init_acl()") Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609040247.8B3lmoqX-lkp@intel.com/ Cc: Mark Fasheh Cc: Joel Becker Cc: Junxiao Bi Cc: Changwei Ge Cc: Jun Piao Cc: Heming Zhao --- fs/ocfs2/namei.c | 9 ++------- fs/ocfs2/xattr.c | 13 +++++-------- fs/ocfs2/xattr.h | 8 ++++---- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index e9c7774ccf91..58c6061ed983 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -336,13 +336,8 @@ static int ocfs2_mknod(struct mnt_idmap *idmap, goto leave; /* calculate meta data/clusters for setting security and acl xattr */ - status = ocfs2_calc_xattr_init(dir, mode, &si, &want_clusters, - &xattr_credits, &want_meta, - &acl_state); - if (status < 0) { - mlog_errno(status); - goto leave; - } + ocfs2_calc_xattr_init(dir, mode, &si, &want_clusters, &xattr_credits, + &want_meta, &acl_state); /* Reserve a cluster if creating an extent based directory. */ if (S_ISDIR(mode) && !ocfs2_supports_inline_data(osb)) { diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 35bcbb0ff607..bfafe059bedf 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -635,12 +635,11 @@ int ocfs2_calc_security_init(struct inode *dir, return ret; } -int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, - struct ocfs2_security_xattr_info *si, - int *want_clusters, int *xattr_credits, - int *want_meta, struct ocfs2_acl_state *acl_state) +void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, + struct ocfs2_security_xattr_info *si, + int *want_clusters, int *xattr_credits, + int *want_meta, struct ocfs2_acl_state *acl_state) { - int ret = 0; struct ocfs2_super *osb = OCFS2_SB(dir->i_sb); int s_size = 0, a_size = 0, acl_len = 0, new_clusters; @@ -662,7 +661,7 @@ int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, } if (!(s_size + a_size)) - return ret; + return; /* * The max space of security xattr taken inline is @@ -728,8 +727,6 @@ int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, } } } - - return ret; } static int ocfs2_xattr_extend_allocation(struct inode *inode, diff --git a/fs/ocfs2/xattr.h b/fs/ocfs2/xattr.h index 5e18513277f1..887cc1a18b1a 100644 --- a/fs/ocfs2/xattr.h +++ b/fs/ocfs2/xattr.h @@ -59,10 +59,10 @@ int ocfs2_calc_security_init(struct inode *, int *, int *, struct ocfs2_alloc_context **); struct ocfs2_acl_state; -int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, - struct ocfs2_security_xattr_info *si, - int *want_clusters, int *xattr_credits, - int *want_meta, struct ocfs2_acl_state *acl_state); +void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, + struct ocfs2_security_xattr_info *si, + int *want_clusters, int *xattr_credits, + int *want_meta, struct ocfs2_acl_state *acl_state); /* * xattrs can live inside an inode, as part of an external xattr block, From f166586f74dd5d9cbadaabf86ef81c8ddf6cafa7 Mon Sep 17 00:00:00 2001 From: Nathan Gao Date: Thu, 3 Sep 2026 17:28:27 -0700 Subject: [PATCH 2013/2366] mm/damon/ops-common: use a page-aligned address in damon_ptep_mkold() __damon_va_prepare_access_check() picks a random byte address within the region and stores it in r->sampling_addr. damon_va_mkold() passes it into a page table walk, which hands it to damon_ptep_mkold() as the address of the page to sample: damon_va_mkold(mm, r->sampling_addr) damon_va_walk_page_range(mm, addr, addr + 1) damon_mkold_pmd_entry() damon_ptep_mkold(pte, vma, addr) ptep_test_and_clear_young(vma, addr, pte) mmu_notifier_clear_young(mm, addr, addr + PAGE_SIZE) For arm64, before commit 6f0e1142173a ("arm64: mm: support batch clearing of the young flag for large folios"), the contpte helper walked exactly CONT_PTES entries from the aligned-down page table pointer and used @addr only to pass down to each entry, so an unaligned value was harmless: ptep = contpte_align_down(ptep); addr = ALIGN_DOWN(addr, CONT_PTE_SIZE); for (i = 0; i < CONT_PTES; i++, ptep++, addr += PAGE_SIZE) Now the range to walk is derived from @addr instead: end = addr + nr * PAGE_SIZE, rounded up to CONT_PTE_SIZE. For a sample in the last page of a contpte block, the sub-page offset puts end just past the block boundary, so the round-up lands a whole block further and the walk clears PTE_AF in CONT_PTES entries beyond the sampled block. For the last block in a page table page, those entries are past the end of that page, so the walk writes into the page that follows. Triggered by the full 7.1/7.2 kernel selftest suite on arm64 (EC2 c/m6g.4xlarge). The kernel sometimes crashes at or shortly after the DAMON test. What the overrun does depends on the page that happens to follow the page table, so there is no single signature. If that page is read-only, the write faults in the sampling path itself: Unable to handle kernel write to read-only memory at virtual address ffff0003c5d2d000 FSC = 0x0f: level 3 permission fault CM = 0, WnR = 1, TnD = 0, TagAccess = 0 CPU: 10 UID: 0 PID: 3487 Comm: kdamond.2 pc : contpte_test_and_clear_young_ptes+0x70/0xc0 lr : damon_ptep_mkold+0x1e8/0x1f8 Call trace: contpte_test_and_clear_young_ptes+0x70/0xc0 (P) damon_mkold_pmd_entry+0x150/0x170 walk_pmd_range+0x110/0x2b0 walk_pud_range+0x10c/0x208 walk_pgd_range+0x134/0x258 __walk_page_range+0x98/0x1b0 walk_page_range_vma_unsafe+0x90/0x148 walk_page_range_vma+0x28/0x40 damon_va_walk_page_range+0x114/0x2b8 damon_va_prepare_access_checks+0xec/0x1a8 kdamond_fn+0x534/0x770 kthread+0x128/0x138 ret_from_fork+0x10/0x20 Otherwise the page is writable, the PTE_AF clearing succeeds silently and the damage only surfaces later, in whatever happened to own the page, so the backtrace is unrelated to DAMON and differs between runs. Pass a page-aligned address to the ptep_test_and_clear_young() call in damon_ptep_mkold(), which is the only place DAMON can reach contpte_test_and_clear_young_ptes() from. Nothing else sees the aligned address, and r->sampling_addr itself is left as is, so the sampling and region bookkeeping semantics are unchanged. Link: https://lore.kernel.org/20260904002829.116381-1-sj@kernel.org Fixes: 6f0e1142173a ("arm64: mm: support batch clearing of the young flag for large folios") Signed-off-by: Nathan Gao Signed-off-by: SJ Park Signed-off-by: Andrew Morton Reviewed-by: SJ Park Reviewed-by: Baolin Wang Cc: Baolin Wang Cc: David Hildenbrand (Arm) Cc: Ryan Roberts Cc: --- mm/damon/ops-common.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mm/damon/ops-common.c b/mm/damon/ops-common.c index fbda70d8ea4d..8fc61d06d358 100644 --- a/mm/damon/ops-common.c +++ b/mm/damon/ops-common.c @@ -61,7 +61,12 @@ void damon_ptep_mkold(pte_t *pte, struct vm_area_struct *vma, unsigned long addr * device aspects. */ if (likely(pte_present(pteval))) - young |= ptep_test_and_clear_young(vma, addr, pte); + /* + * Arch implementation of ptep_test_and_clear_young() may + * require aligned @addr + */ + young |= ptep_test_and_clear_young(vma, PAGE_ALIGN_DOWN(addr), + pte); young |= mmu_notifier_clear_young(vma->vm_mm, addr, addr + PAGE_SIZE); if (young) folio_set_young(folio); From 90179da203ba8b708c84a12a07cd44be0f346334 Mon Sep 17 00:00:00 2001 From: Liew Rui Yan Date: Tue, 8 Sep 2026 06:54:11 -0700 Subject: [PATCH 2014/2366] mm/damon/core: allow esz to be set to zero When the temporal quota goal tuner determines that the goal has been achieved (score >= 10000), it sets esz_bp to zero so that the esz becomes zero. However, damos_set_effective_quota() clamps the esz to min_region_sz when quota->ms is set. This is a minor issue, the main problem is that it doesn't match the description in the documentation, which state that if the goal has already been [over-]achieved, the quota will be set to zero. Fix this by set quota (esz) as minimum as possible. Link: https://lore.kernel.org/20260908135413.97570-1-sj@kernel.org Fixes: 8bbde987c2b8 ("mm/damon/core: disallow time-quota setting zero esz") Signed-off-by: SJ Park Signed-off-by: Liew Rui Yan Signed-off-by: Andrew Morton Reviewed-by: SJ Park Cc: # v7.1.x --- mm/damon/core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 644daf5a1656..2b294fb46648 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3091,6 +3091,7 @@ static void damos_set_effective_quota(struct damon_ctx *ctx, struct damos *s) struct damos_quota *quota = &s->quota; unsigned long throughput; unsigned long esz = ULONG_MAX; + unsigned long esz_time; if (!quota->ms && list_empty("a->goals)) { quota->esz = quota->sz; @@ -3111,8 +3112,8 @@ static void damos_set_effective_quota(struct damon_ctx *ctx, struct damos *s) 1000000, quota->total_charged_ns); else throughput = PAGE_SIZE * 1024; - esz = min(throughput * quota->ms, esz); - esz = max(ctx->min_region_sz, esz); + esz_time = max(throughput * quota->ms, ctx->min_region_sz); + esz = min(esz_time, esz); } if (quota->sz && quota->sz < esz) From 39c0ceedd54557bdc1542de08d22b2ed33e534e4 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Mon, 7 Sep 2026 10:03:56 -0700 Subject: [PATCH 2015/2366] mm/damon/vaddr: avoid hw-driven pte updates during damon_hugetlb_mkold() damon_hugetlb_mkold() reads the page table entry into a local variable, unsets the accessed bit in the variable, and updates the page table entry with the updated variable value. If hardware updates the same page table entry in parallel, the hw updates could be lost. For example, hardware-updated dirty bits might be lost. Avoid the parallel updates by clearing the page table entry when reading it together, using huge_ptep_get_and_clear(). If a parallel write to the memory is made after the clearing, the hw will see the page table entry is cleared, trigger page fault and wait until it is handled. The page fault handling will wait for damon_hugetlb_mkold() due to the page table lock. Because hugetlbfs is an in-memory file system and hugetlb pages cannot be reclaimed, no critical issue is expected to my best knowledge. But definitely this is a nasty bug that should be fixed sooner rather than later. The issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260907170358.100168-1-sj@kernel.org Link: https://lore.kernel.org/20260830160545.98969-1-sj@kernel.org [1] Fixes: 49f4203aae06 ("mm/damon: add access checking for hugetlb pages") Signed-off-by: SJ Park Signed-off-by: Andrew Morton Cc: Baolin Wang Cc: # 5.17.x --- mm/damon/vaddr.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c index 0648400b2d65..04ee2a2c6a4d 100644 --- a/mm/damon/vaddr.c +++ b/mm/damon/vaddr.c @@ -293,22 +293,29 @@ static int damon_mkold_pmd_entry(pmd_t *pmd, unsigned long addr, } #ifdef CONFIG_HUGETLB_PAGE +static bool damon_hugetlb_ptep_mkold(pte_t *pte, struct mm_struct *mm, + struct vm_area_struct *vma, unsigned long addr, pte_t *entry) +{ + unsigned long psize = huge_page_size(hstate_vma(vma)); + + if (!pte_young(*entry)) + return false; + *entry = huge_ptep_get_and_clear(mm, addr, pte, psize); + *entry = pte_mkold(*entry); + set_huge_pte_at(mm, addr, pte, *entry, psize); + return true; +} + static void damon_hugetlb_mkold(pte_t *pte, struct mm_struct *mm, struct vm_area_struct *vma, unsigned long addr) { bool referenced = false; pte_t entry = huge_ptep_get(mm, addr, pte); struct folio *folio = pfn_folio(pte_pfn(entry)); - unsigned long psize = huge_page_size(hstate_vma(vma)); folio_get(folio); - if (pte_young(entry)) { - referenced = true; - entry = pte_mkold(entry); - set_huge_pte_at(mm, addr, pte, entry, psize); - } - + referenced = damon_hugetlb_ptep_mkold(pte, mm, vma, addr, &entry); if (mmu_notifier_clear_young(mm, addr, addr + huge_page_size(hstate_vma(vma)))) referenced = true; From b3723b596b548c837a766aae3553c14a7b15af2b Mon Sep 17 00:00:00 2001 From: Liew Rui Yan Date: Tue, 8 Sep 2026 06:47:38 -0700 Subject: [PATCH 2016/2366] mm/damon/core: fix unconditionally skip last region Once quota set, the charge_{target,addr}_from unconditionally skips and resets at the last region of the tracked target, so the last region can be skipped even when it has not been processed. Example: 1. Target has 2 regions: R1 (0-100 bytes) and R2 (100-200 bytes). 2. Quota is configured to process only 100 bytes per window. 3. Window 1: Processes R1 (0-100). Quota is full. charge_{target, addr}_from is saved at (Target, 100). 4. Window 2: The loop reaches R2. Because R2 is damon_last_region(t), the old code unconditionally returns true, skipping R2 entirely and resetting the charge_{target,addr}_from. Result: R2 is permanently skipped even though it has never been processed. However, it is important to note that this is a very minor issue. This is because it is triggered only when the previous window saved/kept charge_{target,addr}_from, and in the next window, all regions except the last region were skipped by damos_skip_charged_region(). Fix this by only resetting the charge_{target,addr}_from when last region is reached, only skipping when it is applied or cannot split. Link: https://lore.kernel.org/20260908134739.96919-1-sj@kernel.org Fixes: 50585192bc2e ("mm/damon/schemes: skip already charged targets and regions") Signed-off-by: Liew Rui Yan Reviewed-by: SJ Park Signed-off-by: SJ Park Signed-off-by: Andrew Morton Cc: # v5.16.x --- mm/damon/core.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 2b294fb46648..06a253df8d4b 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -2342,36 +2342,39 @@ static bool damos_skip_charged_region(struct damon_target *t, { struct damos_quota *quota = &s->quota; unsigned long sz_to_skip; + bool skip = false; /* Skip previously charged regions */ if (quota->charge_target_from) { if (t != quota->charge_target_from) return true; - if (r == damon_last_region(t)) { - quota->charge_target_from = NULL; - quota->charge_addr_from = 0; - return true; - } if (quota->charge_addr_from && - r->ar.end <= quota->charge_addr_from) - return true; + r->ar.end <= quota->charge_addr_from) { + skip = true; + goto out; + } if (quota->charge_addr_from && r->ar.start < quota->charge_addr_from) { sz_to_skip = ALIGN_DOWN(quota->charge_addr_from - r->ar.start, min_region_sz); if (!sz_to_skip) { - if (damon_sz_region(r) <= min_region_sz) - return true; + if (damon_sz_region(r) <= min_region_sz) { + skip = true; + goto out; + } sz_to_skip = min_region_sz; } damon_split_region_at(t, r, sz_to_skip); - return true; + skip = true; } + } +out: + if (r == damon_last_region(t)) { quota->charge_target_from = NULL; quota->charge_addr_from = 0; } - return false; + return skip; } static void damos_update_stat(struct damos *s, From 9bdad082d44bdcf93716973dcba6be77e8a06e7b Mon Sep 17 00:00:00 2001 From: Jaewook You Date: Mon, 14 Sep 2026 22:23:52 +0900 Subject: [PATCH 2017/2366] mm/hugetlb: preserve mremap address delta when skipping page tables move_hugetlb_page_tables() optimizes mremap() by advancing to the last entry in the page table when the source page table does not exist, either initially or after unsharing a PMD table. The common loop increment then steps to the first entry in the next page table. However, the code advances both the source and destination addresses to the last entries in their respective page tables, which is wrong. The destination address must be advanced only by the same amount as the source address. If the source and destination offsets within their page tables differ, the destination address can be advanced too far, causing follow-up issues. Fix this by advancing the destination address by the source advance distance. With a reproducer, we were able to trigger a kernel panic on x86-64. With this fix in place, we can no longer reproduce the issue. Link: https://lore.kernel.org/20260914132352.472-1-jaewook376@gmail.com Fixes: e95a9851787b ("hugetlb: skip to end of PT page mapping when pte not present") Fixes: 4ddb4d91b82f ("hugetlb: do not update address in huge_pmd_unshare") Signed-off-by: Jaewook You Signed-off-by: Andrew Morton Acked-by: David Hildenbrand (Arm) Cc: Johan Hovold Cc: Muchun Song Cc: Oscar Salvador Cc: Assisted-by: LLM --- mm/hugetlb.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index d28972cd33f5..cea25773a6c9 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -5170,18 +5170,21 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma, hugetlb_vma_lock_write(vma); i_mmap_lock_write(mapping); for (; old_addr < old_end; old_addr += sz, new_addr += sz) { + const unsigned long offset_to_last_entry = + (old_addr | last_addr_mask) - old_addr; + src_pte = hugetlb_walk(vma, old_addr, sz); if (!src_pte) { - old_addr |= last_addr_mask; - new_addr |= last_addr_mask; + old_addr += offset_to_last_entry; + new_addr += offset_to_last_entry; continue; } if (huge_pte_none(huge_ptep_get(mm, old_addr, src_pte))) continue; if (huge_pmd_unshare(&tlb, vma, old_addr, src_pte)) { - old_addr |= last_addr_mask; - new_addr |= last_addr_mask; + old_addr += offset_to_last_entry; + new_addr += offset_to_last_entry; continue; } From b6ac0b3f6013c168f22cad97e79967accacb08e1 Mon Sep 17 00:00:00 2001 From: Jinjiang Tu Date: Tue, 8 Sep 2026 20:29:24 +0800 Subject: [PATCH 2018/2366] mm/rmap: fix missing barrier between anon_vma init and vma->anon_vma publish On arm64 server, we find that a task trying to grab the anon_vma lock triggers hungtask. INFO: task main:2354726 blocked for more than 120 seconds. Tainted: G E 5.10.0-0021.aarch64 #1 "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. task:main state:D stack: 0 pid:2354726 ppid:2350673 flags:0x00000a01 Call trace: __switch_to+0x7c/0xbc __schedule+0x3b4/0x8a0 schedule+0x50/0xe0 rwsem_down_write_slowpath+0x3cc/0x6cc down_write+0x60/0x260 __anon_vma_prepare+0x6c/0x210 do_anonymous_page+0x258/0x660 handle_pte_fault+0x188/0x214 __handle_mm_fault+0x1b0/0x380 handle_mm_fault+0xf4/0x284 do_page_fault+0x19c/0x494 do_translation_fault+0xcc/0xf8 do_mem_abort+0x48/0xac el0_da+0x44/0x80 el0_sync_handler+0x88/0xb4 el0_sync+0x160/0x180 After analyzing the vmcore, we found the anon_vma->root->rwsem.count is -1. There is another anon_vma whose anon_vma->root->rwsem.count is 1, the anon_vma->root->rwsem.owner shows the lock is held, but the stack of the task shows the task doesn't hold the anon_vma lock. After adding more debugging info, we found __anon_vma_prepare() reuses anon_vma and triggers the UAF of anon_vma->root due to missing memory barrier, leading to locking and unlocking two different anon_vma->root, thus leading to an anon_vma will never be unlocked, and another anon_vma couldn't be locked anymore. This race requires two adjacent VMAs that are not merged but are anon_vma-compatible (e.g., they differ in VMA_ACCESS_FLAGS that can be changed by mprotect()). Two threads fault on each VMA concurrently, both calling __anon_vma_prepare() with only mmap_lock held for reading. THREAD A THREAD B __anon_vma_prepare __anon_vma_prepare find_mergeable_anon_vma() -> NULL anon_vma = anon_vma_alloc(); anon_vma->root = anon_vma; // the two stores may be reordered vma->anon_vma = anon_vma; // finds A's anon_vma anon_vma = find_mergeable_anon_vma(vma); anon_vma_lock_write(anon_vma); // may still see the old root down_write(&anon_vma->root->rwsem); anon_vma_unlock_write(anon_vma); // see the new root, never unlock old up_write(&anon_vma->root->rwsem); thread A triggers page fault and calls __anon_vma_prepare() to prepare anon_vma for the faulting vma. __anon_vma_prepare() allocates and initializes a new anon_vma, and then publishes it to the vma with a plain store. anon_vma_prepare() only requires the mmap_lock to be held for reading, so two threads can fault on adjacent VMAs at the same time. While thread A publishes a new anon_vma, thread B could find the anon_vma via find_mergeable_anon_vma() and then locks anon_vma->root->rwsem. The store to anon_vma->root in anon_vma_alloc() and the store to vma->anon_vma can be reordered. The anon_vma_lock_write() and spin_lock() only provide acquire semantics, which do not prevent prior stores from being reordered after them. The release semantics of the corresponding spin_unlock() and anon_vma_unlock_write() come too late, the store to vma->anon_vma is already published before they take effect. As a result, thread B can observe the following order: vma->anon_vma = anon_vma; anon_vma->root = anon_vma; The anon_vma slab is SLAB_TYPESAFE_BY_RCU, so a newly allocated anon_vma may reuse memory from a previously freed one. The constructor (anon_vma_ctor) does not reset anon_vma->root, and __put_anon_vma() doesn't clear it either, so the old root value persists until anon_vma_alloc() overwrites it. If that store isn't visible, thread B reads a root that points to the old anon_vma and locks it. As a result, thread B can call anon_vma_lock_write() with the old root, and call anon_vma_unlock_write() with the new root, leading to an anon_vma will never be unlocked, and another anon_vma couldn't be locked anymore (its count is dropped from 0 to -1 due to wrong unlock). To fix it, change the plain store `vma->anon_vma = anon_vma` to store release, so that the fields of anon_vma are visible before anon_vma is published to vma->anon_vma. At read side, the load of anon_vma and anon_vma->root have address dependency. According to Documentation/memory-barriers.txt and some investigations, only Alpha needs address-dependency barriers and it has been handled by READ_ONCE() in reusable_anon_vma(). We reproduced this issue in v5.10 with KSM enabled. The kernel doesn't merge commit cf7e7a3503df ("mm: prevent KSM from breaking VMA merging for new VMAs"), so there are many adjacent VMAs that aren't merged but are compatible for anon_vma. Without this fix, our production environment could reproduce this issue about 2-5 times each month. After adding a smp_mb() before anon_vma_lock_write(anon_vma) in __anon_vma_prepare(), which is different to this patch, this issue hasn't been reproduced for one month. Link: https://lore.kernel.org/20260908122924.554373-1-tujinjiang@huawei.com Fixes: 5c341ee1dfc8 ("mm: track the root (oldest) anon_vma") Signed-off-by: Jinjiang Tu Signed-off-by: Andrew Morton Reviewed-by: Lance Yang Reviewed-by: Lorenzo Stoakes (ARM) Acked-by: David Hildenbrand (Arm) Acked-by: Vlastimil Babka (SUSE) Cc: Minchan Kim Cc: Harry Yoo Cc: Hiroyouki Kamezawa Cc: Jann Horn Cc: Jinjiang Tu Cc: Kefeng Wang Cc: Larry Woodman Cc: Liam R. Howlett Cc: Nanyong Sun Cc: Rik van Riel Cc: --- mm/rmap.c | 6 +++++- mm/vma.c | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mm/rmap.c b/mm/rmap.c index d1819fd69938..f3b21aaa34ee 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -209,7 +209,11 @@ int __anon_vma_prepare(struct vm_area_struct *vma) /* page_table_lock to protect against threads */ spin_lock(&mm->page_table_lock); if (likely(!vma->anon_vma)) { - vma->anon_vma = anon_vma; + /* + * Make anon_vma fields visible before anon_vma is published. + * Paired with an address dependency in reusable_anon_vma(). + */ + smp_store_release(&vma->anon_vma, anon_vma); anon_vma_chain_assign(vma, avc, anon_vma); anon_rmap_tree_insert(avc, anon_vma); anon_vma->num_active_vmas++; diff --git a/mm/vma.c b/mm/vma.c index f29abb30956b..9f0a0acf694a 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -2094,6 +2094,13 @@ static int anon_vma_compatible(struct vm_area_struct *a, struct vm_area_struct * * acceptable for merging, so we can do all of this optimistically. But * we do that READ_ONCE() to make sure that we never re-load the pointer. * + * The READ_ONCE() establishes an address dependency between anon_vma and + * any access to its fields, which pairs with the assignment to + * vma->anon_vma performed with release semantics in __anon_vma_prepare(). + * + * This is especially important as anon_vma's are SLAB_TYPESAFE_BY_RCU so + * accessing an uninitialised anon_vma's fields may result in a UAF. + * * IOW: that the "list_is_singular()" test on the anon_vma_chain only * matters for the 'stable anon_vma' case (ie the thing we want to avoid * is to return an anon_vma that is "complex" due to having gone through @@ -2108,6 +2115,7 @@ static struct anon_vma *reusable_anon_vma(struct vm_area_struct *old, struct vm_area_struct *b) { if (anon_vma_compatible(a, b)) { + /* Paired with a memory barrier in __anon_vma_prepare(). */ struct anon_vma *anon_vma = READ_ONCE(old->anon_vma); if (anon_vma && list_is_singular(&old->anon_vma_chain)) From 44fcc0bfb0874a95cec2c1672f14d426f86dc68f Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Tue, 8 Sep 2026 09:42:12 +0800 Subject: [PATCH 2019/2366] MAINTAINERS: add Baoquan and Baolin as MGLRU reviewers Baoquan and I have been contributing MGLRU patches and helping review MGLRU related patches for some time. We will continue to follow MGLRU changes, so we'd like to be CC'd on MGLRU related patches. Link: https://lore.kernel.org/06e20ef4f603a4ffeafcdbf623ce2936281457bf.1788831480.git.baolin.wang@linux.alibaba.com Signed-off-by: Baolin Wang Signed-off-by: Andrew Morton Acked-by: Barry Song Acked-by: Qi Zheng Acked-by: Baoquan He Acked-by: Kairui Song Cc: Axel Rasmussen Cc: Shakeel Butt Cc: Wei Xu Cc: Yuanchu Xie --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index c9f866debdf0..a6c0e4bcbc78 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17240,6 +17240,8 @@ R: Barry Song R: Axel Rasmussen R: Yuanchu Xie R: Wei Xu +R: Baoquan He +R: Baolin Wang L: linux-mm@kvack.org S: Maintained W: http://www.linux-mm.org From eb64948249781bda35de04feab5a0acc36aa9051 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Thu, 10 Sep 2026 07:28:45 -0700 Subject: [PATCH 2020/2366] mm/damon/core: reset invalid quota->charge_target_from DAMOS can suddenly stop working if a target process that the quota is just fully charged on is terminated. Fix by catching and processing the corner case. When DAMOS quota is fully charged, the target and the region to continue applying the action in the next round is saved in damos_quota->charge_{target,addr}_from. In the next round, DAMOS iterates targets and regions from the beginning. It skips applying the action to the regions until it visits and skips the saved target/region. Virtual address space targets become invalid if the process is terminated. Trying to apply the scheme to invalid target is just a waste of time. Hence commit 6e4930e33329 ("mm/damon/core: fix wasteful CPU calls by skipping non-existent targets") made the logic to skip invalid targets. However, it does skip before the charged target/region skipping/updating. Let's suppose the user runs DAMOS for multiple virtual address spaces with a quota. The quota exceeded in the middle of a virtual address space. And the process of the address space is terminated. Then the charge_target_from points to the invalid target. The pointer update logic is skipped for the invalid target, so the charge_target_from is never updated. DAMOS action to every target/region is skipped. From the user's perspective, it would look like suddenly DAMOS has stopped working. No critical leak or crash can happen. The user could reinstall the scheme. But this makes use of DAMOS under certain setups quite unreliable. When the invalid target is found, further check the corner case and reset the pointer. This issue was discovered [1] by Sashiko. Link: https://lore.kernel.org/20260910142846.172957-1-sj@kernel.org Link: https://lore.kernel.org/20260830064708.40CA61F000E9@smtp.kernel.org [1] Fixes: 6e4930e33329 ("mm/damon/core: fix wasteful CPU calls by skipping non-existent targets") Signed-off-by: SJ Park Signed-off-by: Andrew Morton Cc: Enze Li Cc: # 7.0.x --- mm/damon/core.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 06a253df8d4b..1764620903f2 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3245,8 +3245,15 @@ static void kdamond_apply_schemes(struct damon_ctx *c) max_region_sz = damon_region_sz_limit(c); mutex_lock(&c->walk_control_lock); damon_for_each_target(t, c) { - if (c->ops.target_valid && c->ops.target_valid(t) == false) + if (c->ops.target_valid && c->ops.target_valid(t) == false) { + damon_for_each_scheme(s, c) { + if (s->quota.charge_target_from != t) + continue; + s->quota.charge_target_from = NULL; + s->quota.charge_addr_from = 0; + } continue; + } damos_apply_target(c, t, max_region_sz); } From 407a5d205179a4ab186571b0e16ec42725dc77bc Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 9 Sep 2026 18:01:07 +0000 Subject: [PATCH 2021/2366] writeback: report a Tasks-RCU quiescent state per cgwb drain pass cleanup_offline_cgwbs_workfn() drains a dying cgwb by calling cleanup_offline_cgwb() until it returns false, with a cond_resched() between passes. On a CONFIG_PREEMPTION kernel that cond_resched() does nothing: _cond_resched() is a plain "return 0", and under PREEMPT_DYNAMIC the full and lazy modes disable it. Since commit 7dadeaa6e851 ("sched: Further restrict the preemption modes") those are the only two models on the architectures with PREEMPT_LAZY support, arm64 and x86 among them, so the drain loop never reports a Tasks-RCU quiescent state. A worker draining a cgwb with millions of attached inodes runs for minutes. On a 6.18 arm64 host in lazy mode the cgwb worker drained one dying cgroup's writeback domain for over 11 minutes. A BPF program unlink (bpf_trampoline_unlink_prog -> bpf_trampoline_update -> unregister_ftrace_direct -> ftrace_shutdown -> synchronize_rcu_tasks()) waited on that grace period while holding the trampoline mutex, 42 tasks queued behind it in D state, and the hung task detector fired at 614 s and panicked the host. Any BPF or ftrace detach during a long drain inherits the drain's length. Fix this by calling cond_resched_tasks_rcu_qs() so we do not stall out anybody who calls sycnrhonize_rcu_tasks(). We put this in a do { } while loop because if we have many small cgroups cleanup_offline_cgwb() will return false and we will never call cond_resched_tasks_rcu_qs(), creating the same problem. Link: https://lore.kernel.org/20260909-cgwb-tasks-rcu-qs-v1-1-967a7754771f@toxicpanda.com Fixes: c22d70a162d3 ("writeback, cgroup: release dying cgwbs by switching attached inodes") Signed-off-by: Josef Bacik Signed-off-by: Andrew Morton Link: https://lore.kernel.org/bpf/9d444098-7c03-4163-af12-bd0a79a51443@paulmck-laptop/ Assisted-by: LLM Acked-by: Tejun Heo Reviewed-by: Roman Gushchin Reviewed-by: Jan Kara Acked-by: Lorenzo Stoakes (ARM) Cc: David Hildenbrand Cc: Dennis Zhou Cc: Liam R. Howlett Cc: Matthew Wilcox (Oracle) Cc: Michal Hocko Cc: Mike Rapoport Cc: "Paul E . McKenney" Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: --- mm/backing-dev.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mm/backing-dev.c b/mm/backing-dev.c index cecbcf9060a6..18e999053bae 100644 --- a/mm/backing-dev.c +++ b/mm/backing-dev.c @@ -910,8 +910,9 @@ static void cleanup_offline_cgwbs_workfn(struct work_struct *work) continue; spin_unlock_irq(&cgwb_lock); - while (cleanup_offline_cgwb(wb)) - cond_resched(); + do { + cond_resched_tasks_rcu_qs(); + } while (cleanup_offline_cgwb(wb)); spin_lock_irq(&cgwb_lock); wb_put(wb); From 692dd08e03f4a5523650e055f033f846bee43a0c Mon Sep 17 00:00:00 2001 From: Xu Xin Date: Mon, 7 Sep 2026 14:53:42 +0800 Subject: [PATCH 2022/2366] MAINTAINERS: update Xu Xin's email Now I'm moving to the @linux.dev account, so map my old email addresses and update them to my new address. Link: https://lore.kernel.org/20260907145342529uOGtNYWTAzrSCFnghfgCb@zte.com.cn Signed-off-by: Xu Xin Signed-off-by: Andrew Morton --- .mailmap | 2 ++ MAINTAINERS | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 59518b814b4f..2447811f773a 100644 --- a/.mailmap +++ b/.mailmap @@ -980,6 +980,8 @@ Wesley Cheng Will Deacon Wolfram Sang Wolfram Sang +Xu Xin +Xu Xin xu xin Yakir Yang Yanteng Si Ying Huang diff --git a/MAINTAINERS b/MAINTAINERS index a6c0e4bcbc78..7d12dfac0149 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17192,7 +17192,7 @@ F: tools/testing/selftests/mm/gup_test.c MEMORY MANAGEMENT - KSM (Kernel Samepage Merging) M: Andrew Morton M: David Hildenbrand -R: Xu Xin +R: Xu Xin R: Chengming Zhou L: linux-mm@kvack.org S: Maintained From b0be01f133aa36d2fd12d71c916cb8a47826b4ed Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Sun, 30 Aug 2026 11:02:01 +0800 Subject: [PATCH 2023/2366] pinctrl: qcom: nord: Split QUP1 SE2/SE3 into lane-pair functions QUP1 SE2 and SE3 pack all four of their lanes pair-wise onto only two pins each: lanes 0/1 (I2C SDA/SCL) at mux value 2 and lanes 2/3 (UART TX/RX) at mux value 1, on gpio127/gpio128 and gpio129/gpio130 respectively. Both mux values were named "qup1_se2" (respectively "qup1_se3"), so the two distinct lane pairs became indistinguishable. msm_pinmux_set_mux() stops at the first entry matching the requested function, which means mux value 1 was always selected and the I2C lanes could never be muxed out. In practice i2c9 and i2c10 got the UART lanes and did not work, while uart9 and uart10 happened to be muxed correctly. Give each lane pair its own function, following the _01/_23 naming already used for the same hardware arrangement by the shikra, eliza, hawi and maili TLMM drivers. Both functions still cover the full pin pair, so a single pinctrl state per protocol remains sufficient. Drop gpio129/gpio130 from the SE2 group list, since those pins belong to SE3 and were never reachable through the SE2 function. Also rename QUP1 SE2/SE3 functions in the binding doc accordingly. While at it, add missing "gpio", "qup3_se0_mira" and "qup3_se0_mirb" to the binding function enum to get the list complete. Fixes: c24dd0826f06 ("pinctrl: qcom: add the TLMM driver for the Nord platforms") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Shawn Guo Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260830030201.135637-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Linus Walleij --- .../bindings/pinctrl/qcom,nord-tlmm.yaml | 7 ++-- drivers/pinctrl/qcom/pinctrl-nord.c | 34 +++++++++++++------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml b/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml index 4bb511719f31..56758a85ead8 100644 --- a/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml +++ b/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml @@ -67,7 +67,7 @@ $defs: Specify the alternative function to be configured for the specified pins. - enum: [ aoss_cti, atest_char, atest_usb20, atest_usb21, + enum: [ gpio, aoss_cti, atest_char, atest_usb20, atest_usb21, aud_intfc0_clk, aud_intfc0_data, aud_intfc0_ws, aud_intfc10_clk, aud_intfc10_data, aud_intfc10_ws, aud_intfc1_clk, aud_intfc1_data, aud_intfc1_ws, @@ -98,9 +98,10 @@ $defs: pcie3_clk_req_n, phase_flag, pll_bist_sync, pll_clk_aux, prng_rosc0, prng_rosc1, pwrbrk_i_n, qdss, qdss_cti, qspi, qup0_se0, qup0_se1, qup0_se2, qup0_se3, qup0_se4, qup0_se5, - qup1_se0, qup1_se1, qup1_se3, qup1_se2, qup1_se4, qup1_se5, + qup1_se0, qup1_se1, qup1_se2_01, qup1_se2_23, qup1_se3_01, + qup1_se3_23, qup1_se4, qup1_se5, qup1_se6, qup2_se0, qup2_se1, qup2_se2, qup2_se3, qup2_se4, - qup2_se5, qup2_se6, + qup2_se5, qup2_se6, qup3_se0_mira, qup3_se0_mirb, sailss_ospi, sdc4_clk, sdc4_cmd, sdc4_data, smb_alert, smb_alert_n, smb_clk, smb_dat, tb_trig_sdc4, tmess_prng0, tmess_prng1, tsc_timer, tsense_pwm, usb0_hs, diff --git a/drivers/pinctrl/qcom/pinctrl-nord.c b/drivers/pinctrl/qcom/pinctrl-nord.c index 7c21306e77ff..7f37f8e819ba 100644 --- a/drivers/pinctrl/qcom/pinctrl-nord.c +++ b/drivers/pinctrl/qcom/pinctrl-nord.c @@ -570,8 +570,10 @@ enum nord_functions { msm_mux_qup0_se5, msm_mux_qup1_se0, msm_mux_qup1_se1, - msm_mux_qup1_se2, - msm_mux_qup1_se3, + msm_mux_qup1_se2_01, + msm_mux_qup1_se2_23, + msm_mux_qup1_se3_01, + msm_mux_qup1_se3_23, msm_mux_qup1_se4, msm_mux_qup1_se5, msm_mux_qup1_se6, @@ -1152,11 +1154,19 @@ static const char *const qup1_se1_groups[] = { "gpio123", "gpio124", "gpio125", "gpio126", }; -static const char *const qup1_se2_groups[] = { - "gpio127", "gpio128", "gpio129", "gpio130", +static const char *const qup1_se2_01_groups[] = { + "gpio127", "gpio128", }; -static const char *const qup1_se3_groups[] = { +static const char *const qup1_se2_23_groups[] = { + "gpio127", "gpio128", +}; + +static const char *const qup1_se3_01_groups[] = { + "gpio129", "gpio130", +}; + +static const char *const qup1_se3_23_groups[] = { "gpio129", "gpio130", }; @@ -1428,8 +1438,10 @@ static const struct pinfunction nord_functions[] = { MSM_PIN_FUNCTION(qup0_se5), MSM_PIN_FUNCTION(qup1_se0), MSM_PIN_FUNCTION(qup1_se1), - MSM_PIN_FUNCTION(qup1_se2), - MSM_PIN_FUNCTION(qup1_se3), + MSM_PIN_FUNCTION(qup1_se2_01), + MSM_PIN_FUNCTION(qup1_se2_23), + MSM_PIN_FUNCTION(qup1_se3_01), + MSM_PIN_FUNCTION(qup1_se3_23), MSM_PIN_FUNCTION(qup1_se4), MSM_PIN_FUNCTION(qup1_se5), MSM_PIN_FUNCTION(qup1_se6), @@ -1633,13 +1645,13 @@ static const struct msm_pingroup nord_groups[] = { _, _, _, _, _, _, _), [126] = PINGROUP(126, qup1_se1, qup1_se0, ccu_i2c_scl, mdp1_vsync_out, _, atest_usb20, ddr_pxi, _, _, _, _), - [127] = PINGROUP(127, qup1_se2, qup1_se2, _, atest_usb21, ddr_pxi, + [127] = PINGROUP(127, qup1_se2_23, qup1_se2_01, _, atest_usb21, ddr_pxi, _, _, _, _, _, _), - [128] = PINGROUP(128, qup1_se2, qup1_se2, _, atest_usb20, ddr_pxi, + [128] = PINGROUP(128, qup1_se2_23, qup1_se2_01, _, atest_usb20, ddr_pxi, _, _, _, _, _, _), - [129] = PINGROUP(129, qup1_se3, qup1_se3, ccu_i2c_sda, mdp1_vsync_out, + [129] = PINGROUP(129, qup1_se3_23, qup1_se3_01, ccu_i2c_sda, mdp1_vsync_out, _, atest_usb21, ddr_pxi, _, _, _, _), - [130] = PINGROUP(130, qup1_se3, qup1_se3, ccu_i2c_scl, mdp1_vsync_out, + [130] = PINGROUP(130, qup1_se3_23, qup1_se3_01, ccu_i2c_scl, mdp1_vsync_out, _, atest_usb20, ddr_pxi, _, _, _, _), [131] = PINGROUP(131, qup1_se4, qup1_se6, ccu_i2c_sda, mdp1_vsync_out, _, atest_usb21, ddr_pxi, _, _, _, _), From 447dcff90557748a5ac384aaf5d70b2c7e3b961d Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:43:40 +0800 Subject: [PATCH 2024/2366] pinctrl: qcom: ipq5210: Publish the OF module alias The IPQ5210 TLMM platform driver can be a module and matches through ipq5210_tlmm_of_match. This table is not published for OF modalias matching. Publish the existing table, preserving arch_initcall ordering and the shared MSM pinctrl probe. Fixes: a549fe22376f ("pinctrl: qcom: Introduce IPQ5210 TLMM driver") Signed-off-by: hpp.iscas Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260905134340.67477-1-hppiscas@163.com Signed-off-by: Bartosz Golaszewski Signed-off-by: Linus Walleij --- drivers/pinctrl/qcom/pinctrl-ipq5210.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pinctrl/qcom/pinctrl-ipq5210.c b/drivers/pinctrl/qcom/pinctrl-ipq5210.c index 827a4ad07a6b..d5b4eb3e7343 100644 --- a/drivers/pinctrl/qcom/pinctrl-ipq5210.c +++ b/drivers/pinctrl/qcom/pinctrl-ipq5210.c @@ -867,6 +867,7 @@ static const struct of_device_id ipq5210_tlmm_of_match[] = { { .compatible = "qcom,ipq5210-tlmm", }, { }, }; +MODULE_DEVICE_TABLE(of, ipq5210_tlmm_of_match); static int ipq5210_tlmm_probe(struct platform_device *pdev) { From aff09d9e37e02dc60bde79035ac15b136d602259 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 18 Sep 2026 15:16:17 +0200 Subject: [PATCH 2025/2366] drm/nouveau/clk: fix list cursor use after loop in nvkm_clk_ustate_update If list_for_each_entry() exits without hitting a break then "pstate" is not a valid pstate pointer. Introduce a "found" variable instead. The check is reachable from userspace: nvkm_clk_ustate_update() takes the pstate id straight from the 'pstate' debugfs file, so requesting an id that is not in clk->states - or any id at all when the perf tables are broken and the list is empty - makes the pstate->pstate != req test dereference the list head cast to a struct nvkm_pstate, which is an out-of-bounds read. Fixes: 7c8565220697 ("drm/nouveau/clk: implement power state and engine clock control in core") Signed-off-by: Dan Carpenter [Francesco: rebased on drm-misc-next, expanded the commit message] Signed-off-by: Francesco Magazzu Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260918131620.405133-2-postadelmaga@gmail.com --- drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c index 572e63846315..5da82db71dde 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c @@ -473,6 +473,7 @@ static int nvkm_clk_ustate_update(struct nvkm_clk *clk, int req) { struct nvkm_pstate *pstate; + bool found = false; int i = 0; if (!clk->allow_reclock) @@ -480,12 +481,14 @@ nvkm_clk_ustate_update(struct nvkm_clk *clk, int req) if (req != -1 && req != -2) { list_for_each_entry(pstate, &clk->states, head) { - if (pstate->pstate == req) + if (pstate->pstate == req) { + found = true; break; + } i++; } - if (pstate->pstate != req) + if (!found) return -EINVAL; req = i; } From 866dbd17c3d5f599b9d93ea9bd5b3d6859ff8350 Mon Sep 17 00:00:00 2001 From: Francesco Magazzu Date: Fri, 18 Sep 2026 15:16:18 +0200 Subject: [PATCH 2026/2366] drm/nouveau/clk: don't use the pstate cursor after the loop nvkm_pstate_prog() walks clk->states looking for the entry at index 'pstatei' and then keeps using the list_for_each_entry cursor after the loop. This is not triggerable today: every caller clamps the index against clk->state_nr before calling, so the loop always breaks on a real entry. It is safe by virtue of what the callers happen to do, not by anything the function itself checks. Should a caller ever pass an index that is not on the list, the cursor would point at the list head rather than at a pstate, and the pstate->base.domain[] and pstate->fanspeed accesses that follow would read past it. Rather than leave that trap in place for the next caller, track whether the entry was found and return -EINVAL if it was not. No functional change. Signed-off-by: Francesco Magazzu Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260918131620.405133-3-postadelmaga@gmail.com --- drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c index 5da82db71dde..a43246ae681f 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c @@ -270,13 +270,19 @@ nvkm_pstate_prog(struct nvkm_clk *clk, int pstatei) struct nvkm_fb *fb = subdev->device->fb; struct nvkm_pci *pci = subdev->device->pci; struct nvkm_pstate *pstate; + bool found = false; int ret, idx = 0; list_for_each_entry(pstate, &clk->states, head) { - if (idx++ == pstatei) + if (idx++ == pstatei) { + found = true; break; + } } + if (!found) + return -EINVAL; + nvkm_debug(subdev, "setting performance state %d\n", pstatei); clk->pstate = pstatei; From 7ca7b8b5f2cc89ee843c2eab3272aac5aafb7b73 Mon Sep 17 00:00:00 2001 From: Francesco Magazzu Date: Fri, 18 Sep 2026 15:16:19 +0200 Subject: [PATCH 2027/2366] drm/nouveau/device: don't use the pstate cursor after the loop nvkm_control_mthd_pstate_attr() looks up the pstate at the index supplied by userspace by walking clk->states, and then keeps using the list_for_each_entry cursor after the loop. This is not triggerable today: the function already rejects args->v0.state >= clk->state_nr before the loop, and clk->state_nr is kept in sync with the number of entries on clk->states, so the lookup always breaks on a real entry. Should the loop ever run to completion, the cursor would point at the list head rather than at a pstate, and the pstate->base.domain[] read and the walk of pstate->list that follow would read past it. Rather than leave that trap in place, track whether the entry was found and return -EINVAL if it was not, like the other lookup failures in this function. No functional change. Signed-off-by: Francesco Magazzu Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260918131620.405133-4-postadelmaga@gmail.com --- drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c index f2e9a06263ce..28702741a88b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c @@ -74,6 +74,7 @@ nvkm_control_mthd_pstate_attr(struct nvkm_control *ctrl, void *data, u32 size) const struct nvkm_domain *domain; struct nvkm_pstate *pstate; struct nvkm_cstate *cstate; + bool found = false; int i = 0, j = -1; u32 lo, hi; int ret = -ENOSYS; @@ -104,10 +105,15 @@ nvkm_control_mthd_pstate_attr(struct nvkm_control *ctrl, void *data, u32 size) if (args->v0.state != NVIF_CONTROL_PSTATE_ATTR_V0_STATE_CURRENT) { list_for_each_entry(pstate, &clk->states, head) { - if (i++ == args->v0.state) + if (i++ == args->v0.state) { + found = true; break; + } } + if (!found) + return -EINVAL; + lo = pstate->base.domain[domain->name]; hi = lo; list_for_each_entry(cstate, &pstate->list, head) { From e5cccdafc855cd5f96f4b51d38114a0360b075d7 Mon Sep 17 00:00:00 2001 From: Francesco Magazzu Date: Fri, 18 Sep 2026 15:16:20 +0200 Subject: [PATCH 2028/2366] drm/nouveau/clk: don't clobber reclock status when restoring volt/fan nvkm_cstate_prog() reuses 'ret' for the voltage and fan-speed restore calls it makes after reprogramming the clocks. Those calls almost always succeed, so the status of the reclock itself is overwritten and the function reports success even when clk->func->calc() or clk->func->prog() failed. The converse is also true: a successful reclock is reported as an error if the final restore call fails, even though that failure is only logged and otherwise ignored. The only consumer of the return value is the error message in nvkm_pstate_work(), so in practice a failing reclock is simply never reported. Nothing else changes, but a function that returns success on failure is a trap for the next caller. Keep the calc/prog status in 'ret' and use a separate local for the restore calls. Fixes: 3eca809b3c05 ("drm/nouveau/clk: cosmetic changes") Signed-off-by: Francesco Magazzu Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260918131620.405133-5-postadelmaga@gmail.com --- drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c index a43246ae681f..1cb83edc78dc 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c @@ -199,16 +199,18 @@ nvkm_cstate_prog(struct nvkm_clk *clk, struct nvkm_pstate *pstate, int cstatei) } if (volt) { - ret = nvkm_volt_set_id(volt, cstate->voltage, - pstate->base.voltage, clk->temp, -1); - if (ret && ret != -ENODEV) - nvkm_error(subdev, "failed to lower voltage: %d\n", ret); + int err = nvkm_volt_set_id(volt, cstate->voltage, + pstate->base.voltage, clk->temp, -1); + + if (err && err != -ENODEV) + nvkm_error(subdev, "failed to lower voltage: %d\n", err); } if (therm) { - ret = nvkm_therm_cstate(therm, pstate->fanspeed, -1); - if (ret && ret != -ENODEV) - nvkm_error(subdev, "failed to lower fan speed: %d\n", ret); + int err = nvkm_therm_cstate(therm, pstate->fanspeed, -1); + + if (err && err != -ENODEV) + nvkm_error(subdev, "failed to lower fan speed: %d\n", err); } return ret; From 6a6870d3077faa501ca97760057ddca22b68418d Mon Sep 17 00:00:00 2001 From: Peiyang He Date: Fri, 18 Sep 2026 10:53:12 +0800 Subject: [PATCH 2029/2366] drm/nouveau: don't bump pin count on failed re-pin in nouveau_bo_pin_locked() nouveau_bo_pin_locked() checks whether an already pinned BO is in a memory domain compatible with a new pin request. When the domains are incompatible, it sets -EBUSY but still calls ttm_bo_pin() before returning. Callers treat a failed nouveau_bo_pin() as not having acquired a new pin, so the extra pin count is never decreased by a matching unpin. This triggers the warning in ttm_bo_release(): WARN_ON_ONCE(bo->pin_count); Found when fuzzing the nouveau driver with a modified Syzkaller: WARNING: drivers/gpu/drm/ttm/ttm_bo.c:256 at ttm_bo_release+0x827/0x9e0 drivers/gpu/drm/ttm/ttm_bo.c:256, CPU#1: syz.3.24/2212 Modules linked in: CPU: 1 UID: 0 PID: 2212 Comm: syz.3.24 Not tainted 7.2.0 #24 PREEMPT(lazy) nouveau 0000:01:00.0: gsp:msg fn:103 len:0x40/0x20 res:0x19 resp:0x19 Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 RIP: 0010:ttm_bo_release+0x827/0x9e0 drivers/gpu/drm/ttm/ttm_bo.c:256 Code: 02 00 0f 85 51 01 00 00 48 8b 7b 08 e8 d2 20 01 00 e9 80 fd ff ff e8 d8 15 c0 fe 90 0f 0b 90 e9 e1 f8 ff ff e8 ca 15 c0 fe 90 <0f> 0b 90 e9 a4 f8 ff ff e8 bc 15 c0 fe be 03 00 00 00 4c 89 e7 e8 msg: 00000000: 05 00 d0 c1 04 00 f0 f1 01 30 00 00 2d 90 00 00 .........0..-... RSP: 0018:ffffc9000f5cf710 EFLAGS: 00010293 RAX: 0000000000000000 RBX: ffff888018e5d2a8 RCX: ffffffff82bb1b36 RDX: ffff888017b68000 RSI: 0000000000000004 RDI: ffff888018e5d2a8 msg: 00000010: 19 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ RBP: ffff88801261c720 R08: 0000000000000001 R09: ffffed10031cba55 R10: ffff888018e5d2ab R11: 00000000000000f3 R12: ffff888018e5d290 R13: ffff888018e5d2d4 R14: ffff88801b219c18 R15: dffffc0000000000 FS: 0000000000000000(0000) GS:ffff8880e0f6f000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000001b31223ffc CR3: 0000000028e00005 CR4: 0000000000770ef0 PKRU: 80000000 Call Trace: kref_put include/linux/kref.h:65 [inline] ttm_bo_put drivers/gpu/drm/ttm/ttm_bo.c:325 [inline] ttm_bo_fini+0x55/0x80 drivers/gpu/drm/ttm/ttm_bo.c:330 nouveau_gem_object_del+0xb2/0x1b0 drivers/gpu/drm/nouveau/nouveau_gem.c:90 drm_gem_object_free+0x5f/0x90 drivers/gpu/drm/drm_gem.c:1165 kref_put include/linux/kref.h:65 [inline] __drm_gem_object_put include/drm/drm_gem.h:562 [inline] drm_gem_object_put include/drm/drm_gem.h:575 [inline] nouveau_abi16_chan_fini.constprop.0+0x44f/0x5a0 drivers/gpu/drm/nouveau/nouveau_abi16.c:195 nouveau 0000:01:00.0: syz.2.23[2209]: Unknown handle 0x00000000 nouveau_abi16_fini+0x1d0/0x340 drivers/gpu/drm/nouveau/nouveau_abi16.c:225 nouveau_drm_postclose+0x18b/0x3e0 drivers/gpu/drm/nouveau/nouveau_drm.c:1284 nouveau 0000:01:00.0: syz.2.23[2209]: validate_init drm_file_free.part.0+0x6d6/0xb60 drivers/gpu/drm/drm_file.c:267 drm_file_free drivers/gpu/drm/drm_file.c:237 [inline] drm_close_helper.isra.0+0x11a/0x160 drivers/gpu/drm/drm_file.c:290 drm_release+0x1ab/0x330 drivers/gpu/drm/drm_file.c:438 __fput+0x39c/0xa60 fs/file_table.c:512 nouveau 0000:01:00.0: syz.2.23[2209]: validate: -2 task_work_run+0x15a/0x230 kernel/task_work.c:233 exit_task_work include/linux/task_work.h:40 [inline] do_exit+0x82b/0x25a0 kernel/exit.c:1009 do_group_exit+0xc2/0x280 kernel/exit.c:1152 get_signal+0x1d6e/0x1f30 kernel/signal.c:3046 arch_do_signal_or_restart+0x7d/0x6e0 arch/x86/kernel/signal.c:337 __exit_to_user_mode_loop kernel/entry/common.c:66 [inline] exit_to_user_mode_loop+0xdf/0x440 kernel/entry/common.c:101 __exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline] syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:230 [inline] syscall_exit_to_user_mode include/linux/entry-common.h:318 [inline] do_syscall_64+0x4f8/0x690 arch/x86/entry/syscall_64.c:100 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f12bac8594d Code: Unable to access opcode bytes at 0x7f12bac85923. RSP: 002b:00007f12b96e70d8 EFLAGS: 00000246 ORIG_RAX: 00000000000000ca RAX: 0000000000000001 RBX: 00007f12baf15fa8 RCX: 00007f12bac8594d RDX: 00000000000f4240 RSI: 0000000000000081 RDI: 00007f12baf15fac RBP: 00007f12baf15fa0 R08: 00007f12baee8000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007f12baf16038 R14: 0000000000000006 R15: 00007ffe2ed394b0 irq event stamp: 47867 hardirqs last enabled at (47883): [] __up_console_sem+0x66/0x70 kernel/printk/printk.c:347 hardirqs last disabled at (47892): [] __up_console_sem+0x4b/0x70 kernel/printk/printk.c:345 softirqs last enabled at (47880): [] __do_softirq kernel/softirq.c:656 [inline] softirqs last enabled at (47880): [] invoke_softirq kernel/softirq.c:496 [inline] softirqs last enabled at (47880): [] __irq_exit_rcu+0x137/0x1c0 kernel/softirq.c:735 softirqs last disabled at (47875): [] __do_softirq kernel/softirq.c:656 [inline] softirqs last disabled at (47875): [] invoke_softirq kernel/softirq.c:496 [inline] softirqs last disabled at (47875): [] __irq_exit_rcu+0x137/0x1c0 kernel/softirq.c:735 Fix by calling ttm_bo_pin() only when the existing placement is compatible with the new pin request. This matches the correct behavior in other DRM drivers such as amdgpu_bo_pin() in amdgpu. Cc: stable@vger.kernel.org Fixes: ad76b3f7c7a0 ("drm/nouveau: teach nouveau_bo_pin() how to force a contig vram allocation") Signed-off-by: Peiyang He Assisted-by: LLM Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/EACEF2F4E098413F+20260918025312.2814889-1-peiyang_he@smail.nju.edu.cn --- drivers/gpu/drm/nouveau/nouveau_bo.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c index 0e8de6d4b36f..6dcb92575eb4 100644 --- a/drivers/gpu/drm/nouveau/nouveau_bo.c +++ b/drivers/gpu/drm/nouveau/nouveau_bo.c @@ -578,8 +578,9 @@ int nouveau_bo_pin_locked(struct nouveau_bo *nvbo, uint32_t domain, bool contig) "0x%08x vs 0x%08x\n", bo, bo->resource->mem_type, domain); ret = -EBUSY; + } else { + ttm_bo_pin(&nvbo->bo); } - ttm_bo_pin(&nvbo->bo); goto out; } From 1717fcc5be575d4768279148ae9465a8b13d4339 Mon Sep 17 00:00:00 2001 From: Giuseppe Ranieri Date: Thu, 17 Sep 2026 21:51:14 +0000 Subject: [PATCH 2030/2366] drm/nouveau/disp: don't reject HDMI config on cards without SCDC nv50_hdmi_enable() passes the sink's SCDC capability from its EDID straight through to nvif_outp_hdmi(). On pre-Maxwell-2 cards there is no hdmi->scdc callback, so nvkm_uoutp_mthd_hdmi() rejects the whole configuration with -EINVAL, and nv50_hdmi_enable() returns before hdmi->ctrl() runs and before the AVI and VSI infoframes are sent. The result on such a card driving an SCDC-capable HDMI 2.0 sink is that HDMI audio silently stops working. Video is unaffected, and nothing is logged, which makes the failure hard to attribute. SCDC is optional, and the hdmi->scdc() call further down is already guarded against a missing callback. Requesting it on a card that cannot do it need not invalidate the rest of the HDMI configuration, so drop that term from the condition and let the existing guard skip SCDC alone. Fixes: 6c6abab20b99 ("drm/nouveau/disp: add output hdmi config method") Signed-off-by: Giuseppe Ranieri Co-Authored-By: Tano Dzhinski Signed-off-by: Tano Dzhinski Tested-by: Tano Dzhinski Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20260917215114.1136715-1-tano.dzhinski@gmail.com --- drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c index 377d0e0cef84..9887b3898505 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c @@ -253,8 +253,7 @@ nvkm_uoutp_mthd_hdmi(struct nvkm_outp *outp, void *argv, u32 argc) if (!ior->func->hdmi || args->v0.max_ac_packet > 0x1f || - args->v0.rekey > 0x7f || - (args->v0.scdc && !ior->func->hdmi->scdc)) + args->v0.rekey > 0x7f) return -EINVAL; if (!args->v0.enable) { From d58384c22739848efe14b34e9586e4f1242f33c0 Mon Sep 17 00:00:00 2001 From: Liz Fong-Jones Date: Fri, 18 Sep 2026 03:56:33 +0000 Subject: [PATCH 2031/2366] PCI: Fix BAR resize for devices on a root bus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pci_do_resource_release_and_resize() releases device BARs that share a bridge window with the BAR being resized, but when the device sits directly on a root bus (pdev->bus->self == NULL) it then skips resource assignment entirely and returns success, leaving the BARs it just released unassigned (IORESOURCE_UNSET). Skipping pbus_reassign_bridge_resources() is correct in that case -- there is no bridge window to adjust -- but the device BARs still have to be reassigned. Before the BAR release was consolidated into the PCI core, this case worked for amdgpu because the driver released the BARs itself and then called pci_assign_unassigned_bus_resources() unconditionally after the resize, which assigns unassigned device BARs also on a root bus. Commit db92e3fef53e ("drm/amdgpu: Remove driver side BAR release before resize") removed that call, so nothing assigns the released BARs anymore. This breaks amdgpu completely on the SolidRun HoneyComb LX2K (NXP LX2160A, arm64, ACPI), where ACPI doesn't expose the Root Port so the GPU endpoint appears directly on a "root bus" of its segment: amdgpu 0004:01:00.0: BAR 0 [mem 0xa400000000-0xa40fffffff 64bit pref]: releasing amdgpu 0004:01:00.0: BAR 2 [mem 0xa410000000-0xa4101fffff 64bit pref]: releasing amdgpu 0004:01:00.0: sw_init of IP block failed -19 amdgpu 0004:01:00.0: amdgpu_device_ip_init failed amdgpu 0004:01:00.0: Fatal error during GPU init No error is logged because the resize path reports success; amdgpu then finds BAR 0 IORESOURCE_UNSET and bails out with -ENODEV. When there is no upstream bridge, call pci_bus_assign_resources() on the root bus to place the BARs released above, using the same alignment-sorted algorithm as normal enumeration instead of a manual per-BAR loop. This also walks the rest of the hierarchy under the root bus, as pci_assign_unassigned_bus_resources() used to for amdgpu before commit db92e3fef53e ("drm/amdgpu: Remove driver side BAR release before resize") removed that call -- the core-side fix that commit asked for ("such a problem should be fixed inside pci_resize_resource() instead"). pci_bus_assign_resources() returns void, so failure is detected by checking whether the released BARs are still assigned afterward; if not, roll back as in the bridged case. This is stricter than the bridged path -- it fails on any unplaced resource, not just required ones -- since a root bus typically has one shared window, and failing loudly seemed better than leaving something silently unassigned. The root bus path also had a locking bug that any fix here necessarily touches: the old "goto out" jumped to up_read(&pci_bus_sem) without a matching down_read() (as does the "goto restore" taken when pci_dev_res_add_to_list() fails in the release loop). Take pci_bus_sem before the BAR release loop so every path through the function holds it exactly once. Fixes: 337b1b566db0 ("PCI: Fix restoring BARs on BAR resize rollback path") Link: https://bugs.launchpad.net/ubuntu/+source/linux-hwe-7.0/+bug/2159596 Suggested-by: Ilpo Järvinen Assisted-by: Claude:claude-fable-5 checkpatch Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Liz Fong-Jones [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Reviewed-by: Ilpo Järvinen Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260918035633.566823-1-lizf@honeycomb.io --- drivers/pci/setup-bus.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index e8c94aa1d3c1..ed16ef7c26fa 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -2380,6 +2380,7 @@ int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size struct resource *res = pci_resource_n(pdev, resno); struct pci_dev_resource *dev_res; struct pci_bus *bus = pdev->bus; + struct pci_dev *bridge = pci_upstream_bridge(pdev); struct resource *b_win, *r; LIST_HEAD(saved); unsigned int i; @@ -2397,6 +2398,8 @@ int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size if (ret) return ret; + down_read(&pci_bus_sem); + pci_dev_for_each_resource(pdev, r, i) { if (i >= PCI_BRIDGE_RESOURCES) break; @@ -2415,13 +2418,21 @@ int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size pci_resize_resource_set_size(pdev, resno, size); - if (!bus->self) - goto out; + if (bridge) { + ret = pbus_reassign_bridge_resources(bus, res, &saved); + if (ret) + goto restore; + } else { + /* No bridge window to adjust; let the core reassign the bus. */ + pci_bus_assign_resources(bus); - down_read(&pci_bus_sem); - ret = pbus_reassign_bridge_resources(bus, res, &saved); - if (ret) - goto restore; + list_for_each_entry(dev_res, &saved, list) { + if (!resource_assigned(dev_res->res)) { + ret = -ENOSPC; + goto restore; + } + } + } out: up_read(&pci_bus_sem); From 8805840aad73df7146778be243a196d48b4f6430 Mon Sep 17 00:00:00 2001 From: Angel J Date: Fri, 18 Sep 2026 14:55:40 -0500 Subject: [PATCH 2032/2366] PCI: of_property: Omit bus properties without a subordinate bus A bridge (a device with a Type 1 header) may not have a secondary bus allocated (pdev->subordinate), e.g., if there are no available bus numbers or the bridge secondary/subordinate bus numbers are not writable. The dynamic OF helpers of_pci_prop_bus_range() and of_pci_prop_intr_map() dereference pdev->subordinate without checking it. When CONFIG_PCI_DYNAMIC_OF_NODES is enabled, this can cause a NULL pointer dereference and early boot hang. Generate 'bus-range' and 'interrupt-map' properties only when a subordinate bus exists. Keep the node and its remaining properties for bridges without one. The problem was latent since 407d1a51921e ("PCI: Create device tree node for bridge"), but wasn't reachable until 1f340724419e ("PCI: of: Create device tree PCI host bridge node"), which appeared in v6.15. Before 1f340724419e, of_pci_make_dev_node() returned early because the parent OF node was missing. Fixes: 407d1a51921e ("PCI: Create device tree node for bridge") Signed-off-by: Angel J [bhelgaas: move pdev->subordinate test to callees, commit log] Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org # v6.6+ Link: https://patch.msgid.link/20260918195540.GA1187209@bhelgaas --- drivers/pci/of_property.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/pci/of_property.c b/drivers/pci/of_property.c index 75a358f73e69..1500740cc55d 100644 --- a/drivers/pci/of_property.c +++ b/drivers/pci/of_property.c @@ -95,9 +95,13 @@ static int of_pci_prop_bus_range(struct pci_dev *pdev, struct of_changeset *ocs, struct device_node *np) { - u32 bus_range[] = { pdev->subordinate->busn_res.start, - pdev->subordinate->busn_res.end }; + u32 bus_range[2]; + if (!pdev->subordinate) + return 0; + + bus_range[0] = pdev->subordinate->busn_res.start; + bus_range[1] = pdev->subordinate->busn_res.end; return of_changeset_add_prop_u32_array(ocs, np, "bus-range", bus_range, ARRAY_SIZE(bus_range)); } @@ -220,6 +224,9 @@ static int of_pci_prop_intr_map(struct pci_dev *pdev, struct of_changeset *ocs, int ret; u8 pin; + if (!pdev->subordinate) + return 0; + pnode = pci_device_to_OF_node(pdev->bus->self); if (!pnode) pnode = pci_bus_to_OF_node(pdev->bus); From 99cc2a62e07a44a22254d7beca9ef1f8ad886d0d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 13 Sep 2026 04:42:33 +0000 Subject: [PATCH 2033/2366] tipc: reject invalid and unexpected GRP_ACK_MSG to prevent bc_ackers underflow Commit 48a5fe38772b ("tipc: fix bc_ackers underflow on duplicate GRP_ACK_MSG") rejected duplicate/stale ACKs in tipc_group_proto_rcv() by returning early when less_eq(acked, m->bc_acked). However, that check remains incomplete in two ways: 1. When grp->bc_ackers is zero (e.g. on a quiet group, when replicast ACKs were not requested, or after all expected members have already acknowledged), an unexpected GRP_ACK_MSG with acked > m->bc_acked passes less_eq() and unconditionally decrements grp->bc_ackers. Because bc_ackers is a u16, this wraps to 65535, causing tipc_group_bc_cong() to permanently report congestion and blocking all future group broadcasts on the socket. 2. During an active broadcast round (grp->bc_ackers > 0), the sender transmits packet S and advances grp->bc_snd_nxt to S + 1. Receivers increment their expected counter to S + 1 upon consuming packet S, so the only valid ACK value for the current round is strictly acked == grp->bc_snd_nxt. However, tipc_group_update_bc_members() initializes each member's m->bc_acked to prev = grp->bc_snd_nxt - 1 (S - 1 before increment). This leaves a 2-sequence gap (S - 1 to S + 1) in sequence space. An incoming ACK is therefore neither rejected as duplicate nor prevented from decrementing grp->bc_ackers if an unexpected or stale value (such as S) is received. A member sending acked = S followed by acked = S + 1 could decrement grp->bc_ackers twice in the same round, prematurely clearing bc_ackers or underflowing it. Fix this by: - Dropping GRP_ACK_MSG immediately if grp->bc_ackers is zero. - Requiring acked == grp->bc_snd_nxt and rejecting duplicates where m->bc_acked == acked. Because replicast broadcast rounds are strictly sequential, only grp->bc_snd_nxt can be acknowledged, and each member can acknowledge at most once per round. Note that a related pre-existing issue in tipc_group_delete_member() (where grp->bc_ackers decrementing to zero upon member departure does not restore *grp->open or trigger a socket wakeup) will be addressed in a separate patch. Fixes: 48a5fe38772b ("tipc: fix bc_ackers underflow on duplicate GRP_ACK_MSG") Fixes: 2f487712b893 ("tipc: guarantee that group broadcast doesn't bypass group unicast") Reported-by: James Burton Cc: stable@vger.kernel.org Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260913044233.193927-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/tipc/group.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/tipc/group.c b/net/tipc/group.c index 14e6732624e2..74f6d3dac078 100644 --- a/net/tipc/group.c +++ b/net/tipc/group.c @@ -797,10 +797,10 @@ void tipc_group_proto_rcv(struct tipc_group *grp, bool *usr_wakeup, tipc_group_open(m, usr_wakeup); return; case GRP_ACK_MSG: - if (!m) + if (!m || !grp->bc_ackers) return; acked = msg_grp_bc_acked(hdr); - if (less_eq(acked, m->bc_acked)) + if (acked != grp->bc_snd_nxt || m->bc_acked == acked) return; m->bc_acked = acked; if (--grp->bc_ackers) From 47abe7a5c4eb53269aca3506446f851572a059a3 Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Tue, 15 Sep 2026 22:08:16 +0700 Subject: [PATCH 2034/2366] net/sched: act_ct: don't WARN on benign flow_offload_alloc() failure flow_offload_alloc() returns NULL when the conntrack entry is dying (e.g. raced with a conntrack flush) or when the GFP_ATOMIC allocation fails; both are expected under load and neither is a kernel bug. This path runs from softirq on every committed packet, so with panic_on_warn=1 an unprivileged user can panic the box just by racing a conntrack flush against a `tc ... action ct commit` classifier. Reproduced with a custom repro under QEMU: a small, fixed set of UDP flows through `tc filter ... action ct commit` on lo, raced against threads flooding bare ctnetlink CT_DELETE (flush) requests. Hits WARNING: net/sched/act_ct.c:437 (tcf_ct_flow_table_add(), inlined into tcf_ct_act() in this build) within ~15s on the unpatched kernel; same setup is clean on the patched kernel. The fix itself is behavior-preserving: both branches already did `goto err_alloc` before and after, only the WARN is removed. Fixes: 64ff70b80fd4 ("net/sched: act_ct: Offload established connections to flow table") Reported-by: syzbot+6cc37aba98dac721c415@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6cc37aba98dac721c415 Signed-off-by: Nguyen Ngoc Thang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260915150816.36487-1-ngocthang2710.1999@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/act_ct.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index 9080cb386c16..55f3521edb4c 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -432,11 +432,10 @@ static void tcf_ct_flow_table_add(struct tcf_ct_flow_table *ct_ft, if (test_and_set_bit(IPS_OFFLOAD_BIT, &ct->status)) return; + /* NULL if ct is dying (raced flush) or the atomic alloc failed. */ entry = flow_offload_alloc(ct); - if (!entry) { - WARN_ON_ONCE(1); + if (!entry) goto err_alloc; - } if (tcp) { ct->proto.tcp.seen[0].flags |= IP_CT_TCP_FLAG_BE_LIBERAL; From 2566866fc30965d915d0b52b5c3323b362619f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Tue, 15 Sep 2026 12:48:07 +0000 Subject: [PATCH 2035/2366] net: gue: reject invalid REMCSUM offsets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The REMCSUM option carries an absolute checksum start and checksum field offset. gue_remcsum() passes them to skb_remcsum_process(), whose partial path stores offset - start in the u16 skb->csum_offset variable. If offset is less than start, this underflows. A forwarded packet can retain CHECKSUM_PARTIAL and reach a NETIF_F_HW_CSUM driver which trusts the metadata, leading skb_copy_and_csum_dev() to write two bytes about 64 KiB beyond the destination buffer. Reject reversed tuples in validate_gue_flags(), after the existing length validation, so all GUE parsers enforce the ordering in one place. Fixes: fe881ef11cf0 ("gue: Use checksum partial with remote checksum offload") Signed-off-by: Jérémy Jean Link: https://patch.msgid.link/20260915124806.2852293-2-Jeremy.Jean@oss.cyber.gouv.fr Signed-off-by: Jakub Kicinski --- include/net/gue.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/include/net/gue.h b/include/net/gue.h index caefd6da8693..d377155fd0b3 100644 --- a/include/net/gue.h +++ b/include/net/gue.h @@ -84,8 +84,9 @@ static inline size_t guehdr_priv_flags_len(__be32 flags) } /* Validate standard and private flags. Returns non-zero (meaning invalid) - * if there is an unknown standard or private flags, or the options length for - * the flags exceeds the options length specific in hlen of the GUE header. + * if there is an unknown standard or private flags, if the options length for + * the flags exceeds the options length specified in hlen of the GUE header, or + * if a private option contains invalid data. */ static inline int validate_gue_flags(struct guehdr *guehdr, size_t optlen) { @@ -103,8 +104,8 @@ static inline int validate_gue_flags(struct guehdr *guehdr, size_t optlen) /* Private flags are last four bytes accounted in * guehdr_flags_len */ - __be32 pflags = *(__be32 *)((void *)&guehdr[1] + - len - GUE_LEN_PRIV); + void *data = (void *)&guehdr[1] + len; + __be32 pflags = *(__be32 *)(data - GUE_LEN_PRIV); if (pflags & ~GUE_PFLAGS_ALL) return 1; @@ -112,6 +113,16 @@ static inline int validate_gue_flags(struct guehdr *guehdr, size_t optlen) len += guehdr_priv_flags_len(pflags); if (len > optlen) return 1; + + if (pflags & GUE_PFLAG_REMCSUM) { + __be16 *pd = data; + + /* The field offset pd[1] must not be less + * than the start pd[0]. + */ + if (ntohs(pd[1]) < ntohs(pd[0])) + return 1; + } } return 0; From 310d1ac61a4d5a2ca8356a3a48d263acf54503ce Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 16 Sep 2026 15:30:13 +0200 Subject: [PATCH 2036/2366] net: ethernet: mtk_eth_soc: unregister net_devices in case of probe failure If register_netdev() fails for one of the MTK_MAX_DEVS devices in mtk_probe(), the error path jumps to err_deinit_ppe, skipping mtk_unreg_dev(). The previously registered net_devices are then freed by mtk_free_dev() while still in NETREG_REGISTERED state, hitting the BUG_ON(dev->reg_state != NETREG_UNREGISTERED). Route the register_netdev() failure to err_unreg_netdev so the net_devices registered so far are properly unregistered before being freed. Fixes: 8a8a9e89f801 ("net: ethernet: mediatek: cleanup error path inside mtk_hw_init") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260916-mtk_eth_soc-netdev-fix-v1-1-5dac50eb65b1@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mediatek/mtk_eth_soc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c index fd7a49ae88d0..2ea5dfe85539 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -4509,6 +4509,10 @@ static int mtk_unreg_dev(struct mtk_eth *eth) mac = netdev_priv(eth->netdev[i]); if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) unregister_netdevice_notifier(&mac->device_notifier); + + if (eth->netdev[i]->reg_state != NETREG_REGISTERED) + continue; + unregister_netdev(eth->netdev[i]); } @@ -5344,7 +5348,7 @@ static int mtk_probe(struct platform_device *pdev) err = register_netdev(eth->netdev[i]); if (err) { dev_err(eth->dev, "error bringing up device\n"); - goto err_deinit_ppe; + goto err_unreg_netdev; } else netif_info(eth, probe, eth->netdev[i], "mediatek frame engine at 0x%08lx, irq %d\n", From 24fedc7a569bce181728f0dd616504bef9f1513b Mon Sep 17 00:00:00 2001 From: Andy Moreton Date: Wed, 16 Sep 2026 13:56:41 +0100 Subject: [PATCH 2037/2366] sfc: add X4D PF support X4D is an X4 controller instance as an IP block in an SoC. It has the same feature set as X4. Signed-off-by: Andy Moreton Reviewed-by: Pieter Jansen van Vuuren Reviewed-by: Alejandro Lucero Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260916125641.12238-1-alucerop@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/sfc/efx.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/sfc/efx.c b/drivers/net/ethernet/sfc/efx.c index 3806cd3dd7f4..953f1d90b9b1 100644 --- a/drivers/net/ethernet/sfc/efx.c +++ b/drivers/net/ethernet/sfc/efx.c @@ -904,6 +904,10 @@ static const struct pci_device_id efx_pci_table[] = { .driver_data = (unsigned long)&efx_x4_nic_type}, {PCI_DEVICE(PCI_VENDOR_ID_SOLARFLARE, 0x2c03), /* X4 PF (FF only) */ .driver_data = (unsigned long)&efx_x4_nic_type}, + {PCI_DEVICE(PCI_VENDOR_ID_SOLARFLARE, 0x8c03), /* X4D PF (FF/LL) */ + .driver_data = (unsigned long)&efx_x4_nic_type}, + {PCI_DEVICE(PCI_VENDOR_ID_SOLARFLARE, 0xac03), /* X4D PF (FF only) */ + .driver_data = (unsigned long)&efx_x4_nic_type}, {0} /* end of list */ }; From ee319bd3a0e976af5087cbe59ebc50a66f31d202 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Wed, 16 Sep 2026 21:57:53 +0200 Subject: [PATCH 2038/2366] ipv6: do not let ipv6_find_hdr() return an offset past the packet end ipv6_find_hdr() walks the extension header chain, skipping each header by the length that header itself declares. ipv6_optlen() returns up to 2048, and the skip is never checked against skb->len, so the offset stored in *offset can point past the end of the packet. openvswitch installs that offset as the transport header, and update_ipv6_checksum() then reads and writes the transport checksum field out of bounds: BUG: KASAN: slab-use-after-free in inet_proto_csum_replace16+0x445/0x470 Read of size 2 at addr ffff88810b754b06 by task ovs_ipv6_oob/629 CPU: 4 UID: 1000 PID: 629 Comm: ovs_ipv6_oob Tainted: G N 7.3.0-rc3+ #348 Call Trace: inet_proto_csum_replace16+0x445/0x470 set_ipv6_addr+0x3dd/0x460 do_execute_actions+0x6a3d/0x7c40 ovs_execute_actions+0xfd/0x480 ovs_packet_cmd_execute+0xc38/0xf20 genl_rcv_msg+0x59e/0x870 netlink_rcv_skb+0x18b/0x450 genl_rcv+0x2d/0x40 netlink_unicast+0x6bc/0xa20 The buggy address belongs to the object at ffff88810b754980 which belongs to the cache skbuff_small_head of size 704 The buggy address is located 390 bytes inside of freed 704-byte region [ffff88810b754980, ffff88810b754c40) Other callers use that offset too, so bound it here rather than in one caller. Reject a header whose declared length does not fit in the packet. ipv6_find_hdr() already fails with -EBADMSG on a malformed chain, so this adds no new failure mode. Fixes: f8f626754ebe ("ipv6: Move ipv6_find_hdr() out of Netfilter code.") Suggested-by: Ilya Maximets Suggested-by: Eric Dumazet Cc: stable@vger.kernel.org Signed-off-by: Norbert Szetei Reviewed-by: Ido Schimmel Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/8F80BA1A-DDFD-432D-9075-242A3435FEB5@doyensec.com Signed-off-by: Jakub Kicinski --- net/ipv6/exthdrs_core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv6/exthdrs_core.c b/net/ipv6/exthdrs_core.c index 9d06d487e8b1..4a9748338cf4 100644 --- a/net/ipv6/exthdrs_core.c +++ b/net/ipv6/exthdrs_core.c @@ -278,6 +278,9 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset, hdrlen = ipv6_optlen(hp); if (!found) { + if (skb->len - start < hdrlen) + return -EBADMSG; + nexthdr = hp->nexthdr; start += hdrlen; } From dd47bcf279f1083f09bf5266890b26263361022b Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 16 Sep 2026 23:09:24 +0000 Subject: [PATCH 2039/2366] ip6_gre: Call ip6erspan_tunnel_unlink_md() in ip6erspan_changelink(). The cited commit accidentally added ip6gre_tunnel_unlink_md() in ip6erspan_changelink(). Let's correct it to ip6erspan_tunnel_unlink_md(). Fixes: b80d0b93b991 ("net: ip6_gre: fix tunnel metadata device sharing.") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Xuanqiang Luo Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260916230927.378957-1-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_gre.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index 8ebda0b6a78b..e61cb10b50dc 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -2279,7 +2279,7 @@ static int ip6erspan_changelink(struct net_device *dev, struct nlattr *tb[], return PTR_ERR(t); ip6erspan_set_version(data, &p); - ip6gre_tunnel_unlink_md(ign, t); + ip6erspan_tunnel_unlink_md(ign, t); ip6gre_tunnel_unlink(ign, t); ip6erspan_tnl_change(t, &p, !tb[IFLA_MTU]); ip6erspan_tunnel_link_md(ign, t); From 95c4d54ed02283e9a09e8cd7360e384daa67a741 Mon Sep 17 00:00:00 2001 From: Abhishek Ojha Date: Wed, 16 Sep 2026 19:19:28 -0400 Subject: [PATCH 2040/2366] net: phy: micrel: Advance register data pointer in write loop lanphy_write_reg_data() does not advance the data pointer while iterating over the register table. As a result, it writes the first entry num times and leaves the remaining errata registers unconfigured. Single-entry tables are unaffected, but tables with multiple entries leave every entry after the first unapplied. Advance the data pointer after each successful write so every table entry is applied in order. Fixes: c8732e933925 ("net: phy: micrel: lan8842 errata") Cc: stable@vger.kernel.org Signed-off-by: Abhishek Ojha Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260916231928.1336305-1-abhishek.ojha@savoirfairelinux.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/micrel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index ae830781824b..5c8461db7b4b 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -6344,6 +6344,7 @@ static int lanphy_write_reg_data(struct phy_device *phydev, data->val); if (ret) break; + data++; } return ret; From 1b82958f3f035df5ccaab5430a2302f08a5d5351 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Mon, 14 Sep 2026 14:09:57 -0700 Subject: [PATCH 2041/2366] net: ethtool: keep rtnl_lock for the ioctl self test An offline self test that brings the interface down and back up with netif_close() / netif_open() requires rtnl_lock for both. Since the ethtool IOCTL path became rtnl-optional for ops-locked drivers, the ETHTOOL_TEST ioctl runs holding only the netdev instance lock, so on an ops-locked driver the self test now tears the device down without rtnl_lock. With lockdep this reproduces deterministically on every offline self test on such a driver; note the sole lock held is the instance lock, not rtnl: WARNING: suspicious RCU usage net/core/netpoll.c:207 suspicious rcu_dereference_protected() usage! 1 lock held by ethtool/107: #0: (&dev->lock){+.+.}, at: dev_ethtool Call Trace: netpoll_poll_disable __dev_close_many netif_close_many netif_close fbnic_self_test dev_ethtool_locked dev_ethtool dev_ioctl sock_ioctl __x64_sys_ioctl Without lockdep the same condition trips ASSERT_RTNL() in __dev_close_many() / __dev_open(); that check only samples the global rtnl state, so it can be masked by a concurrent rtnl holder, but the device is still being reconfigured without the lock it requires. The ethtool self_test is a legacy ioctl-only command, so an ETHTOOL_TEST case is only needed on the ioctl path. Add an opt-in bit for drivers whose self test needs rtnl_lock and set it on the ops-locked drivers whose offline self test tears the interface down and up: - fbnic (ops-locked via queue_mgmt_ops): fbnic_self_test() offline path uses netif_close() / netif_open(). - bnxt (ops-locked via queue_mgmt_ops): bnxt_self_test() offline path goes through bnxt_close_nic() / bnxt_half_open_nic() / bnxt_half_close_nic() / bnxt_open_nic(), which close and reopen the device. Fixes: f994752b1127 ("net: ethtool: optionally skip rtnl_lock on IOCTL path") Signed-off-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/178942019771.7700.338431553546884773.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 3 ++- drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c | 3 ++- include/linux/ethtool.h | 2 ++ net/ethtool/common.h | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c index 62bc9cae613c..622e89587e5d 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c @@ -5733,7 +5733,8 @@ const struct ethtool_ops bnxt_ethtool_ops = { .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | ETHTOOL_OP_NEEDS_RTNL_SCOALESCE | - ETHTOOL_OP_NEEDS_RTNL_RSS, + ETHTOOL_OP_NEEDS_RTNL_RSS | + ETHTOOL_OP_NEEDS_RTNL_TEST, .supported_coalesce_params = ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_MAX_FRAMES | ETHTOOL_COALESCE_USECS_IRQ | diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c index 0e47088ec44b..423f179c9d47 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c @@ -2025,7 +2025,8 @@ static const struct ethtool_ops fbnic_ethtool_ops = { ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM | ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | - ETHTOOL_OP_NEEDS_RTNL_GLINK, + ETHTOOL_OP_NEEDS_RTNL_GLINK | + ETHTOOL_OP_NEEDS_RTNL_TEST, .get_drvinfo = fbnic_get_drvinfo, .get_regs_len = fbnic_get_regs_len, .get_regs = fbnic_get_regs, diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 253600c0eccd..c4c9ce038611 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -944,6 +944,7 @@ struct kernel_ethtool_ts_info { #define ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM BIT(6) #define ETHTOOL_OP_NEEDS_RTNL_RSS BIT(7) #define ETHTOOL_OP_NEEDS_RTNL_GLINK BIT(8) +#define ETHTOOL_OP_NEEDS_RTNL_TEST BIT(9) /** * struct ethtool_ops - optional netdev operations @@ -981,6 +982,7 @@ struct kernel_ethtool_ts_info { * - netdev_update_features() * - netif_set_real_num_tx_queues() * - ethtool_op_get_link() (syncs link watch under rtnl_lock) + * - netif_open() / netif_close() (used by @self_test) * * @get_drvinfo: Report driver/device information. Modern drivers no * longer have to implement this callback. Most fields are diff --git a/net/ethtool/common.h b/net/ethtool/common.h index 4e5356e26f40..ae32e7fdb563 100644 --- a/net/ethtool/common.h +++ b/net/ethtool/common.h @@ -163,6 +163,8 @@ ethtool_ioctl_needs_rtnl(const struct net_device *dev, u32 ethcmd) return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_RSS; case ETHTOOL_GLINK: return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GLINK; + case ETHTOOL_TEST: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_TEST; } return false; } From 1f4c73064a50f53d596c6f1d06d2d700f43c4b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20T=C3=B6pel?= Date: Mon, 14 Sep 2026 14:10:04 -0700 Subject: [PATCH 2042/2366] eth: fbnic: Handle maximum standalone channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standalone channels use one NAPI vector for each Tx and Rx queue. fbnic's allocation path excludes FBNIC_MAX_TXQS from that layout. A 64-Tx/64-Rx configuration therefore records 128 vectors but allocates only 64, leaving NULL entries that resource setup dereferences. Include the maximum vector count in standalone allocation. Fixes: bc6107771bb4 ("eth: fbnic: Allocate a netdevice and napi vectors with queues") Signed-off-by: Björn Töpel Reviewed-by: Simon Horman Link: https://patch.msgid.link/178942020457.7700.13129750616387075931.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index 661dee1661af..a30aa4450848 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -1791,7 +1791,7 @@ int fbnic_alloc_napi_vectors(struct fbnic_net *fbn) int err; /* Allocate 1 Tx queue per napi vector */ - if (num_napi < FBNIC_MAX_TXQS && num_napi == num_tx + num_rx) { + if (num_napi <= FBNIC_MAX_TXQS && num_napi == num_tx + num_rx) { while (num_tx) { err = fbnic_alloc_napi_vector(fbd, fbn, num_napi, v_idx, From b5d9e9d4d0c13bc8b60d8d97e7a07fb25fea639e Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Mon, 14 Sep 2026 14:10:11 -0700 Subject: [PATCH 2043/2366] eth: fbnic: use the Rx queue napi pointer to find the napi vector The queue management ndos pick the napi vector for an Rx queue with: nv = fbn->napi[idx % fbn->num_napi]; The issue is this is only correct in the cases where there are no standalone Tx vectors. In those cases we were allocating the Tx vectors first and then the Rx so the queues would be pointing to Tx NAPI vectors instead of the Rx ones. The mapping the ndos want is already recorded. fbnic_set_netif_napi() publishes it with netif_queue_set_napi(), which stores the napi pointer in netdev_rx_queue.napi, and fbnic_reset_netif_napi() clears it again. Both run under the netdev instance lock that the queue management ndos also hold, so the pointer can be read directly. Use it and drop the divide. The pointer is NULL exactly while the datapath is down, so fbnic_queue_mem_alloc() can reject that case rather than reaching into freed state: netdev_rx_queue_restart() calls it before it tests netif_running(), and fbnic_pm_suspend() leaves netif_running() true across a PCIe recovery that never completes, so a queue restart can arrive after fbnic_stop() has freed the rings and the vectors. fbnic_stop() clears the association in fbnic_reset_netif_queues() before fbnic_free_napi_vectors(), so the NULL is always published first. fbnic_queue_start() and fbnic_queue_stop() need no check of their own, as netdev_rx_queue_reconfig() only reaches them once fbnic_queue_mem_alloc() has succeeded under the same instance lock. Fixes: da43127a8edc ("eth: fbnic: support queue ops / zero-copy Rx") Signed-off-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/178942021136.7700.4391219358260544104.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 26 +++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index a30aa4450848..10caacffee0f 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -2853,6 +2854,17 @@ void fbnic_napi_depletion_check(struct net_device *netdev) fbnic_wrfl(fbd); } +/* Returns the napi vector servicing an Rx queue, or NULL if the datapath + * is torn down. The association is published by fbnic_set_netif_napi() + * and cleared by fbnic_reset_netif_napi(), both under the instance lock. + */ +static struct fbnic_napi_vector *fbnic_rxq_nv(struct net_device *dev, int idx) +{ + struct napi_struct *napi = __netif_get_rx_queue(dev, idx)->napi; + + return napi ? container_of(napi, struct fbnic_napi_vector, napi) : NULL; +} + static int fbnic_queue_mem_alloc(struct net_device *dev, struct netdev_queue_config *qcfg, void *qmem, int idx) @@ -2865,8 +2877,16 @@ static int fbnic_queue_mem_alloc(struct net_device *dev, if (!netif_running(dev)) return fbnic_alloc_qt_page_pools(fbn, qt, idx); + /* A failed PCIe recovery or resume can leave the datapath torn down + * while netif_running() is still true. This ndo runs before + * netdev_rx_queue_restart() checks netif_running(), so bail out + * rather than touching rings and vectors that are already freed. + */ + nv = fbnic_rxq_nv(dev, idx); + if (!nv) + return -ENETDOWN; + real = container_of(fbn->rx[idx], struct fbnic_q_triad, cmpl); - nv = fbn->napi[idx % fbn->num_napi]; fbnic_ring_init(&qt->sub0, real->sub0.doorbell, real->sub0.q_idx, real->sub0.flags); @@ -2917,7 +2937,7 @@ static int fbnic_queue_start(struct net_device *dev, struct fbnic_q_triad *real; real = container_of(fbn->rx[idx], struct fbnic_q_triad, cmpl); - nv = fbn->napi[idx % fbn->num_napi]; + nv = fbnic_rxq_nv(dev, idx); fbnic_aggregate_ring_bdq_counters(fbn, &real->sub0); fbnic_aggregate_ring_bdq_counters(fbn, &real->sub1); @@ -2939,7 +2959,7 @@ static int fbnic_queue_stop(struct net_device *dev, void *qmem, int idx) int err; real = container_of(fbn->rx[idx], struct fbnic_q_triad, cmpl); - nv = fbn->napi[idx % fbn->num_napi]; + nv = fbnic_rxq_nv(dev, idx); fbnic_dbg_nv_exit(nv); napi_disable_locked(&nv->napi); From 4bcc4a92c603fe7f062cea22e20da2e0ad6b12c3 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Mon, 14 Sep 2026 14:10:18 -0700 Subject: [PATCH 2044/2366] eth: fbnic: reset num_napi when the napi vectors are freed fbn->num_napi is the count of live napi vectors, each of which owns an IRQ. The PM path had freed them without clearing the count. fbnic_pm_suspend() tears the datapath down via ndo_stop() and frees the IRQs, but leaves netif_running() true so resume knows to re-open. Resume rebuilds the datapath in __fbnic_pm_resume() and fbnic_reset_queues() sets num_napi and __fbnic_open() re-allocates the vectors. When the datapath is torn down but never rebuilt, num_napi is left pointing at freed vectors under 2 different scenarios: - a PCIe error recovery that fails (fbnic_err_slot_reset() -> __fbnic_pm_resume() returns an error -> PCI_ERS_RESULT_DISCONNECT), so .resume never runs; or - an __fbnic_open() that fails partway on resume and unwinds, freeing the vectors after fbnic_reset_queues() has already set num_napi. The netdev is then running with num_napi > 0 but napi[] freed, and the eventual remove/unbind close re-enters fbnic_down() -> fbnic_dbg_down() and dereferences the freed vectors: BUG: kernel NULL pointer dereference, address: 0000000000000210 RIP: fbnic_dbg_down+0x28 Clear num_napi when the vectors are freed: in the suspend teardown (a good resume re-establishes it before __fbnic_open()) and on the resume open failure. A redundant ndo_stop() then walks an empty napi[]. The normal ndo_stop() down/up cycle is untouched and keeps num_napi for the next ndo_open(). Fixes: bc6107771bb4 ("eth: fbnic: Allocate a netdevice and napi vectors with queues") Signed-off-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/178942021809.7700.10804028989308077839.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_pci.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c index 8b9bc9e8ea56..c6698e3002a1 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c @@ -434,6 +434,7 @@ static int fbnic_pm_suspend(struct device *dev) { struct fbnic_dev *fbd = dev_get_drvdata(dev); struct net_device *netdev = fbd->netdev; + struct fbnic_net *fbn; if (fbnic_init_failure(fbd)) goto null_uc_addr; @@ -441,11 +442,16 @@ static int fbnic_pm_suspend(struct device *dev) rtnl_lock(); netdev_lock(netdev); + fbn = netdev_priv(netdev); + netif_device_detach(netdev); if (netif_running(netdev)) netdev->netdev_ops->ndo_stop(netdev); + /* The IRQs are about to be freed, so drop the napi vector count */ + fbn->num_napi = 0; + netdev_unlock(netdev); rtnl_unlock(); @@ -508,16 +514,20 @@ static int __fbnic_pm_resume(struct device *dev) if (fbnic_init_failure(fbd)) return 0; + rtnl_lock(); + netdev_lock(netdev); + fbn = netdev_priv(netdev); /* Reset the queues if needed */ fbnic_reset_queues(fbn, fbn->num_tx_queues, fbn->num_rx_queues); - rtnl_lock(); - netdev_lock(netdev); - - if (netif_running(netdev)) + if (netif_running(netdev)) { err = __fbnic_open(fbn); + /* On failure the vectors are freed, so drop the count */ + if (err) + fbn->num_napi = 0; + } netdev_unlock(netdev); rtnl_unlock(); From 8947f13e436a4ff5eed9f8f019b2865a07af4bb2 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Mon, 14 Sep 2026 14:10:25 -0700 Subject: [PATCH 2045/2366] eth: fbnic: Set AW_FLUSH_MODE alongside AW_FLUSH when flushing the mailbox When tearing down the FW mailbox Rx ring, fbnic_mbx_reset_desc_ring() writes AW_CFG with FLUSH set and everything else, BME included, cleared. Clearing BME halts the device's writes to the host but leaves the staged requests parked in the PUL write pipeline rather than draining them, so on the write path FLUSH alone never terminates the outstanding requests and the flush the firmware waits on never completes. Add the FLUSH_MODE definition and set both bits so the staged writes drain out of the pipeline on their own. BME stays cleared, so nothing lands on the host; it is restored later in fbnic_mbx_init_desc_ring() when the ring is rebuilt, once the outstanding writes are gone. The read path is unaffected. AR_CFG has no equivalent mode bit and AR_FLUSH terminates the outstanding reads by itself, so it is left as is. Both writes remain plain stores rather than read-modify-writes. That is deliberate: the matching write in fbnic_mbx_init_desc_ring() restores BME and the TLP attributes, and clears both flush bits as a side effect. Fixes: 3b12f00ddd08 ("fbnic: Gate AXI read/write enabling on FW mailbox") Signed-off-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/178942022583.7700.11050671998277309744.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_csr.h | 1 + drivers/net/ethernet/meta/fbnic/fbnic_fw.c | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h index 64b958df7774..14af30e189d6 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h @@ -974,6 +974,7 @@ enum { /* PUL User Registers */ #define FBNIC_CSR_START_PUL_USER 0x31000 /* CSR section delimiter */ #define FBNIC_PUL_OB_TLP_HDR_AW_CFG 0x3103d /* 0xc40f4 */ +#define FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH_MODE CSR_BIT(20) #define FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH CSR_BIT(19) #define FBNIC_PUL_OB_TLP_HDR_AW_CFG_BME CSR_BIT(18) #define FBNIC_PUL_OB_TLP_HDR_AW_CFG_RDE_ATTR CSR_GENMASK(17, 15) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c index 283d25fae79e..59aa879798b9 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c @@ -60,8 +60,15 @@ static void fbnic_mbx_reset_desc_ring(struct fbnic_dev *fbd, int mbx_idx) */ switch (mbx_idx) { case FBNIC_IPC_MBX_RX_IDX: + /* Clearing BME blocks the device from writing to the host + * but leaves the requests parked in the write pipeline. The + * write path only clears outstanding requests when both FLUSH + * and FLUSH_MODE are set; FLUSH_MODE lets them drain without + * landing on the host. + */ wr32(fbd, FBNIC_PUL_OB_TLP_HDR_AW_CFG, - FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH); + FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH | + FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH_MODE); break; case FBNIC_IPC_MBX_TX_IDX: wr32(fbd, FBNIC_PUL_OB_TLP_HDR_AR_CFG, From 1b97a269a5bdde20d4e69511f27649c9cb82b7c7 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Mon, 14 Sep 2026 14:10:33 -0700 Subject: [PATCH 2046/2366] eth: fbnic: Handle FW mailbox completions flagged with an error The firmware can complete a mailbox descriptor while also setting FW_ERR to indicate it could not process the request, for example on a mailbox DMA error. The completion carries no valid data. The driver did not check FW_ERR. On the Rx mailbox it would sync and parse the stale page as a normal message, and on the Tx mailbox it silently freed the request. If the initial capabilities exchange in fbnic_mbx_poll_tx_ready() hit FW_ERR -- on the Tx request or on the Rx response descriptor -- no response was parsed and the poll spun until it timed out even though the ring was healthy. Check FW_ERR on both mailboxes. Count it per-mailbox in fbnic_fw_mbx.resp_error, which is also shown in debugfs, warn (rate limited, since the bit is firmware controlled), and drop the Rx page instead of parsing it. In fbnic_mbx_poll_tx_ready() re-issue the capabilities request when either the Tx or the Rx resp_error counter advances, so a FW_ERR on the request or on its response triggers a retry rather than a timeout. A valid capabilities response is honored before the retry check, so a response parsed in the same poll as an unrelated FW_ERR is not discarded. The counters are mailbox-wide rather than keyed to the capabilities request; that is sufficient here because the exchange runs during bring-up before any other mailbox traffic, and any spurious retry is bounded by the existing 10s timeout. Fixes: da3cde08209e ("eth: fbnic: Add FW communication mechanism") Signed-off-by: Alexander Duyck Reviewed-by: Simon Horman Link: https://patch.msgid.link/178942023343.7700.9423398932961964439.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_csr.h | 4 ++ .../net/ethernet/meta/fbnic/fbnic_debugfs.c | 4 +- drivers/net/ethernet/meta/fbnic/fbnic_fw.c | 38 ++++++++++++++++++- drivers/net/ethernet/meta/fbnic/fbnic_fw.h | 1 + 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h index 14af30e189d6..baba3471bf5a 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h @@ -1216,6 +1216,10 @@ enum { #define FBNIC_IPC_MBX_DESC_LEN_MASK DESC_GENMASK(63, 48) #define FBNIC_IPC_MBX_DESC_EOM DESC_BIT(46) #define FBNIC_IPC_MBX_DESC_ADDR_MASK DESC_GENMASK(45, 3) +/* Set with FW_CMPL when the FW completed a descriptor without successfully + * processing it (e.g. a mailbox DMA error); the completion has no valid data. + */ +#define FBNIC_IPC_MBX_DESC_FW_ERR DESC_BIT(2) #define FBNIC_IPC_MBX_DESC_FW_CMPL DESC_BIT(1) #define FBNIC_IPC_MBX_DESC_HOST_CMPL DESC_BIT(0) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c b/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c index 3c4563c8f403..6edfa0aa69f1 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c @@ -539,8 +539,8 @@ static void fbnic_dbg_fw_mbx_display(struct seq_file *s, /* Generate header */ seq_puts(s, mbx_idx == FBNIC_IPC_MBX_RX_IDX ? "Rx\n" : "Tx\n"); - seq_printf(s, "Rdy: %d Head: %d Tail: %d\n", - mbx->ready, mbx->head, mbx->tail); + seq_printf(s, "Rdy: %d Head: %d Tail: %d resp_error: %llu\n", + mbx->ready, mbx->head, mbx->tail, mbx->resp_error); snprintf(hdr, sizeof(hdr), "%3s %-4s %s %-12s %s %-3s %-16s\n", "Idx", "Len", "E", "Addr", "F", "H", "Raw"); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c index 59aa879798b9..6d7eb8479edf 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c @@ -292,6 +292,12 @@ static void fbnic_mbx_process_tx_msgs(struct fbnic_dev *fbd) if (!(desc & FBNIC_IPC_MBX_DESC_FW_CMPL)) break; + if (desc & FBNIC_IPC_MBX_DESC_FW_ERR) { + tx_mbx->resp_error++; + dev_warn_ratelimited(fbd->dev, + "FW completed a Tx mailbox request with an error\n"); + } + fbnic_mbx_unmap_and_free_msg(fbd, FBNIC_IPC_MBX_TX_IDX, head); head++; @@ -1673,6 +1679,13 @@ static void fbnic_mbx_process_rx_msgs(struct fbnic_dev *fbd) if (!(desc & FBNIC_IPC_MBX_DESC_FW_CMPL)) break; + if (desc & FBNIC_IPC_MBX_DESC_FW_ERR) { + rx_mbx->resp_error++; + dev_warn_ratelimited(fbd->dev, + "FW reported an error on an Rx mailbox message; dropping\n"); + goto next_page; + } + dma_sync_single_for_cpu(fbd->dev, rx_mbx->buf_info[head].addr, FBNIC_RX_PAGE_SIZE, DMA_FROM_DEVICE); @@ -1740,7 +1753,9 @@ void fbnic_mbx_poll(struct fbnic_dev *fbd) int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd) { struct fbnic_fw_mbx *tx_mbx = &fbd->mbx[FBNIC_IPC_MBX_TX_IDX]; + struct fbnic_fw_mbx *rx_mbx = &fbd->mbx[FBNIC_IPC_MBX_RX_IDX]; unsigned long timeout = jiffies + 10 * HZ + 1; + u64 tx_resp_error, rx_resp_error; int err, i; do { @@ -1771,6 +1786,9 @@ int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd) * mgmt.version once we get the actual version from the firmware * in the capabilities request message. */ +send_cap_req: + tx_resp_error = tx_mbx->resp_error; + rx_resp_error = rx_mbx->resp_error; err = fbnic_fw_xmit_simple_msg(fbd, FBNIC_TLV_MSG_ID_HOST_CAP_REQ); if (err) goto clean_mbx; @@ -1788,9 +1806,27 @@ int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd) msleep(20); fbnic_mbx_poll(fbd); + /* A valid capabilities response ends the poll. Check it + * before the FW_ERR retry below so a response parsed in the + * same poll as an unrelated FW_ERR is not discarded. + */ + if (fbd->fw_cap.running.mgmt.version >= MIN_FW_VER_CODE) + break; + /* set err, but wait till mgmt.version check to report it */ - if (!time_is_after_jiffies(timeout)) + if (!time_is_after_jiffies(timeout)) { err = -ETIMEDOUT; + continue; + } + + /* The FW can flag our capabilities request (Tx) or its + * response (Rx) with FW_ERR, in which case it produced no + * usable response. The ring is not wedged, so re-issue the + * request instead of spinning until the timeout. + */ + if (tx_mbx->resp_error != tx_resp_error || + rx_mbx->resp_error != rx_resp_error) + goto send_cap_req; } return 0; diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.h b/drivers/net/ethernet/meta/fbnic/fbnic_fw.h index d84723e4cfa3..5f9969247e30 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.h @@ -13,6 +13,7 @@ struct fbnic_tlv_msg; struct fbnic_fw_mbx { u8 ready, head, tail; + u64 resp_error; struct { struct fbnic_tlv_msg *msg; dma_addr_t addr; From d2c31b837406395e576afeb25958c98e9938f3f6 Mon Sep 17 00:00:00 2001 From: Yiqi Sun Date: Tue, 15 Sep 2026 17:50:17 +0800 Subject: [PATCH 2047/2366] sctp: avoid livelock while updating retransmit path sctp_assoc_update_retran_path() can loop forever when every remaining transport, including retran_path, is SCTP_UNCONFIRMED: the state check runs before the wraparound test, so the loop cannot observe that it has completed a full pass. Fix this by considering a transport only when it is not UNCONFIRMED, then checking whether the walk has returned to retran_path. This makes the full-pass termination independent of the transport state while preserving the existing fallback selection semantics. Also restore the NULL guard around the retran_path assignment. In the all-UNCONFIRMED case there is no eligible replacement transport, and installing NULL would leave later retransmit-path users and the debug print with a NULL path. Fixes: 4c47af4d5eb2 ("net: sctp: rework multihoming retransmission path selection to rfc4960") Signed-off-by: Yiqi Sun Acked-by: Xin Long Link: https://patch.msgid.link/20260915095017.942213-1-sunyiqixm@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/associola.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/net/sctp/associola.c b/net/sctp/associola.c index c0512c827d0f..4521be3bd85a 100644 --- a/net/sctp/associola.c +++ b/net/sctp/associola.c @@ -1289,18 +1289,19 @@ void sctp_assoc_update_retran_path(struct sctp_association *asoc) /* Manually skip the head element. */ if (&trans->transports == &asoc->peer.transport_addr_list) continue; - if (trans->state == SCTP_UNCONFIRMED) - continue; - trans_next = sctp_trans_elect_best(trans, trans_next); - /* Active is good enough for immediate return. */ - if (trans_next->state == SCTP_ACTIVE) - break; + if (trans->state != SCTP_UNCONFIRMED) { + trans_next = sctp_trans_elect_best(trans, trans_next); + /* Active is good enough for immediate return. */ + if (trans_next->state == SCTP_ACTIVE) + break; + } /* We've reached the end, time to update path. */ if (trans == asoc->peer.retran_path) break; } - asoc->peer.retran_path = trans_next; + if (trans_next) + asoc->peer.retran_path = trans_next; pr_debug("%s: association:%p updated new path to addr:%pISpc\n", __func__, asoc, &asoc->peer.retran_path->ipaddr.sa); From 4581c3d2adc3c73a019bc38db64ca11f28bbd7fd Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Thu, 17 Sep 2026 14:27:23 +0200 Subject: [PATCH 2048/2366] net/mlx5e: advertise MACsec offload only when supported Commit 339ccec8d43d ("net/mlx5: Enable MACsec offload feature for VLAN interface") added NETIF_F_HW_MACSEC unconditionally to vlan_features so that VLAN devices could inherit MACsec offload support. mlx5e_build_nic_netdev subsequently copies vlan_features into hw_features and features. As a result, all mlx5e NIC netdevices advertise MACsec hardware offload, even when the firmware does not support it and the driver does not install macsec_ops. Set the MACsec feature bits in mlx5e_macsec_build_netdev, after device capabilities have been validated. This preserves MACsec-over-VLAN support and the ethtool feature control on capable devices, without advertising either on unsupported hardware. Fixes: 339ccec8d43d ("net/mlx5: Enable MACsec offload feature for VLAN interface") Cc: stable@vger.kernel.org Reviewed-by: Tariq Toukan Signed-off-by: Ralf Lici Link: https://patch.msgid.link/20260917122724.654639-1-ralf@mandelbit.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c | 2 ++ drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c index daff53ba7d09..38a3415acf7a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c @@ -1724,6 +1724,8 @@ void mlx5e_macsec_build_netdev(struct mlx5e_priv *priv) mlx5_core_dbg(priv->mdev, "mlx5e: MACsec acceleration enabled\n"); netdev->macsec_ops = &macsec_offload_ops; netdev->features |= NETIF_F_HW_MACSEC; + netdev->hw_features |= NETIF_F_HW_MACSEC; + netdev->vlan_features |= NETIF_F_HW_MACSEC; netif_keep_dst(netdev); } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index fc110a7d16e8..4c6060d54fbe 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -5851,7 +5851,6 @@ static void mlx5e_build_nic_netdev(struct net_device *netdev) netdev->vlan_features |= NETIF_F_SG; netdev->vlan_features |= NETIF_F_HW_CSUM; - netdev->vlan_features |= NETIF_F_HW_MACSEC; netdev->vlan_features |= NETIF_F_GRO; netdev->vlan_features |= NETIF_F_TSO; netdev->vlan_features |= NETIF_F_TSO6; From 6c096bb08de97cdca051fecddad22cac6a1fd275 Mon Sep 17 00:00:00 2001 From: Quentin Armitage Date: Tue, 15 Sep 2026 22:33:21 +0100 Subject: [PATCH 2049/2366] net: allow IFLA_INET_CONF messages when NLA_F_NESTED unset Commit fa8fca88714c ("ipv4: validate IPV4_DEVCONF attributes properly") added validation of IFLA_INET_CONF attributes, and in the process changed the call of nla_for_each_nested() to nla_parse_nested(). A side effect of this change is that the IFLA_INET_CONF option is now tested for NLA_F_NESTED being set, and fails if it is not. Prior to the commit there was no check of NLA_F_NESTED. Change nla_parse_nested() to nla_parse(). This restores the previous functionality of not checking NLA_F_NESTED, thereby allowing code that (incorrectly) doesn't set NLA_F_NESTED to continue to work. This issue was identified because keepalived started logging errors when it was configuring macvlans that it created. Fixes: fa8fca88714c ("ipv4: validate IPV4_DEVCONF attributes properly") Signed-off-by: Quentin Armitage Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260915213320.1527029-2-quentin@armitage.org.uk Signed-off-by: Jakub Kicinski --- net/ipv4/devinet.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index a90be57c63be..5b6b11c943e4 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -2117,9 +2117,10 @@ static int inet_validate_link_af(const struct net_device *dev, return err; if (tb[IFLA_INET_CONF]) { - err = nla_parse_nested(nested_tb, IPV4_DEVCONF_MAX, - tb[IFLA_INET_CONF], inet_devconf_policy, - extack); + err = nla_parse(nested_tb, IPV4_DEVCONF_MAX, + nla_data(tb[IFLA_INET_CONF]), + nla_len(tb[IFLA_INET_CONF]), + inet_devconf_policy, extack); if (err < 0) return err; From 10396a2d6d41d594975b6ece712278570c3c970c Mon Sep 17 00:00:00 2001 From: Holger Dengler Date: Fri, 14 Aug 2026 16:20:10 +0200 Subject: [PATCH 2050/2366] crypto: s390/hmac - Generate intermediate CV for API partial block handling The API partial block handling requires a intermediate chaining value (CV). The internal function hash_data() sets the function code correctly, so also call cpacf_kimd() instruction for intermediate CV generation, as cpacf_klmd() always generate the final hash value. Cc: stable@vger.kernel.org # 6.15+ Fixes: 08811169ac01 ("crypto: s390/hmac - Use API partial block handling") Signed-off-by: Holger Dengler Reviewed-by: Harald Freudenberger Signed-off-by: Herbert Xu --- arch/s390/crypto/hmac_s390.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/s390/crypto/hmac_s390.c b/arch/s390/crypto/hmac_s390.c index f8cd09f341d4..445fa7bbd958 100644 --- a/arch/s390/crypto/hmac_s390.c +++ b/arch/s390/crypto/hmac_s390.c @@ -150,7 +150,10 @@ static int hash_data(const u8 *in, unsigned int inlen, #undef PARAM_INIT - cpacf_klmd(func, ¶m, in, inlen); + if (final) + cpacf_klmd(func, ¶m, in, inlen); + else + cpacf_kimd(func, ¶m, in, inlen); memcpy(digest, ¶m, digestsize); From 63edf5a009ae366369a1b484cd9ae4ee7c51946c Mon Sep 17 00:00:00 2001 From: "Chang S. Bae" Date: Wed, 16 Sep 2026 23:00:03 +0000 Subject: [PATCH 2051/2366] x86/build/64: Prevent native builds from generating EGPR use Omar reports that CONFIG_X86_NATIVE_CPU=y allows builds to opportunistically emit instructions using %r16-%r31 (EGPRs) when the build host supports APX since the commit: ea1dcca1de12 ("x86/kbuild/64: Add the CONFIG_X86_NATIVE_CPU option to locally optimize the kernel with '-march=native'") But the kernel is not yet prepared to use new registers internally. For example, there is no context-switch support for general in-kernel use. Explicitly disable EGPR use when building with -march=native. For C, since GCC 14 and Clang 18, both compilers support suppressing EGPR use with -mno-apx-features=egpr, whose availability can be detected via cc-option. For Rust, pass features=-apxf through the generated JSON to avoid unstable-feature warnings, see https://github.com/rust-lang/rust/issues/139284 Note Rust only accepts the option to disable APX instructions entirely or not. Support for this gating also depends on the Rust/LLVM combination. Rust 1.88 introduced the `apxf` feature option, but versions prior to 1.93 may emit an `apxf` attribute to the backend that only LLVM 23 or later can interpret. Restrict native Rust builds accordingly. Fixes: ea1dcca1de12 ("x86/kbuild/64: Add the CONFIG_X86_NATIVE_CPU option to locally optimize the kernel with '-march=native'") Reported-by: Omar Avelar Signed-off-by: Chang S. Bae Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Nathan Chancellor Acked-by: Miguel Ojeda Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260916230003.1144622-1-chang.seok.bae@intel.com --- arch/x86/Kconfig.cpu | 11 +++++++++++ arch/x86/Makefile | 5 +++++ scripts/generate_rust_target.rs | 5 +++++ 3 files changed, 21 insertions(+) diff --git a/arch/x86/Kconfig.cpu b/arch/x86/Kconfig.cpu index e4654388d794..6e7a366f0798 100644 --- a/arch/x86/Kconfig.cpu +++ b/arch/x86/Kconfig.cpu @@ -204,10 +204,21 @@ config CC_HAS_MARCH_NATIVE # usage warnings that only appear wth '-march=native'. depends on CC_IS_GCC || CLANG_VERSION >= 190100 +config RUSTC_HAS_APXF + # The kernel isn't ready for in-kernel APX instructions. Without + # explicit frontend gating of APX, the backend may emit those + # instructions in native builds. + # + # Rust 1.88 added the `apxf` feature option, but versions before 1.93 + # emit an `apxf` target attribute that only LLVM 23+ can interpret. + def_bool (RUSTC_VERSION >= 108800 && RUSTC_LLVM_MAJOR_VERSION >= 23) || \ + RUSTC_VERSION >= 109300 + config X86_NATIVE_CPU bool "Build and optimize for local/native CPU" depends on X86_64 depends on CC_HAS_MARCH_NATIVE + depends on !RUST || RUSTC_HAS_APXF help Optimize for the current CPU used to compile the kernel. Use this option if you intend to build the kernel for your diff --git a/arch/x86/Makefile b/arch/x86/Makefile index 598f178102ee..8af6b80cffdd 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile @@ -161,6 +161,11 @@ else ifdef CONFIG_X86_NATIVE_CPU KBUILD_CFLAGS += -march=native + # Prevent the compiler from generating EGPR use. The kernel is + # not yet prepared for general in-kernel use. + KBUILD_CFLAGS += $(call cc-option,-mno-apx-features=egpr) + + # generate_rust_target.rs handles Rust APX gating. KBUILD_RUSTFLAGS += -Ctarget-cpu=native else KBUILD_CFLAGS += -march=x86-64 -mtune=generic diff --git a/scripts/generate_rust_target.rs b/scripts/generate_rust_target.rs index 3bf296581a88..7687b0dd5474 100644 --- a/scripts/generate_rust_target.rs +++ b/scripts/generate_rust_target.rs @@ -224,6 +224,11 @@ fn main() { features += ",+harden-sls-ijmp"; features += ",+harden-sls-ret"; } + if cfg.has("X86_NATIVE_CPU") { + // Prevent the backend from generating APX instructions. The kernel is not yet prepared + // for general in-kernel EGPR use. + features += ",-apxf"; + } ts.push("features", features); ts.push("llvm-target", "x86_64-linux-gnu"); ts.push("supported-sanitizers", ["kcfi", "kernel-address"]); From 8901cee9316d53a5a97398f026c2d59a3043159d Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Sat, 19 Sep 2026 03:42:10 +0200 Subject: [PATCH 2052/2366] bpf: Compare stack frames in regs_exact() regs_exact() compares the register state up to id, followed by the ID mappings, but does not compare frameno. The PTR_TO_STACK case in regsafe() checks frameno separately, which is bypassed when exact comparison is requested. Consequently, infinite-loop detection can treat pointers to different stack frames as the same pointer and reject a finite loop. For example, initialize fp-8 to zero in the caller and to one in the callee, then pass the caller's fp-8 to the callee as r1: loop: r0 = *(u64 *)(r1 + 0); if r0 != 0 goto done; r1 = r10; r1 += -8; goto loop; done: exit; The loop terminates after reading the callee's slot on its second iteration. At the loop header, however, the only relevant difference is r1's frameno, so exact comparison incorrectly reports an infinite loop. The same problem occurs when the pointer is spilled to the stack. Move frameno into the type-specific metadata union, ahead of id, so the existing prefix comparison in regs_exact() covers it. Ordinary stack pointers do not use another union member. Iterator and IRQ stack-slot states use their dedicated union views and do not need a frame lookup. This also keeps bpf_reg_state at 80 bytes. Since frameno now shares storage with other pointer metadata, it is only meaningful for PTR_TO_STACK registers. Return NULL from bpf_func() for other register types. process_iter_arg(), get_constant_map_key() and is_dynptr_reg_valid_init() look up the frame before checking the register type and would otherwise index frame[] with a byte of the register's map or BTF pointer. They dereference the frame only after their type check. Move the states_maybe_looping() boundary from frameno to precise after the field relocation. Its prefix comparison continues to cover the complete value state and now includes frameno. Continue to ignore precise. Precision marks control whether pruning may ignore scalar ranges; they do not change the represented values, and exact comparison already compares those ranges unconditionally. Marks can also change through backtracking while an ancestor state is still being explored. Fixes: d5b892fd607a ("bpf: make infinite loop detection in is_state_visited() exact") Reported-by: Eduard Zingerman Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Acked-by: Eduard Zingerman Link: https://patch.msgid.link/20260919014213.1840880-2-memxor@gmail.com --- include/linux/bpf_verifier.h | 23 +++++++++++++++-------- kernel/bpf/states.c | 7 ++----- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index a7202b44ab10..6fe8e5dc57aa 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -45,6 +45,14 @@ struct bpf_reg_state { union { /* valid when type == PTR_TO_PACKET */ int range; + /* + * Valid when type == PTR_TO_STACK. Inside the callee two registers + * can be both PTR_TO_STACK like R1=fp-8 and R2=fp-8, but one of them + * points to this function stack while another to the caller's stack. + * To differentiate them 'frameno' is used which is an index in + * bpf_verifier_state->frame[] array pointing to bpf_func_state. + */ + u8 frameno; /* * For CONST_PTR_TO_MAP, PTR_TO_MAP_KEY, PTR_TO_MAP_VALUE and @@ -155,14 +163,6 @@ struct bpf_reg_state { * during state comparisons. */ u32 map_uid; - /* - * Inside the callee two registers can be both PTR_TO_STACK like - * R1=fp-8 and R2=fp-8, but one of them points to this function stack - * while another to the caller's stack. To differentiate them 'frameno' - * is used which is an index in bpf_verifier_state->frame[] array - * pointing to bpf_func_state. - */ - u8 frameno; /* if (!precise && SCALAR_VALUE) min/max/tnum don't affect safety */ bool precise; }; @@ -1239,11 +1239,18 @@ static inline int bpf_get_spi(s32 off) return (-off - 1) / BPF_REG_SIZE; } +/* + * Return the function state a stack pointer register refers to. frameno + * shares storage with other pointer metadata, so return NULL for any + * other register type instead of indexing frame[] with aliased bytes. + */ static inline struct bpf_func_state *bpf_func(struct bpf_verifier_env *env, const struct bpf_reg_state *reg) { struct bpf_verifier_state *cur = env->cur_state; + if (reg->type != PTR_TO_STACK) + return NULL; return cur->frame[reg->frameno]; } diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c index e4ec007f7fa6..012b82513a3b 100644 --- a/kernel/bpf/states.c +++ b/kernel/bpf/states.c @@ -644,10 +644,7 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, return range_within(rold, rcur) && tnum_in(rold->var_off, rcur->var_off); case PTR_TO_STACK: - /* two stack pointers are equal only if they're pointing to - * the same stack frame, since fp-8 in foo != fp-8 in bar - */ - return regs_exact(rold, rcur, idmap) && rold->frameno == rcur->frameno; + return regs_exact(rold, rcur, idmap); case PTR_TO_ARENA: return true; case PTR_TO_INSN: @@ -1126,7 +1123,7 @@ static bool states_maybe_looping(struct bpf_verifier_state *old, fcur = cur->frame[fr]; for (i = 0; i < MAX_BPF_REG; i++) if (memcmp(&fold->regs[i], &fcur->regs[i], - offsetof(struct bpf_reg_state, frameno))) + offsetof(struct bpf_reg_state, precise))) return false; return true; } From 070587848985efcfcd45104c5266ba76a1165f55 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Sat, 19 Sep 2026 03:42:11 +0200 Subject: [PATCH 2053/2366] selftests/bpf: Cover frame changes in bounded loops Add a finite loop whose progress is represented only by changing the frame number of a stack pointer. The loop first reads zero from the caller's stack, switches to the same offset in the callee's stack, and exits after reading one on its next iteration. Force frequent checkpoints so the test exercises infinite-loop detection, and check that the program returns one when run. Without the frameno comparison in regs_exact(), the program is rejected with an "infinite loop detected" diagnostic instead of loading successfully. Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Tested-by: Eduard Zingerman Link: https://patch.msgid.link/20260919014213.1840880-3-memxor@gmail.com --- .../selftests/bpf/progs/verifier_loops1.c | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_loops1.c b/tools/testing/selftests/bpf/progs/verifier_loops1.c index d248ce877f14..48a966cda199 100644 --- a/tools/testing/selftests/bpf/progs/verifier_loops1.c +++ b/tools/testing/selftests/bpf/progs/verifier_loops1.c @@ -303,4 +303,40 @@ __naked void maybe_exit_scc_bug1(void) ::: __clobber_all); } +/* + * The loop reads zero from the caller's stack on its first iteration and + * one from the callee's stack on its second iteration. At the loop header, + * only the frame number of the pointer in r1 changes. + */ +static __naked __noinline __used +void loop_stack_frames_reg(void) +{ + asm volatile ( + "*(u64 *)(r10 - 8) = 1;" +"1:" + "r0 = *(u64 *)(r1 + 0);" + "if r0 != 0 goto 2f;" + "r1 = r10;" + "r1 += -8;" + "goto 1b;" +"2:" + "exit;" + ::: __clobber_all); +} + +SEC("xdp") +__description("bounded loop changing stack frame in a register") +__success __retval(1) +__flag(BPF_F_TEST_STATE_FREQ) +__naked void bounded_loop_stack_frames_reg(void) +{ + asm volatile ( + "*(u64 *)(r10 - 8) = 0;" + "r1 = r10;" + "r1 += -8;" + "call loop_stack_frames_reg;" + "exit;" + ::: __clobber_all); +} + char _license[] SEC("license") = "GPL"; From bfc888f04588f591851e95c974954cfca58e6c19 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 14 Sep 2026 15:24:42 +0200 Subject: [PATCH 2054/2366] bpf: Bound ownership depth through local kptrs and graph roots Program-allocated objects can own other local objects through referenced kptrs. bpf_obj_free_fields() follows those pointers through __bpf_obj_drop_impl() synchronously, before the object storage is freed through RCU. A self-referential local kptr type therefore permits arbitrarily deep object chains, and dropping the head can exhaust the kernel stack. Long acyclic type chains have the same problem. btf_check_and_fixup_fields() still assumes referenced kptrs only point to kernel types and checks ownership through list and rbtree roots only. Its existing rule is sufficient for graph-only cycles: the target of each graph edge must contain a node, so every type in a cycle has both a root and a node. The rule rejects such a type owning another root, breaking every cycle. It also limits graph-only chains to three types, or two if the first type contains a node, and conservatively rejects longer acyclic chains. The missing local-kptr edges, rather than a missed graph-only cycle, are the bug introduced by support for bpf_kptr_xchg() into local kptrs. Replace that restriction with one bounded ownership walk covering graph roots and local referenced kptrs. Run it after all BTF records have been fixed up, reject cycles and paths deeper than eight record-bearing types, and cache each type's suffix depth while checking it against the remaining budget. This also permits the longer acyclic graph-only layouts rejected by the old rule; update their existing BTF tests accordingly. Keep the bound independent of MAX_CALL_FRAMES because recursive destruction can run below a BPF call chain. A plain local pointee without special-field metadata adds only a final non-recursing drop. Non-owning kptrs and kernel-BTF kptrs do not recurse through local records and remain outside the walk. Include local percpu-kptr edges too, although allocation of percpu objects with special fields is currently forbidden, so that relaxing that restriction cannot bypass the ownership bound. btf_check_and_fixup_fields() continues to initialize graph_root.value_rec, including for separately allocated map records. The ownership relationships belong to immutable program BTF and only need validation at BTF load time. Fixes: b0966c724584 ("bpf: Support bpf_kptr_xchg into local kptr") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260914132444.2564218-2-memxor@gmail.com --- kernel/bpf/btf.c | 138 +++++++++++------- .../selftests/bpf/prog_tests/linked_list.c | 4 +- 2 files changed, 90 insertions(+), 52 deletions(-) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index d67a169ba2bb..d870bc5e50bc 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -4268,13 +4268,10 @@ int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec) { int i; - /* There are three types that signify ownership of some other type: - * kptr_ref, bpf_list_head, bpf_rb_root. - * kptr_ref only supports storing kernel types, which can't store - * references to program allocated local types. - * - * Hence we only need to ensure that bpf_{list_head,rb_root} ownership - * does not form cycles. + /* + * Check fields which require the complete BTF and initialize runtime + * metadata. Ownership relationships are validated after every record has + * been fixed up. */ if (IS_ERR_OR_NULL(rec) || !(rec->field_mask & (BPF_GRAPH_ROOT | BPF_UPTR))) return 0; @@ -4305,53 +4302,90 @@ int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec) if (!meta) return -EFAULT; rec->fields[i].graph_root.value_rec = meta->record; - - /* We need to set value_rec for all root types, but no need - * to check ownership cycle for a type unless it's also a - * node type. - */ - if (!(rec->field_mask & BPF_GRAPH_NODE)) - continue; - - /* We need to ensure ownership acyclicity among all types. The - * proper way to do it would be to topologically sort all BTF - * IDs based on the ownership edges, since there can be multiple - * bpf_{list_head,rb_node} in a type. Instead, we use the - * following resaoning: - * - * - A type can only be owned by another type in user BTF if it - * has a bpf_{list,rb}_node. Let's call these node types. - * - A type can only _own_ another type in user BTF if it has a - * bpf_{list_head,rb_root}. Let's call these root types. - * - * We ensure that if a type is both a root and node, its - * element types cannot be root types. - * - * To ensure acyclicity: - * - * When A is an root type but not a node, its ownership - * chain can be: - * A -> B -> C - * Where: - * - A is an root, e.g. has bpf_rb_root. - * - B is both a root and node, e.g. has bpf_rb_node and - * bpf_list_head. - * - C is only an root, e.g. has bpf_list_node - * - * When A is both a root and node, some other type already - * owns it in the BTF domain, hence it can not own - * another root type through any of the ownership edges. - * A -> B - * Where: - * - A is both an root and node. - * - B is only an node. - */ - if (meta->record->field_mask & BPF_GRAPH_ROOT) - return -ELOOP; } return 0; } +static int btf_owned_type_idx(const struct btf *btf, struct btf_struct_metas *tab, + const struct btf_field *field) +{ + struct btf_struct_meta *meta; + u32 btf_id; + + if (field->type & BPF_GRAPH_ROOT) { + btf_id = field->graph_root.value_btf_id; + } else if (field->type == BPF_KPTR_REF || field->type == BPF_KPTR_PERCPU) { + if (btf_is_kernel(field->kptr.btf)) + return -ENOENT; + btf_id = field->kptr.btf_id; + } else { + return -ENOENT; + } + + meta = btf_find_struct_meta(btf, btf_id); + if (!meta) + return field->type & BPF_GRAPH_ROOT ? -EFAULT : -ENOENT; + return meta - tab->types; +} + +/* + * Each ownership edge adds kernel frames through bpf_obj_free_fields() and + * __bpf_obj_drop_impl(). Keep the bound deliberately small because object + * destruction can itself run below a BPF call chain. A final pointee without + * special fields is not present in the struct metadata table and adds only a + * non-recursing drop. + */ +#define BTF_MAX_OWNERSHIP_DEPTH 8 + +static int btf_ownership_depth(const struct btf *btf, + struct btf_struct_metas *tab, u8 *depth, + int idx, int depth_left) +{ + const struct btf_record *rec = tab->types[idx].record; + int i, ret, max_depth = 0; + + if (!depth_left) + return -ELOOP; + if (depth[idx]) + goto done; + + for (i = 0; i < rec->cnt; i++) { + ret = btf_owned_type_idx(btf, tab, &rec->fields[i]); + if (ret == -ENOENT) + continue; + if (ret < 0) + return ret; + ret = btf_ownership_depth(btf, tab, depth, ret, depth_left - 1); + if (ret < 0) + return ret; + max_depth = max(max_depth, ret); + } + depth[idx] = max_depth + 1; +done: + return depth[idx] > depth_left ? -ELOOP : depth[idx]; +} + +static int btf_check_ownership_depth(const struct btf *btf, + struct btf_struct_metas *tab) +{ + u8 *depth; + int i, ret = 0; + + depth = kvcalloc(tab->cnt, sizeof(*depth), GFP_KERNEL | __GFP_NOWARN); + if (!depth) + return -ENOMEM; + + for (i = 0; i < tab->cnt; i++) { + ret = btf_ownership_depth(btf, tab, depth, i, + BTF_MAX_OWNERSHIP_DEPTH); + if (ret < 0) + break; + ret = 0; + } + kvfree(depth); + return ret; +} + static void __btf_struct_show(const struct btf *btf, const struct btf_type *t, u32 type_id, void *data, u8 bits_offset, struct btf_show *show) @@ -6044,6 +6078,10 @@ static struct btf *btf_parse(const union bpf_attr *attr, bpfptr_t uattr, if (err < 0) goto errout_meta; } + + err = btf_check_ownership_depth(btf, struct_meta_tab); + if (err < 0) + goto errout_meta; } err = bpf_log_attr_finalize(attr_log, &env->log); diff --git a/tools/testing/selftests/bpf/prog_tests/linked_list.c b/tools/testing/selftests/bpf/prog_tests/linked_list.c index c3d133c6a00d..52fabbee3dd5 100644 --- a/tools/testing/selftests/bpf/prog_tests/linked_list.c +++ b/tools/testing/selftests/bpf/prog_tests/linked_list.c @@ -714,7 +714,7 @@ static void test_btf(void) break; err = btf__load_into_kernel(btf); - ASSERT_EQ(err, -ELOOP, "check btf"); + ASSERT_EQ(err, 0, "check btf"); btf__free(btf); break; } @@ -773,7 +773,7 @@ static void test_btf(void) break; err = btf__load_into_kernel(btf); - ASSERT_EQ(err, -ELOOP, "check btf"); + ASSERT_EQ(err, 0, "check btf"); btf__free(btf); break; } From 0288ed67482b6e370eb3fa6b07720df435907970 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Mon, 14 Sep 2026 15:24:43 +0200 Subject: [PATCH 2055/2366] selftests/bpf: Check local object ownership depth Build raw program BTF records to exercise local object ownership without constructing a runtime chain deep enough to threaten the kernel stack. Cover referenced-kptr and percpu-kptr self-cycles, a two-type cycle, and a cycle mixing a graph root with a referenced kptr. The existing graph-only check accepts these local-kptr cycles and over-limit kptr chains; the fix rejects them with -ELOOP. Pin the eight-record depth boundary with a terminal plain object. Exercise both parent-first and child-first BTF orders, and a shared suffix reached first through a shorter path. These cases require cached suffix depths to be checked against the remaining depth budget on each path. Check list and rbtree chains of three, four, eight, and nine types. This covers the old graph-only depth boundary and the new explicit bound. The existing linked-list BTF tests still reject pure graph cycles and now accept the longer acyclic layouts previously rejected by the conservative rule. Although bpf_percpu_obj_new() currently rejects types with special fields, require the percpu cycle to fail at BTF load so future support cannot bypass the ownership bound. Keep a positive control for a non-owning kptr, which remains outside the ownership graph. Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260914132444.2564218-3-memxor@gmail.com --- .../bpf/prog_tests/local_kptr_ownership.c | 296 ++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c diff --git a/tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c b/tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c new file mode 100644 index 000000000000..a487aa68f2ee --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#include +#include +#include + +#define SPIN_LOCK 2 +#define LIST_HEAD 3 +#define LIST_NODE 4 +/* Keep in sync with BTF_MAX_OWNERSHIP_DEPTH. */ +#define MAX_OWNERSHIP_DEPTH 8 + +static struct btf *init_btf(void) +{ + struct btf *btf; + int id; + + btf = btf__new_empty(); + if (!ASSERT_OK_PTR(btf, "btf__new_empty")) + return NULL; + id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED); + if (!ASSERT_EQ(id, 1, "btf__add_int")) + goto err_out; + id = btf__add_struct(btf, "bpf_spin_lock", 4); + if (!ASSERT_EQ(id, SPIN_LOCK, "btf__add_struct bpf_spin_lock")) + goto err_out; + id = btf__add_struct(btf, "bpf_list_head", 16); + if (!ASSERT_EQ(id, LIST_HEAD, "btf__add_struct bpf_list_head")) + goto err_out; + id = btf__add_struct(btf, "bpf_list_node", 24); + if (!ASSERT_EQ(id, LIST_NODE, "btf__add_struct bpf_list_node")) + goto err_out; + return btf; + +err_out: + btf__free(btf); + return NULL; +} + +static int add_local_kptr(struct btf *btf, int pointee_id, const char *tag) +{ + int id; + + id = btf__add_type_tag(btf, tag, pointee_id); + if (!ASSERT_GT(id, 0, "btf__add_type_tag")) + return id; + id = btf__add_ptr(btf, id); + ASSERT_GT(id, 0, "btf__add_ptr"); + return id; +} + +static void test_self_cycle(const char *tag, int expected_err) +{ + struct btf *btf; + int id, err; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + id = add_local_kptr(btf, 7, tag); + if (id <= 0) + goto out; + id = btf__add_struct(btf, "self_cycle", 8); + if (!ASSERT_EQ(id, 7, "btf__add_struct self_cycle")) + goto out; + err = btf__add_field(btf, "next", 6, 0, 0); + if (!ASSERT_OK(err, "btf__add_field self_cycle::next")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, expected_err, "check btf"); +out: + btf__free(btf); +} + +static void test_aba_cycle(void) +{ + struct btf *btf; + int id, err; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + id = add_local_kptr(btf, 10, "kptr"); + if (id <= 0) + goto out; + id = add_local_kptr(btf, 9, "kptr"); + if (id <= 0) + goto out; + id = btf__add_struct(btf, "cycle_a", 8); + if (!ASSERT_EQ(id, 9, "btf__add_struct cycle_a")) + goto out; + err = btf__add_field(btf, "b", 6, 0, 0); + if (!ASSERT_OK(err, "btf__add_field cycle_a::b")) + goto out; + id = btf__add_struct(btf, "cycle_b", 8); + if (!ASSERT_EQ(id, 10, "btf__add_struct cycle_b")) + goto out; + err = btf__add_field(btf, "a", 8, 0, 0); + if (!ASSERT_OK(err, "btf__add_field cycle_b::a")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, -ELOOP, "check btf"); +out: + btf__free(btf); +} + +static void test_mixed_cycle(void) +{ + struct btf *btf; + int id, err; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + id = add_local_kptr(btf, 7, "kptr"); + if (id <= 0) + goto out; + id = btf__add_struct(btf, "mixed_owner", 20); + if (!ASSERT_EQ(id, 7, "btf__add_struct mixed_owner")) + goto out; + err = btf__add_field(btf, "root", LIST_HEAD, 0, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_owner::root")) + goto out; + err = btf__add_field(btf, "lock", SPIN_LOCK, 128, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_owner::lock")) + goto out; + id = btf__add_decl_tag(btf, "contains:mixed_node:node", 7, 0); + if (!ASSERT_EQ(id, 8, "btf__add_decl_tag mixed_owner")) + goto out; + id = btf__add_struct(btf, "mixed_node", 32); + if (!ASSERT_EQ(id, 9, "btf__add_struct mixed_node")) + goto out; + err = btf__add_field(btf, "node", LIST_NODE, 0, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_node::node")) + goto out; + err = btf__add_field(btf, "owner", 6, 192, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_node::owner")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, -ELOOP, "check btf"); +out: + btf__free(btf); +} + +static void test_acyclic_depth(int depth, bool child_first, bool shared_suffix, int expected_err) +{ + int ptr_id[MAX_OWNERSHIP_DEPTH + 1]; + int first_struct_id; + struct btf *btf; + int id, err, i, n, pointee_id; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + first_struct_id = 5 + 2 * depth; + for (i = 0; i < depth; i++) { + if (i == depth - 1) + pointee_id = first_struct_id + depth; + else + pointee_id = first_struct_id + (child_first ? depth - 2 - i : i + 1); + ptr_id[i] = add_local_kptr(btf, pointee_id, "kptr"); + if (ptr_id[i] <= 0) + goto out; + } + for (n = 0; n < depth; n++) { + char name[32]; + int offset = 0; + + i = child_first ? depth - 1 - n : n; + snprintf(name, sizeof(name), "owner_%d", i); + id = btf__add_struct(btf, name, shared_suffix && !i ? 16 : 8); + if (!ASSERT_EQ(id, first_struct_id + n, "btf__add_struct owner")) + goto out; + if (shared_suffix && !i) { + /* + * Visit the shared suffix through the shorter path before + * reaching it again with less remaining depth. + */ + err = btf__add_field(btf, "suffix", ptr_id[1], 0, 0); + if (!ASSERT_OK(err, "btf__add_field owner::suffix")) + goto out; + offset = 64; + } + err = btf__add_field(btf, "next", ptr_id[i], offset, 0); + if (!ASSERT_OK(err, "btf__add_field owner::next")) + goto out; + } + id = btf__add_struct(btf, "plain_leaf", 4); + if (!ASSERT_EQ(id, first_struct_id + depth, "btf__add_struct plain_leaf")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, expected_err, "check btf"); +out: + btf__free(btf); +} + +static void test_graph_depth(bool rbtree, int depth, int expected_err) +{ + int root_type = LIST_HEAD, node_type = LIST_NODE, node_size = 24; + int id, err, i, lock_off, root_off, size; + struct btf *btf; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + if (rbtree) { + root_type = btf__add_struct(btf, "bpf_rb_root", 16); + if (!ASSERT_GT(root_type, 0, "btf__add_struct bpf_rb_root")) + goto out; + node_type = btf__add_struct(btf, "bpf_rb_node", 32); + if (!ASSERT_GT(node_type, 0, "btf__add_struct bpf_rb_node")) + goto out; + node_size = 32; + } + + for (i = 0; i < depth; i++) { + char name[32], tag[64]; + + lock_off = i ? node_size : 0; + root_off = lock_off + 8; + size = i == depth - 1 ? node_size : root_off + 16; + snprintf(name, sizeof(name), "graph_owner_%d", i); + id = btf__add_struct(btf, name, size); + if (!ASSERT_GT(id, 0, "btf__add_struct graph_owner")) + goto out; + if (i) { + err = btf__add_field(btf, "node", node_type, 0, 0); + if (!ASSERT_OK(err, "btf__add_field graph_owner::node")) + goto out; + } + if (i == depth - 1) + continue; + err = btf__add_field(btf, "lock", SPIN_LOCK, lock_off * 8, 0); + if (!ASSERT_OK(err, "btf__add_field graph_owner::lock")) + goto out; + err = btf__add_field(btf, "root", root_type, root_off * 8, 0); + if (!ASSERT_OK(err, "btf__add_field graph_owner::root")) + goto out; + snprintf(tag, sizeof(tag), "contains:graph_owner_%d:node", i + 1); + err = btf__add_decl_tag(btf, tag, id, i ? 2 : 1); + if (!ASSERT_GT(err, 0, "btf__add_decl_tag graph_owner")) + goto out; + } + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, expected_err, "check btf"); +out: + btf__free(btf); +} + +void test_local_kptr_ownership(void) +{ + if (test__start_subtest("self_cycle")) + test_self_cycle("kptr", -ELOOP); + if (test__start_subtest("untrusted_self_cycle")) + test_self_cycle("kptr_untrusted", 0); + if (test__start_subtest("percpu_self_cycle")) + test_self_cycle("percpu_kptr", -ELOOP); + if (test__start_subtest("ABA_cycle")) + test_aba_cycle(); + if (test__start_subtest("mixed_graph_root_cycle")) + test_mixed_cycle(); + if (test__start_subtest("max_acyclic")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH, false, false, 0); + if (test__start_subtest("too_deep_acyclic")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, false, false, -ELOOP); + if (test__start_subtest("max_acyclic_child_first")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH, true, false, 0); + if (test__start_subtest("too_deep_acyclic_child_first")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, true, false, -ELOOP); + if (test__start_subtest("max_acyclic_shared_suffix")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH, false, true, 0); + if (test__start_subtest("too_deep_acyclic_shared_suffix")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, false, true, -ELOOP); + if (test__start_subtest("list_three_types")) + test_graph_depth(false, 3, 0); + if (test__start_subtest("list_four_types")) + test_graph_depth(false, 4, 0); + if (test__start_subtest("list_max_depth")) + test_graph_depth(false, MAX_OWNERSHIP_DEPTH, 0); + if (test__start_subtest("list_too_deep")) + test_graph_depth(false, MAX_OWNERSHIP_DEPTH + 1, -ELOOP); + if (test__start_subtest("rbtree_three_types")) + test_graph_depth(true, 3, 0); + if (test__start_subtest("rbtree_four_types")) + test_graph_depth(true, 4, 0); + if (test__start_subtest("rbtree_max_depth")) + test_graph_depth(true, MAX_OWNERSHIP_DEPTH, 0); + if (test__start_subtest("rbtree_too_deep")) + test_graph_depth(true, MAX_OWNERSHIP_DEPTH + 1, -ELOOP); +} From 3bd46666cfe51e2bd333fb46a0234694ca594230 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Fri, 18 Sep 2026 14:28:37 -1000 Subject: [PATCH 2056/2366] sched_ext: Pass the initial cmask to cid-form ops.enable() The cid-form API has an obvious hole. A task's cid mask is only visible through ops.set_cmask(), which fires on affinity changes and class switches but not when a task enters a scheduler through fork, sub-sched enable or re-home, and there is no p->cpus_ptr equivalent to fall back on. Schedulers work around it by seeding the mask in ops.init_task() from p->cpus_ptr cid by cid, which is subtly wrong: on sub-sched enable and re-home, an affinity change between init_task() and enable() is delivered to the sched the task is still on, and nothing corrects the new sched's copy afterwards. Fix it by adding struct scx_enable_args to cid-form ops.enable() carrying the task's cmask, built in the per-cpu scratch under the rq lock as the task enters the scheduler, and calling set_cmask() with the same mask right after enable(), ahead of set_weight(). A scheduler can then track affinity in set_cmask() alone, and scx_qmap drops its init_task() seed. set_cmask() no longer fires for a cid-form task before it is enabled, and the class-switch republish in switching_to_scx() is limited to the cpu form. This changes the cid-form ops.enable() signature, which is fine as the cid-form API is still considered unreleased. An args struct rather than a bare cmask argument leaves room for more initial state without another signature change, and the cmask travels as a plain arena address because BTF can't type arena struct members yet. v2: The cmask travels as a u64 arena address, cmask_arena_addr, instead of a kernel-typed pointer, with the typing limitation and the planned typed alias documented (Sashiko review). v3: The initial set_cmask() is delivered before set_weight() so that the mask is in place when weight-dependent state is derived (Andrea Righi). Selftest added. Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 92 +++++++++++++++++++++++----------- kernel/sched/ext/internal.h | 56 ++++++++++++++++++--- tools/sched_ext/scx_qmap.bpf.c | 3 -- 3 files changed, 110 insertions(+), 41 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index f568fd9973f6..ad391a8cbd05 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -447,37 +447,45 @@ static void switch_rq_lock(struct rq *from, struct rq *to) DEFINE_STATIC_KEY_FALSE(__scx_is_cid_type); /** - * scx_call_op_set_cpumask - invoke ops.set_cpumask / ops_cid.set_cmask for @task + * scx_fill_cmask_scratch - Build this cpu's arena cmask from @cpumask + * @sch: scx_sched whose scratch to fill + * @cpumask: cpus to translate into cids + * + * The scratch lives in BPF-writable arena memory and its header can't be + * trusted, so it is rewritten from kernel geometry rather than read. Caller + * must hold an rq lock so this cpu is the sole kernel writer for as long as the + * returned address is in use. + */ +static struct scx_cmask *scx_fill_cmask_scratch(struct scx_sched *sch, + const struct cpumask *cpumask) +{ + struct scx_cmask *kern_va = *this_cpu_ptr(sch->set_cmask_scratch); + struct scx_cmask_ref ref; + + scx_cmask_ref_init_kern(sch, kern_va, 0, num_possible_cpus(), &ref); + scx_cmask_ref_from_cpumask(&ref, cpumask); + return kern_va; +} + +/** + * scx_call_op_set_cpumask - Invoke the set_cpumask or set_cmask op for @task * @sch: scx_sched being invoked * @rq: rq to update as the currently-locked rq, or NULL * @task: task whose affinity is changing * @cpumask: new cpumask * - * For cid-form schedulers, translate @cpumask to a cmask via the per-cpu - * scratch in cid.c and dispatch through the ops_cid union view. Caller - * must hold @rq's rq lock so this_cpu_ptr is stable across the call. + * For cid-form schedulers, translate @cpumask to a cmask in the per-cpu scratch + * and dispatch through the ops_cid union view. Caller must hold @rq's rq lock. */ static inline void scx_call_op_set_cpumask(struct scx_sched *sch, struct rq *rq, struct task_struct *task, const struct cpumask *cpumask) { - if (scx_is_cid_type()) { - struct scx_cmask *kern_va = *this_cpu_ptr(sch->set_cmask_scratch); - struct scx_cmask_ref ref; - - /* - * Build the per-cpu arena cmask from kernel geometry via @ref, - * never reading its BPF-writable header. set_cmask()'s __arena - * argument takes the kernel address and the struct_ops - * trampoline rebases it into BPF's arena pointer form. The rq - * lock makes this cpu the sole kernel writer. - */ - scx_cmask_ref_init_kern(sch, kern_va, 0, num_possible_cpus(), &ref); - scx_cmask_ref_from_cpumask(&ref, cpumask); - SCX_CALL_CID_OP_TASK(sch, set_cmask, rq, task, kern_va); - } else { + if (scx_is_cid_type()) + SCX_CALL_CID_OP_TASK(sch, set_cmask, rq, task, + scx_fill_cmask_scratch(sch, cpumask)); + else SCX_CALL_OP_TASK(sch, set_cpumask, rq, task, cpumask); - } } enum scx_dsq_iter_flags { @@ -3634,8 +3642,12 @@ static void set_cpus_allowed_scx(struct task_struct *p, * * Fine-grained memory write control is enforced by BPF making the const * designation pointless. Cast it away when calling the operation. + * + * The cid form receives the initial mask when the task is enabled and + * hears about changes only afterwards, see struct scx_enable_args. */ - if (SCX_HAS_OP(sch, set_cpumask)) + if (SCX_HAS_OP(sch, set_cpumask) && + (!scx_is_cid_type() || scx_get_task_state(p) == SCX_TASK_ENABLED)) scx_call_op_set_cpumask(sch, task_rq(p), p, (struct cpumask *)p->cpus_ptr); } @@ -3944,8 +3956,27 @@ static void __scx_enable_task(struct scx_sched *sch, struct task_struct *p) p->scx.weight = sched_weight_to_cgroup(weight); - if (SCX_HAS_OP(sch, enable)) - SCX_CALL_OP_TASK(sch, enable, rq, p); + if (SCX_HAS_OP(sch, enable)) { + if (scx_is_cid_type()) { + struct scx_cmask *cmask = scx_fill_cmask_scratch(sch, p->cpus_ptr); + struct scx_enable_args args = { + .cmask_arena_addr = scx_kaddr_to_arena(sch, cmask), + }; + + SCX_CALL_CID_OP_TASK(sch, enable, rq, p, &args); + } else { + SCX_CALL_OP_TASK(sch, enable, rq, p); + } + } + + /* + * The initial mask also goes out through set_cmask() so a scheduler can + * track affinity there alone, and before set_weight() so that the mask + * is in place when weight-dependent state is derived, see struct + * scx_enable_args. + */ + if (scx_is_cid_type() && SCX_HAS_OP(sch, set_cpumask)) + scx_call_op_set_cpumask(sch, rq, p, p->cpus_ptr); if (SCX_HAS_OP(sch, set_weight)) SCX_CALL_OP_TASK(sch, set_weight, rq, p, p->scx.weight); @@ -4288,9 +4319,10 @@ static void switching_to_scx(struct rq *rq, struct task_struct *p) /* * set_cpus_allowed_scx() is not called while @p is associated with a - * different scheduler class. Keep the BPF scheduler up-to-date. + * different scheduler class. Keep the BPF scheduler up-to-date. The cid + * form gets its mask from scx_enable_task(). */ - if (SCX_HAS_OP(sch, set_cpumask)) + if (!scx_is_cid_type() && SCX_HAS_OP(sch, set_cpumask)) scx_call_op_set_cpumask(sch, rq, p, (struct cpumask *)p->cpus_ptr); } @@ -8374,10 +8406,11 @@ static struct bpf_struct_ops bpf_sched_ext_ops = { /* * cid-form cfi stubs. Stubs whose signatures match the cpu-form (param types * identical, only param names differ across structs) are reused. Some need - * fresh stubs, set_cmask due to an argument type difference and the sub-sched - * notifiers because no cpu-form stub exists to reuse. + * fresh stubs, set_cmask and enable due to argument differences and the + * sub-sched notifiers because no cpu-form stub exists to reuse. */ static void sched_ext_ops_cid__set_cmask(struct task_struct *p, const struct scx_cmask *cmask__arena) {} +static void sched_ext_ops_cid__enable(struct task_struct *p, struct scx_enable_args *args) {} static void sched_ext_ops__sub_caps_updated(const struct scx_cmask *cmask__arena, u64 caps) {} static void sched_ext_ops__sub_ecaps_updated(s32 cid, u64 before, u64 after) {} @@ -8398,7 +8431,7 @@ static struct sched_ext_ops_cid __bpf_ops_sched_ext_ops_cid = { .update_idle = sched_ext_ops__update_idle, .init_task = sched_ext_ops__init_task, .exit_task = sched_ext_ops__exit_task, - .enable = sched_ext_ops__enable, + .enable = sched_ext_ops_cid__enable, .disable = sched_ext_ops__disable, #ifdef CONFIG_EXT_GROUP_SCHED .cpuctl_init = sched_ext_ops__cgroup_init, @@ -10421,8 +10454,7 @@ __bpf_kfunc const void *scx_bpf_online_cmask(const struct bpf_prog_aux *aux) if (unlikely(!online)) return NULL; - /* BPF rebases by the low 32 bits, like __arena callback args */ - return (void *)((unsigned long)online - sch->arena_kern_base); + return (void *)scx_kaddr_to_arena(sch, online); } /** diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index d150de10a5c9..1df8f583b0ec 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -250,6 +250,31 @@ struct scx_exit_task_args { bool cancelled; }; +/** + * struct scx_enable_args - Argument container for cid-form ops.enable() + * @cmask_arena_addr: BPF arena address of the cmask of cids the task may run on + * + * @cmask_arena_addr is the task's affinity as it enters the scheduler. + * set_cmask() delivers the same mask right after enable(), before set_weight() + * and the first enqueue, then every affinity change afterwards, and is never + * called before enable(). A scheduler may therefore track affinity in + * set_cmask() alone. + * + * The kernel builds the mask in the scheduler arena from its own geometry, so + * the header is valid regardless of what the scheduler last wrote there. The + * memory is per-cpu scratch reused once the callback returns: copy the bits + * out, don't keep the address. The set_cmask() argument follows the same rules. + * + * The address is a plain value rather than a typed pointer because BTF can't + * mark a struct member as an arena pointer yet and a pointer member would reach + * the program typed as a kernel pointer. Cast it to struct scx_cmask __arena * + * before use. Once arena members can be typed, a typed alias will join this + * field in an anonymous union at the same offset. + */ +struct scx_enable_args { + u64 cmask_arena_addr; +}; + /* argument container for ops.cgroup_init() */ struct scx_cgroup_init_args { /* the weight of the cgroup [1..10000] */ @@ -1037,6 +1062,7 @@ struct sched_ext_ops { * - dispatch -> dispatch (cpu arg is now cid) * - update_idle -> update_idle (cpu arg is now cid) * - set_cpumask -> set_cmask (cmask instead of cpumask) + * - enable -> enable (takes struct scx_enable_args) * - cpu_online -> cid_online * - cpu_offline -> cid_offline * - dump_cpu -> dump_cid @@ -1070,7 +1096,7 @@ struct sched_ext_ops_cid { struct scx_init_task_args *args); void (*exit_task)(struct task_struct *p, struct scx_exit_task_args *args); - void (*enable)(struct task_struct *p); + void (*enable)(struct task_struct *p, struct scx_enable_args *args); void (*disable)(struct task_struct *p); void (*dump)(struct scx_dump_ctx *ctx); void (*dump_cid)(struct scx_dump_ctx *ctx, s32 cid, bool idle); @@ -1533,7 +1559,8 @@ struct scx_sched { * by BUILD_BUG_ON in scx_init()). The anonymous union lets the kernel * access either view of the same storage without function-pointer * casts: use .ops for cpu-form and shared fields, .ops_cid for the - * cid-renamed callbacks (set_cmask, select_cid, cid_online, ...). + * callbacks whose cid-form signature differs (set_cmask, enable, + * select_cid, cid_online, ...). */ union { struct sched_ext_ops ops; @@ -1556,9 +1583,9 @@ struct scx_sched { uintptr_t arena_kern_base; /* - * Per-CPU arena cmask used by scx_call_op_set_cpumask() to hand a cmask - * to ops_cid.set_cmask(). The kernel writes through the stored kern_va - * and passes it to the callback's __arena argument. + * Per-CPU arena cmask the kernel fills from a task's cpumask and hands + * to ops_cid.enable() and ops_cid.set_cmask(). The stored pointers are + * the kernel addresses. */ struct scx_cmask * __percpu *set_cmask_scratch; struct scx_cmask *online_cmask; @@ -1669,6 +1696,19 @@ static inline void *scx_arena_to_kaddr(struct scx_sched *sch, const void *bpf_pt return (void *)(sch->arena_kern_base + (u32)(uintptr_t)bpf_ptr); } +/** + * scx_kaddr_to_arena - Translate a kernel arena address to the BPF form + * @sch: scheduler whose arena hosts @kaddr + * @kaddr: kernel address inside @sch's arena + * + * __arena callback arguments need no translation. Addresses handed to BPF any + * other way, such as struct fields and kfunc return values, go through this. + */ +static inline uintptr_t scx_kaddr_to_arena(struct scx_sched *sch, const void *kaddr) +{ + return (uintptr_t)kaddr - sch->arena_kern_base; +} + enum scx_wake_flags { /* expose select WF_* flags as enums */ SCX_WAKE_FORK = WF_FORK, @@ -2302,9 +2342,9 @@ do { \ } while (0) /* - * Dispatch a task op through the cid-form ops_cid table. Only set_cmask() needs - * this: it takes an arena cmask address instead of a cpumask, so it cannot be - * invoked via its cpu-form set_cpumask() slot. + * Dispatch a task op through the cid-form ops_cid table, for the ops whose + * cid-form signature differs from the cpu-form slot: set_cmask() takes an arena + * cmask instead of a cpumask and enable() takes scx_enable_args. */ #define SCX_CALL_CID_OP_TASK(sch, op, locked_rq, task, args...) \ __SCX_CALL_OP_TASK(sch, ops_cid, op, locked_rq, task, ##args) diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index 67b7c01cae55..2f3653199842 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -961,9 +961,6 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init_task, struct task_struct *p, taskc->highpri = false; taskc->core_sched_seq = 0; cmask_init(&taskc->cpus_allowed, 0, scx_bpf_nr_cids()); - bpf_rcu_read_lock(); - cmask_from_cpumask(&taskc->cpus_allowed, p->cpus_ptr); - bpf_rcu_read_unlock(); v = bpf_task_storage_get(&task_ctx_stor, p, NULL, BPF_LOCAL_STORAGE_GET_F_CREATE); From d781d1b78acf547bafeb1592e8ba4020dce515c6 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 19 Sep 2026 04:08:20 -1000 Subject: [PATCH 2057/2366] selftests/sched_ext: Check the cmask cid-form ops.enable() receives cid-form ops.enable() now hands the task's cmask to the scheduler and set_cmask() repeats it right after. Add a cid-form selftest that checks both against p->cpus_ptr, that they match each other, that the initial set_cmask() lands before set_weight() and before the task first becomes runnable, and that set_cmask() never precedes enable(), across class-switch enables, fork-path enables and live affinity changes. v2: Mismatch details returned through a caller-local struct instead of globals, alloc_words validated in the header check, loop bounded by nr_cids directly (Andrea Righi). Signed-off-by: Tejun Heo --- tools/testing/selftests/sched_ext/Makefile | 1 + .../selftests/sched_ext/enable_cmask.bpf.c | 217 ++++++++++++++++++ .../selftests/sched_ext/enable_cmask.c | 138 +++++++++++ 3 files changed, 356 insertions(+) create mode 100644 tools/testing/selftests/sched_ext/enable_cmask.bpf.c create mode 100644 tools/testing/selftests/sched_ext/enable_cmask.c diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile index 49897727f535..4e06d0baaeec 100644 --- a/tools/testing/selftests/sched_ext/Makefile +++ b/tools/testing/selftests/sched_ext/Makefile @@ -169,6 +169,7 @@ auto-test-targets := \ ddsp_bogus_dsq_fail \ ddsp_vtimelocal_fail \ dsp_local_on \ + enable_cmask \ enq_select_cpu \ exit \ hotplug \ diff --git a/tools/testing/selftests/sched_ext/enable_cmask.bpf.c b/tools/testing/selftests/sched_ext/enable_cmask.bpf.c new file mode 100644 index 000000000000..0068f3c7ab3c --- /dev/null +++ b/tools/testing/selftests/sched_ext/enable_cmask.bpf.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A cid-form scheduler checking the cmask cid-form ops.enable() receives: the + * header, every cid bit against p->cpus_ptr, and that set_cmask() follows with + * the same mask before set_weight() and before the task first becomes runnable, + * and never runs before enable(). + * + * Copyright (c) 2026 Tejun Heo + */ +#include + +char _license[] SEC("license") = "GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_ARENA); + __uint(map_flags, BPF_F_MMAPABLE); + __uint(max_entries, 1 << 16); +} arena SEC(".maps"); + +struct task_ctx { + u64 enable_fp; /* fingerprint of the mask enable() received */ + bool enabled; + bool pending; /* enable() ran, the initial set_cmask() hasn't */ +}; + +struct { + __uint(type, BPF_MAP_TYPE_TASK_STORAGE); + __uint(map_flags, BPF_F_NO_PREALLOC); + __type(key, int); + __type(value, struct task_ctx); +} task_ctx_stor SEC(".maps"); + +/* details of a cid bit mismatch, filled by check_mask() */ +struct mask_mismatch { + s32 cid; + bool want; + bool got; +}; + +u64 nr_enable, nr_initial_set_cmask, nr_set_cmask, nr_set_weight; + +UEI_DEFINE(uei); + +static struct task_ctx *lookup_task_ctx(struct task_struct *p) +{ + struct task_ctx *tctx; + + tctx = bpf_task_storage_get(&task_ctx_stor, p, 0, 0); + if (!tctx) + scx_bpf_error("task_ctx lookup failed for %s[%d]", p->comm, p->pid); + return tctx; +} + +/* + * Verify @m's header and every cid bit against @p's cpumask and fingerprint the + * bits into @fp. Return 0 on success, -EINVAL on a bad header, -ENOENT on a cid + * without a cpu and -EIO on a bit mismatch with the details in @mm. + */ +static int check_mask(struct task_struct *p, const struct scx_cmask __arena *m, u64 *fp, + struct mask_mismatch *mm) +{ + u32 nr_cids = scx_bpf_nr_cids(); + u64 h = 0; + s32 cid; + + if (m->base || m->nr_cids != nr_cids || m->alloc_words != CMASK_NR_WORDS(nr_cids)) + return -EINVAL; + + bpf_for(cid, 0, nr_cids) { + bool want, got; + s32 cpu; + + cpu = scx_bpf_cid_to_cpu(cid); + if (cpu < 0) + return -ENOENT; + want = bpf_cpumask_test_cpu(cpu, p->cpus_ptr); + got = cmask_test(cid, m); + if (want != got) { + mm->cid = cid; + mm->want = want; + mm->got = got; + return -EIO; + } + h = h * 31 + got; + } + + *fp = h; + return 0; +} + +s32 BPF_STRUCT_OPS_SLEEPABLE(enable_cmask_init_task, struct task_struct *p, + struct scx_init_task_args *args) +{ + if (!bpf_task_storage_get(&task_ctx_stor, p, 0, BPF_LOCAL_STORAGE_GET_F_CREATE)) + return -ENOMEM; + return 0; +} + +void BPF_STRUCT_OPS(enable_cmask_enable, struct task_struct *p, struct scx_enable_args *args) +{ + struct scx_cmask __arena *m = (struct scx_cmask __arena *)args->cmask_arena_addr; + struct mask_mismatch mm = {}; + struct task_ctx *tctx; + int ret; + + asm volatile("" :: "r"(&arena)); + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + __sync_fetch_and_add(&nr_enable, 1); + if (tctx->enabled || tctx->pending) { + scx_bpf_error("enable: %s[%d] enabled twice", p->comm, p->pid); + return; + } + + ret = check_mask(p, m, &tctx->enable_fp, &mm); + if (ret) { + scx_bpf_error("enable: %s[%d] cmask check failed %d cid=%d want=%d got=%d", + p->comm, p->pid, ret, mm.cid, mm.want, mm.got); + return; + } + tctx->enabled = true; + tctx->pending = true; +} + +void BPF_STRUCT_OPS(enable_cmask_set_cmask, struct task_struct *p, + struct scx_cmask __arena *m) +{ + struct mask_mismatch mm = {}; + struct task_ctx *tctx; + u64 fp; + int ret; + + asm volatile("" :: "r"(&arena)); + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + __sync_fetch_and_add(&nr_set_cmask, 1); + if (!tctx->enabled) { + scx_bpf_error("set_cmask: %s[%d] not enabled", p->comm, p->pid); + return; + } + + ret = check_mask(p, m, &fp, &mm); + if (ret) { + scx_bpf_error("set_cmask: %s[%d] cmask check failed %d cid=%d want=%d got=%d", + p->comm, p->pid, ret, mm.cid, mm.want, mm.got); + return; + } + + if (tctx->pending) { + if (fp != tctx->enable_fp) { + scx_bpf_error("set_cmask: %s[%d] initial mask differs from enable()", + p->comm, p->pid); + return; + } + tctx->pending = false; + __sync_fetch_and_add(&nr_initial_set_cmask, 1); + } +} + +void BPF_STRUCT_OPS(enable_cmask_set_weight, struct task_struct *p, u32 weight) +{ + struct task_ctx *tctx; + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + __sync_fetch_and_add(&nr_set_weight, 1); + if (tctx->pending) + scx_bpf_error("set_weight: %s[%d] before the initial set_cmask()", p->comm, + p->pid); +} + +void BPF_STRUCT_OPS(enable_cmask_runnable, struct task_struct *p, u64 enq_flags) +{ + struct task_ctx *tctx; + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + if (tctx->pending) + scx_bpf_error("runnable: %s[%d] before the initial set_cmask()", p->comm, + p->pid); +} + +void BPF_STRUCT_OPS(enable_cmask_disable, struct task_struct *p) +{ + struct task_ctx *tctx; + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + tctx->enabled = false; + tctx->pending = false; +} + +void BPF_STRUCT_OPS(enable_cmask_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); +} + +SCX_OPS_CID_DEFINE(enable_cmask_ops, + .init_task = (void *)enable_cmask_init_task, + .enable = (void *)enable_cmask_enable, + .set_cmask = (void *)enable_cmask_set_cmask, + .set_weight = (void *)enable_cmask_set_weight, + .runnable = (void *)enable_cmask_runnable, + .disable = (void *)enable_cmask_disable, + .exit = (void *)enable_cmask_exit, + .flags = SCX_OPS_SWITCH_PARTIAL, + .name = "enable_cmask"); diff --git a/tools/testing/selftests/sched_ext/enable_cmask.c b/tools/testing/selftests/sched_ext/enable_cmask.c new file mode 100644 index 000000000000..556bcad4431d --- /dev/null +++ b/tools/testing/selftests/sched_ext/enable_cmask.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Tejun Heo */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include "enable_cmask.bpf.skel.h" +#include "scx_test.h" + +#define SCHED_EXT 7 +#define NR_CHILDREN 8 +#define MAX_CPUS 1024 + +static int cpus[MAX_CPUS]; +static int nr_cpus; + +static void spin_ms(int ms) +{ + struct timespec start, now; + + clock_gettime(CLOCK_MONOTONIC, &start); + do { + clock_gettime(CLOCK_MONOTONIC, &now); + } while ((now.tv_sec - start.tv_sec) * 1000 + + (now.tv_nsec - start.tv_nsec) / 1000000 < ms); +} + +static int pin(pid_t pid, int idx) +{ + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(cpus[idx % nr_cpus], &set); + return sched_setaffinity(pid, sizeof(set), &set); +} + +/* + * Pin, switch to SCHED_EXT for a class-switch enable, fork a grandchild that + * inherits the policy for a fork-path enable, then change affinity a few times + * while running for set_cmask() on live tasks. + */ +static int child(int idx) +{ + struct sched_param param = {}; + int i, status; + pid_t pid; + + if (pin(0, idx) || sched_setscheduler(0, SCHED_EXT, ¶m)) + return 1; + + pid = fork(); + if (pid < 0) + return 1; + if (!pid) { + spin_ms(20); + return 0; + } + + for (i = 1; i <= 4; i++) { + if (pin(0, idx + i)) + return 1; + spin_ms(5); + } + + return waitpid(pid, &status, 0) == pid && !status ? 0 : 1; +} + +static enum scx_test_status run(void *ctx) +{ + struct enable_cmask *skel; + struct bpf_link *link; + pid_t pids[NR_CHILDREN]; + cpu_set_t set; + int i, status, failed = 0; + + if (!__COMPAT_struct_has_field("scx_enable_args", "cmask_arena_addr")) + return SCX_TEST_SKIP; + + SCX_FAIL_IF(sched_getaffinity(0, sizeof(set), &set), "Failed to read affinity"); + for (i = 0; i < MAX_CPUS && i < CPU_SETSIZE; i++) + if (CPU_ISSET(i, &set)) + cpus[nr_cpus++] = i; + if (nr_cpus < 2) + return SCX_TEST_SKIP; + + skel = enable_cmask__open(); + SCX_FAIL_IF(!skel, "Failed to open"); + SCX_ENUM_INIT(skel); + SCX_FAIL_IF(enable_cmask__load(skel), "Failed to load skel"); + + link = bpf_map__attach_struct_ops(skel->maps.enable_cmask_ops); + SCX_FAIL_IF(!link, "Failed to attach struct_ops"); + + for (i = 0; i < NR_CHILDREN; i++) { + pids[i] = fork(); + SCX_FAIL_IF(pids[i] < 0, "Failed to fork"); + if (!pids[i]) + exit(child(i)); + } + + /* affinity changes from the outside race with the children's own */ + for (i = 0; i < NR_CHILDREN; i++) + pin(pids[i], i + NR_CHILDREN); + + for (i = 0; i < NR_CHILDREN; i++) { + if (waitpid(pids[i], &status, 0) != pids[i] || status) + failed++; + } + + bpf_link__destroy(link); + + SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG)); + SCX_EQ(failed, 0); + SCX_GE(skel->bss->nr_enable, 2 * NR_CHILDREN); + SCX_EQ(skel->bss->nr_initial_set_cmask, skel->bss->nr_enable); + SCX_GT(skel->bss->nr_set_cmask, skel->bss->nr_initial_set_cmask); + SCX_GE(skel->bss->nr_set_weight, skel->bss->nr_enable); + printf("enable=%lu initial_set_cmask=%lu set_cmask=%lu set_weight=%lu\n", + (unsigned long)skel->bss->nr_enable, + (unsigned long)skel->bss->nr_initial_set_cmask, + (unsigned long)skel->bss->nr_set_cmask, + (unsigned long)skel->bss->nr_set_weight); + + enable_cmask__destroy(skel); + return SCX_TEST_PASS; +} + +struct scx_test enable_cmask = { + .name = "enable_cmask", + .description = "Check the cid-form ops.enable() cmask and the set_cmask() after it", + .run = run, +}; +REGISTER_SCX_TEST(&enable_cmask) From 6b1bca1b1ab77f60a62087337bfe6e2f0efb9e6d Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 10 Aug 2026 02:56:16 +0200 Subject: [PATCH 2058/2366] KVM: arm64: Fix AArch32 DBGBXVR handling The consolidation of the breakpoint and watchpoint register accessors switched DBGBXVR from trap_bvr() to trap_dbg_wb_reg(). The latter selects backing storage with demux_wb_reg(), which only handles Op2 values 4 through 7. Since DBGBXVR uses Op2 1, an AArch32 guest access hits KVM_BUG_ON() and marks the VM dead. DBGBXVR aliases DBGBVR_EL1[63:32], and its AA32(HI) descriptor already selects the upper half. Map Op2 1 to dbg_bvr[] alongside Op2 4, restoring the pre-regression behavior. Fixes: 3ce9f3357e9e ("KVM: arm64: Fold DBGxVR/DBGxCR accessors into common set") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Mehltretter Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260810005616.13227-1-kmehltretter@gmail.com Signed-off-by: Oliver Upton --- arch/arm64/kvm/sys_regs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c index 44aae52c473d..a2f4e769a428 100644 --- a/arch/arm64/kvm/sys_regs.c +++ b/arch/arm64/kvm/sys_regs.c @@ -926,6 +926,7 @@ static u64 *demux_wb_reg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *rd) struct kvm_guest_debug_arch *dbg = &vcpu->arch.vcpu_debug_state; switch (rd->Op2) { + case 0b001: case 0b100: return &dbg->dbg_bvr[rd->CRm]; case 0b101: From c21eaa72f02fc6e85621cbe09d303d8fb8bd39cd Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Wed, 16 Sep 2026 20:48:30 +0200 Subject: [PATCH 2059/2366] posix-cpu-timers: Prevent freeing a timer which is queued on the expiry list Kijo analyzed another race in the POSIX CPU timer code: Commit bf635681c906 converted cpu_timer::firing from a tristate value to a boolean. This lost the distinction between "not owned by the firing list" and "still owned, but delivery was canceled". The resulting race is: expiry handler timer_settime() timer_delete() -------------- --------------- -------------- collect timer onto private firing list firing = true observes firing = true firing = false return TIMER_RETRY wait for handler observes firing = false finish deletion unhash and free timer resume list traversal read freed elist.next -> UAF The firing bit is clearly the wrong indicator since that commit. Check whether the timer is queued on the expiry list or not instead. If it is queued clear the firing bit to prevent signal delivery as before and return TIMER_RETRY so the caller unlocks the timer which allows the expiry code to make progress and remove it from the list. Fixes: bf635681c906 ("posix-cpu-timers: Cleanup the firing logic") Reported-by: Kijo Park Debugged-by: Kijo Park Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Frederic Weisbecker Cc: stable@vger.kernel.org --- kernel/time/posix-cpu-timers.c | 40 +++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index d73d31c7994f..0bf4fcd969c8 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -408,6 +408,7 @@ static int posix_cpu_timer_create(struct k_itimer *new_timer) new_timer->kclock = &clock_posix_cpu; timerqueue_init(&new_timer->it.cpu.node); + INIT_LIST_HEAD(&new_timer->it.cpu.elist); new_timer->it.cpu.pid = get_pid(pid); rcu_read_unlock(); return 0; @@ -565,6 +566,24 @@ static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned l return NULL; } +/* + * If the timer is queued on the expiry list, then it cannot be dequeued because + * the firing list is not protected by sighand->lock. The delivery path is + * waiting for the timer lock. So go back, unlock and retry. + */ +static bool posix_cpu_timer_on_expiry_list(struct k_itimer *timer) +{ + if (list_empty(&timer->it.cpu.elist)) + return false; + + /* + * Prevent signal delivery as there is no point in delivering a signal + * which is made obsolete right away. + */ + timer->it.cpu.firing = false; + return true; +} + /* * Clean up a CPU-clock timer that is about to be destroyed. * This is called from timer deletion with the timer already locked. @@ -580,18 +599,10 @@ static int posix_cpu_timer_del(struct k_itimer *timer) p = timer_lock_sighand(timer, &flags); if (likely(p)) { - if (timer->it.cpu.firing) { - /* - * Prevent signal delivery. The timer cannot be dequeued - * because it is on the firing list which is not protected - * by sighand->lock. The delivery path is waiting for - * the timer lock. So go back, unlock and retry. - */ - timer->it.cpu.firing = false; + if (posix_cpu_timer_on_expiry_list(timer)) ret = TIMER_RETRY; - } else { + else disarm_timer(timer, p); - } unlock_task_sighand(p, &flags); } @@ -731,14 +742,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, /* Retrieve the current expiry time before disarming the timer */ old_expires = cpu_timer_getexpires(ctmr); - if (unlikely(timer->it.cpu.firing)) { - /* - * Prevent signal delivery. The timer cannot be dequeued - * because it is on the firing list which is not protected - * by sighand->lock. The delivery path is waiting for - * the timer lock. So go back, unlock and retry. - */ - timer->it.cpu.firing = false; + if (posix_cpu_timer_on_expiry_list(timer)) { ret = TIMER_RETRY; } else { cpu_timer_dequeue(ctmr); From c82b797abe668d0b668601a93ba2c0b071a63574 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Mon, 14 Sep 2026 14:51:23 +0800 Subject: [PATCH 2060/2366] net/sched: reject IDR error pointers when deleting actions tcf_action_delete() drops the reference held by its lookup before calling tcf_idr_delete_index() with the saved action index. An unlocked classifier can remove that action and reserve the same IDR slot with ERR_PTR(-EBUSY) in between. tcf_idr_delete_index() only checks the lookup result for NULL. It therefore treats the reservation as a tc_action and dereferences tcfa_bindcnt. A hardware execution breakpoint was used to schedule the interleaving without changing the kernel source. KASAN reported this decoded trace: BUG: KASAN: null-ptr-deref in tca_action_gd+0x5b9/0x1010 Read of size 4 at addr 0000000000000010 by task poc/150 Oops: general protection fault, probably for non-canonical address 0xdffffc0000000002 RIP: tca_action_gd+0x5c0/0x1010: arch_atomic_read at arch/x86/include/asm/atomic.h:23 raw_atomic_read at include/linux/atomic/atomic-arch-fallback.h:457 atomic_read at include/linux/atomic/atomic-instrumented.h:33 tcf_idr_delete_index at net/sched/act_api.c:766 tcf_action_delete at net/sched/act_api.c:1859 tcf_del_notify at net/sched/act_api.c:2014 tca_action_gd at net/sched/act_api.c:2064 R13: 0000000000000010 R15: fffffffffffffff0 Kernel panic - not syncing: Fatal exception R15 contains ERR_PTR(-EBUSY), and adding the tcfa_bindcnt offset produces the address in R13. With the guard applied, the same reproducer returned -ENOENT without a KASAN report or panic. Treat error pointers as absent and return -ENOENT. Fixes: 0190c1d452a9 ("net: sched: atomically check-allocate action") Cc: stable@vger.kernel.org Reported-by: Xiang Mei Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20260914065123.4109709-2-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/act_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 3f653721c45f..e45a63be397c 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -758,7 +758,7 @@ static int tcf_idr_delete_index(struct tcf_idrinfo *idrinfo, u32 index) mutex_lock(&idrinfo->lock); p = idr_find(&idrinfo->action_idr, index); - if (!p) { + if (IS_ERR_OR_NULL(p)) { mutex_unlock(&idrinfo->lock); return -ENOENT; } From 9d565b6b72fe3f41fd43636e143072848105189f Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Tue, 15 Sep 2026 00:06:58 +0100 Subject: [PATCH 2061/2366] net: usb: catc: bound the RX packet length in catc_rx_done() catc_rx_done() walks a multi-packet URB, reading a two-byte length from each packet header. Its bound, pkt_len > urb->actual_length, ignores the header offset and compares against the whole transfer rather than the bytes left from pkt_start, so a crafted packet header makes skb_copy_to_linear_data() read past the buffer. A length below ETH_HLEN is also accepted, including zero, and eth_type_trans() then reads a MAC header from the uninitialised tailroom of a shorter skb. The is_f5u011 branch takes its length straight from the transfer, so a zero-length URB reaches the same path. Track the bytes remaining from the current packet, and reject a header that does not fit, a length past what is left, and a length below an Ethernet header. A transfer shorter than an Ethernet header, including a zero-length one, previously became a runt skb passed to netif_rx() and counted as received; it is now counted in rx_length_errors and ends the walk. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Aamir Ahmed Reviewed-by: Simon Horman Link: https://patch.msgid.link/AS8P251MB00015FD7716F38C345619B56C8BB2@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM Signed-off-by: Jakub Kicinski --- drivers/net/usb/catc.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/net/usb/catc.c b/drivers/net/usb/catc.c index 96e82f94edcf..39b678f175dd 100644 --- a/drivers/net/usb/catc.c +++ b/drivers/net/usb/catc.c @@ -233,17 +233,26 @@ static void catc_rx_done(struct urb *urb) } do { - if(!catc->is_f5u011) { - pkt_len = le16_to_cpup((__le16*)pkt_start); - if (pkt_len > urb->actual_length) { + int remaining = urb->actual_length - + (pkt_start - (u8 *)urb->transfer_buffer); + + if (!catc->is_f5u011) { + if (remaining < pkt_offset) { catc->netdev->stats.rx_length_errors++; catc->netdev->stats.rx_errors++; break; } + pkt_len = le16_to_cpup((__le16 *)pkt_start); } else { pkt_len = urb->actual_length; } + if (pkt_len < ETH_HLEN || pkt_len + pkt_offset > remaining) { + catc->netdev->stats.rx_length_errors++; + catc->netdev->stats.rx_errors++; + break; + } + if (!(skb = dev_alloc_skb(pkt_len))) return; From ab888242fce4f16f6c4d4c6ec53939ad36aa3b3a Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Tue, 15 Sep 2026 01:31:52 -0700 Subject: [PATCH 2062/2366] vlan: require the MAC header to be present in __vlan_insert_inner_tag() __vlan_insert_inner_tag() only guarantees head room via skb_cow_head(), never that mac_len bytes of MAC header are present. Its ETH_HLEN wrappers - __vlan_insert_tag() under skb_vlan_push(), and vlan_insert_tag() under validate_xmit_vlan() on the generic transmit path - therefore rewrite the first 16 bytes at skb->data: a 12-byte memmove plus two 2-byte stores at +12 and +14. No caller supplies the bound, while the pop helpers use skb_ensure_writable()/pskb_may_pull(). An IFF_TUN device has hard_header_len == 0, so packet_snd() accepts a one-byte AF_PACKET/SOCK_RAW frame. The first vlan push only sets a hwaccel tag; the next - clsact "action vlan push" or bpf_skb_vlan_push() - enters the helper with skb->len still 1. The head comes from skbuff_small_head without __GFP_ZERO, so each push drags bytes from beyond skb->tail into the frame. After three the one-byte send leaves as 13 bytes carrying 11 bytes of uninitialised slab: 0000: 5a b3 62 12 80 88 ff ff 00 b3 62 12 81 `------------------------------' only 0x5a was sent; the rest is slab, here the top 56 bits of a linear-map address Require the MAC header the helper rewrites to be present, so such a frame is dropped rather than transmitted. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: co+0ea1ac045375cf05@bugs.sh Signed-off-by: Xiang Mei Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260915083152.705309-1-xmei5@asu.edu Signed-off-by: Jakub Kicinski --- include/linux/if_vlan.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h index 20cc16ea4e5a..4846032bf4ff 100644 --- a/include/linux/if_vlan.h +++ b/include/linux/if_vlan.h @@ -365,6 +365,9 @@ static inline int __vlan_insert_inner_tag(struct sk_buff *skb, const u8 meta_len = mac_len > ETH_TLEN ? skb_metadata_len(skb) : 0; struct vlan_ethhdr *veth; + if (unlikely(!pskb_may_pull(skb, mac_len))) + return -EINVAL; + if (skb_cow_head(skb, meta_len + VLAN_HLEN) < 0) return -ENOMEM; From a11212910cf09b2fe8db9afa41ef60c4f81879c5 Mon Sep 17 00:00:00 2001 From: Yuqi Xu Date: Sat, 19 Sep 2026 16:45:02 +0800 Subject: [PATCH 2063/2366] bpf: Check params size before reading reserved fields bpf_crypto_ctx_create() is a kfunc whose second argument is declared with the __sz annotation, so the verifier only guarantees that params__sz bytes of params are valid. The function nevertheless reads params->reserved[0] and params->reserved[1] (offsets 14 and 15) before comparing params__sz against the size of struct bpf_crypto_params, so a BPF program can pass a shorter buffer and have the kernel read past the region that was validated for it. Move the size check in front of the reserved field reads. Fixes: 3e1c6f35409f ("bpf: make common crypto API for TC/XDP programs") Reported-by: Vega Signed-off-by: Yuqi Xu Signed-off-by: Alexei Starovoitov Reviewed-by: Ren Wei Link: https://patch.msgid.link/4f3ab4b03e79017e215521743996555439bf0bb3.1789802413.git.xuyuqiabc@gmail.com --- kernel/bpf/crypto.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/crypto.c b/kernel/bpf/crypto.c index 51f89cecefb4..3f3fe2450fc6 100644 --- a/kernel/bpf/crypto.c +++ b/kernel/bpf/crypto.c @@ -149,8 +149,9 @@ bpf_crypto_ctx_create(const struct bpf_crypto_params *params, u32 params__sz, const struct bpf_crypto_type *type; struct bpf_crypto_ctx *ctx; - if (!params || params->reserved[0] || params->reserved[1] || - params__sz != sizeof(struct bpf_crypto_params)) { + if (!params || + params__sz != sizeof(struct bpf_crypto_params) || + params->reserved[0] || params->reserved[1]) { *err = -EINVAL; return NULL; } From 8a60ade2277e1f0e0d0578d565354e52292fa46d Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Thu, 17 Sep 2026 06:57:32 -0400 Subject: [PATCH 2064/2366] net/sched: sch_hfsc: bound the classify inner-filter walk with a drift budget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hfsc_classify() applies the "filter may only point downwards" level check only when the filter result carries no bound class. A filter created with a flowid gets res.class set once at bind time, so the check never runs for it during classification. hfsc_adjust_levels() can later raise a class's level without revalidating existing bindings, leaving two binds that were each legal at bind time pointing at each other; the classify walk then bounces between two interior classes forever with the qdisc lock held and BH disabled — a soft lockup from a single packet. The stuck walk trips the watchdog: watchdog: BUG: soft lockup - CPU#3 stuck for 13s! [ping:444] RIP: 0010:u32_classify+0x542/0x17f0 ... tcf_classify+0x66/0xa0 hfsc_enqueue+0x166/0xdf0 Bound the traversal with a budget of non-descending hops, the only way a configured walk can move without descending the class tree once levels drift after bind time. The budget is cumulative over the whole walk and is deliberately not reset on a descending hop: a chain that alternates a descent with a lateral hop would return the budget every lap and never trip. Descending hops never decrement it, so legitimately deep trees are unaffected and a terminating lateral chain still classifies normally. Drop the packet with a rate-limited warning once the budget is exhausted, mirroring the merged HTB fix. This is a follow-up to commit 729c4896ab82 ("net/sched: sch_htb: limit htb_classify inner-class filter hops"), which bounded the same classify loop on the HTB side but left the HFSC walk unbounded. Conditions to recreate the bug: - CONFIG_NET_SCHED, CONFIG_NET_SCH_HFSC, CONFIG_NET_CLS_U32, CONFIG_LOCKUP_DETECTOR. - Build a cycle with two legal-at-bind-time flowid binds and a level drift: class X 1:1 (child of root) with leaf child 1:10; class Y 1:2 (sibling of X) with children 1:20 and 1:200; root u32 filter flowid 1:1; filter on X flowid 1:2 (legal when Y is a leaf); after Y's level rises to 2, filter on Y flowid 1:1 (legal then). Send one packet (ping on the device). Unfixed kernel: classify spins with the qdisc lock held; with softlockup_panic=1 it panics. - Reachable from unprivileged user via unshare -Urn (CAP_NET_ADMIN). Fixes: a2f79227138c ("net_sched: sch_hfsc: fix classification loops") Reported-by: Sashiko (gemini + nipa) Closes: https://lore.kernel.org/netdev/QDISC-CTUU.v2.20260913192614@mojatatu.com/ Link: https://sashiko.dev/#/patchset/QDISC-CTUU.v2.20260913192614@mojatatu.com Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/QDISC-CTUU.v2.20260913192614%40mojatatu.com Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-CTUU.v3.20260916184908@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/sch_hfsc.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c index e87f5021a199..284490fd6ca9 100644 --- a/net/sched/sch_hfsc.c +++ b/net/sched/sch_hfsc.c @@ -386,6 +386,15 @@ cftree_update(struct hfsc_class *cl) #define SM_MASK ((1ULL << SM_SHIFT) - 1) #define ISM_MASK ((1ULL << ISM_SHIFT) - 1) +/* + * Cap on the non-descending hops a classify walk may take before its + * filter chain is treated as misconfigured. A flowid binding that was + * legal at bind time can become lateral once hfsc_adjust_levels() + * raises a class level; a few such hops are legitimate, an unbounded + * run means the chain cycles. + */ +#define HFSC_CLASSIFY_MAX_DRIFT 8 + static inline u64 seg_x2y(u64 x, u64 sm) { @@ -1133,6 +1142,7 @@ hfsc_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) struct hfsc_class *head, *cl; struct tcf_result res; struct tcf_proto *tcf; + unsigned int drift; int result; if (TC_H_MAJ(skb->priority ^ sch->handle) == 0 && @@ -1142,6 +1152,7 @@ hfsc_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; head = &q->root; + drift = HFSC_CLASSIFY_MAX_DRIFT; tcf = rcu_dereference_bh(q->root.filter_list); while (tcf && (result = tcf_classify_qdisc(skb, tcf, &res, false)) >= 0) { #ifdef CONFIG_NET_CLS_ACT @@ -1167,6 +1178,17 @@ hfsc_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) if (cl->level == 0) return cl; /* hit leaf class */ + /* + * flowid binds skip the level check above (res.class is set + * at bind time and levels drift after), so a walk can follow + * lateral hops without descending; a bounded number of them + * is legal, more means the chain cycles. + */ + if (cl->level >= head->level && drift-- == 0) { + pr_warn_ratelimited("hfsc: classify hop budget exhausted, dropping packet\n"); + return NULL; + } + /* apply inner filter chain */ tcf = rcu_dereference_bh(cl->filter_list); head = cl; From 1e24c4f2ee44be0eee94092b5d13cbdb4bdf0d60 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Thu, 17 Sep 2026 06:57:33 -0400 Subject: [PATCH 2065/2366] selftests: tc-testing: add a lateral-drift hfsc classify-walk test The classify-loop fix bounds a walk's non-descending hops, so the guard must not misfire on a legal walk that reaches its leaf through a level-drift lateral chain. Add a case that builds exactly that chain and asserts traffic still reaches the chain's own leaf. A lateral hop can only exist because a bind was legal when it was made and a later class add raised the target's level, so the setup binds each hop while the target is still a leaf and only then deepens it: bind 1:1 -> 1:2 while 1:2 is a leaf, add 1:20 under 1:2, add 1:3 and bind 1:2 -> 1:3 while 1:3 is a leaf, then add 1:30 and 1:31 under 1:3 and bind 1:3 -> 1:31. The walk root -> 1:1 -> 1:2 -> 1:3 -> 1:31 then takes two lateral hops and must reach leaf 1:31. The default class is 1:30, distinct from the asserted leaf, and the verify pattern is anchored to the 1:31 stats line, so neither a fall-through to the default nor a nonzero count on another class can satisfy the check. On the patched kernel the test passes; with the bound forced to zero the walk falls to the default and 1:31 stays idle, so the test fails. Reviewed-by: Victor Nogueira Tested-by: hybris Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-CTUU.v3.20260916184908@mojatatu.com.2 Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/qdiscs/hfsc.json | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json index c98c339424d4..4f6bbb8b57f9 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json @@ -169,5 +169,39 @@ "teardown": [ "$TC qdisc del dev $DUMMY handle 1: root" ] + }, + { + "id": "8c39", + "name": "HFSC classify walk still reaches leaf after lateral drift", + "category": [ + "qdisc", + "hfsc" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "ip link set lo up", + "$TC qdisc add dev lo handle 1: root hfsc default 30", + "$TC class add dev lo parent 1: classid 1:1 hfsc rt m2 100kbit", + "$TC class add dev lo parent 1:1 classid 1:10 hfsc rt m2 50kbit", + "$TC class add dev lo parent 1: classid 1:2 hfsc rt m2 100kbit", + "$TC filter add dev lo parent 1: protocol ip prio 1 u32 match u8 0 0 at 0 flowid 1:1", + "$TC filter add dev lo parent 1:1 protocol ip prio 1 u32 match u8 0 0 at 0 flowid 1:2", + "$TC class add dev lo parent 1:2 classid 1:20 hfsc rt m2 10kbit", + "$TC class add dev lo parent 1: classid 1:3 hfsc rt m2 100kbit", + "$TC filter add dev lo parent 1:2 protocol ip prio 1 u32 match u8 0 0 at 0 flowid 1:3", + "$TC class add dev lo parent 1:3 classid 1:30 hfsc rt m2 10kbit", + "$TC class add dev lo parent 1:3 classid 1:31 hfsc rt m2 100kbit", + "$TC filter add dev lo parent 1:3 protocol ip prio 1 u32 match u8 0 0 at 0 flowid 1:31" + ], + "cmdUnderTest": "ping -n -c 10 -W 1 127.0.0.1", + "expExitCode": "0", + "verifyCmd": "$TC -s class show dev lo", + "matchPattern": "class hfsc 1:31 parent 1:3 rt[^\\n]*\\n Sent [0-9]+ bytes [1-9][0-9]* pkt", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev lo handle 1: root" + ] } ] From 8f6f8a48399f82f4a83f7b9f25b9707e0062a4f9 Mon Sep 17 00:00:00 2001 From: Adarsh Das Date: Sat, 19 Sep 2026 16:27:32 +0530 Subject: [PATCH 2066/2366] smb: client: delete compound mids on send failure before unlock When sending a compound request fails, smb_send_rqst() kicks off a reconnect. compound_send_recv() still has those mids on pending_mid_q, but it unlocks the server without removing them first. During reconnect, cifs_abort_connection() walks pending_mid_q and runs each mid callback. With no response yet, those callbacks return credits and drop in_flight. Then compound_send_recv()'s send-error path returns the same credits again. in_flight ends up decremented twice and smb2_add_credits() WARNs. syzbot hits this during SMB2_negotiate when the socket send fails. cifs_call_async() already calls delete_mid() before unlock on send failure. Do the same for compound chains and set cancelled_mid[] so the out: path does not delete them again. Fixes: ee258d79159a ("CIFS: Move credit processing to mid callbacks for SMB3") Reported-by: syzbot+eeb58d2197d88720a228@syzkaller.appspotmail.com Closes: https://lore.kernel.org/r/6a727d22.40259c87.584f4.04ce.GAE@google.com Tested-by: syzbot+eeb58d2197d88720a228@syzkaller.appspotmail.com Assisted-by: LLM Cc: stable@vger.kernel.org Signed-off-by: Adarsh Das Signed-off-by: Paulo Alcantara --- fs/smb/client/transport.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index e266859818a4..7df5b3447aea 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -965,6 +965,10 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, if (rc < 0) { revert_current_mid(server, num_rqst); server->sequence_number -= 2; + for (i = 0; i < num_rqst; i++) { + delete_mid(server, mid[i]); + cancelled_mid[i] = true; + } } cifs_server_unlock(server); From f7eeb1af8537b05953fb1c88ab8b59d94059a381 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Thu, 27 Aug 2026 23:43:01 +0800 Subject: [PATCH 2067/2366] i2c: at91: release DMA channels on remove and probe error at91_twi_configure_dma() requests exclusive tx/rx DMA channels, but nothing ever releases them on driver detach, and the probe error path after the channels are acquired (i2c_add_numbered_adapter() failure) returns without releasing them either, because the remove callback is not invoked after a failed probe. Move the release into a helper, call it from the existing configure-failure path, the adapter-registration failure path, and at91_twi_remove(). Fixes: 60937b2cdbf9 ("i2c: at91: add dma support") Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Cc: # v3.8+ Acked-by: Mukesh Kumar Savaliya Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260827-i2c-dma-channel-leak-v1-1-271d4adc03a0@cherr.cc --- drivers/i2c/busses/i2c-at91-core.c | 3 +++ drivers/i2c/busses/i2c-at91-master.c | 12 +++++++++++- drivers/i2c/busses/i2c-at91.h | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-at91-core.c b/drivers/i2c/busses/i2c-at91-core.c index b64adef778d4..8ca4556d9664 100644 --- a/drivers/i2c/busses/i2c-at91-core.c +++ b/drivers/i2c/busses/i2c-at91-core.c @@ -255,6 +255,7 @@ static int at91_twi_probe(struct platform_device *pdev) if (rc) { pm_runtime_disable(dev->dev); pm_runtime_set_suspended(dev->dev); + at91_twi_dma_release(dev); return rc; } @@ -270,6 +271,8 @@ static void at91_twi_remove(struct platform_device *pdev) i2c_del_adapter(&dev->adapter); + at91_twi_dma_release(dev); + pm_runtime_disable(dev->dev); pm_runtime_set_suspended(dev->dev); } diff --git a/drivers/i2c/busses/i2c-at91-master.c b/drivers/i2c/busses/i2c-at91-master.c index 894cedbca99f..68238cc8aee0 100644 --- a/drivers/i2c/busses/i2c-at91-master.c +++ b/drivers/i2c/busses/i2c-at91-master.c @@ -817,11 +817,21 @@ static int at91_twi_configure_dma(struct at91_twi_dev *dev, u32 phy_addr) error: if (ret != -EPROBE_DEFER) dev_info(dev->dev, "can't get DMA channel, continue without DMA support\n"); + at91_twi_dma_release(dev); + return ret; +} + +void at91_twi_dma_release(struct at91_twi_dev *dev) +{ + struct at91_twi_dma *dma = &dev->dma; + if (dma->chan_rx) dma_release_channel(dma->chan_rx); if (dma->chan_tx) dma_release_channel(dma->chan_tx); - return ret; + dma->chan_rx = NULL; + dma->chan_tx = NULL; + dev->use_dma = false; } static int at91_init_twi_recovery_gpio(struct platform_device *pdev, diff --git a/drivers/i2c/busses/i2c-at91.h b/drivers/i2c/busses/i2c-at91.h index 942e9c3973bb..d68fcbbc3e0f 100644 --- a/drivers/i2c/busses/i2c-at91.h +++ b/drivers/i2c/busses/i2c-at91.h @@ -172,6 +172,7 @@ void at91_twi_irq_restore(struct at91_twi_dev *dev); void at91_init_twi_bus(struct at91_twi_dev *dev); void at91_init_twi_bus_master(struct at91_twi_dev *dev); +void at91_twi_dma_release(struct at91_twi_dev *dev); int at91_twi_probe_master(struct platform_device *pdev, u32 phy_addr, struct at91_twi_dev *dev); From e9f03b9625e2eeaca357b065c92d5b14064a1583 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Thu, 27 Aug 2026 23:43:02 +0800 Subject: [PATCH 2068/2366] i2c: imx: release DMA channels on probe error i2c_imx_dma_request() acquires exclusive tx/rx DMA channels and is optional: on errors other than -EPROBE_DEFER the driver falls back to PIO mode and probe continues. If i2c_add_numbered_adapter() then fails, probe returns through clk_notifier_unregister without releasing the channels, because the remove callback is not invoked after a failed probe. Release the channels on the probe error path, mirroring i2c_imx_remove(). Fixes: ce1a78840ff7 ("i2c: imx: add DMA support for freescale i2c driver") Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Cc: # v3.19+ Reviewed-by: Frank Li Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260827-i2c-dma-channel-leak-v1-2-271d4adc03a0@cherr.cc --- drivers/i2c/busses/i2c-imx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index 4dc3df6a41a4..ff5a91158df9 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -1880,6 +1880,8 @@ static int i2c_imx_probe(struct platform_device *pdev) clk_notifier_unregister: clk_notifier_unregister(i2c_imx->clk, &i2c_imx->clk_change_nb); + if (i2c_imx->dma) + i2c_imx_dma_free(i2c_imx); free_irq(irq, i2c_imx); rpm_disable: pm_runtime_put_noidle(&pdev->dev); From 268aacb2e2a5c94d08e23194961234d0710c8407 Mon Sep 17 00:00:00 2001 From: Shengzhuo Wei Date: Thu, 27 Aug 2026 23:43:03 +0800 Subject: [PATCH 2069/2366] i2c: qcom-geni: release DMA channels on probe error geni_i2c_init() grabs exclusive GPI tx/rx DMA channels when the serial engine runs in GPI mode. If i2c_add_adapter() subsequently fails, probe returns without releasing the channels, because the remove callback is not invoked after a failed probe. The adapter-registration failure path used to release the channels via its err_dma label; that release was dropped when the probe tail was restructured into geni_i2c_init(). Release the channels on the adapter-registration failure path, mirroring geni_i2c_remove(). Fixes: d8d3bb127ad1 ("i2c: qcom-geni: Isolate serial engine setup") Assisted-by: GLM:5.3 Signed-off-by: Shengzhuo Wei Reviewed-by: Konrad Dybcio Reviewed-by: Mukesh Kumar Savaliya Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260827-i2c-dma-channel-leak-v1-3-271d4adc03a0@cherr.cc --- drivers/i2c/busses/i2c-qcom-geni.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index 00013b41a6f5..e9e41a174c20 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -1189,8 +1189,10 @@ static int geni_i2c_probe(struct platform_device *pdev) return ret; ret = i2c_add_adapter(&gi2c->adap); - if (ret) + if (ret) { + release_gpi_dma(gi2c); return dev_err_probe(dev, ret, "Error adding i2c adapter\n"); + } dev_dbg(dev, "Geni-I2C adaptor successfully added\n"); From 7362a1553eb09a8cdf8be7e509bd5309a8342486 Mon Sep 17 00:00:00 2001 From: Liu Zhenlong Date: Wed, 19 Aug 2026 01:57:50 +0800 Subject: [PATCH 2070/2366] i2c: qcom-cci: fix device_node refcount leak in cci_probe()/cci_remove() The of_node_put() matching of_node_get() runs after i2c_del_adapter(), whose trailing memset() zeroes adap->dev and thus adap->dev.of_node, making the put a no-op and leaking the node on every adapter removal and error cleanup. Use a devm action: the pointer is captured at registration, out of reach of that memset(), and devres runs the put once on probe failure and detach, replacing the three manual of_node_put() calls. The setup loop uses the scoped iterator form so the child node is released automatically if devm_add_action_or_reset() fails mid-loop. Suggested-by: Konrad Dybcio Fixes: 02a4a69667a2 ("i2c: qcom-cci: don't put a device tree node before i2c_add_adapter()") Assisted-by: Claude:claude-opus-5 Signed-off-by: Liu Zhenlong Cc: # v5.17+ Reviewed-by: Vladimir Zapolskiy Reviewed-by: Konrad Dybcio Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260818175750.4205-1-dragonliu2018@gmail.com --- drivers/i2c/busses/i2c-qcom-cci.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-cci.c b/drivers/i2c/busses/i2c-qcom-cci.c index 25b6e4e9e3fa..873e901a23d7 100644 --- a/drivers/i2c/busses/i2c-qcom-cci.c +++ b/drivers/i2c/busses/i2c-qcom-cci.c @@ -497,10 +497,14 @@ static const struct dev_pm_ops qcom_cci_pm = { SET_RUNTIME_PM_OPS(cci_suspend_runtime, cci_resume_runtime, NULL) }; +static void cci_put_of_node(void *data) +{ + of_node_put(data); +} + static int cci_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *child; struct resource *r; struct cci *cci; int ret, i; @@ -516,7 +520,7 @@ static int cci_probe(struct platform_device *pdev) if (!cci->data) return -ENOENT; - for_each_available_child_of_node(dev->of_node, child) { + for_each_available_child_of_node_scoped(dev->of_node, child) { struct cci_master *master; u32 idx; @@ -537,6 +541,9 @@ static int cci_probe(struct platform_device *pdev) master->adap.algo = &cci_algo; master->adap.dev.parent = dev; master->adap.dev.of_node = of_node_get(child); + ret = devm_add_action_or_reset(dev, cci_put_of_node, child); + if (ret) + return ret; master->master = idx; master->cci = cci; @@ -606,10 +613,8 @@ static int cci_probe(struct platform_device *pdev) continue; ret = i2c_add_adapter(&cci->master[i].adap); - if (ret < 0) { - of_node_put(cci->master[i].adap.dev.of_node); + if (ret < 0) goto error_i2c; - } } return 0; @@ -617,10 +622,8 @@ static int cci_probe(struct platform_device *pdev) error_i2c: for (--i ; i >= 0; i--) { - if (cci->master[i].cci) { + if (cci->master[i].cci) i2c_del_adapter(&cci->master[i].adap); - of_node_put(cci->master[i].adap.dev.of_node); - } } disable_clocks: cci_disable_clocks(cci); @@ -636,7 +639,6 @@ static void cci_remove(struct platform_device *pdev) for (i = 0; i < cci->data->num_masters; i++) { if (cci->master[i].cci) { i2c_del_adapter(&cci->master[i].adap); - of_node_put(cci->master[i].adap.dev.of_node); cci_halt(cci, i); } } From c4e941bb7654bcbdfb0b6f3341dc2acfdf235c8d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 15 Sep 2026 22:10:30 +0200 Subject: [PATCH 2071/2366] landlock: Work around gcc-16 -Wuninitialized warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gcc has a bug with -ftrivial-auto-var-init=pattern that produces a warning for correct code that uses sparse bitfields: security/landlock/fs.c: In function 'is_access_to_paths_allowed.isra': security/landlock/fs.c:767:28: error: '_layer_masks_child1' is used uninitialized [-Werror=uninitialized] 767 | struct layer_masks _layer_masks_child1, _layer_masks_child2; | ^~~~~~~~~~~~~~~~~~~ security/landlock/fs.c:767:28: note: '_layer_masks_child1' declared here 767 | struct layer_masks _layer_masks_child1, _layer_masks_child2; | ^~~~~~~~~~~~~~~~~~~ security/landlock/fs.c: In function 'hook_unix_find': security/landlock/fs.c:1649:28: error: 'layer_masks' is used uninitialized [-Werror=uninitialized] 1649 | struct layer_masks layer_masks; | ^~~~~~~~~~~ security/landlock/fs.c:1649:28: note: 'layer_masks' declared here 1649 | struct layer_masks layer_masks; | ^~~~~~~~~~~ To work around this, change the definition of struct layer_mask to use an explictit padding field. Link: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110743 Link: https://lore.kernel.org/all/20260619082133.3504146-1-arnd@kernel.org/ Fixes: a260c0055665 ("landlock: Add a place for flags to layer rules") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260915201036.3527935-1-arnd@kernel.org [mic: Use BITS_PER_TYPE(), fix kdoc warnings, fix commit message according to v2 changes] Cc: stable@vger.kernel.org Signed-off-by: Mickaël Salaün --- security/landlock/access.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/security/landlock/access.h b/security/landlock/access.h index bbbb41f41147..f843835851d0 100644 --- a/security/landlock/access.h +++ b/security/landlock/access.h @@ -61,6 +61,10 @@ union access_masks_all { static_assert(sizeof(typeof_member(union access_masks_all, masks)) == sizeof(typeof_member(union access_masks_all, all))); +#define _LANDLOCK_LAYER_MASK_PADDING \ + (BITS_PER_TYPE(access_mask_t) - LANDLOCK_NUM_ACCESS_MAX - \ + IS_ENABLED(CONFIG_SECURITY_LANDLOCK_LOG)) + /** * struct layer_mask - The access rights and rule flags for a layer. * @@ -81,6 +85,10 @@ struct layer_mask { */ access_mask_t quiet : 1; #endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + /** + * @__pad: Padding for the compiler's bitfield initialization. + */ + access_mask_t __pad : _LANDLOCK_LAYER_MASK_PADDING; } __packed __aligned(sizeof(access_mask_t)); /* From f71ecaece401cef287cdca12aad785fec809cb9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:24 +0200 Subject: [PATCH 2072/2366] landlock: Fix tracepoint fixed-width type names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new Landlock tracepoints use UAPI-prefixed __u32 and __u64 names for callback arguments and record fields, including internal IDs that are not Landlock UAPI values. Typed BPF consumers see callback typedef names through BTF. Use the kernel u32 and u64 aliases before release so the tracepoint contract does not present internal values as Landlock UAPI types. This changes BTF-visible typedef spelling but not integer widths, calling conventions, tracefs formats, or record layouts. Cc: Günther Noack Cc: Steven Rostedt Link: https://patch.msgid.link/20260918185036.608651-2-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 68 ++++++++++++++++----------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index d05253afaf59..4984f80923ed 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -243,8 +243,8 @@ static inline const char *__trace_landlock_print_layers( * Field encoding * ~~~~~~~~~~~~~~ * - * Fields that mirror the Landlock UAPI use the same C types and endianness - * (e.g. network ports are __u64 in host endianness, like + * Fields that mirror the Landlock UAPI preserve their widths and endianness + * (e.g. network ports are u64 in host endianness, like * landlock_net_port_attr.port). Per-event details, such as where a value * is byte-swapped, live in the field's own kdoc. * @@ -319,8 +319,8 @@ TRACE_EVENT(landlock_create_ruleset, TP_ARGS(ruleset), TP_STRUCT__entry( - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, ruleset_id ) + __field( u32, ruleset_version ) __field( access_mask_t, handled_fs ) __field( access_mask_t, handled_net ) __field( access_mask_t, scoped ) @@ -359,8 +359,8 @@ TRACE_EVENT(landlock_free_ruleset, TP_ARGS(ruleset), TP_STRUCT__entry( - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, ruleset_id ) + __field( u32, ruleset_version ) ), TP_fast_assign( @@ -396,8 +396,8 @@ TRACE_EVENT(landlock_add_rule_fs, TP_ARGS(ruleset, access_rights, path, pathname), TP_STRUCT__entry( - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, ruleset_id ) + __field( u32, ruleset_version ) __field( access_mask_t, access_rights ) __field( dev_t, dev ) __field( ino_t, ino ) @@ -443,15 +443,15 @@ TRACE_EVENT(landlock_add_rule_fs, TRACE_EVENT(landlock_add_rule_net, TP_PROTO(const struct landlock_ruleset *ruleset, - access_mask_t access_rights, __u64 port), + access_mask_t access_rights, u64 port), TP_ARGS(ruleset, access_rights, port), TP_STRUCT__entry( - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, ruleset_id ) + __field( u32, ruleset_version ) __field( access_mask_t, access_rights ) - __field( __u64, port ) + __field( u64, port ) ), TP_fast_assign( @@ -495,10 +495,10 @@ TRACE_EVENT(landlock_create_domain, TP_ARGS(domain, ruleset), TP_STRUCT__entry( - __field( __u64, domain_id ) - __field( __u64, parent_id ) - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, domain_id ) + __field( u64, parent_id ) + __field( u64, ruleset_id ) + __field( u32, ruleset_version ) ), TP_fast_assign( @@ -557,7 +557,7 @@ TRACE_EVENT(landlock_enforce_domain, TP_ARGS(domain, complete, process_wide, no_new_privs), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, complete ) __field( bool, process_wide ) __field( bool, no_new_privs ) @@ -595,8 +595,8 @@ TRACE_EVENT(landlock_free_domain, TP_ARGS(hierarchy), TP_STRUCT__entry( - __field( __u64, domain_id ) - __field( __u64, denials ) + __field( u64, domain_id ) + __field( u64, denials ) ), TP_fast_assign( @@ -631,7 +631,7 @@ TRACE_EVENT(landlock_check_rule_fs, TP_ARGS(domain, rule, access_request, dentry), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( access_mask_t, access_request ) __field( dev_t, dev ) __field( ino_t, ino ) @@ -675,14 +675,14 @@ TRACE_EVENT(landlock_check_rule_net, TP_PROTO(const struct landlock_domain *domain, const struct landlock_rule *rule, - access_mask_t access_request, __u64 port), + access_mask_t access_request, u64 port), TP_ARGS(domain, rule, access_request, port), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( access_mask_t, access_request ) - __field( __u64, port ) + __field( u64, port ) __dynamic_array(access_mask_t, grants, domain->num_layers) ), @@ -729,7 +729,7 @@ TRACE_EVENT(landlock_deny_access_fs, TP_ARGS(hierarchy, same_exec, logged, blockers, path, pathname), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) __field( access_mask_t, blockers ) @@ -791,17 +791,17 @@ TRACE_EVENT(landlock_deny_access_net, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, bool logged, access_mask_t blockers, const struct sock *sk, - __u64 sport, __u64 dport), + u64 sport, u64 dport), TP_ARGS(hierarchy, same_exec, logged, blockers, sk, sport, dport), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) __field( access_mask_t, blockers ) - __field( __u64, sport ) - __field( __u64, dport ) + __field( u64, sport ) + __field( u64, dport ) ), TP_fast_assign( @@ -842,10 +842,10 @@ TRACE_EVENT(landlock_deny_ptrace, TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( __u64, tracee_domain_id) + __field( u64, tracee_domain_id) __field( pid_t, tracee_pid ) __string( tracee_comm, tracee->comm ) ), @@ -891,10 +891,10 @@ TRACE_EVENT(landlock_deny_scope_signal, TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( __u64, target_domain_id) + __field( u64, target_domain_id) __field( pid_t, target_pid ) __string( target_comm, target->comm ) ), @@ -940,10 +940,10 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket, TP_ARGS(hierarchy, same_exec, logged, peer_domain_id, peer), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( __u64, peer_domain_id ) + __field( u64, peer_domain_id ) __field( pid_t, peer_pid ) /* * Abstract socket names are untrusted binary data from From 0de33ca344fbf983d380d78db6eeb6fe312d5a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:25 +0200 Subject: [PATCH 2073/2366] landlock: Fix filesystem denial blocker reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filesystem topology denials are rendered with an empty blockers value because their blocker is identified by the request type instead of an access mask. Introduce the private struct landlock_blockers to carry the request type and final missing access mask to filesystem and network denial tracepoints. Copy both members into named trace-record fields, then use the type to print change_topology for topology denials while preserving symbolic access masks for ordinary denials. The request type lets typed BPF consumers distinguish topology denials from access denials. Keeping the native access mask in a pointer-reached field also lets CO-RE adjust existing programs' load width if access_mask_t grows. Cc: Günther Noack Cc: Steven Rostedt Fixes: 01ce260f5ccf ("landlock: Add landlock_deny_access_fs and landlock_deny_access_net") Link: https://patch.msgid.link/20260918185036.608651-3-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 51 ++++++++++++++++++++++++--------- security/landlock/log.h | 5 ++++ security/landlock/trace.c | 19 +++++++++--- 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index 4984f80923ed..4e304cab1dab 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -17,7 +17,9 @@ #include #include +enum landlock_request_type; struct dentry; +struct landlock_blockers; struct landlock_domain; struct landlock_hierarchy; struct landlock_rule; @@ -26,6 +28,10 @@ struct path; struct sock; struct task_struct; +TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY); +TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_ACCESS); +TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NET_ACCESS); + #ifdef CREATE_TRACE_POINTS /* About 6 KiB, leaving about 2 KiB for sibling helpers and fixed fields. */ @@ -182,6 +188,9 @@ static inline const char *__trace_landlock_print_layers( /* Maps a shared _LANDLOCK_*_NAMES entry to a __print_flags() pair. */ #define _LANDLOCK_NAME_ENTRY(mask, name) { mask, name } +#define _LANDLOCK_FS_BLOCKER_TYPE_NAMES \ + { LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, "change_topology" } + /** * DOC: Landlock trace events * @@ -281,6 +290,13 @@ static inline const char *__trace_landlock_print_layers( * the two parties without kernel-internal state. The ID is a scalar * snapshot, not a live domain pointer that could dangle: an optional * relational referent is a scalar (0 sentinel), not a nullable pointer. + * + * Blocker fields + * ~~~~~~~~~~~~~~ + * + * The filesystem and network blocker arguments identify the request type + * and carry its final missing access subset when applicable. The type + * determines how to interpret the access value. */ /* @@ -712,8 +728,7 @@ TRACE_EVENT(landlock_check_rule_net, * domain field. * @same_exec: Whether the current task entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. - * @blockers: Access mask that was blocked (zero for a mount-topology - * change, whose only blocker is the operation itself). + * @blockers: Request type and final missing access subset (never NULL). * @path: Filesystem path that was denied (never NULL). * @pathname: Resolved path string (never NULL; an error placeholder on * resolution failure). @@ -723,8 +738,8 @@ TRACE_EVENT(landlock_check_rule_net, TRACE_EVENT(landlock_deny_access_fs, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, - bool logged, access_mask_t blockers, const struct path *path, - const char *pathname), + bool logged, const struct landlock_blockers *blockers, + const struct path *path, const char *pathname), TP_ARGS(hierarchy, same_exec, logged, blockers, path, pathname), @@ -732,7 +747,8 @@ TRACE_EVENT(landlock_deny_access_fs, __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( access_mask_t, blockers ) + __field( enum landlock_request_type, blockers_type ) + __field( access_mask_t, blockers_access ) __field( dev_t, dev ) __field( ino_t, ino ) __string( pathname, pathname ) @@ -744,7 +760,8 @@ TRACE_EVENT(landlock_deny_access_fs, __entry->domain_id = hierarchy->id; __entry->same_exec = same_exec; __entry->logged = logged; - __entry->blockers = blockers; + __entry->blockers_type = blockers->type; + __entry->blockers_access = blockers->access; __entry->dev = path->dentry->d_sb->s_dev; /* * A negative dentry has no backing inode, so mirror the @@ -756,7 +773,10 @@ TRACE_EVENT(landlock_deny_access_fs, TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s dev=%u:%u ino=%lu path=%s", __entry->domain_id, __entry->same_exec, __entry->logged, - __print_flags(__entry->blockers, "|", _LANDLOCK_ACCESS_FS_NAMES), + __entry->blockers_type == LANDLOCK_REQUEST_FS_ACCESS ? + __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_FS_NAMES) : + __print_symbolic(__entry->blockers_type, + _LANDLOCK_FS_BLOCKER_TYPE_NAMES), MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __trace_print_untrusted_str(p, __get_str(pathname), __get_dynamic_array_len(pathname) - 1)) @@ -769,7 +789,7 @@ TRACE_EVENT(landlock_deny_access_fs, * domain field. * @same_exec: Whether the current task entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. - * @blockers: Access mask that was blocked. + * @blockers: Request type and final missing access subset (never NULL). * @sk: Socket object (never NULL), read without a socket lock, so its * fields are a best-effort snapshot. The denied endpoint is not * available: the hook runs before :manpage:`bind(2)` / @@ -790,8 +810,8 @@ TRACE_EVENT(landlock_deny_access_fs, TRACE_EVENT(landlock_deny_access_net, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, - bool logged, access_mask_t blockers, const struct sock *sk, - u64 sport, u64 dport), + bool logged, const struct landlock_blockers *blockers, + const struct sock *sk, u64 sport, u64 dport), TP_ARGS(hierarchy, same_exec, logged, blockers, sk, sport, dport), @@ -799,7 +819,8 @@ TRACE_EVENT(landlock_deny_access_net, __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( access_mask_t, blockers ) + __field( enum landlock_request_type, blockers_type ) + __field( access_mask_t, blockers_access ) __field( u64, sport ) __field( u64, dport ) ), @@ -808,14 +829,17 @@ TRACE_EVENT(landlock_deny_access_net, __entry->domain_id = hierarchy->id; __entry->same_exec = same_exec; __entry->logged = logged; - __entry->blockers = blockers; + __entry->blockers_type = blockers->type; + __entry->blockers_access = blockers->access; __entry->sport = sport; __entry->dport = dport; ), TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s sport=%llu dport=%llu", __entry->domain_id, __entry->same_exec, __entry->logged, - __print_flags(__entry->blockers, "|", _LANDLOCK_ACCESS_NET_NAMES), + __entry->blockers_type == LANDLOCK_REQUEST_NET_ACCESS ? + __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_NET_NAMES) : + "unknown", __entry->sport, __entry->dport) ); @@ -991,6 +1015,7 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket, __get_dynamic_array_len(sun_path) - 1)) ); +#undef _LANDLOCK_FS_BLOCKER_TYPE_NAMES #undef _LANDLOCK_NAME_ENTRY #endif /* _TRACE_LANDLOCK_H */ diff --git a/security/landlock/log.h b/security/landlock/log.h index e0a6e44f3ddd..04f3e241e765 100644 --- a/security/landlock/log.h +++ b/security/landlock/log.h @@ -25,6 +25,11 @@ enum landlock_request_type { LANDLOCK_REQUEST_SCOPE_SIGNAL, }; +struct landlock_blockers { + access_mask_t access; + enum landlock_request_type type; +}; + /* * We should be careful to only use a variable of this type for * landlock_log_denial(). This way, the compiler can remove it entirely if diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 8c21e5de6f0d..58276cc32d3f 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -61,7 +61,7 @@ void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy * * @request: Detail of the user space request. * @youngest_denied: The youngest hierarchy node that denied the access. - * @missing: The set of denied access rights. + * @missing: The final missing access subset, when applicable. * @same_exec: Whether the current task is the same executable that called * landlock_restrict_self() for the denying domain, as computed * by landlock_log_denial(). @@ -83,6 +83,10 @@ void landlock_trace_denial( case LANDLOCK_REQUEST_FS_ACCESS: case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: if (trace_landlock_deny_access_fs_enabled()) { + const struct landlock_blockers blockers = { + .access = missing, + .type = request->type, + }; char *buf __free(__putname) = __getname(); struct path dentry_path; const char *pathname; @@ -147,16 +151,23 @@ void landlock_trace_denial( trace_landlock_deny_access_fs(youngest_denied, same_exec, logged, - missing, path, pathname); + &blockers, path, + pathname); } break; case LANDLOCK_REQUEST_NET_ACCESS: - if (trace_landlock_deny_access_net_enabled()) + if (trace_landlock_deny_access_net_enabled()) { + const struct landlock_blockers blockers = { + .access = missing, + .type = request->type, + }; + trace_landlock_deny_access_net( - youngest_denied, same_exec, logged, missing, + youngest_denied, same_exec, logged, &blockers, request->audit.u.net->sk, ntohs(request->audit.u.net->sport), ntohs(request->audit.u.net->dport)); + } break; case LANDLOCK_REQUEST_PTRACE: if (trace_landlock_deny_ptrace_enabled()) From 1a985d3890ed8caa428390a5682e957503f14060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:26 +0200 Subject: [PATCH 2074/2366] landlock: Fix rule tracepoint context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Name each event after the identity it reports. Add-rule events describe UAPI rule insertion, so rename them after LANDLOCK_RULE_PATH_BENEATH and LANDLOCK_RULE_NET_PORT. Check-rule events describe matches in internal rule trees, so rename them after LANDLOCK_KEY_INODE and LANDLOCK_KEY_NET_PORT. This remains accurate if multiple UAPI rule types share one lookup and stored rule. Keep denial event names based on filesystem and network families because they describe final access decisions. Use u64 for growable access masks passed by value to add-rule and check-rule typed BTF callbacks. CO-RE can relocate pointer-reached fields, but it cannot widen a scalar callback slot declared by a BPF program. Keep native access_mask_t for internal state and trace records. For add-rule callbacks, report the normalized per-call contribution passed to landlock_insert_rule() and expose the complete validated flags value. Put the ruleset and flags first as a common invocation prefix. This distinguishes duplicate and effective-zero additions without recovering arguments from saved syscall registers. Cc: Günther Noack Cc: Steven Rostedt Fixes: 63747c94774d ("landlock: Add landlock_add_rule_fs and landlock_add_rule_net tracepoints") Fixes: 3f1f106e4c14 ("landlock: Add tracepoints for rule checking") Link: https://patch.msgid.link/20260918185036.608651-4-mic@digikod.net Signed-off-by: Mickaël Salaün --- Documentation/trace/events-landlock.rst | 22 +++---- include/trace/events/landlock.h | 62 +++++++++++-------- security/landlock/fs.c | 10 +-- security/landlock/net.c | 5 +- tools/testing/selftests/landlock/fs_test.c | 28 ++++----- tools/testing/selftests/landlock/net_test.c | 11 ++-- tools/testing/selftests/landlock/trace.h | 56 ++++++++--------- .../selftests/landlock/trace_fs_test.c | 54 +++++++++------- tools/testing/selftests/landlock/trace_test.c | 51 ++++++++------- 9 files changed, 162 insertions(+), 137 deletions(-) diff --git a/Documentation/trace/events-landlock.rst b/Documentation/trace/events-landlock.rst index af9267cca47d..9bb81a5c676e 100644 --- a/Documentation/trace/events-landlock.rst +++ b/Documentation/trace/events-landlock.rst @@ -6,7 +6,7 @@ Landlock Trace Events ===================== :Author: Mickaël Salaün -:Date: August 2026 +:Date: September 2026 Landlock emits trace events for sandbox lifecycle operations and access denials. These events can be consumed by ftrace (for human-readable @@ -31,8 +31,8 @@ Landlock trace events are organized in four categories: **Syscall events** are emitted during Landlock system calls: - ``landlock_create_ruleset``: a new ruleset is created -- ``landlock_add_rule_fs``: a filesystem rule is added to a ruleset -- ``landlock_add_rule_net``: a network port rule is added to a ruleset +- ``landlock_add_rule_path_beneath``: a filesystem rule is added to a ruleset +- ``landlock_add_rule_net_port``: a network port rule is added to a ruleset - ``landlock_create_domain``: a new domain is created from a ruleset - ``landlock_enforce_domain``: a domain is enforced on a thread @@ -47,8 +47,8 @@ Landlock trace events are organized in four categories: **Rule evaluation events** are emitted during rule matching: -- ``landlock_check_rule_fs``: a filesystem rule is evaluated -- ``landlock_check_rule_net``: a network port rule is evaluated +- ``landlock_check_rule_inode``: an inode-keyed rule is evaluated +- ``landlock_check_rule_net_port``: a network-port-keyed rule is evaluated **Lifecycle events**: @@ -189,7 +189,7 @@ rather than the caller's, so correlate those to the syscall by domain ID. Interpreting check_rule events ============================== -The ``check_rule_fs`` and ``check_rule_net`` events expose the per-layer +The ``check_rule_inode`` and ``check_rule_net_port`` events expose the per-layer rule evaluation, which is useful for understanding *why* a specific access is allowed or denied. @@ -244,22 +244,22 @@ check. For example, a program sandboxed with read and execute access to the whole filesystem reads ``/etc/passwd``; both the ``execve()`` and the -read match the rule covering ``/`` (inode 2), so ``check_rule_fs`` fires +read match the rule covering ``/`` (inode 2), so ``check_rule_inode`` fires with the requested rights intersected against what that rule grants. The ``access_request=`` mask includes ``truncate`` because the file-open hook evaluates that optional right alongside the required access, but the rule does not grant it, so ``truncate`` never appears in ``grants=``:: - cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file} - cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file} + cat-127 [...] landlock_check_rule_inode: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file} + cat-127 [...] landlock_check_rule_inode: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file} The ``[...]`` replaces the ftrace CPU, flags, and timestamp columns. A single ``grants=`` group means the enforcing domain has one layer. With two nested sandboxes that each grant the same rights, the rule spans both layers, so ``grants=`` has one group per layer:: - cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file} - cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file} + cat-128 [...] landlock_check_rule_inode: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file} + cat-128 [...] landlock_check_rule_inode: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file} eBPF access =========== diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index 4e304cab1dab..b7e6a6638822 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -28,6 +28,8 @@ struct path; struct sock; struct task_struct; +static_assert(sizeof(access_mask_t) <= sizeof(u64)); + TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY); TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_ACCESS); TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NET_ACCESS); @@ -114,7 +116,7 @@ __trace_print_untrusted_str(struct trace_seq *p, const char *src, size_t len) * Fills the dense per-domain-layer array layers (one access mask per layer, * indexed by level - 1) from rule's sparse layer stack, keeping only the * requested rights (access_request). Layers with no matching rule entry get - * a zero mask. Shared by the check_rule_fs and check_rule_net events. + * a zero mask. Shared by the check_rule_inode and check_rule_net_port events. * * rule->layers is sorted by ascending level, with levels in the domain's * [1, num_layers] range (see landlock_merge_ruleset()), so every entry maps @@ -389,12 +391,14 @@ TRACE_EVENT(landlock_free_ruleset, ); /** - * landlock_add_rule_fs - Filesystem rule added to a ruleset + * landlock_add_rule_path_beneath - Path-beneath rule added to a ruleset * * @ruleset: Source ruleset (never NULL). - * @access_rights: Effective access mask stored in the rule, not the raw - * sys_landlock_add_rule() argument (unhandled rights - * added). + * @flags: Complete validated landlock_add_rule_flags value supplied by this + * successful call, not the rule's accumulated quiet state. + * @access_rights: Canonical per-call access mask passed to + * landlock_insert_rule() after normalization, not the raw + * sys_landlock_add_rule() argument or accumulated rule. * @path: Filesystem path for the rule (never NULL). * @pathname: Resolved absolute path string (never NULL; error placeholder * on resolution failure). @@ -403,13 +407,13 @@ TRACE_EVENT(landlock_free_ruleset, * the reported ruleset is a stable snapshot that no concurrent writer can * change. */ -TRACE_EVENT(landlock_add_rule_fs, +TRACE_EVENT(landlock_add_rule_path_beneath, - TP_PROTO(const struct landlock_ruleset *ruleset, - access_mask_t access_rights, const struct path *path, + TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags, + u64 access_rights, const struct path *path, const char *pathname), - TP_ARGS(ruleset, access_rights, path, pathname), + TP_ARGS(ruleset, flags, access_rights, path, pathname), TP_STRUCT__entry( __field( u64, ruleset_id ) @@ -443,25 +447,27 @@ TRACE_EVENT(landlock_add_rule_fs, ); /** - * landlock_add_rule_net - Network port rule added to a ruleset + * landlock_add_rule_net_port - Network-port rule added to a ruleset * * @ruleset: Source ruleset (never NULL). - * @access_rights: Effective access mask stored in the rule, not the raw - * sys_landlock_add_rule() argument (unhandled rights - * added). - * @port: Network port, the landlock_net_port_attr.port UAPI value - * forwarded directly. + * @flags: Complete validated landlock_add_rule_flags value supplied by this + * successful call, not the rule's accumulated quiet state. + * @access_rights: Canonical per-call access mask passed to + * landlock_insert_rule() after normalization, not the raw + * sys_landlock_add_rule() argument or accumulated rule. + * @port: Network port in host endianness, forwarded directly from + * &landlock_net_port_attr.port. * * Emitted by sys_landlock_add_rule() under the modified ruleset's lock, so * the reported ruleset is a stable snapshot that no concurrent writer can * change. */ -TRACE_EVENT(landlock_add_rule_net, +TRACE_EVENT(landlock_add_rule_net_port, - TP_PROTO(const struct landlock_ruleset *ruleset, - access_mask_t access_rights, u64 port), + TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags, + u64 access_rights, u64 port), - TP_ARGS(ruleset, access_rights, port), + TP_ARGS(ruleset, flags, access_rights, port), TP_STRUCT__entry( __field( u64, ruleset_id ) @@ -625,7 +631,7 @@ TRACE_EVENT(landlock_free_domain, ); /** - * landlock_check_rule_fs - Filesystem rule evaluated during access check + * landlock_check_rule_inode - Inode rule evaluated during access check * * @domain: Enforcing domain (never NULL). * @rule: Matching rule with per-layer access masks (never NULL). @@ -638,11 +644,11 @@ TRACE_EVENT(landlock_free_domain, * domain layer. See Documentation/trace/events-landlock.rst for how to * interpret it. */ -TRACE_EVENT(landlock_check_rule_fs, +TRACE_EVENT(landlock_check_rule_inode, TP_PROTO(const struct landlock_domain *domain, const struct landlock_rule *rule, - access_mask_t access_request, const struct dentry *dentry), + u64 access_request, const struct dentry *dentry), TP_ARGS(domain, rule, access_request, dentry), @@ -664,7 +670,8 @@ TRACE_EVENT(landlock_check_rule_fs, __trace_landlock_fill_layers(__get_dynamic_array(grants), __get_dynamic_array_len(grants) / sizeof(access_mask_t), - rule, access_request); + rule, + (access_mask_t)access_request); ), TP_printk("domain=%llx access_request=%s dev=%u:%u ino=%lu grants=%s", @@ -675,7 +682,7 @@ TRACE_EVENT(landlock_check_rule_fs, ); /** - * landlock_check_rule_net - Network port rule evaluated during access check + * landlock_check_rule_net_port - Network port rule evaluated * * @domain: Enforcing domain (never NULL). * @rule: Matching rule with per-layer access masks (never NULL). @@ -687,11 +694,11 @@ TRACE_EVENT(landlock_check_rule_fs, * layer. See Documentation/trace/events-landlock.rst for how to * interpret it. */ -TRACE_EVENT(landlock_check_rule_net, +TRACE_EVENT(landlock_check_rule_net_port, TP_PROTO(const struct landlock_domain *domain, const struct landlock_rule *rule, - access_mask_t access_request, u64 port), + u64 access_request, u64 port), TP_ARGS(domain, rule, access_request, port), @@ -711,7 +718,8 @@ TRACE_EVENT(landlock_check_rule_net, __trace_landlock_fill_layers(__get_dynamic_array(grants), __get_dynamic_array_len(grants) / sizeof(access_mask_t), - rule, access_request); + rule, + (access_mask_t)access_request); ), TP_printk("domain=%llx access_request=%s port=%llu grants=%s", diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 330a1871bf94..cab43892ec2f 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -356,14 +356,14 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, * held for BTF consistency (enforced by lockdep_assert_held in * TP_fast_assign). */ - if (!err && trace_landlock_add_rule_fs_enabled()) { + if (!err && trace_landlock_add_rule_path_beneath_enabled()) { char *buffer __free(__putname) = __getname(); const char *pathname = buffer ? resolve_path_for_trace(path, buffer) : ""; - trace_landlock_add_rule_fs(ruleset, access_rights, path, - pathname); + trace_landlock_add_rule_path_beneath( + ruleset, flags, access_rights, path, pathname); } mutex_unlock(&ruleset->lock); @@ -423,8 +423,8 @@ static bool unmask_layers_fs(const struct landlock_domain *const domain, ret = landlock_unmask_layers(domain, id, masks, &rule); if (rule) - trace_landlock_check_rule_fs(domain, rule, access_request, - dentry); + trace_landlock_check_rule_inode(domain, rule, access_request, + dentry); return ret; } diff --git a/security/landlock/net.c b/security/landlock/net.c index 8f2aaac54b33..5552c60388f8 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -47,7 +47,8 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset, * TP_fast_assign). */ if (!err) - trace_landlock_add_rule_net(ruleset, access_rights, port); + trace_landlock_add_rule_net_port(ruleset, flags, access_rights, + port); mutex_unlock(&ruleset->lock); return err; @@ -63,7 +64,7 @@ static bool unmask_layers_net(const struct landlock_domain *const domain, ret = landlock_unmask_layers(domain, id, masks, &rule); if (rule) - trace_landlock_check_rule_net( + trace_landlock_check_rule_net_port( domain, rule, access_request, ntohs((__force __be16)id.key.data)); return ret; diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 18dbdb99aeba..6e979cef884d 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -10493,9 +10493,9 @@ FIXTURE_TEARDOWN_PARENT(trace_layout1) } /* - * Verifies that check_rule_fs events include correct field values: domain, dev, - * ino, access_request, and grants. All values are verified against stat() of - * the rule path on a deterministic tmpfs layout. + * Verifies that check_rule_inode events include correct field values: domain, + * dev, ino, access_request, and grants. All values are verified against stat() + * of the rule path on a deterministic tmpfs layout. */ TEST_F(trace_layout1, check_rule_fs_fields) { @@ -10529,7 +10529,7 @@ TEST_F(trace_layout1, check_rule_fs_fields) EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK))) { - TH_LOG("Expected 1 check_rule_fs event\n%s", buf); + TH_LOG("Expected 1 check_rule_inode event\n%s", buf); } ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK), @@ -10570,8 +10570,8 @@ TEST_F(trace_layout1, check_rule_fs_fields) } /* - * Verifies check_rule_fs behavior with multiple rules. With rules at s1d1 and - * s1d2 (a child of s1d1), accessing s1d2 produces only 1 event because the + * Verifies check_rule_inode behavior with multiple rules. With rules at s1d1 + * and s1d2 (a child of s1d1), accessing s1d2 produces only 1 event because the * pathwalk short-circuits after the first rule fully unmasks the single layer. */ TEST_F(trace_layout1, check_rule_fs_multiple_rules) @@ -10643,14 +10643,14 @@ TEST_F(trace_layout1, check_rule_fs_multiple_rules) ASSERT_NE(NULL, buf); /* - * Only 1 check_rule_fs event: the rule on dir_s1d2 fully unmasked the - * single layer, so the pathwalk short-circuits before reaching the + * Only one check_rule_inode event: the rule on dir_s1d2 fully unmasks + * the single layer, so the pathwalk short-circuits before reaching the * dir_s1d1 rule. */ count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); EXPECT_EQ(1, count) { - TH_LOG("Expected 1 check_rule_fs event, got %d\n%s", count, + TH_LOG("Expected 1 check_rule_inode event, got %d\n%s", count, buf); } @@ -10777,7 +10777,7 @@ TEST_F(trace_layout1, check_rule_fs_optional_access) count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); EXPECT_EQ(1, count) { - TH_LOG("Expected 1 check_rule_fs event, got %d\n%s", count, + TH_LOG("Expected 1 check_rule_inode event, got %d\n%s", count, buf); } @@ -10796,7 +10796,7 @@ TEST_F(trace_layout1, check_rule_fs_optional_access) } /* - * Verifies that check_rule_fs fires for a rule that matches the inode even when + * Verifies that check_rule_inode fires for a rule matching the inode even when * it grants none of the requested rights, so the grants set is empty. Landlock * cannot know a rule ignores the request before reading it, so the event is * still emitted (grants={}), which lets a tracer see that the rule matched. @@ -10884,7 +10884,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant) count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); EXPECT_EQ(2, count) { - TH_LOG("Expected 2 check_rule_fs events, got %d\n%s", count, + TH_LOG("Expected 2 check_rule_inode events, got %d\n%s", count, buf); } @@ -10894,7 +10894,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant) tracefs_count_matches( buf, TRACE_PREFIX( - TRACE_TASK) "landlock_check_rule_fs: domain=[0-9a-f]\\+ " + TRACE_TASK) "landlock_check_rule_inode: domain=[0-9a-f]\\+ " "access_request=read_dir " "dev=[0-9]\\+:[0-9]\\+ ino=[0-9]\\+ " "grants={}$")) @@ -10908,7 +10908,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant) tracefs_count_matches( buf, TRACE_PREFIX( - TRACE_TASK) "landlock_check_rule_fs: domain=[0-9a-f]\\+ " + TRACE_TASK) "landlock_check_rule_inode: domain=[0-9a-f]\\+ " "access_request=read_dir " "dev=[0-9]\\+:[0-9]\\+ ino=[0-9]\\+ " "grants={read_dir}$")) diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index a18761e0fd82..4fb705e1596d 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -3712,7 +3712,7 @@ TEST_F(trace_net_connect, deny_access_net) free(buf); } -/* Field verification for the check_rule_net event on an allowed access. */ +/* Field verification for the check_rule_net_port event on an allowed access. */ /* clang-format off */ FIXTURE(trace_net_check_rule) { @@ -3757,10 +3757,11 @@ FIXTURE_TEARDOWN(trace_net_check_rule) /* * Verifies that an allowed bind matching a net-port rule emits exactly one - * landlock_check_rule_net event with the enforcing domain, the requested + * landlock_check_rule_net_port event with the enforcing domain, the requested * access, the checked port (host endianness), and the per-layer grants. The - * whole event is anchored to exact values so a revert of the check_rule_net - * emit (or a byte-order or field-plumbing regression) fails the test. + * whole event is anchored to exact values so removing the check_rule_net_port + * emission or introducing a byte-order or field-plumbing regression fails the + * test. */ TEST_F(trace_net_check_rule, check_rule_net_fields) { @@ -3832,7 +3833,7 @@ TEST_F(trace_net_check_rule, check_rule_net_fields) EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_CHECK_RULE_NET(TRACE_TASK))) { - TH_LOG("Expected 1 check_rule_net event\n%s", buf); + TH_LOG("Expected 1 check_rule_net_port event\n%s", buf); } ASSERT_EQ(0, diff --git a/tools/testing/selftests/landlock/trace.h b/tools/testing/selftests/landlock/trace.h index ba0c5e92001f..e6873853376c 100644 --- a/tools/testing/selftests/landlock/trace.h +++ b/tools/testing/selftests/landlock/trace.h @@ -27,14 +27,14 @@ TRACEFS_LANDLOCK_DIR "/landlock_create_domain/enable" #define TRACEFS_ENFORCE_DOMAIN_ENABLE \ TRACEFS_LANDLOCK_DIR "/landlock_enforce_domain/enable" -#define TRACEFS_ADD_RULE_FS_ENABLE \ - TRACEFS_LANDLOCK_DIR "/landlock_add_rule_fs/enable" -#define TRACEFS_ADD_RULE_NET_ENABLE \ - TRACEFS_LANDLOCK_DIR "/landlock_add_rule_net/enable" +#define TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE \ + TRACEFS_LANDLOCK_DIR "/landlock_add_rule_path_beneath/enable" +#define TRACEFS_ADD_RULE_NET_PORT_ENABLE \ + TRACEFS_LANDLOCK_DIR "/landlock_add_rule_net_port/enable" #define TRACEFS_CHECK_RULE_FS_ENABLE \ - TRACEFS_LANDLOCK_DIR "/landlock_check_rule_fs/enable" + TRACEFS_LANDLOCK_DIR "/landlock_check_rule_inode/enable" #define TRACEFS_CHECK_RULE_NET_ENABLE \ - TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net/enable" + TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net_port/enable" #define TRACEFS_DENY_ACCESS_FS_ENABLE \ TRACEFS_LANDLOCK_DIR "/landlock_deny_access_fs/enable" #define TRACEFS_DENY_ACCESS_NET_ENABLE \ @@ -79,18 +79,18 @@ */ #define KWORKER_TASK "kworker/[0-9]\\+:[0-9]\\+" -#define REGEX_ADD_RULE_FS(task) \ - TRACE_PREFIX(task) \ - "landlock_add_rule_fs: " \ - "ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \ - "access_rights=[a-z_|]* " \ - "dev=[0-9]\\+:[0-9]\\+ " \ - "ino=[0-9]\\+ " \ +#define REGEX_ADD_RULE_PATH_BENEATH(task) \ + TRACE_PREFIX(task) \ + "landlock_add_rule_path_beneath: " \ + "ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \ + "access_rights=[a-z_|]* " \ + "dev=[0-9]\\+:[0-9]\\+ " \ + "ino=[0-9]\\+ " \ "path=[^ ]\\+$" -#define REGEX_ADD_RULE_NET(task) \ +#define REGEX_ADD_RULE_NET_PORT(task) \ TRACE_PREFIX(task) \ - "landlock_add_rule_net: " \ + "landlock_add_rule_net_port: " \ "ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \ "access_rights=[a-z_|]* " \ "port=[0-9]\\+$" @@ -110,21 +110,21 @@ "parent=[0-9a-f]\\+ " \ "ruleset=[0-9a-f]\\+\\.[0-9]\\+$" -#define REGEX_CHECK_RULE_FS(task) \ - TRACE_PREFIX(task) \ - "landlock_check_rule_fs: " \ - "domain=[0-9a-f]\\+ " \ - "access_request=[a-z_|]* " \ - "dev=[0-9]\\+:[0-9]\\+ " \ - "ino=[0-9]\\+ " \ +#define REGEX_CHECK_RULE_FS(task) \ + TRACE_PREFIX(task) \ + "landlock_check_rule_inode: " \ + "domain=[0-9a-f]\\+ " \ + "access_request=[a-z_|]* " \ + "dev=[0-9]\\+:[0-9]\\+ " \ + "ino=[0-9]\\+ " \ "grants={[a-z_|,]*}$" -#define REGEX_CHECK_RULE_NET(task) \ - TRACE_PREFIX(task) \ - "landlock_check_rule_net: " \ - "domain=[0-9a-f]\\+ " \ - "access_request=[a-z_|]* " \ - "port=[0-9]\\+ " \ +#define REGEX_CHECK_RULE_NET(task) \ + TRACE_PREFIX(task) \ + "landlock_check_rule_net_port: " \ + "domain=[0-9a-f]\\+ " \ + "access_request=[a-z_|]* " \ + "port=[0-9]\\+ " \ "grants={[a-z_|,]*}$" #define REGEX_DENY_ACCESS_FS(task) \ diff --git a/tools/testing/selftests/landlock/trace_fs_test.c b/tools/testing/selftests/landlock/trace_fs_test.c index 4543a25c1f55..6666d4746cb1 100644 --- a/tools/testing/selftests/landlock/trace_fs_test.c +++ b/tools/testing/selftests/landlock/trace_fs_test.c @@ -121,7 +121,8 @@ FIXTURE_SETUP(trace_fs) } self->tracefs_ok = 1; - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true)); + ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, + true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true)); ASSERT_EQ(0, tracefs_clear()); @@ -134,7 +135,7 @@ FIXTURE_TEARDOWN(trace_fs) return; set_cap(_metadata, CAP_SYS_ADMIN); - tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false); + tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false); tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false); tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false); tracefs_fixture_teardown(); @@ -183,11 +184,11 @@ TEST_F(trace_fs, unsandboxed) } /* - * Verifies that adding a filesystem rule emits a landlock_add_rule_fs trace - * event with the expected path and field values: ruleset ID is non-zero, - * access_rights is non-zero, and path matches. + * Verifies that adding a filesystem rule emits a landlock_add_rule_path_beneath + * event with the expected path and field values: the ruleset ID and + * access_rights are non-zero, and the path matches. */ -TEST_F(trace_fs, add_rule_fs) +TEST_F(trace_fs, add_rule_path_beneath) { struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE | @@ -215,28 +216,30 @@ TEST_F(trace_fs, add_rule_fs) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + count = tracefs_count_matches(buf, + REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)); EXPECT_EQ(1, count) { - TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf); + TH_LOG("Expected 1 add_rule_path_beneath event, got %d\n%s", + count, buf); } /* Ruleset ID should be non-zero. */ - ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), - "ruleset", field_buf, - sizeof(field_buf))); + ASSERT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK), + "ruleset", field_buf, sizeof(field_buf))); EXPECT_STRNE("0", field_buf); /* Access rights should be non-zero. */ - ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), - "access_rights", field_buf, - sizeof(field_buf))); + ASSERT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK), + "access_rights", field_buf, sizeof(field_buf))); EXPECT_STRNE("", field_buf); /* Path should be /usr. */ - ASSERT_EQ(0, - tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), - "path", field_buf, sizeof(field_buf))); + ASSERT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK), + "path", field_buf, sizeof(field_buf))); EXPECT_STREQ("/usr", field_buf); free(buf); @@ -246,7 +249,7 @@ TEST_F(trace_fs, add_rule_fs) * Verifies that a path whose escaping exceeds the trace scratch sequence does * not corrupt a sibling symbolic field. */ -TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) +TEST_F(trace_fs, add_rule_path_beneath_escaped_path_overflow) { static const char access_prefix[] = "execute|write_file|read_file|"; static const char access_suffix[] = "|ioctl_dev|resolve_unix"; @@ -277,10 +280,12 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + count = tracefs_count_matches(buf, + REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)); EXPECT_EQ(1, count) { - TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf); + TH_LOG("Expected 1 add_rule_path_beneath event, got %d\n%s", + count, buf); } /* @@ -288,9 +293,9 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) * field also catches scratch-sequence poisoning when the compiler * evaluates the overflowing path first, as GCC currently does. */ - ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), - "access_rights", field_buf, - sizeof(field_buf))); + ASSERT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK), + "access_rights", field_buf, sizeof(field_buf))); EXPECT_EQ(0, strncmp(field_buf, access_prefix, sizeof(access_prefix) - 1)); EXPECT_EQ(NULL, strstr(field_buf, "|refer|")); @@ -298,7 +303,8 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) ASSERT_LE(sizeof(access_suffix) - 1, field_len); EXPECT_STREQ(access_suffix, field_buf + field_len - (sizeof(access_suffix) - 1)); - expect_truncated_path(_metadata, buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + expect_truncated_path(_metadata, buf, + REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)); free(buf); } diff --git a/tools/testing/selftests/landlock/trace_test.c b/tools/testing/selftests/landlock/trace_test.c index afdaf8511b3a..f9b293a9dd56 100644 --- a/tools/testing/selftests/landlock/trace_test.c +++ b/tools/testing/selftests/landlock/trace_test.c @@ -49,8 +49,10 @@ FIXTURE_SETUP(trace) ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true)); - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true)); - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, true)); + ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, + true)); + ASSERT_EQ(0, + tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true)); @@ -72,8 +74,8 @@ FIXTURE_TEARDOWN(trace) tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false); tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false); tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false); - tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false); - tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false); + tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false); + tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, false); tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false); tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false); tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false); @@ -103,8 +105,10 @@ TEST_F(trace, no_trace_when_disabled) ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false)); - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false)); - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false)); + ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, + false)); + ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, + false)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false)); @@ -265,10 +269,11 @@ TEST_F(trace, ruleset_version) ASSERT_NE(0, !!dot); EXPECT_STREQ("0", dot + 1); - /* Verify 2 add_rule_fs events were emitted. */ - EXPECT_EQ(2, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK))) + /* Verify two add_rule_path_beneath events were emitted. */ + EXPECT_EQ(2, tracefs_count_matches( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK))) { - TH_LOG("Expected 2 add_rule_fs events\n%s", buf); + TH_LOG("Expected 2 add_rule_path_beneath events\n%s", buf); } /* @@ -373,7 +378,7 @@ TEST_F(trace, create_domain) tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); ASSERT_LE(1, check_count) { - TH_LOG("Expected check_rule_fs events\n%s", buf); + TH_LOG("Expected check_rule_inode events\n%s", buf); } EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK), @@ -508,9 +513,11 @@ TEST_F(trace, add_rule_invalid_fd) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - EXPECT_EQ(0, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK))) + EXPECT_EQ(0, tracefs_count_matches( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK))) { - TH_LOG("No add_rule_fs event expected on invalid fd\n%s", buf); + TH_LOG("No add_rule_path_beneath event expected on invalid fd\n%s", + buf); } free(buf); @@ -902,10 +909,10 @@ TEST_F(trace, non_audit_visible_denial_counting) } /* - * Verifies that landlock_add_rule_net emits a trace event with the correct port - * and allowed access mask fields. + * Verifies that landlock_add_rule_net_port emits a trace event with the correct + * port and allowed access mask fields. */ -TEST_F(trace, add_rule_net_fields) +TEST_F(trace, add_rule_net_port_fields) { struct landlock_ruleset_attr ruleset_attr = { .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, @@ -931,9 +938,10 @@ TEST_F(trace, add_rule_net_fields) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_ADD_RULE_NET(TRACE_TASK))) + EXPECT_EQ(1, tracefs_count_matches(buf, + REGEX_ADD_RULE_NET_PORT(TRACE_TASK))) { - TH_LOG("Expected 1 add_rule_net event\n%s", buf); + TH_LOG("Expected 1 add_rule_net_port event\n%s", buf); } /* @@ -941,7 +949,8 @@ TEST_F(trace, add_rule_net_fields) * (landlock_net_port_attr.port). On little-endian, htons(8080) is * 36895, so this comparison catches byte-order bugs. */ - EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK), + EXPECT_EQ(0, tracefs_extract_field(buf, + REGEX_ADD_RULE_NET_PORT(TRACE_TASK), "port", field, sizeof(field))); EXPECT_STREQ("8080", field); /* @@ -950,9 +959,9 @@ TEST_F(trace, add_rule_net_fields) * net access bits are unhandled because the ruleset only handles * BIND_TCP). */ - EXPECT_EQ(0, - tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK), - "access_rights", field, sizeof(field))); + EXPECT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_NET_PORT(TRACE_TASK), + "access_rights", field, sizeof(field))); EXPECT_STREQ("bind_tcp|connect_tcp|bind_udp|connect_send_udp", field); free(buf); From 98b04ab00f0e738d69bd718228db55483a911b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:27 +0200 Subject: [PATCH 2075/2366] landlock: Fix network denial trace context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Network denial events report source and destination ports reconstructed from audit data. Their zero values are ambiguous, and neither identifies the complete endpoint that Landlock checked. Carry the checked sockaddr and its signed length in a private trace-only context. For an enabled event, validate the length and copy only the initialized prefix into zeroed local storage. This prevents a typed BPF program from reading uninitialized bytes while exposing the socket family, socket, address, and length. Replace the source and destination trace-record fields with one signed port derived from the checked address. A value of -1 means that no port was checked, zero is a valid port, and positive values use host endianness. Bind blockers select the bind address; connect and send blockers select the destination. Cc: Günther Noack Cc: Steven Rostedt Fixes: 01ce260f5ccf ("landlock: Add landlock_deny_access_fs and landlock_deny_access_net") Link: https://patch.msgid.link/20260918185036.608651-5-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 73 +++++++++++++-------- security/landlock/log.h | 32 +++++++-- security/landlock/net.c | 37 ++++++++--- security/landlock/trace.c | 20 +++++- tools/testing/selftests/landlock/net_test.c | 49 +++++--------- tools/testing/selftests/landlock/trace.h | 3 +- 6 files changed, 135 insertions(+), 79 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index b7e6a6638822..a982a7cfa881 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -10,7 +10,10 @@ #if !defined(_TRACE_LANDLOCK_H) || defined(TRACE_HEADER_MULTI_READ) #define _TRACE_LANDLOCK_H +#include +#include #include +#include #include #include #include @@ -790,6 +793,11 @@ TRACE_EVENT(landlock_deny_access_fs, __get_dynamic_array_len(pathname) - 1)) ); +static_assert(offsetof(struct sockaddr_in, sin_port) == + offsetof(struct sockaddr_in6, sin6_port)); +static_assert(sizeof_field(struct sockaddr_in, sin_port) == + sizeof_field(struct sockaddr_in6, sin6_port)); + /** * landlock_deny_access_net - Network access denied * @@ -798,30 +806,31 @@ TRACE_EVENT(landlock_deny_access_fs, * @same_exec: Whether the current task entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. * @blockers: Request type and final missing access subset (never NULL). - * @sk: Socket object (never NULL), read without a socket lock, so its - * fields are a best-effort snapshot. The denied endpoint is not - * available: the hook runs before :manpage:`bind(2)` / - * :manpage:`connect(2)` sets the socket addresses. - * @sport: Source port in host endianness, set for bind denials (zero for - * an autobind/ephemeral port); zero for connect and send denials. - * @dport: Destination port in host endianness, set for connect and send - * denials; zero for bind denials, and also zero for a UDP send to - * an AF_UNSPEC address on an IPv6 socket (indistinguishable from a - * real destination port 0). The bind-vs-connect direction is - * given by @blockers, not by which port is set. + * @sk: Socket object (never NULL), read without a socket lock, so its fields + * are a best-effort snapshot. + * @socket_family: Socket-family snapshot used by the verdict. + * @address: Authoritative address checked by the verdict (never NULL). + * The producer copies @addrlen bytes from the checked address and + * zeroes the remaining storage before emission. The + * &sockaddr_in.sin_port or &sockaddr_in6.sin6_port member, when + * present, remains in network endianness. + * @addrlen: Validated signed length of @address. * - * Emitted when a Landlock domain denies a network operation. - * - * The port fields are converted from the socket's network byte order to - * host endianness before emitting. + * Emitted when a Landlock domain denies a network operation. The blocker + * identifies whether the address is a bind or connect/send policy object. + * The flattened port field is converted from the checked address to host + * endianness, or is -1 when no port was checked. Zero is a valid checked + * port. */ TRACE_EVENT(landlock_deny_access_net, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, bool logged, const struct landlock_blockers *blockers, - const struct sock *sk, u64 sport, u64 dport), + const struct sock *sk, u16 socket_family, + const struct sockaddr_storage *address, int addrlen), - TP_ARGS(hierarchy, same_exec, logged, blockers, sk, sport, dport), + TP_ARGS(hierarchy, same_exec, logged, blockers, sk, socket_family, + address, addrlen), TP_STRUCT__entry( __field( u64, domain_id ) @@ -829,26 +838,36 @@ TRACE_EVENT(landlock_deny_access_net, __field( bool, logged ) __field( enum landlock_request_type, blockers_type ) __field( access_mask_t, blockers_access ) - __field( u64, sport ) - __field( u64, dport ) + __field( s64, port ) ), TP_fast_assign( + const struct sockaddr *const addr = + (const struct sockaddr *)address; + const bool has_port = + addrlen >= (int)offsetofend(struct sockaddr_in, sin_port) && + (addr->sa_family == AF_INET || + addr->sa_family == AF_INET6 || + (addr->sa_family == AF_UNSPEC && + socket_family == AF_INET)); + __entry->domain_id = hierarchy->id; __entry->same_exec = same_exec; __entry->logged = logged; __entry->blockers_type = blockers->type; __entry->blockers_access = blockers->access; - __entry->sport = sport; - __entry->dport = dport; + __entry->port = + has_port ? + ntohs(((const struct sockaddr_in *)addr)->sin_port) : + -1; ), - TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s sport=%llu dport=%llu", - __entry->domain_id, __entry->same_exec, __entry->logged, - __entry->blockers_type == LANDLOCK_REQUEST_NET_ACCESS ? - __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_NET_NAMES) : - "unknown", - __entry->sport, __entry->dport) + TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s port=%lld", + __entry->domain_id, __entry->same_exec, __entry->logged, + __entry->blockers_type == LANDLOCK_REQUEST_NET_ACCESS ? + __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_NET_NAMES) : + "unknown", + __entry->port) ); /** diff --git a/security/landlock/log.h b/security/landlock/log.h index 04f3e241e765..4587c2b1566d 100644 --- a/security/landlock/log.h +++ b/security/landlock/log.h @@ -15,6 +15,7 @@ struct landlock_cred_security; struct landlock_hierarchy; +struct sockaddr; enum landlock_request_type { LANDLOCK_REQUEST_PTRACE = 1, @@ -30,6 +31,16 @@ struct landlock_blockers { enum landlock_request_type type; }; +#ifdef CONFIG_TRACEPOINTS + +struct landlock_net_trace { + const struct sockaddr *address; + int addrlen; + u16 socket_family; +}; + +#endif /* CONFIG_TRACEPOINTS */ + /* * We should be careful to only use a variable of this type for * landlock_log_denial(). This way, the compiler can remove it entirely if @@ -57,13 +68,20 @@ struct landlock_request { deny_masks_t deny_masks; optional_access_t quiet_optional_accesses; - /* - * Other-party domain ID for a relational (scope/ptrace) denial, or 0 if - * that party is unsandboxed. An ID, not a pointer: the other task can - * replace its credential and free the domain it referenced. Trace path - * only; audit ignores it. - */ - u64 other_domain_id; + union { + /* + * Other-party domain ID for a relational (scope/ptrace) denial, + * or 0 if that party is unsandboxed. Store an ID, not a + * pointer: the other task can replace its credential and free + * the domain it referenced. Trace-only; audit ignores it. + */ + u64 other_domain_id; + +#ifdef CONFIG_TRACEPOINTS + /* Synchronous context for a network denial. */ + const struct landlock_net_trace *trace_net; +#endif /* CONFIG_TRACEPOINTS */ + }; }; #ifdef CONFIG_SECURITY_LANDLOCK_LOG diff --git a/security/landlock/net.c b/security/landlock/net.c index 5552c60388f8..6fe0dbde3b78 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -93,7 +93,7 @@ static int current_check_access_socket(struct socket *const sock, return 0; /* Checks for minimal header length to safely read sa_family. */ - if (addrlen < offsetofend(typeof(*address), sa_family)) + if (addrlen < (int)offsetofend(typeof(*address), sa_family)) return -EINVAL; /* @@ -145,6 +145,17 @@ static int current_check_access_socket(struct socket *const sock, .audit.u.net = &audit_net, .access = access_request, .layer_masks = &layer_masks, +#ifdef CONFIG_TRACEPOINTS + .trace_net = + &(struct landlock_net_trace){ + .address = + address, + .addrlen = + addrlen, + .socket_family = + sock_family, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return -EACCES; } @@ -276,14 +287,22 @@ static int current_check_access_socket(struct socket *const sock, audit_net.family = address->sa_family; audit_net.sk = sock->sk; - landlock_log_denial(subject, - &(struct landlock_request){ - .type = LANDLOCK_REQUEST_NET_ACCESS, - .audit.type = LSM_AUDIT_DATA_NET, - .audit.u.net = &audit_net, - .access = access_request, - .layer_masks = &layer_masks, - }); + landlock_log_denial( + subject, &(struct landlock_request){ + .type = LANDLOCK_REQUEST_NET_ACCESS, + .audit.type = LSM_AUDIT_DATA_NET, + .audit.u.net = &audit_net, + .access = access_request, + .layer_masks = &layer_masks, +#ifdef CONFIG_TRACEPOINTS + .trace_net = + &(struct landlock_net_trace){ + .address = address, + .addrlen = addrlen, + .socket_family = sock_family, + }, +#endif /* CONFIG_TRACEPOINTS */ + }); return -EACCES; } diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 58276cc32d3f..9be86638f905 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "access.h" @@ -157,16 +158,31 @@ void landlock_trace_denial( break; case LANDLOCK_REQUEST_NET_ACCESS: if (trace_landlock_deny_access_net_enabled()) { + const struct landlock_net_trace *const trace_net = + request->trace_net; const struct landlock_blockers blockers = { .access = missing, .type = request->type, }; + struct sockaddr_storage address = {}; + if (WARN_ON_ONCE(!trace_net || !trace_net->address)) + return; + + if (WARN_ON_ONCE( + trace_net->addrlen < + (int)offsetofend(struct sockaddr, + sa_family) || + trace_net->addrlen > (int)sizeof(address))) + return; + + memcpy(&address, trace_net->address, + trace_net->addrlen); trace_landlock_deny_access_net( youngest_denied, same_exec, logged, &blockers, request->audit.u.net->sk, - ntohs(request->audit.u.net->sport), - ntohs(request->audit.u.net->dport)); + trace_net->socket_family, &address, + trace_net->addrlen); } break; case LANDLOCK_REQUEST_PTRACE: diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 4fb705e1596d..28942438e154 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -3482,8 +3482,8 @@ TEST_F(trace_net, deny_access_net_bind) /* * Anchors the denial fields shared by every deny_access_net event so a field - * test proves more than sport/dport: the denying domain, the same-exec bit, the - * audit-logging verdict, and the blocked access all stay populated. + * test proves more than the checked endpoint: the denying domain, the same-exec + * bit, the audit-logging verdict, and the blocked access all stay populated. */ static void expect_net_deny_common_fields(struct __test_metadata *const _metadata, @@ -3569,38 +3569,38 @@ FIXTURE_VARIANT(trace_net_connect) { bool deny_connect; }; +/* Denied connect() to the next IPv4 port. */ /* clang-format off */ - -/* Denied connect(): sport=0, dport=. */ FIXTURE_VARIANT_ADD(trace_net_connect, connect_denied) { + /* clang-format on */ .handled = LANDLOCK_ACCESS_NET_CONNECT_TCP, .bind_base_first = false, .deny_connect = true, }; -/* Denied bind(): sport=, dport=0. */ +/* Denied bind() to the next IPv4 port. */ +/* clang-format off */ FIXTURE_VARIANT_ADD(trace_net_connect, bind_fields) { + /* clang-format on */ .handled = LANDLOCK_ACCESS_NET_BIND_TCP, .bind_base_first = false, .deny_connect = false, }; -/* Denied connect() after an allowed bind(): the connect fields (sport=0). */ +/* Denied connect() after an allowed bind() uses the checked destination. */ +/* clang-format off */ FIXTURE_VARIANT_ADD(trace_net_connect, connect_after_bind) { - .handled = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP, + /* clang-format on */ + .handled = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, .bind_base_first = true, .deny_connect = true, }; -/* clang-format on */ - /* - * A denied TCP bind(2) or connect(2) emits one deny_access_net event. The port - * is reported in the field matching the denied operation, in host endianness - * (the UAPI landlock_net_port_attr.port convention): a connect denial reports - * sport=0 dport=, a bind denial reports sport= dport=0, so a - * byte-order or field-swap bug is caught. A prior allowed bind - * (connect_after_bind) does not change the connect denial's fields. + * A denied TCP bind(2) or connect(2) emits one deny_access_net event with the + * checked IPv4 port in host endianness (the UAPI landlock_net_port_attr.port + * convention). A prior allowed bind does not change a connect denial's port. */ TEST_F(trace_net_connect, deny_access_net) { @@ -3693,21 +3693,13 @@ TEST_F(trace_net_connect, deny_access_net) expect_net_deny_common_fields(_metadata, buf); - /* - * The denied operation's port field carries the port; the other is 0. - */ snprintf(expected, sizeof(expected), "%llu", (unsigned long long)(sock_port_start + 1)); ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK), - "sport", field, sizeof(field))); - EXPECT_STREQ(variant->deny_connect ? "0" : expected, field); - - ASSERT_EQ(0, - tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK), - "dport", field, sizeof(field))); - EXPECT_STREQ(variant->deny_connect ? expected : "0", field); + "port", field, sizeof(field))); + EXPECT_STREQ(expected, field); free(buf); } @@ -3867,11 +3859,4 @@ TEST_F(trace_net_check_rule, check_rule_net_fields) free(buf); } -/* - * IPv6 network trace tests are intentionally elided. IPv6 hook dispatch uses - * the same current_check_access_socket() code path as IPv4, validated by the - * audit tests in this file. The trace events use the same blockers/sport/dport - * fields regardless of address family. - */ - TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/landlock/trace.h b/tools/testing/selftests/landlock/trace.h index e6873853376c..2ec863362173 100644 --- a/tools/testing/selftests/landlock/trace.h +++ b/tools/testing/selftests/landlock/trace.h @@ -145,8 +145,7 @@ "same_exec=[01] " \ "logged=[01] " \ "blockers=[a-z_|]* " \ - "sport=[0-9]\\+ " \ - "dport=[0-9]\\+$" + "port=-\\?[0-9]\\+$" #define REGEX_DENY_PTRACE(task) \ TRACE_PREFIX(task) \ From 7ad69ac63315506e2091bf46d5c96c49f6ce439e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:28 +0200 Subject: [PATCH 2076/2366] landlock: Report the actual ptrace tracer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ptrace denial callback identifies only the tracee. Current is the tracer during hook_ptrace_access_check(), but it is the tracee during PTRACE_TRACEME, where the parent is the actual tracer. A consumer therefore cannot infer both parties from the existing arguments. Append the actual tracer task to the typed-BPF callback: current for hook_ptrace_access_check() and parent for hook_ptrace_traceme(). Carry it with the tracee domain ID in a private ptrace context. Both hooks keep the selected tasks alive through synchronous dispatch, so no extra task reference is needed. Keep the tracefs record unchanged. The new context is available only to typed BPF, while same_exec continues to describe the tracer that owns the denying policy. Cc: Günther Noack Cc: Steven Rostedt Fixes: bb91730f16c0 ("landlock: Add tracepoints for ptrace and scope denials") Link: https://patch.msgid.link/20260918185036.608651-6-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 11 ++++++++--- security/landlock/log.h | 16 ++++++++++++---- security/landlock/task.c | 28 +++++++++++++++++----------- security/landlock/trace.c | 20 ++++++++++++++------ 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index a982a7cfa881..8c6ebf958d66 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -295,6 +295,8 @@ static inline const char *__trace_landlock_print_layers( * the two parties without kernel-internal state. The ID is a scalar * snapshot, not a live domain pointer that could dangle: an optional * relational referent is a scalar (0 sentinel), not a nullable pointer. + * For ptrace, same_exec instead describes the tracer, even for + * PTRACE_TRACEME, and may differ from the current task. * * Blocker fields * ~~~~~~~~~~~~~~ @@ -875,12 +877,14 @@ TRACE_EVENT(landlock_deny_access_net, * * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the * domain field. - * @same_exec: Whether the current task entered the denying domain itself. + * @same_exec: Whether the tracer entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. * @tracee_domain_id: The tracee's Landlock domain ID, or 0 if the tracee * is unsandboxed. * @tracee: The target task ptrace acted on (never NULL). tracee_pid is * the init-namespace TGID (like audit's opid). + * @tracer: The tracer or proposed tracer (never NULL); for PTRACE_TRACEME + * this is the parent, not the syscall caller. * * Emitted when a Landlock domain denies a ptrace operation. */ @@ -888,9 +892,10 @@ TRACE_EVENT(landlock_deny_ptrace, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, bool logged, u64 tracee_domain_id, - const struct task_struct *tracee), + const struct task_struct *tracee, + const struct task_struct *tracer), - TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee), + TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee, tracer), TP_STRUCT__entry( __field( u64, domain_id ) diff --git a/security/landlock/log.h b/security/landlock/log.h index 4587c2b1566d..821df6f711c6 100644 --- a/security/landlock/log.h +++ b/security/landlock/log.h @@ -16,6 +16,7 @@ struct landlock_cred_security; struct landlock_hierarchy; struct sockaddr; +struct task_struct; enum landlock_request_type { LANDLOCK_REQUEST_PTRACE = 1, @@ -39,6 +40,11 @@ struct landlock_net_trace { u16 socket_family; }; +struct landlock_ptrace_trace { + u64 tracee_domain_id; + const struct task_struct *tracer; +}; + #endif /* CONFIG_TRACEPOINTS */ /* @@ -70,16 +76,18 @@ struct landlock_request { union { /* - * Other-party domain ID for a relational (scope/ptrace) denial, - * or 0 if that party is unsandboxed. Store an ID, not a - * pointer: the other task can replace its credential and free - * the domain it referenced. Trace-only; audit ignores it. + * Other-party domain ID for a scope denial, or 0 if that party + * is unsandboxed. Store an ID, not a pointer: the other task + * can replace its credential and free the domain it referenced. + * Audit ignores this trace-only field. */ u64 other_domain_id; #ifdef CONFIG_TRACEPOINTS /* Synchronous context for a network denial. */ const struct landlock_net_trace *trace_net; + /* Consumed only by the synchronous trace dispatcher. */ + const struct landlock_ptrace_trace *trace_ptrace; #endif /* CONFIG_TRACEPOINTS */ }; }; diff --git a/security/landlock/task.c b/security/landlock/task.c index 4491ce31ae04..445f6b921a87 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -88,7 +88,9 @@ static int hook_ptrace_access_check(struct task_struct *const child, const unsigned int mode) { const struct landlock_cred_security *parent_subject; +#ifdef CONFIG_TRACEPOINTS u64 tracee_domain_id = 0; +#endif /* CONFIG_TRACEPOINTS */ int err; /* Quick return for non-landlocked tasks. */ @@ -100,10 +102,10 @@ static int hook_ptrace_access_check(struct task_struct *const child, const struct landlock_domain *const child_dom = landlock_get_task_domain(child); err = domain_ptrace(parent_subject->domain, child_dom); -#ifdef CONFIG_SECURITY_LANDLOCK_LOG +#ifdef CONFIG_TRACEPOINTS if (child_dom) tracee_domain_id = child_dom->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#endif /* CONFIG_TRACEPOINTS */ } if (!err) @@ -121,7 +123,12 @@ static int hook_ptrace_access_check(struct task_struct *const child, .u.tsk = child, }, .layer_plus_one = parent_subject->domain->num_layers, - .other_domain_id = tracee_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_ptrace = &(struct landlock_ptrace_trace) { + .tracee_domain_id = tracee_domain_id, + .tracer = current, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return err; @@ -142,7 +149,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent) { const struct landlock_cred_security *parent_subject; const struct landlock_domain *child_dom; - u64 tracee_domain_id = 0; int err; child_dom = landlock_get_current_domain(); @@ -154,12 +160,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent) if (!err) return 0; -#ifdef CONFIG_SECURITY_LANDLOCK_LOG - /* The tracee is the current task; its domain is stable here. */ - if (child_dom) - tracee_domain_id = child_dom->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ - /* * For the ptrace_traceme case, we log the domain which is the cause of * the denial, which means the parent domain instead of the current @@ -174,7 +174,13 @@ static int hook_ptrace_traceme(struct task_struct *const parent) .u.tsk = current, }, .layer_plus_one = parent_subject->domain->num_layers, - .other_domain_id = tracee_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_ptrace = &(struct landlock_ptrace_trace) { + /* The current task's domain is stable here. */ + .tracee_domain_id = child_dom ? child_dom->hierarchy->id : 0, + .tracer = parent, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return err; } diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 9be86638f905..43091c052f77 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -63,7 +63,7 @@ void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy * @request: Detail of the user space request. * @youngest_denied: The youngest hierarchy node that denied the access. * @missing: The final missing access subset, when applicable. - * @same_exec: Whether the current task is the same executable that called + * @same_exec: Whether the policy subject is the same executable that called * landlock_restrict_self() for the denying domain, as computed * by landlock_log_denial(). * @logged: Whether the domain's policy selects this denial for logging, as @@ -186,11 +186,19 @@ void landlock_trace_denial( } break; case LANDLOCK_REQUEST_PTRACE: - if (trace_landlock_deny_ptrace_enabled()) - trace_landlock_deny_ptrace(youngest_denied, same_exec, - logged, - request->other_domain_id, - request->audit.u.tsk); + if (trace_landlock_deny_ptrace_enabled()) { + const struct landlock_ptrace_trace *const trace_ptrace = + request->trace_ptrace; + + if (WARN_ON_ONCE(!trace_ptrace || + !trace_ptrace->tracer)) + return; + + trace_landlock_deny_ptrace( + youngest_denied, same_exec, logged, + trace_ptrace->tracee_domain_id, + request->audit.u.tsk, trace_ptrace->tracer); + } break; case LANDLOCK_REQUEST_SCOPE_SIGNAL: if (trace_landlock_deny_scope_signal_enabled()) From 0889db596a25ecde210e66fa0db70bc6a6d91f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:29 +0200 Subject: [PATCH 2077/2366] landlock: Report the effective signal number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The signal-scope denial callback identifies its target but not the effective signal. This loses permission-probe signal zero and makes the file-owner hook's zero sentinel ambiguous. Append an int signal argument to the typed-BPF callback. Preserve sig, including zero, in hook_task_kill(). In hook_file_send_sigiotask(), translate signum zero to SIGIO at the producer, where its meaning is known. Carry the effective signal and target domain ID in a private, stack-backed context consumed synchronously. This requires no allocation or task reference in the interrupt-capable file-owner path. Gate this context and the remaining scope-only domain IDs with CONFIG_TRACEPOINTS. Keep the tracefs record and audit output unchanged. Cc: Günther Noack Cc: Steven Rostedt Fixes: bb91730f16c0 ("landlock: Add tracepoints for ptrace and scope denials") Link: https://patch.msgid.link/20260918185036.608651-7-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 8 +++-- security/landlock/log.h | 21 ++++++++---- security/landlock/task.c | 59 +++++++++++++++++++++++---------- security/landlock/trace.c | 12 +++++-- 4 files changed, 71 insertions(+), 29 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index 8c6ebf958d66..5da0f12ed2c3 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -928,12 +928,14 @@ TRACE_EVENT(landlock_deny_ptrace, * * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the * domain field. - * @same_exec: Whether the current task entered the denying domain itself. + * @same_exec: Whether the policy subject entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. * @target_domain_id: The target's Landlock domain ID, or 0 if the target * is unsandboxed. * @target: The task the signal was aimed at (never NULL). target_pid is * the init-namespace TGID (like audit's opid). + * @signal: The signal selected by the denied check. Zero is a permission + * probe, not an absent value. * * Emitted when a Landlock domain denies signal delivery to a scoped-out * target. @@ -942,9 +944,9 @@ TRACE_EVENT(landlock_deny_scope_signal, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, bool logged, u64 target_domain_id, - const struct task_struct *target), + const struct task_struct *target, int signal), - TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target), + TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target, signal), TP_STRUCT__entry( __field( u64, domain_id ) diff --git a/security/landlock/log.h b/security/landlock/log.h index 821df6f711c6..faa30e26e42a 100644 --- a/security/landlock/log.h +++ b/security/landlock/log.h @@ -45,6 +45,11 @@ struct landlock_ptrace_trace { const struct task_struct *tracer; }; +struct landlock_signal_trace { + u64 target_domain_id; + int signal; +}; + #endif /* CONFIG_TRACEPOINTS */ /* @@ -74,22 +79,24 @@ struct landlock_request { deny_masks_t deny_masks; optional_access_t quiet_optional_accesses; +#ifdef CONFIG_TRACEPOINTS union { /* - * Other-party domain ID for a scope denial, or 0 if that party - * is unsandboxed. Store an ID, not a pointer: the other task - * can replace its credential and free the domain it referenced. - * Audit ignores this trace-only field. + * Other-party domain ID for an abstract UNIX socket scope + * denial, or 0 if that party is unsandboxed. Store an ID, not + * a pointer: the other task can replace its credential and free + * the domain it referenced. */ u64 other_domain_id; -#ifdef CONFIG_TRACEPOINTS /* Synchronous context for a network denial. */ const struct landlock_net_trace *trace_net; - /* Consumed only by the synchronous trace dispatcher. */ + /* Synchronous context for a ptrace denial. */ const struct landlock_ptrace_trace *trace_ptrace; -#endif /* CONFIG_TRACEPOINTS */ + /* Synchronous context for a signal denial. */ + const struct landlock_signal_trace *trace_signal; }; +#endif /* CONFIG_TRACEPOINTS */ }; #ifdef CONFIG_SECURITY_LANDLOCK_LOG diff --git a/security/landlock/task.c b/security/landlock/task.c index 445f6b921a87..d9eae86fc552 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -256,8 +256,7 @@ static bool domain_is_scoped(const struct landlock_domain *const client, } static bool sock_is_scoped(struct sock *const other, - const struct landlock_domain *const domain, - u64 *const peer_domain_id) + const struct landlock_domain *const domain) { const struct landlock_domain *dom_other; @@ -275,13 +274,23 @@ static bool sock_is_scoped(struct sock *const other, return false; dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain; -#ifdef CONFIG_SECURITY_LANDLOCK_LOG - *peer_domain_id = dom_other ? dom_other->hierarchy->id : 0; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ return domain_is_scoped(domain, dom_other, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); } +#ifdef CONFIG_TRACEPOINTS + +static u64 get_socket_domain_id(const struct sock *const other) +{ + const struct landlock_domain *domain; + + lockdep_assert_held(&unix_sk(other)->lock); + domain = landlock_cred(other->sk_socket->file->f_cred)->domain; + return domain ? domain->hierarchy->id : 0; +} + +#endif /* CONFIG_TRACEPOINTS */ + static bool is_abstract_socket(struct sock *const sock) { struct unix_address *addr = unix_sk(sock)->addr; @@ -305,7 +314,6 @@ static int hook_unix_stream_connect(struct sock *const sock, struct sock *const newsk) { size_t handle_layer; - u64 peer_domain_id = 0; const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), unix_scope, &handle_layer); @@ -317,7 +325,7 @@ static int hook_unix_stream_connect(struct sock *const sock, if (!is_abstract_socket(other)) return 0; - if (!sock_is_scoped(other, subject->domain, &peer_domain_id)) + if (!sock_is_scoped(other, subject->domain)) return 0; landlock_log_denial(subject, &(struct landlock_request) { @@ -329,7 +337,9 @@ static int hook_unix_stream_connect(struct sock *const sock, }, }, .layer_plus_one = handle_layer + 1, - .other_domain_id = peer_domain_id, +#ifdef CONFIG_TRACEPOINTS + .other_domain_id = get_socket_domain_id(other), +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } @@ -338,7 +348,6 @@ static int hook_unix_may_send(struct socket *const sock, struct socket *const other) { size_t handle_layer; - u64 peer_domain_id = 0; const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), unix_scope, &handle_layer); @@ -356,7 +365,7 @@ static int hook_unix_may_send(struct socket *const sock, if (!is_abstract_socket(other->sk)) return 0; - if (!sock_is_scoped(other->sk, subject->domain, &peer_domain_id)) + if (!sock_is_scoped(other->sk, subject->domain)) return 0; landlock_log_denial(subject, &(struct landlock_request) { @@ -368,7 +377,9 @@ static int hook_unix_may_send(struct socket *const sock, }, }, .layer_plus_one = handle_layer + 1, - .other_domain_id = peer_domain_id, +#ifdef CONFIG_TRACEPOINTS + .other_domain_id = get_socket_domain_id(other->sk), +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } @@ -383,7 +394,9 @@ static int hook_task_kill(struct task_struct *const p, { bool is_scoped; size_t handle_layer; +#ifdef CONFIG_TRACEPOINTS u64 target_domain_id = 0; +#endif /* CONFIG_TRACEPOINTS */ const struct landlock_cred_security *subject; if (!cred) { @@ -415,10 +428,10 @@ static int hook_task_kill(struct task_struct *const p, is_scoped = domain_is_scoped(subject->domain, other, signal_scope.scope); -#ifdef CONFIG_SECURITY_LANDLOCK_LOG +#ifdef CONFIG_TRACEPOINTS if (other) target_domain_id = other->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#endif /* CONFIG_TRACEPOINTS */ } if (!is_scoped) @@ -431,7 +444,12 @@ static int hook_task_kill(struct task_struct *const p, .u.tsk = p, }, .layer_plus_one = handle_layer + 1, - .other_domain_id = target_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_signal = &(struct landlock_signal_trace) { + .target_domain_id = target_domain_id, + .signal = sig, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } @@ -441,7 +459,9 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, { const struct landlock_cred_security *subject; bool is_scoped = false; +#ifdef CONFIG_TRACEPOINTS u64 target_domain_id = 0; +#endif /* CONFIG_TRACEPOINTS */ /* Lock already held by send_sigio() and send_sigurg(). */ lockdep_assert_held(&fown->lock); @@ -474,10 +494,10 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, is_scoped = domain_is_scoped(subject->domain, other, signal_scope.scope); -#ifdef CONFIG_SECURITY_LANDLOCK_LOG +#ifdef CONFIG_TRACEPOINTS if (other) target_domain_id = other->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#endif /* CONFIG_TRACEPOINTS */ } if (!is_scoped) @@ -492,7 +512,12 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, #ifdef CONFIG_SECURITY_LANDLOCK_LOG .layer_plus_one = landlock_file(fown->file)->fown_layer + 1, #endif /* CONFIG_SECURITY_LANDLOCK_LOG */ - .other_domain_id = target_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_signal = &(struct landlock_signal_trace) { + .target_domain_id = target_domain_id, + .signal = signum ? signum : SIGIO, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 43091c052f77..225dbf37bab0 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -201,10 +201,18 @@ void landlock_trace_denial( } break; case LANDLOCK_REQUEST_SCOPE_SIGNAL: - if (trace_landlock_deny_scope_signal_enabled()) + if (trace_landlock_deny_scope_signal_enabled()) { + const struct landlock_signal_trace *const trace_signal = + request->trace_signal; + + if (WARN_ON_ONCE(!trace_signal)) + return; + trace_landlock_deny_scope_signal( youngest_denied, same_exec, logged, - request->other_domain_id, request->audit.u.tsk); + trace_signal->target_domain_id, + request->audit.u.tsk, trace_signal->signal); + } break; case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: if (trace_landlock_deny_scope_abstract_unix_socket_enabled()) From c6dea91d846f192eb6ddbd44f5fd873035b8b285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:30 +0200 Subject: [PATCH 2078/2366] selftests/landlock: Test filesystem denial blockers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filesystem denial traces identify the policy change needed to allow a request, so require exact blocker values rather than merely nonempty output. Pin a READ_DIR denial to exactly one event with blockers=read_dir. Pin a REFER-only mount denial to EPERM and exactly one event with blockers=change_topology. The mount child retains CAP_SYS_ADMIN so Landlock is the only expected source of EPERM. This prevents a later capability failure from masking a Landlock regression; the trace-collecting parent remains unsandboxed. Cc: Günther Noack Cc: Steven Rostedt Link: https://patch.msgid.link/20260918185036.608651-8-mic@digikod.net Signed-off-by: Mickaël Salaün --- .../selftests/landlock/trace_fs_test.c | 76 ++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/landlock/trace_fs_test.c b/tools/testing/selftests/landlock/trace_fs_test.c index 6666d4746cb1..64014ade3a0e 100644 --- a/tools/testing/selftests/landlock/trace_fs_test.c +++ b/tools/testing/selftests/landlock/trace_fs_test.c @@ -548,7 +548,8 @@ TEST_F(trace_fs, check_rule_nested) */ TEST_F(trace_fs, deny_access_fs_denied) { - char *buf; + const char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK); + char *buf, blockers[64]; int count; ASSERT_EQ(0, tracefs_clear_buf()); @@ -564,8 +565,77 @@ TEST_F(trace_fs, deny_access_fs_denied) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - count = tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)); - EXPECT_LE(1, count); + count = tracefs_count_matches(buf, event_regex); + EXPECT_EQ(1, count) + { + TH_LOG("Expected 1 access denial, got %d\n%s", count, buf); + } + ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "blockers", + blockers, sizeof(blockers))); + EXPECT_STREQ("read_dir", blockers); + + free(buf); +} + +/* + * Verifies that a denied mount reports the singleton topology blocker rather + * than an empty access mask. + */ +TEST_F(trace_fs, deny_change_topology) +{ + const char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK); + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = LANDLOCK_ACCESS_FS_REFER, + }; + char *buf, blockers[64]; + int count, ruleset_fd, status; + pid_t pid; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + ASSERT_EQ(0, tracefs_clear_buf()); + + /* Ensure that Landlock is the only expected mount denial. */ + set_cap(_metadata, CAP_SYS_ADMIN); + pid = fork(); + ASSERT_LE(0, pid); + if (pid == 0) { + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + close(ruleset_fd); + _exit(1); + } + if (landlock_restrict_self(ruleset_fd, 0)) { + close(ruleset_fd); + _exit(2); + } + close(ruleset_fd); + + if (mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL) != -1) + _exit(3); + + if (errno != EPERM) + _exit(4); + + _exit(0); + } + close(ruleset_fd); + clear_cap(_metadata, CAP_SYS_ADMIN); + + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + buf = tracefs_read_buf(); + ASSERT_NE(NULL, buf); + count = tracefs_count_matches(buf, event_regex); + EXPECT_EQ(1, count) + { + TH_LOG("Expected 1 topology denial, got %d\n%s", count, buf); + } + ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "blockers", + blockers, sizeof(blockers))); + EXPECT_STREQ("change_topology", blockers); free(buf); } From c8dcb17205a689e96e2b8e46f22575a39b5b6320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:31 +0200 Subject: [PATCH 2079/2366] selftests/landlock: Test network denial context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Network denial events now report the port from their one authoritative validated address through one signed field. Verify this value directly without inferring a source or destination role. Exercise a nonzero IPv4 TCP bind, an explicit-zero IPv4 UDP bind, a synthetic-zero IPv6 UDP autobind, and a family-only AF_UNSPEC UDP send. Require exactly one event with the exact policy blocker for each shape. The value -1 distinguishes a family-only address with no validated port from the two valid port-zero cases. Test coverage for security/landlock is 91.6% of 2625 lines according to LLVM 22. Cc: Günther Noack Cc: Steven Rostedt Link: https://patch.msgid.link/20260918185036.608651-9-mic@digikod.net [mic: Add test coverage] Signed-off-by: Mickaël Salaün --- tools/testing/selftests/landlock/net_test.c | 257 +++++++++++--------- 1 file changed, 146 insertions(+), 111 deletions(-) diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 28942438e154..16afbfdf06bb 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -3481,13 +3481,14 @@ TEST_F(trace_net, deny_access_net_bind) } /* - * Anchors the denial fields shared by every deny_access_net event so a field - * test proves more than the checked endpoint: the denying domain, the same-exec - * bit, the audit-logging verdict, and the blocked access all stay populated. + * Anchors the denial fields shared by every deny_access_net event so a port + * test also proves the denying domain, execution status, logging verdict, and + * exact blocked access. */ static void expect_net_deny_common_fields(struct __test_metadata *const _metadata, - const char *const buf) + const char *const buf, + const char *const expected_blockers) { char field[64]; @@ -3511,18 +3512,21 @@ expect_net_deny_common_fields(struct __test_metadata *const _metadata, ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK), "blockers", field, sizeof(field))); - EXPECT_STRNE("", field); + EXPECT_STREQ(expected_blockers, field); } -/* Connect and field-check tests use a separate fixture without variants. */ +enum trace_net_operation { + TRACE_NET_BIND, + TRACE_NET_SEND, +}; /* clang-format off */ -FIXTURE(trace_net_connect) { +FIXTURE(trace_net_address) { /* clang-format on */ int tracefs_ok; }; -FIXTURE_SETUP(trace_net_connect) +FIXTURE_SETUP(trace_net_address) { int ret; @@ -3547,7 +3551,7 @@ FIXTURE_SETUP(trace_net_connect) clear_cap(_metadata, CAP_SYS_ADMIN); } -FIXTURE_TEARDOWN(trace_net_connect) +FIXTURE_TEARDOWN(trace_net_address) { if (!self->tracefs_ok) return; @@ -3559,146 +3563,177 @@ FIXTURE_TEARDOWN(trace_net_connect) } /* clang-format off */ -FIXTURE_VARIANT(trace_net_connect) { +FIXTURE_VARIANT(trace_net_address) { /* clang-format on */ - /* handled_access_net, also the access allowed on the base port. */ - __u64 handled; - /* Bind the allowed base port before the denied operation. */ - bool bind_base_first; - /* Denied operation on the next port: connect (true) or bind (false). */ - bool deny_connect; + int socket_family; + int socket_type; + enum trace_net_operation operation; + int address_family; + socklen_t addrlen; + __u64 handled_access; + const char *expected_blockers; + bool address_port_zero; + bool expected_address_port; + int expected_port; }; -/* Denied connect() to the next IPv4 port. */ /* clang-format off */ -FIXTURE_VARIANT_ADD(trace_net_connect, connect_denied) { +FIXTURE_VARIANT_ADD(trace_net_address, ipv4_tcp_bind) { /* clang-format on */ - .handled = LANDLOCK_ACCESS_NET_CONNECT_TCP, - .bind_base_first = false, - .deny_connect = true, + .socket_family = AF_INET, + .socket_type = SOCK_STREAM, + .operation = TRACE_NET_BIND, + .address_family = AF_INET, + .addrlen = sizeof(struct sockaddr_in), + .handled_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .expected_blockers = "bind_tcp", + .expected_address_port = true, }; -/* Denied bind() to the next IPv4 port. */ +/* Explicit bind(0) has a checked zero port. */ /* clang-format off */ -FIXTURE_VARIANT_ADD(trace_net_connect, bind_fields) { +FIXTURE_VARIANT_ADD(trace_net_address, ipv4_udp_bind_zero) { /* clang-format on */ - .handled = LANDLOCK_ACCESS_NET_BIND_TCP, - .bind_base_first = false, - .deny_connect = false, + .socket_family = AF_INET, + .socket_type = SOCK_DGRAM, + .operation = TRACE_NET_BIND, + .address_family = AF_INET, + .addrlen = sizeof(struct sockaddr_in), + .handled_access = LANDLOCK_ACCESS_NET_BIND_UDP, + .expected_blockers = "bind_udp", + .address_port_zero = true, + .expected_port = 0, }; -/* Denied connect() after an allowed bind() uses the checked destination. */ +/* A UDP send can deny its synthetic unspecified bind endpoint. */ /* clang-format off */ -FIXTURE_VARIANT_ADD(trace_net_connect, connect_after_bind) { +FIXTURE_VARIANT_ADD(trace_net_address, ipv6_udp_autobind) { /* clang-format on */ - .handled = LANDLOCK_ACCESS_NET_BIND_TCP | - LANDLOCK_ACCESS_NET_CONNECT_TCP, - .bind_base_first = true, - .deny_connect = true, + .socket_family = AF_INET6, + .socket_type = SOCK_DGRAM, + .operation = TRACE_NET_SEND, + .address_family = AF_INET6, + .addrlen = sizeof(struct sockaddr_in6), + .handled_access = LANDLOCK_ACCESS_NET_BIND_UDP, + .expected_blockers = "bind_udp", + .expected_port = 0, }; -/* - * A denied TCP bind(2) or connect(2) emits one deny_access_net event with the - * checked IPv4 port in host endianness (the UAPI landlock_net_port_attr.port - * convention). A prior allowed bind does not change a connect denial's port. - */ -TEST_F(trace_net_connect, deny_access_net) +/* A family-only address has no checked port. */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(trace_net_address, ipv6_unspec_udp_send_min) { + /* clang-format on */ + .socket_family = AF_INET6, + .socket_type = SOCK_DGRAM, + .operation = TRACE_NET_SEND, + .address_family = AF_UNSPEC, + .addrlen = sizeof(sa_family_t), + .handled_access = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP, + .expected_blockers = "connect_send_udp", + .expected_port = -1, +}; + +static void set_trace_net_address(struct sockaddr_storage *const storage, + const int socket_family, + const int address_family, + const unsigned short port) { - pid_t child; - int status; - char *buf; + memset(storage, 0, sizeof(*storage)); + + if (socket_family == AF_INET) { + struct sockaddr_in *const addr4 = (struct sockaddr_in *)storage; + + addr4->sin_family = address_family; + addr4->sin_port = htons(port); + addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } else { + struct sockaddr_in6 *const addr6 = + (struct sockaddr_in6 *)storage; + + addr6->sin6_family = address_family; + addr6->sin6_port = htons(port); + addr6->sin6_addr = in6addr_loopback; + } +} + +/* Verifies the actionable signed port for representative checked shapes. */ +TEST_F(trace_net_address, deny_access_net) +{ + const char *const event_regex = REGEX_DENY_ACCESS_NET(TRACE_TASK); + const unsigned short address_port = + variant->address_port_zero ? 0 : sock_port_start + 1; + const int expected_port = variant->expected_address_port ? + address_port : + variant->expected_port; + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = variant->handled_access, + }; + struct sockaddr_storage address; char field[64], expected[16]; + char *buf; + int count, ret, ruleset_fd, socket_fd, status; + pid_t child; if (!self->tracefs_ok) SKIP(return, "tracefs not available"); + set_trace_net_address(&address, variant->socket_family, + variant->address_family, address_port); + socket_fd = socket(variant->socket_family, + variant->socket_type | SOCK_CLOEXEC, 0); + ASSERT_LE(0, socket_fd); + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + ASSERT_EQ(0, tracefs_clear_buf()); + child = fork(); ASSERT_LE(0, child); - if (child == 0) { - struct landlock_ruleset_attr ruleset_attr = { - .handled_access_net = variant->handled, - }; - struct landlock_net_port_attr port_attr = { - .allowed_access = variant->handled, - .port = sock_port_start, - }; - struct sockaddr_in addr = { - .sin_family = AF_INET, - .sin_addr.s_addr = htonl(INADDR_LOOPBACK), - }; - int ruleset_fd, sock_fd, optval = 1, ret; - - ruleset_fd = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); - if (ruleset_fd < 0) + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) _exit(1); - if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, - &port_attr, 0)) { - close(ruleset_fd); - _exit(1); - } - prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); - if (landlock_restrict_self(ruleset_fd, 0)) { - close(ruleset_fd); - _exit(1); - } + if (landlock_restrict_self(ruleset_fd, 0)) + _exit(2); close(ruleset_fd); - sock_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); - if (sock_fd < 0) - _exit(1); - - /* Bind the allowed base port first (succeeds, no event). */ - if (variant->bind_base_first) { - setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, - sizeof(optval)); - addr.sin_port = htons(sock_port_start); - if (bind(sock_fd, (struct sockaddr *)&addr, - sizeof(addr))) { - close(sock_fd); - _exit(1); - } - } - - /* Denied operation on the next port. */ - addr.sin_port = htons(sock_port_start + 1); - if (variant->deny_connect) - ret = connect(sock_fd, (struct sockaddr *)&addr, - sizeof(addr)); - else - ret = bind(sock_fd, (struct sockaddr *)&addr, - sizeof(addr)); - if (ret == 0) { - close(sock_fd); - _exit(2); - } - if (errno != EACCES) { - close(sock_fd); + switch (variant->operation) { + case TRACE_NET_BIND: + ret = bind(socket_fd, (const struct sockaddr *)&address, + variant->addrlen); + break; + case TRACE_NET_SEND: + ret = sendto(socket_fd, "A", 1, MSG_NOSIGNAL, + (const struct sockaddr *)&address, + variant->addrlen); + break; + default: _exit(3); } - close(sock_fd); + if (ret >= 0 || errno != EACCES) + _exit(4); + close(socket_fd); + _exit(0); } + close(ruleset_fd); + close(socket_fd); ASSERT_EQ(child, waitpid(child, &status, 0)); ASSERT_TRUE(WIFEXITED(status)); - EXPECT_EQ(0, WEXITSTATUS(status)); + ASSERT_EQ(0, WEXITSTATUS(status)); buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); + count = tracefs_count_matches(buf, event_regex); + if (count != 1) + TH_LOG("Expected 1 denial event, got %d\n%s", count, buf); + ASSERT_EQ(1, count); + expect_net_deny_common_fields(_metadata, buf, + variant->expected_blockers); - EXPECT_EQ(1, tracefs_count_matches(buf, - REGEX_DENY_ACCESS_NET(TRACE_TASK))); - - expect_net_deny_common_fields(_metadata, buf); - - snprintf(expected, sizeof(expected), "%llu", - (unsigned long long)(sock_port_start + 1)); - - ASSERT_EQ(0, - tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK), - "port", field, sizeof(field))); + ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "port", field, + sizeof(field))); + snprintf(expected, sizeof(expected), "%d", expected_port); EXPECT_STREQ(expected, field); free(buf); From 3fa5aa398edf94653926ad5e68b89f69490481b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Fri, 18 Sep 2026 20:50:32 +0200 Subject: [PATCH 2080/2366] landlock: Fix tracepoint contract documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tracepoint documentation claims that denial and lifecycle events expose every input needed to reproduce a verdict. Instead document how denial, ruleset, and domain events identify the denying policy, checked operation and object, and reason for denial. Direct consumers to generic tracepoints for additional operational context. State the reconstruction limits: IDs are boot-local, rule checks have no request ID, and exported records may be lost or cross-CPU reordered. Also replace the incorrect BPF_RAW_TRACEPOINT guidance with libbpf SEC("tp_btf/...") attachment and refer consumers to the event prototypes for callback argument layouts. Cc: Günther Noack Cc: Steven Rostedt Link: https://patch.msgid.link/20260918185036.608651-10-mic@digikod.net Signed-off-by: Mickaël Salaün --- Documentation/trace/events-landlock.rst | 30 ++++++++++++--------- include/trace/events/landlock.h | 36 ++++++++++++------------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/Documentation/trace/events-landlock.rst b/Documentation/trace/events-landlock.rst index 9bb81a5c676e..304a8d06273e 100644 --- a/Documentation/trace/events-landlock.rst +++ b/Documentation/trace/events-landlock.rst @@ -141,10 +141,10 @@ in some field formats: treated as untrusted input and escaped in the trace text output so it cannot inject field separators or control characters. -- **Other party's domain**: A scope or ptrace denial compares the - subject's denying domain (``domain=``, always the enforcing domain and - never the current task) with the other party's domain, so these - tracepoints also report the other party's domain as a scalar ID: +- **Other party's domain**: A scope or ptrace denial compares the subject's + denying domain (``domain=``), which is the enforcing domain and not + necessarily the current task's domain, with the other party's domain. + These tracepoints also report the other party's domain as a scalar ID: ``tracee_domain=`` (ptrace), ``target_domain=`` (signal), and ``peer_domain=`` (abstract unix socket). It is ``0`` when the other party is unsandboxed, and otherwise a domain ID that a consumer resolves @@ -231,9 +231,10 @@ contribution, not the final decision: through another matching rule, or the right is denied and appears in the ``blockers=`` field of the corresponding ``deny_access`` event. -To reconstruct the decision for an object, aggregate the ``grants=`` -groups of all ``check_rule`` events emitted for that object during the -check. +For an access check that a consumer can delimit, aggregate the ``grants=`` +groups of all matching ``check_rule`` events. These events do not carry a +request ID; use their execution context and generic tracepoints to separate +concurrent or successive checks of the same object. .. note:: @@ -264,9 +265,11 @@ layers, so ``grants=`` has one group per layer:: eBPF access =========== -eBPF programs attached via ``BPF_RAW_TRACEPOINT`` can access the -tracepoint arguments directly through BTF. The arguments include both -standard kernel objects and Landlock-internal objects: +BTF-enabled raw tracepoint programs attached through libbpf +``SEC("tp_btf/...")`` sections receive typed callback arguments. The +event prototypes in `Event reference`_ document their argument layouts. +The arguments include both standard kernel objects and Landlock-internal +objects: - Standard kernel objects (``struct task_struct``, ``struct sock``, ``struct path``, ``struct dentry``) can be used with existing BPF @@ -277,7 +280,7 @@ standard kernel objects and Landlock-internal objects: Internal struct layouts may change between kernel versions; use CO-RE for field relocation. -A stateful eBPF program observes the full event stream and maintains +A stateful eBPF program attached before sandbox construction can maintain per-domain state in BPF maps: 1. On ``landlock_create_domain``: record the domain ID and parent (the @@ -293,7 +296,10 @@ per-domain state in BPF maps: final statistics. This approach requires no kernel modification and no Landlock-specific -BPF helpers. The Landlock IDs serve as correlation keys across events. +BPF helpers. Landlock IDs serve as correlation keys within one boot. +Records exported through tracing or BPF buffers can be lost, and records +from different CPUs are not globally ordered, so consumers must detect and +reconcile incomplete state. Audit filtering equivalence =========================== diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index 5da0f12ed2c3..523ba5ea9870 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -206,22 +206,22 @@ static inline const char *__trace_landlock_print_layers( * Decision context * ~~~~~~~~~~~~~~~~ * - * A denial event, together with the lifecycle events, exposes the full - * set of inputs the verdict consumed, so a consumer that tracked domain - * creation (landlock_create_ruleset, landlock_create_domain) can verify - * or reproduce the Landlock decision rather than merely observe it - * happened. In who/what/why terms: who is the denying domain (the domain - * field, always the subject that enforced the policy, never the current - * task), what is the operation and its object, and why is every other - * input the verdict weighed. + * A denial event identifies the domain whose policy denied the request, the + * Landlock operation and policy object that were checked, and the blocker or + * domain relationship responsible for the denial. When tracing starts with + * sandbox construction, ruleset and domain events provide the policy history + * needed to interpret these identifiers. The denying domain is the subject + * that enforced the policy, not necessarily current. Generic tracepoints can + * provide additional operational context. * * Lifecycle consistency * ~~~~~~~~~~~~~~~~~~~~~~ * - * Lifecycle events are balanced: a creation event always has a matching - * deallocation event and vice versa, so an eBPF program can model object - * lifetimes from the trace stream without reconciliation logic. A creation - * event fires while the object is still private to the calling thread + * Lifecycle emission is balanced: a creation event always has a matching + * deallocation event and vice versa. A consumer that observes an object's + * complete lifetime can model it from this pair; one that attaches late or + * loses exported records must reconcile incomplete state. A creation event + * fires while the object is still private to the calling thread * (landlock_create_ruleset fires before the ruleset's file descriptor is * installed, so it cannot race a concurrent :manpage:`close(2)`); if fd * installation later fails and the ruleset is freed, free_ruleset still @@ -295,7 +295,8 @@ static inline const char *__trace_landlock_print_layers( * the two parties without kernel-internal state. The ID is a scalar * snapshot, not a live domain pointer that could dangle: an optional * relational referent is a scalar (0 sentinel), not a nullable pointer. - * For ptrace, same_exec instead describes the tracer, even for + * Nonzero IDs are unique within one boot. For ptrace, same_exec instead + * describes the tracer, even for * PTRACE_TRACEME, and may differ from the current task. * * Blocker fields @@ -1031,11 +1032,10 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket, __entry->logged = logged; __entry->peer_domain_id = peer_domain_id; /* - * Best-effort (0 for a datagram peer). sk_peer_pid is - * canonically guarded by sk->sk_peer_lock, but the target - * peer's peercred is set once and not updated concurrently in - * these hooks, so this READ_ONCE() is safe; sun_path is the - * reliable identifier. + * Best-effort (0 for a datagram peer). The caller holds the + * peer's AF_UNIX state lock, serializing published peercred + * updates. The peer socket keeps a reference to sk_peer_pid + * through pid_nr(); sun_path is the reliable identifier. */ peer_pid = READ_ONCE(peer->sk_peer_pid); __entry->peer_pid = peer_pid ? pid_nr(peer_pid) : 0; From b74aad23d99b279bb34d135795f39a6d8ecdc075 Mon Sep 17 00:00:00 2001 From: Peiyang He Date: Tue, 8 Sep 2026 20:13:23 +0800 Subject: [PATCH 2081/2366] drm/virtio: fix memory leak of fence event on execbuffer failure virtio_gpu_execbuffer_ioctl() reserves a DRM event with drm_event_reserve_init() when VIRTGPU_EXECBUF_RING_IDX selects a ring that userspace has enabled polling for. virtio_gpu_init_submit() does this before the BO handles, the command buffer, the syncobj arrays and the in-fence are processed, so every later error path runs with the event already pending, including plain argument validation failures such as an invalid bo_handle or an in-syncobj that carries no fence. On those paths, virtio_gpu_cleanup_submit() drops the out-fence without cancelling the event. The fence is freed without ever having been emitted, taking the only driver-side pointer to the event with it. Closing the DRM file does not help. drm_events_release() unlinks pending events but deliberately leaves the freeing to the driver's later drm_send_event(), which never runs for an orphaned event, so the allocation is leaked permanently. Found when fuzzing the virtio driver with Syzkaller: BUG: memory leak unreferenced object 0xffff88802c176e80 (size 96): comm "syz.1.367", pid 10561, jiffies 4294960122 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ c8 6e 17 2c 80 88 ff ff 00 00 00 00 00 00 00 00 .n.,............ backtrace (crc e1973c6b): kmemleak_alloc_recursive include/linux/kmemleak.h:44 [inline] slab_post_alloc_hook mm/slub.c:4597 [inline] slab_alloc_node mm/slub.c:4917 [inline] __kmalloc_cache_noprof+0x49d/0x6f0 mm/slub.c:5485 _kmalloc_noprof include/linux/slab.h:988 [inline] _kzalloc_noprof include/linux/slab.h:1309 [inline] virtio_gpu_fence_event_create drivers/gpu/drm/virtio/virtgpu_submit.c:282 [inline] virtio_gpu_init_submit drivers/gpu/drm/virtio/virtgpu_submit.c:398 [inline] virtio_gpu_execbuffer_ioctl+0xbbf/0x1aa0 drivers/gpu/drm/virtio/virtgpu_submit.c:505 drm_ioctl_kernel+0x1f4/0x3e0 drivers/gpu/drm/drm_ioctl.c:817 drm_ioctl+0x5f4/0xc70 drivers/gpu/drm/drm_ioctl.c:914 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x116/0x800 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fix by cancelling and freeing the DRM event on the execbuffer error path before dropping the fence. Clear the fence's event pointer after cancellation so it does not retain a dangling pointer. Fixes: cd7f5ca33585 ("drm/virtio: implement context init: add virtio_gpu_fence_event") Cc: stable@vger.kernel.org Signed-off-by: Peiyang He Assisted-by: Codex:gpt-5.6-luna Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/D320EAB5680C1411+20260908121323.2405044-1-peiyang_he@smail.nju.edu.cn --- drivers/gpu/drm/virtio/virtgpu_submit.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_submit.c b/drivers/gpu/drm/virtio/virtgpu_submit.c index 32cb1e4aa425..734b1e976a75 100644 --- a/drivers/gpu/drm/virtio/virtgpu_submit.c +++ b/drivers/gpu/drm/virtio/virtgpu_submit.c @@ -538,6 +538,10 @@ int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data, virtio_gpu_process_post_deps(&submit); virtio_gpu_complete_submit(&submit); cleanup: + if (ret && submit.out_fence && submit.out_fence->e) { + drm_event_cancel_free(dev, &submit.out_fence->e->base); + submit.out_fence->e = NULL; + } virtio_gpu_cleanup_submit(&submit); return ret; From 36570ef2244cc4d7563b1f0157bc0f032498638c Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Tue, 15 Sep 2026 15:39:10 +0800 Subject: [PATCH 2082/2366] drm/virtio: fix object leak when drm_gem_handle_create() fails virtio_gpu_gem_create() owns the reference taken by virtio_gpu_object_create(). On the drm_gem_handle_create() error path it calls drm_gem_object_release() instead of dropping that reference. drm_gem_object_release() is the inverse of drm_gem_object_init() and does not touch the reference count or call obj->funcs->free(), so it is only correct as the last step of a destructor, as in virtio_gpu_cleanup_object(). Using it here leaves the bo at refcount 1 with no remaining reference, so virtio_gpu_free_object() never runs and the shmem pages, sg table and virtio_gpu_object are leaked. Since virtio_gpu_object_create() has already set bo->created, VIRTIO_GPU_CMD_RESOURCE_UNREF is not queued either, leaking the host-side resource and the resource id. drm_gem_handle_create_tail() drops the handle reference on all of its internal error paths, so the caller only has to drop its own. Use drm_gem_object_put(), matching the success path below. Fixes: dc5698e80cf7 ("Add virtio gpu driver.") Reported-by: Yuhao Jiang Assisted-by: Claude:claude-opus-5 Signed-off-by: Junrui Luo Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260915-fixes-v2-1-a0d799e4db66@outlook.com --- drivers/gpu/drm/virtio/virtgpu_gem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_gem.c b/drivers/gpu/drm/virtio/virtgpu_gem.c index 66c3f6f74e9c..d2f0b8a3f172 100644 --- a/drivers/gpu/drm/virtio/virtgpu_gem.c +++ b/drivers/gpu/drm/virtio/virtgpu_gem.c @@ -45,7 +45,7 @@ static int virtio_gpu_gem_create(struct drm_file *file, ret = drm_gem_handle_create(file, &obj->base.base, &handle); if (ret) { - drm_gem_object_release(&obj->base.base); + drm_gem_object_put(&obj->base.base); return ret; } From 477bc3068fc3777b9d8ffd79e265b0dfdf2d3a6b Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Tue, 15 Sep 2026 15:39:11 +0800 Subject: [PATCH 2083/2366] drm/virtio: fix object leak in virtio_gpu_resource_create_ioctl() virtio_gpu_resource_create_ioctl() calls drm_gem_object_release() on the drm_gem_handle_create() error path instead of dropping the reference it owns, so obj->funcs->free() never runs and the virtio_gpu_object, its pages and sg table, the resource id and the host-side resource are leaked. Use drm_gem_object_put() instead. Fixes: 62fb7a5e1096 ("virtio-gpu: add 3d/virgl support") Assisted-by: Claude:claude-opus-5 Signed-off-by: Junrui Luo Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260915-fixes-v2-2-a0d799e4db66@outlook.com --- drivers/gpu/drm/virtio/virtgpu_ioctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_ioctl.c b/drivers/gpu/drm/virtio/virtgpu_ioctl.c index 3d8e4ccdb7c1..d16f07abb266 100644 --- a/drivers/gpu/drm/virtio/virtgpu_ioctl.c +++ b/drivers/gpu/drm/virtio/virtgpu_ioctl.c @@ -185,7 +185,7 @@ static int virtio_gpu_resource_create_ioctl(struct drm_device *dev, void *data, ret = drm_gem_handle_create(file, obj, &handle); if (ret) { - drm_gem_object_release(obj); + drm_gem_object_put(obj); return ret; } From 24b6d5c7641412c9ebef0d4c8b888d49a0e6b880 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Tue, 15 Sep 2026 15:39:12 +0800 Subject: [PATCH 2084/2366] drm/virtio: fix object leaks in virtio_gpu_resource_create_blob_ioctl() virtio_gpu_resource_create_blob_ioctl() calls drm_gem_object_release() on both the virtio_gpu_resource_assign_uuid() and drm_gem_handle_create() error paths instead of dropping the reference it owns, so obj->funcs->free() never runs and the virtio_gpu_object, the resource id and the host-side resource are leaked. Use drm_gem_object_put() instead. Fixes: 897b4d1acaf5 ("drm/virtio: implement blob resources: resource create blob ioctl") Assisted-by: Claude:claude-opus-5 Signed-off-by: Junrui Luo Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260915-fixes-v2-3-a0d799e4db66@outlook.com --- drivers/gpu/drm/virtio/virtgpu_ioctl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_ioctl.c b/drivers/gpu/drm/virtio/virtgpu_ioctl.c index d16f07abb266..fcdb07a37972 100644 --- a/drivers/gpu/drm/virtio/virtgpu_ioctl.c +++ b/drivers/gpu/drm/virtio/virtgpu_ioctl.c @@ -557,14 +557,14 @@ static int virtio_gpu_resource_create_blob_ioctl(struct drm_device *dev, if (params.blob_flags & VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE) { ret = virtio_gpu_resource_assign_uuid(vgdev, bo); if (ret) { - drm_gem_object_release(obj); + drm_gem_object_put(obj); return ret; } } ret = drm_gem_handle_create(file, obj, &handle); if (ret) { - drm_gem_object_release(obj); + drm_gem_object_put(obj); return ret; } From 036d28db1818af2f9d80db771f5405da84d7732d Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Tue, 15 Sep 2026 15:39:13 +0800 Subject: [PATCH 2085/2366] drm/virtio: release the GEM object on virtio_gpu_vram_create() errors virtio_gpu_vram_create() frees the object with a bare kfree(vram) on both error paths after drm_gem_private_object_init() has run, and on the second one after drm_gem_create_mmap_offset() has linked obj->vma_node into the device's VMA offset manager. The freed object stays in that interval tree, so a later lookup or insertion walks freed memory, and the dma_resv and gpuva lock are never destroyed. Call drm_gem_object_release() before kfree() on both paths. Fixes: 16845c5d5409 ("drm/virtio: implement blob resources: implement vram object") Assisted-by: Claude:claude-opus-5 Signed-off-by: Junrui Luo Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260915-fixes-v2-4-a0d799e4db66@outlook.com --- drivers/gpu/drm/virtio/virtgpu_vram.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_vram.c b/drivers/gpu/drm/virtio/virtgpu_vram.c index 5b4a3ab81cd5..01241ce4d07c 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vram.c +++ b/drivers/gpu/drm/virtio/virtgpu_vram.c @@ -215,16 +215,12 @@ int virtio_gpu_vram_create(struct virtio_gpu_device *vgdev, /* Create fake offset */ ret = drm_gem_create_mmap_offset(obj); - if (ret) { - kfree(vram); - return ret; - } + if (ret) + goto err_release_obj; ret = virtio_gpu_resource_id_get(vgdev, &vram->base.hw_res_handle); - if (ret) { - kfree(vram); - return ret; - } + if (ret) + goto err_release_obj; virtio_gpu_cmd_resource_create_blob(vgdev, &vram->base, params, NULL, 0); @@ -240,6 +236,11 @@ int virtio_gpu_vram_create(struct virtio_gpu_device *vgdev, *bo_ptr = &vram->base; return 0; + +err_release_obj: + drm_gem_object_release(obj); + kfree(vram); + return ret; } void virtio_gpu_vram_map_deferred(struct virtio_gpu_object_vram *vram) From 1e3b08de63274d0b009e99ef51cd6a9c0c6bf08c Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Fri, 11 Sep 2026 17:42:03 +0300 Subject: [PATCH 2086/2366] Revert "drm/virtio: Allow importing prime buffers when 3D is enabled" Guest userspace may import udmabuf to vrend. Vrend doesn't support guest blobs, and thus, further 3d operations with the imported blob are failing. Typical scenario of the problem shown with mouse cursor RGBA image imported into virtio-gpu, which previously was rejected by virtio-gpu driver. Revert enabling guest blobs importing into vrend to fix the regression. Link: https://gitlab.freedesktop.org/virgl/virglrenderer/-/work_items/674 Fixes: df4dc947c46b ("drm/virtio: Allow importing prime buffers when 3D is enabled") Signed-off-by: Dmitry Osipenko Reviewed-by: Val Packett Link: https://patch.msgid.link/20260911144204.2089401-1-dmitry.osipenko@collabora.com --- drivers/gpu/drm/virtio/virtgpu_prime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_prime.c b/drivers/gpu/drm/virtio/virtgpu_prime.c index 149e6bcb5878..ebf471044d06 100644 --- a/drivers/gpu/drm/virtio/virtgpu_prime.c +++ b/drivers/gpu/drm/virtio/virtgpu_prime.c @@ -349,7 +349,7 @@ struct drm_gem_object *virtgpu_gem_prime_import(struct drm_device *dev, } } - if (!vgdev->has_resource_blob) + if (!vgdev->has_resource_blob || vgdev->has_virgl_3d) return drm_gem_prime_import(dev, buf); bo = kzalloc_obj(*bo); From 846b3c64fe3e77d9db20a7e3e62dbbb637c773e1 Mon Sep 17 00:00:00 2001 From: Peiyang He Date: Wed, 9 Sep 2026 17:11:14 +0800 Subject: [PATCH 2087/2366] drm/virtio: fix NULL pointer dereference on fence allocation failure virtio_gpu_fence_alloc() can fail due to memory pressure and return NULL, but its caller like virtio_gpu_init_submit() never checks it. Later, virtio_gpu_init_submit() passes the NULL fence to virtio_gpu_fence_event_create(), which unconditionally dereferences it. Found when fuzzing the virtio driver with Syzkaller: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000012: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000090-0x0000000000000097] CPU: 1 UID: 0 PID: 9991 Comm: syz.0.121 Not tainted 7.2.0 #4 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org 04/01/2014 RIP: 0010:virtio_gpu_fence_event_create drivers/gpu/drm/virtio/virtgpu_submit.c:295 [inline] RIP: 0010:virtio_gpu_init_submit drivers/gpu/drm/virtio/virtgpu_submit.c:398 [inline] RIP: 0010:virtio_gpu_execbuffer_ioctl+0xc78/0x1aa0 drivers/gpu/drm/virtio/virtgpu_submit.c:505 Code: 85 ed 0f 85 21 09 00 00 e8 05 5a c9 fb 48 8b 44 24 10 48 8d b8 90 00 00 00 48 b8 00 00 00 00 00 fc ff df 48 89 fa 48 c1 ea 03 <80> 3c 02 00 0f 85 9a 0d 00 00 48 8b 44 24 10 4c 89 b0 90 00 00 00 RSP: 0018:ffffc900039dfad0 EFLAGS: 00010216 RAX: dffffc0000000000 RBX: ffffc900039dfdd8 RCX: ffffffff85f6fd3d RDX: 0000000000000012 RSI: ffffffff85f6fd4b RDI: 0000000000000090 RBP: 0000000000000000 R0virtio_gpu_virgl_process_cmd: ctrl 0x102, error 0x1203 R10: 0000000000000000 R11: 0000000000000000 R12: ffff8880132c4000 R13: 0000000000000000 R14: ffff888073b6c700 R15: 000000000000003b FS: 00007fab480b96c0(0000) GS:ffff8880eb6e9000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007effbf5e55a8 CR3: 0000000048d19000 CR4: 0000000000350ef0 Call Trace: drm_ioctl_kernel+0x1f4/0x3e0 drivers/gpu/drm/drm_ioctl.c:817 drm_ioctl+0x5f4/0xc70 drivers/gpu/drm/drm_ioctl.c:914 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x116/0x800 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7fab471a82bd Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b0 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007fab480b9018 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 00007fab47435fa0 RCX: 00007fab471a82bd RDX: 00002000000000c0 RSI: 00000000c0406442 RDI: 0000000000000003 RBP: 00007fab480b9080 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001 R13: 00007fab47436038 R14: 00007fab47435fa0 R15: 00007ffe85ab0740 Modules linked in: ---[ end trace 0000000000000000 ]--- RIP: 0010:virtio_gpu_fence_event_create drivers/gpu/drm/virtio/virtgpu_submit.c:295 [inline] RIP: 0010:virtio_gpu_init_submit drivers/gpu/drm/virtio/virtgpu_submit.c:398 [inline] RIP: 0010:virtio_gpu_execbuffer_ioctl+0xc78/0x1aa0 drivers/gpu/drm/virtio/virtgpu_submit.c:505 Code: 85 ed 0f 85 21 09 00 00 e8 05 5a c9 fb 48 8b 44 24 10 48 8d b8 90 00 00 00 48 b8 00 00 00 00 00 fc ff df 48 89 fa 48 c1 ea 03 <80> 3c 02 00 0f 85 9a 0d 00 00 48 8b 44 24 10 4c 89 b0 90 00 00 00 RSP: 0018:ffffc900039dfad0 EFLAGS: 00010216 RAX: dffffc0000000000 RBX: ffffc900039dfdd8 RCX: ffffffff85f6fd3d RDX: 0000000000000012 RSI: ffffffff85f6fd4b RDI: 0000000000000090 RBP: 0000000000000000 R08: 0000000000000005 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000000 R12: ffff8880132c4000 R13: 0000000000000000 R14: ffff888073b6c700 R15: 000000000000003b FS: 00007fab480b96c0(0000) GS:ffff888098ae9000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f24c3759000 CR3: 0000000048d19000 CR4: 0000000000350ef0 ---------------- Code disassembly (best guess): 0: 85 ed test %ebp,%ebp 2: 0f 85 21 09 00 00 jne 0x929 8: e8 05 5a c9 fb call 0xfbc95a12 d: 48 8b 44 24 10 mov 0x10(%rsp),%rax 12: 48 8d b8 90 00 00 00 lea 0x90(%rax),%rdi 19: 48 b8 00 00 00 00 00 movabs $0xdffffc0000000000,%rax 20: fc ff df 23: 48 89 fa mov %rdi,%rdx 26: 48 c1 ea 03 shr $0x3,%rdx * 2a: 80 3c 02 00 cmpb $0x0,(%rdx,%rax,1) <-- trapping instruction 2e: 0f 85 9a 0d 00 00 jne 0xdce 34: 48 8b 44 24 10 mov 0x10(%rsp),%rax 39: 4c 89 b0 90 00 00 00 mov %r14,0x90(%rax) Fix by checking virtio_gpu_fence_alloc() in virtio_gpu_init_submit() and returning -ENOMEM before any later code can dereference the NULL fence. Fixes: 70d1ace56db6 ("drm/virtio: Conditionally allocate virtio_gpu_fence") Cc: stable@vger.kernel.org Signed-off-by: Peiyang He Assisted-by: Codex:gpt-5.5 Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/00EFE4BA92889B14+20260909091114.2622550-1-peiyang_he@smail.nju.edu.cn --- drivers/gpu/drm/virtio/virtgpu_submit.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_submit.c b/drivers/gpu/drm/virtio/virtgpu_submit.c index 734b1e976a75..3d35326dd904 100644 --- a/drivers/gpu/drm/virtio/virtgpu_submit.c +++ b/drivers/gpu/drm/virtio/virtgpu_submit.c @@ -389,10 +389,13 @@ static int virtio_gpu_init_submit(struct virtio_gpu_submit *submit, if ((exbuf->flags & VIRTGPU_EXECBUF_FENCE_FD_OUT) || exbuf->num_out_syncobjs || exbuf->num_bo_handles || - drm_fence_event) + drm_fence_event) { out_fence = virtio_gpu_fence_alloc(vgdev, fence_ctx, ring_idx); - else + if (!out_fence) + return -ENOMEM; + } else { out_fence = NULL; + } if (drm_fence_event) { err = virtio_gpu_fence_event_create(dev, file, out_fence, ring_idx); From 6947b78df4d25bd1e86b26870b614ea54c625b1e Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Fri, 28 Aug 2026 17:01:23 +0800 Subject: [PATCH 2088/2366] drm/virtio: Add pixel blend mode property to cursor plane The cursor plane exposes a format with an alpha channel (DRM_FORMAT_ARGB8888) without a pixel blend mode property. Since commit 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") this triggers a warning during drm_mode_config_validate(): [ 0.649020] ------------[ cut here ]------------ [ 0.649040] [PLANE:36:plane-1] pixel format with alpha exposed but blend mode not setup [ 0.649081] WARNING: drivers/gpu/drm/drm_mode_config.c:872 at drm_mode_config_validate ...... [ 0.649761] Call trace: [ 0.649764] drm_mode_config_validate+0x398/0x558 [drm] (P) [ 0.649912] drm_dev_register+0x1cc/0x2a0 [drm] [ 0.650058] virtio_gpu_probe+0xd4/0x1c0 [virtio_gpu] [ 0.650088] virtio_dev_probe+0x1c8/0x310 ...... [ 0.650261] ---[ end trace 0000000000000000 ]--- Create the property with the only supported blend mode, DRM_MODE_BLEND_PREMULTI, which is also the property's default and matches what userspace had to assume before the property existed. Fixes: 860e748bddcc ("drm: ensure blend mode supported if pixel format with alpha exposed") Reported-by: Ye Liu Signed-off-by: Shixiong Ou Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260828090123.784944-1-oushixiong1025@163.com --- drivers/gpu/drm/virtio/virtgpu_plane.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_plane.c b/drivers/gpu/drm/virtio/virtgpu_plane.c index 640815af4098..b422eba42a5f 100644 --- a/drivers/gpu/drm/virtio/virtgpu_plane.c +++ b/drivers/gpu/drm/virtio/virtgpu_plane.c @@ -589,6 +589,7 @@ struct drm_plane *virtio_gpu_plane_init(struct virtio_gpu_device *vgdev, struct drm_plane *plane; const uint32_t *formats; int nformats; + int ret; if (type == DRM_PLANE_TYPE_CURSOR) { formats = virtio_gpu_cursor_formats; @@ -614,5 +615,17 @@ struct drm_plane *virtio_gpu_plane_init(struct virtio_gpu_device *vgdev, drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_PREMULTI)); + if (type == DRM_PLANE_TYPE_CURSOR) { + /* + * The cursor plane exposes a format with an alpha channel, + * which requires a blend mode property. The host blends + * premultiplied alpha, matching the property's default. + */ + ret = drm_plane_create_blend_mode_property(plane, + BIT(DRM_MODE_BLEND_PREMULTI)); + if (ret) + return ERR_PTR(ret); + } + return plane; } From 598c1c3e895590f845e04455d5580ea28ffde666 Mon Sep 17 00:00:00 2001 From: Benjamin Leggett Date: Fri, 14 Aug 2026 18:20:11 -0400 Subject: [PATCH 2089/2366] drm/virtio: sync shmem backing on guest-bound transfers virtio_gpu_cmd_transfer_to_host_{2d,3d}() sync the shmem backing for the device before the transfer, but nothing syncs for the CPU when a transfer runs the other way. That breaks two ways. Where the DMA layer bounces, the device writes into the bounce buffer while the guest keeps reading the original pages. Where DMA is not coherent, the device writes memory while the CPU keeps stale cache lines, because nothing reaches arch_sync_dma_for_cpu(). Either way DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST hands back stale data. Sashiko originally found this in https://lore.kernel.org/dri-devel/20260806231002.27B4D1F000E9@smtp.kernel.org but the suggestion there to fix this with dma_sync_sgtable_for_cpu() isn't a sufficient fix, for two reasons. - The transfer is asynchronous. virtio_gpu_cmd_transfer_from_host_3d() only queues the command, so a sync there would run before the device had written anything. It belongs on completion, and ahead of any fence signalling. A waiter woken by the fence would otherwise race the sync and read the backing pages regardless. It needs its own pass over the reclaim list rather than a step inside the existing one, because virtio_gpu_fence_event_process() also signals every earlier fence in the same context, so any entry in that loop may signal an earlier entry's fence. - The transfer is also partial, carrying an offset, a level and a box. Where the mapping bounces, a sync for the CPU copies the whole mapping back, so unless the mapping is primed first the regions the device did not write come back holding whatever the bounce buffer contained, discarding data the guest owned. So the fix: Prime the mapping before queueing, tag the vbuffer, and sync for the CPU on completion before the fence is signalled. A second transfer must not snapshot the mapping while an earlier one is still in flight, or the snapshot would predate whatever the CPU wrote once the earlier fence signalled and the later sync would discard it. To mitigate this, wait for outstanding fences under the reservation before priming. Neither sync copies anything unless the mapping genuinely bounces: swiotlb_sync_single_for_cpu() and its Xen counterpart look the address up in the bounce pool and return early when it is absent. On a platform with non-coherent DMA they still perform the necessary cache maintenance. The range cannot be narrowed to the box, since for a non-blob resource virtio_gpu_transfer_from_host_ioctl() rejects a caller-supplied stride and layer_stride, leaving the layout to the host and the guest with no way to work out which bytes the device writes. A host3d guest blob does carry both, so its extent could be bounded, but the sync is left whole there too rather than special-cased: priming makes the untouched regions round-trip unchanged either way. Behaviour changes worth noting: - TRANSFER_FROM_HOST can now block, where before it returned as soon as the command was queued. Repeated readbacks of one resource serialise, and a readback can wait behind an earlier queued command that touched it, since virtio_gpu_array_add_fence() tags uploads, execbufs and plane flushes alike with DMA_RESV_USAGE_WRITE. -ERESTARTSYS was already possible here via dma_resv_lock_interruptible(). - A CPU write racing an in-flight transfer to the same resource is now lost, where before it survived and the transfer was lost instead. Priming captures the pages as of queueing, so a write landing before completion is overwritten by the sync. - TRANSFER_TO_HOST can also block now, but only while a guest-bound transfer on the same resource is outstanding, which happens only for callers that issue both without waiting. - Where a batch of completions contains a guest-bound transfer, the sync pass delays fence signalling for the whole batch. Only bounced pages are copied and the swiotlb pool bounds it. A batch with no such transfer is unaffected. Tested under QEMU on x86 with swiotlb=force and virtio-vga-gl iommu_platform=on, which forces both preconditions required to hit the original bug. Fixes: a3b815f09bb8 ("drm/virtio: add iommu support.") Reported-by: Sashiko AI review Closes: https://lore.kernel.org/dri-devel/20260806231002.27B4D1F000E9@smtp.kernel.org/ Signed-off-by: Benjamin Leggett Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260814-virtgpu-from-host-sync-v4-1-64dd736b1779@edera.io --- drivers/gpu/drm/virtio/virtgpu_drv.h | 5 +++ drivers/gpu/drm/virtio/virtgpu_ioctl.c | 43 ++++++++++++++++++++++++ drivers/gpu/drm/virtio/virtgpu_vq.c | 46 ++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index 9df4c7117341..f3bbbe4468bf 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -114,6 +114,8 @@ struct virtio_gpu_object { bool dumb; bool created; bool attached; + /* a guest-bound transfer is queued and its mapping not yet synced */ + bool from_host_pending; bool host3d_blob, guest_blob; uint32_t blob_mem, blob_flags; @@ -196,6 +198,9 @@ struct virtio_gpu_vbuffer { struct list_head list; uint32_t seqno; + + /* guest-bound transfer whose shmem backing needs a CPU sync */ + bool sync_for_cpu; }; struct virtio_gpu_output { diff --git a/drivers/gpu/drm/virtio/virtgpu_ioctl.c b/drivers/gpu/drm/virtio/virtgpu_ioctl.c index fcdb07a37972..81e70a12b356 100644 --- a/drivers/gpu/drm/virtio/virtgpu_ioctl.c +++ b/drivers/gpu/drm/virtio/virtgpu_ioctl.c @@ -261,6 +261,27 @@ static int virtio_gpu_transfer_from_host_ioctl(struct drm_device *dev, if (ret != 0) goto err_put_free; + if (virtio_gpu_is_shmem(bo) && virtio_gpu_use_dma_api(vgdev->vdev)) { + /* + * The sync on completion restores the whole mapping, so an + * earlier transfer has to be done before this one snapshots it. + * Otherwise the snapshot predates anything the CPU wrote once + * that transfer's fence signalled, and the later sync would + * discard it. Nothing can add a fence behind our back here, + * since doing so takes the reservation we already hold. + * This writes the pages, so it waits as a writer does. READ + * usage covers existing readers. + */ + long wait = dma_resv_wait_timeout(objs->objs[0]->resv, + DMA_RESV_USAGE_READ, true, + MAX_SCHEDULE_TIMEOUT); + + if (wait < 0) { + ret = wait; + goto err_unlock; + } + } + fence = virtio_gpu_fence_alloc(vgdev, vgdev->fence_drv.context, 0); if (!fence) { ret = -ENOMEM; @@ -320,6 +341,28 @@ static int virtio_gpu_transfer_to_host_ioctl(struct drm_device *dev, void *data, if (ret != 0) goto err_put_free; + /* + * A transfer the other way may have queued without yet syncing + * its mapping. Pushing the guest pages into it now would + * discard what the device wrote there, so wait for that sync: + * it runs before the fence it belongs to is signalled. The + * flag is only set under this reservation, so it cannot appear + * behind our back, and the acquire pairs with the release in + * that sync, so finding it clear means the pages it wrote are + * visible here too. + */ + if (smp_load_acquire(&bo->from_host_pending)) { + long wait = dma_resv_wait_timeout(objs->objs[0]->resv, + DMA_RESV_USAGE_WRITE, + true, + MAX_SCHEDULE_TIMEOUT); + + if (wait < 0) { + ret = wait; + goto err_unlock; + } + } + ret = -ENOMEM; fence = virtio_gpu_fence_alloc(vgdev, vgdev->fence_drv.context, 0); diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c index c02c03c10d92..d99fb9e326e8 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vq.c +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c @@ -256,6 +256,33 @@ void virtio_gpu_dequeue_ctrl_func(struct work_struct *work) } while (!virtqueue_enable_cb(vgdev->ctrlq.vq)); spin_unlock(&vgdev->ctrlq.qlock); + /* + * Sync guest-bound transfers before signalling anything, so that a + * waiter cannot read the backing pages while what the device wrote is + * still in a bounce buffer. This cannot be folded into the loop below: + * virtio_gpu_fence_event_process() also signals every earlier fence in + * the same context, so any entry there may signal this entry's fence. + */ + list_for_each_entry(entry, &reclaim_list, list) { + if (entry->sync_for_cpu) { + struct virtio_gpu_object *bo = + gem_to_virtio_gpu_obj(entry->objs->objs[0]); + + dma_sync_sgtable_for_cpu(vgdev->vdev->dev.parent, + bo->base.sgt, DMA_FROM_DEVICE); + /* + * Release, so a transfer the other way that skips its + * wait on the strength of this cannot go on to read + * the backing pages before the sync above is visible. + * Nothing orders the two otherwise: where the mapping + * bounces on a coherent device the sync is a plain + * copy, and dma_direct_sync_sg_for_cpu() emits its + * barrier only for the non-coherent case. + */ + smp_store_release(&bo->from_host_pending, false); + } + } + list_for_each_entry(entry, &reclaim_list, list) { resp = (struct virtio_gpu_ctrl_hdr *)entry->resp_buf; @@ -1278,12 +1305,31 @@ void virtio_gpu_cmd_transfer_from_host_3d(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(objs->objs[0]); struct virtio_gpu_transfer_host_3d *cmd_p; struct virtio_gpu_vbuffer *vbuf; + bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev); cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p)); memset(cmd_p, 0, sizeof(*cmd_p)); vbuf->objs = objs; + if (virtio_gpu_is_shmem(bo) && use_dma_api) { + /* + * The device writes only the requested box, so prime the + * mapping with the current contents: otherwise the sync on + * completion would hand back whatever a bounce buffer held for + * the regions the device does not touch. + */ + dma_sync_sgtable_for_device(vgdev->vdev->dev.parent, + bo->base.sgt, DMA_TO_DEVICE); + vbuf->sync_for_cpu = true; + /* + * Set under the reservation the caller holds, so a transfer + * the other way cannot miss it and push the guest pages into + * the mapping while the device still owns it. + */ + WRITE_ONCE(bo->from_host_pending, true); + } + cmd_p->hdr.type = cpu_to_le32(VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D); cmd_p->hdr.ctx_id = cpu_to_le32(ctx_id); cmd_p->resource_id = cpu_to_le32(bo->hw_res_handle); From 6a5719cc3ef2e4d9857cc4ae18e6db09d59a8cc9 Mon Sep 17 00:00:00 2001 From: Daniel J Blueman Date: Sun, 20 Sep 2026 16:16:08 +0200 Subject: [PATCH 2090/2366] net: qrtr: resend HELLO on MHI resume Since the MHI HELLO exchange was relocated, it is sent only at device registration. During a suspend-resume cycle, the firmware in WiFi cards such as WCN7850 indefinitely waits for another HELLO, triggering: ath12k_wifi7_pci 0004:01:00.0: timeout while waiting for restart complete ath12k_wifi7_pci 0004:01:00.0: failed to resume core: -110 Fix this by triggering the handshake from resume_early in the MHI transport. Validated on Qualcomm X1E-801800 on Lenovo Slim 7x across 10 suspend-resume cycles. Fixes: 544d85de4dc2 ("net: qrtr: Send HELLO message on endpoint register") Signed-off-by: Daniel J Blueman Reviewed-by: Manivannan Sadhasivam Reported-by: Jeff Johnson Link: https://lore.kernel.org/all/6257c447-788d-4362-851e-0d552bcf7c56@oss.qualcomm.com/ Tested-by: Jeff Johnson Reported-by: Vlastimil Babka (SUSE) Link: https://lore.kernel.org/all/ab1491bb-cca5-4145-ac7d-31c966abf7b4@suse.com/ Tested-by: Vlastimil Babka (SUSE) Reported-by: Takashi Iwai Link: https://lore.kernel.org/all/87a4plsg4w.wl-tiwai@suse.de/ Tested-by: Takashi Iwai Signed-off-by: Thorsten Leemhuis Signed-off-by: Linus Torvalds --- net/qrtr/af_qrtr.c | 13 +++++++++++++ net/qrtr/mhi.c | 9 ++++++++- net/qrtr/qrtr.h | 2 ++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/net/qrtr/af_qrtr.c b/net/qrtr/af_qrtr.c index 78347c937af7..e7b3647424b8 100644 --- a/net/qrtr/af_qrtr.c +++ b/net/qrtr/af_qrtr.c @@ -623,6 +623,19 @@ static void qrtr_hello_work(struct work_struct *work) qrtr_port_put(ctrl); } +/* Trigger the HELLO handshake after the remote has been reset, eg on resume */ +void qrtr_endpoint_hello(struct qrtr_endpoint *ep) +{ + struct qrtr_node *node = ep->node; + + mutex_lock(&node->ep_lock); + node->hello_sent = false; + mutex_unlock(&node->ep_lock); + + schedule_delayed_work(&node->say_hello, 0); +} +EXPORT_SYMBOL_GPL(qrtr_endpoint_hello); + /** * qrtr_endpoint_register() - register a new endpoint * @ep: endpoint to register diff --git a/net/qrtr/mhi.c b/net/qrtr/mhi.c index 3990da1a65dc..e9a4bb92ce76 100644 --- a/net/qrtr/mhi.c +++ b/net/qrtr/mhi.c @@ -183,6 +183,7 @@ static int __maybe_unused qcom_mhi_qrtr_pm_suspend_late(struct device *dev) static int __maybe_unused qcom_mhi_qrtr_pm_resume_early(struct device *dev) { struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev); + struct qrtr_mhi_dev *qdev = dev_get_drvdata(dev); enum mhi_state state; int rc; @@ -201,7 +202,13 @@ static int __maybe_unused qcom_mhi_qrtr_pm_resume_early(struct device *dev) return rc; } - return qcom_mhi_qrtr_queue_dl_buffers(mhi_dev); + rc = qcom_mhi_qrtr_queue_dl_buffers(mhi_dev); + if (rc) + return rc; + + qrtr_endpoint_hello(&qdev->ep); + + return 0; } static const struct dev_pm_ops qcom_mhi_qrtr_pm_ops = { diff --git a/net/qrtr/qrtr.h b/net/qrtr/qrtr.h index 3f2d28696062..de2de69a6199 100644 --- a/net/qrtr/qrtr.h +++ b/net/qrtr/qrtr.h @@ -27,6 +27,8 @@ int qrtr_endpoint_register(struct qrtr_endpoint *ep, unsigned int nid); void qrtr_endpoint_unregister(struct qrtr_endpoint *ep); +void qrtr_endpoint_hello(struct qrtr_endpoint *ep); + int qrtr_endpoint_post(struct qrtr_endpoint *ep, const void *data, size_t len); int qrtr_ns_init(void); From 93f51579e7df248780214094418f205253383cc5 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 20 Sep 2026 13:48:15 -0700 Subject: [PATCH 2091/2366] Linux 7.3-rc4 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0f1b80100b47..751a08643bf8 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc3 +EXTRAVERSION = -rc4 NAME = Baby Opossum Posse # *DOCUMENTATION* From ae2c5bf969573708cd5b6bb6393631255d83c3fe Mon Sep 17 00:00:00 2001 From: Prathamesh Shete Date: Wed, 16 Sep 2026 06:51:32 +0000 Subject: [PATCH 2092/2366] pinctrl: tegra238: Fix register bank for AON pin groups The AON pin controller has a single register region and therefore, the bank defined in the tegra238_functions[] and tegra238_aon_groups[] for the AON pin groups must be 0. However, commit 25cac7292d49 ("pinctrl: tegra: Add Tegra238 pinmux driver") incorrectly specified the bank for these pins as 1 and not 0. This means that in the tegra_pinctrl_probe() function we use an invalid index when accessing the pmx->regs[] array which causes an incorrect address to be used for accessing the pinmux registers. Fix this by correcting the bank for the AON pin groups. Fixes: 25cac7292d49 ("pinctrl: tegra: Add Tegra238 pinmux driver") Signed-off-by: Prathamesh Shete Signed-off-by: Linus Walleij --- drivers/pinctrl/tegra/pinctrl-tegra238.c | 204 +++++++++++------------ 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/drivers/pinctrl/tegra/pinctrl-tegra238.c b/drivers/pinctrl/tegra/pinctrl-tegra238.c index ec482365f14f..40aba285944e 100644 --- a/drivers/pinctrl/tegra/pinctrl-tegra238.c +++ b/drivers/pinctrl/tegra/pinctrl-tegra238.c @@ -1744,57 +1744,57 @@ static const char * const tegra238_functions[] = { #define drive_sdmmc1_dat0_pu2 DRV_PINGROUP_ENTRY_Y(0x8034, 28, 2, 30, 2, -1, -1, -1, -1, 0) #define drive_ufs0_rst_n_pv1 DRV_PINGROUP_ENTRY_Y(0x11004, 12, 5, 24, 5, -1, -1, -1, -1, 0) #define drive_ufs0_ref_clk_pv0 DRV_PINGROUP_ENTRY_Y(0x1100c, 12, 5, 24, 5, -1, -1, -1, -1, 0) -#define drive_batt_oc_paa4 DRV_PINGROUP_ENTRY_Y(0x1024, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_bootv_ctl_n_paa0 DRV_PINGROUP_ENTRY_Y(0x102c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_vcomp_alert_paa2 DRV_PINGROUP_ENTRY_Y(0x105c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_hdmi_cec_pbb0 DRV_PINGROUP_ENTRY_Y(0x1064, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_touch_clk_pdd3 DRV_PINGROUP_ENTRY_Y(0x106c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart3_rx_pcc6 DRV_PINGROUP_ENTRY_Y(0x1074, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart3_tx_pcc5 DRV_PINGROUP_ENTRY_Y(0x107c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen8_i2c_sda_pdd2 DRV_PINGROUP_ENTRY_Y(0x1084, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen8_i2c_scl_pdd1 DRV_PINGROUP_ENTRY_Y(0x108c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_mosi_pcc2 DRV_PINGROUP_ENTRY_Y(0x1094, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen2_i2c_scl_pcc7 DRV_PINGROUP_ENTRY_Y(0x109c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_cs0_pcc3 DRV_PINGROUP_ENTRY_Y(0x10a4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen2_i2c_sda_pdd0 DRV_PINGROUP_ENTRY_Y(0x10ac, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_sck_pcc0 DRV_PINGROUP_ENTRY_Y(0x10b4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_miso_pcc1 DRV_PINGROUP_ENTRY_Y(0x10bc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio49_pee2 DRV_PINGROUP_ENTRY_Y(0x10c4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio50_pee4 DRV_PINGROUP_ENTRY_Y(0x10cc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio82_pee3 DRV_PINGROUP_ENTRY_Y(0x10d4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio71_pff2 DRV_PINGROUP_ENTRY_Y(0x10dc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio76_pff7 DRV_PINGROUP_ENTRY_Y(0x10e4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio74_pff5 DRV_PINGROUP_ENTRY_Y(0x10ec, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio00_paa1 DRV_PINGROUP_ENTRY_Y(0x10f4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio19_pdd6 DRV_PINGROUP_ENTRY_Y(0x10fc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio86_phh3 DRV_PINGROUP_ENTRY_Y(0x1104, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio72_pff3 DRV_PINGROUP_ENTRY_Y(0x110c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio77_pgg0 DRV_PINGROUP_ENTRY_Y(0x1114, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio80_pff6 DRV_PINGROUP_ENTRY_Y(0x111c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio84_pgg1 DRV_PINGROUP_ENTRY_Y(0x1124, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio83_pee5 DRV_PINGROUP_ENTRY_Y(0x112c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio73_pff4 DRV_PINGROUP_ENTRY_Y(0x1134, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio70_pff1 DRV_PINGROUP_ENTRY_Y(0x113c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio04_paa5 DRV_PINGROUP_ENTRY_Y(0x1144, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio85_pgg6 DRV_PINGROUP_ENTRY_Y(0x114c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio69_pff0 DRV_PINGROUP_ENTRY_Y(0x1154, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio25_paa6 DRV_PINGROUP_ENTRY_Y(0x115c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio26_paa7 DRV_PINGROUP_ENTRY_Y(0x1164, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_tx_pgg7 DRV_PINGROUP_ENTRY_Y(0x116c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_rx_phh0 DRV_PINGROUP_ENTRY_Y(0x1174, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_tx_pgg2 DRV_PINGROUP_ENTRY_Y(0x117c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_rx_pgg3 DRV_PINGROUP_ENTRY_Y(0x1184, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_cts_pgg5 DRV_PINGROUP_ENTRY_Y(0x118c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_rts_pgg4 DRV_PINGROUP_ENTRY_Y(0x1194, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_cts_phh2 DRV_PINGROUP_ENTRY_Y(0x119c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_rts_phh1 DRV_PINGROUP_ENTRY_Y(0x11a4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm7_pee1 DRV_PINGROUP_ENTRY_Y(0x11ac, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm2_pdd7 DRV_PINGROUP_ENTRY_Y(0x11b4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm3_pee0 DRV_PINGROUP_ENTRY_Y(0x11bc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm1_paa3 DRV_PINGROUP_ENTRY_Y(0x11c4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_cs1_pcc4 DRV_PINGROUP_ENTRY_Y(0x11cc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_dmic1_clk_pdd4 DRV_PINGROUP_ENTRY_Y(0x11d4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_dmic1_dat_pdd5 DRV_PINGROUP_ENTRY_Y(0x11dc, 12, 5, 20, 5, -1, -1, -1, -1, 1) +#define drive_batt_oc_paa4 DRV_PINGROUP_ENTRY_Y(0x1024, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_bootv_ctl_n_paa0 DRV_PINGROUP_ENTRY_Y(0x102c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_vcomp_alert_paa2 DRV_PINGROUP_ENTRY_Y(0x105c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_hdmi_cec_pbb0 DRV_PINGROUP_ENTRY_Y(0x1064, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_touch_clk_pdd3 DRV_PINGROUP_ENTRY_Y(0x106c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart3_rx_pcc6 DRV_PINGROUP_ENTRY_Y(0x1074, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart3_tx_pcc5 DRV_PINGROUP_ENTRY_Y(0x107c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen8_i2c_sda_pdd2 DRV_PINGROUP_ENTRY_Y(0x1084, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen8_i2c_scl_pdd1 DRV_PINGROUP_ENTRY_Y(0x108c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_mosi_pcc2 DRV_PINGROUP_ENTRY_Y(0x1094, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen2_i2c_scl_pcc7 DRV_PINGROUP_ENTRY_Y(0x109c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_cs0_pcc3 DRV_PINGROUP_ENTRY_Y(0x10a4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen2_i2c_sda_pdd0 DRV_PINGROUP_ENTRY_Y(0x10ac, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_sck_pcc0 DRV_PINGROUP_ENTRY_Y(0x10b4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_miso_pcc1 DRV_PINGROUP_ENTRY_Y(0x10bc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio49_pee2 DRV_PINGROUP_ENTRY_Y(0x10c4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio50_pee4 DRV_PINGROUP_ENTRY_Y(0x10cc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio82_pee3 DRV_PINGROUP_ENTRY_Y(0x10d4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio71_pff2 DRV_PINGROUP_ENTRY_Y(0x10dc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio76_pff7 DRV_PINGROUP_ENTRY_Y(0x10e4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio74_pff5 DRV_PINGROUP_ENTRY_Y(0x10ec, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio00_paa1 DRV_PINGROUP_ENTRY_Y(0x10f4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio19_pdd6 DRV_PINGROUP_ENTRY_Y(0x10fc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio86_phh3 DRV_PINGROUP_ENTRY_Y(0x1104, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio72_pff3 DRV_PINGROUP_ENTRY_Y(0x110c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio77_pgg0 DRV_PINGROUP_ENTRY_Y(0x1114, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio80_pff6 DRV_PINGROUP_ENTRY_Y(0x111c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio84_pgg1 DRV_PINGROUP_ENTRY_Y(0x1124, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio83_pee5 DRV_PINGROUP_ENTRY_Y(0x112c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio73_pff4 DRV_PINGROUP_ENTRY_Y(0x1134, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio70_pff1 DRV_PINGROUP_ENTRY_Y(0x113c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio04_paa5 DRV_PINGROUP_ENTRY_Y(0x1144, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio85_pgg6 DRV_PINGROUP_ENTRY_Y(0x114c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio69_pff0 DRV_PINGROUP_ENTRY_Y(0x1154, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio25_paa6 DRV_PINGROUP_ENTRY_Y(0x115c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio26_paa7 DRV_PINGROUP_ENTRY_Y(0x1164, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_tx_pgg7 DRV_PINGROUP_ENTRY_Y(0x116c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_rx_phh0 DRV_PINGROUP_ENTRY_Y(0x1174, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_tx_pgg2 DRV_PINGROUP_ENTRY_Y(0x117c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_rx_pgg3 DRV_PINGROUP_ENTRY_Y(0x1184, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_cts_pgg5 DRV_PINGROUP_ENTRY_Y(0x118c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_rts_pgg4 DRV_PINGROUP_ENTRY_Y(0x1194, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_cts_phh2 DRV_PINGROUP_ENTRY_Y(0x119c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_rts_phh1 DRV_PINGROUP_ENTRY_Y(0x11a4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm7_pee1 DRV_PINGROUP_ENTRY_Y(0x11ac, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm2_pdd7 DRV_PINGROUP_ENTRY_Y(0x11b4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm3_pee0 DRV_PINGROUP_ENTRY_Y(0x11bc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm1_paa3 DRV_PINGROUP_ENTRY_Y(0x11c4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_cs1_pcc4 DRV_PINGROUP_ENTRY_Y(0x11cc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_dmic1_clk_pdd4 DRV_PINGROUP_ENTRY_Y(0x11d4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_dmic1_dat_pdd5 DRV_PINGROUP_ENTRY_Y(0x11dc, 12, 5, 20, 5, -1, -1, -1, -1, 0) #define drive_sdmmc1_comp DRV_PINGROUP_ENTRY_N @@ -1961,57 +1961,57 @@ static const struct tegra_pingroup tegra238_groups[] = { }; static const struct tegra_pingroup tegra238_aon_groups[] = { - PINGROUP(bootv_ctl_n_paa0, RSVD0, RSVD1, RSVD2, RSVD3, 0x1028, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio00_paa1, RSVD0, RSVD1, RSVD2, RSVD3, 0x10f0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(vcomp_alert_paa2, SOC_THERM_OC1, RSVD1, RSVD2, RSVD3, 0x1058, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm1_paa3, GP_PWM1, RSVD1, RSVD2, RSVD3, 0x11c0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(batt_oc_paa4, SOC_THERM_OC2, RSVD1, RSVD2, RSVD3, 0x1020, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio04_paa5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1140, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio25_paa6, RSVD0, RSVD1, RSVD2, RSVD3, 0x1158, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio26_paa7, RSVD0, SOC_THERM_OC3, RSVD2, RSVD3, 0x1160, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(hdmi_cec_pbb0, HDMI_CEC, RSVD1, RSVD2, RSVD3, 0x1060, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_sck_pcc0, SPI2_SCK, RSVD1, RSVD2, RSVD3, 0x10b0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_miso_pcc1, SPI2_DIN, RSVD1, RSVD2, RSVD3, 0x10b8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_mosi_pcc2, SPI2_DOUT, RSVD1, RSVD2, RSVD3, 0x1090, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_cs0_pcc3, SPI2_CS0, RSVD1, RSVD2, RSVD3, 0x10a0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_cs1_pcc4, SPI2_CS1, RSVD1, RSVD2, RSVD3, 0x11c8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(uart3_tx_pcc5, UARTC_TXD, RSVD1, RSVD2, RSVD3, 0x1078, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart3_rx_pcc6, UARTC_RXD, RSVD1, RSVD2, RSVD3, 0x1070, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen2_i2c_scl_pcc7, I2C2_CLK, RSVD1, RSVD2, RSVD3, 0x1098, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen2_i2c_sda_pdd0, I2C2_DAT, RSVD1, RSVD2, RSVD3, 0x10a8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen8_i2c_scl_pdd1, I2C8_CLK, RSVD1, RSVD2, RSVD3, 0x1088, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen8_i2c_sda_pdd2, I2C8_DAT, RSVD1, RSVD2, RSVD3, 0x1080, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(touch_clk_pdd3, GP_PWM4, TOUCH_CLK, RSVD2, RSVD3, 0x1068, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(dmic1_clk_pdd4, DMIC1_CLK, RSVD1, DMIC5_CLK, RSVD3, 0x11d0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(dmic1_dat_pdd5, DMIC1_DAT, RSVD1, DMIC5_DAT, RSVD3, 0x11d8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio19_pdd6, RSVD0, WDT_RESET_OUTB, RSVD2, RSVD3, 0x10f8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio49_pee2, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio50_pee4, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio82_pee3, RSVD0, RSVD1, RSVD2, RSVD3, 0x10d0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio71_pff2, PPC_MODE_1, RSVD1, RSVD2, RSVD3, 0x10d8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio76_pff7, RSVD0, RSVD1, TSC_EDGE_OUT0, TSC_EDGE_OUT0A, 0x10e0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio74_pff5, PPC_READY, PPC_I2C_DAT, RSVD2, RSVD3, 0x10e8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio86_phh3, RSVD0, SPI5_CS1, TSC_EDGE_OUT3, TSC_EDGE_OUT0D, 0x1100, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio72_pff3, PPC_MODE_2, RSVD1, RSVD2, RSVD3, 0x1108, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio77_pgg0, RSVD0, RSVD1, TSC_EDGE_OUT1, TSC_EDGE_OUT0B, 0x1110, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio80_pff6, RSVD0, PPC_RST_N, RSVD2, RSVD3, 0x1118, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio84_pgg1, RSVD0, RSVD1, TSC_EDGE_OUT2, TSC_EDGE_OUT0C, 0x1120, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio83_pee5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1128, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio73_pff4, PPC_CC, PPC_I2C_CLK, RSVD2, RSVD3, 0x1130, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio70_pff1, PPC_MODE_0, RSVD1, RSVD2, RSVD3, 0x1138, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio85_pgg6, RSVD0, SPI4_CS1, RSVD2, RSVD3, 0x1148, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio69_pff0, PPC_INT_N, RSVD1, RSVD2, RSVD3, 0x1150, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_tx_pgg7, UARTE_TXD, SPI5_SCK, RSVD2, RSVD3, 0x1168, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_rx_phh0, UARTE_RXD, SPI5_MISO, RSVD2, RSVD3, 0x1170, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_tx_pgg2, UARTB_TXD, SPI4_SCK, RSVD2, RSVD3, 0x1178, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_rx_pgg3, UARTB_RXD, SPI4_MISO, RSVD2, RSVD3, 0x1180, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_cts_pgg5, UARTB_CTS, SPI4_CS0, RSVD2, RSVD3, 0x1188, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_rts_pgg4, UARTB_RTS, SPI4_MOSI, RSVD2, RSVD3, 0x1190, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_cts_phh2, UARTE_CTS, SPI5_CS0, RSVD2, RSVD3, 0x1198, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_rts_phh1, UARTE_RTS, SPI5_MOSI, RSVD2, RSVD3, 0x11a0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm2_pdd7, GP_PWM2, LED_BLINK, RSVD2, RSVD3, 0x11b0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm3_pee0, GP_PWM3, RSVD1, RSVD2, RSVD3, 0x11b8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm7_pee1, GP_PWM7, RSVD1, RSVD2, RSVD3, 0x11a8, 1, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(bootv_ctl_n_paa0, RSVD0, RSVD1, RSVD2, RSVD3, 0x1028, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio00_paa1, RSVD0, RSVD1, RSVD2, RSVD3, 0x10f0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(vcomp_alert_paa2, SOC_THERM_OC1, RSVD1, RSVD2, RSVD3, 0x1058, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm1_paa3, GP_PWM1, RSVD1, RSVD2, RSVD3, 0x11c0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(batt_oc_paa4, SOC_THERM_OC2, RSVD1, RSVD2, RSVD3, 0x1020, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio04_paa5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1140, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio25_paa6, RSVD0, RSVD1, RSVD2, RSVD3, 0x1158, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio26_paa7, RSVD0, SOC_THERM_OC3, RSVD2, RSVD3, 0x1160, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(hdmi_cec_pbb0, HDMI_CEC, RSVD1, RSVD2, RSVD3, 0x1060, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_sck_pcc0, SPI2_SCK, RSVD1, RSVD2, RSVD3, 0x10b0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_miso_pcc1, SPI2_DIN, RSVD1, RSVD2, RSVD3, 0x10b8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_mosi_pcc2, SPI2_DOUT, RSVD1, RSVD2, RSVD3, 0x1090, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_cs0_pcc3, SPI2_CS0, RSVD1, RSVD2, RSVD3, 0x10a0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_cs1_pcc4, SPI2_CS1, RSVD1, RSVD2, RSVD3, 0x11c8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(uart3_tx_pcc5, UARTC_TXD, RSVD1, RSVD2, RSVD3, 0x1078, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart3_rx_pcc6, UARTC_RXD, RSVD1, RSVD2, RSVD3, 0x1070, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen2_i2c_scl_pcc7, I2C2_CLK, RSVD1, RSVD2, RSVD3, 0x1098, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen2_i2c_sda_pdd0, I2C2_DAT, RSVD1, RSVD2, RSVD3, 0x10a8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen8_i2c_scl_pdd1, I2C8_CLK, RSVD1, RSVD2, RSVD3, 0x1088, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen8_i2c_sda_pdd2, I2C8_DAT, RSVD1, RSVD2, RSVD3, 0x1080, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(touch_clk_pdd3, GP_PWM4, TOUCH_CLK, RSVD2, RSVD3, 0x1068, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(dmic1_clk_pdd4, DMIC1_CLK, RSVD1, DMIC5_CLK, RSVD3, 0x11d0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(dmic1_dat_pdd5, DMIC1_DAT, RSVD1, DMIC5_DAT, RSVD3, 0x11d8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio19_pdd6, RSVD0, WDT_RESET_OUTB, RSVD2, RSVD3, 0x10f8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio49_pee2, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio50_pee4, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio82_pee3, RSVD0, RSVD1, RSVD2, RSVD3, 0x10d0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio71_pff2, PPC_MODE_1, RSVD1, RSVD2, RSVD3, 0x10d8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio76_pff7, RSVD0, RSVD1, TSC_EDGE_OUT0, TSC_EDGE_OUT0A, 0x10e0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio74_pff5, PPC_READY, PPC_I2C_DAT, RSVD2, RSVD3, 0x10e8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio86_phh3, RSVD0, SPI5_CS1, TSC_EDGE_OUT3, TSC_EDGE_OUT0D, 0x1100, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio72_pff3, PPC_MODE_2, RSVD1, RSVD2, RSVD3, 0x1108, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio77_pgg0, RSVD0, RSVD1, TSC_EDGE_OUT1, TSC_EDGE_OUT0B, 0x1110, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio80_pff6, RSVD0, PPC_RST_N, RSVD2, RSVD3, 0x1118, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio84_pgg1, RSVD0, RSVD1, TSC_EDGE_OUT2, TSC_EDGE_OUT0C, 0x1120, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio83_pee5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1128, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio73_pff4, PPC_CC, PPC_I2C_CLK, RSVD2, RSVD3, 0x1130, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio70_pff1, PPC_MODE_0, RSVD1, RSVD2, RSVD3, 0x1138, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio85_pgg6, RSVD0, SPI4_CS1, RSVD2, RSVD3, 0x1148, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio69_pff0, PPC_INT_N, RSVD1, RSVD2, RSVD3, 0x1150, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_tx_pgg7, UARTE_TXD, SPI5_SCK, RSVD2, RSVD3, 0x1168, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_rx_phh0, UARTE_RXD, SPI5_MISO, RSVD2, RSVD3, 0x1170, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_tx_pgg2, UARTB_TXD, SPI4_SCK, RSVD2, RSVD3, 0x1178, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_rx_pgg3, UARTB_RXD, SPI4_MISO, RSVD2, RSVD3, 0x1180, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_cts_pgg5, UARTB_CTS, SPI4_CS0, RSVD2, RSVD3, 0x1188, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_rts_pgg4, UARTB_RTS, SPI4_MOSI, RSVD2, RSVD3, 0x1190, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_cts_phh2, UARTE_CTS, SPI5_CS0, RSVD2, RSVD3, 0x1198, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_rts_phh1, UARTE_RTS, SPI5_MOSI, RSVD2, RSVD3, 0x11a0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm2_pdd7, GP_PWM2, LED_BLINK, RSVD2, RSVD3, 0x11b0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm3_pee0, GP_PWM3, RSVD1, RSVD2, RSVD3, 0x11b8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm7_pee1, GP_PWM7, RSVD1, RSVD2, RSVD3, 0x11a8, 0, Y, 5, 7, 6, 8, -1, 10, 12), }; static const struct tegra_pinctrl_soc_data tegra238_pinctrl_aon = { From ada667890773e033d2f40dc94176e3beb930b516 Mon Sep 17 00:00:00 2001 From: Li Youhong Date: Fri, 4 Sep 2026 09:49:58 +0800 Subject: [PATCH 2093/2366] drm/bridge: samsung-dsim: fix TE GPIO lifetime for host attach When the Exynos DSI driver was generalized into samsung-dsim, the TE GPIO acquisition was switched from gpiod_get_optional() to devm_gpiod_get_optional() while keeping the matching gpiod_put() calls. That combination is wrong for a managed descriptor. However, dropping the puts and keeping the managed get is also wrong: samsung_dsim_register_te_irq() runs from the DSI host attach callback, and host detach/reattach can happen without destroying the device that owns the managed action. A second attach would then request the GPIO again without having released it. Switch back to a non-managed gpiod_get_optional() and keep the explicit gpiod_put() on the request_irq() error path and in samsung_dsim_unregister_te_irq(). Fixes: e7447128ca4a ("drm: bridge: Generalize Exynos-DSI driver into a Samsung DSIM bridge") Suggested-by: Luca Ceresoli Signed-off-by: Li Youhong Reviewed-by: Luca Ceresoli Tested-by: Luca Ceresoli Link: https://patch.msgid.link/20260904014958.1572918-1-dayou5941@163.com [Luca: remove unnecessary comment] Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/samsung-dsim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c index e2fc69fc51b6..4694241f4d22 100644 --- a/drivers/gpu/drm/bridge/samsung-dsim.c +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -1862,7 +1862,7 @@ static int samsung_dsim_register_te_irq(struct samsung_dsim *dsi, struct device int te_gpio_irq; int ret; - dsi->te_gpio = devm_gpiod_get_optional(dev, "te", GPIOD_IN); + dsi->te_gpio = gpiod_get_optional(dev, "te", GPIOD_IN); if (!dsi->te_gpio) return 0; else if (IS_ERR(dsi->te_gpio)) From 7a6d08ee0f0e30023d18779bb314db8fd9a3b6d4 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 16:50:22 +0200 Subject: [PATCH 2094/2366] ovpn: preserve IPv6 scope id for netlink peer endpoints ovpn accepts OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID and reports bind->remote.in6.sin6_scope_id in peer dumps, but the netlink endpoint parser never copied the attribute into the sockaddr_in6 used to create or update the peer bind. As a result, an IPv6 link-local remote endpoint configured through netlink loses its interface scope, unlike on the peer float path where ipv6_iface_scope_id populates the field. The UDPv6 output path then builds a flow with flowi6_oif set to zero and route lookup can fail or select the wrong interface. Copy the scope id when parsing non-v4-mapped IPv6 remote endpoints. The existing precheck already rejects the scope-id attribute for IPv4 and v4-mapped IPv6 remotes. Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/netlink.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 4dad85294198..2ba762082acc 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -100,6 +100,8 @@ static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs, struct sockaddr_in6 *sin6; struct sockaddr_in *sin; struct in6_addr *in6; + struct nlattr *scope; + u32 scope_id = 0; __be16 port = 0; __be32 *in; @@ -114,6 +116,9 @@ static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs, } else if (attrs[OVPN_A_PEER_REMOTE_IPV6]) { ss->ss_family = AF_INET6; in6 = nla_data(attrs[OVPN_A_PEER_REMOTE_IPV6]); + scope = attrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]; + if (scope) + scope_id = nla_get_u32(scope); } else { return false; } @@ -126,6 +131,7 @@ static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs, if (!ipv6_addr_v4mapped(in6)) { sin6 = (struct sockaddr_in6 *)ss; sin6->sin6_port = port; + sin6->sin6_scope_id = scope_id; memcpy(&sin6->sin6_addr, in6, sizeof(*in6)); break; } From 77393b4d72dfeb764b2af2b848acc659f6fcfd0a Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 16:50:23 +0200 Subject: [PATCH 2095/2366] ovpn: skip UDP source validation for unspecified addresses ovpn validates the cached local UDP source address before reusing or refreshing a peer dst cache. This is only meaningful when a concrete source address is selected. For IPv6, calling ipv6_chk_addr with :: checks whether the unspecified address itself is configured on the host. A peer may legitimately have bind->local.ipv6 set to :: when no local endpoint was configured or after a stale learned address was cleared. In that case the source should be left unspecified and selected by ip6_dst_lookup_flow(). For IPv4, inet_confirm_addr(..., local = 0, ...) asks for local address autoselection rather than validating a chosen source. Skip the precheck there as well and let ip_route_output_flow select or reject the source. Only validate non-zero/non-any source addresses. Fixes: 08857b5ec5d9 ("ovpn: implement basic TX path (UDP)") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/udp.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index 7f69e8890b5b..df4750dabd1e 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -161,8 +161,8 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, if (rt) goto transmit; - if (unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0, fl.saddr, - RT_SCOPE_HOST))) { + if (fl.saddr && unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0, + fl.saddr, RT_SCOPE_HOST))) { /* we may end up here when the cached address is not usable * anymore. In this case we reset address/cache and perform a * new look up @@ -238,7 +238,8 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, if (dst) goto transmit; - if (unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) { + if (!ipv6_addr_any(&fl.saddr) && + unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) { /* we may end up here when the cached address is not usable * anymore. In this case we reset address/cache and perform a * new look up From 7c66b7a4ae80a9309e6dc1d24b7b6b897e6348eb Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 16:50:24 +0200 Subject: [PATCH 2096/2366] ovpn: track UDP socket route key for peer dst cache ovpn stores the route used to transmit UDP packets in a per-peer dst cache. A cached dst is only valid for the route lookup inputs used when it was resolved. Some of those inputs are mutable while userspace still owns the UDP socket. In particular, changes to the socket mark or UDP source port do not invalidate ovpn's peer dst cache, so ovpn can keep using a route selected with an old socket route key. Replace the cached mark with a route key containing the socket-owned lookup inputs currently used by ovpn, and reset the peer dst cache when the key changes. Before storing a newly looked-up dst, recheck the route key under the peer lock so a dst resolved for stale socket state is not published. Fixes: 08857b5ec5d9 ("ovpn: implement basic TX path (UDP)") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 1 + drivers/net/ovpn/peer.h | 19 ++++++++- drivers/net/ovpn/udp.c | 90 +++++++++++++++++++++++++++++++++++------ 3 files changed, 95 insertions(+), 15 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index c95656ca7c35..b400783c2efa 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -113,6 +113,7 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id) RCU_INIT_POINTER(peer->bind, NULL); ovpn_crypto_state_init(&peer->crypto); spin_lock_init(&peer->lock); + seqcount_spinlock_init(&peer->route_key_seq, &peer->lock); kref_init(&peer->refcount); ovpn_peer_stats_init(&peer->vpn_stats); ovpn_peer_stats_init(&peer->link_stats); diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index dfa5c0037e02..063535699ecd 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -10,6 +10,7 @@ #ifndef _NET_OVPN_OVPNPEER_H_ #define _NET_OVPN_OVPNPEER_H_ +#include #include #include @@ -17,6 +18,16 @@ #include "socket.h" #include "stats.h" +/** + * struct ovpn_route_key - route key used for the peer dst cache + * @mark: fwmark used for route lookup + * @sport: UDP source port used for route lookup + */ +struct ovpn_route_key { + u32 mark; + __be16 sport; +}; + /** * struct ovpn_peer - the main remote peer object * @ovpn: main openvpn instance this peer belongs to @@ -45,6 +56,8 @@ * @tcp.sk_cb.ops: pointer to the original prot_ops object (TCP only) * @crypto: the crypto configuration (ciphers, keys, etc..) * @dst_cache: cache for dst_entry used to send to peer + * @route_key: route key matching the current dst cache contents + * @route_key_seq: seqcount protecting lockless route_key reads * @bind: remote peer binding * @keepalive_interval: seconds after which a new keepalive should be sent * @keepalive_xmit_exp: future timestamp when next keepalive should be sent @@ -55,7 +68,7 @@ * @vpn_stats: per-peer in-VPN TX/RX stats * @link_stats: per-peer link/transport TX/RX stats * @delete_reason: why peer was deleted (i.e. timeout, transport error, ..) - * @lock: protects binding to peer (bind) and keepalive* fields + * @lock: protects binding to peer (bind), route_key and keepalive* fields * @refcount: reference counter * @rcu: used to free peer in an RCU safe way * @release_entry: entry for the socket release list @@ -99,6 +112,8 @@ struct ovpn_peer { } tcp; struct ovpn_crypto_state crypto; struct dst_cache dst_cache; + struct ovpn_route_key route_key; + seqcount_spinlock_t route_key_seq; struct ovpn_bind __rcu *bind; unsigned long keepalive_interval; unsigned long keepalive_xmit_exp; @@ -109,7 +124,7 @@ struct ovpn_peer { struct ovpn_peer_stats vpn_stats; struct ovpn_peer_stats link_stats; enum ovpn_del_peer_reason delete_reason; - spinlock_t lock; /* protects bind and keepalive* */ + spinlock_t lock; /* protects bind, route_key and keepalive* */ struct kref refcount; struct rcu_head rcu; struct llist_node release_entry; diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index df4750dabd1e..c6d591cb7ff4 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -131,6 +131,48 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb) return 0; } +static bool ovpn_route_key_equal(const struct ovpn_route_key *a, + const struct ovpn_route_key *b) +{ + return a->mark == b->mark && a->sport == b->sport; +} + +/** + * ovpn_dst_cache_check_key - reset peer dst cache after key changes + * @peer: the peer owning the dst cache + * @cache: the cache that might need to be reset + * @key: the route key for the packet being transmitted + * + * Reset the peer dst cache if it was populated for a different route key. + */ +static void ovpn_dst_cache_check_key(struct ovpn_peer *peer, + struct dst_cache *cache, + const struct ovpn_route_key *key) +{ + struct ovpn_route_key old_key; + unsigned int seq; + + /* snapshot the saved key before deciding whether the cache matches */ + do { + seq = read_seqcount_begin(&peer->route_key_seq); + old_key = peer->route_key; + } while (read_seqcount_retry(&peer->route_key_seq, seq)); + + /* nothing changed: the current cache can be reused */ + if (likely(ovpn_route_key_equal(&old_key, key))) + return; + + /* recheck under lock because another path may have updated the key */ + spin_lock_bh(&peer->lock); + if (!ovpn_route_key_equal(&peer->route_key, key)) { + write_seqcount_begin(&peer->route_key_seq); + peer->route_key = *key; + dst_cache_reset(cache); + write_seqcount_end(&peer->route_key_seq); + } + spin_unlock_bh(&peer->lock); +} + /** * ovpn_udp4_output - send IPv4 packet over udp socket * @peer: the destination peer @@ -138,21 +180,23 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb) * @cache: dst cache * @sk: the socket to send the packet over * @skb: the packet to send + * @key: the route key snapshot used for cache validation and flow lookup * * Return: 0 on success or a negative error code otherwise */ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct dst_cache *cache, struct sock *sk, - struct sk_buff *skb) + struct sk_buff *skb, + const struct ovpn_route_key *key) { struct rtable *rt; struct flowi4 fl = { .saddr = bind->local.ipv4.s_addr, .daddr = bind->remote.in4.sin_addr.s_addr, - .fl4_sport = inet_sk(sk)->inet_sport, + .fl4_sport = key->sport, .fl4_dport = bind->remote.in4.sin_port, .flowi4_proto = sk->sk_protocol, - .flowi4_mark = sk->sk_mark, + .flowi4_mark = key->mark, }; int ret; @@ -193,7 +237,12 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, ret); goto err; } - dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + + /* avoid storing a stale cache */ + spin_lock_bh(&peer->lock); + if (likely(ovpn_route_key_equal(key, &peer->route_key))) + dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + spin_unlock_bh(&peer->lock); transmit: udp_tunnel_xmit_skb(rt, sk, skb, fl.saddr, fl.daddr, 0, @@ -213,12 +262,14 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, * @cache: dst cache * @sk: the socket to send the packet over * @skb: the packet to send + * @key: the route key snapshot used for cache validation and flow lookup * * Return: 0 on success or a negative error code otherwise */ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct dst_cache *cache, struct sock *sk, - struct sk_buff *skb) + struct sk_buff *skb, + const struct ovpn_route_key *key) { struct dst_entry *dst; int ret; @@ -226,10 +277,10 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct flowi6 fl = { .saddr = bind->local.ipv6, .daddr = bind->remote.in6.sin6_addr, - .fl6_sport = inet_sk(sk)->inet_sport, + .fl6_sport = key->sport, .fl6_dport = bind->remote.in6.sin6_port, .flowi6_proto = sk->sk_protocol, - .flowi6_mark = sk->sk_mark, + .flowi6_mark = key->mark, .flowi6_oif = bind->remote.in6.sin6_scope_id, }; @@ -259,7 +310,12 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, &bind->remote.in6, ret); goto err; } - dst_cache_set_ip6(cache, dst, &fl.saddr); + + /* avoid storing a stale cache */ + spin_lock_bh(&peer->lock); + if (likely(ovpn_route_key_equal(key, &peer->route_key))) + dst_cache_set_ip6(cache, dst, &fl.saddr); + spin_unlock_bh(&peer->lock); transmit: /* user IPv6 packets may be larger than the transport interface @@ -288,6 +344,7 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, * @cache: dst cache * @sk: the socket to send the packet over * @skb: the packet to send + * @key: route key snapshot used for cache validation and flow lookup * * rcu_read_lock should be held on entry. * On return, the skb is consumed. @@ -295,7 +352,8 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, * Return: 0 on success or a negative error code otherwise */ static int ovpn_udp_output(struct ovpn_peer *peer, struct dst_cache *cache, - struct sock *sk, struct sk_buff *skb) + struct sock *sk, struct sk_buff *skb, + struct ovpn_route_key *key) { struct ovpn_bind *bind; int ret; @@ -315,11 +373,11 @@ static int ovpn_udp_output(struct ovpn_peer *peer, struct dst_cache *cache, switch (bind->remote.in4.sin_family) { case AF_INET: - ret = ovpn_udp4_output(peer, bind, cache, sk, skb); + ret = ovpn_udp4_output(peer, bind, cache, sk, skb, key); break; #if IS_ENABLED(CONFIG_IPV6) case AF_INET6: - ret = ovpn_udp6_output(peer, bind, cache, sk, skb); + ret = ovpn_udp6_output(peer, bind, cache, sk, skb, key); break; #endif default: @@ -341,15 +399,21 @@ static int ovpn_udp_output(struct ovpn_peer *peer, struct dst_cache *cache, void ovpn_udp_send_skb(struct ovpn_peer *peer, struct sock *sk, struct sk_buff *skb) { + struct ovpn_route_key key = { + .mark = READ_ONCE(sk->sk_mark), + .sport = READ_ONCE(inet_sk(sk)->inet_sport), + }; int ret; skb->dev = peer->ovpn->dev; - skb->mark = READ_ONCE(sk->sk_mark); + skb->mark = key.mark; /* no checksum performed at this layer */ skb->ip_summed = CHECKSUM_NONE; + ovpn_dst_cache_check_key(peer, &peer->dst_cache, &key); + /* crypto layer -> transport (UDP) */ - ret = ovpn_udp_output(peer, &peer->dst_cache, sk, skb); + ret = ovpn_udp_output(peer, &peer->dst_cache, sk, skb, &key); if (unlikely(ret < 0)) kfree_skb(skb); } From fa603710bdb9aea33c0d9cc2c05ed24d84f58753 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 16:50:25 +0200 Subject: [PATCH 2097/2366] ovpn: validate peer state before caching UDP dst UDP route lookup runs without peer->lock while the bind is protected by RCU. The route key is snapshotted separately. Either can change while the lookup is in progress. The TX path currently checks only the route key before publishing the looked-up dst. If the bind changes but the route key does not, a dst resolved from the old endpoint can be installed in the cache after the bind replacement. Compare both the bind pointer and the route key under peer->lock before updating the cache. The RCU read-side critical section keeps the old bind alive throughout the lookup, so pointer identity is sufficient to detect a replacement. Fixes: f0281c1d3732 ("ovpn: add support for updating local or remote UDP endpoint") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/udp.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index c6d591cb7ff4..eeef4a7229f5 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -173,6 +173,35 @@ static void ovpn_dst_cache_check_key(struct ovpn_peer *peer, spin_unlock_bh(&peer->lock); } +/** + * ovpn_dst_cache_current - check whether a route lookup matches peer state + * @peer: the peer owning the bind and dst cache + * @bind: the RCU bind used for the route lookup + * @key: the route key used for the route lookup + * + * Check that @bind is still the current peer bind and that @key still matches + * the peer route key. The caller must hold @peer->lock. The TX path keeps + * @bind inside an RCU read-side critical section, so pointer identity is enough + * to detect whether the bind was replaced while the route lookup was running. + * + * Return: true if the lookup result still matches the current peer state and + * may update the dst cache. + */ +static bool ovpn_dst_cache_current(const struct ovpn_peer *peer, + const struct ovpn_bind *bind, + const struct ovpn_route_key *key) +{ + const struct ovpn_bind *curr_bind; + + lockdep_assert_held(&peer->lock); + + curr_bind = rcu_dereference_protected(peer->bind, + lockdep_is_held(&peer->lock)); + + return curr_bind == bind && + ovpn_route_key_equal(key, &peer->route_key); +} + /** * ovpn_udp4_output - send IPv4 packet over udp socket * @peer: the destination peer @@ -240,7 +269,7 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, /* avoid storing a stale cache */ spin_lock_bh(&peer->lock); - if (likely(ovpn_route_key_equal(key, &peer->route_key))) + if (likely(ovpn_dst_cache_current(peer, bind, key))) dst_cache_set_ip4(cache, &rt->dst, fl.saddr); spin_unlock_bh(&peer->lock); @@ -313,7 +342,7 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, /* avoid storing a stale cache */ spin_lock_bh(&peer->lock); - if (likely(ovpn_route_key_equal(key, &peer->route_key))) + if (likely(ovpn_dst_cache_current(peer, bind, key))) dst_cache_set_ip6(cache, dst, &fl.saddr); spin_unlock_bh(&peer->lock); From aea934a221ec6a867221e5b765f65f1857befd53 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 16:50:26 +0200 Subject: [PATCH 2098/2366] ovpn: replace bind when learning local endpoint struct ovpn_bind is published through peer->bind with RCU, but local endpoint learning updates bind->local in place under peer->lock. UDP TX reads the field without that lock. In particular, a concurrent IPv6 update can therefore result in a torn address read. Use ovpn_peer_reset_sockaddr to publish a replacement bind when learning a new local endpoint, just as a remote endpoint change does. Preserve the current remote address and reset the dst cache only after the new bind has been published successfully. Track remote endpoint changes separately so that float notification and transport-address rehashing remain limited to actual peer floats. Fixes: f0281c1d3732 ("ovpn: add support for updating local or remote UDP endpoint") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 43 ++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index b400783c2efa..430c6cd48db8 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -200,13 +200,12 @@ static void __ovpn_peer_hash_transp_addr(struct ovpn_peer *peer, */ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) { + const void *local_ip = NULL; struct sockaddr_storage ss; struct sockaddr_in6 *sa6; - bool reset_cache = false; struct sockaddr_in *sa; struct ovpn_bind *bind; - const void *local_ip; - size_t salen = 0; + bool floated = false; spin_lock_bh(&peer->lock); bind = rcu_dereference_protected(peer->bind, @@ -233,8 +232,7 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) .sin_addr.s_addr = ip_hdr(skb)->saddr, .sin_port = udp_hdr(skb)->source, }; - salen = sizeof(*sa); - reset_cache = true; + floated = true; break; } @@ -246,10 +244,12 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) netdev_name(peer->ovpn->dev), peer->id, &bind->local.ipv4.s_addr, &ip_hdr(skb)->daddr); - bind->local.ipv4.s_addr = ip_hdr(skb)->daddr; - reset_cache = true; + local_ip = &ip_hdr(skb)->daddr; + memcpy(&ss, &bind->remote, sizeof(struct sockaddr_in)); + break; } - break; + /* nothing changed */ + goto unlock; case htons(ETH_P_IPV6): /* float check */ if (unlikely(!ovpn_bind_skb_src_match(bind, skb))) { @@ -271,8 +271,7 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) ipv6_iface_scope_id(&ipv6_hdr(skb)->saddr, skb->skb_iif), }; - salen = sizeof(*sa6); - reset_cache = true; + floated = true; break; } @@ -285,26 +284,30 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) netdev_name(peer->ovpn->dev), peer->id, &bind->local.ipv6, &ipv6_hdr(skb)->daddr); - bind->local.ipv6 = ipv6_hdr(skb)->daddr; - reset_cache = true; + local_ip = &ipv6_hdr(skb)->daddr; + memcpy(&ss, &bind->remote, sizeof(struct sockaddr_in6)); + break; } - break; + /* nothing changed */ + goto unlock; default: goto unlock; } - if (unlikely(reset_cache)) - dst_cache_reset(&peer->dst_cache); - - /* if the peer did not float, we can bail out now */ - if (likely(!salen)) - goto unlock; - if (unlikely(ovpn_peer_reset_sockaddr(peer, (struct sockaddr_storage *)&ss, local_ip) < 0)) goto unlock; + /* reset the cache only after a successful bind update to avoid useless + * cache misses on concurrent TX + */ + dst_cache_reset(&peer->dst_cache); + + /* if only the local address changed, bail out now */ + if (!floated) + goto unlock; + net_dbg_ratelimited("%s: peer %d floated to %pIScp", netdev_name(peer->ovpn->dev), peer->id, &ss); From 7d8104988f423572df1f3347ce578037b1043f34 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 16:50:27 +0200 Subject: [PATCH 2099/2366] ovpn: replace bind when clearing stale local source The UDP output fallback clears bind->local in place when the remembered source address is no longer usable. The bind is RCU-published and read locklessly by concurrent TX, so an IPv6 reader can observe a torn address. Retry the route lookup with source address autoselection without modifying the bind. After a successful lookup, revalidate the bind and route key under peer->lock, reset the dst cache, and best-effort publish a replacement bind with a wildcard local address. Do not cache the resolved dst when clearing the local source. Replacing the source invalidates all per-CPU cache entries, while dst_cache_set_ip4 and dst_cache_set_ip6 update only the current CPU slot. The current packet can still use the resolved route; if bind allocation fails, a later cache miss retries the repair. Fixes: 08857b5ec5d9 ("ovpn: implement basic TX path (UDP)") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/udp.c | 81 +++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index eeef4a7229f5..055cdb1bee13 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -185,7 +185,7 @@ static void ovpn_dst_cache_check_key(struct ovpn_peer *peer, * to detect whether the bind was replaced while the route lookup was running. * * Return: true if the lookup result still matches the current peer state and - * may update the dst cache. + * may update the dst cache or replace the bind. */ static bool ovpn_dst_cache_current(const struct ovpn_peer *peer, const struct ovpn_bind *bind, @@ -218,6 +218,9 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct sk_buff *skb, const struct ovpn_route_key *key) { + struct sockaddr_storage remote; + struct in_addr local = {}; + bool reset_local = false; struct rtable *rt; struct flowi4 fl = { .saddr = bind->local.ipv4.s_addr, @@ -236,24 +239,17 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, if (fl.saddr && unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0, fl.saddr, RT_SCOPE_HOST))) { - /* we may end up here when the cached address is not usable - * anymore. In this case we reset address/cache and perform a - * new look up + /* The learned local address is not usable anymore. + * Retry with source address autoselection. */ fl.saddr = 0; - spin_lock_bh(&peer->lock); - bind->local.ipv4.s_addr = 0; - spin_unlock_bh(&peer->lock); - dst_cache_reset(cache); + reset_local = true; } rt = ip_route_output_flow(sock_net(sk), &fl, sk); if (IS_ERR(rt) && PTR_ERR(rt) == -EINVAL) { fl.saddr = 0; - spin_lock_bh(&peer->lock); - bind->local.ipv4.s_addr = 0; - spin_unlock_bh(&peer->lock); - dst_cache_reset(cache); + reset_local = true; rt = ip_route_output_flow(sock_net(sk), &fl, sk); } @@ -267,10 +263,28 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, goto err; } - /* avoid storing a stale cache */ + /* avoid storing a stale cache or local address */ spin_lock_bh(&peer->lock); - if (likely(ovpn_dst_cache_current(peer, bind, key))) - dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + if (likely(ovpn_dst_cache_current(peer, bind, key))) { + if (!reset_local) { + dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + spin_unlock_bh(&peer->lock); + goto transmit; + } + + /* invalidate per-CPU dst entries that may still carry + * the stale source + */ + dst_cache_reset(cache); + + /* preserve the current remote */ + memcpy(&remote, &bind->remote, sizeof(struct sockaddr_in)); + /* The current packet already has a valid wildcard-source route. + * If replacing the bind fails, leave the stale local in place; + * a later cache miss will retry the repair. + */ + ovpn_peer_reset_sockaddr(peer, &remote, &local); + } spin_unlock_bh(&peer->lock); transmit: @@ -300,6 +314,9 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct sk_buff *skb, const struct ovpn_route_key *key) { + struct in6_addr local = in6addr_any; + struct sockaddr_storage remote; + bool reset_local = false; struct dst_entry *dst; int ret; @@ -320,15 +337,11 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, if (!ipv6_addr_any(&fl.saddr) && unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) { - /* we may end up here when the cached address is not usable - * anymore. In this case we reset address/cache and perform a - * new look up + /* The learned local address is not usable anymore. + * Retry with source address autoselection. */ fl.saddr = in6addr_any; - spin_lock_bh(&peer->lock); - bind->local.ipv6 = in6addr_any; - spin_unlock_bh(&peer->lock); - dst_cache_reset(cache); + reset_local = true; } dst = ip6_dst_lookup_flow(sock_net(sk), sk, &fl, NULL); @@ -340,10 +353,28 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, goto err; } - /* avoid storing a stale cache */ + /* avoid storing a stale cache or local address */ spin_lock_bh(&peer->lock); - if (likely(ovpn_dst_cache_current(peer, bind, key))) - dst_cache_set_ip6(cache, dst, &fl.saddr); + if (likely(ovpn_dst_cache_current(peer, bind, key))) { + if (!reset_local) { + dst_cache_set_ip6(cache, dst, &fl.saddr); + spin_unlock_bh(&peer->lock); + goto transmit; + } + + /* invalidate per-CPU dst entries that may still carry + * the stale source + */ + dst_cache_reset(cache); + + /* preserve the current remote */ + memcpy(&remote, &bind->remote, sizeof(struct sockaddr_in6)); + /* The current packet already has a valid wildcard-source route. + * If replacing the bind fails, leave the stale local in place; + * a later cache miss will retry the repair. + */ + ovpn_peer_reset_sockaddr(peer, &remote, &local); + } spin_unlock_bh(&peer->lock); transmit: From b43beccb3713fafada57814b0a652f4a876eb75f Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 15:00:06 +0200 Subject: [PATCH 2100/2366] ovpn: always unhash old VPN addresses before rehashing ovpn_peer_hash_vpn_ip updates the per-peer VPN address hash entries after userspace changes a peer VPN address. The current code removes an old hash entry only when the new address for that family is not the unspecified address. When an address is cleared to 0.0.0.0 or ::, its hash node therefore remains linked in the bucket selected by the old address. The address comparison performed during lookup prevents the old address from matching, but the table retains a stale entry until the peer is removed or another address is configured for that family. Always remove both old VPN address hash entries before conditionally adding the currently configured addresses back. This ensures that a cleared address leaves its hash node unhashed. Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 430c6cd48db8..bbd9e17fb0bf 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -994,10 +994,11 @@ void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer) if (hlist_unhashed(&peer->hash_entry_id)) return; - if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) { - /* remove potential old hashing */ - hlist_nulls_del_init_rcu(&peer->hash_entry_addr4); + /* remove potential old hashing */ + hlist_nulls_del_init_rcu(&peer->hash_entry_addr4); + hlist_nulls_del_init_rcu(&peer->hash_entry_addr6); + if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) { nhead = ovpn_get_hash_head(peer->ovpn->peers->by_vpn_addr4, &peer->vpn_addrs.ipv4, sizeof(peer->vpn_addrs.ipv4)); @@ -1005,9 +1006,6 @@ void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer) } if (!ipv6_addr_any(&peer->vpn_addrs.ipv6)) { - /* remove potential old hashing */ - hlist_nulls_del_init_rcu(&peer->hash_entry_addr6); - nhead = ovpn_get_hash_head(peer->ovpn->peers->by_vpn_addr6, &peer->vpn_addrs.ipv6, sizeof(peer->vpn_addrs.ipv6)); From d25e885b31a0f2808d936f95c9a558a8a792669b Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 15:00:07 +0200 Subject: [PATCH 2101/2366] ovpn: reject duplicate peer VPN addresses In MP mode, ovpn uses the peer VPN addresses as lookup keys for selecting the peer that should receive an outgoing tunnel packet. However, the netlink peer configuration path does not currently reject duplicate VPN addresses. If two peers are configured with the same VPN address, both can be inserted in the VPN address hash table and lookups return whichever peer is found first. This makes peer selection ambiguous and dependent on hash insertion order. Reject peer creation or update when the resulting VPN address is already assigned to another peer. Ignore unspecified addresses because those are not inserted in the VPN address hash tables. This changes such configurations from being accepted to being rejected, but they have never worked reliably because peer selection is ambiguous. Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/netlink.c | 37 ++++++++++++++++----- drivers/net/ovpn/peer.c | 67 +++++++++++++++++++++++++++++++++++++- drivers/net/ovpn/peer.h | 6 ++++ 3 files changed, 101 insertions(+), 9 deletions(-) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 2ba762082acc..e23f7d1f49e0 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -480,8 +480,10 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; struct ovpn_priv *ovpn = info->user_ptr[0]; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct in6_addr vpn_addr6; + struct in_addr vpn_addr4; struct ovpn_socket *sock; struct ovpn_peer *peer; u32 peer_id; @@ -528,28 +530,47 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) rcu_read_unlock(); spin_lock_bh(&ovpn->lock); - ret = ovpn_nl_peer_modify(peer, info, attrs); - if (ret < 0) { - spin_unlock_bh(&ovpn->lock); - ovpn_peer_put(peer); - return ret; + + /* reject peer with conflicting VPN address */ + if (attrs[OVPN_A_PEER_VPN_IPV4]) { + vpn_addr4.s_addr = nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]); + if (ovpn_peer_vpn_addr_conflict4(ovpn, peer, &vpn_addr4)) + goto addr_conflict; } + if (attrs[OVPN_A_PEER_VPN_IPV6]) { + vpn_addr6 = nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]); + if (ovpn_peer_vpn_addr_conflict6(ovpn, peer, &vpn_addr6)) + goto addr_conflict; + } + + ret = ovpn_nl_peer_modify(peer, info, attrs); + if (ret < 0) + goto unlock; /* ret == 1 means that VPN IPv4/6 has been modified and rehashing * is required */ - if (ret > 0) + if (ret > 0) { ovpn_peer_hash_vpn_ip(peer); + ret = 0; + } /* if the remote endpoint was updated, the by_transp_addr hash bucket * also needs to be refreshed, otherwise incoming packets from the new * remote address would fail the lockless lookup */ if (attrs[OVPN_A_PEER_REMOTE_IPV4] || attrs[OVPN_A_PEER_REMOTE_IPV6]) ovpn_peer_hash_transp_addr(peer); + +unlock: spin_unlock_bh(&ovpn->lock); ovpn_peer_put(peer); - return 0; + return ret; +addr_conflict: + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "VPN IP is already assigned to another peer"); + ret = -EADDRINUSE; + goto unlock; } static int ovpn_nl_send_peer(struct sk_buff *skb, const struct genl_info *info, diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index bbd9e17fb0bf..2067825bb5b6 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -488,7 +488,7 @@ static struct ovpn_peer *ovpn_peer_get_by_vpn_addr4(struct ovpn_priv *ovpn, * Return: the peer if found or NULL otherwise */ static struct ovpn_peer *ovpn_peer_get_by_vpn_addr6(struct ovpn_priv *ovpn, - struct in6_addr *addr) + const struct in6_addr *addr) { struct hlist_nulls_head *nhead; struct hlist_nulls_node *ntmp; @@ -513,6 +513,64 @@ static struct ovpn_peer *ovpn_peer_get_by_vpn_addr6(struct ovpn_priv *ovpn, return NULL; } +/** + * ovpn_peer_vpn_addr_conflict4 - check if the VPN v4 address is already in use + * @ovpn: the openvpn instance to search + * @peer: peer being added or updated, or NULL + * @addr: VPN IPv4 address to check + * + * Check whether @addr is already assigned to another peer. @peer is ignored + * when found, allowing peer updates that keep an existing address. + * Unspecified addresses are ignored. + * + * Note: the caller must hold @ovpn->lock. + * + * Return: true on conflict, false otherwise. + */ +bool ovpn_peer_vpn_addr_conflict4(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in_addr *addr) +{ + struct ovpn_peer *tmp = NULL; + + lockdep_assert_held(&ovpn->lock); + + /* we don't hash INADDR_ANY, no conflict in that case */ + if (addr->s_addr != htonl(INADDR_ANY)) + tmp = ovpn_peer_get_by_vpn_addr4(ovpn, addr->s_addr); + + return tmp && tmp != peer; +} + +/** + * ovpn_peer_vpn_addr_conflict6 - check if the VPN v6 address is already in use + * @ovpn: the openvpn instance to search + * @peer: peer being added or updated, or NULL + * @addr: VPN IPv6 address to check + * + * Check whether @addr is already assigned to another peer. @peer is ignored + * when found, allowing peer updates that keep an existing address. + * Unspecified addresses are ignored. + * + * Note: the caller must hold @ovpn->lock. + * + * Return: true on conflict, false otherwise. + */ +bool ovpn_peer_vpn_addr_conflict6(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in6_addr *addr) +{ + struct ovpn_peer *tmp = NULL; + + lockdep_assert_held(&ovpn->lock); + + /* we don't hash ::, no conflict in that case */ + if (!ipv6_addr_any(addr)) + tmp = ovpn_peer_get_by_vpn_addr6(ovpn, addr); + + return tmp && tmp != peer; +} + /** * ovpn_peer_transp_match - check if sockaddr and peer binding match * @peer: the peer to get the binding from @@ -1040,6 +1098,13 @@ static int ovpn_peer_add_mp(struct ovpn_priv *ovpn, struct ovpn_peer *peer) goto out; } + /* reject peer with conflicting VPN address */ + if (ovpn_peer_vpn_addr_conflict4(ovpn, NULL, &peer->vpn_addrs.ipv4) || + ovpn_peer_vpn_addr_conflict6(ovpn, NULL, &peer->vpn_addrs.ipv6)) { + ret = -EADDRINUSE; + goto out; + } + bind = rcu_dereference_protected(peer->bind, true); /* peers connected via TCP have bind == NULL */ if (bind) { diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 063535699ecd..1879bfb76992 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -164,6 +164,12 @@ struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id); struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb); +bool ovpn_peer_vpn_addr_conflict4(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in_addr *addr); +bool ovpn_peer_vpn_addr_conflict6(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in6_addr *addr); void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer); void ovpn_peer_hash_transp_addr(struct ovpn_peer *peer); bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb, From 025af3a0a892514f9f27f186338ba3d44365547a Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 15:00:08 +0200 Subject: [PATCH 2102/2366] ovpn: reject multipeer peers without VPN addresses In MP mode, ovpn uses the peer VPN addresses to select the peer for outgoing tunnel packets. Peer creation currently requires a VPN IPv4 or IPv6 attribute, but it only checks for the presence of the attribute and not for a usable address value. This allows userspace to create an MP peer with only unspecified VPN addresses, or to update an existing peer so that both VPN address families become unspecified. Such a peer cannot be selected through the VPN address hash tables. Reject MP peer creation or update when the resulting peer would not have at least one VPN address configured. This changes such configurations from being accepted to being rejected, but they have never been usable because the peer cannot be selected through the VPN address hash tables. Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/netlink.c | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index e23f7d1f49e0..e9e0f75e0443 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -352,8 +352,10 @@ static int ovpn_nl_peer_modify(struct ovpn_peer *peer, struct genl_info *info, int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct in_addr vpn_addr4 = { .s_addr = htonl(INADDR_ANY) }; + struct in6_addr vpn_addr6 = IN6ADDR_ANY_INIT; struct ovpn_priv *ovpn = info->user_ptr[0]; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; struct ovpn_socket *ovpn_sock; struct socket *sock = NULL; struct ovpn_peer *peer; @@ -377,11 +379,20 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) return -EINVAL; /* in MP mode VPN IPs are required for selecting the right peer */ - if (ovpn->mode == OVPN_MODE_MP && !attrs[OVPN_A_PEER_VPN_IPV4] && - !attrs[OVPN_A_PEER_VPN_IPV6]) { - NL_SET_ERR_MSG_FMT_MOD(info->extack, - "VPN IP must be provided in MP mode"); - return -EINVAL; + if (ovpn->mode == OVPN_MODE_MP) { + if (attrs[OVPN_A_PEER_VPN_IPV4]) + vpn_addr4.s_addr = + nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]); + if (attrs[OVPN_A_PEER_VPN_IPV6]) + vpn_addr6 = + nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]); + + if (vpn_addr4.s_addr == htonl(INADDR_ANY) && + ipv6_addr_any(&vpn_addr6)) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "at least one VPN IP must be configured in MP mode"); + return -EINVAL; + } } peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]); @@ -531,6 +542,9 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) spin_lock_bh(&ovpn->lock); + vpn_addr4 = peer->vpn_addrs.ipv4; + vpn_addr6 = peer->vpn_addrs.ipv6; + /* reject peer with conflicting VPN address */ if (attrs[OVPN_A_PEER_VPN_IPV4]) { vpn_addr4.s_addr = nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]); @@ -543,6 +557,16 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) goto addr_conflict; } + /* in MP mode VPN IPs are required for selecting the right peer */ + if (ovpn->mode == OVPN_MODE_MP && + vpn_addr4.s_addr == htonl(INADDR_ANY) && + ipv6_addr_any(&vpn_addr6)) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "at least one VPN IP must be configured in MP mode"); + ret = -EINVAL; + goto unlock; + } + ret = ovpn_nl_peer_modify(peer, info, attrs); if (ret < 0) goto unlock; From 5940f3407b78062442cb01f541ef6eed709fc380 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 15:00:09 +0200 Subject: [PATCH 2103/2366] ovpn: reject invalid peer VPN addresses In MP mode, ovpn uses peer VPN addresses as lookup keys for selecting the peer that should receive outgoing tunnel packets. The netlink configuration path currently accepts address values that cannot sensibly identify a VPN peer, such as multicast, broadcast or loopback addresses. Reject invalid peer VPN addresses when creating or updating an MP peer. Keep accepting the unspecified address as the internal unset value, provided that at least one VPN address family remains configured. Fixes: 1d36a36f6d53 ("ovpn: implement peer add/get/dump/delete via netlink") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/netlink.c | 55 +++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index e9e0f75e0443..5432bc2eb8e8 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -185,6 +185,39 @@ static sa_family_t ovpn_nl_family_get(struct nlattr *addr4, return AF_UNSPEC; } +static int ovpn_nl_peer_check_vpn_addrs(const struct in_addr *addr4, + const struct in6_addr *addr6, + struct genl_info *info) +{ + int addr6_type; + + if (addr4->s_addr == htonl(INADDR_ANY) && ipv6_addr_any(addr6)) { + NL_SET_ERR_MSG_MOD(info->extack, + "at least one VPN IP must be configured in MP mode"); + return -EINVAL; + } + + if (ipv4_is_multicast(addr4->s_addr) || ipv4_is_lbcast(addr4->s_addr) || + ipv4_is_loopback(addr4->s_addr)) { + NL_SET_ERR_MSG_MOD(info->extack, + "VPN IPv4 address must be valid unicast or any"); + return -EADDRNOTAVAIL; + } + + if (!ipv6_addr_any(addr6)) { + addr6_type = ipv6_addr_type(addr6); + + if (!(addr6_type & IPV6_ADDR_UNICAST) || + (addr6_type & (IPV6_ADDR_LOOPBACK | IPV6_ADDR_COMPATv4))) { + NL_SET_ERR_MSG_MOD(info->extack, + "VPN IPv6 address must be valid unicast or any"); + return -EADDRNOTAVAIL; + } + } + + return 0; +} + static int ovpn_nl_peer_precheck(struct ovpn_priv *ovpn, struct genl_info *info, struct nlattr **attrs) @@ -387,12 +420,10 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) vpn_addr6 = nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]); - if (vpn_addr4.s_addr == htonl(INADDR_ANY) && - ipv6_addr_any(&vpn_addr6)) { - NL_SET_ERR_MSG_FMT_MOD(info->extack, - "at least one VPN IP must be configured in MP mode"); - return -EINVAL; - } + ret = ovpn_nl_peer_check_vpn_addrs(&vpn_addr4, &vpn_addr6, + info); + if (ret < 0) + return ret; } peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]); @@ -558,13 +589,11 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) } /* in MP mode VPN IPs are required for selecting the right peer */ - if (ovpn->mode == OVPN_MODE_MP && - vpn_addr4.s_addr == htonl(INADDR_ANY) && - ipv6_addr_any(&vpn_addr6)) { - NL_SET_ERR_MSG_FMT_MOD(info->extack, - "at least one VPN IP must be configured in MP mode"); - ret = -EINVAL; - goto unlock; + if (ovpn->mode == OVPN_MODE_MP) { + ret = ovpn_nl_peer_check_vpn_addrs(&vpn_addr4, &vpn_addr6, + info); + if (ret < 0) + goto unlock; } ret = ovpn_nl_peer_modify(peer, info, attrs); From 006208026819d5e9e5ec07b3e73d95960a059327 Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Fri, 28 Aug 2026 15:00:10 +0200 Subject: [PATCH 2104/2366] selftests: ovpn: validate peer VPN addresses Exercise peer VPN address validation through both peer creation and update. Check missing, unspecified, duplicate, multicast, broadcast, loopback, IPv4-compatible and IPv4-mapped addresses. Temporarily configure a peer with both address families to verify that either family can be cleared while the other remains configured, then restore the original addresses before running the existing traffic tests. Extend ovpn-cli peer updates with an optional VPN address and preserve peer creation errors so the negative tests can observe rejected netlink requests. Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- tools/testing/selftests/net/ovpn/common.sh | 13 ++++ tools/testing/selftests/net/ovpn/ovpn-cli.c | 54 ++++++++++----- tools/testing/selftests/net/ovpn/test.sh | 75 ++++++++++++++++++++- 3 files changed, 123 insertions(+), 19 deletions(-) diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh index 2d844eb3aa6e..5e9c81e885e6 100644 --- a/tools/testing/selftests/net/ovpn/common.sh +++ b/tools/testing/selftests/net/ovpn/common.sh @@ -136,6 +136,19 @@ ovpn_create_ns() { ip netns add "ovpn_peer${1}" } +ovpn_peer_vpn_addr() { + local peer="$1" + local file + + if [ "${OVPN_PROTO}" == "UDP" ]; then + file="${OVPN_UDP_PEERS_FILE}" + else + file="${OVPN_TCP_PEERS_FILE}" + fi + + awk -v peer="${peer}" '$1 == peer {print $NF; exit}' "${file}" +} + ovpn_setup_ns() { local peer="ovpn_peer${1}" local server_ns="ovpn_peer0" diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index f4effa7580c0..3b612a8a18fe 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -650,6 +650,26 @@ static int ovpn_connect(struct ovpn_ctx *ovpn) return ret; } +static int ovpn_nl_put_vpn_addr(struct nl_msg *msg, + const struct ovpn_ctx *ovpn) +{ + if (!ovpn->peer_ip_set) + return 0; + + switch (ovpn->peer_ip.in4.sin_family) { + case AF_INET: + return nla_put_u32(msg, OVPN_A_PEER_VPN_IPV4, + ovpn->peer_ip.in4.sin_addr.s_addr); + case AF_INET6: + return nla_put(msg, OVPN_A_PEER_VPN_IPV6, + sizeof(struct in6_addr), + &ovpn->peer_ip.in6.sin6_addr); + default: + fprintf(stderr, "Invalid family for peer address\n"); + return -EAFNOSUPPORT; + } +} + static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp) { struct nlattr *attr; @@ -691,22 +711,9 @@ static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp) } } - if (ovpn->peer_ip_set) { - switch (ovpn->peer_ip.in4.sin_family) { - case AF_INET: - NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_VPN_IPV4, - ovpn->peer_ip.in4.sin_addr.s_addr); - break; - case AF_INET6: - NLA_PUT(ctx->nl_msg, OVPN_A_PEER_VPN_IPV6, - sizeof(struct in6_addr), - &ovpn->peer_ip.in6.sin6_addr); - break; - default: - fprintf(stderr, "Invalid family for peer address\n"); - goto nla_put_failure; - } - } + ret = ovpn_nl_put_vpn_addr(ctx->nl_msg, ovpn); + if (ret) + goto nla_put_failure; nla_nest_end(ctx->nl_msg, attr); @@ -732,6 +739,10 @@ static int ovpn_set_peer(struct ovpn_ctx *ovpn) ovpn->keepalive_interval); NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_TIMEOUT, ovpn->keepalive_timeout); + + ret = ovpn_nl_put_vpn_addr(ctx->nl_msg, ovpn); + if (ret) + goto nla_put_failure; nla_nest_end(ctx->nl_msg, attr); ret = ovpn_nl_msg_send(ctx, NULL); @@ -1730,13 +1741,14 @@ static void usage(const char *cmd) fprintf(stderr, "\tmark: socket FW mark value\n"); fprintf(stderr, - "* set_peer : set peer attributes\n"); + "* set_peer [vpnaddr]: set peer attributes\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); fprintf(stderr, "\tpeer_id: peer ID of the peer to modify\n"); fprintf(stderr, "\tkeepalive_interval: interval for sending ping messages\n"); fprintf(stderr, "\tkeepalive_timeout: time after which a peer is timed out\n"); + fprintf(stderr, "\tvpnaddr: peer VPN IP\n"); fprintf(stderr, "* del_peer : delete peer\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); @@ -2090,6 +2102,8 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn) return ret; ret = ovpn_new_peer(ovpn, false); + if (ret < 0) + return ret; ovpn_waitbg(); break; case CMD_NEW_MULTI_PEER: @@ -2331,6 +2345,12 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) "keepalive interval value out of range\n"); return -1; } + + if (argc > 6) { + ret = ovpn_parse_remote(ovpn, NULL, NULL, argv[6]); + if (ret < 0) + return -1; + } break; case CMD_DEL_PEER: if (argc < 4) diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh index 9b5610837032..392109d5e14e 100755 --- a/tools/testing/selftests/net/ovpn/test.sh +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -56,6 +56,76 @@ ovpn_prepare_network() { done } +ovpn_new_test_peer() { + local peer_id="$1" + + shift + ip netns exec ovpn_peer0 "${OVPN_CLI}" new_peer tun0 \ + "${peer_id}" none 65000 10.10.1.2 1 "$@" +} + +ovpn_set_peer_vpn_addr() { + ip netns exec ovpn_peer0 "${OVPN_CLI}" set_peer tun0 \ + "$1" 60 120 "$2" +} + +ovpn_run_vpn_addr_validation() { + local addr + local peer1_addr4 + local test_peer_id=$((OVPN_NUM_PEERS + 1)) + local test_peer_addr6="2001:db8::2" + # Do not include 0.0.0.0 or :: here. They are invalid on creation, but + # clear one address family on update and are valid if the other remains. + local -a invalid_addrs=( + "127.0.0.1" + "224.0.0.1" + "255.255.255.255" + "::1" + "::192.0.2.1" + "::ffff:192.0.2.1" + "ff02::1" + ) + + peer1_addr4=$(ovpn_peer_vpn_addr 1) + + ovpn_cmd_fail "reject peer without VPN address" \ + ovpn_new_test_peer "${test_peer_id}" + + for addr in "0.0.0.0" "::" "${invalid_addrs[@]}"; do + ovpn_cmd_fail "reject new peer VPN address ${addr}" \ + ovpn_new_test_peer "${test_peer_id}" "${addr}" + done + + ovpn_cmd_fail "reject duplicate IPv4 address on peer creation" \ + ovpn_new_test_peer "${test_peer_id}" "${peer1_addr4}" + ovpn_cmd_fail "reject clearing the last peer VPN address" \ + ovpn_set_peer_vpn_addr 1 0.0.0.0 + + for addr in "${invalid_addrs[@]}"; do + ovpn_cmd_fail "reject updated peer VPN address ${addr}" \ + ovpn_set_peer_vpn_addr 1 "${addr}" + done + + ovpn_cmd_fail "reject duplicate IPv4 address on peer update" \ + ovpn_set_peer_vpn_addr 2 "${peer1_addr4}" + + ovpn_cmd_ok "add peer IPv6 address" \ + ovpn_set_peer_vpn_addr 1 "${test_peer_addr6}" + ovpn_cmd_fail "reject duplicate IPv6 address on peer creation" \ + ovpn_new_test_peer "${test_peer_id}" "${test_peer_addr6}" + ovpn_cmd_fail "reject duplicate IPv6 address on peer update" \ + ovpn_set_peer_vpn_addr 2 "${test_peer_addr6}" + + ovpn_cmd_ok "clear peer IPv4 address" \ + ovpn_set_peer_vpn_addr 1 0.0.0.0 + ovpn_cmd_fail "reject clearing the remaining peer IPv6 address" \ + ovpn_set_peer_vpn_addr 1 :: + ovpn_cmd_ok "restore peer IPv4 address" \ + ovpn_set_peer_vpn_addr 1 "${peer1_addr4}" + ovpn_cmd_ok "clear peer IPv6 address" \ + ovpn_set_peer_vpn_addr 1 :: +} + ovpn_run_basic_traffic() { local p local header1 @@ -293,15 +363,16 @@ trap ovpn_stage_err ERR ktap_print_header if [ "${OVPN_FLOAT}" == "1" ]; then - ktap_set_plan 13 + ktap_set_plan 14 else - ktap_set_plan 12 + ktap_set_plan 13 fi ovpn_cleanup modprobe -q ovpn || true ovpn_run_stage "setup network topology" ovpn_prepare_network +ovpn_run_stage "validate peer VPN addresses" ovpn_run_vpn_addr_validation ovpn_run_stage "run baseline data traffic" ovpn_run_basic_traffic ovpn_run_stage "run LAN traffic behind peer1" ovpn_run_lan_traffic [ "${OVPN_FLOAT}" == "1" ] && ovpn_run_stage "run floating peer checks" \ From e6bae5034ef4a61f3f062b18140d4f58c8b9a149 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 18 Sep 2026 14:40:32 +0200 Subject: [PATCH 2105/2366] ata: libata-core: Extend Samsung LPM quirk to AMD controllers A Samsung SSD 870 QVO 8TB connected to an AMD 600 Series chipset SATA controller is reported to time out on STANDBY IMMEDIATE during system suspend with med_power_with_dipm enabled. The command completes when using max_performance instead. The existing Samsung LPM quirk only matches ATI controllers, leaving AMD controllers unaffected. Rename it to ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD and extend the vendor check to AMD for the same Samsung SSD model patterns. Keep LPM behavior unchanged for other controller vendors, including Intel. Leave ATA_QUIRK_NO_NCQ_ON_ATI restricted to ATI, since the reported AMD issue concerns LPM rather than NCQ. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221986 Reviewed-by: Damien Le Moal Reviewed-by: Mario Limonciello (AMD) > --- Link: https://lore.kernel.org/r/20260918124030.1962773-5-cassel@kernel.org Signed-off-by: Niklas Cassel --- drivers/ata/libata-core.c | 15 ++++++++------- include/linux/libata.h | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index f482c0a6d7e9..92233fb25051 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -2972,9 +2972,10 @@ static void ata_dev_config_lpm(struct ata_device *dev) (dev->id[ATA_ID_SATA_CAPABILITY] & 0xe) == 0x2) dev->quirks |= ATA_QUIRK_NOLPM; - /* ATI specific quirk */ - if ((dev->quirks & ATA_QUIRK_NO_LPM_ON_ATI) && - ata_dev_check_adapter(dev, PCI_VENDOR_ID_ATI)) + /* ATI and AMD specific quirk */ + if ((dev->quirks & ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD) && + (ata_dev_check_adapter(dev, PCI_VENDOR_ID_ATI) || + ata_dev_check_adapter(dev, PCI_VENDOR_ID_AMD))) dev->quirks |= ATA_QUIRK_NOLPM; } @@ -4136,7 +4137,7 @@ static const char * const ata_quirk_names[] = { [__ATA_QUIRK_MAX_SEC] = "maxsec", [__ATA_QUIRK_MAX_TRIM_128M] = "maxtrim128m", [__ATA_QUIRK_NO_NCQ_ON_ATI] = "noncqonati", - [__ATA_QUIRK_NO_LPM_ON_ATI] = "nolpmonati", + [__ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD] = "nolpmonatiandamd", [__ATA_QUIRK_NO_ID_DEV_LOG] = "noiddevlog", [__ATA_QUIRK_NO_LOG_DIR] = "nologdir", [__ATA_QUIRK_NO_FUA] = "nofua", @@ -4420,15 +4421,15 @@ static const struct ata_dev_quirks_entry __ata_dev_quirks[] = { { "Samsung SSD 860*", NULL, ATA_QUIRK_NO_NCQ_TRIM | ATA_QUIRK_ZERO_AFTER_TRIM | ATA_QUIRK_NO_NCQ_ON_ATI | - ATA_QUIRK_NO_LPM_ON_ATI }, + ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD }, { "Samsung SSD 870*", NULL, ATA_QUIRK_NO_NCQ_TRIM | ATA_QUIRK_ZERO_AFTER_TRIM | ATA_QUIRK_NO_NCQ_ON_ATI | - ATA_QUIRK_NO_LPM_ON_ATI }, + ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD }, { "SAMSUNG*MZ7LH*", NULL, ATA_QUIRK_NO_NCQ_TRIM | ATA_QUIRK_ZERO_AFTER_TRIM | ATA_QUIRK_NO_NCQ_ON_ATI | - ATA_QUIRK_NO_LPM_ON_ATI }, + ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD }, { "FCCT*M500*", NULL, ATA_QUIRK_NO_NCQ_TRIM | ATA_QUIRK_ZERO_AFTER_TRIM }, diff --git a/include/linux/libata.h b/include/linux/libata.h index 313e96173b19..48bde275968d 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -76,7 +76,7 @@ enum ata_quirks { __ATA_QUIRK_MAX_SEC, /* Limit max sectors */ __ATA_QUIRK_MAX_TRIM_128M, /* Limit max trim size to 128M */ __ATA_QUIRK_NO_NCQ_ON_ATI, /* Disable NCQ on ATI chipset */ - __ATA_QUIRK_NO_LPM_ON_ATI, /* Disable LPM on ATI chipset */ + __ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD, /* Disable LPM on ATI and AMD chipsets */ __ATA_QUIRK_NO_ID_DEV_LOG, /* Identify device log missing */ __ATA_QUIRK_NO_LOG_DIR, /* Do not read log directory */ __ATA_QUIRK_NO_FUA, /* Do not use FUA */ @@ -115,7 +115,7 @@ enum { ATA_QUIRK_MAX_SEC = BIT_ULL(__ATA_QUIRK_MAX_SEC), ATA_QUIRK_MAX_TRIM_128M = BIT_ULL(__ATA_QUIRK_MAX_TRIM_128M), ATA_QUIRK_NO_NCQ_ON_ATI = BIT_ULL(__ATA_QUIRK_NO_NCQ_ON_ATI), - ATA_QUIRK_NO_LPM_ON_ATI = BIT_ULL(__ATA_QUIRK_NO_LPM_ON_ATI), + ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD = BIT_ULL(__ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD), ATA_QUIRK_NO_ID_DEV_LOG = BIT_ULL(__ATA_QUIRK_NO_ID_DEV_LOG), ATA_QUIRK_NO_LOG_DIR = BIT_ULL(__ATA_QUIRK_NO_LOG_DIR), ATA_QUIRK_NO_FUA = BIT_ULL(__ATA_QUIRK_NO_FUA), From 88a0474d92ba102fff860db3cae9da87a0964fbf Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 18 Sep 2026 14:40:33 +0200 Subject: [PATCH 2106/2366] ata: libata: Correct libata.force parameter documentation Align the documented libata.force options with their implementation. The force table accepts PIO modes 0 through 6, not mode 7, and ncqati controls NCQ generally rather than only queued TRIM. The max_sec_1024 and max_sec_lba48 options only set transfer size limits. Remove the misleading claim that they can also clear them. Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20260918124030.1962773-6-cassel@kernel.org Signed-off-by: Niklas Cassel --- Documentation/admin-guide/kernel-parameters.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 33cd30996e47..24459f7ff810 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -3491,7 +3491,7 @@ Kernel parameters * SATA link speed limit: 1.5Gbps or 3.0Gbps. - * Transfer mode: pio[0-7], mwdma[0-4] and udma[0-7]. + * Transfer mode: pio[0-6], mwdma[0-4] and udma[0-7]. udma[/][16,25,33,44,66,100,133] notation is also allowed. @@ -3509,7 +3509,7 @@ Kernel parameters * [no]ncqtrim: Enable or disable queued DSM TRIM. - * [no]ncqati: Enable or disable NCQ trim on ATI chipset. + * [no]ncqati: Enable or disable NCQ on ATI chipsets. * [no]trim: Enable or disable (unqueued) TRIM. @@ -3540,11 +3540,9 @@ Kernel parameters * max_sec_128: Set transfer size limit to 128 sectors. - * max_sec_1024: Set or clear transfer size limit to - 1024 sectors. + * max_sec_1024: Set transfer size limit to 1024 sectors. - * max_sec_lba48: Set or clear transfer size limit to - 65535 sectors. + * max_sec_lba48: Set transfer size limit to 65535 sectors. * external: Mark port as external (hotplug-capable). From ea4debcd8016f73c5dee3a29250a3d7977f015ef Mon Sep 17 00:00:00 2001 From: Sk Anirban Date: Wed, 9 Sep 2026 17:19:32 +0530 Subject: [PATCH 2107/2366] drm/xe/gt_throttle: Report power brake as a throttle reason on CRI CRI defines bit 5 of the perf limit reasons register as a power brake (PWRBRK) indicator. Add PWRBRK_MASK and a reason_pwrbrk sysfs attribute for CRI in place of reason_ratl. Signed-off-by: Sk Anirban Fixes: 8578e6d0546c ("drm/xe/gt_throttle: Drop individual show functions") Reviewed-by: Raag Jadav Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260909114931.1039331-2-sk.anirban@intel.com (cherry picked from commit e199c851c0ab608a0ca89e7be1756a1461b41a2c) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/regs/xe_gt_regs.h | 1 + drivers/gpu/drm/xe/xe_gt_throttle.c | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/regs/xe_gt_regs.h b/drivers/gpu/drm/xe/regs/xe_gt_regs.h index 08251c7a1a4b..247a736a54aa 100644 --- a/drivers/gpu/drm/xe/regs/xe_gt_regs.h +++ b/drivers/gpu/drm/xe/regs/xe_gt_regs.h @@ -651,6 +651,7 @@ #define MEM_THERMAL_MASK REG_BIT(2) #define VR_THERMAL_MASK REG_BIT(3) #define ICCMAX_MASK REG_BIT(4) +#define PWRBRK_MASK REG_BIT(5) #define SOC_AVG_THERMAL_MASK REG_BIT(6) #define FASTVMODE_MASK REG_BIT(7) #define PSYS_PL1_MASK REG_BIT(12) diff --git a/drivers/gpu/drm/xe/xe_gt_throttle.c b/drivers/gpu/drm/xe/xe_gt_throttle.c index 1e7e3a31aa69..c0af5484611d 100644 --- a/drivers/gpu/drm/xe/xe_gt_throttle.c +++ b/drivers/gpu/drm/xe/xe_gt_throttle.c @@ -39,7 +39,7 @@ * - ``reason_mem_thermal``: Memory thermal * - ``reason_vr_thermal``: VR thermal * - ``reason_iccmax``: ICCMAX - * - ``reason_ratl``: RATL thermal algorithm + * - ``reason_pwrbrk``: Power brake * - ``reason_soc_avg_thermal``: SoC average temp * - ``reason_fastvmode``: VR is hitting FastVMode * - ``reason_psys_pl1``: PSYS PL1 @@ -200,6 +200,7 @@ static THROTTLE_ATTR_RO(reason_psys_pl1, PSYS_PL1_MASK); static THROTTLE_ATTR_RO(reason_psys_pl2, PSYS_PL2_MASK); static THROTTLE_ATTR_RO(reason_p0_freq, P0_FREQ_MASK); static THROTTLE_ATTR_RO(reason_psys_crit, PSYS_CRIT_MASK); +static THROTTLE_ATTR_RO(reason_pwrbrk, PWRBRK_MASK); static struct attribute *cri_throttle_attrs[] = { /* Common */ @@ -209,12 +210,12 @@ static struct attribute *cri_throttle_attrs[] = { &attr_reason_pl2.attr.attr, &attr_reason_pl4.attr.attr, &attr_reason_prochot.attr.attr, - &attr_reason_ratl.attr.attr, /* CRI */ &attr_reason_vr_thermal.attr.attr, &attr_reason_soc_thermal.attr.attr, &attr_reason_mem_thermal.attr.attr, &attr_reason_iccmax.attr.attr, + &attr_reason_pwrbrk.attr.attr, &attr_reason_soc_avg_thermal.attr.attr, &attr_reason_fastvmode.attr.attr, &attr_reason_psys_pl1.attr.attr, From c7a925c84704ec431598f9411dd89c0e16ae34ae Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Mon, 14 Sep 2026 21:53:18 +0000 Subject: [PATCH 2108/2366] drm/xe/tlb_inval: Treat wedged-device invalidations as complete A TLB invalidation issued on a wedged device fails with -ENOTRECOVERABLE. xe_tlb_inval_issue() squashes only -ECANCELED, so the error reaches callers that treat it as unexpected and WARN, tainting the kernel on a wedge that was deliberately caused: ggtt_invalidate_gt_tlb() drivers/gpu/drm/xe/xe_ggtt.c xe_svm_invalidate() drivers/gpu/drm/xe/xe_svm.c xe_bo_trigger_rebind() drivers/gpu/drm/xe/xe_bo.c xe_vma_userptr_do_inval() drivers/gpu/drm/xe/xe_userptr.c igt@xe_exec_reset@gt-reset-fault-injection hits the GGTT one, turning an otherwise passing run into an abort: *ERROR* SIGID=102 FATAL (-EIO) WEDGED: Device declared wedged! Tile0: GT1: Failed to invalidate GGTT (-ENOTRECOVERABLE) WARNING: drivers/gpu/drm/xe/xe_ggtt.c:588 at ggtt_invalidate_gt_tlb Workqueue: xe-guc-destroy-wq __guc_exec_queue_destroy_async [xe] ggtt_node_remove+0xe3/0x100 [xe] xe_ggtt_remove_bo+0x89/0x2c0 [xe] xe_ttm_bo_destroy+0xcb/0x330 [xe] ... xe_lrc_destroy+0x74/0x90 [xe] xe_exec_queue_fini+0x2d/0x60 [xe] -ECANCELED and -ENOTRECOVERABLE mean the same thing at this layer: the message was dropped rather than delivered, and the fence has already been signalled before the error is returned, so there is nothing left to wait for. Squash both. A wedged device is only recovered by a fresh initialisation, so the error return in xe_bo_trigger_rebind() becomes unreachable by design. v2: fix all invalidation paths. (Sashiko) Fixes: 50fa9acac26f ("drm/xe/guc: distinguish wedged from recoverable cancellation") Assisted-by: Claude:claude-opus-5 Cc: Sk Anirban Cc: Matthew Brost Signed-off-by: Shuicheng Lin Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260914215318.200603-1-shuicheng.lin@intel.com (cherry picked from commit af14e3705345cb57c53b237169873233052a5c16) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_tlb_inval.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_tlb_inval.c b/drivers/gpu/drm/xe/xe_tlb_inval.c index bbd21d393062..67b205bf1291 100644 --- a/drivers/gpu/drm/xe/xe_tlb_inval.c +++ b/drivers/gpu/drm/xe/xe_tlb_inval.c @@ -280,7 +280,8 @@ static void xe_tlb_inval_fence_prep(struct xe_tlb_inval_fence *fence) xe_tlb_inval_fence_signal_unlocked((__fence)); \ mutex_unlock(&(__tlb_inval)->seqno_lock); \ \ - __ret == -ECANCELED ? 0 : __ret; \ + /* Undelivered: fence already signalled, report done */ \ + (__ret == -ECANCELED || __ret == -ENOTRECOVERABLE) ? 0 : __ret; \ }) /** From be1df8badae513e01d9575398438716cfae18655 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Thu, 17 Sep 2026 13:31:58 -0700 Subject: [PATCH 2109/2366] drm/xe: Keep walking on SVM eviction failure The desired behavior for SVM eviction failures, which can occur due to various uncontrollable races, is for TTM to continue walking the LRU list and look for another eviction candidate. This is expressed by returning -ENOSPC from the ->move() callback. Adjust the SVM eviction failure path because of races in ->move() to return -ENOSPC so that TTM continues searching for another buffer to evict. Fixes: 3ca608dc7561 ("drm/xe: Basic SVM BO eviction") Cc: stable@vger.kernel.org Signed-off-by: Matthew Brost Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260917203158.292823-1-matthew.brost@intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 36a86c23588b8f57c9d20feb4cf5a2ab27e3baba) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_bo.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index dde309821237..b4921a627ff3 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -1037,6 +1037,13 @@ static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict, } else { drm_dbg(&xe->drm, "Evict system allocator BO failed=%pe\n", ERR_PTR(ret)); + /* + * The semantic we want upon SVM eviction failure + * because of racing access is keep walking for + * eviction, which is -ENOSPC. + */ + if (ret == -EBUSY) + ret = -ENOSPC; } goto out; From 24a22fb3c731474b68e986af6804db450fb88617 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Fri, 18 Sep 2026 14:10:35 +0100 Subject: [PATCH 2110/2366] drm/xe/vm: nuke PTs only after unlinking contested VMAs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In xe_vm_close_and_put(), external-BO VMAs are queued on the contested list for deferred destruction via xe_vma_destroy_unlocked(). However, xe_vm_pt_destroy() was previously invoked before processing contested VMAs, destroying vm->pt_root while those VMAs were still linked to their respective buffer objects (vm_bo->list.gpuva). If a concurrent thread evicts one of those shared buffer objects, xe_bo_trigger_rebind() holding only bo->resv walks the BO's VMAs and, in fault mode, calls xe_vm_invalidate_vma() -> xe_pt_zap_ptes(). Because vm->pt_root[tile->id] is already NULL, dereferencing pt->level causes a NULL ptr deref. Fix this by deferring xe_vm_free_scratch() and xe_vm_pt_destroy() until after all contested VMAs have been unlinked and destroyed. User is reporting hitting a NULL ptr deref in xe_pt_zap_ptes(), which could be explained by this race. Assisted-by: LLM Fixes: b06d47be7c83 ("drm/xe: Port Xe to GPUVA") Link: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/9290 Signed-off-by: Matthew Auld Cc: Thomas Hellström Cc: Matthew Brost Cc: # v6.12+ Reviewed-by: Thomas Hellström Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260918131034.598078-2-matthew.auld@intel.com (cherry picked from commit c2863648959489767f08892fd6e90577d2ea0b6a) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_vm.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index 23952ad8951e..ef20e205a734 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -1947,21 +1947,13 @@ void xe_vm_close_and_put(struct xe_vm *vm) vma->gpuva.flags |= XE_VMA_DESTROYED; } - /* - * All vm operations will add shared fences to resv. - * The only exception is eviction for a shared object, - * but even so, the unbind when evicted would still - * install a fence to resv. Hence it's safe to - * destroy the pagetables immediately. - */ - xe_vm_free_scratch(vm); - xe_vm_pt_destroy(vm); xe_vm_unlock(vm); /* - * VM is now dead, cannot re-add nodes to vm->vmas if it's NULL - * Since we hold a refcount to the bo, we can remove and free - * the members safely without locking. + * Unlink and destroy all contested external-BO VMAs before destroying + * the page tables. Otherwise, concurrent eviction holding only bo->resv + * can walk the BO's VMAs and attempt to invalidate/zap page tables that + * have already been freed. */ list_for_each_entry_safe(vma, next_vma, &contested, combined_links.destroy) { @@ -1969,6 +1961,11 @@ void xe_vm_close_and_put(struct xe_vm *vm) xe_vma_destroy_unlocked(vma); } + xe_vm_lock(vm, false); + xe_vm_free_scratch(vm); + xe_vm_pt_destroy(vm); + xe_vm_unlock(vm); + xe_svm_fini(vm); up_write(&vm->lock); From f0ca020cbb9bb7f3f4ea8ba1dfcf30a282aec91e Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 22:17:38 +0000 Subject: [PATCH 2111/2366] Bluetooth: bnep: fix out-of-bounds reads on short RX/TX frames and control fallthrough Fix multiple out-of-bounds reads in Bluetooth BNEP frame processing: 1. In bnep_rx_frame() and bnep_ctrl_frame() (net/bluetooth/bnep/core.c), use pskb_may_pull() to verify the BNEP header, control type byte, filter count, and extension headers exist before reading them, and return 0 after handling BNEP_CONTROL instead of falling through to Ethernet frame submission when no extension headers follow. 2. In bnep_net_xmit() (net/bluetooth/bnep/netdev.c), verify skb->len >= ETH_HLEN with pskb_may_pull() before reading the 14-byte Ethernet header to prevent an out-of-bounds heap read and infoleak on short AF_PACKET TX frames. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Assisted-by: LLM Signed-off-by: Hui Peng Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/bnep/core.c | 17 ++++++++++++++++- net/bluetooth/bnep/netdev.c | 8 +++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/bnep/core.c b/net/bluetooth/bnep/core.c index f7d88c33e23e..ad24d2486665 100644 --- a/net/bluetooth/bnep/core.c +++ b/net/bluetooth/bnep/core.c @@ -270,9 +270,14 @@ static int bnep_rx_extension(struct bnep_session *s, struct sk_buff *skb) BT_DBG("type 0x%x len %u", h->type, h->len); + if (skb->len < h->len) { + err = -EILSEQ; + break; + } + switch (h->type & BNEP_TYPE_MASK) { case BNEP_EXT_CONTROL: - bnep_rx_control(s, skb->data, skb->len); + bnep_rx_control(s, skb->data, h->len); break; default: @@ -373,6 +378,11 @@ static int bnep_rx_frame(struct bnep_session *s, struct sk_buff *skb) goto badframe; } + if ((type & BNEP_TYPE_MASK) == BNEP_CONTROL) { + kfree_skb(skb); + return 0; + } + /* Strip 802.1p header */ if (ntohs(s->eh.h_proto) == ETH_P_8021Q) { if (!skb_pull(skb, 4)) @@ -451,6 +461,11 @@ static int bnep_tx_frame(struct bnep_session *s, struct sk_buff *skb) goto send; } + if (skb->len < ETH_HLEN) { + kfree_skb(skb); + return 0; + } + iv[il++] = (struct kvec) { &type, 1 }; len++; diff --git a/net/bluetooth/bnep/netdev.c b/net/bluetooth/bnep/netdev.c index ee1e39a3daff..b451ef457741 100644 --- a/net/bluetooth/bnep/netdev.c +++ b/net/bluetooth/bnep/netdev.c @@ -166,6 +166,12 @@ static netdev_tx_t bnep_net_xmit(struct sk_buff *skb, BT_DBG("skb %p, dev %p", skb, dev); + if (!pskb_may_pull(skb, ETH_HLEN)) { + dev->stats.tx_dropped++; + kfree_skb(skb); + return NETDEV_TX_OK; + } + #ifdef CONFIG_BT_BNEP_MC_FILTER if (bnep_net_mc_filter(skb, s)) { kfree_skb(skb); @@ -218,7 +224,7 @@ void bnep_net_setup(struct net_device *dev) dev->addr_len = ETH_ALEN; ether_setup(dev); - dev->min_mtu = 0; + dev->min_mtu = ETH_MIN_MTU; dev->max_mtu = ETH_MAX_MTU; dev->priv_flags &= ~IFF_TX_SKB_SHARING; dev->netdev_ops = &bnep_netdev_ops; From 37a11129345337efd6eef8e62b03b6348cd0dd8b Mon Sep 17 00:00:00 2001 From: Ravindra Date: Tue, 15 Sep 2026 10:42:15 +0530 Subject: [PATCH 2112/2366] Bluetooth: btintel_pcie: validate device-supplied DMA indices In btintel_pcie_msix_rx_handle(), the driver processes RX completion descriptors (urbd1) written by the PCIe device into DMA-coherent memory. urbd1->frbd_tag (a 16-bit field fully controlled by the device firmware via DMA) is used directly as an array index into rxq->bufs[] without any bounds check. rxq->bufs[] has only BTINTEL_PCIE_RX_DESCS_COUNT (64) entries, while frbd_tag can be any value 0-65535. A malicious or malfunctioning device can write an out-of-range frbd_tag, causing the driver to dereference an out-of-bounds data_buf pointer. Additionally, cr_hia is read from a DMA-shared index array also writable by the device; if the device sets cr_hia >= rxq->count, the while-loop never terminates because cr_tia is wrapped via modulo rxq->count and can never equal an out-of-range cr_hia. Add bounds validation for cr_hia and frbd_tag in the RX path, and cr_hia in the TX path. Log invalid values with bt_dev_err before returning. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Signed-off-by: Ravindra Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 6e6e2b19815c..2819e001797b 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1099,6 +1099,11 @@ static void btintel_pcie_msix_tx_handle(struct btintel_pcie_data *data) txq = &data->txq; + if (cr_hia >= txq->count) { + bt_dev_err(data->hdev, "TXQ: invalid cr_hia %u", cr_hia); + return; + } + while (cr_tia != cr_hia) { data->tx_wait_done = true; wake_up(&data->tx_wait_q); @@ -1650,6 +1655,11 @@ static void btintel_pcie_msix_rx_handle(struct btintel_pcie_data *data) rxq = &data->rxq; + if (cr_hia >= rxq->count) { + bt_dev_err(hdev, "RXQ: invalid cr_hia %u", cr_hia); + return; + } + /* The firmware sends multiple CD in a single MSI-X and it needs to * process all received CDs in this interrupt. */ @@ -1657,6 +1667,12 @@ static void btintel_pcie_msix_rx_handle(struct btintel_pcie_data *data) urbd1 = &rxq->urbd1s[cr_tia]; ipc_print_urbd1(data->hdev, urbd1, cr_tia); + if (urbd1->frbd_tag >= rxq->count) { + bt_dev_err(hdev, "RXQ: invalid frbd_tag %u", + urbd1->frbd_tag); + return; + } + buf = &rxq->bufs[urbd1->frbd_tag]; if (!buf) { bt_dev_err(hdev, "RXQ: failed to get the DMA buffer for %d", From 46f8ffd0a1f1eb6cbc94946a92c11ef601e228a1 Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 11:25:18 +0000 Subject: [PATCH 2113/2366] Bluetooth: RFCOMM: fix NULL dereference of dlc->session in RFCOMM_CONNINFO The RFCOMM_CONNINFO getsockopt handler accepts a socket that is not connected as long as deferred setup is enabled: if (sk->sk_state != BT_CONNECTED && !rfcomm_pi(sk)->dlc->defer_setup) { err = -ENOTCONN; break; } l2cap_sk = rfcomm_pi(sk)->dlc->session->sock->sk; dlc->defer_setup is set in rfcomm_sock_init() when rfcomm_connect_ind() creates a child socket for an incoming connection on a listening socket that has BT_DEFER_SETUP enabled. It is never cleared afterwards. The session, however, can go away underneath it. rfcomm_recv_disc() forces the dlc state before tearing it down: d->state = BT_CLOSED; __rfcomm_dlc_close(d, err); The RFCOMM_DEFER_SETUP early return in __rfcomm_dlc_close() only covers BT_CONNECT, BT_CONFIG, BT_OPEN and BT_CONNECT2, so with the state already BT_CLOSED that switch does not match and the function falls through to rfcomm_dlc_unlink(), which sets d->session = NULL, while d->defer_setup stays 1. A getsockopt(SOL_RFCOMM, RFCOMM_CONNINFO) on the accepted socket after that point therefore skips the -ENOTCONN path -- sk->sk_state is BT_CLOSED, but dlc->defer_setup is still set -- and dereferences the NULL session. No race is needed: once the DISC has been processed, the dereference is unconditional. Reproduced on a KASAN kernel under QEMU with a BR/EDR peer emulated over /dev/vhci: the peer brings up an ACL link, opens L2CAP on the RFCOMM PSM, starts a session and sends SABM for a channel bound with BT_DEFER_SETUP, and sends DISC for that dlci after the socket has been accepted. getsockopt(SOL_RFCOMM, RFCOMM_CONNINFO) on the accepted socket then hits: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000002: 0000 [#1] SMP KASAN PTI KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017] CPU: 1 UID: 0 PID: 150 Comm: init Tainted: G B 7.3.0-rc3-g5dd1818b15d9 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996) RIP: 0010:rfcomm_sock_getsockopt+0x529/0x780 Call Trace: do_sock_getsockopt+0x3ad/0x7d0 __sys_getsockopt+0x10e/0x1b0 __x64_sys_getsockopt+0xc2/0x160 do_syscall_64+0xda/0x4b0 entry_SYSCALL_64_after_hwframe+0x77/0x7f 0x10 is the offset of sock in struct rfcomm_session; rfcomm_sock_getsockopt_old() is inlined into rfcomm_sock_getsockopt(). Commit 43a556b2fd43 ("Bluetooth: RFCOMM: take rfcomm_mutex for the deferred setup accept") fixed the same "a remote DISC clears the session while deferred setup is still flagged" problem in rfcomm_dlc_accept(); this is the remaining instance of it, in the getsockopt path. Deferred setup only leaves a socket usable here once it has reached BT_CONNECT2, so restrict the exception to that state and check that a session is actually present before following it. Fixes: bb23c0ab8246 ("Bluetooth: Add support for deferring RFCOMM connection setup") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/sock.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index e2486bc11cbc..fb924d0e34ec 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -786,8 +786,10 @@ static int rfcomm_sock_getsockopt_old(struct socket *sock, int optname, break; case RFCOMM_CONNINFO: - if (sk->sk_state != BT_CONNECTED && - !rfcomm_pi(sk)->dlc->defer_setup) { + if ((sk->sk_state != BT_CONNECTED && + !(sk->sk_state == BT_CONNECT2 && + rfcomm_pi(sk)->dlc->defer_setup)) || + !rfcomm_pi(sk)->dlc->session) { err = -ENOTCONN; break; } From 6d91041bb38b97e2feb625123cc0529d7b83a0e1 Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 11:25:14 +0000 Subject: [PATCH 2114/2366] Bluetooth: RFCOMM: Reject short EA=0 frames in rfcomm_recv_frame() While rfcomm_recv_frame() verifies that skb->len is at least sizeof(*hdr) + 1 (4 bytes: 3-byte header + 1-byte FCS), an RFCOMM frame with an extended 2-byte length field (!__test_ea(hdr->len)) has a 4-byte header plus a 1-byte FCS (5 bytes minimum, sizeof(*hdr) + 2). When a 4-byte RFCOMM frame with EA == 0 arrives: 1. The initial skb->len < sizeof(*hdr) + 1 check passes (4 < 4 is false). 2. Trimming the FCS byte decrements skb->len to 3. 3. If __check_fcs() succeeds, skb_pull(skb, 4) fails (4 > 3) and returns NULL without advancing skb->data. 4. Because the return value of skb_pull() is ignored, the un-pulled 3-byte struct rfcomm_hdr remains at skb->data and is either queued as application payload via rfcomm_recv_data() or parsed as a multiplexer control command via rfcomm_recv_mcc() on DLCI 0. Fix this by extending the length check in rfcomm_recv_frame() to also require skb->len >= sizeof(*hdr) + 2 when !__test_ea(hdr->len). Fixes: b230e5bf501c ("Bluetooth: RFCOMM: validate skb length in rfcomm_recv_frame") Assisted-by: LLM Signed-off-by: Hui Peng Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index f7463f092283..d91e2a6ee26c 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -1817,7 +1817,8 @@ static struct rfcomm_session *rfcomm_recv_frame(struct rfcomm_session *s, return s; } - if (skb->len < sizeof(*hdr) + 1) { + if (skb->len < sizeof(*hdr) + 1 || + (!__test_ea(hdr->len) && skb->len < sizeof(*hdr) + 2)) { kfree_skb(skb); return s; } From 94b7e3a7e871ae27d4935c76959dfc61829f27f9 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Sat, 19 Sep 2026 22:09:20 +0200 Subject: [PATCH 2115/2366] parisc: Increase kernel stack size to 32kb For 64-bit Linux kernels, increase the default kernel stack size (THREAD_SIZE_ORDER) to 32 kB, in order to avoid kernel crashes which have been triggered recently when building the debian vtk9 package with gcc 17: stackcheck: kworker/u128:0 will most likely overflow kernel stack (sp:179a83af0, stk bottom-top:179a80000-179a84000) Kernel panic - not syncing: low stack detected by irq handler - check messages CPU: 2 UID: 0 PID: 30760 Comm: kworker/u128:0 Tainted: G W 6.18.46-dirty #1 NONE Tainted: [W]=WARN Hardware name: 9000/800/rp3440 Workqueue: writeback wb_workfn (flush-259:0) Backtrace: [<000000004022f050>] show_stack+0x70/0x90 [<000000004022378c>] dump_stack_lvl+0x124/0x190 [<000000004022382c>] dump_stack+0x34/0x48 [<000000004020212c>] vpanic+0x204/0x648 [<00000000402025c4>] panic+0x54/0x58 [<0000000040232230>] do_cpu_irq_mask+0x3f8/0x440 [<0000000040227070>] intr_return+0x0/0xc Signed-off-by: Helge Deller Reported-by: John David Anglin Cc: stable@vger.kernel.org # v6.18+ --- arch/parisc/include/asm/thread_info.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/parisc/include/asm/thread_info.h b/arch/parisc/include/asm/thread_info.h index b283738bb6da..249370706242 100644 --- a/arch/parisc/include/asm/thread_info.h +++ b/arch/parisc/include/asm/thread_info.h @@ -24,7 +24,7 @@ struct thread_info { /* thread information allocation */ -#ifdef CONFIG_IRQSTACKS +#if defined(CONFIG_IRQSTACKS) && !defined(CONFIG_64BIT) #define THREAD_SIZE_ORDER 2 /* PA-RISC requires at least 16k stack */ #else #define THREAD_SIZE_ORDER 3 /* PA-RISC requires at least 32k stack */ From 79a9172f3ab4ad8392c5e5c8944b9b7710ade620 Mon Sep 17 00:00:00 2001 From: Xu Yunxiang Date: Mon, 21 Sep 2026 05:04:21 +0800 Subject: [PATCH 2116/2366] bpf: Reject non-negative offsets in stack_slot_obj_get_spi() bpf_get_spi() computes (-off - 1) / BPF_REG_SIZE using C division, which truncates toward zero. For off == 0, this produces spi 0, the same index used by the valid stack slot at fp-8. stack_slot_obj_get_spi() currently checks alignment and the resulting spi bounds, but does not reject the non-negative offset itself. It can therefore validate a PTR_TO_STACK register holding fp+0 against an iterator stored at fp-8 even though the runtime receives the actual fp+0 pointer. An effectful iterator kfunc can then interpret memory outside the BPF stack as iterator state. Reject non-negative offsets before converting the offset to an spi. All valid stack objects begin at a negative offset from the frame pointer. Fixes: 06accc8779c1 ("bpf: add support for open-coded iterator loops") Signed-off-by: Xu Yunxiang Signed-off-by: Andrii Nakryiko Reviewed-by: Sun Jian Link: https://lore.kernel.org/bpf/20260920210423.345636-2-xyx2021@mail.ustc.edu.cn --- kernel/bpf/verifier.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 02be326f235c..dd8bb179d39e 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -567,7 +567,7 @@ static int stack_slot_obj_get_spi(struct bpf_verifier_env *env, struct bpf_reg_s } off = reg->var_off.value; - if (off % BPF_REG_SIZE) { + if (off >= 0 || off % BPF_REG_SIZE) { verbose(env, "cannot pass in %s at an offset=%d\n", obj_kind, off); return -EINVAL; } From 8244668cbbffc8e242b2c5204a2dea20bfca096c Mon Sep 17 00:00:00 2001 From: Xu Yunxiang Date: Mon, 21 Sep 2026 05:04:22 +0800 Subject: [PATCH 2117/2366] selftests/bpf: Reject iterator destruction through fp+0 Add a verifier regression test that initializes a numeric iterator at fp-8 and attempts to destroy it through fp+0. The verifier must reject the non-negative offset instead of treating it as the initialized stack slot. Check the offset diagnostic to ensure rejection happens at the stack object address check. The numeric iterator destroy operation is a no-op; this test checks verifier rejection and does not run the program. Signed-off-by: Xu Yunxiang Signed-off-by: Andrii Nakryiko Reviewed-by: Sun Jian Link: https://lore.kernel.org/bpf/20260920210423.345636-3-xyx2021@mail.ustc.edu.cn --- .../selftests/bpf/progs/iters_state_safety.c | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/iters_state_safety.c b/tools/testing/selftests/bpf/progs/iters_state_safety.c index 646026430e9b..e5bb9fe6d5e5 100644 --- a/tools/testing/selftests/bpf/progs/iters_state_safety.c +++ b/tools/testing/selftests/bpf/progs/iters_state_safety.c @@ -52,6 +52,28 @@ int create_and_destroy(void *ctx) return 0; } +/* fp+0 is not a stack slot. bpf_get_spi(0) used to alias spi 0 (fp-8). */ +SEC("?raw_tp") +__failure __msg("cannot pass in iter at an offset=0") +int destroy_fp0_fail(void *ctx) +{ + struct bpf_iter_num iter; + + asm volatile ("r1 = %[iter];" + "r2 = 0;" + "r3 = 1000;" + "call %[bpf_iter_num_new];" + /* r10 is fp+0, one byte above the top of the BPF stack */ + "r1 = r10;" + "call %[bpf_iter_num_destroy];" + : + : __imm_ptr(iter), ITER_HELPERS + : __clobber_common + ); + + return 0; +} + SEC("?raw_tp") __failure __msg("Unreleased reference id=1") int create_and_forget_to_destroy_fail(void *ctx) From d06f2ebf67ff2962fe00d687e4f0d4703eb41a12 Mon Sep 17 00:00:00 2001 From: Ratheesh Kannoth Date: Wed, 16 Sep 2026 07:51:11 +0530 Subject: [PATCH 2118/2366] octeontx2-af: Fix memory scaling limitation in SR-IOV mode The original code used DMA_ATTR_FORCE_CONTIGUOUS, which could exhaust the CMA pool when a large number of VFs were requested. Fix this by switching to the DMA streaming API. This is equivalent on Octeon platforms, which provide full I/O coherency via the SMMU. Cc: Leon Romanovsky Fixes: 73d33dbc0723 ("octeontx2-af: Use DMA_ATTR_FORCE_CONTIGUOUS attribute in DMA alloc") Signed-off-by: Ratheesh Kannoth Reviewed-by: Leon Romanovsky Link: https://patch.msgid.link/20260916022111.1083017-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- .../ethernet/marvell/octeontx2/af/common.h | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/common.h b/drivers/net/ethernet/marvell/octeontx2/af/common.h index 779413a383b7..78e42549d990 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/common.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/common.h @@ -7,6 +7,10 @@ #ifndef COMMON_H #define COMMON_H +#include +#include +#include + #include "rvu_struct.h" #define OTX2_ALIGN 128 /* Align to cacheline */ @@ -44,6 +48,33 @@ struct qmem { u32 qsize; }; +static inline void *otx2_dma_alloc_coherent(struct device *dev, size_t size, + dma_addr_t *dma_handle) +{ + dma_addr_t dma_addr; + void *vaddr; + + vaddr = kzalloc(size, GFP_KERNEL); + if (!vaddr) + return NULL; + + dma_addr = dma_map_single(dev, vaddr, size, DMA_BIDIRECTIONAL); + if (dma_mapping_error(dev, dma_addr)) { + kfree(vaddr); + return NULL; + } + + *dma_handle = dma_addr; + return vaddr; +} + +static inline void otx2_dma_free_coherent(struct device *dev, size_t size, + void *vaddr, dma_addr_t dma_handle) +{ + dma_unmap_single(dev, dma_handle, size, DMA_BIDIRECTIONAL); + kfree(vaddr); +} + static inline int qmem_alloc(struct device *dev, struct qmem **q, int qsize, int entry_sz) { @@ -60,8 +91,11 @@ static inline int qmem_alloc(struct device *dev, struct qmem **q, qmem->entry_sz = entry_sz; qmem->alloc_sz = (qsize * entry_sz) + OTX2_ALIGN; - qmem->base = dma_alloc_attrs(dev, qmem->alloc_sz, &qmem->iova, - GFP_KERNEL, DMA_ATTR_FORCE_CONTIGUOUS); + + if (get_order(PAGE_ALIGN(qmem->alloc_sz)) > MAX_PAGE_ORDER) + return -ENOMEM; + + qmem->base = otx2_dma_alloc_coherent(dev, qmem->alloc_sz, &qmem->iova); if (!qmem->base) return -ENOMEM; @@ -80,10 +114,9 @@ static inline void qmem_free(struct device *dev, struct qmem *qmem) return; if (qmem->base) - dma_free_attrs(dev, qmem->alloc_sz, - qmem->base - qmem->align, - qmem->iova - qmem->align, - DMA_ATTR_FORCE_CONTIGUOUS); + otx2_dma_free_coherent(dev, qmem->alloc_sz, + qmem->base - qmem->align, + qmem->iova - qmem->align); devm_kfree(dev, qmem); } From 0346ec2f080b40d95ed05b853bb9226289e75212 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Fri, 18 Sep 2026 08:22:05 +0000 Subject: [PATCH 2119/2366] ipv6: Prevent rt6_insert_exception() for dying fib6_info. Before the cited commit, fib6_nh_flush_exceptions() always set from->exception_bucket_flushed = 1 under rt6_exception_lock to prevent rt6_insert_exception() from inserting a new exception for a dying fib6_info. The flag was replaced with the FIB6_EXCEPTION_BUCKET_FLUSHED bit stored in nh->rt6i_exception_bucket. The problem is that now the bit is only set when the bucket is not NULL and fib6_nh_flush_exceptions() is called from fib6_nh_release() after fib6_ref has already reached zero. If rt6_insert_exception() is called while the target fib6_info is being removed via fib6_purge_rt(), a new exception could be created successfully because rt6_flush_exceptions() no longer sets the bit. This creates a reference cycle between the fib6_info and the exception route, leaking the fib6_info, its nexthop device, and all per-CPU routes in fib6_nh->rt6i_pcpu, which stalls netdev unregistration. [ 34.680602] unregister_netdevice: waiting for gre6 to become free. Usage count = 68 [ 44.920675] unregister_netdevice: waiting for gre6 to become free. Usage count = 68 [ 55.176582] unregister_netdevice: waiting for gre6 to become free. Usage count = 68 Let's call fib6_drop_pcpu_from() before rt6_flush_exceptions(), to set fib6_destroying before rt6_exception_lock, and check f6i->fib6_destroying in rt6_insert_exception(). Note that FIB6_EXCEPTION_BUCKET_FLUSHED logic is dead and we can clean it up in net-next. Fixes: cc5c073a693f ("ipv6: Move exception bucket to fib6_nh") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260918082209.2853582-1-kuniyu@google.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_fib.c | 2 +- net/ipv6/route.c | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 9ea75703b38d..9ff761962b45 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -1043,8 +1043,8 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn, struct fib6_table *table = rt->fib6_table; /* Flush all cached dst in exception table */ - rt6_flush_exceptions(rt); fib6_drop_pcpu_from(rt); + rt6_flush_exceptions(rt); if (rt->nh) { spin_lock(&rt->nh->lock); diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 08bd68f1b5bb..884d9ab0d50d 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -1729,6 +1729,11 @@ static int rt6_insert_exception(struct rt6_info *nrt, spin_lock_bh(&rt6_exception_lock); + if (f6i->fib6_destroying) { + err = -ENOENT; + goto out; + } + bucket = rcu_dereference_protected(nh->rt6i_exception_bucket, lockdep_is_held(&rt6_exception_lock)); if (!bucket) { From 31995571219c8ac30913d9c0dccad033fbb0b3da Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Fri, 18 Sep 2026 11:55:40 +0200 Subject: [PATCH 2120/2366] net: don't require the hwtstamp NDOs when a PHY provides timestamping Removing the legacy ioctl fallback made both hwtstamp NDOs mandatory. A device that only timestamps in its PHY implements neither, so SIOCSHWTSTAMP fails with EOPNOTSUPP before anything looks at the PHY and PTP stops working there. The check only ever picked the legacy path. That path is gone, so drop it and test where the NDOs are actually called. SIOCGHWTSTAMP is new here, not restored. The old path went through phy_mii_ioctl(), which only handled SIOCSHWTSTAMP. Such a device now returns -ENODEV while absent instead of -EOPNOTSUPP, like the ones that do implement the NDOs. Fixes: 5062245a5a7f ("net: remove legacy way to get/set HW timestamp config") Signed-off-by: Nicolai Buchwitz Reviewed-by: Kory Maincent Link: https://patch.msgid.link/20260918095540.34286-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- net/core/dev_ioctl.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c index a320e264eaaf..164643140a52 100644 --- a/net/core/dev_ioctl.c +++ b/net/core/dev_ioctl.c @@ -276,19 +276,18 @@ int dev_get_hwtstamp_phylib(struct net_device *dev, if (phy_is_default_hwtstamp(dev->phydev)) return phy_hwtstamp_get(dev->phydev, cfg); + if (!dev->netdev_ops->ndo_hwtstamp_get) + return -EOPNOTSUPP; + return dev->netdev_ops->ndo_hwtstamp_get(dev, cfg); } static int dev_get_hwtstamp(struct net_device *dev, struct ifreq *ifr) { - const struct net_device_ops *ops = dev->netdev_ops; struct kernel_hwtstamp_config kernel_cfg = {}; struct hwtstamp_config cfg; int err; - if (!ops->ndo_hwtstamp_get) - return -EOPNOTSUPP; - if (!netif_device_present(dev)) return -ENODEV; @@ -359,12 +358,18 @@ int dev_set_hwtstamp_phylib(struct net_device *dev, cfg->source = phy_ts ? HWTSTAMP_SOURCE_PHYLIB : HWTSTAMP_SOURCE_NETDEV; if (phy_ts && dev->see_all_hwtstamp_requests) { + if (!ops->ndo_hwtstamp_get) + return -EOPNOTSUPP; + err = ops->ndo_hwtstamp_get(dev, &old_cfg); if (err) return err; } if (!phy_ts || dev->see_all_hwtstamp_requests) { + if (!ops->ndo_hwtstamp_set) + return -EOPNOTSUPP; + err = ops->ndo_hwtstamp_set(dev, cfg, extack); if (err) { if (extack->_msg) @@ -390,7 +395,6 @@ int dev_set_hwtstamp_phylib(struct net_device *dev, static int dev_set_hwtstamp(struct net_device *dev, struct ifreq *ifr) { - const struct net_device_ops *ops = dev->netdev_ops; struct kernel_hwtstamp_config kernel_cfg = {}; struct netlink_ext_ack extack = {}; struct hwtstamp_config cfg; @@ -413,9 +417,6 @@ static int dev_set_hwtstamp(struct net_device *dev, struct ifreq *ifr) return err; } - if (!ops->ndo_hwtstamp_set) - return -EOPNOTSUPP; - if (!netif_device_present(dev)) return -ENODEV; @@ -441,15 +442,11 @@ static int dev_set_hwtstamp(struct net_device *dev, struct ifreq *ifr) int generic_hwtstamp_get_lower(struct net_device *dev, struct kernel_hwtstamp_config *kernel_cfg) { - const struct net_device_ops *ops = dev->netdev_ops; int err; if (!netif_device_present(dev)) return -ENODEV; - if (!ops->ndo_hwtstamp_get) - return -EOPNOTSUPP; - netdev_lock_ops(dev); err = dev_get_hwtstamp_phylib(dev, kernel_cfg); netdev_unlock_ops(dev); @@ -462,15 +459,11 @@ int generic_hwtstamp_set_lower(struct net_device *dev, struct kernel_hwtstamp_config *kernel_cfg, struct netlink_ext_ack *extack) { - const struct net_device_ops *ops = dev->netdev_ops; int err; if (!netif_device_present(dev)) return -ENODEV; - if (!ops->ndo_hwtstamp_set) - return -EOPNOTSUPP; - netdev_lock_ops(dev); err = dev_set_hwtstamp_phylib(dev, kernel_cfg, extack); netdev_unlock_ops(dev); From 7cce782d8327b7291334c4a304cf3fd909a74d9d Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Thu, 17 Sep 2026 16:37:36 +0200 Subject: [PATCH 2121/2366] dpll: use exact lookup for reference sync pin id dpll_pin_ref_sync_state_set() looks up the reference sync pin in the pin->ref_sync_pins xarray, which is keyed by the sync pin's id (see dpll_pin_ref_sync_pair_add() using xa_insert() with ref_sync_pin->id). The pin id to operate on is supplied by userspace via DPLL_A_PIN_ID. The lookup however used xa_find() with a ULONG_MAX limit, which returns the first present entry with an index greater than or equal to the requested id, not the entry stored exactly at that id. If userspace passes an id that is not paired as a reference sync pin, but another pin with a higher id is present in the xarray, xa_find() silently returns that wrong pin and the subsequent ref_sync_set() operates on it. The request only fails when the given id is larger than every present key. Use xa_load() for an exact-key lookup instead, mirroring the deletion path in dpll_pin_ref_sync_pair_del(). Fixes: 58256a26bfb3 ("dpll: add reference sync get/set") Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260917143736.526221-1-ivecera@redhat.com Signed-off-by: Jakub Kicinski --- drivers/dpll/dpll_netlink.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 45365214fbef..fb24fd53f2e1 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -1210,8 +1210,7 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, struct dpll_device *dpll; int ret; - ref_sync_pin = xa_find(&pin->ref_sync_pins, &ref_sync_pin_idx, - ULONG_MAX, XA_PRESENT); + ref_sync_pin = xa_load(&pin->ref_sync_pins, ref_sync_pin_idx); if (!ref_sync_pin) { NL_SET_ERR_MSG(extack, "reference sync pin not found"); return -EINVAL; From c06bde80ae7a7b595732f7cabcb92cf08db9d56a Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 20 Sep 2026 11:47:45 +0800 Subject: [PATCH 2122/2366] net: usb: sr9700: include receive overhead in the length check The receive fixup subtracts the Ethernet CRC from the reported packet length, but compares that payload length against the whole remaining receive buffer. The following copy starts after the three-byte header, and the cursor advance consumes both that header and the four-byte CRC. Require the payload to fit after SR_RX_OVERHEAD before copying it or advancing to the next packet. The loop already ensures that the remaining buffer is larger than the overhead, so the subtraction is safe. The issue was found by our static-analysis tool. Fixes: c9b37458e956 ("USB2NET : SR9700 : One chip USB 1.1 USB2NET SR9700Device Driver Support") Reviewed-by: Ethan Nelson-Moore Tested-by: Ethan Nelson-Moore Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260920034745.18468-1-hppiscas@163.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/sr9700.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/usb/sr9700.c b/drivers/net/usb/sr9700.c index 937e6fef3ac6..50981a28376a 100644 --- a/drivers/net/usb/sr9700.c +++ b/drivers/net/usb/sr9700.c @@ -355,7 +355,8 @@ static int sr9700_rx_fixup(struct usbnet *dev, struct sk_buff *skb) /* ignore the CRC length */ len = (skb->data[1] | (skb->data[2] << 8)) - 4; - if (len > ETH_FRAME_LEN || len > skb->len || len < 0) + if (len > ETH_FRAME_LEN || len < 0 || + len > skb->len - SR_RX_OVERHEAD) return 0; /* the last packet of current skb */ From be581d6635579489eadff9cea4caee142ec6d8bc Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Fri, 18 Sep 2026 22:49:30 +0900 Subject: [PATCH 2123/2366] selftests: net: fix CONFIG_SYSCTL sort order in configs Commit 8d75c338f0bc ("sysctl: remove CONFIG_PROC_SYSCTL, it just mirrors CONFIG_SYSCTL") renamed CONFIG_PROC_SYSCTL to CONFIG_SYSCTL in place, which left the entry out of alphabetical order in the net and packetdrill configs. The netdev CI check for sorted selftest configs now fails for every patch that touches either file. Fixes: 8d75c338f0bc ("sysctl: remove CONFIG_PROC_SYSCTL, it just mirrors CONFIG_SYSCTL") Signed-off-by: Yuya Kusakabe Reviewed-by: Joel Granados Link: https://patch.msgid.link/20260918-selftests-net-config-sort-v1-1-968ea6e8c1b7@gmail.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/config | 2 +- tools/testing/selftests/net/packetdrill/config | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config index 30d5fcb09a83..737e7e6327b3 100644 --- a/tools/testing/selftests/net/config +++ b/tools/testing/selftests/net/config @@ -118,10 +118,10 @@ CONFIG_NFT_NAT=m CONFIG_NUMA=y CONFIG_OPENVSWITCH=m CONFIG_PAGE_POOL_STATS=y -CONFIG_SYSCTL=y CONFIG_PSAMPLE=m CONFIG_RPS=y CONFIG_SYN_COOKIES=y +CONFIG_SYSCTL=y CONFIG_SYSFS=y CONFIG_TAP=m CONFIG_TCP_CONG_DCTCP=y diff --git a/tools/testing/selftests/net/packetdrill/config b/tools/testing/selftests/net/packetdrill/config index 83dde525c53c..b7df8bf30920 100644 --- a/tools/testing/selftests/net/packetdrill/config +++ b/tools/testing/selftests/net/packetdrill/config @@ -4,8 +4,8 @@ CONFIG_IPV6=y CONFIG_NET_NS=y CONFIG_NET_SCH_FIFO=y CONFIG_NET_SCH_FQ=y -CONFIG_SYSCTL=y CONFIG_SYN_COOKIES=y +CONFIG_SYSCTL=y CONFIG_TCP_CONG_CUBIC=y CONFIG_TCP_MD5SIG=y CONFIG_TUN=y From 10de7ed8ef4840da9ca21de4c29578657ac367db Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Thu, 17 Sep 2026 13:55:42 +0200 Subject: [PATCH 2124/2366] net/mlx5e: fix swapped IPv6 IPsec policy masks IPv6 XFRM policies may use different source and destination prefix lengths. mlx5e_ipsec_policy_mask() builds the corresponding masks independently, but setup_fte_addr6() installs each mask in the opposite address field. When the prefix lengths differ, this makes the source match use the destination prefix and the destination match use the source prefix. The resulting hardware rule can both miss traffic covered by the policy and match traffic outside it. Install each mask in its corresponding match field. Fixes: ca7992f52c2c ("net/mlx5e: Properly match IPsec subnet addresses") Cc: stable@vger.kernel.org Signed-off-by: Andrea Parri Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260917115542.177675-1-parri.andrea@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c index 329608c59313..8ffa8068e90a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c @@ -1564,14 +1564,14 @@ static void setup_fte_addr6(struct mlx5_flow_spec *spec, memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value, outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6), saddr, 16); memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_criteria, - outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6), dmask, 16); + outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6), smask, 16); } if (!addr6_all_zero(daddr)) { memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value, outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6), daddr, 16); memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_criteria, - outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6), smask, 16); + outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6), dmask, 16); } } From 67f4c1c6a1b51e203d986779299824d1c2c590a6 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 16 Sep 2026 15:29:24 +0000 Subject: [PATCH 2125/2366] smb: client: fix create context out-of-bounds reads smb2_parse_contexts() validates the complete create-context area but does not limit each record to its Next field before dispatching it. A malformed chain can therefore expose bytes beyond the current context to a handler. The QFid handler also used a full response-structure cast although it only reads DiskFileId. The SMB2/SMB3 lease parsers made the same layout assumption: they read LeaseState and LeaseFlags at canonical offsets rather than at DataOffset. A valid non-canonical DataOffset could therefore yield unrelated in-bounds data, while a short DataLength was still accepted. Limit each context to its Next value, reject offsets before the context header, and reject malformed chains. Bound the name range by the current context and do not dispatch a known handler when DataLength is zero. Read the QFid DiskFileId only when the context data covers that field. Parse the lease context from DataOffset and require DataLength to match the v1 or v2 lease_context size used by ksmbd. A size mismatch skips lease parsing without failing the open. Fixes: b8c32dbb0deb ("CIFS: Request SMB2.1 leases") Fixes: f047390a097e ("CIFS: Add create lease v2 context for SMB3") Fixes: 89a5bfa350fa ("smb3: optimize open to not send query file internal info") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Tested-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2ops.c | 28 ++++++++++++++++++-------- fs/smb/client/smb2pdu.c | 44 +++++++++++++++++++++++++++++++---------- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 3464470d3297..aa142420dae2 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4572,25 +4572,37 @@ smb3_create_lease_buf(u8 *lease_key, u8 oplock, u8 *parent_lease_key, __le32 fla static __u8 smb2_parse_lease_buf(void *buf, __u16 *epoch, char *lease_key) { - struct create_lease *lc = (struct create_lease *)buf; + struct create_context *cc = buf; + struct lease_context lc; *epoch = 0; /* not used */ - if (lc->lcontext.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE) + if (le32_to_cpu(cc->DataLength) != sizeof(lc)) + return 0; + + memcpy(&lc, (u8 *)cc + le16_to_cpu(cc->DataOffset), sizeof(lc)); + if (lc.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE) return SMB2_OPLOCK_LEVEL_NOCHANGE; - return le32_to_cpu(lc->lcontext.LeaseState); + return le32_to_cpu(lc.LeaseState); } static __u8 smb3_parse_lease_buf(void *buf, __u16 *epoch, char *lease_key) { - struct create_lease_v2 *lc = (struct create_lease_v2 *)buf; + struct create_context *cc = buf; + struct lease_context_v2 lc; - *epoch = le16_to_cpu(lc->lcontext.Epoch); - if (lc->lcontext.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE) + if (le32_to_cpu(cc->DataLength) != sizeof(lc)) { + *epoch = 0; + return 0; + } + + memcpy(&lc, (u8 *)cc + le16_to_cpu(cc->DataOffset), sizeof(lc)); + *epoch = le16_to_cpu(lc.Epoch); + if (lc.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE) return SMB2_OPLOCK_LEVEL_NOCHANGE; if (lease_key) - memcpy(lease_key, &lc->lcontext.LeaseKey, SMB2_LEASE_KEY_SIZE); - return le32_to_cpu(lc->lcontext.LeaseState); + memcpy(lease_key, lc.LeaseKey, SMB2_LEASE_KEY_SIZE); + return le32_to_cpu(lc.LeaseState); } static unsigned int diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 880ce12f50c4..4046500dbe93 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -2379,11 +2379,17 @@ create_reconnect_durable_buf(struct cifs_fid *fid) static void parse_query_id_ctxt(struct create_context *cc, struct smb2_file_all_info *buf) { - struct create_disk_id_rsp *pdisk_id = (struct create_disk_id_rsp *)cc; + u16 doff = le16_to_cpu(cc->DataOffset); + u32 dlen = le32_to_cpu(cc->DataLength); + u8 *beg; - cifs_dbg(FYI, "parse query id context 0x%llx 0x%llx\n", - pdisk_id->DiskFileId, pdisk_id->VolumeId); - buf->IndexNumber = pdisk_id->DiskFileId; + if (dlen < sizeof(__le64)) + return; + + beg = (u8 *)cc + doff; + memcpy(&buf->IndexNumber, beg, sizeof(__le64)); + cifs_dbg(FYI, "parse query id context 0x%llx\n", + le64_to_cpu(buf->IndexNumber)); } static void @@ -2431,6 +2437,7 @@ int smb2_parse_contexts(struct TCP_Server_Info *server, struct smb2_create_rsp *rsp = rsp_iov->iov_base; struct create_context *cc; size_t rem, off, len; + size_t cc_len; size_t doff, dlen; size_t noff, nlen; char *name; @@ -2453,29 +2460,41 @@ int smb2_parse_contexts(struct TCP_Server_Info *server, buf->IndexNumber = 0; while (rem >= sizeof(*cc)) { + off = le32_to_cpu(cc->Next); + if (off) { + if ((off & 0x7) || off >= rem || off < sizeof(*cc)) + return -EINVAL; + cc_len = off; + } else { + cc_len = rem; + } + doff = le16_to_cpu(cc->DataOffset); dlen = le32_to_cpu(cc->DataLength); - if (check_add_overflow(doff, dlen, &len) || len > rem) + if (doff < sizeof(*cc) || + check_add_overflow(doff, dlen, &len) || len > cc_len) return -EINVAL; noff = le16_to_cpu(cc->NameOffset); nlen = le16_to_cpu(cc->NameLength); - if (noff + nlen > doff) + if (noff < sizeof(*cc) || + check_add_overflow(noff, nlen, &len) || len > cc_len || + (dlen && len > doff)) return -EINVAL; name = (char *)cc + noff; switch (nlen) { case 4: - if (!strncmp(name, SMB2_CREATE_REQUEST_LEASE, 4)) { + if (dlen && !strncmp(name, SMB2_CREATE_REQUEST_LEASE, 4)) { *oplock = server->ops->parse_lease_buf(cc, epoch, lease_key); - } else if (buf && + } else if (dlen && buf && !strncmp(name, SMB2_CREATE_QUERY_ON_DISK_ID, 4)) { parse_query_id_ctxt(cc, buf); } break; case 16: - if (posix && !memcmp(name, smb3_create_tag_posix, 16)) + if (dlen && posix && !memcmp(name, smb3_create_tag_posix, 16)) parse_posix_ctxt(cc, buf, posix); break; default: @@ -2487,13 +2506,18 @@ int smb2_parse_contexts(struct TCP_Server_Info *server, } off = le32_to_cpu(cc->Next); - if (!off) + if (!off) { + rem = 0; break; + } if (check_sub_overflow(rem, off, &rem)) return -EINVAL; cc = (struct create_context *)((u8 *)cc + off); } + if (rem) + return -EINVAL; + if (rsp->OplockLevel != SMB2_OPLOCK_LEVEL_LEASE) *oplock = rsp->OplockLevel; From fa2e9900dd2a3f5a1e7ef5a8c5e8d435feedbfcc Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 16 Sep 2026 15:29:25 +0000 Subject: [PATCH 2126/2366] smb: client: validate POSIX create context length parse_posix_ctxt() reads the fixed nlink, reparse_tag, and mode fields before checking that the POSIX create context contains them. A short context can pass the generic checks and still make these fixed-width reads run past its declared data. The current in-tree smb2_open_file() path passes a NULL posix pointer, so this handler is not reached on the ordinary open path. Still require the POSIX data to cover all three fields before reading them because the helper performs those unguarded reads. Keep the existing soft-failure behavior so malformed optional metadata does not fail the open. Fixes: 69dda3059e7a ("cifs: add SMB2_open() arg to return POSIX data") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Tested-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2pdu.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 4046500dbe93..538d708b0404 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -2396,12 +2396,15 @@ static void parse_posix_ctxt(struct create_context *cc, struct smb2_file_all_info *info, struct create_posix_rsp *posix) { - int sid_len; u8 *beg = (u8 *)cc + le16_to_cpu(cc->DataOffset); - u8 *end = beg + le32_to_cpu(cc->DataLength); + u32 dlen = le32_to_cpu(cc->DataLength); + u8 *end = beg + dlen; + int sid_len; u8 *sid; memset(posix, 0, sizeof(*posix)); + if (dlen < 3 * sizeof(__le32)) + return; posix->nlink = get_unaligned_le32(beg); posix->reparse_tag = get_unaligned_le32(beg + 4); From 566820af017e81497fb5e9d3ad6e7ffe2828bc8b Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 16 Sep 2026 15:29:26 +0000 Subject: [PATCH 2127/2366] smb: client: close handle after create-context parsing failure SMB2_open() accounts a successful CREATE response as a remote open before parsing its create contexts. If smb2_parse_contexts() rejects malformed context data, SMB2_open() returns without closing the handle, leaving the server-side handle open and num_remote_opens elevated. Close the handle after a post-CREATE context parsing failure so the error path releases the remote resource and balances the open count. Fixes: af1689a9b770 ("smb: client: fix potential OOBs in smb2_parse_contexts()") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Tested-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2pdu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 538d708b0404..3d7ead36d1a0 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -3416,6 +3416,9 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path, rc = smb2_parse_contexts(server, &rsp_iov, &oparms->fid->epoch, oparms->fid->lease_key, oplock, file_info, posix); + if (rc) + SMB2_close(xid, tcon, oparms->fid->persistent_fid, + oparms->fid->volatile_fid); trace_smb3_open_done(xid, rsp->PersistentFileId, tcon->tid, ses->Suid, oparms->create_options, oparms->desired_access, From d2ff5fb93ea83034025850266b5eed391f96b825 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 16 Sep 2026 15:29:27 +0000 Subject: [PATCH 2128/2366] smb: client: clean up failed cached directory opens open_cached_dir() sends CREATE and QUERY_INFO as a compound request. If the CREATE succeeds but a later command returns an error, the function must retain the CREATE FID so common cleanup can issue SMB2_close(). It also must not treat a response error as a valid CREATE. Validate the CREATE response before using its fields, record the FIDs, and mark the handle open before handling errors from later compound commands. Move the -EREMCHG reconnect handling before response validation so a missing response does not hide the reconnect request. Count the handle when it is marked open; confirmed close responses decrement the counter, while existing close retry behavior remains best effort on transport failures. Fixes: b0f6df737a1c ("cifs: cache FILE_ALL_INFO for the shared root handle") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Tested-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/cached_dir.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index 88d5e9a32f28..647fa26da4d2 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -8,6 +8,7 @@ #include #include "cifsglob.h" #include "cifsproto.h" +#include "../common/smb2status.h" #include "cifs_debug.h" #include "smb2proto.h" #include "cached_dir.h" @@ -323,25 +324,37 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, rc = compound_send_recv(xid, ses, server, flags, 2, rqst, resp_buftype, rsp_iov); - if (rc) { - if (rc == -EREMCHG) { - tcon->need_reconnect = true; - pr_warn_once("server share %s deleted\n", - tcon->tree_name); - } + if (rc == -EREMCHG) { + tcon->need_reconnect = true; + pr_warn_once("server share %s deleted\n", + tcon->tree_name); + } + + if (!rsp_iov[0].iov_base || rsp_iov[0].iov_len < sizeof(*o_rsp)) { + if (!rc) + rc = -EIO; goto oshr_free; } - cfid->is_open = true; - - spin_lock(&cfids->cfid_list_lock); o_rsp = (struct smb2_create_rsp *)rsp_iov[0].iov_base; + if (o_rsp->hdr.Status != STATUS_SUCCESS) { + if (!rc) + rc = -EIO; + goto oshr_free; + } + oparms.fid->persistent_fid = o_rsp->PersistentFileId; oparms.fid->volatile_fid = o_rsp->VolatileFileId; #ifdef CONFIG_CIFS_DEBUG2 oparms.fid->mid = le64_to_cpu(o_rsp->hdr.MessageId); #endif /* CIFS_DEBUG2 */ + cfid->is_open = true; + atomic_inc(&tcon->num_remote_opens); + if (rc) + goto oshr_free; + + spin_lock(&cfids->cfid_list_lock); if (o_rsp->OplockLevel != SMB2_OPLOCK_LEVEL_LEASE) { spin_unlock(&cfids->cfid_list_lock); @@ -408,7 +421,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, close_cached_dir(cfid); } else { *ret_cfid = cfid; - atomic_inc(&tcon->num_remote_opens); } kfree(utf16_path); From 6c5c547f037bc18f0b8d0b5db5a648f8f630ce85 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 16 Sep 2026 15:29:28 +0000 Subject: [PATCH 2129/2366] smb: client: close completed creates on compound wait errors compound_send_recv() waits for responses in order. If a later wait is interrupted, or if a later MID fails during response synchronization, an earlier CREATE may already have opened a remote handle. The earlier mid is then released without invoking handle_cancelled_mid(), leaving the remote handle open because no FID was copied to the caller. Mark completed earlier mids as cancelled when a compound wait or MID synchronization aborts. Keep their response buffers attached while the MIDs are synchronized, and transfer them only after synchronization of the processed responses, so the release path can inspect successful CREATE responses and queue SMB2_close() after a later failure. Account for a remote open only after the close work is allocated and before it is queued, since the caller has not yet updated num_remote_opens. Mark the create+close compound used by smb2_unlink() so it is not closed again. Non-CREATE responses and compounds that already include a close keep their existing behavior. Fixes: e0bba0b85481 ("cifs: add compound_send_recv()") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Tested-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2inode.c | 2 +- fs/smb/client/smb2misc.c | 9 ++++-- fs/smb/client/transport.c | 67 +++++++++++++++++++++++++++++++-------- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 13fe8e3b48f3..ecd7a65cab08 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -1121,7 +1121,7 @@ smb2_unlink(const unsigned int xid, struct cifs_tcon *tcon, const char *name, struct kvec close_iov; int resp_buftype[2]; struct cifs_fid fid; - int flags = 0; + int flags = CIFS_CP_CREATE_CLOSE_OP; __u8 oplock; int rc; diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c index 0cfe60ae42c3..5e5cf92d1eb3 100644 --- a/fs/smb/client/smb2misc.c +++ b/fs/smb/client/smb2misc.c @@ -834,7 +834,8 @@ smb2_cancelled_close_fid(struct work_struct *work) */ static int __smb2_handle_cancelled_cmd(struct cifs_tcon *tcon, __u16 cmd, __u64 mid, - __u64 persistent_fid, __u64 volatile_fid) + __u64 persistent_fid, __u64 volatile_fid, + bool account_remote_open) { struct close_cancelled_open *cancelled; @@ -848,6 +849,8 @@ __smb2_handle_cancelled_cmd(struct cifs_tcon *tcon, __u16 cmd, __u64 mid, cancelled->cmd = cmd; cancelled->mid = mid; INIT_WORK(&cancelled->work, smb2_cancelled_close_fid); + if (account_remote_open) + atomic_inc(&tcon->num_remote_opens); WARN_ON(queue_work(cifsiod_wq, &cancelled->work) == false); return 0; @@ -884,7 +887,7 @@ smb2_handle_cancelled_close(struct cifs_tcon *tcon, __u64 persistent_fid, spin_unlock(&tcon->tc_lock); rc = __smb2_handle_cancelled_cmd(tcon, SMB2_CLOSE_HE, 0, - persistent_fid, volatile_fid); + persistent_fid, volatile_fid, false); if (rc) cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_close); @@ -912,7 +915,7 @@ smb2_handle_cancelled_mid(struct mid_q_entry *mid, struct TCP_Server_Info *serve le16_to_cpu(hdr->Command), le64_to_cpu(hdr->MessageId), rsp->PersistentFileId, - rsp->VolatileFileId); + rsp->VolatileFileId, true); if (rc) cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_mid); diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index 7df5b3447aea..6e21b5f8754a 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -805,6 +805,18 @@ cifs_cancelled_callback(struct TCP_Server_Info *server, struct mid_q_entry *mid) release_mid(server, mid); } +static void +cifs_mark_compound_mids_cancelled(struct mid_q_entry **mid, int count) +{ + int i; + + for (i = 0; i < count; i++) { + spin_lock(&mid[i]->mid_lock); + mid[i]->wait_cancelled = true; + spin_unlock(&mid[i]->mid_lock); + } +} + /* * cifs_pick_channel - pick an eligible channel for network operations * @@ -865,6 +877,7 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, int *resp_buf_type, struct kvec *resp_iov) { int i, j, optype, rc = 0; + int num_processed = 0; struct mid_q_entry *mid[MAX_COMPOUND]; bool cancelled_mid[MAX_COMPOUND] = {false}; struct cifs_credits credits[MAX_COMPOUND] = { @@ -1015,6 +1028,14 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, break; } if (rc != 0) { + /* + * A completed CREATE earlier in the compound chain may have + * opened a remote handle even though a later wait was + * interrupted. Mark it cancelled so __release_mid() invokes + * the existing unmatched-open cleanup. + */ + cifs_mark_compound_mids_cancelled(mid, i); + for (; i < num_rqst; i++) { cifs_server_dbg(FYI, "Cancelling wait for mid %llu cmd: %d\n", mid[i]->mid, le16_to_cpu(mid[i]->command)); @@ -1037,6 +1058,14 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, rc = cifs_sync_mid_result(mid[i], server); if (rc != 0) { + /* + * A previous CREATE may have completed before this + * response failed. Mark it cancelled so its remote + * handle is closed when the mid is released. + */ + cifs_mark_compound_mids_cancelled(mid, i); + /* Keep their response buffers for cancelled-mid cleanup. */ + num_processed = 0; /* mark this mid as cancelled to not free it below */ cancelled_mid[i] = true; goto out; @@ -1046,13 +1075,24 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, mid[i]->mid_state != MID_RESPONSE_READY) { rc = smb_EIO1(smb_eio_trace_rx_mid_unready, mid[i]->mid_state); cifs_dbg(FYI, "Bad MID state?\n"); + cifs_mark_compound_mids_cancelled(mid, i); + num_processed = 0; goto out; } rc = server->ops->check_receive(mid[i], server, flags & CIFS_LOG_ERROR); + num_processed = i + 1; + } - if (resp_iov) { +out: + /* + * Delay moving response buffers out of their mids until response + * synchronization completes. This lets cancelled-mid cleanup inspect + * an earlier CREATE response if a later MID fails. + */ + if (resp_iov) { + for (i = 0; i < num_processed; i++) { buf = (char *)mid[i]->resp_buf; resp_iov[i].iov_base = buf; resp_iov[i].iov_len = mid[i]->resp_buf_size; @@ -1071,21 +1111,22 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, /* * Compounding is never used during session establish. */ - spin_lock(&ses->ses_lock); - if ((ses->ses_status == SES_NEW) || (optype & CIFS_NEG_OP) || (optype & CIFS_SESS_OP)) { - struct kvec iov = { - .iov_base = resp_iov[0].iov_base, - .iov_len = resp_iov[0].iov_len - }; - spin_unlock(&ses->ses_lock); - cifs_server_lock(server); - smb311_update_preauth_hash(ses, server, &iov, 1); - cifs_server_unlock(server); + if (num_processed == num_rqst) { spin_lock(&ses->ses_lock); + if ((ses->ses_status == SES_NEW) || (optype & CIFS_NEG_OP) || (optype & CIFS_SESS_OP)) { + struct kvec iov = { + .iov_base = resp_iov[0].iov_base, + .iov_len = resp_iov[0].iov_len + }; + spin_unlock(&ses->ses_lock); + cifs_server_lock(server); + smb311_update_preauth_hash(ses, server, &iov, 1); + cifs_server_unlock(server); + spin_lock(&ses->ses_lock); + } + spin_unlock(&ses->ses_lock); } - spin_unlock(&ses->ses_lock); -out: /* * This will dequeue all mids. After this it is important that the * demultiplex_thread will not process any of these mids any further. From 2e828035d5d736d904c238ae7ec3f77c0d6270bf Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Wed, 16 Sep 2026 15:29:29 +0000 Subject: [PATCH 2130/2366] smb: client: preserve create-context parsing errors smb2_compound_op() saves the result from compound_send_recv() in tmp_rc. For SMB2_OP_OPEN_QUERY it then parses the CREATE contexts, but the final assignment of rc from tmp_rc discards a parsing error. A malformed create-context response can therefore be reported as successful to smb2_query_path_info(). Keep a create-context parsing error in tmp_rc so it survives per-command response processing and is returned to the caller. Fixes: b07687edee99 ("cifs: Improve SMB2+ stat() to work also without FILE_READ_ATTRIBUTES") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Tested-by: Frank Sorenson Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2inode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index ecd7a65cab08..f46a62eae659 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -598,8 +598,10 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, /* smb2_parse_contexts() fills idata->fi.IndexNumber */ rc = smb2_parse_contexts(server, &rsp_iov[0], &oparms->fid->epoch, oparms->fid->lease_key, &oplock, &idata->fi, NULL); - if (rc) + if (rc) { cifs_dbg(VFS, "rc: %d parsing context of compound op\n", rc); + tmp_rc = rc; + } } for (i = 0; i < num_cmds; i++) { From be31fe6333f534155e6b408f1ef6d77974bb41aa Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sun, 20 Sep 2026 19:14:32 +0000 Subject: [PATCH 2131/2366] ipv6: Fix dst leak for uncached routes. ip6_route_output_flags(), ip6_rt_put_flags(), and ip6_dst_check() detect an uncached route by list_empty(&rt->dst.rt_uncached), which replaced the static DST_NOCACHE flag check in commit a4c2fd7f7891 ("net: remove DST_NOCACHE flag"). When a device is unregistered, rt6_uncached_list_flush_dev() unlinks uncached routes tied to the device from rt6_uncached_list. Previously, they were moved to another list with list_move() (__list_del_entry() + list_add()), and since commit 98aa546af5e4 ("inet: remove (struct uncached_list)->quarantine"), the routes are just unlinked with list_del_init(). If list_del_init() runs concurrently, list_empty() evaluates to true; ip6_route_output_flags() calls dst_hold_safe() incorrectly and ip6_rt_put_flags() skips ip6_rt_put(), leaking dst, and thus dev tied via rt->from as well. The same race is partially fixed by commit 9a6f0c4d5796 ("dst: fix races in rt6_uncached_list_del() and rt_del_uncached_list()"). Let's check rt6->dst.rt_uncached_list instead. Note that IPv4 does not have the same issue. Fixes: 98aa546af5e4 ("inet: remove (struct uncached_list)->quarantine") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Hangbin Liu Reviewed-by: Xuanqiang Luo Reviewed-by: Ido Schimmel Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260920191558.2990636-1-kuniyu@google.com Signed-off-by: Jakub Kicinski --- include/net/ip6_route.h | 4 ++-- net/ipv6/route.c | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index b9e8d2b759e9..0f9b7a260d25 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -101,12 +101,12 @@ static inline struct dst_entry *ip6_route_output(struct net *net, } /* Only conditionally release dst if flags indicates - * !RT6_LOOKUP_F_DST_NOREF or dst is in uncached_list. + * !RT6_LOOKUP_F_DST_NOREF or dst is uncached. */ static inline void ip6_rt_put_flags(struct rt6_info *rt, int flags) { if (!(flags & RT6_LOOKUP_F_DST_NOREF) || - !list_empty(&rt->dst.rt_uncached)) + rt->dst.rt_uncached_list) ip6_rt_put(rt); } diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 884d9ab0d50d..153ce16628c1 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -139,6 +139,7 @@ void rt6_uncached_list_add(struct rt6_info *rt) { struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list); + /* Set once and never cleared: non-NULL marks an uncached route. */ rt->dst.rt_uncached_list = ul; spin_lock_bh(&ul->lock); @@ -2726,8 +2727,8 @@ struct dst_entry *ip6_route_output_flags(struct net *net, rcu_read_lock(); dst = ip6_route_output_flags_noref(net, sk, fl6, flags); rt6 = dst_rt6_info(dst); - /* For dst cached in uncached_list, refcnt is already taken. */ - if (list_empty(&rt6->dst.rt_uncached) && !dst_hold_safe(dst)) { + /* For an uncached dst, refcnt is already taken. */ + if (!rt6->dst.rt_uncached_list && !dst_hold_safe(dst)) { dst = &net->ipv6.ip6_null_entry->dst; dst_hold(dst); } @@ -2836,7 +2837,7 @@ INDIRECT_CALLABLE_SCOPE struct dst_entry *ip6_dst_check(struct dst_entry *dst, from = rcu_dereference(rt->from); if (from && (rt->rt6i_flags & RTF_PCPU || - unlikely(!list_empty(&rt->dst.rt_uncached)))) + unlikely(rt->dst.rt_uncached_list))) dst_ret = rt6_dst_from_check(rt, from, cookie); else dst_ret = rt6_check(rt, from, cookie); From 6c43c72748fffd29dec15cd1f31e9a32949bc437 Mon Sep 17 00:00:00 2001 From: Melody Wang Date: Mon, 14 Sep 2026 00:57:17 +0000 Subject: [PATCH 2132/2366] x86/sev: Make vTPM SVSM calls preemption-safe Two functions in the SVSM vTPM guest implementation do not disable preemption when fetching the SVSM Calling Area Address (CAA). The SVSM CAA is a per-CPU structure. When a thread is preempted and migrated to a different CPU after fetching the per-CPU CAA, the SVSM call will execute on the new CPU with the original CPU's CAA. Which is wrong. Move the CAA fetching operation inside svsm_perform_call_protocol() which disables interrupts around the SVSM call and thus runs preemption-safe. Fixes: 770de678bc28 ("x86/sev: Add SVSM vTPM probe/send_command functions") Signed-off-by: Melody Wang Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Stefano Garzarella Cc: stable@vger.kernel.org Link: https://patch.msgid.link/a5bc0d4a2c462a0089109e145c21626b244b2ff0.1789345277.git.huibo.wang@amd.com --- arch/x86/coco/sev/svsm.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/arch/x86/coco/sev/svsm.c b/arch/x86/coco/sev/svsm.c index 916d62cd17dc..2d493d55ff2e 100644 --- a/arch/x86/coco/sev/svsm.c +++ b/arch/x86/coco/sev/svsm.c @@ -74,6 +74,14 @@ int svsm_perform_call_protocol(struct svsm_call *call) flags = native_local_irq_save(); + /* + * 'caa' is a per-CPU variable. To avoid using a stale or incorrect + * 'caa' if the task is preempted or migrated to another CPU after it + * is fetched, always fetch 'caa' and then issue the SVSM call with + * interrupts disabled. This ensures the correct 'caa' is used. + */ + call->caa = svsm_get_caa(); + ghcb = __sev_get_ghcb(&state); do { @@ -321,7 +329,6 @@ int snp_svsm_vtpm_send_command(u8 *buffer) { struct svsm_call call = {}; - call.caa = svsm_get_caa(); call.rax = SVSM_VTPM_CALL(SVSM_VTPM_CMD); call.rcx = __pa(buffer); @@ -345,7 +352,6 @@ bool snp_svsm_vtpm_probe(void) if (!snp_vmpl) return false; - call.caa = svsm_get_caa(); call.rax = SVSM_VTPM_CALL(SVSM_VTPM_QUERY); if (svsm_perform_call_protocol(&call)) From 72b782097e534ab48152dd25aaba40dda5f25f9e Mon Sep 17 00:00:00 2001 From: Jakub Pawlak Date: Mon, 14 Sep 2026 10:43:01 +0200 Subject: [PATCH 2133/2366] accel/ivpu: Use separate flag for job timeout Use separate flag to mark a job timeout as a reason of starting context_abort_work. This allows to distinguish engine reset reason and clearly adjust reset procedure flow. The flag is cleared in ivpu_prepare_for_reset(), which every recovery and suspend path already funnels through, so that the state is clean after recovery. Cc: stable@vger.kernel.org # v7.1+ Fixes: ade00a6c903f ("accel/ivpu: Perform engine reset instead of device recovery on TDR") Signed-off-by: Jakub Pawlak Reviewed-by: Dawid Osuchowski Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20260914084301.894028-1-karol.wachowski@linux.intel.com --- drivers/accel/ivpu/ivpu_drv.c | 3 ++- drivers/accel/ivpu/ivpu_drv.h | 2 +- drivers/accel/ivpu/ivpu_job.c | 7 +++---- drivers/accel/ivpu/ivpu_mmu.c | 1 - drivers/accel/ivpu/ivpu_pm.c | 1 + 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_drv.c b/drivers/accel/ivpu/ivpu_drv.c index 95120957f42a..8c1c87e69f91 100644 --- a/drivers/accel/ivpu/ivpu_drv.c +++ b/drivers/accel/ivpu/ivpu_drv.c @@ -515,6 +515,7 @@ void ivpu_prepare_for_reset(struct ivpu_device *vdev) { ivpu_hw_irq_disable(vdev); disable_irq(vdev->irq); + atomic_set(&vdev->job_timeout_detected, 0); flush_work(&vdev->irq_dct_work); flush_work(&vdev->context_abort_work); flush_work(&vdev->job_destroy_work); @@ -710,7 +711,7 @@ static int ivpu_dev_init(struct ivpu_device *vdev) vdev->context_xa_limit.max = IVPU_USER_CONTEXT_MAX_SSID; atomic64_set(&vdev->unique_id_counter, 0); atomic_set(&vdev->job_timeout_counter, 0); - atomic_set(&vdev->faults_detected, 0); + atomic_set(&vdev->job_timeout_detected, 0); xa_init_flags(&vdev->context_xa, XA_FLAGS_ALLOC | XA_FLAGS_LOCK_IRQ); xa_init_flags(&vdev->submitted_jobs_xa, XA_FLAGS_ALLOC1); xa_init_flags(&vdev->db_xa, XA_FLAGS_ALLOC1); diff --git a/drivers/accel/ivpu/ivpu_drv.h b/drivers/accel/ivpu/ivpu_drv.h index 86d7c9966cac..6f4012926478 100644 --- a/drivers/accel/ivpu/ivpu_drv.h +++ b/drivers/accel/ivpu/ivpu_drv.h @@ -171,7 +171,7 @@ struct ivpu_device { struct xarray submitted_jobs_xa; struct ivpu_ipc_consumer job_done_consumer; atomic_t job_timeout_counter; - atomic_t faults_detected; + atomic_t job_timeout_detected; atomic64_t unique_id_counter; diff --git a/drivers/accel/ivpu/ivpu_job.c b/drivers/accel/ivpu/ivpu_job.c index ebb2c865b09a..4689b8ab519d 100644 --- a/drivers/accel/ivpu/ivpu_job.c +++ b/drivers/accel/ivpu/ivpu_job.c @@ -621,7 +621,6 @@ bool ivpu_job_handle_engine_error(struct ivpu_device *vdev, u32 job_id, u32 job_ * status and ensure both are handled in the same way */ job->file_priv->has_mmu_faults = true; - atomic_set(&vdev->faults_detected, 1); queue_work(system_percpu_wq, &vdev->context_abort_work); return true; } @@ -1175,10 +1174,10 @@ static int reset_engine_and_mark_faulty_contexts(struct ivpu_device *vdev) return ret; /* - * If faults are detected, ignore guilty contexts from engine reset as NPU may not be stuck - * and could return currently running good context and faulty contexts are already marked + * If job timeout is detected, read guilty context from engine reset, for other reasons + * faulty context is already known */ - if (atomic_cmpxchg(&vdev->faults_detected, 1, 0) == 1) + if (atomic_cmpxchg(&vdev->job_timeout_detected, 1, 0) == 0) return 0; num_impacted_contexts = resp.payload.engine_reset_done.num_impacted_contexts; diff --git a/drivers/accel/ivpu/ivpu_mmu.c b/drivers/accel/ivpu/ivpu_mmu.c index 41efd8985fa6..b2025274f91d 100644 --- a/drivers/accel/ivpu/ivpu_mmu.c +++ b/drivers/accel/ivpu/ivpu_mmu.c @@ -964,7 +964,6 @@ void ivpu_mmu_irq_evtq_handler(struct ivpu_device *vdev) file_priv = xa_load(&vdev->context_xa, ssid); if (file_priv) { if (!READ_ONCE(file_priv->has_mmu_faults)) { - atomic_set(&vdev->faults_detected, 1); ivpu_mmu_dump_event(vdev, event); WRITE_ONCE(file_priv->has_mmu_faults, true); } diff --git a/drivers/accel/ivpu/ivpu_pm.c b/drivers/accel/ivpu/ivpu_pm.c index c1ce8329790e..de0becbfdffb 100644 --- a/drivers/accel/ivpu/ivpu_pm.c +++ b/drivers/accel/ivpu/ivpu_pm.c @@ -229,6 +229,7 @@ static void ivpu_job_timeout_work(struct work_struct *work) ivpu_jsm_state_dump(vdev); ivpu_dev_coredump(vdev); + atomic_set(&vdev->job_timeout_detected, 1); queue_work(system_percpu_wq, &vdev->context_abort_work); } From 0d6526f82c3cdefcca47f73f5fc08dc6f335eac6 Mon Sep 17 00:00:00 2001 From: Tim Chen Date: Mon, 21 Sep 2026 17:37:22 -0700 Subject: [PATCH 2134/2366] sched/cache: Keep nr_pref_llc_running in the runnable domain, to fix LLC mis-scheduling bug alb_break_llc() decides whether to break LLC preference during active load balance. It does so by testing that every runnable fair task on the source rq prefers its LLC: env->src_rq->nr_pref_llc_running == env->src_rq->cfs.h_nr_runnable But the two counters cover different sets. nr_pref_llc_running is updated in account_llc_enqueue()/account_llc_dequeue(), next to cfs_rq->nr_queued, so it follows queued tasks. h_nr_runnable is updated in set_delayed()/ clear_delayed() and drops delay-dequeued tasks. So under DELAY_DEQUEUE, a preferring task that goes to sleep stays counted in nr_pref_llc_running while h_nr_runnable falls. The equality then breaks, alb_break_llc() returns false, and active balance is free to pull a task off its preferred LLC. Active balance only moves runnable tasks, and this is the only LLC check it consults: once the stopper runs, LBF_ACTIVE_LB skips the per-task test in can_migrate_task(). The runnable set is the one we want. Fix it on the counter side. A task should be counted in nr_pref_llc_running exactly while it is both queued on its preferred LLC (pref_llc_queued) and runnable (!sched_delayed). Define that membership once in task_pref_llc_runnable(), and adjust the counter only through pref_llc_running_inc()/pref_llc_running_dec() from the four sites that change either input: account_llc_enqueue(), account_llc_dequeue(), set_delayed() and clear_delayed(). Gating every update on the same predicate keeps the delay, wake and dequeue paths from double-counting or underflowing; see the comments at those sites for the ordering. nr_llc_running and sd->llc_counts are not touched and stay on queued semantics. Fixes: 714059f79ff0 ("sched/cache: Handle moving single tasks to/from their preferred LLC") Closes: https://lore.kernel.org/lkml/20260827135000.735138-1-zhanxusheng@xiaomi.com/ Reported-by: Zhan Xusheng Suggested-by: Chen Yu Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Kayra Cizmeci Cc: # v7.2.x Link: https://patch.msgid.link/06af61afedac32e6477f57feb4d658f6c411c3af.1790035273.git.tim.c.chen@linux.intel.com --- kernel/sched/fair.c | 63 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 7455a83a6a99..de3d589fa8eb 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1546,6 +1546,28 @@ static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p, (scale * per_cpu(sd_llc_size, cpu))); } +/* + * A task counts in nr_pref_llc_running while it is queued on its preferred + * LLC (pref_llc_queued) and runnable (!sched_delayed), keeping the counter in + * the runnable domain so alb_break_llc() can compare it with h_nr_runnable. + */ +static bool task_pref_llc_runnable(struct task_struct *p) +{ + return p->pref_llc_queued && !p->se.sched_delayed; +} + +static void pref_llc_running_inc(struct rq *rq, struct task_struct *p) +{ + if (task_pref_llc_runnable(p)) + rq->nr_pref_llc_running++; +} + +static void pref_llc_running_dec(struct rq *rq, struct task_struct *p) +{ + if (task_pref_llc_runnable(p)) + rq->nr_pref_llc_running--; +} + static void account_llc_enqueue(struct rq *rq, struct task_struct *p) { int pref_llc, pref_llc_queued; @@ -1557,7 +1579,6 @@ static void account_llc_enqueue(struct rq *rq, struct task_struct *p) pref_llc_queued = (pref_llc == task_llc(p)); rq->nr_llc_running++; - rq->nr_pref_llc_running += pref_llc_queued; /* * Record whether p is enqueued on its preferred @@ -1575,6 +1596,9 @@ static void account_llc_enqueue(struct rq *rq, struct task_struct *p) */ p->pref_llc_queued = pref_llc_queued; + /* Skipped while delayed; clear_delayed() adds it back on wake. */ + pref_llc_running_inc(rq, p); + sd = rcu_dereference_all(rq->sd); if (sd && (unsigned int)pref_llc < sd->llc_max) sd->llc_counts[pref_llc]++; @@ -1591,7 +1615,12 @@ static void account_llc_dequeue(struct rq *rq, struct task_struct *p) rq->nr_llc_running--; if (p->pref_llc_queued) { - rq->nr_pref_llc_running--; + /* + * Skipped if still delayed (set_delayed() already removed it); + * clearing pref_llc_queued below also stops clear_delayed() + * from re-adding it. + */ + pref_llc_running_dec(rq, p); /* * Update the status in case * other logic might query @@ -1995,6 +2024,7 @@ void init_sched_mm(struct task_struct *p) * polluting account_llc_enqueue(). */ p->preferred_llc = -1; + p->pref_llc_queued = 0; } #else /* CONFIG_SCHED_CACHE */ @@ -2016,6 +2046,10 @@ static void account_llc_enqueue(struct rq *rq, struct task_struct *p) {} static void account_llc_dequeue(struct rq *rq, struct task_struct *p) {} +static void pref_llc_running_inc(struct rq *rq, struct task_struct *p) {} + +static void pref_llc_running_dec(struct rq *rq, struct task_struct *p) {} + #endif /* CONFIG_SCHED_CACHE */ /* @@ -6390,15 +6424,27 @@ static __always_inline void return_cfs_rq_runtime(struct cfs_rq *cfs_rq); static void set_delayed(struct sched_entity *se) { - se->sched_delayed = 1; - /* * Delayed se of cfs_rq have no tasks queued on them. * Do not adjust h_nr_runnable since __dequeue_task() * will account it for blocked tasks. + * + * This check can be removed because when flat pick + * patches get merged as only task can get delayed, + * same for clear_delayed(). */ - if (!entity_is_task(se)) + if (!entity_is_task(se)) { + se->sched_delayed = 1; return; + } + + /* + * Drop a task leaving the runnable set. + * Needs to be called before sched_delayed is set. + * clear_delayed() mirrors this after clearing the flag. + */ + pref_llc_running_dec(rq_of(cfs_rq_of(se)), task_of(se)); + se->sched_delayed = 1; for_each_sched_entity(se) { struct cfs_rq *cfs_rq = cfs_rq_of(se); @@ -6420,6 +6466,13 @@ static void clear_delayed(struct sched_entity *se) if (!entity_is_task(se)) return; + /* + * Re-add on wake, after sched_delayed is cleared. On a final delayed + * dequeue account_llc_dequeue() already cleared pref_llc_queued, so + * this does nothing. + */ + pref_llc_running_inc(rq_of(cfs_rq_of(se)), task_of(se)); + for_each_sched_entity(se) { struct cfs_rq *cfs_rq = cfs_rq_of(se); From d6013e2465d98d524b030a81c1223882a1bb7e4c Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Mon, 21 Sep 2026 17:37:23 -0700 Subject: [PATCH 2135/2366] sched/cache: Honor migrate_llc_task semantics in active load balance, to fix LLC mis-scheduling bug Cache aware scheduling introduced the migrate_llc_task migration type to direct tasks toward their preferred LLC, but its semantics can be lost when passive load balance falls back to active load balance (ALB). This may allow ALB to select a candidate whose preferred LLC does not match the destination, moving it away from its preferred LLC. Example scenario: src_rq has two runnable tasks, p1 and p2. p1 prefers dst_rq (dst_llc), while p2 prefers src_rq (src_llc). In this case, migrate_llc_task is set because src_rq has at least one task, p1, that wants to migrate to dst_rq. In ALB, can_migrate_task() finds p2 and returns true for it, thus moving p2 out of its preferred LLC. Solution: The CPU stopper in ALB constructs a fresh lb_env that does not inherit migration_type from the passive load-balance pass. Two approaches are possible: (a) Add a new member to struct rq so ALB can inherit migrate_llc_task from the passive LB that triggered it. (b) Define a new flag LBF_ACTIVE_LB_LLC and select the stopper callback at kick time to preserve the migration semantics across the asynchronous boundary. We choose (b) because it avoids passing migration_type through the stopper, which would affect the meaning of migration_type for delayed-dequeue tasks. Fixes: e4c9a4cb244a ("sched/cache: Add migrate_llc_task migration type for cache-aware balancing") Suggested-by: Chen Yu Signed-off-by: Lu Wang Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Tim Chen Reviewed-by: Chen Yu Cc: # v7.2.x Link: https://patch.msgid.link/cb39f64a17fc2b76097264aaec74a2d6dfff4315.1790035273.git.tim.c.chen@linux.intel.com --- kernel/sched/fair.c | 57 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index de3d589fa8eb..514bd54ccd56 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -10448,6 +10448,7 @@ enum migration_type { #define LBF_SOME_PINNED 0x08 #define LBF_ACTIVE_LB 0x10 #define LBF_LLC_PINNED 0x20 +#define LBF_ACTIVE_LB_LLC 0x40 struct lb_env { struct sched_domain *sd; @@ -10866,6 +10867,21 @@ alb_break_llc(struct lb_env *env) return false; } +/* + * Returns true if p's preferred LLC does not match the destination CPU + * under migrate_llc_task semantics. Passive LB passes migrate_llc_task + * in env->migration_type, while active LB carries LBF_ACTIVE_LB_LLC in + * env->flags to avoid overwriting env->migration_type. + */ +static inline bool +migrate_llc_task_wrong_dst(struct task_struct *p, struct lb_env *env) +{ + return sched_cache_enabled() && + (env->migration_type == migrate_llc_task || + env->flags & LBF_ACTIVE_LB_LLC) && + READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu); +} + /* * Check if migrating task p from env->src_cpu to * env->dst_cpu breaks LLC localiy. @@ -10894,8 +10910,7 @@ static bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) * run on env->dst_cpu, skip the tasks do not prefer * env->dst_cpu, and find the one that prefers. */ - if (env->migration_type == migrate_llc_task && - READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu)) + if (migrate_llc_task_wrong_dst(p, env)) return true; if (can_migrate_llc_task(env, p) != mig_forbid) @@ -10917,6 +10932,12 @@ alb_break_llc(struct lb_env *env) return false; } +static inline bool +migrate_llc_task_wrong_dst(struct task_struct *p, struct lb_env *env) +{ + return false; +} + static inline bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) { @@ -11016,7 +11037,7 @@ int can_migrate_task(struct task_struct *p, struct lb_env *env) * 4) too many balance attempts have failed. */ if (env->flags & LBF_ACTIVE_LB) - return 1; + return !migrate_llc_task_wrong_dst(p, env); degrades = migrate_degrades_locality(p, env); if (!degrades) { @@ -13415,6 +13436,20 @@ static int need_active_balance(struct lb_env *env) } static int active_load_balance_cpu_stop(void *data); +static int active_load_balance_llc_cpu_stop(void *data); + +/* + * migration_type is checked elsewhere to decide migration policy, so + * it shouldn't be repurposed just to flag an LLC-directed active + * balance across the stopper. Pick the callback here instead. + */ +static inline cpu_stop_fn_t alb_stop_fn(struct lb_env *env) +{ + if (env->migration_type == migrate_llc_task) + return active_load_balance_llc_cpu_stop; + + return active_load_balance_cpu_stop; +} static int should_we_balance(struct lb_env *env) { @@ -13760,7 +13795,7 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq, } if (active_balance) { stop_one_cpu_nowait(cpu_of(busiest), - active_load_balance_cpu_stop, busiest, + alb_stop_fn(&env), busiest, &busiest->active_balance_work); } preempt_enable(); @@ -13865,7 +13900,7 @@ update_next_balance(struct sched_domain *sd, unsigned long *next_balance) * least 1 task to be running on each physical CPU where possible, and * avoids physical / logical imbalances. */ -static int active_load_balance_cpu_stop(void *data) +static int __active_load_balance_cpu_stop(void *data, unsigned int lb_flags) { struct rq *busiest_rq = data; int busiest_cpu = cpu_of(busiest_rq); @@ -13915,7 +13950,7 @@ static int active_load_balance_cpu_stop(void *data) .src_cpu = busiest_rq->cpu, .src_rq = busiest_rq, .idle = CPU_IDLE, - .flags = LBF_ACTIVE_LB, + .flags = LBF_ACTIVE_LB | lb_flags, }; schedstat_inc(sd->alb_count); @@ -13943,6 +13978,16 @@ static int active_load_balance_cpu_stop(void *data) return 0; } +static int active_load_balance_cpu_stop(void *data) +{ + return __active_load_balance_cpu_stop(data, 0); +} + +static int active_load_balance_llc_cpu_stop(void *data) +{ + return __active_load_balance_cpu_stop(data, LBF_ACTIVE_LB_LLC); +} + /* * Scale the max sched_balance_rq interval with the number of CPUs in the system. * This trades load-balance latency on larger machines for less cross talk. From 28f9c0e0a0b94c5d3e1b634db545f6e1f94858c5 Mon Sep 17 00:00:00 2001 From: Tim Chen Date: Mon, 21 Sep 2026 17:37:24 -0700 Subject: [PATCH 2136/2366] sched/cache: Decouple sched_cache_group from mm to fix UAF Currently the sched cache grouping is by mm and the scheduling statistics sched_cache_stat lives in the mm structure. This ties the life cycle of scheduling stats with mm. In account_mm_sched(), the scheduling stats are accessed by task->mm->sc_stat. However, a task may be switching mm on one CPU when another CPU is running account_mm_sched(), and possibly accessing the old mm that was freed. This problem was found when running tests with KASAN by Hyunwoo: https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/ Instead of serializing the mm access by introducing extra acquisition of rq lock in the mm free path, extract sched_cache_stat from mm_struct, rename it as sched_cache_group and manage its life cycle apart from mm_struct with its own ref counting. This allows us in the next patch access sched_cache_group directly from task, and add a refcount on sched_cache_group when a task links to it. This prevents the use after free issue when accessing stale and released old mm and its sched cache stat a task switches to a new mm while account_mm_sched() is done elsewhere. The other benefit of this restructure is in the future, the grouping of tasks to a LLC would have the flexibility to be associated with a user defined grouping, or cgroup, cookie group, numa_group or others instead of just with a single mm address space. Rename sched_cache_stat to sched_cache_group and turn it into a refcounted object allocated from mm_struct. The mm_struct now holds a pointer (sched_cache_grp) to this object instead of embedding it. Fixes: df0d98475954 ("sched/cache: Introduce infrastructure for cache-aware load balancing") Closes: https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/ Closes: https://lore.kernel.org/all/343a7e07-7fad-4979-9c9b-82ec038c293c@linux.dev/ Reported-by: Hyunwoo Kim Reported-by: Zenghui Yu (Huawei) Co-developed-by: Chen Yu Signed-off-by: Chen Yu Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: #7.2.x Link: https://patch.msgid.link/91fd1e3266707c865bc9abecfb3e17bc676712df.1790035273.git.tim.c.chen@linux.intel.com --- include/linux/mm_types.h | 15 ++-- include/linux/sched.h | 6 +- kernel/exit.c | 11 ++- kernel/sched/fair.c | 173 ++++++++++++++++++++++++++++----------- 4 files changed, 144 insertions(+), 61 deletions(-) diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index 6d815f6440c9..f3e5a2fadbe5 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -1226,7 +1226,7 @@ struct mm_struct { struct mm_mm_cid mm_cid; /* sched_cache related statistics */ - struct sched_cache_stat sc_stat; + struct sched_cache_group *sched_cache_grp; #ifdef CONFIG_MMU atomic_long_t pgtables_bytes; /* size of all page tables */ #endif @@ -1624,8 +1624,9 @@ static inline unsigned int mm_cid_size(void) #endif /* CONFIG_SCHED_MM_CID */ #ifdef CONFIG_SCHED_CACHE -void mm_init_sched(struct mm_struct *mm, - struct sched_cache_time __percpu *pcpu_sched); +int mm_init_sched(struct mm_struct *mm, + struct sched_cache_time __percpu *pcpu_sched); +void mm_destroy_sched(struct mm_struct *mm); static inline int mm_alloc_sched_noprof(struct mm_struct *mm) { @@ -1635,17 +1636,11 @@ static inline int mm_alloc_sched_noprof(struct mm_struct *mm) if (!pcpu_sched) return -ENOMEM; - mm_init_sched(mm, pcpu_sched); - return 0; + return mm_init_sched(mm, pcpu_sched); } #define mm_alloc_sched(...) alloc_hooks(mm_alloc_sched_noprof(__VA_ARGS__)) -static inline void mm_destroy_sched(struct mm_struct *mm) -{ - free_percpu(mm->sc_stat.pcpu_sched); - mm->sc_stat.pcpu_sched = NULL; -} #else /* !CONFIG_SCHED_CACHE */ static inline int mm_alloc_sched(struct mm_struct *mm) { return 0; } diff --git a/include/linux/sched.h b/include/linux/sched.h index 705970d07614..e14ad43522c8 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -2405,7 +2405,7 @@ struct sched_cache_time { unsigned long epoch; }; -struct sched_cache_stat { +struct sched_cache_group { struct sched_cache_time __percpu *pcpu_sched; raw_spinlock_t lock; unsigned long epoch; @@ -2413,11 +2413,13 @@ struct sched_cache_stat { unsigned long next_scan; unsigned long footprint; int cpu; + refcount_t refcnt; + struct rcu_head rcu; } ____cacheline_aligned_in_smp; #else -struct sched_cache_stat { }; +struct sched_cache_group { }; #endif diff --git a/kernel/exit.c b/kernel/exit.c index 424c44a42a4d..024350e9b48c 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -558,18 +558,23 @@ void mm_update_next_owner(struct mm_struct *mm) */ static void exit_mm_sched_cache(struct mm_struct *mm) { + struct sched_cache_group *grp; unsigned long fp, sub; if (!current->total_numa_faults) return; /* * No lock protection due to performance considerations. - * Make sure mm->sc_stat.footprint does not become + * Make sure the group footprint does not become * negative. */ - fp = READ_ONCE(mm->sc_stat.footprint); + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp) + return; + + fp = READ_ONCE(grp->footprint); sub = min(fp, current->total_numa_faults); - WRITE_ONCE(mm->sc_stat.footprint, fp - sub); + WRITE_ONCE(grp->footprint, fp - sub); } #else static inline void exit_mm_sched_cache(struct mm_struct *mm) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 514bd54ccd56..f0a9586332fd 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1492,12 +1492,17 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) return true; if (static_branch_likely(&sched_numa_balancing)) { + struct sched_cache_group *grp = READ_ONCE(mm->sched_cache_grp); + + if (!grp) + return true; + /* * TBD: RDT exclusive LLC ways reserved should be * excluded. */ llc = sd->llc_bytes; - footprint = READ_ONCE(mm->sc_stat.footprint); + footprint = READ_ONCE(grp->footprint); /* * Scale the LLC size by 256*llc_aggr_tolerance @@ -1529,6 +1534,7 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p, int cpu) { + struct sched_cache_group *grp; int scale; if (get_nr_threads(p) <= 1) @@ -1542,7 +1548,11 @@ static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p, if (scale == INT_MAX) return false; - return !fits_capacity((mm->sc_stat.nr_running_avg * cpu_smt_num_threads), + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp) + return true; + + return !fits_capacity((READ_ONCE(grp->nr_running_avg) * cpu_smt_num_threads), (scale * per_cpu(sd_llc_size, cpu))); } @@ -1648,12 +1658,20 @@ static void account_llc_dequeue(struct rq *rq, struct task_struct *p) } } -void mm_init_sched(struct mm_struct *mm, - struct sched_cache_time __percpu *_pcpu_sched) +int mm_init_sched(struct mm_struct *mm, + struct sched_cache_time __percpu *_pcpu_sched) { + struct sched_cache_group *grp; unsigned long epoch = 0; int i; + grp = kzalloc_obj(*grp); + if (!grp) { + free_percpu(_pcpu_sched); + mm->sched_cache_grp = NULL; + return -ENOMEM; + } + for_each_possible_cpu(i) { struct sched_cache_time *pcpu_sched = per_cpu_ptr(_pcpu_sched, i); struct rq *rq = cpu_rq(i); @@ -1664,18 +1682,51 @@ void mm_init_sched(struct mm_struct *mm, epoch = rq->cpu_epoch; } - raw_spin_lock_init(&mm->sc_stat.lock); - mm->sc_stat.epoch = epoch; - mm->sc_stat.cpu = -1; - mm->sc_stat.next_scan = jiffies; - mm->sc_stat.nr_running_avg = 0; - mm->sc_stat.footprint = 0; + raw_spin_lock_init(&grp->lock); + grp->epoch = epoch; + grp->cpu = -1; + grp->next_scan = jiffies; + grp->nr_running_avg = 0; + grp->footprint = 0; + refcount_set(&grp->refcnt, 1); /* - * The update to mm->sc_stat should not be reordered - * before initialization to mm's other fields, in case + * The update to grp->pcpu_sched should not be reordered + * before initialization to grp's other fields, in case * the readers may get invalid mm_sched_epoch, etc. */ - smp_store_release(&mm->sc_stat.pcpu_sched, _pcpu_sched); + smp_store_release(&grp->pcpu_sched, _pcpu_sched); + /* + * Publish the group last. Not every reader qualifies it by + * grp->pcpu_sched - can_migrate_llc_task() only checks that the + * pointer is non-NULL before reading grp->footprint and + * grp->nr_running_avg - so a reachable group must already be + * fully initialized. + */ + smp_store_release(&mm->sched_cache_grp, grp); + return 0; +} + +static void sched_cache_group_free_rcu(struct rcu_head *rcu) +{ + struct sched_cache_group *grp = + container_of(rcu, struct sched_cache_group, rcu); + + free_percpu(grp->pcpu_sched); + kfree(grp); +} + +static void sched_cache_group_put(struct sched_cache_group *grp) +{ + if (!grp || !refcount_dec_and_test(&grp->refcnt)) + return; + + call_rcu(&grp->rcu, sched_cache_group_free_rcu); +} + +void mm_destroy_sched(struct mm_struct *mm) +{ + sched_cache_group_put(mm->sched_cache_grp); + mm->sched_cache_grp = NULL; } /* because why would C be fully specified */ @@ -1729,11 +1780,16 @@ static unsigned long fraction_mm_sched(struct rq *rq, static int get_pref_llc(struct task_struct *p, struct mm_struct *mm) { int mm_sched_llc = -1, mm_sched_cpu; + struct sched_cache_group *grp; if (!mm) return -1; - mm_sched_cpu = READ_ONCE(mm->sc_stat.cpu); + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp) + return -1; + + mm_sched_cpu = READ_ONCE(grp->cpu); if (mm_sched_cpu != -1) { mm_sched_llc = llc_id(mm_sched_cpu); @@ -1764,6 +1820,7 @@ static inline void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) { struct sched_cache_time *pcpu_sched; + struct sched_cache_group *grp; struct mm_struct *mm = p->mm; int mm_sched_llc = -1; unsigned long epoch; @@ -1777,10 +1834,14 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) * init_task, kthreads and user thread created * by user_mode_thread() don't have mm. */ - if (!mm || !mm->sc_stat.pcpu_sched) + if (!mm) return; - pcpu_sched = per_cpu_ptr(mm->sc_stat.pcpu_sched, cpu_of(rq)); + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp || !grp->pcpu_sched) + return; + + pcpu_sched = per_cpu_ptr(grp->pcpu_sched, cpu_of(rq)); scoped_guard (raw_spinlock, &rq->cpu_epoch_lock) { __update_mm_sched(rq, pcpu_sched); @@ -1793,11 +1854,11 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) * If this process hasn't hit task_cache_work() for a while invalidate * its preferred state. */ - if ((long)(epoch - READ_ONCE(mm->sc_stat.epoch)) > llc_epoch_affinity_timeout || + if ((long)(epoch - READ_ONCE(grp->epoch)) > llc_epoch_affinity_timeout || invalid_llc_nr(mm, p, cpu_of(rq)) || exceed_llc_capacity(mm, cpu_of(rq))) { - if (READ_ONCE(mm->sc_stat.cpu) != -1) - WRITE_ONCE(mm->sc_stat.cpu, -1); + if (READ_ONCE(grp->cpu) != -1) + WRITE_ONCE(grp->cpu, -1); } mm_sched_llc = get_pref_llc(p, mm); @@ -1814,30 +1875,35 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) static void task_tick_cache(struct rq *rq, struct task_struct *p) { struct callback_head *work = &p->cache_work; + struct sched_cache_group *grp; struct mm_struct *mm = p->mm; unsigned long epoch; if (!sched_cache_enabled()) return; - if (!mm || p->flags & PF_KTHREAD || - !mm->sc_stat.pcpu_sched) + if (!mm || p->flags & PF_KTHREAD) + return; + + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp || !grp->pcpu_sched) return; epoch = rq->cpu_epoch; /* avoid moving backwards */ - if (time_after_eq(mm->sc_stat.epoch, epoch)) + if (time_after_eq(grp->epoch, epoch)) return; - guard(raw_spinlock)(&mm->sc_stat.lock); + guard(raw_spinlock)(&grp->lock); if (work->next == work) { task_work_add(p, work, TWA_RESUME); - WRITE_ONCE(mm->sc_stat.epoch, epoch); + WRITE_ONCE(grp->epoch, epoch); } } -static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p) +static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p, + struct sched_cache_group *grp) { #ifdef CONFIG_NUMA_BALANCING int cpu, curr_cpu, nid, pref_nid; @@ -1845,7 +1911,7 @@ static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p) if (!static_branch_likely(&sched_numa_balancing)) goto out; - cpu = READ_ONCE(p->mm->sc_stat.cpu); + cpu = READ_ONCE(grp->cpu); if (cpu != -1) nid = cpu_to_node(cpu); curr_cpu = task_cpu(p); @@ -1906,6 +1972,7 @@ static void task_cache_work(struct callback_head *work) unsigned long next_scan, now = jiffies; struct task_struct *p = current, *cur; unsigned long curr_m_a_occ = 0; + struct sched_cache_group *grp; struct mm_struct *mm = p->mm; unsigned long m_a_occ = 0; cpumask_var_t cpus; @@ -1917,12 +1984,16 @@ static void task_cache_work(struct callback_head *work) if (p->flags & PF_EXITING) return; - next_scan = READ_ONCE(mm->sc_stat.next_scan); + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp) + return; + + next_scan = READ_ONCE(grp->next_scan); if (time_before(now, next_scan)) return; /* only 1 thread is allowed to scan */ - if (!try_cmpxchg(&mm->sc_stat.next_scan, &next_scan, + if (!try_cmpxchg(&grp->next_scan, &next_scan, now + max_t(unsigned long, READ_ONCE(llc_epoch_period), 1))) return; @@ -1930,8 +2001,8 @@ static void task_cache_work(struct callback_head *work) curr_cpu = task_cpu(p); if (invalid_llc_nr(mm, p, curr_cpu) || exceed_llc_capacity(mm, curr_cpu)) { - if (READ_ONCE(mm->sc_stat.cpu) != -1) - WRITE_ONCE(mm->sc_stat.cpu, -1); + if (READ_ONCE(grp->cpu) != -1) + WRITE_ONCE(grp->cpu, -1); return; } @@ -1942,7 +2013,7 @@ static void task_cache_work(struct callback_head *work) scoped_guard (cpus_read_lock) { guard(rcu)(); - get_scan_cpumasks(cpus, p); + get_scan_cpumasks(cpus, p, grp); for_each_cpu(cpu, cpus) { /* XXX sched_cluster_active */ @@ -1955,7 +2026,7 @@ static void task_cache_work(struct callback_head *work) for_each_cpu(i, sched_domain_span(sd)) { occ = fraction_mm_sched(cpu_rq(i), - per_cpu_ptr(mm->sc_stat.pcpu_sched, i)); + per_cpu_ptr(grp->pcpu_sched, i)); a_occ += occ; if (occ > m_occ) { m_occ = occ; @@ -1988,7 +2059,7 @@ static void task_cache_work(struct callback_head *work) m_a_cpu = m_cpu; } - if (llc_id(cpu) == llc_id(READ_ONCE(mm->sc_stat.cpu))) + if (llc_id(cpu) == llc_id(READ_ONCE(grp->cpu))) curr_m_a_occ = a_occ; cpumask_andnot(cpus, cpus, sched_domain_span(sd)); @@ -1997,7 +2068,7 @@ static void task_cache_work(struct callback_head *work) if (m_a_occ > (2 * curr_m_a_occ)) { /* - * Avoid switching sc_stat.cpu too fast. + * Avoid switching sched_cache_grp->cpu too fast. * The reason to choose 2X is because: * 1. It is better to keep the preferred LLC stable, * rather than changing it frequently and cause migrations @@ -2006,10 +2077,10 @@ static void task_cache_work(struct callback_head *work) * 3. 2X is chosen based on test results, as it delivers * the optimal performance gain so far. */ - WRITE_ONCE(mm->sc_stat.cpu, m_a_cpu); + WRITE_ONCE(grp->cpu, m_a_cpu); } - update_avg_scale(&mm->sc_stat.nr_running_avg, nr_running); + update_avg_scale(&grp->nr_running_avg, nr_running); free_cpumask_var(cpus); } @@ -3726,6 +3797,7 @@ static int preferred_group_nid(struct task_struct *p, int nid) static void task_numa_placement(struct task_struct *p) __context_unsafe(/* conditional locking */) { + struct sched_cache_group __maybe_unused *grp; int seq, nid, max_nid = NUMA_NO_NODE; unsigned long max_faults = 0; unsigned long fault_types[2] = { 0, 0 }; @@ -3818,19 +3890,23 @@ static void task_numa_placement(struct task_struct *p) * heuristic and occasional lost updates are tolerable. * * If a task exits, its corresponding footprint must - * be subtracted from the mm->sc_stat.footprint, otherwise - * the mm->sc_stat.footprint will not converge: - * the exiting thread's footprint remains unchanged/undecayed - * in mm->sc_stat.footprint. See exit_mm(). + * be subtracted from the mm->sched_cache_grp->footprint, + * otherwise the mm->sched_cache_grp->footprint will not + * converge: the exiting thread's footprint remains + * unchanged/undecayed in mm->sched_cache_grp->footprint. + * See exit_mm(). * * Lost updates and unsynchronized subtraction * in exit_mm() can cause footprint + diff to * go negative. Clamp to zero to prevent the * unsigned footprint from wrapping. */ - new_fp = (long)READ_ONCE(p->mm->sc_stat.footprint) + diff; - WRITE_ONCE(p->mm->sc_stat.footprint, - max(new_fp, 0L)); + grp = READ_ONCE(p->mm->sched_cache_grp); + if (!grp) + continue; + + new_fp = (long)READ_ONCE(grp->footprint) + diff; + WRITE_ONCE(grp->footprint, max(new_fp, 0L)); #endif } @@ -10778,6 +10854,7 @@ static inline bool task_misfits_asym_cpu(struct lb_env *env, struct task_struct static enum llc_mig can_migrate_llc_task(struct lb_env *env, struct task_struct *p) { + struct sched_cache_group *grp; struct mm_struct *mm; bool to_pref; int cpu, src_cpu, dst_cpu; @@ -10791,15 +10868,19 @@ static enum llc_mig can_migrate_llc_task(struct lb_env *env, if (!mm) return mig_unrestricted; - cpu = READ_ONCE(mm->sc_stat.cpu); + grp = READ_ONCE(mm->sched_cache_grp); + if (!grp) + return mig_unrestricted; + + cpu = READ_ONCE(grp->cpu); if (cpu < 0 || cpus_share_cache(src_cpu, dst_cpu)) return mig_unrestricted; /* skip cache aware load balance for too many threads */ if (invalid_llc_nr(mm, p, dst_cpu) || exceed_llc_capacity(mm, dst_cpu)) { - if (READ_ONCE(mm->sc_stat.cpu) != -1) - WRITE_ONCE(mm->sc_stat.cpu, -1); + if (READ_ONCE(grp->cpu) != -1) + WRITE_ONCE(grp->cpu, -1); return mig_unrestricted; } From b636fef85bda7d1bab9c0a45067ab1508d79d946 Mon Sep 17 00:00:00 2001 From: Tim Chen Date: Mon, 21 Sep 2026 17:37:25 -0700 Subject: [PATCH 2137/2366] sched/cache: Introduce task_struct->sched_cache_grp to fix UAF Add a sched_cache_grp pointer to task_struct so that scheduler code can access the cache group directly via the task, without going through mm->sched_cache_grp. This decouples the scheduler's hot-path accesses from the mm_struct. Each task holds its own refcount on the sched_cache_group, separate from the reference held by its mm_struct. The reference is acquired in copy_mm() (fork) and exec_mmap() (exec), and released in exit_mm(). This fixes the use-after-free when account_mm_sched() reaches the group through a task whose mm is being switched, as reported by Hyunwoo: https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/ Convert all scheduler code in fair.c and exit.c to use p->sched_cache_grp instead of p->mm->sched_cache_grp. Keep the fork/exec/exit reference management out of the generic mm paths: add sched_cache_fork(), sched_cache_fork_cleanup(), sched_cache_exec_mmap() and sched_cache_exit_mm() in kernel/sched/cache_sched.c (with empty stubs for !CONFIG_SCHED_CACHE), so fs/exec.c, kernel/fork.c and kernel/exit.c each call one helper instead of open-coding the refcounting under #ifdef. Also add sched_cache_group_get() and task_cache_group_get(). Fixes: df0d98475954 ("sched/cache: Introduce infrastructure for cache-aware load balancing") Closes: https://lore.kernel.org/lkml/apPb-Dr4nPYuHQOK@v4bel/ Closes: https://lore.kernel.org/all/343a7e07-7fad-4979-9c9b-82ec038c293c@linux.dev/ Reported-by: Hyunwoo Kim Reported-by: Zenghui Yu (Huawei) Co-developed-by: Chen Yu Signed-off-by: Chen Yu Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: #7.2.x Link: https://patch.msgid.link/ae7081dc54736bf115215f9867abb2711a7403fb.1790035273.git.tim.c.chen@linux.intel.com --- fs/exec.c | 1 + include/linux/sched.h | 14 +++ kernel/exit.c | 33 +------ kernel/fork.c | 2 + kernel/sched/fair.c | 196 +++++++++++++++++++++++++++++------------- 5 files changed, 154 insertions(+), 92 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 819643408e6d..a5269b5e00df 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -882,6 +882,7 @@ static int exec_mmap(struct linux_binprm *bprm) active_mm = tsk->active_mm; tsk->active_mm = mm; tsk->mm = mm; + sched_cache_exec_mmap(tsk, mm); mm_init_cid(mm, tsk); exec_state = task_exec_state_replace(tsk, exec_state); /* diff --git a/include/linux/sched.h b/include/linux/sched.h index e14ad43522c8..d35ae49a991f 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1433,6 +1433,7 @@ struct task_struct { #ifdef CONFIG_SCHED_CACHE struct callback_head cache_work; + struct sched_cache_group __rcu *sched_cache_grp; int preferred_llc; /* 1: task was enqueued to its preferred LLC, 0 otherwise */ int pref_llc_queued; @@ -2417,10 +2418,23 @@ struct sched_cache_group { struct rcu_head rcu; } ____cacheline_aligned_in_smp; +struct sched_cache_group *sched_cache_group_get(struct sched_cache_group *grp); +struct sched_cache_group *task_cache_group_get(struct task_struct *p); + +void sched_cache_fork(struct task_struct *p); +void sched_cache_fork_cleanup(struct task_struct *p); +void sched_cache_exec_mmap(struct task_struct *p, struct mm_struct *mm); +void sched_cache_exit_mm(struct task_struct *p); + #else struct sched_cache_group { }; +static inline void sched_cache_fork(struct task_struct *p) { } +static inline void sched_cache_fork_cleanup(struct task_struct *p) { } +static inline void sched_cache_exec_mmap(struct task_struct *p, struct mm_struct *mm) { } +static inline void sched_cache_exit_mm(struct task_struct *p) { } + #endif #ifndef MODULE diff --git a/kernel/exit.c b/kernel/exit.c index 024350e9b48c..282328d2b4cf 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -551,37 +551,6 @@ void mm_update_next_owner(struct mm_struct *mm) } #endif /* CONFIG_MEMCG */ -#if defined(CONFIG_SCHED_CACHE) && defined(CONFIG_NUMA_BALANCING) -/* - * Subtract the memory footprint of the current task from - * mm. - */ -static void exit_mm_sched_cache(struct mm_struct *mm) -{ - struct sched_cache_group *grp; - unsigned long fp, sub; - - if (!current->total_numa_faults) - return; - /* - * No lock protection due to performance considerations. - * Make sure the group footprint does not become - * negative. - */ - grp = READ_ONCE(mm->sched_cache_grp); - if (!grp) - return; - - fp = READ_ONCE(grp->footprint); - sub = min(fp, current->total_numa_faults); - WRITE_ONCE(grp->footprint, fp - sub); -} -#else -static inline void exit_mm_sched_cache(struct mm_struct *mm) -{ -} -#endif /* CONFIG_SCHED_CACHE CONFIG_NUMA_BALANCING */ - /* * Turn us into a lazy TLB process if we * aren't already.. @@ -594,7 +563,7 @@ static void exit_mm(void) if (!mm) return; - exit_mm_sched_cache(mm); + sched_cache_exit_mm(current); mmap_read_lock(mm); mmgrab_lazy_tlb(mm); diff --git a/kernel/fork.c b/kernel/fork.c index 5ef413368912..10f2d05d816a 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1599,6 +1599,7 @@ static int copy_mm(u64 clone_flags, struct task_struct *tsk) tsk->mm = mm; tsk->active_mm = mm; + sched_cache_fork(tsk); return 0; } @@ -2602,6 +2603,7 @@ __latent_entropy struct task_struct *copy_process( bad_fork_cleanup_namespaces: exit_nsproxy_namespaces(p); bad_fork_cleanup_mm: + sched_cache_fork_cleanup(p); if (p->mm) { mm_clear_owner(p->mm, p); mmput(p->mm); diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index f0a9586332fd..974a7dfe3215 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1478,7 +1478,7 @@ static inline int get_sched_cache_scale(int mul) return (1 + (tol - 1) * mul); } -static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) +static bool exceed_llc_capacity(struct sched_cache_group *grp, int cpu) { #ifdef CONFIG_NUMA_BALANCING unsigned long llc, footprint; @@ -1492,11 +1492,6 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) return true; if (static_branch_likely(&sched_numa_balancing)) { - struct sched_cache_group *grp = READ_ONCE(mm->sched_cache_grp); - - if (!grp) - return true; - /* * TBD: RDT exclusive LLC ways reserved should be * excluded. @@ -1531,10 +1526,9 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) return false; } -static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p, +static bool invalid_llc_nr(struct sched_cache_group *grp, struct task_struct *p, int cpu) { - struct sched_cache_group *grp; int scale; if (get_nr_threads(p) <= 1) @@ -1548,10 +1542,6 @@ static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p, if (scale == INT_MAX) return false; - grp = READ_ONCE(mm->sched_cache_grp); - if (!grp) - return true; - return !fits_capacity((READ_ONCE(grp->nr_running_avg) * cpu_smt_num_threads), (scale * per_cpu(sd_llc_size, cpu))); } @@ -1723,6 +1713,96 @@ static void sched_cache_group_put(struct sched_cache_group *grp) call_rcu(&grp->rcu, sched_cache_group_free_rcu); } +DEFINE_FREE(sched_cache_group_put, struct sched_cache_group *, + sched_cache_group_put(_T)); + +#define rcu_deref_sched_cache_grp(tsk) \ + rcu_dereference_check((tsk)->sched_cache_grp, (tsk) == current) + +static struct sched_cache_group *sched_cache_replace_grp(struct task_struct *p, + struct sched_cache_group *new) +{ + struct sched_cache_group *old; + + old = rcu_deref_sched_cache_grp(p); + rcu_assign_pointer(p->sched_cache_grp, new); + + return old; +} + +struct sched_cache_group *sched_cache_group_get(struct sched_cache_group *grp) +{ + /* + * refcount_inc_not_zero() is the acquire primitive for lockless + * (RCU) lookups; plain refcount_inc() would scribble the count if + * it already reached zero. Return NULL in that case. + */ + if (grp && !refcount_inc_not_zero(&grp->refcnt)) + grp = NULL; + + return grp; +} + +struct sched_cache_group *task_cache_group_get(struct task_struct *p) +{ + guard(rcu)(); + return sched_cache_group_get(rcu_dereference(p->sched_cache_grp)); +} + +void sched_cache_fork(struct task_struct *p) +{ + /* + * The child takes its own reference on the mm's cache group, separate + * from the reference held by the mm. @p is not yet visible to readers, + * so a plain initializing store is enough. + */ + RCU_INIT_POINTER(p->sched_cache_grp, + sched_cache_group_get(p->mm->sched_cache_grp)); +} + +void sched_cache_fork_cleanup(struct task_struct *p) +{ + /* + * A fork that fails after sched_cache_fork() never reaches exit_mm(), + * so drop the reference here. @p never became visible, so there are no + * concurrent readers and the reference we hold keeps the group alive. + */ + sched_cache_group_put(rcu_access_pointer(p->sched_cache_grp)); + RCU_INIT_POINTER(p->sched_cache_grp, NULL); +} + +void sched_cache_exec_mmap(struct task_struct *p, struct mm_struct *mm) +{ + struct sched_cache_group *old; + + /* + * Acquire the new reference before publishing the pointer, then drop + * the old one. @p is current and the only writer of its own pointer. + */ + old = sched_cache_replace_grp(p, sched_cache_group_get(mm->sched_cache_grp)); + sched_cache_group_put(old); +} + +void sched_cache_exit_mm(struct task_struct *p) +{ + struct sched_cache_group *grp = sched_cache_replace_grp(p, NULL); + +#ifdef CONFIG_NUMA_BALANCING + /* + * Subtract this task's footprint from the group before dropping the + * reference, so the group footprint converges as its threads exit. + * Unlocked for performance; clamp to avoid underflow. + */ + if (grp && p->total_numa_faults) { + unsigned long fp = READ_ONCE(grp->footprint); + unsigned long sub = min(fp, p->total_numa_faults); + + WRITE_ONCE(grp->footprint, fp - sub); + } +#endif + sched_cache_group_put(grp); +} + void mm_destroy_sched(struct mm_struct *mm) { sched_cache_group_put(mm->sched_cache_grp); @@ -1777,15 +1857,10 @@ static unsigned long fraction_mm_sched(struct rq *rq, return div64_u64(NICE_0_LOAD * pcpu_sched->runtime, rq->cpu_runtime + 1); } -static int get_pref_llc(struct task_struct *p, struct mm_struct *mm) +static int get_pref_llc(struct task_struct *p, struct sched_cache_group *grp) { int mm_sched_llc = -1, mm_sched_cpu; - struct sched_cache_group *grp; - if (!mm) - return -1; - - grp = READ_ONCE(mm->sched_cache_grp); if (!grp) return -1; @@ -1819,9 +1894,8 @@ static unsigned int task_running_on_cpu(int cpu, struct task_struct *p); static inline void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) { + struct sched_cache_group *grp = rcu_dereference_all(p->sched_cache_grp); struct sched_cache_time *pcpu_sched; - struct sched_cache_group *grp; - struct mm_struct *mm = p->mm; int mm_sched_llc = -1; unsigned long epoch; @@ -1832,12 +1906,8 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) return; /* * init_task, kthreads and user thread created - * by user_mode_thread() don't have mm. + * by user_mode_thread() don't have a cache group. */ - if (!mm) - return; - - grp = READ_ONCE(mm->sched_cache_grp); if (!grp || !grp->pcpu_sched) return; @@ -1855,13 +1925,13 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) * its preferred state. */ if ((long)(epoch - READ_ONCE(grp->epoch)) > llc_epoch_affinity_timeout || - invalid_llc_nr(mm, p, cpu_of(rq)) || - exceed_llc_capacity(mm, cpu_of(rq))) { + invalid_llc_nr(grp, p, cpu_of(rq)) || + exceed_llc_capacity(grp, cpu_of(rq))) { if (READ_ONCE(grp->cpu) != -1) WRITE_ONCE(grp->cpu, -1); } - mm_sched_llc = get_pref_llc(p, mm); + mm_sched_llc = get_pref_llc(p, grp); /* task not on rq accounted later in account_entity_enqueue() */ if (task_running_on_cpu(rq->cpu, p) && @@ -1874,19 +1944,15 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) static void task_tick_cache(struct rq *rq, struct task_struct *p) { + struct sched_cache_group *grp = rcu_dereference_all(p->sched_cache_grp); struct callback_head *work = &p->cache_work; - struct sched_cache_group *grp; - struct mm_struct *mm = p->mm; unsigned long epoch; if (!sched_cache_enabled()) return; - if (!mm || p->flags & PF_KTHREAD) - return; - - grp = READ_ONCE(mm->sched_cache_grp); - if (!grp || !grp->pcpu_sched) + if (!grp || p->flags & PF_KTHREAD || + !grp->pcpu_sched) return; epoch = rq->cpu_epoch; @@ -1968,14 +2034,13 @@ static inline void update_avg_scale(u64 *avg, u64 sample) static void task_cache_work(struct callback_head *work) { + struct sched_cache_group *grp __free(sched_cache_group_put) = NULL; + cpumask_var_t cpus __free(free_cpumask_var) = CPUMASK_VAR_NULL; int cpu, m_a_cpu = -1, nr_running = 0, curr_cpu; unsigned long next_scan, now = jiffies; struct task_struct *p = current, *cur; unsigned long curr_m_a_occ = 0; - struct sched_cache_group *grp; - struct mm_struct *mm = p->mm; unsigned long m_a_occ = 0; - cpumask_var_t cpus; WARN_ON_ONCE(work != &p->cache_work); @@ -1984,7 +2049,12 @@ static void task_cache_work(struct callback_head *work) if (p->flags & PF_EXITING) return; - grp = READ_ONCE(mm->sched_cache_grp); + /* + * A reference makes sure grp is not released by others. The rcu + * lock can not be held till after zalloc_cpumask_var() below, + * because the latter might sleep. + */ + grp = task_cache_group_get(p); if (!grp) return; @@ -1999,8 +2069,8 @@ static void task_cache_work(struct callback_head *work) return; curr_cpu = task_cpu(p); - if (invalid_llc_nr(mm, p, curr_cpu) || - exceed_llc_capacity(mm, curr_cpu)) { + if (invalid_llc_nr(grp, p, curr_cpu) || + exceed_llc_capacity(grp, curr_cpu)) { if (READ_ONCE(grp->cpu) != -1) WRITE_ONCE(grp->cpu, -1); @@ -2033,9 +2103,13 @@ static void task_cache_work(struct callback_head *work) m_cpu = i; } + /* + * rcu_access_pointer() is used because the + * pointer is only compared, never dereferenced. + */ cur = rcu_dereference_all(cpu_rq(i)->curr); if (cur && !(cur->flags & (PF_EXITING | PF_KTHREAD)) && - cur->mm == mm) + rcu_access_pointer(cur->sched_cache_grp) == grp) nr_running++; } @@ -2081,7 +2155,6 @@ static void task_cache_work(struct callback_head *work) } update_avg_scale(&grp->nr_running_avg, nr_running); - free_cpumask_var(cpus); } void init_sched_mm(struct task_struct *p) @@ -2090,6 +2163,13 @@ void init_sched_mm(struct task_struct *p) init_task_work(work, task_cache_work); work->next = work; + /* + * dup_task_struct() copies the parent's task_struct, including its + * sched_cache_grp, for which the child holds no reference. Clear it + * here - before copy_mm() runs - so the child never carries a + * borrowed pointer that the fork error path would put. + */ + RCU_INIT_POINTER(p->sched_cache_grp, NULL); /* * Reset new task's preference to avoid * polluting account_llc_enqueue(). @@ -3890,10 +3970,9 @@ static void task_numa_placement(struct task_struct *p) * heuristic and occasional lost updates are tolerable. * * If a task exits, its corresponding footprint must - * be subtracted from the mm->sched_cache_grp->footprint, - * otherwise the mm->sched_cache_grp->footprint will not - * converge: the exiting thread's footprint remains - * unchanged/undecayed in mm->sched_cache_grp->footprint. + * be subtracted from p->sched_cache_grp->footprint, + * otherwise the footprint will not converge: the + * exiting thread's footprint remains unchanged/undecayed. * See exit_mm(). * * Lost updates and unsynchronized subtraction @@ -3901,12 +3980,14 @@ static void task_numa_placement(struct task_struct *p) * go negative. Clamp to zero to prevent the * unsigned footprint from wrapping. */ - grp = READ_ONCE(p->mm->sched_cache_grp); - if (!grp) - continue; + scoped_guard(rcu) { + grp = rcu_dereference(p->sched_cache_grp); - new_fp = (long)READ_ONCE(grp->footprint) + diff; - WRITE_ONCE(grp->footprint, max(new_fp, 0L)); + if (grp) { + new_fp = (long)READ_ONCE(grp->footprint) + diff; + WRITE_ONCE(grp->footprint, max(new_fp, 0L)); + } + } #endif } @@ -10855,7 +10936,6 @@ static enum llc_mig can_migrate_llc_task(struct lb_env *env, struct task_struct *p) { struct sched_cache_group *grp; - struct mm_struct *mm; bool to_pref; int cpu, src_cpu, dst_cpu; @@ -10864,11 +10944,7 @@ static enum llc_mig can_migrate_llc_task(struct lb_env *env, src_cpu = env->src_cpu; dst_cpu = env->dst_cpu; - mm = p->mm; - if (!mm) - return mig_unrestricted; - - grp = READ_ONCE(mm->sched_cache_grp); + grp = rcu_dereference_all(p->sched_cache_grp); if (!grp) return mig_unrestricted; @@ -10877,8 +10953,8 @@ static enum llc_mig can_migrate_llc_task(struct lb_env *env, return mig_unrestricted; /* skip cache aware load balance for too many threads */ - if (invalid_llc_nr(mm, p, dst_cpu) || - exceed_llc_capacity(mm, dst_cpu)) { + if (invalid_llc_nr(grp, p, dst_cpu) || + exceed_llc_capacity(grp, dst_cpu)) { if (READ_ONCE(grp->cpu) != -1) WRITE_ONCE(grp->cpu, -1); return mig_unrestricted; From 65efcccddc83d6a19e8a2e2a6117811e39e589bf Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Mon, 21 Sep 2026 17:37:26 -0700 Subject: [PATCH 2138/2366] sched/cache: Skip kernel threads for cache aware scheduling to rubustify the code Kernel thread should not be covered by cache aware scheduling as it borrows the statistics from the user space thread. Filter the kernel thread in account_mm_sched(). In theory a kernel thread does not have any valid cache group, so !grp should gate the kernel thread. Add the PF_KTHREAD check explicitly here for safety reasons, to guard against future modifications and to pair with task_tick_cache(). Fixes: df0d98475954 ("sched/cache: Introduce infrastructure for cache-aware load balancing") Signed-off-by: Chen Yu Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # 7.2.x Link: https://patch.msgid.link/058f0c6ea7b991c177a17de347fa3157f25489a7.1790035273.git.tim.c.chen@linux.intel.com --- kernel/sched/fair.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 974a7dfe3215..57360f5cdde4 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1907,8 +1907,14 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) /* * init_task, kthreads and user thread created * by user_mode_thread() don't have a cache group. + * In theory a kernel thread does not have any valid + * cache group, because sched_cache_fork() is not + * invoked for a kernel thread - !grp should gate the + * kernel thread. Use the PF_KTHREAD check explicitly + * here for safety reasons, to guard against future + * modifications and to pair with task_tick_cache(). */ - if (!grp || !grp->pcpu_sched) + if (p->flags & PF_KTHREAD || !grp || !grp->pcpu_sched) return; pcpu_sched = per_cpu_ptr(grp->pcpu_sched, cpu_of(rq)); From 3cb0243767fd033bdce95f4f1b5882172a2f8119 Mon Sep 17 00:00:00 2001 From: Davi Chaves Azevedo Date: Mon, 21 Sep 2026 17:37:27 -0700 Subject: [PATCH 2139/2366] sched/cache: Refresh LLC capacity across CPU hotplug, to fix capacity underestimation bug The scheduler scales LLC capacity by the fraction of cache-sharing CPUs covered by a domain: llc_bytes = cache_size * span_weight / shared_weight During CPU teardown, sched_cpu_deactivate() rebuilds scheduler domains before cacheinfo_cpu_pre_down() removes the CPU from shared_cpu_map. The new domains therefore use the old sharing weight. The later call to sched_update_llc_bytes() looks up the departing CPU's sd_llc, which has already been detached, and returns without correcting the surviving CPUs. On a Ryzen 5 7535U with twelve logical CPUs sharing a 16 MiB LLC, offlining one SMT sibling left the remaining CPUs with: llc_bytes = floor(16777216 * 11 / 12) = 15379114 bytes The correct capacity is still 16777216 bytes. On systems with active cache-aware scheduling, an underestimated capacity can cause exceed_llc_capacity() to reject aggregation for a process whose footprint would fit. Unchanged cpuset partitions sharing the physical cache can also retain stale capacity when a CPU comes online in another partition. Pass the cache-sharing mask already retained by cacheinfo to the scheduler update. Refresh every surviving CPU using its own LLC domain so that each partition receives the correct share. This also preserves the correction needed as cache-sharing maps grow during boot. Keep the existing CPU-hotplug and scheduler-domain synchronization. The update remains on the hotplug path; no steady-state scheduling operation or persistent allocation is added. Fixes: 7030513a0877 ("sched/cache: Calculate the LLC size and store it in sched_domain") Signed-off-by: Davi Chaves Azevedo Signed-off-by: Tim Chen Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Chen Yu Reviewed-by: Tim Chen Reviewed-by: K Prateek Nayak Tested-by: Chen Yu Tested-by: K Prateek Nayak Cc: # v7.2.x Link: https://patch.msgid.link/6751d93e15889e624796c74db0bfe66603d60b1b.1790035273.git.tim.c.chen@linux.intel.com --- drivers/base/cacheinfo.c | 11 ++++++----- include/linux/sched/topology.h | 4 ++-- kernel/sched/topology.c | 22 +++++++++++++--------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c index 9f9c72727a05..7a47a392568a 100644 --- a/drivers/base/cacheinfo.c +++ b/drivers/base/cacheinfo.c @@ -1040,9 +1040,10 @@ static int cacheinfo_cpu_online(unsigned int cpu) rc = cache_add_dev(cpu); if (rc) goto err; - if (cpu_map_shared_cache(true, cpu, &cpu_map)) + if (cpu_map_shared_cache(true, cpu, &cpu_map)) { update_per_cpu_data_slice_size(true, cpu, cpu_map); - sched_update_llc_bytes(cpu); + sched_update_llc_bytes(cpu_map); + } return 0; err: free_cache_attributes(cpu); @@ -1059,10 +1060,10 @@ static int cacheinfo_cpu_pre_down(unsigned int cpu) cpu_cache_sysfs_exit(cpu); free_cache_attributes(cpu); - if (nr_shared > 1) + if (nr_shared > 1) { update_per_cpu_data_slice_size(false, cpu, cpu_map); - - sched_update_llc_bytes(cpu); + sched_update_llc_bytes(cpu_map); + } return 0; } diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h index b5d9d7c2b8ad..f96812d71c51 100644 --- a/include/linux/sched/topology.h +++ b/include/linux/sched/topology.h @@ -281,9 +281,9 @@ static inline int task_node(const struct task_struct *p) } #ifdef CONFIG_SCHED_CACHE -extern void sched_update_llc_bytes(unsigned int cpu); +extern void sched_update_llc_bytes(const struct cpumask *cpus); #else -static inline void sched_update_llc_bytes(unsigned int cpu) { } +static inline void sched_update_llc_bytes(const struct cpumask *cpus) { } #endif #endif /* _LINUX_SCHED_TOPOLOGY_H */ diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c index 0248227d983a..3dab0253976f 100644 --- a/kernel/sched/topology.c +++ b/kernel/sched/topology.c @@ -985,8 +985,8 @@ void sched_cache_active_set(void) } /* - * Update the bottom sched_domain's llc_bytes for @cpu and all its - * LLC siblings. Called from cacheinfo_cpu_online() or + * Update the bottom sched_domain's llc_bytes for @cpus sharing a physical + * LLC. Called from cacheinfo_cpu_online() or * cacheinfo_cpu_pre_down() with cpu hotplug lock held. * * Note: get_effective_llc_bytes() returns 0 on PowerPC. @@ -996,17 +996,13 @@ void sched_cache_active_set(void) * and does not populates the per-CPU struct cpu_cacheinfo array * that get_cpu_cacheinfo_llc() reads. */ -void sched_update_llc_bytes(unsigned int cpu) +void sched_update_llc_bytes(const struct cpumask *cpus) { struct sched_domain *sd, *sdp; unsigned int i; sched_domains_mutex_lock(); - sdp = rcu_dereference_sched_domain(per_cpu(sd_llc, cpu)); - if (!sdp) - goto unlock; - /* * ci->shared_cpu_map is built incrementally as CPUs come * online, so the first CPU in an LLC initially sees @@ -1014,14 +1010,22 @@ void sched_update_llc_bytes(unsigned int cpu) * get_effective_llc_bytes(). Re-evaluating every LLC * sibling on each online event corrects this once the full * shared_cpu_map is known. + * + * The departing CPU's domains have already been detached when + * cacheinfo removes it. Use the surviving cache siblings instead. + * They may belong to different cpuset partitions, so use each CPU's + * own LLC domain to scale its share of the physical cache. */ - for_each_cpu(i, sched_domain_span(sdp)) { + for_each_cpu(i, cpus) { + sdp = rcu_dereference_sched_domain(per_cpu(sd_llc, i)); + if (!sdp) + continue; + sd = rcu_dereference_sched_domain(cpu_rq(i)->sd); if (sd) sd->llc_bytes = get_effective_llc_bytes(i, sdp); } -unlock: sched_domains_mutex_unlock(); } From 5fd0783b99d4af98f65cd58b56ec203d1d426104 Mon Sep 17 00:00:00 2001 From: Shardul Bankar Date: Thu, 17 Sep 2026 14:55:32 +0530 Subject: [PATCH 2140/2366] udp: relocate a connected socket in the 4-tuple hash table on re-connect A connected UDP socket that connects again to a different peer is not re-filed in the 4-tuple hash table: sk binds to 127.0.0.1:21001 sk connects to 127.0.0.2:20001 // filed under hash(sk, peer1) sk connects to 127.0.0.3:20002 // still filed under hash(sk, peer1) packet from 127.0.0.3:20002 // hash(sk, peer2) misses, so the // lookup falls back to scoring the // hash2 chain for this address // and port udp_lib_hash4() returns early when the socket is already hashed, assuming ->rehash() relocates it. ->rehash() runs from __ip{4,6}_datagram_connect() only while the receive address is unset, which a second connect never is: the first connect assigns it, whether the socket was bound to a specific address or to the wildcard. commit 644f9108f3a5 ("udp: Make rehash4 independent in udp_lib_rehash()") added that early return and named connect(AF_UNSPEC) as the way around it. That workaround does not help a socket with both SOCK_BINDADDR_LOCK and SOCK_BINDPORT_LOCK set, because __udp_disconnect() skips ->rehash() for the first and ->unhash() for the second. Delivery is correct either way. Relocate the socket when the hash it is filed under differs from the one requested, which is what commit 78c91ae2c6de ("ipv4/udp: Add 4-tuple hash for connected socket") did before the early return became unconditional. It is done here under hslot->lock, which that version did not take, to match udp_lib_rehash() and udp_lib_unhash(). hslot2 is unchanged, so hash4_cnt needs no adjustment, as in udp_lib_rehash(). A first connect is unaffected, and IPv6 shares the code. With 500 sockets on the port, a re-connected socket measured 522,553 pps without this change and 2,055,078 with it. The UDP side was noted as remaining work in [1]. Link: https://lore.kernel.org/netdev/apnHqmYZQ4yzOP4N@v4bel/ [1] Fixes: 644f9108f3a5 ("udp: Make rehash4 independent in udp_lib_rehash()") Assisted-by: LLM Signed-off-by: Shardul Bankar Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260917-udp_hash4_fix_v1-v1-1-718891af0d7a@mpiricsoftware.com Signed-off-by: Paolo Abeni --- net/ipv4/udp.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index bb8cfc62cb00..0fa3cdbdcc21 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -617,14 +617,23 @@ void udp_lib_hash4(struct sock *sk, u16 hash) struct net *net = sock_net(sk); struct udp_table *udptable; - /* Connected udp socket can re-connect to another remote address, which - * will be handled by rehash. Thus no need to redo hash4 here. - */ - if (udp_hashed4(sk)) - return; - udptable = net->ipv4.udp_table; hslot = udp_hashslot(udptable, net, udp_sk(sk)->udp_port_hash); + + /* A connected socket can re-connect to another address. rehash() + * relocates it, but only runs when the local address changes, so a + * socket bound to a specific address would stay filed under the + * previous peer's hash. Move it here. + */ + if (udp_hashed4(sk)) { + if (udp_sk(sk)->udp_lrpa_hash != hash) { + spin_lock_bh(&hslot->lock); + udp_rehash4(udptable, sk, hash); + spin_unlock_bh(&hslot->lock); + } + return; + } + hslot2 = udp_hashslot2(udptable, udp_sk(sk)->udp_portaddr_hash); hslot4 = udp_hashslot4(udptable, hash); udp_sk(sk)->udp_lrpa_hash = hash; From 9e95b1a94c9c49b4ba722251bbca2759b9c51737 Mon Sep 17 00:00:00 2001 From: Shardul Bankar Date: Thu, 17 Sep 2026 14:55:33 +0530 Subject: [PATCH 2141/2366] udp: remove a disconnected socket from the 4-tuple hash table A UDP socket bound to a specific address and port keeps its entry in the 4-tuple hash table after it is disconnected: sk binds to 127.0.0.1:21001 sk connects to 127.0.0.2:20001 // filed in the 4-tuple table sk disconnects, connect(AF_UNSPEC) // still filed, peer now 0.0.0.0:0 __udp_disconnect() takes a socket out of that table only as a side effect of ->rehash() or ->unhash(), and it skips ->rehash() when SOCK_BINDADDR_LOCK is set and ->unhash() when SOCK_BINDPORT_LOCK is set. commit 6996a2d2d0a6 ("udp: Unhash auto-bound connected sk from 4-tuple hash table when disconnected.") fixed the same end state for a wildcard-bound socket, by a path this one does not take. The entry is counted whether or not anything hits it. hash4_cnt on the hash2 slot stays raised for as long as the socket lives, so udp_has_hash4() keeps sending every packet for that address and port through the 4-tuple lookup first. On IPv6 it can also be hit. __udp_disconnect() does not clear sk_v6_daddr, so udp_v6_rehash() files the entry under the peer the socket was connected to with a zero dport, and inet6_match() compares that same field: a datagram from the former peer with a zero source port matches, and source port zero is accepted on receive. On IPv4 the peer is cleared, so a match would need a zero source address as well, which the routing layer rejects as martian. The stale sk_v6_daddr is a separate defect, not addressed here; removing the entry closes this path either way. The entry can also be relocated. __udp_disconnect() clears sk_bound_dev_if, so a subsequent SO_BINDTODEVICE calls ->rehash(), and because the receive address is still specific udp_lib_rehash() moves the entry instead of removing it, into the bucket that (rcv_saddr, num, 0, 0) hashes to -- a pure function of the address and port, so every socket reaching this state on one address and port collects in one bucket. The bucket cannot be chosen from outside, as udp_ehashfn() is seeded with a per-boot secret. This last one became reachable only with commit 644f9108f3a5 ("udp: Make rehash4 independent in udp_lib_rehash()"), which moved the hash4 handling out of a branch a disconnected socket does not take; the stale entry itself dates from the commit in Fixes. Take the socket out of the table before __udp_disconnect() runs, while it still matches how it was filed. This also reaches the wildcard case ahead of udp_lib_rehash()'s udp_unhash4() branch, leaving that branch unreachable from udp_disconnect(); removing it belongs in net-next. udp_disconnect() and udp_abort() are the only UDP entries into __udp_disconnect(), which is shared with raw, ping and l2tp sockets that are not struct udp_sock: ping_prot.obj_size is sizeof(struct inet_sock), so udp_hashed4() on one would read past the allocation. Fixes: 78c91ae2c6de ("ipv4/udp: Add 4-tuple hash for connected socket") Assisted-by: LLM Signed-off-by: Shardul Bankar Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260917-udp_hash4_fix_v1-v1-2-718891af0d7a@mpiricsoftware.com Signed-off-by: Paolo Abeni --- net/ipv4/udp.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 0fa3cdbdcc21..b090bd1f59e8 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -2206,9 +2206,31 @@ int __udp_disconnect(struct sock *sk, int flags) } EXPORT_SYMBOL(__udp_disconnect); +/* __udp_disconnect() takes a socket out of the 4-tuple hash table only via + * ->rehash() or ->unhash(), and neither runs for a socket bound to a + * specific address and port. Remove it here, before its peer is cleared. + */ +static void udp_unhash4_on_disconnect(struct sock *sk) +{ + struct net *net = sock_net(sk); + struct udp_table *udptable; + struct udp_hslot *hslot; + + if (!udp_hashed4(sk)) + return; + + udptable = net->ipv4.udp_table; + hslot = udp_hashslot(udptable, net, udp_sk(sk)->udp_port_hash); + + spin_lock_bh(&hslot->lock); + udp_unhash4(udptable, sk); + spin_unlock_bh(&hslot->lock); +} + int udp_disconnect(struct sock *sk, int flags) { lock_sock(sk); + udp_unhash4_on_disconnect(sk); __udp_disconnect(sk, flags); release_sock(sk); return 0; @@ -3140,6 +3162,7 @@ int udp_abort(struct sock *sk, int err) sk->sk_err = err; sk_error_report(sk); + udp_unhash4_on_disconnect(sk); __udp_disconnect(sk, 0); out: From cec38d5c098a350dcf084d345025136ade7e6d1e Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:09 -0700 Subject: [PATCH 2142/2366] perf/x86/intel: Ensure KVM guest PEBS path doesn't set unwanted PERF_GLOBAL_CTRL bits When reinstating PEBS counters into PERF_GLOBAL_CTRL for a KVM guest, mask the value with perf's desired/original PERF_GLOBAL_CTRL value to ensure KVM doesn't unintentionally set reserved bits in PERF_GLOBAL_CTRL. E.g. if the guest's PEBS_ENABLE value had bit 63, "Enable Precise Store", set, then using the raw guest PEBS value would propagate bit 63 to the guest's PERF_GLOBAL_CTRL value (which thankfully would be a failed VM-Entry, not a VMX Abort). The only reason this bug isn't reachable is because KVM doesn't support "Enable Precise Store" (which is probably a KVM bug?), i.e. bit 63 can't be set in kvm_pmu->pebs_enable and thus not in arr[pebs_enable].guest. In other words, this _should_ be a glorified NOP in the current code base. Fixes: c59a1f106f5c ("KVM: x86/pmu: Add IA32_PEBS_ENABLE MSR emulation for extended PEBS") Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-2-seanjc@google.com --- arch/x86/events/intel/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 1ac2ca35db53..0400b5111db9 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -5359,7 +5359,7 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) arr[pebs_enable].guest &= ~kvm_pmu->host_cross_mapped_mask; arr[global_ctrl].guest &= ~kvm_pmu->host_cross_mapped_mask; /* Set hw GLOBAL_CTRL bits for PEBS counter when it runs for guest */ - arr[global_ctrl].guest |= arr[pebs_enable].guest; + arr[global_ctrl].guest |= intel_ctrl & arr[pebs_enable].guest; } return arr; From 4b64dbdc5861477f148e13d1ed127e7fe7182e4f Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:10 -0700 Subject: [PATCH 2143/2366] perf/x86/intel: Don't write PEBS_ENABLED on host<=>guest xfers if CPU has PEBS isolation, to fix stuck PEBS_ENABLED When filling the list of MSRs to be loaded by KVM on VM-Enter and VM-Exit, *never* insert an entry for PEBS_ENABLED if the CPU properly isolates PEBS events, in which case disabling counters via PERF_GLOBAL_CTRL is sufficient to prevent unwanted PEBS events in the guest (or host). Because perf loads PEBS_ENABLE with the unfiltered cpu_hw_events.pebs_enabled, i.e. with both host and guest masks, there is no need to load different values for the guest versus host, perf+KVM can and should simply control which counters are enabled/disabled via PERF_GLOBAL_CTRL. Avoiding touching PEBS_ENABLED "fixes" a bug where PEBS_ENABLED can end up with "stuck" bits if a PEBS event is throttled between generating the list and actually entering the guest (Intel CPUs can't arbtitrarily block NMIs). Fixes in quotes because leaving PEBS_ENABLED as-is doesn't fix the underlying problem of perf (via PMIs) being able to modify state after the perf<=>KVM handoff. But not writing PEBS_ENABLED is desirable no matter what, as stating the obvious, leaving PEBS_ENABLED as-is avoids three MSR writes on every VMX transition: one each on entry/exit, and one more explicit WRMSR to zero PEBS_ENABLED before VM-Entry (KVM assumes the only reason PEBS_ENABLED is in the load list is if the CPU lacks PEBS isolation and thus needs a quiescent period). Opportunistically add comments to (better) explain the rules for generating the set of PEBS counters that will be active while the guest is running, along with a FIXME for the suspected hack-a-fix where perf disables guest PEBS if _any_ PEBS event is configured to count in the host (commit 854250329c02 ("KVM: x86/pmu: Disable guest PEBS temporarily in two rare situations") doesn't explain the motivation, at all). Fixes: c59a1f106f5c ("KVM: x86/pmu: Add IA32_PEBS_ENABLE MSR emulation for extended PEBS") Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-3-seanjc@google.com --- arch/x86/events/intel/core.c | 55 ++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 0400b5111db9..5ac8fe31871b 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -5292,12 +5292,15 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) struct kvm_pmu *kvm_pmu = (struct kvm_pmu *)data; u64 intel_ctrl = hybrid(cpuc->pmu, intel_ctrl); u64 pebs_mask = cpuc->pebs_enabled & x86_pmu.pebs_capable; - int global_ctrl, pebs_enable; + u64 guest_pebs_mask; + int global_ctrl; /* * In addition to obeying exclude_guest/exclude_host, remove bits being * used for PEBS when running a guest, because PEBS writes to virtual - * addresses (not physical addresses). + * addresses (not physical addresses). If the guest wants to utilize + * PEBS, and PEBS can be safely enabled in the guest, bits for the guest's + * PEBS-enabled counters will be OR'd back in as appropriate. */ *nr = 0; global_ctrl = (*nr)++; @@ -5344,24 +5347,40 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) }; } - pebs_enable = (*nr)++; - arr[pebs_enable] = (struct perf_guest_switch_msr){ - .msr = MSR_IA32_PEBS_ENABLE, - .host = cpuc->pebs_enabled & ~cpuc->intel_ctrl_guest_mask, - .guest = pebs_mask & ~cpuc->intel_ctrl_host_mask & kvm_pmu->pebs_enable, - }; + /* + * Restrict guest PEBS events to counters that (a) perf supports, (b) + * the guest wants to use for PEBS, (c) are not excluded from counting + * in the guest, and (d) _are_ excluded from counting in the host. + */ + guest_pebs_mask = pebs_mask & intel_ctrl & kvm_pmu->pebs_enable & + ~cpuc->intel_ctrl_host_mask & + cpuc->intel_ctrl_guest_mask; - if (arr[pebs_enable].host) { - /* Disable guest PEBS if host PEBS is enabled. */ - arr[pebs_enable].guest = 0; - } else { - /* Disable guest PEBS thoroughly for cross-mapped PEBS counters. */ - arr[pebs_enable].guest &= ~kvm_pmu->host_cross_mapped_mask; - arr[global_ctrl].guest &= ~kvm_pmu->host_cross_mapped_mask; - /* Set hw GLOBAL_CTRL bits for PEBS counter when it runs for guest */ - arr[global_ctrl].guest |= intel_ctrl & arr[pebs_enable].guest; - } + /* + * Disable counters where the guest PMC is different than the host PMC + * being used on behalf of the guest, as the PEBS record includes + * PERF_GLOBAL_STATUS, i.e. the guest will see overflow status for the + * wrong counter(s). + */ + guest_pebs_mask &= ~kvm_pmu->host_cross_mapped_mask; + /* + * FIXME: Allow guest and host usage of PEBS events to co-exist instead + * of disabling guest PEBS entirely if the host is using PEBS. + * What exactly goes wrong if guest and host are using PEBS is + * unknown. + */ + if (pebs_mask & ~cpuc->intel_ctrl_guest_mask) + guest_pebs_mask = 0; + + /* + * Do NOT mess with PEBS_ENABLED. As above, disabling counters via + * PERF_GLOBAL_CTRL is sufficient, and loading a stale PEBS_ENABLED, + * e.g. on VM-Exit, can put the system in a bad state. Simply enable + * counters in PERF_GLOBAL_CTRL, as perf load PEBS_ENABLED with the + * full value, i.e. perf *also* relies on PERF_GLOBAL_CTRL. + */ + arr[global_ctrl].guest |= guest_pebs_mask; return arr; } From d06260e99eb93d2942b7af4ccd789eb8a6c829d3 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:11 -0700 Subject: [PATCH 2144/2366] perf/x86/intel: Don't pointlessly context switch DS_AREA (and PEBS config) if PEBS is unused When filling the list of MSRs to be loaded by KVM on VM-Enter and VM-Exit, load the guest values for DS_AREA and (conditionally) MSR_PEBS_DATA_CFG if and only if PEBS will be active in the guest, i.e. only if a PEBS record may be generated while running the guest. As shown by the !pebs_ept path, it's perfectly safe to run with the host's DS_AREA, so long as PEBS-enabled counters are disabled via PERF_GLOBAL_CTRL. Omitting DS_AREA and MSR_PEBS_DATA_CFG when PEBS is unused saves two MSR writes per MSR on each VMX transition, i.e. eliminates two/four pointless MSR writes on each VMX roundtrip when PEBS isn't being used by the guest. Fixes: c59a1f106f5c ("KVM: x86/pmu: Add IA32_PEBS_ENABLE MSR emulation for extended PEBS") Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Jim Mattson Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-4-seanjc@google.com --- arch/x86/events/intel/core.c | 39 +++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 5ac8fe31871b..75c74e8fe2f4 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -5330,23 +5330,14 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) return arr; } + /* + * If the guest won't use PEBS or the CPU doesn't support PEBS in the + * guest, then there's nothing more to do as disabling PMCs via + * PERF_GLOBAL_CTRL is sufficient on CPUs with guest/host isolation. + */ if (!kvm_pmu || !x86_pmu.pebs_ept) return arr; - arr[(*nr)++] = (struct perf_guest_switch_msr){ - .msr = MSR_IA32_DS_AREA, - .host = (unsigned long)cpuc->ds, - .guest = kvm_pmu->ds_area, - }; - - if (x86_pmu.intel_cap.pebs_baseline) { - arr[(*nr)++] = (struct perf_guest_switch_msr){ - .msr = MSR_PEBS_DATA_CFG, - .host = cpuc->active_pebs_data_cfg, - .guest = kvm_pmu->pebs_data_cfg, - }; - } - /* * Restrict guest PEBS events to counters that (a) perf supports, (b) * the guest wants to use for PEBS, (c) are not excluded from counting @@ -5373,6 +5364,26 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) if (pebs_mask & ~cpuc->intel_ctrl_guest_mask) guest_pebs_mask = 0; + /* + * Context switch DS_AREA and PEBS_DATA_CFG if and only if PEBS will be + * active in the guest; if no records will be generated while the guest + * is running, then simply keep the host values resident in hardware. + */ + arr[(*nr)++] = (struct perf_guest_switch_msr){ + .msr = MSR_IA32_DS_AREA, + .host = (unsigned long)cpuc->ds, + .guest = guest_pebs_mask ? kvm_pmu->ds_area : (unsigned long)cpuc->ds, + }; + + if (x86_pmu.intel_cap.pebs_baseline) { + arr[(*nr)++] = (struct perf_guest_switch_msr){ + .msr = MSR_PEBS_DATA_CFG, + .host = cpuc->active_pebs_data_cfg, + .guest = guest_pebs_mask ? kvm_pmu->pebs_data_cfg : + cpuc->active_pebs_data_cfg, + }; + } + /* * Do NOT mess with PEBS_ENABLED. As above, disabling counters via * PERF_GLOBAL_CTRL is sufficient, and loading a stale PEBS_ENABLED, From a391618e1d563f099e4c2a704f45d08329ccdf7c Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:12 -0700 Subject: [PATCH 2145/2366] perf/x86/intel: Make @data a mandatory param for intel_guest_get_msrs() Drop "support" for passing a NULL @data/@kvm_pmu param when getting guest MSRs. KVM, the only in-tree user, unconditionally passes a non-NULL pointer, and carrying code that suggests @data may be NULL is confusing, e.g. incorrectly implies that there are scenarios where KVM doesn't pass a PMU context. Fixes: 8183a538cd95 ("KVM: x86/pmu: Add IA32_DS_AREA MSR emulation to support guest DS") Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Jim Mattson Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-5-seanjc@google.com --- arch/x86/events/intel/core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 75c74e8fe2f4..757d4082de60 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -5331,11 +5331,11 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) } /* - * If the guest won't use PEBS or the CPU doesn't support PEBS in the - * guest, then there's nothing more to do as disabling PMCs via - * PERF_GLOBAL_CTRL is sufficient on CPUs with guest/host isolation. + * If the CPU doesn't support PEBS in the guest, then there's nothing + * more to do as disabling PMCs via PERF_GLOBAL_CTRL is sufficient on + * CPUs with guest/host isolation. */ - if (!kvm_pmu || !x86_pmu.pebs_ept) + if (!x86_pmu.pebs_ept) return arr; /* From a92e1a412c53dc0d9ad639e7abf8b3fc70a5b6ad Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Thu, 17 Sep 2026 14:33:36 -0400 Subject: [PATCH 2146/2366] tg3: clean up PHYLIB resources on probe failure tg3_get_invariants() can register an MDIO bus and connect a PHY for USE_PHYLIB devices. If tg3_init_one() later fails, its common error path releases the mappings and netdev without undoing those PHYLIB resources. Disconnect the PHY and unregister the MDIO bus before the remaining teardown. Guard PHY cleanup with USE_PHYLIB to match tg3_phy_init(), and call tg3_mdio_fini() unconditionally to match tg3_mdio_init(). The existing IS_CONNECTED and MDIOBUS_INITED flags make both helpers safe when initialization only completed partially. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 158d7abdae85 ("tg3: Add mdio bus registration") Assisted-by: OpenAI:GPT-5.6 Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260917183336.36239-1-mhun512@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/broadcom/tg3.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c index 73a4b569b03e..caa7a6caa6e2 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c @@ -18047,6 +18047,10 @@ static int tg3_init_one(struct pci_dev *pdev, return 0; err_out_apeunmap: + if (tg3_flag(tp, USE_PHYLIB)) + tg3_phy_fini(tp); + tg3_mdio_fini(tp); + if (tp->aperegs) { iounmap(tp->aperegs); tp->aperegs = NULL; From 89dc568e8c0be60e05e5fcd0b528c79077d7b84b Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:23 +0800 Subject: [PATCH 2147/2366] perf/x86/intel: Fix GRT PEBS load/store direction for latency events, to fix sample classification On Gracemont, intel_grt_pebs_event_constraints[] applies LAT_CONSTRAINT constraints to MEM_UOPS_RETIRED.{LOAD,STORE}_LATENCY, but does not set explicit LOAD/STORE flags for those events. The PEBS latency path (pebs_latency_data(), via __grt_latency_data()) uses the event flags to determine memory operation direction. Without an explicit STORE flag, samples from MEM_UOPS_RETIRED.STORE_LATENCY can be misclassified as LOADs. Set explicit LOAD/STORE flags in intel_grt_pebs_event_constraints[] for: - MEM_UOPS_RETIRED.LOAD_LATENCY - MEM_UOPS_RETIRED.STORE_LATENCY Also update __grt_latency_data() to explicitly interpret these flags when assigning the sampled memory operation direction. This fixes incorrect STORE sample classification. Fixes: 39a41278f041 ("perf/x86/intel: Fix PEBS memory access info encoding for ADL") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # v7.2+ Link: https://patch.msgid.link/20260917015234.981153-2-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index b98029b44052..fff98f457576 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -455,6 +455,7 @@ static inline void pebs_set_tlb_lock(u64 *val, bool tlb, bool lock) static u64 __grt_latency_data(struct perf_event *event, u64 status, u8 dse, bool tlb, bool lock, bool blk) { + union perf_mem_data_src src; u64 val; WARN_ON_ONCE(is_hybrid() && @@ -470,7 +471,16 @@ static u64 __grt_latency_data(struct perf_event *event, u64 status, else val |= P(BLK, NA); - return val; + src.val = val; + + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_LDLAT | PERF_X86_EVENT_PEBS_LD_HSW)) + src.mem_op = P(OP, LOAD); + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_STLAT | PERF_X86_EVENT_PEBS_ST_HSW)) + src.mem_op = P(OP, STORE); + + return src.val; } u64 grt_latency_data(struct perf_event *event, u64 status) @@ -1291,8 +1301,8 @@ struct event_constraint intel_glm_pebs_event_constraints[] = { struct event_constraint intel_grt_pebs_event_constraints[] = { /* Allow all events as PEBS with no flags */ - INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0x3), - INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0x3f), + INTEL_HYBRID_LDLAT_CONSTRAINT(0x5d0, 0x3), + INTEL_HYBRID_STLAT_CONSTRAINT(0x6d0, 0x3f), EVENT_CONSTRAINT_END }; From e961d6db42d1f1b66c81438969a648f08573bd9c Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:24 +0800 Subject: [PATCH 2148/2366] perf/x86/intel: Fix CMT PEBS load/store direction for latency events, to fix sample classification The same bug exists on Crestmont as on Gracemont: intel_cmt_pebs_event_constraints[] applies LAT_CONSTRAINT constraints to MEM_UOPS_RETIRED.{LOAD,STORE}_LATENCY, but does not set explicit LOAD/STORE flags for those events. The PEBS latency path (pebs_latency_data(), via cmt_latency_data) uses the event flags to determine memory operation direction. Without an explicit STORE flag, samples from MEM_UOPS_RETIRED.STORE_LATENCY can be misclassified as LOADs. Set explicit LOAD/STORE flags in intel_cmt_pebs_event_constraints[] for: - MEM_UOPS_RETIRED.LOAD_LATENCY - MEM_UOPS_RETIRED.STORE_LATENCY This fixes incorrect STORE sample classification. Fixes: e99fb45436ea ("perf/x86/intel: Update event constraints and cache_extra_regsfor MTL") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # v7.2+ Link: https://patch.msgid.link/20260917015234.981153-3-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index fff98f457576..fa7e0268b4f2 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1308,8 +1308,8 @@ struct event_constraint intel_grt_pebs_event_constraints[] = { struct event_constraint intel_cmt_pebs_event_constraints[] = { /* Allow all events as PEBS with no flags */ - INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0x3), - INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0xff), + INTEL_HYBRID_LDLAT_CONSTRAINT(0x5d0, 0x3), + INTEL_HYBRID_STLAT_CONSTRAINT(0x6d0, 0xff), EVENT_CONSTRAINT_END }; From 2777ec9852277a06ae68fee0c4f1a32783e4a999 Mon Sep 17 00:00:00 2001 From: Nemesa Garg Date: Wed, 9 Sep 2026 16:33:32 +0530 Subject: [PATCH 2149/2366] drm/i915/psr: Clear stale sel fetch enable bits on sel fetch disable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selective fetch is dropped while pipe CRC is active, and the planes keep their SEL_FETCH_PLANE_CTL / SEL_FETCH_CUR_CTL enable bit set in hardware over that. A plane disabled while selective fetch is off never gets the bit cleared, as the disable path is guarded by enable_psr2_sel_fetch. Once selective fetch comes back the hardware resumes fetching for a plane that is no longer enabled and keeps its DDB range reserved. Clear the bits as selective fetch is turned off instead. Atomic check has both the old and the new crtc state, so record the transition there and let the plane and cursor arm paths write the registers to 0 for that commit. v2: Drop the old_crtc_state->hw.active check. [Jouni] Fixes: b1f5279b5981 ("drm/i915/psr: Move plane sel fetch configuration into plane source files") Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/8739 Assisted-by: Copilot:Claude-Opus-5 Signed-off-by: Nemesa Garg Reviewed-by: Jouni Högander Signed-off-by: Suraj Kandpal Link: https://patch.msgid.link/20260909110332.3528029-3-nemesa.garg@intel.com (cherry picked from commit a4c0e7f80429eda6990960971aebd4e4b9533cc6) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_cursor.c | 7 +++++-- .../gpu/drm/i915/display/intel_display_types.h | 2 ++ drivers/gpu/drm/i915/display/intel_psr.c | 15 +++++++++++++++ .../gpu/drm/i915/display/skl_universal_plane.c | 9 ++++----- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_cursor.c b/drivers/gpu/drm/i915/display/intel_cursor.c index 0673f16f6fd0..824ffeef0103 100644 --- a/drivers/gpu/drm/i915/display/intel_cursor.c +++ b/drivers/gpu/drm/i915/display/intel_cursor.c @@ -536,7 +536,8 @@ static void i9xx_cursor_disable_sel_fetch_arm(struct intel_dsb *dsb, struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) + if (!crtc_state->enable_psr2_sel_fetch && + !crtc_state->clear_psr2_sel_fetch) return; intel_de_write_dsb(display, dsb, SEL_FETCH_CUR_CTL(pipe), 0); @@ -569,8 +570,10 @@ static void i9xx_cursor_update_sel_fetch_arm(struct intel_dsb *dsb, struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) + if (!crtc_state->enable_psr2_sel_fetch) { + i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); return; + } if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) { if (crtc_state->enable_psr2_su_region_et) { diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index 43d53a98dae7..f6a9b0de1ade 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -1187,6 +1187,8 @@ struct intel_crtc_state { bool has_sel_update; bool enable_psr2_sel_fetch; bool enable_psr2_su_region_et; + /* Drop the stale selective fetch enable bits as selective fetch is turned off */ + bool clear_psr2_sel_fetch; bool req_psr2_sdp_prior_scanline; bool has_panel_replay; bool link_off_after_as_sdp_when_pr_active; diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c index 40e3d7095996..c28bcb1a0184 100644 --- a/drivers/gpu/drm/i915/display/intel_psr.c +++ b/drivers/gpu/drm/i915/display/intel_psr.c @@ -2883,6 +2883,8 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, struct intel_crtc *crtc) { struct intel_display *display = to_intel_display(state); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); struct intel_crtc_state *crtc_state = intel_atomic_get_new_crtc_state(state, crtc); struct intel_plane_state *new_plane_state, *old_plane_state; struct intel_plane *plane; @@ -2895,6 +2897,19 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, bool full_update = false, su_area_changed; int i, ret; + /* + * Selective fetch is not always usable, for instance it is dropped + * while pipe CRC is active. The planes keep their selective fetch + * enable bit set in hardware over that, and a plane disabled while + * selective fetch is off never gets the bit cleared. Once selective + * fetch comes back the hardware would resume fetching for a plane that + * is no longer enabled and keep its DDB range reserved, so have the + * plane update drop the bit for every plane of the pipe as selective + * fetch is turned off. + */ + crtc_state->clear_psr2_sel_fetch = old_crtc_state->enable_psr2_sel_fetch && + !crtc_state->enable_psr2_sel_fetch; + if (!crtc_state->enable_psr2_sel_fetch) return 0; diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c index 07a683293352..eb5ed981b40f 100644 --- a/drivers/gpu/drm/i915/display/skl_universal_plane.c +++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c @@ -885,7 +885,8 @@ static void icl_plane_disable_sel_fetch_arm(struct intel_dsb *dsb, struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) + if (!crtc_state->enable_psr2_sel_fetch && + !crtc_state->clear_psr2_sel_fetch) return; intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), 0); @@ -1634,10 +1635,8 @@ static void icl_plane_update_sel_fetch_arm(struct intel_dsb *dsb, struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) - return; - - if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) + if (crtc_state->enable_psr2_sel_fetch && + drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), SEL_FETCH_PLANE_CTL_ENABLE); else From 5271d81f99dd01d983d439930eb056952485e15e Mon Sep 17 00:00:00 2001 From: Ankit Nautiyal Date: Mon, 7 Sep 2026 09:15:55 +0530 Subject: [PATCH 2150/2366] drm/i915/quirks: Limit eDP rate to HBR2 on HP Pavilion Plus 14-ew1 The eDP panel on the HP Pavilion Plus Laptop 14-ew1xxx advertises HBR3 while leaving the TPS4 support bit clear. The output however flickers, once link is trained with HBR3. Until commit 8c9006283e4b ("Revert "drm/i915/dp: Reject HBR3 when sink doesn't support TPS4"") such sinks were capped at HBR2 by the TPS4 check which incidentally kept this panel stable. That check was reverted because other panels legitimately need HBR3 without advertising TPS4, and the per-machine QUIRK_EDP_LIMIT_RATE_HBR2 was introduced to handle the affected machines instead. Add the machine to the list of devices that need the QUIRK_EDP_LIMIT_RATE_HBR2. Fixes: 8c9006283e4b ("Revert "drm/i915/dp: Reject HBR3 when sink doesn't support TPS4"") Reported-by: Annoy Cc Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16743 Cc: # v6.18+ Tested-by: Annoy Cc Signed-off-by: Ankit Nautiyal Reviewed-by: Nemesa Garg Link: https://patch.msgid.link/20260907034555.2753846-1-ankit.k.nautiyal@intel.com (cherry picked from commit 550b703fdbb2a2022faa75b4b11ab135241afbd9) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c index 33245f44c0d5..7d7db774d8c7 100644 --- a/drivers/gpu/drm/i915/display/intel_quirks.c +++ b/drivers/gpu/drm/i915/display/intel_quirks.c @@ -257,6 +257,9 @@ static struct intel_quirk intel_quirks[] = { /* Dell XPS 13 7390 2-in-1 */ { 0x8a52, 0x1028, 0x08b0, quirk_edp_limit_rate_hbr2 }, + /* HP Pavilion Plus Laptop 14-ew1xxx */ + { 0x7d55, 0x103c, 0x8c31, quirk_edp_limit_rate_hbr2 }, + /* Xiaomi Book Pro 14 2026 */ { 0xb081, 0x1d72, 0x2424, quirk_disable_psr2 }, }; From 8302c5f475fa5a4ed36a7d32c7b965023d5d7066 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:25 +0800 Subject: [PATCH 2151/2366] perf/x86/intel: Fix DKT PEBS load/store direction for latency events, to fix sample classification Same bug exists on Darkmont as on Gracemont: intel_dkt_pebs_event_constraints[] applies LAT_CONSTRAINT constraints to MEM_UOPS_RETIRED.{LOAD,STORE}_LATENCY, but does not set explicit LOAD/STORE flags for those events. The PEBS latency path (pebs_latency_data(), via cmt_latency_data) uses the event flags to determine memory operation direction. Without an explicit STORE flag, samples from MEM_UOPS_RETIRED.STORE_LATENCY can be misclassified as LOADs. Set explicit LOAD/STORE flags in intel_dkt_pebs_event_constraints[] for: - MEM_UOPS_RETIRED.LOAD_LATENCY - MEM_UOPS_RETIRED.STORE_LATENCY This fixes incorrect STORE sample classification. Additionally remove INTEL_HYBRID_LAT_CONSTRAINT() since no one uses it anymore. Fixes: 65fd435095bb ("perf/x86/intel: Update event constraints for PTL") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # v7.2+ Link: https://patch.msgid.link/20260917015234.981153-4-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 4 ++-- arch/x86/events/perf_event.h | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index fa7e0268b4f2..ea5b331c58d1 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1315,8 +1315,8 @@ struct event_constraint intel_cmt_pebs_event_constraints[] = { struct event_constraint intel_dkt_pebs_event_constraints[] = { /* Allow all events as PEBS with no flags */ - INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0xff), - INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0xff), + INTEL_HYBRID_LDLAT_CONSTRAINT(0x5d0, 0xff), + INTEL_HYBRID_STLAT_CONSTRAINT(0x6d0, 0xff), EVENT_CONSTRAINT_END }; diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 4680cba91340..fab9da78a5c7 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -517,10 +517,6 @@ struct cpu_hw_events { __EVENT_CONSTRAINT(c, n, INTEL_ARCH_EVENT_MASK|X86_ALL_EVENT_FLAGS, \ HWEIGHT(n), 0, PERF_X86_EVENT_PEBS_ST) -#define INTEL_HYBRID_LAT_CONSTRAINT(c, n) \ - __EVENT_CONSTRAINT(c, n, INTEL_ARCH_EVENT_MASK|X86_ALL_EVENT_FLAGS, \ - HWEIGHT(n), 0, PERF_X86_EVENT_PEBS_LAT_HYBRID) - #define INTEL_HYBRID_LDLAT_CONSTRAINT(c, n) \ __EVENT_CONSTRAINT(c, n, INTEL_ARCH_EVENT_MASK|X86_ALL_EVENT_FLAGS, \ HWEIGHT(n), 0, PERF_X86_EVENT_PEBS_LAT_HYBRID|PERF_X86_EVENT_PEBS_LD_HSW) From acbe9a3b60b9a6ace8ef11fe898f586251c84592 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Mon, 7 Sep 2026 20:44:12 +0300 Subject: [PATCH 2152/2366] drm/i915/dp_mst: Fix configuring FEC for a disconnected stream During an atomic commit after all the MST stream CRTC state is computed the driver ensures that the FEC is configured the same way (enabled or disabled) for all the streams on a given MST topology's link. drm_dp_mst_port_downstream_of_parent() used to determine if a stream is downstream of an MST port will return false if the whole topology is disconnected, since in that case it can't verify that the port/ parent_port passed to it is in the given MST topology. This is a problem during the above FEC configuration check, since intel_dp_mst_check_dsc_change()->get_pipes_downstream_of_mst_ports() will not return all the stream CRTCs/pipes for the topology as expected. Since passing parent_port==NULL to get_pipes_downstream_of_mst_port() is meant to return all the streams for the given topology (i.e. mst_mgr) skip checking if an MST port is downstream of a parent port in this case. This fixes a problem where the FEC configuration check explained above failed to ensure that all streams' FEC is configured the same way if the topology was disconnected, leading to a FEC state mismatch error. Cc: stable@vger.kernel.org # v6.10+ Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16073 Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16384 Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260907174413.741851-1-imre.deak@intel.com (cherry picked from commit 270681fbffbba2b6ccf5b7e3c34b8b563b36167f) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_dp_mst.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index 57daed0b0b36..9e65b4a1c71d 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -852,7 +852,8 @@ static u8 get_pipes_downstream_of_mst_port(struct intel_atomic_state *state, if (&connector->mst.dp->mst.mgr != mst_mgr) continue; - if (connector->mst.port != parent_port && + if (parent_port && + connector->mst.port != parent_port && !drm_dp_mst_port_downstream_of_parent(mst_mgr, connector->mst.port, parent_port)) From a443e0b8d647c1401b110d9f919d8c6cb8607260 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Mon, 7 Sep 2026 20:44:13 +0300 Subject: [PATCH 2153/2366] drm/i915/dp_mst: Fix configuring TUs for a disconnected stream During an atomic commit after all the MST stream CRTC state is computed the driver ensures that the sum of TUs of all the streams on a given MST topology link is within limits (63 for 8b10 and 64 for 128b132b). For a disconnected stream the DRM MST core's BW verification doesn't ensure this, because the topology state it uses for this is destroyed as soon as the stream (i.e. MST connector/port) is disconnected. The driver should keep the link state valid even for such disconnected streams, as userspace may disable them one-by-one only in a deferred way. Ensure the link's sum of TUs stays within limits in this case by simply reusing the maximum link BPP limit from the stream's (i.e. CRTC's) old state. The disconnection can happen either via the whole topology getting disconnected or via only the given stream's port getting disconnected. Check for both of these conditions separately, as a connector gets unregistered after a link disconnect event only in a deferred way. Cc: stable@vger.kernel.org # v6.10+ Link: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16073 Link: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/16384 Reviewed-by: Luca Coelho Signed-off-by: Imre Deak Link: https://patch.msgid.link/20260907174413.741851-2-imre.deak@intel.com (cherry picked from commit ee00f8fbb2b202002ab90834e02e9ba372773a36) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_dp_mst.c | 21 ++++++++++++++++++++ drivers/gpu/drm/i915/display/intel_dp_mst.h | 2 ++ drivers/gpu/drm/i915/display/intel_link_bw.c | 3 ++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index 9e65b4a1c71d..fb3942f56b1f 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -2168,6 +2168,27 @@ bool intel_dp_mst_crtc_needs_modeset(struct intel_atomic_state *state, return false; } +bool intel_dp_mst_stream_disconnected(struct intel_atomic_state *state, + const struct intel_crtc *crtc) +{ + struct intel_connector *connector; + + connector = get_connector_in_state_for_crtc(state, crtc); + if (!connector) + return false; + + if (!connector->mst.dp) + return false; + + if (!connector->mst.dp->mst.mgr.mst_state) + return true; + + if (drm_connector_is_unregistered(&connector->base)) + return true; + + return false; +} + /** * intel_dp_mst_prepare_probe - Prepare an MST link for topology probing * @intel_dp: DP port object diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.h b/drivers/gpu/drm/i915/display/intel_dp_mst.h index ab09b487c6bb..8ce89242c05c 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.h +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.h @@ -28,6 +28,8 @@ int intel_dp_mst_atomic_check_link(struct intel_atomic_state *state, struct intel_link_bw_limits *limits); bool intel_dp_mst_crtc_needs_modeset(struct intel_atomic_state *state, struct intel_crtc *crtc); +bool intel_dp_mst_stream_disconnected(struct intel_atomic_state *state, + const struct intel_crtc *crtc); void intel_dp_mst_prepare_probe(struct intel_dp *intel_dp); bool intel_dp_mst_verify_dpcd_state(struct intel_dp *intel_dp); diff --git a/drivers/gpu/drm/i915/display/intel_link_bw.c b/drivers/gpu/drm/i915/display/intel_link_bw.c index b47474a3e9fe..e71e76d6fd3e 100644 --- a/drivers/gpu/drm/i915/display/intel_link_bw.c +++ b/drivers/gpu/drm/i915/display/intel_link_bw.c @@ -64,7 +64,8 @@ void intel_link_bw_init_limits(struct intel_atomic_state *state, intel_atomic_get_new_crtc_state(state, crtc); int forced_bpp_x16 = get_forced_link_bpp_x16(state, crtc); - if (state->base.duplicated && crtc_state) { + if ((state->base.duplicated && crtc_state) || + intel_dp_mst_stream_disconnected(state, crtc)) { limits->max_bpp_x16[pipe] = crtc_state->max_link_bpp_x16; if (intel_dsc_enabled_on_link(crtc_state)) limits->link_dsc_pipes |= BIT(pipe); From bb2635be7646a6a9e40a27becb936fe3cdcccf8d Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Tue, 15 Sep 2026 19:06:20 +0300 Subject: [PATCH 2154/2366] drm/i915/dp: use EXPORT_SYMBOL_IF_KUNIT() for kunit helpers Use EXPORT_SYMBOL_IF_KUNIT() instead of the regular EXPORT_SYMBOL() to export the symbols to the kunit namespace. Otherwise, the symbols get exported for all the kernel to see, and the corresponding MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING") in the tests is meaningless. Fixes: 2eb9982ff179 ("drm/i915/kunit: Export link training and caps funcs for testing") Cc: Imre Deak Reviewed-by: Imre Deak Link: https://patch.msgid.link/20260915160620.779372-1-jani.nikula@intel.com Signed-off-by: Jani Nikula (cherry picked from commit 4ffdb772716e4279d63dfaaadf965da73e799aeb) --- drivers/gpu/drm/i915/display/intel_dp_link_caps.c | 6 ++++-- drivers/gpu/drm/i915/display/intel_dp_link_training.c | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 98657aa4d3d5..abec3e2519ca 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -3,6 +3,8 @@ * Copyright © 2026 Intel Corporation */ +#include + #include #include #include @@ -1302,14 +1304,14 @@ void intel_dp_link_caps_cleanup(struct intel_dp_link_caps *link_caps) const struct intel_dp_link_caps_test_ops i915_display_dp_link_caps_test_ops = { INTEL_DP_LINK_CAPS_TEST_OPS_INIT }; -EXPORT_SYMBOL(i915_display_dp_link_caps_test_ops); +EXPORT_SYMBOL_IF_KUNIT(i915_display_dp_link_caps_test_ops); #else const struct intel_dp_link_caps_test_ops intel_display_dp_link_caps_test_ops = { INTEL_DP_LINK_CAPS_TEST_OPS_INIT }; -EXPORT_SYMBOL(intel_display_dp_link_caps_test_ops); +EXPORT_SYMBOL_IF_KUNIT(intel_display_dp_link_caps_test_ops); #endif /* I915 */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c index cb92cff90614..9a692f4fdfee 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c @@ -2825,14 +2825,14 @@ void intel_dp_link_training_cleanup(struct intel_dp_link_training *link_training const struct intel_dp_link_training_test_ops i915_display_dp_link_training_test_ops = { INTEL_DP_LINK_TRAINING_TEST_OPS_INIT }; -EXPORT_SYMBOL(i915_display_dp_link_training_test_ops); +EXPORT_SYMBOL_IF_KUNIT(i915_display_dp_link_training_test_ops); #else const struct intel_dp_link_training_test_ops intel_display_dp_link_training_test_ops = { INTEL_DP_LINK_TRAINING_TEST_OPS_INIT }; -EXPORT_SYMBOL(intel_display_dp_link_training_test_ops); +EXPORT_SYMBOL_IF_KUNIT(intel_display_dp_link_training_test_ops); #endif /* I915 */ From d2da6696e0c4e60414706e607029d0bb0330c67e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Thu, 3 Sep 2026 13:36:21 +0200 Subject: [PATCH 2155/2366] drm/i915: fix incorrect RCU teardown order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i915_gem_busy_ioctl uses dma_resv_for_each_fence_unlocked() to iterate over the fences in an GEM object without holding a reference but only the RCU read side lock. What can happen here is that the GEM object is destroyed concurrently while i915_gem_busy_ioctl is still running. This won't free the GEM objects memory, but still drops all the dma_fence references. Now when dma_resv_for_each_fence_unlocked() sees a destroyed dma_fence it assumes that a new fence list was installed and re-starts the loop. But in the case of a destroyed GEM object a new fence list is never installed, only the old one freed and therefore the iteration never finishes resulting in an endless loop. The solution is to drop the fence references only after the RCU grace period. The fixes tag is not necessary the patch introducing the problem, but the one making it so worse that we need to address it. This problem was pointed out by Sashiko-bot. Signed-off-by: Christian König Fixes: 912ff2ebd695 ("drm/i915: use the new iterator in i915_gem_busy_ioctl v2") CC: stable@vger.kernel.org Reviewed-by: Tvrtko Ursulin Signed-off-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20260903113621.54660-1-christian.koenig@amd.com (cherry picked from commit 5113479556025093bf8133bb2dcaa33be2d50921) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/gem/i915_gem_object.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object.c b/drivers/gpu/drm/i915/gem/i915_gem_object.c index 5172d3982654..9e01f8b2079a 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_object.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_object.c @@ -89,6 +89,7 @@ struct drm_i915_gem_object *i915_gem_object_alloc(void) void i915_gem_object_free(struct drm_i915_gem_object *obj) { + dma_resv_fini(&obj->base._resv); return kmem_cache_free(slab_objects, obj); } @@ -144,7 +145,6 @@ void __i915_gem_object_fini(struct drm_i915_gem_object *obj) { mutex_destroy(&obj->mm.get_page.lock); mutex_destroy(&obj->mm.get_dma_page.lock); - dma_resv_fini(&obj->base._resv); } /** From 335b0642812ee0f2b7798b634ce507b8b7c9fe0c Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:26 +0800 Subject: [PATCH 2156/2366] perf/x86/intel: Update arw_latency_data() mem-op direction handling Align arw_latency_data() with other *_latency_data() helpers by explicitly decoding LOAD/STORE event flags when setting the sampled memory operation direction. This keeps the latency data path behavior consistent across platforms and avoids relying on implicit direction inference. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260917015234.981153-5-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index ea5b331c58d1..c0004b08b087 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -538,7 +538,11 @@ static u64 arw_latency_data(struct perf_event *event, u64 status) val |= P(BLK, NA); src.val = val; - if (event->hw.flags & PERF_X86_EVENT_PEBS_ST_HSW) + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_LDLAT | PERF_X86_EVENT_PEBS_LD_HSW)) + src.mem_op = P(OP, LOAD); + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_STLAT | PERF_X86_EVENT_PEBS_ST_HSW)) src.mem_op = P(OP, STORE); return src.val; From 7c944595cc43a664019edc22505b6d6f6039be5e Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:27 +0800 Subject: [PATCH 2157/2366] perf/x86/intel: Remove incorrect LionCove PEBS data-source constraints On Lion Cove, PEBS data source is valid only for these events: - MEM_TRANS_RETIRED.LOAD_LATENCY (0x1cd) - MEM_TRANS_RETIRED.STORE_SAMPLE (0x2cd) The perfmon database (https://github.com/intel/perfmon) previously tagged additional memory events such as MEM_INST_RETIRED.STLB_MISS_LOADS with L1_Hit_Indication, implying PEBS data-source support, which is incorrect. The database has since been fixed, but intel_lnc_pebs_event_constraints[] still follows the old definition and marks those events as data-source capable. As a result, get_data_src() may decode data-source information for events that do not provide valid PEBS data-source data and mislead users. Remove those non-data-source memory events from the Lion Cove PEBS constraint table so matching falls back to the regular non-PEBS constraints, which already provide the same counter constraints. Also update lnc_latency_data() to decode LOAD/STORE flags explicitly when setting memory operation direction, for consistency with other *_latency_data() helpers. Fixes: a932aa0e868f ("perf/x86: Add Lunar Lake and Arrow Lake support") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: Link: https://patch.msgid.link/20260917015234.981153-6-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index c0004b08b087..cbcc0b36536b 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -577,7 +577,11 @@ static u64 lnc_latency_data(struct perf_event *event, u64 status) val |= P(BLK, NA); src.val = val; - if (event->hw.flags & PERF_X86_EVENT_PEBS_ST_HSW) + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_LDLAT | PERF_X86_EVENT_PEBS_LD_HSW)) + src.mem_op = P(OP, LOAD); + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_STLAT | PERF_X86_EVENT_PEBS_ST_HSW)) src.mem_op = P(OP, STORE); return src.val; @@ -1510,24 +1514,8 @@ struct event_constraint intel_lnc_pebs_event_constraints[] = { INTEL_FLAGS_UEVENT_CONSTRAINT(0x012a, 0x1), /* OCR.* events */ INTEL_FLAGS_UEVENT_CONSTRAINT(0x012b, 0x1), /* OCR.* events */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x04a4, 0x1), /* TOPDOWN.BAD_SPEC_SLOTS */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x08a4, 0x1), /* TOPDOWN.BR_MISPREDICT_SLOTS */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x10a4, 0x8), /* TOPDOWN.MEMORY_BOUND_SLOTS */ - INTEL_HYBRID_LDLAT_CONSTRAINT(0x1cd, 0x3fc), INTEL_HYBRID_STLAT_CONSTRAINT(0x2cd, 0x3), - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x11d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x12d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x21d0, 0xf), /* MEM_INST_RETIRED.LOCK_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x41d0, 0xf), /* MEM_INST_RETIRED.SPLIT_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x42d0, 0xf), /* MEM_INST_RETIRED.SPLIT_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x81d0, 0xf), /* MEM_INST_RETIRED.ALL_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x82d0, 0xf), /* MEM_INST_RETIRED.ALL_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x87d0, 0x3ff), /* MEM_INST_RETIRED.ANY */ - - INTEL_FLAGS_EVENT_CONSTRAINT_DATALA_LD_RANGE(0xd1, 0xd4, 0xf), - - INTEL_FLAGS_EVENT_CONSTRAINT(0xd0, 0xf), /* * Everything else is handled by PMU_FL_PEBS_ALL, because we From 9f93d33ad65af9853974c1ec4ba1f937e8ba1376 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:28 +0800 Subject: [PATCH 2158/2366] perf/x86/intel: Remove incorrect Panther Cove PEBS data-source constraints Same issue exists on Panther Cove, PEBS data source is valid only for these events: - MEM_TRANS_RETIRED.LOAD_LATENCY (0x1cd) - MEM_TRANS_RETIRED.STORE_SAMPLE (0x2cd) The perfmon database (https://github.com/intel/perfmon) previously tagged additional memory events such as MEM_INST_RETIRED.STLB_MISS_LOADS with L1_Hit_Indication, implying PEBS data-source support, which is incorrect. The database has since been fixed, but intel_pnc_pebs_event_constraints[] still follows the old definition and marks those events as data-source capable. As a result, get_data_src() may decode data-source information for events that do not provide valid PEBS data-source data and mislead users. Remove those non-data-source memory events from the Pather Cove PEBS constraint table so matching falls back to the regular non-PEBS constraints, which already provide the same counter constraints. Also update pnc_latency_data() to decode LOAD/STORE flags explicitly when setting memory operation direction, for consistency with other *_latency_data() helpers. Fixes: d345b6bb8860 ("perf/x86/intel: Add core PMU support for DMR") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # v7.0+ Link: https://patch.msgid.link/20260917015234.981153-7-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index cbcc0b36536b..72179bcf6d0d 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -639,7 +639,11 @@ u64 pnc_latency_data(struct perf_event *event, u64 status) val |= P(BLK, NA); src.val = val; - if (event->hw.flags & PERF_X86_EVENT_PEBS_ST_HSW) + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_LDLAT | PERF_X86_EVENT_PEBS_LD_HSW)) + src.mem_op = P(OP, LOAD); + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_STLAT | PERF_X86_EVENT_PEBS_ST_HSW)) src.mem_op = P(OP, STORE); return src.val; @@ -1528,18 +1532,6 @@ struct event_constraint intel_lnc_pebs_event_constraints[] = { struct event_constraint intel_pnc_pebs_event_constraints[] = { INTEL_HYBRID_LDLAT_CONSTRAINT(0x1cd, 0xfc), INTEL_HYBRID_STLAT_CONSTRAINT(0x2cd, 0x3), - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x11d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x12d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x21d0, 0xf), /* MEM_INST_RETIRED.LOCK_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x41d0, 0xf), /* MEM_INST_RETIRED.SPLIT_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x42d0, 0xf), /* MEM_INST_RETIRED.SPLIT_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x81d0, 0xf), /* MEM_INST_RETIRED.ALL_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x82d0, 0xf), /* MEM_INST_RETIRED.ALL_STORES */ - - INTEL_FLAGS_EVENT_CONSTRAINT_DATALA_LD_RANGE(0xd1, 0xd4, 0xf), - - INTEL_FLAGS_EVENT_CONSTRAINT(0xd0, 0xf), - INTEL_FLAGS_EVENT_CONSTRAINT(0xd6, 0xf), /* * Everything else is handled by PMU_FL_PEBS_ALL, because we From 0ac5d6ca2c3b7d047963b67dccef17902dc6c017 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:29 +0800 Subject: [PATCH 2159/2366] perf/x86/intel: Fix Panther Cove PEBS data-source snoop states For Panther Cove, the snoop states for the data source encodings "Prefetch Promotion" and "Cross Core Prefetch Promotion" should be SNOOP_NONE instead of SNOOP_MISS. Fix the incorrect snooping states for Panther Cove. Fixes: d2bdcde9626c ("perf/x86/intel: Add support for PEBS memory auxiliary info field in DMR") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # v7.0+ Link: https://patch.msgid.link/20260917015234.981153-8-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 72179bcf6d0d..d0329a7eb8a5 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -277,8 +277,8 @@ static u64 pnc_pebs_l2_hit_data_source[PNC_PEBS_DATA_SOURCE_MAX] = { 0, /* 0x06: Reserved */ OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, HIT), /* 0x07: L2 Hit Snoop HIT */ OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, HITM), /* 0x08: L2 Hit Snoop Hit Modified */ - OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, MISS), /* 0x09: Prefetch Promotion */ - OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, MISS), /* 0x0a: Cross Core Prefetch Promotion */ + OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, NONE), /* 0x09: Prefetch Promotion */ + OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, NONE), /* 0x0a: Cross Core Prefetch Promotion */ 0, /* 0x0b: Reserved */ 0, /* 0x0c: Reserved */ 0, /* 0x0d: Reserved */ From 858b37ca19d3f695f7fa94cd14be5a856fd8e7d7 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:30 +0800 Subject: [PATCH 2160/2366] perf/x86/intel: Delete dead NVL PEBS data-source initcall Nova Lake now uses the OMR data-source table for PEBS data-source decoding and no longer depends on the legacy static pebs_data_source[] mapping. Remove the dead intel_pmu_pebs_data_source_lnl() initialization call for NVL. Fixes: c847a208f43b ("perf/x86/intel: Add core PMU support for Novalake") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260917015234.981153-9-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 757d4082de60..a7dbbed8f94e 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -8824,8 +8824,6 @@ __init int intel_pmu_init(void) /* Initialize Atom core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_ATOM_IDX]; intel_pmu_init_arw(&pmu->pmu); - - intel_pmu_pebs_data_source_lnl(); break; default: From 04a7ef3b7aa3af34202285043e3423a2e3983595 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:31 +0800 Subject: [PATCH 2161/2366] perf/x86/intel: Constrain Panther Cove UOPS_DISPATCHED events to PMCs 0-3 Per the latest Panther Cove event definitions, the following events are only supported on PMCs 0-3: - UOPS_DISPATCHED.INT_EU_ALL (0x1b2) - UOPS_DISPATCHED.ALU (0x2b2) Add explicit event constraints for these two events so scheduling does not place them on unsupported counters. Fixes: d345b6bb8860 ("perf/x86/intel: Add core PMU support for DMR") Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Cc: # v7.0+ Link: https://patch.msgid.link/20260917015234.981153-10-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index a7dbbed8f94e..ecf6d6325dfe 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -506,6 +506,8 @@ static struct event_constraint intel_pnc_event_constraints[] = { INTEL_EVENT_CONSTRAINT(0xce, 0x1), INTEL_UEVENT_CONSTRAINT(0x01b1, 0x8), + INTEL_UEVENT_CONSTRAINT(0x01b2, 0xf), + INTEL_UEVENT_CONSTRAINT(0x02b2, 0xf), INTEL_UEVENT_CONSTRAINT(0x0847, 0xf), INTEL_UEVENT_CONSTRAINT(0x0446, 0xf), INTEL_UEVENT_CONSTRAINT(0x0846, 0xf), From ff1621adfdd7cfb73f2ccdd3856cd3462ac98ac5 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:32 +0800 Subject: [PATCH 2162/2366] perf/x86/intel: Fix precise OMR event scheduling for DMR/NVL The latest perfmon event database introduces below precise OMR event support for DMR/NVL: - MEM_LOAD_L2_MISS_RETIRED.* (event 0xd6) - MEM_STORE_L2_MISS_RETIRED.* (event 0x4f) These events use the same OMR MSRs as the existing OMR events, but they are not listed in intel_pnc_extra_regs[]. As a result, perf cannot assign the required OMR extra registers when scheduling them. Add the new precise OMR events to intel_pnc_extra_regs[] so they can be scheduled with the correct OMR MSRs. MEM_LOAD_L2_MISS_RETIRED.* remains limited to GP counters 0-3, while MEM_STORE_L2_MISS_RETIRED.* is available on all GP counters. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260917015234.981153-11-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index ecf6d6325dfe..22715602f7f8 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -522,6 +522,14 @@ static struct extra_reg intel_pnc_extra_regs[] __read_mostly = { INTEL_UEVENT_EXTRA_REG(0x022a, MSR_OMR_1, 0x40ffffff0000ffffull, OMR_1), INTEL_UEVENT_EXTRA_REG(0x042a, MSR_OMR_2, 0x40ffffff0000ffffull, OMR_2), INTEL_UEVENT_EXTRA_REG(0x082a, MSR_OMR_3, 0x40ffffff0000ffffull, OMR_3), + INTEL_UEVENT_EXTRA_REG(0x014f, MSR_OMR_0, 0x40ffffff0000ffffull, OMR_0), + INTEL_UEVENT_EXTRA_REG(0x024f, MSR_OMR_1, 0x40ffffff0000ffffull, OMR_1), + INTEL_UEVENT_EXTRA_REG(0x044f, MSR_OMR_2, 0x40ffffff0000ffffull, OMR_2), + INTEL_UEVENT_EXTRA_REG(0x084f, MSR_OMR_3, 0x40ffffff0000ffffull, OMR_3), + INTEL_UEVENT_EXTRA_REG(0x01d6, MSR_OMR_0, 0x40ffffff0000ffffull, OMR_0), + INTEL_UEVENT_EXTRA_REG(0x02d6, MSR_OMR_1, 0x40ffffff0000ffffull, OMR_1), + INTEL_UEVENT_EXTRA_REG(0x04d6, MSR_OMR_2, 0x40ffffff0000ffffull, OMR_2), + INTEL_UEVENT_EXTRA_REG(0x08d6, MSR_OMR_3, 0x40ffffff0000ffffull, OMR_3), INTEL_UEVENT_PEBS_LDLAT_EXTRA_REG(0x01cd), INTEL_UEVENT_EXTRA_REG(0x02c6, MSR_PEBS_FRONTEND, 0x9, FE), INTEL_UEVENT_EXTRA_REG(0x03c6, MSR_PEBS_FRONTEND, 0x7fff1f, FE), From 0102c8c7fdfc4bd700971daff2b94470f4eafae4 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:33 +0800 Subject: [PATCH 2163/2366] perf/x86/intel: Rename DMR offcore_rsp attribute to offmodule_rsp DMR introduces Offmodule Response events in place of the legacy Offcore Response events, but it still exposes the inherited offcore_rsp PMU attribute for programming the corresponding MSR data. Rename the DMR PMU attribute to offmodule_rsp so the sysfs interface matches the underlying event name and avoids user & tooling confusion. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260917015234.981153-12-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 22715602f7f8..3843dbe530a5 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -6601,6 +6601,8 @@ static void intel_pmu_filter(struct pmu *pmu, int cpu, bool *ret) PMU_FORMAT_ATTR(offcore_rsp, "config1:0-63"); +PMU_FORMAT_ATTR(offmodule_rsp, "config1:0-63"); + PMU_FORMAT_ATTR(ldlat, "config1:0-15"); PMU_FORMAT_ATTR(frontend, "config1:0-23"); @@ -6649,6 +6651,20 @@ static struct attribute *skl_format_attr[] = { NULL, }; +static struct attribute *pnc_format_attr_rtm[] = { + &format_attr_in_tx.attr, + &format_attr_in_tx_cp.attr, + &format_attr_offmodule_rsp.attr, + &format_attr_ldlat.attr, + NULL +}; + +static struct attribute *pnc_format_attr[] = { + &format_attr_offmodule_rsp.attr, + &format_attr_ldlat.attr, + NULL +}; + static __initconst const struct x86_pmu core_pmu = { .name = "core", .handle_irq = x86_pmu_handle_irq, @@ -8589,6 +8605,8 @@ __init int intel_pmu_init(void) case INTEL_DIAMONDRAPIDS_X: intel_pmu_init_pnc(NULL); x86_pmu.pebs_latency_data = pnc_latency_data; + extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? + pnc_format_attr_rtm : pnc_format_attr; pr_cont("Panthercove events, "); name = "panthercove"; @@ -8597,13 +8615,12 @@ __init int intel_pmu_init(void) glc_common: intel_pmu_init_glc(NULL); intel_pmu_pebs_data_source_skl(true); - + extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? + hsw_format_attr : nhm_format_attr; glc_base: x86_pmu.pebs_ept = 1; x86_pmu.hw_config = hsw_hw_config; x86_pmu.get_event_constraints = glc_get_event_constraints; - extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? - hsw_format_attr : nhm_format_attr; extra_skl_attr = skl_format_attr; mem_attr = glc_events_attrs; td_attr = glc_td_events_attrs; From d4d9ccbad527af115b8e83a7a2b74785c0e8dfea Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:34 +0800 Subject: [PATCH 2164/2366] perf/x86/intel: Rename NVL offcore_rsp attribute to offmodule_rsp NVL introduces Offmodule Response events in place of the legacy Offcore Response events, but it still exposes the inherited offcore_rsp PMU attribute for programming the corresponding MSR data. Rename the NVL PMU attribute to offmodule_rsp so the sysfs interface matches the underlying event name and avoids user & tooling confusion. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260917015234.981153-13-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 3843dbe530a5..3ef80882843e 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -7530,6 +7530,7 @@ static struct attribute *adl_hybrid_tsx_attrs[] = { FORMAT_ATTR_HYBRID(in_tx, hybrid_big); FORMAT_ATTR_HYBRID(in_tx_cp, hybrid_big); FORMAT_ATTR_HYBRID(offcore_rsp, hybrid_big_small_tiny); +FORMAT_ATTR_HYBRID(offmodule_rsp, hybrid_big_small_tiny); FORMAT_ATTR_HYBRID(ldlat, hybrid_big_small_tiny); FORMAT_ATTR_HYBRID(frontend, hybrid_big); @@ -7568,6 +7569,23 @@ static struct attribute *mtl_hybrid_extra_attr[] = { NULL }; +static struct attribute *nvl_hybrid_extra_attr_rtm[] = { + ADL_HYBRID_RTM_FORMAT_ATTR, + FORMAT_HYBRID_PTR(offmodule_rsp), + FORMAT_HYBRID_PTR(ldlat), + FORMAT_HYBRID_PTR(frontend), + FORMAT_HYBRID_PTR(snoop_rsp), + NULL +}; + +static struct attribute *nvl_hybrid_extra_attr[] = { + FORMAT_HYBRID_PTR(offmodule_rsp), + FORMAT_HYBRID_PTR(ldlat), + FORMAT_HYBRID_PTR(frontend), + FORMAT_HYBRID_PTR(snoop_rsp), + NULL +}; + static bool is_attr_for_this_pmu(struct kobject *kobj, struct attribute *attr) { struct device *dev = kobj_to_dev(kobj); @@ -8842,7 +8860,7 @@ __init int intel_pmu_init(void) mem_attr = mtl_hybrid_mem_attrs; tsx_attr = adl_hybrid_tsx_attrs; extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? - mtl_hybrid_extra_attr_rtm : mtl_hybrid_extra_attr; + nvl_hybrid_extra_attr_rtm : nvl_hybrid_extra_attr; /* Initialize big core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; From 09b7040a1b79f4f61cdad6d9af972e045bb7c498 Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Wed, 16 Sep 2026 17:14:10 +0200 Subject: [PATCH 2165/2366] s390/pci: Fix leak of struct pci_dev reference in zpci_report_status() In zpci_report_status(), a reference to the pdev associated with the zdev being reported about is acquired using pci_get_slot(). This reference needs to be dropped with pci_dev_put(), but this call is missing, thus leaking the reference. On subsequent hot unplug, this will cause the struct pci_dev to not be released, leaking memory and preventing reattach. At the same time, the only existing caller already holds a pdev reference. So instead of reacquiring and then dropping another reference, simply pass the existing pdev pointer to zpci_report_status(). This gets rid of the need for pci_get_slot() as well as the zdev->zbus check. Cc: stable@vger.kernel.org Fixes: 4ec6054e7321 ("s390/pci: Report PCI error recovery results via SCLP") Signed-off-by: Niklas Schnelle Reviewed-by: Benjamin Block Reviewed-by: Farhan Ali Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_event.c | 2 +- arch/s390/pci/pci_report.c | 11 +++++------ arch/s390/pci/pci_report.h | 4 +++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c index f317a1465dad..3b4941b65840 100644 --- a/arch/s390/pci/pci_event.c +++ b/arch/s390/pci/pci_event.c @@ -298,7 +298,7 @@ static pci_ers_result_t zpci_event_attempt_error_recovery(struct pci_dev *pdev, pci_uevent_ers(pdev, PCI_ERS_RESULT_RECOVERED); out_unlock: device_unlock(&pdev->dev); - zpci_report_status(zdev, "recovery", status_str); + zpci_report_status(zdev, pdev, "recovery", status_str); return ers_res; } diff --git a/arch/s390/pci/pci_report.c b/arch/s390/pci/pci_report.c index 7030f7052926..867419779219 100644 --- a/arch/s390/pci/pci_report.c +++ b/arch/s390/pci/pci_report.c @@ -89,7 +89,8 @@ static struct debug_view debug_log_view = { /** * zpci_report_status - Report the status of operations on a PCI device - * @zdev: The PCI device for which to report status + * @zdev: The zPCI device for which to report status + * @pdev: The PCI device associated with the zdev if any, NULL otherwise * @operation: A string representing the operation reported * @status: A string representing the status of the operation * @@ -103,15 +104,15 @@ static struct debug_view debug_log_view = { * * Return: 0 on success an error code < 0 otherwise. */ -int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char *status) +int zpci_report_status(struct zpci_dev *zdev, struct pci_dev *pdev, + const char *operation, const char *status) { struct zpci_report_error *report; struct pci_driver *driver = NULL; - struct pci_dev *pdev = NULL; char *buf, *end; int ret; - if (!zdev || !zdev->zbus) + if (!zdev) return -ENODEV; /* Protected virtualization hosts get nothing from us */ @@ -121,8 +122,6 @@ int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char report = (void *)get_zeroed_page(GFP_KERNEL); if (!report) return -ENOMEM; - if (zdev->zbus->bus) - pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn); if (pdev) driver = to_pci_driver(pdev->dev.driver); diff --git a/arch/s390/pci/pci_report.h b/arch/s390/pci/pci_report.h index e08003d51a97..dd7b0b05001c 100644 --- a/arch/s390/pci/pci_report.h +++ b/arch/s390/pci/pci_report.h @@ -8,9 +8,11 @@ */ #ifndef __S390_PCI_REPORT_H #define __S390_PCI_REPORT_H +#include struct zpci_dev; -int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char *status); +int zpci_report_status(struct zpci_dev *zdev, struct pci_dev *pdev, + const char *operation, const char *status); #endif /* __S390_PCI_REPORT_H */ From 0261aef4b15efcee2860ab857e5cb05e9bfa47b0 Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Wed, 16 Sep 2026 17:14:11 +0200 Subject: [PATCH 2166/2366] s390/pci: Fix missing device lock in zpci_report_status() When pdev is non-NULL, zpci_report_status() accesses the device's driver. To get a consistent state matching the recovery, the device lock needs to be held. Do so by expanding the existing device lock critical section. The lock only needs to be held when the pdev is non-NULL, so extract the pdev-specific reporting into a helper function which also adds a lockdep assertion to detect calls without the device lock held. Cc: stable@vger.kernel.org Fixes: 4ec6054e7321 ("s390/pci: Report PCI error recovery results via SCLP") Signed-off-by: Niklas Schnelle Reviewed-by: Benjamin Block Reviewed-by: Farhan Ali Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_event.c | 2 +- arch/s390/pci/pci_report.c | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c index 3b4941b65840..ec93f34b6e19 100644 --- a/arch/s390/pci/pci_event.c +++ b/arch/s390/pci/pci_event.c @@ -297,8 +297,8 @@ static pci_ers_result_t zpci_event_attempt_error_recovery(struct pci_dev *pdev, driver->err_handler->resume(pdev); pci_uevent_ers(pdev, PCI_ERS_RESULT_RECOVERED); out_unlock: - device_unlock(&pdev->dev); zpci_report_status(zdev, pdev, "recovery", status_str); + device_unlock(&pdev->dev); return ers_res; } diff --git a/arch/s390/pci/pci_report.c b/arch/s390/pci/pci_report.c index 867419779219..72ecabf7003c 100644 --- a/arch/s390/pci/pci_report.c +++ b/arch/s390/pci/pci_report.c @@ -87,6 +87,19 @@ static struct debug_view debug_log_view = { NULL }; +static ssize_t zpci_report_pdev(struct pci_dev *pdev, char *buf, size_t size) +{ + struct pci_driver *driver; + const char *start = buf; + char *end = buf + size; + + device_lock_assert(&pdev->dev); + buf += scnprintf(buf, end - buf, "state: %s\n", zpci_state_str(pdev->error_state)); + driver = to_pci_driver(pdev->dev.driver); + buf += scnprintf(buf, end - buf, "driver: %s\n", (driver) ? driver->name : "n/a"); + return buf - start; +} + /** * zpci_report_status - Report the status of operations on a PCI device * @zdev: The zPCI device for which to report status @@ -108,7 +121,6 @@ int zpci_report_status(struct zpci_dev *zdev, struct pci_dev *pdev, const char *operation, const char *status) { struct zpci_report_error *report; - struct pci_driver *driver = NULL; char *buf, *end; int ret; @@ -122,16 +134,13 @@ int zpci_report_status(struct zpci_dev *zdev, struct pci_dev *pdev, report = (void *)get_zeroed_page(GFP_KERNEL); if (!report) return -ENOMEM; - if (pdev) - driver = to_pci_driver(pdev->dev.driver); buf = report->data.log_data; end = report->data.log_data + ZPCI_REPORT_DATA_SIZE; buf += scnprintf(buf, end - buf, "report: %s\n", operation); buf += scnprintf(buf, end - buf, "status: %s\n", status); - buf += scnprintf(buf, end - buf, "state: %s\n", - (pdev) ? zpci_state_str(pdev->error_state) : "n/a"); - buf += scnprintf(buf, end - buf, "driver: %s\n", (driver) ? driver->name : "n/a"); + if (pdev) + buf += zpci_report_pdev(pdev, buf, end - buf); ret = debug_dump(pci_debug_msg_id, &debug_log_view, buf, end - buf, true); if (ret < 0) pr_err("Reading PCI debug messages failed with code %d\n", ret); From a1120bea9bc8ea9d9ab2f9904a63b9a228bf2ffc Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Wed, 16 Sep 2026 17:14:12 +0200 Subject: [PATCH 2167/2366] s390/pci: Report SCLP status on error events when no pdev is associated With commit 4ec6054e7321 ("s390/pci: Report PCI error recovery results via SCLP") SCLP reports are generated when recovery is performed in response to an error event. If such an error event arrives but no pdev is currently associated with the zdev, e.g. because it was removed or not yet probed, no report is generated. Fix this by generating a report specific to an error event for a zdev without an associated pdev. Cc: stable@vger.kernel.org Fixes: 4ec6054e7321 ("s390/pci: Report PCI error recovery results via SCLP") Signed-off-by: Niklas Schnelle Reviewed-by: Benjamin Block Reviewed-by: Farhan Ali Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_event.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c index ec93f34b6e19..7b538c6ae11b 100644 --- a/arch/s390/pci/pci_event.c +++ b/arch/s390/pci/pci_event.c @@ -361,8 +361,10 @@ static void __zpci_event_error(struct zpci_ccdf_err *ccdf) __zpci_event_print_error(pdev, ccdf); - if (!pdev) + if (!pdev) { + zpci_report_status(zdev, NULL, "error event", "no pdev bound"); goto no_pdev; + } switch (ccdf->pec) { case 0x002a: /* Error event concerns FMB */ From 3a43be7a1fd06a35cf9e621b88283b3b6e7d281c Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Wed, 16 Sep 2026 17:14:13 +0200 Subject: [PATCH 2168/2366] s390/pci: Don't report recovery success on skipped recovery When a PCI device is already in the permanent failure state, recovery is skipped, but the SCLP recovery report still shows success. Fix this by changing the status string to explicitly state that recovery was skipped due to permanent failure. Cc: stable@vger.kernel.org Fixes: 4ec6054e7321 ("s390/pci: Report PCI error recovery results via SCLP") Signed-off-by: Niklas Schnelle Reviewed-by: Benjamin Block Reviewed-by: Farhan Ali Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_event.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c index 7b538c6ae11b..d6af4015223e 100644 --- a/arch/s390/pci/pci_event.c +++ b/arch/s390/pci/pci_event.c @@ -226,6 +226,7 @@ static pci_ers_result_t zpci_event_attempt_error_recovery(struct pci_dev *pdev, device_lock(&pdev->dev); if (pdev->error_state == pci_channel_io_perm_failure) { ers_res = PCI_ERS_RESULT_DISCONNECT; + status_str = "skipped (permanent failure)"; goto out_unlock; } pdev->error_state = pci_channel_io_frozen; From f4d04425e66af2ecee9d1a49ae0484436f3c2fd1 Mon Sep 17 00:00:00 2001 From: Peter Oberparleiter Date: Mon, 21 Sep 2026 15:16:48 +0200 Subject: [PATCH 2169/2366] s390/cmf: Fix virtual vs physical address confusion The measurement block address is an absolute address. Define the associated schib_config and schib fields as dma64_t to enable automatic detection of incorrect assignments. Also add the missing virt_to_dma64() translation. Without this fix, a wrong address will be used by firmware when storing extended format channel measurement data on kernels built with CONFIG_RANDOMIZE_IDENTITY_BASE=y. Fixes: 14edd0d73bfe ("s390/cmf: fix virtual vs physical address confusion") Cc: stable@vger.kernel.org Signed-off-by: Peter Oberparleiter Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens --- drivers/s390/cio/cio.h | 5 +++-- drivers/s390/cio/cmf.c | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/s390/cio/cio.h b/drivers/s390/cio/cio.h index bad142c536e1..6d28a62bc67a 100644 --- a/drivers/s390/cio/cio.h +++ b/drivers/s390/cio/cio.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -49,7 +50,7 @@ struct pmcw { /* Target SCHIB configuration. */ struct schib_config { - u64 mba; + dma64_t mba; u32 intparm; u16 mbi; u32 isc:3; @@ -66,7 +67,7 @@ struct schib_config { struct schib { struct pmcw pmcw; /* path management control word */ union scsw scsw; /* subchannel status word */ - __u64 mba; /* measurement block address */ + dma64_t mba; /* measurement block address */ __u8 mda[4]; /* model dependent area */ } __attribute__ ((packed,aligned(4))); diff --git a/drivers/s390/cio/cmf.c b/drivers/s390/cio/cmf.c index 92ab3d546fe4..66b14fedbd18 100644 --- a/drivers/s390/cio/cmf.c +++ b/drivers/s390/cio/cmf.c @@ -183,7 +183,7 @@ static int set_schib(struct ccw_device *cdev, u32 mme, int mbfc, sch->config.mbfc = mbfc; /* address can be either a block address or a block index */ if (mbfc) - sch->config.mba = address; + sch->config.mba = address ? virt_to_dma64((void *)address) : 0; else sch->config.mbi = address; From 4467df89dbca6a3e9dbc343a315324bb192603d6 Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Wed, 16 Sep 2026 18:53:30 +0200 Subject: [PATCH 2170/2366] s390/debug: Reject NULL debug info in debug_dump() debug_dump() passes id on to debug_info_copy(), which dereferences in->name unchecked. debug_unregister(), debug_set_size() and debug_register_view() guard against a NULL id but debug_dump() does not. Nothing reaches this today, but add the check for consistency. Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260910110147.96E851F000FF@smtp.kernel.org/ Signed-off-by: Mikhail Zaslonko Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens --- arch/s390/kernel/debug.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index b5bf8284dbfc..354ce78fc0cf 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -823,6 +823,9 @@ ssize_t debug_dump(debug_info_t *id, struct debug_view *view, file_private_info_t *p_info; size_t size, offset = 0; + if (!id) + return -EINVAL; + /* Need space for '\0' byte */ if (buf_size < 1) return 0; From 28e29992b034acffc9342df216c06097825ce610 Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Fri, 18 Sep 2026 17:13:51 +0200 Subject: [PATCH 2171/2366] s390/debug: Do not register views for failed static debug areas __REGISTER_STATIC_DEBUG_INFO() calls debug_register_view() unconditionally, even when debug_register_static() has failed. In that case _debug_register() was never reached and id->debugfs_root_entry is still NULL, so debugfs_create_file() places the view file in the debugfs root directory. For sclp_err this leaves a /sys/kernel/debug/hex_ascii file with nothing to indicate which debug log it belongs to. debug_register_static() is not exported and the macro is its only caller, so let it return an error code and skip the view registration when it fails. No debugfs files are created for such an area then. Reproduce by booting with s390dbf=sclp_err::100000000. The sclp_err registration fails, no s390dbf/sclp_err/ directory is created, and a hex_ascii file appears in the debugfs root instead. Fixes: d72541f94512 ("s390/debug: add early tracing support") Signed-off-by: Mikhail Zaslonko Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens --- arch/s390/include/asm/debug.h | 8 ++++++-- arch/s390/kernel/debug.c | 12 +++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/arch/s390/include/asm/debug.h b/arch/s390/include/asm/debug.h index 39d484c59774..ad438d6352c8 100644 --- a/arch/s390/include/asm/debug.h +++ b/arch/s390/include/asm/debug.h @@ -460,7 +460,11 @@ static int VNAME(var, active_entries)[EARLY_AREAS] __initdata #define __REGISTER_STATIC_DEBUG_INFO(var, name, pages, areas, view) \ static int __init VNAME(var, reg)(void) \ { \ - debug_register_static(&var, (pages), (areas)); \ + int rc; \ + \ + rc = debug_register_static(&var, (pages), (areas)); \ + if (rc) \ + return rc; \ debug_register_view(&var, (view)); \ return 0; \ } \ @@ -493,7 +497,7 @@ static debug_info_t __refdata var = \ static debug_info_t __used __section(".s390dbf_info") *VNAME(var, info) = &var; \ __REGISTER_STATIC_DEBUG_INFO(var, name, pages, nr_areas, view) -void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas); +int debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas); #endif /* MODULE */ diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index 354ce78fc0cf..142dc1487187 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -953,8 +953,12 @@ EXPORT_SYMBOL(debug_register); * * Note: This function is called automatically via an initcall generated by * DEFINE_STATIC_DEBUG_INFO. + * + * Return: + * - 0 on success + * - negative error code on failure */ -void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) +int debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) { unsigned long flags; debug_info_t *copy; @@ -962,7 +966,7 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) if (!initialized) { pr_err("Tried to register debug feature %s too early\n", id->name); - return; + return -EINVAL; } debug_get_param(id->name, &id->level, &pages_per_area, false); @@ -978,7 +982,7 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) id->active_entries = NULL; raw_spin_unlock_irqrestore(&id->lock, flags); - return; + return -ENOMEM; } /* Replace static trace area with dynamic copy. */ @@ -996,6 +1000,8 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) mutex_lock(&debug_mutex); _debug_register(id); mutex_unlock(&debug_mutex); + + return 0; } /* Remove debugfs entries. */ From a644f09b2090ad22a13fbcf9d141084f573108ef Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 11:01:35 +0000 Subject: [PATCH 2172/2366] fsl/fman: Fix clk reference leak in read_dts_node() of_clk_get() returns a clock with its reference count incremented, but read_dts_node() only uses it to read the rate and never calls clk_put(). The clock is not stored anywhere, so the reference cannot be released later either. Release the clock once its rate has been read, which also covers the error path taken when the rate is zero. Fixes: 414fd46e7762 ("fsl/fman: Add FMan support") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260917110135.2148068-1-vulab@iscas.ac.cn Signed-off-by: Paolo Abeni --- drivers/net/ethernet/freescale/fman/fman.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/freescale/fman/fman.c b/drivers/net/ethernet/freescale/fman/fman.c index 299bab043175..46cc28895e56 100644 --- a/drivers/net/ethernet/freescale/fman/fman.c +++ b/drivers/net/ethernet/freescale/fman/fman.c @@ -2734,6 +2734,7 @@ static struct fman *read_dts_node(struct platform_device *of_dev) } clk_rate = clk_get_rate(clk); + clk_put(clk); if (!clk_rate) { err = -EINVAL; dev_err(&of_dev->dev, "%s: Failed to determine FM%d clock rate\n", From 012bfcd5a51082d5a65f096dfb9ca652b5267965 Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Wed, 16 Sep 2026 18:06:26 +0200 Subject: [PATCH 2173/2366] s390/debug: Fix NULL pointer dereference in debug_info_copy() When debug_register_static() fails, it clears areas, active_pages and active_entries but leaves the area bounds unchanged. Copying such an area, either by opening its view file or via debug_dump(), makes debug_info_copy() dereference the NULL pointers. Skip the copy loop when the source has no areas. Closes: https://lore.kernel.org/r/20260903132123.12F271F00A3F@smtp.kernel.org Fixes: d72541f94512 ("s390/debug: add early tracing support") Signed-off-by: Mikhail Zaslonko Reviewed-by: Peter Oberparleiter Acked-by: Heiko Carstens Signed-off-by: Heiko Carstens --- arch/s390/kernel/debug.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index 142dc1487187..3b68f5c58333 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -434,7 +434,8 @@ static debug_info_t *debug_info_copy(debug_info_t *in, int mode) debug_info_free(rc); } while (1); - if (mode == NO_AREAS) + /* debug_register_static() failure leaves areas NULL, bounds intact */ + if (mode == NO_AREAS || !in->areas) goto out; for (i = 0; i < in->nr_areas; i++) { From 2d14720beb58870b15a52b236c3ab0be0e06e915 Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Sun, 20 Sep 2026 00:19:37 +0500 Subject: [PATCH 2174/2366] net: spacemit: clear TX descriptor on fragment mapping failure emac_tx_mem_map() writes TX_DESC_0_OWN into the ring descriptor for every slot beyond old_head as soon as that slot's memset()'d local copy is committed with "*tx_desc_addr = tx_desc", i.e. before the buffers for that slot have necessarily all been mapped successfully. If emac_tx_map_frag() then fails on a later fragment, the err_free_skb path calls emac_free_tx_buf() to unmap and drop the skb, but leaves the already-written descriptor memory untouched, and tx_ring->head is never advanced past old_head (the "tx_ring->head = head" store is skipped by the goto). So a slot between old_head and the rolled-back head can be left with TX_DESC_0_OWN set and buffer_addr_{1,2} pointing at DMA mappings that emac_free_tx_buf() just tore down, while software considers that slot free again. The next successful emac_tx_mem_map() call only rebuilds old_head itself; if the DMA engine auto-advances into the following descriptor once it finishes old_head's packet, it will fetch that stale, already-unmapped address. emac_tx_clean_desc() already treats emac_free_tx_buf() and clearing the descriptor as a pair when reclaiming completed descriptors; do the same in the mapping failure path. Fixes: bfec6d7f2001 ("net: spacemit: Add K1 Ethernet MAC") Signed-off-by: Muhammad Bilal Reviewed-by: Vivian Wang Reviewed-by: Troy Mitchell Link: https://patch.msgid.link/20260919191937.271202-1-meatuni001@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/spacemit/k1_emac.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/spacemit/k1_emac.c b/drivers/net/ethernet/spacemit/k1_emac.c index f7f16397a2c2..d641ac26a1e8 100644 --- a/drivers/net/ethernet/spacemit/k1_emac.c +++ b/drivers/net/ethernet/spacemit/k1_emac.c @@ -803,6 +803,9 @@ static void emac_tx_mem_map(struct emac_priv *priv, struct sk_buff *skb) while (i != head) { emac_free_tx_buf(priv, i); + tx_desc_addr = &((struct emac_desc *)tx_ring->desc_addr)[i]; + memset(tx_desc_addr, 0, sizeof(*tx_desc_addr)); + if (++i == tx_ring->total_cnt) i = 0; } From ac4334522e4ba4a3b6710dd5d4cc98092824b8ca Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 18 Sep 2026 11:28:04 +0700 Subject: [PATCH 2175/2366] net: ethernet: ti: netcp: fix pm_runtime usage counter leak on error pm_runtime_get_sync() leaves the runtime PM usage counter incremented even when it fails, but the error path in netcp_probe() does not call pm_runtime_put_noidle() to balance it, leaking a reference each time resume fails. Use pm_runtime_resume_and_get() instead, which automatically drops the usage counter on failure, fixing the leak. Fixes: 84640e27f230 ("net: netcp: Add Keystone NetCP core ethernet driver") Signed-off-by: bui duc phuc Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260918042804.13101-1-phucduc.bui@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/ti/netcp_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/ti/netcp_core.c b/drivers/net/ethernet/ti/netcp_core.c index eb8fc2ed05f4..4f9e20468bbb 100644 --- a/drivers/net/ethernet/ti/netcp_core.c +++ b/drivers/net/ethernet/ti/netcp_core.c @@ -2225,7 +2225,7 @@ static int netcp_probe(struct platform_device *pdev) return -ENOMEM; pm_runtime_enable(&pdev->dev); - ret = pm_runtime_get_sync(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0) { dev_err(dev, "Failed to enable NETCP power-domain\n"); pm_runtime_disable(&pdev->dev); From 999e8295bc41d6ce45b8e54f88150efa96f3f01e Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 11:08:28 +0000 Subject: [PATCH 2176/2366] net: hisilicon: hns_dsaf_mac: fix mdio device leak in hns_mac_register_phy() hns_dsaf_find_platform_device() returns the mdio platform device with its reference count incremented. hns_mac_register_phy() never drops that reference, so the mdio device can not be released. Release the reference on both the deferred probe and the normal path. Fixes: 1d1afa2ebf82 ("net: hns: register phy device in each mac initial sequence") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260917110828.2148390-1-vulab@iscas.ac.cn Signed-off-by: Paolo Abeni --- drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c b/drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c index bc6b269be299..f1cb6d56e4b9 100644 --- a/drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c +++ b/drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c @@ -793,6 +793,7 @@ static int hns_mac_register_phy(struct hns_mac_cb *mac_cb) dev_err(mac_cb->dev, "mac%d mdio is NULL, dsaf will probe again later\n", mac_cb->mac_id); + put_device(&pdev->dev); return -EPROBE_DEFER; } @@ -801,6 +802,8 @@ static int hns_mac_register_phy(struct hns_mac_cb *mac_cb) dev_dbg(mac_cb->dev, "mac%d register phy addr:%d\n", mac_cb->mac_id, addr); + put_device(&pdev->dev); + return rc; } From 23d42b9a3bcd55b17d3b371544fedc708c2397e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Fri, 18 Sep 2026 21:53:52 +0200 Subject: [PATCH 2177/2366] net: macb: fix dma_alloc_coherent() leak on macb_alloc() error paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix 3 leaks in macb_alloc() error paths: - Tx buffer allocated but crossing a 4G boundary: Tx leaked. - Rx buffer allocation fails: Tx leaked. - Rx buffer allocated but crossing a 4G boundary: Tx & Rx leaked. This is because our error handling calls macb_free(bp) which in turn frees the buffers stored in bp->queues[0], but nothing has been stored in there. Fix by storing allocated buffers into bp->queues[0] ASAP. Fixes: 78d901897b3c ("net: macb: single dma_alloc_coherent() for DMA descriptors") Cc: stable@vger.kernel.org Signed-off-by: Théo Lebrun Reviewed-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260918-macb-alloc-leak-v1-1-aba9a3d4f6e3@bootlin.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/cadence/macb_main.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index b8234ac4b602..8e5c034dc3a4 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2749,14 +2749,24 @@ static int macb_alloc(struct macb *bp) size = bp->num_queues * macb_tx_ring_size_per_queue(bp); tx = dma_alloc_coherent(dev, size, &tx_dma, GFP_KERNEL); - if (!tx || upper_32_bits(tx_dma) != upper_32_bits(tx_dma + size - 1)) + if (!tx) + goto out_err; + /* Record the buffer so that the error path frees it. */ + bp->queues[0].tx_ring = tx; + bp->queues[0].tx_ring_dma = tx_dma; + if (upper_32_bits(tx_dma) != upper_32_bits(tx_dma + size - 1)) goto out_err; netdev_dbg(bp->netdev, "Allocated %zu bytes for %u TX rings at %08lx (mapped %p)\n", size, bp->num_queues, (unsigned long)tx_dma, tx); size = bp->num_queues * macb_rx_ring_size_per_queue(bp); rx = dma_alloc_coherent(dev, size, &rx_dma, GFP_KERNEL); - if (!rx || upper_32_bits(rx_dma) != upper_32_bits(rx_dma + size - 1)) + if (!rx) + goto out_err; + /* Record the buffer so that the error path frees it. */ + bp->queues[0].rx_ring = rx; + bp->queues[0].rx_ring_dma = rx_dma; + if (upper_32_bits(rx_dma) != upper_32_bits(rx_dma + size - 1)) goto out_err; netdev_dbg(bp->netdev, "Allocated %zu bytes for %u RX rings at %08lx (mapped %p)\n", size, bp->num_queues, (unsigned long)rx_dma, rx); From 261e8a37ecbaf462cdf9c336d2b2f5056088401a Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Fri, 18 Sep 2026 15:29:48 -0700 Subject: [PATCH 2178/2366] genetlink: report the real command id for dump-only ops in policy dumps The op-to-policy map a CTRL_CMD_GETPOLICY dump returns is the only way for userspace to find out which policy index belongs to which command. ctrl_dumppolicy_put_op() tags the nest with doit->cmd, but an op which only has a dumpit has no doit and every path which fills the split ops in zeroes it out, so those entries all claim to be command 0. nlctrl's own CTRL_CMD_GETPOLICY and NETDEV_CMD_QSTATS_GET are both in that group: [{'family-id': 16, 'op-policy': {'do': 0, 'dump': 0, 'op-id': 3}}, {'family-id': 16, 'op-policy': {'dump': 1, 'op-id': 0}}, ctrl_fill_info() gets this right - it uses the iterator's cmd for CTRL_ATTR_OP_ID - so the two introspection interfaces of the same family contradict each other today. Pass the command in rather than reconstructing it from doit->cmd | dumpit->cmd inside the helper, both callers already have it. Fixes: 26588edbef60 ("genetlink: support split policies in ctrl_dumppolicy_put_op()") Signed-off-by: Jakub Kicinski Link: https://patch.msgid.link/20260918222949.4190284-1-kuba@kernel.org Signed-off-by: Paolo Abeni --- net/netlink/genetlink.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c index 41d37442f186..5cc1037d4917 100644 --- a/net/netlink/genetlink.c +++ b/net/netlink/genetlink.c @@ -1656,7 +1656,7 @@ static void *ctrl_dumppolicy_prep(struct sk_buff *skb, } static int ctrl_dumppolicy_put_op(struct sk_buff *skb, - struct netlink_callback *cb, + struct netlink_callback *cb, u32 cmd, struct genl_split_ops *doit, struct genl_split_ops *dumpit) { @@ -1677,7 +1677,7 @@ static int ctrl_dumppolicy_put_op(struct sk_buff *skb, if (!nest_pol) goto err; - nest_op = nla_nest_start(skb, doit->cmd); + nest_op = nla_nest_start(skb, cmd); if (!nest_op) goto err; @@ -1721,7 +1721,8 @@ static int ctrl_dumppolicy(struct sk_buff *skb, struct netlink_callback *cb) &doit, &dumpit))) return -ENOENT; - if (ctrl_dumppolicy_put_op(skb, cb, &doit, &dumpit)) + if (ctrl_dumppolicy_put_op(skb, cb, ctx->op, + &doit, &dumpit)) return skb->len; /* done with the per-op policy index list */ @@ -1730,6 +1731,7 @@ static int ctrl_dumppolicy(struct sk_buff *skb, struct netlink_callback *cb) while (ctx->dump_map) { if (ctrl_dumppolicy_put_op(skb, cb, + ctx->op_iter->cmd, &ctx->op_iter->doit, &ctx->op_iter->dumpit)) return skb->len; From a87529034b9c3c3f3bb71c33e2d6d94658e06383 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Fri, 18 Sep 2026 15:29:49 -0700 Subject: [PATCH 2179/2366] selftests: net: nl_nlctrl: check the op ids in the policy map Validate that the op map in the policy dump is correct. Signed-off-by: Jakub Kicinski Link: https://patch.msgid.link/20260918222949.4190284-2-kuba@kernel.org Signed-off-by: Paolo Abeni --- tools/testing/selftests/net/nl_nlctrl.py | 116 +++++++++++++++++++---- 1 file changed, 99 insertions(+), 17 deletions(-) diff --git a/tools/testing/selftests/net/nl_nlctrl.py b/tools/testing/selftests/net/nl_nlctrl.py index fe1f66dc9435..237b3d273260 100755 --- a/tools/testing/selftests/net/nl_nlctrl.py +++ b/tools/testing/selftests/net/nl_nlctrl.py @@ -9,40 +9,86 @@ from lib.py import ksft_run, ksft_exit from lib.py import ksft_eq, ksft_ge, ksft_true, ksft_in, ksft_not_in from lib.py import NetdevFamily, EthtoolFamily, NlctrlFamily +# Families we can expect to always be around, and which between them +# cover ops with a do, with a dump, and with both. +FAMILIES = ('nlctrl', 'netdev') -def getfamily_do(ctrl) -> None: - """Query a single family by name and validate its ops.""" - fam = ctrl.getfamily({'family-name': 'netdev'}) - ksft_eq(fam['family-name'], 'netdev') + +def _get_ops(ctrl, name): + """Get the ops of a family, keyed by command id.""" + fam = ctrl.getfamily({'family-name': name}) + ksft_eq(fam['family-name'], name) ksft_true(fam['family-id'] > 0) # The format of ops is quite odd, [{$idx: {"id"...}}, {$idx: {"id"...}}] # Discard the indices and re-key by command id. ops_by_id = {v['id']: v for op in fam['ops'] for v in op.values()} - ksft_eq(len(ops_by_id), len(fam['ops'])) + ksft_eq(len(ops_by_id), len(fam['ops']), + comment=f"{name} lists a command twice") + return ops_by_id - # All ops should have a policy (either do or dump has one) - for op in ops_by_id.values(): - ksft_in('cmd-cap-haspol', op['flags'], - comment=f"op {op['id']} missing haspol") + +def _get_policy_map(ctrl, req): + """ + The policy map in the Netlink replies looks like this: + + [{'family-id': 16, 'op-policy': {'do': 0, 'dump': 0, 'op-id': 3}}, + {'family-id': 16, 'op-policy': {'dump': 1, 'op-id': 4}}, ...] + + Return the mapping: + + {3:{'do','dump'}, 4:{'dump'}} + + The policy itself is discarded here, only return which command has policy. + """ + pol_map = {} + for msg in ctrl.getpolicy(req, dump=True): + if 'op-policy' not in msg: + continue + modes = dict(msg['op-policy']) + cmd = modes.pop('op-id') + ksft_not_in(cmd, pol_map, comment=f"command {cmd} reported twice") + pol_map[cmd] = set(modes.keys()) + return pol_map + + +def getfamily_do(ctrl) -> None: + """Query single families by name and validate their ops.""" + ops = {name: _get_ops(ctrl, name) for name in FAMILIES} + + for name, ops_by_id in ops.items(): + for op in ops_by_id.values(): + # All ops in nlctrl and netdev have a policy + ksft_in('cmd-cap-haspol', op['flags'], + comment=f"{name} op {op['id']} missing haspol") + ksft_true(op['flags'] & {'cmd-cap-do', 'cmd-cap-dump'}, + comment=f"{name} op {op['id']} has no handler") + + # nlctrl getfamily (id 3) does both, getpolicy (id 10) is dump-only + ksft_in('cmd-cap-do', ops['nlctrl'][3]['flags']) + ksft_in('cmd-cap-dump', ops['nlctrl'][3]['flags']) + ksft_not_in('cmd-cap-do', ops['nlctrl'][10]['flags']) + ksft_in('cmd-cap-dump', ops['nlctrl'][10]['flags']) + + netdev = ops['netdev'] # dev-get (id 1) should support both do and dump - ksft_in('cmd-cap-do', ops_by_id[1]['flags']) - ksft_in('cmd-cap-dump', ops_by_id[1]['flags']) + ksft_in('cmd-cap-do', netdev[1]['flags']) + ksft_in('cmd-cap-dump', netdev[1]['flags']) # qstats-get (id 12) is dump-only - ksft_not_in('cmd-cap-do', ops_by_id[12]['flags']) - ksft_in('cmd-cap-dump', ops_by_id[12]['flags']) + ksft_not_in('cmd-cap-do', netdev[12]['flags']) + ksft_in('cmd-cap-dump', netdev[12]['flags']) # napi-set (id 14) is do-only and requires admin - ksft_in('cmd-cap-do', ops_by_id[14]['flags']) - ksft_not_in('cmd-cap-dump', ops_by_id[14]['flags']) - ksft_in('admin-perm', ops_by_id[14]['flags']) + ksft_in('cmd-cap-do', netdev[14]['flags']) + ksft_not_in('cmd-cap-dump', netdev[14]['flags']) + ksft_in('admin-perm', netdev[14]['flags']) # Notification-only commands (dev-add/del/change-ntf etc.) must # not appear in the ops list since they have no do/dump handlers. for ntf_id in [2, 3, 4, 6, 7, 8]: - ksft_not_in(ntf_id, ops_by_id, + ksft_not_in(ntf_id, netdev, comment=f"ntf-only cmd {ntf_id} should not be in ops") @@ -103,6 +149,41 @@ def getpolicy_dump(_ctrl) -> None: comment="linkinfo-set should not have a dump policy") +def getpolicy_op_map(ctrl) -> None: + """Check the op-to-policy map consistency. Each op with 'haspol' flag + has to have a policy. The policy back-references must name only + real ops that exist, have given modes (do vs dump) and have 'haspol'. + """ + for name in FAMILIES: + ops_by_id = _get_ops(ctrl, name) + haspol = {cmd for cmd, op in ops_by_id.items() + if 'cmd-cap-haspol' in op['flags']} + + pol_map = _get_policy_map(ctrl, {'family-name': name}) + ksft_eq(set(pol_map), haspol, + comment=f"{name} policy map does not match the op list") + + # Walk the op list rather than the map, the map may be missing + # the very op we are after. Asking for a command the family does + # not have is an error, so it must not come from the map either. + for cmd in sorted(haspol): + modes = pol_map.get(cmd, set()) + + # The kernel only reports a mode the op actually has. + if 'do' in modes: + ksft_in('cmd-cap-do', ops_by_id[cmd]['flags'], + comment=f"{name} cmd {cmd} has no do") + if 'dump' in modes: + ksft_in('cmd-cap-dump', ops_by_id[cmd]['flags'], + comment=f"{name} cmd {cmd} has no dump") + + # Asking for one op builds the map in a different place in + # the kernel, it has to report what the full dump did. + single = _get_policy_map(ctrl, {'family-name': name, 'op': cmd}) + ksft_eq(single, {cmd: modes}, + comment=f"{name} cmd {cmd} policy differs from the dump") + + def getpolicy_by_op(_ctrl) -> None: """Query policy for specific ops, check attr names are resolved.""" ndev = NetdevFamily() @@ -122,6 +203,7 @@ def main() -> None: ksft_run([getfamily_do, getfamily_dump, getpolicy_dump, + getpolicy_op_map, getpolicy_by_op], args=(ctrl, )) ksft_exit() From 9892d71cf0ce3ff3d4fed2d9a3968fd4feb1c918 Mon Sep 17 00:00:00 2001 From: Coia Prant Date: Sun, 20 Sep 2026 01:20:21 +0800 Subject: [PATCH 2180/2366] net: pcs: xpcs: fix clock reference leak on xpcs_init_clks failure xpcs_init_clks() takes references with clk_bulk_get_optional() and then enables them with clk_bulk_prepare_enable(). If the enable step fails, the function returns without dropping the references. xpcs_create() handles the failure through out_free_data, which calls xpcs_free_data() but never xpcs_clear_clks(), so the clk references are leaked. Add the missing clk_bulk_put() on the enable failure path. The prepare/enable side is already rolled back by clk_bulk_prepare_enable() itself. Fixes: f6bb3e9d98c2 ("net: pcs: xpcs: Add Synopsys DW xPCS platform device driver") Signed-off-by: Coia Prant Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260919172021.2336748-1-coiaprant@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/pcs/pcs-xpcs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index 0337e2bcc012..b415b93d77c1 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -1545,8 +1545,10 @@ static int xpcs_init_clks(struct dw_xpcs *xpcs) return dev_err_probe(dev, ret, "Failed to get clocks\n"); ret = clk_bulk_prepare_enable(DW_XPCS_NUM_CLKS, xpcs->clks); - if (ret) + if (ret) { + clk_bulk_put(DW_XPCS_NUM_CLKS, xpcs->clks); return dev_err_probe(dev, ret, "Failed to enable clocks\n"); + } return 0; } From 17741334d00bf5ebd37f8c1c36bc9c146a351deb Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 11:58:11 +0000 Subject: [PATCH 2181/2366] net: usb: lan78xx: Fix URB reference leak in lan78xx_submit_deferred_urbs() usb_get_from_anchor() hands over a reference to the URB, which the caller must release. lan78xx_submit_deferred_urbs() never does, so every deferred Tx URB keeps an extra reference: the counter grows on each suspend/resume cycle and the URBs are never freed when the buffers are released. Drop the reference after submitting, and on the path that drops the packet instead of submitting it. Fixes: 5f4cc6e25148 ("lan78xx: Fix race conditions in suspend/resume handling") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260917115811.2150119-1-vulab@iscas.ac.cn Signed-off-by: Paolo Abeni --- drivers/net/usb/lan78xx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c index cb782d81d84f..5655941f1478 100644 --- a/drivers/net/usb/lan78xx.c +++ b/drivers/net/usb/lan78xx.c @@ -5239,10 +5239,12 @@ static bool lan78xx_submit_deferred_urbs(struct lan78xx_net *dev) !netif_carrier_ok(dev->net) || pipe_halted) { lan78xx_release_tx_buf(dev, skb); + usb_put_urb(urb); continue; } ret = usb_submit_urb(urb, GFP_ATOMIC); + usb_put_urb(urb); if (ret == 0) { netif_trans_update(dev->net); From 10180a277549339020b08000206092c07e0bff5a Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 14:16:07 -0700 Subject: [PATCH 2182/2366] KVM: x86: Re-pend GET_NESTED_STATE_PAGES if getting said pages fails Re-pend GET_NESTED_STATE_PAGES before exiting to userspace if getting the nested pages fails in the KVM_RUN path. If userspace re-runs the vCPU, and vmcs02 holds valid PFNs from the *previous* run of L2, then KVM could re-enter L2 with stale, unpinned PFNs mapped into e.g. the vAPIC page. Note, both SVM and VMX (as of commit 11722439fb20 ("KVM: nVMX: Ensure KVM_REQ_GET_NESTED_STATE_PAGES is cleared on VM-Exit") ensure the request is cleared on VM-Exit (including the "forced" case), i.e. there is no risk of double-mapping due to emulated VMLAUNCH/VMRESUME/VMRUN *and* the request trying to map the nested pages. Fixes: 671ddc700fd0 ("KVM: nVMX: Don't leak L1 MMIO regions to L2") Cc: stable@vger.kernel.org Reported-by: Jinwoo Lee Closes: https://lore.kernel.org/all/20260813043932.3214460-1-rkskek9254@gmail.com Reported-by: Stefan Teodorescu Link: https://patch.msgid.link/20260921211608.1030158-2-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/x86.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index a137dc6dd8c6..4ec17aaff413 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -8072,6 +8072,7 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) if (kvm_check_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu)) { if (unlikely(!kvm_nested_call(get_nested_state_pages)(vcpu))) { + kvm_make_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu); r = 0; goto out; } From c1214f293d77c6e425a379f90d09bdb4815993a8 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 14:16:08 -0700 Subject: [PATCH 2183/2366] KVM: x86: Fill kvm_run exit fields in common get_nested_state_pages() error paths Fill kvm_run with "internal error, emulation" in the common error handling paths for getting nested state pages, as requiring each check to manually fill kvm_run is error prone and requires a non-trivial amount of copy+paste. Specifically, both SVM and VMX fail to fill kvm_run if load_pdptrs() fails, and SVM fails to fill kvm_run if kvm_hv_verify_vp_assist() fails. If those flows fail, the *best* case scenario is that KVM will exit to userspace with KVM_EXIT_UNKNOWN. The worst case scenario is that KVM exits with a stale exit_reason and confuses userspace. Note, SVM never exits to userspace if something goes sideways when dealing with vmcb12 assets while emulating VMRUN, i.e. lack of SVM-specific code is not a bug. Fixes: 0f85722341b0 ("KVM: nVMX: delay loading of PDPTRs to KVM_REQ_GET_NESTED_STATE_PAGES") Fixes: 232f75d3b4b5 ("KVM: nSVM: call nested_svm_load_cr3 on nested state load") Fixes: 3f4a812edf5c ("KVM: nSVM: hyper-v: Enable L2 TLB flush") Cc: stable@vger.kernel.org Reported-by: Jinwoo Lee Closes: https://lore.kernel.org/all/20260813043932.3214460-1-rkskek9254@gmail.com Reported-by: Stefan Teodorescu Link: https://patch.msgid.link/20260921211608.1030158-3-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/nested.c | 7 +------ arch/x86/kvm/vmx/nested.c | 15 +++++---------- arch/x86/kvm/x86.c | 3 +++ 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c index 73f37b050d0a..f9090b601efa 100644 --- a/arch/x86/kvm/svm/nested.c +++ b/arch/x86/kvm/svm/nested.c @@ -2125,13 +2125,8 @@ static bool svm_get_nested_state_pages(struct kvm_vcpu *vcpu) return false; } - if (!nested_svm_merge_msrpm(vcpu)) { - vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; - vcpu->run->internal.suberror = - KVM_INTERNAL_ERROR_EMULATION; - vcpu->run->internal.ndata = 0; + if (!nested_svm_merge_msrpm(vcpu)) return false; - } if (kvm_hv_verify_vp_assist(vcpu)) return false; diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c index 151873407abd..40c1a5f6fa8a 100644 --- a/arch/x86/kvm/vmx/nested.c +++ b/arch/x86/kvm/vmx/nested.c @@ -3465,10 +3465,6 @@ static bool nested_get_vmcs12_pages(struct kvm_vcpu *vcpu) } else { pr_debug_ratelimited("%s: no backing for APIC-access address in vmcs12\n", __func__); - vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; - vcpu->run->internal.suberror = - KVM_INTERNAL_ERROR_EMULATION; - vcpu->run->internal.ndata = 0; return false; } } @@ -3539,11 +3535,6 @@ static bool vmx_get_nested_state_pages(struct kvm_vcpu *vcpu) if (!nested_get_evmcs_page(vcpu)) { pr_debug_ratelimited("%s: enlightened vmptrld failed\n", __func__); - vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; - vcpu->run->internal.suberror = - KVM_INTERNAL_ERROR_EMULATION; - vcpu->run->internal.ndata = 0; - return false; } #endif @@ -3915,8 +3906,12 @@ static int nested_vmx_run(struct kvm_vcpu *vcpu, bool launch) vmentry_failed: vcpu->arch.nested_run_pending = 0; - if (status == NVMX_VMENTRY_KVM_INTERNAL_ERROR) + if (status == NVMX_VMENTRY_KVM_INTERNAL_ERROR) { + vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; + vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_EMULATION; + vcpu->run->internal.ndata = 0; return 0; + } if (status == NVMX_VMENTRY_VMEXIT) return 1; WARN_ON_ONCE(status != NVMX_VMENTRY_VMFAIL); diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 4ec17aaff413..aad065d035fb 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -8072,6 +8072,9 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) if (kvm_check_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu)) { if (unlikely(!kvm_nested_call(get_nested_state_pages)(vcpu))) { + vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; + vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_EMULATION; + vcpu->run->internal.ndata = 0; kvm_make_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu); r = 0; goto out; From 277d3623d99a4fc2623bfb7d191b649ca380605d Mon Sep 17 00:00:00 2001 From: Zeng Chi Date: Mon, 21 Sep 2026 18:24:42 +0800 Subject: [PATCH 2184/2366] KVM: Don't treat reserved xarray entries as having memory attributes kvm_vm_set_mem_attributes() reserves an xarray entry for every gfn in the range before storing the new attributes, so that the store loop can't fail partway through. If one of the reservations fails, e.g. with -ENOMEM, the entries that were already reserved are left in the array. That is harmless as far as xa_reserve() is concerned, as the reserved entries read back as NULL via xa_load(), but it confuses the "does this range have no attributes at all" check: if (!attrs) return !xas_find(&xas, end - 1); A reserved entry is XA_ZERO_ENTRY, not NULL, and xas_find() returns it as present. So a leftover reservation makes KVM report that a fully shared range has attributes even though kvm_get_memory_attributes() returns none for every gfn in the range. On x86, the next time mixed-attribute tracking is recomputed for the range (memslot creation, or a later attribute change that straddles the 2MiB page), hugepage_has_attrs() treats a fully shared 2MiB range as mixed and refuses to map it with a hugepage, until userspace happens to set attributes on the range again. Drop the shortcut and handle the !attrs case in the per-index loop, using xas_next_entry() to find the next non-NULL entry. xas_next_entry() is essentially an optimized xas_find(), so the effective change is that the !attrs lookup now goes through xas_retry() like the attrs != 0 case, i.e. reserved entries are skipped and retry entries restart the walk. Don't check the index when no entry is found, as the xarray leaves the xas index in a bogus state in that case; no entry simply means the rest of the range has no attributes. KVM never stores a non-NULL entry with a value of zero (clearing stores NULL), but such an entry would be returned by xas_next_entry() and trip the index check, so WARN if one is ever seen. Fixes: 5a475554db1e ("KVM: Introduce per-page memory attributes") Cc: stable@vger.kernel.org Suggested-by: Sean Christopherson Cc: David Ballesteros Signed-off-by: Zeng Chi Link: https://patch.msgid.link/20260921102442.1232375-1-zeng_chi911@163.com [sean: expand comment to elaborate on xarray APIs, split optimization out] Signed-off-by: Sean Christopherson --- virt/kvm/kvm_main.c | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 65eb26a0520d..2a046e95f95e 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -2447,14 +2447,36 @@ bool kvm_range_has_memory_attributes(struct kvm *kvm, gfn_t start, gfn_t end, return (kvm_get_memory_attributes(kvm, start) & mask) == attrs; guard(rcu)(); - if (!attrs) - return !xas_find(&xas, end - 1); + /* + * Lookup the entry for each index instead of iterating over the xarray + * as KVM deletes/nullifies entries to represent "no attributes", and + * the xas index is effectively invalid when no entry is found. I.e. + * matching non-zero attributes for *every* entry effectively requires + * a manually lookup for each index. + * + * Skip pre-allocated, reserved entries, or restart the lookup if the + * xarray was concurrently modified, via xas_retry() ("retry" means the + * entry holds an internal xarray value, i.e. is either invalid or NULL + * from the caller's perspective). + * + * Use xas_next() when looking for non-zero attributes to optimize for + * the case where the start of the range (or the entire range) doesn't + * have any attributes, as xas_next() returns literally the next entry, + * whereas xas_next_entry() returns the next non-NULL entry (bounded by + * a maximum index). + */ for (index = start; index < end; index++) { do { - entry = xas_next(&xas); + entry = attrs ? xas_next(&xas) : + xas_next_entry(&xas, end - 1); } while (xas_retry(&xas, entry)); + if (!entry) + return !attrs; + + WARN_ON_ONCE(!xa_to_value(entry)); + if (xas.xa_index != index || (xa_to_value(entry) & mask) != attrs) return false; From 382e5d514b6f35bdda2ab9044b4eed23d2ec4254 Mon Sep 17 00:00:00 2001 From: David Ballesteros Date: Tue, 15 Sep 2026 17:53:57 +0000 Subject: [PATCH 2185/2366] KVM: Ensure memory attributes xarray nodes are accounted to the caller's memcg Explicitly instantiate the memory attributes xarray with XA_FLAGS_ACCOUNT to ensure that all allocations are accounted to the memcg. Frustratingly, memory allocations done in the "fastpath" do not honor the passed in gfp, even for an explicit xa_reserve(). Only the rare, slow path __xas_nomem() honors the original gfp. E.g. xa_reserve(..., GFP_KERNEL_ACCOUNT) | -> ... | -> __xa_cmpxchg_raw() | -> xas_store() <== does not take @gfp | -> xas_create() | -> xas_alloc() The bug was confirmed by observing that a process in a cgroup limited to 256 MiB grew radix_tree_node slab by ~512 MiB while its memory.current stayed near 0. Fixes: 5a475554db1e ("KVM: Introduce per-page memory attributes") Cc: stable@vger.kernel.org Assisted-by: Claude-Code:claude-opus-5 Signed-off-by: David Ballesteros Link: https://patch.msgid.link/20260915175335.138547-4-davimaba.v@proton.me [sean: rewrite changelog, tag for stable] Signed-off-by: Sean Christopherson --- virt/kvm/kvm_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 2a046e95f95e..df643ec3ea75 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -1117,7 +1117,7 @@ static struct kvm *kvm_create_vm(unsigned long type, const char *fdname) rcuwait_init(&kvm->mn_memslots_update_rcuwait); xa_init(&kvm->vcpu_array); #ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES - xa_init(&kvm->mem_attr_array); + xa_init_flags(&kvm->mem_attr_array, XA_FLAGS_ACCOUNT); #endif INIT_LIST_HEAD(&kvm->gpc_list); From 119e9db233ad0f4cbd4cfb9f9385a7bca378b37d Mon Sep 17 00:00:00 2001 From: Zeng Chi Date: Mon, 21 Sep 2026 18:24:42 +0800 Subject: [PATCH 2186/2366] KVM: Don't pre-reserve xarray entries when storing empty/NULL attributes Skip the xarray reservation loop when clearing all memory attributes, as storing NULL only erases the entry and never needs to allocate, so no reservation (and no cleanup of a failed one) is required in that case. Suggested-by: Sean Christopherson Cc: David Ballesteros Signed-off-by: Zeng Chi Link: https://patch.msgid.link/20260921102442.1232375-1-zeng_chi911@163.com [sean: split to separate patch] Signed-off-by: Sean Christopherson --- virt/kvm/kvm_main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index df643ec3ea75..85f42289748d 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -2593,9 +2593,10 @@ static int kvm_vm_set_mem_attributes(struct kvm *kvm, gfn_t start, gfn_t end, /* * Reserve memory ahead of time to avoid having to deal with failures - * partway through setting the new attributes. + * partway through setting the new attributes. Storing NULL never + * allocates, so no reservations are needed when clearing. */ - for (i = start; i < end; i++) { + for (i = start; entry && i < end; i++) { r = xa_reserve(&kvm->mem_attr_array, i, GFP_KERNEL_ACCOUNT); if (r) goto out_unlock; From 141008dec73521ccf64878517460cec8b3297251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Aceda=C5=84ski?= Date: Wed, 16 Sep 2026 19:30:30 +0200 Subject: [PATCH 2187/2366] drm/xe: Limit sg segment size to PAGE_SIZE on Xen PV MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix display corruption on Xen PV dom0, where DMA buffers are not guaranteed machine-contiguous, in which case bounce buffering kicks in, breaking xe's memory coherency assumptions. Apply the same workaround i915 carries in i915_sg_segment_size() since commit 78a07fe777c4 ("drm/i915: stop abusing swiotlb_max_segment"). Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Reported-by: Marek Marczykowski-Górecki Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/8382 Link: https://lore.kernel.org/xen-devel/aYtznP_tT6xNPwf-@mail-itl/ Link: https://lore.kernel.org/all/20221020110308.1582518-1-hch@lst.de/ # i915 counterpart Cc: Christoph Hellwig Cc: Robert Beckett Cc: stable@vger.kernel.org # v6.8+ Signed-off-by: Szymon Acedański Reviewed-by: Thomas Hellström Signed-off-by: Thomas Hellström Link: https://patch.msgid.link/20260916173030.3223833-1-accek@invisiblethingslab.com (cherry picked from commit 77f704158f099b952681f207478a22d5b8218edb) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_bo.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h index e8081af5bfc1..152bfcffe399 100644 --- a/drivers/gpu/drm/xe/xe_bo.h +++ b/drivers/gpu/drm/xe/xe_bo.h @@ -9,6 +9,8 @@ #include #include +#include + #include "xe_bo_types.h" #include "xe_ggtt.h" #include "xe_macros.h" @@ -575,6 +577,23 @@ static inline unsigned int xe_sg_segment_size(struct device *dev) struct scatterlist __maybe_unused sg; size_t max = BIT_ULL(sizeof(sg.length) * 8) - 1; + /* + * For Xen PV guests pages aren't contiguous in DMA (machine) address + * space. The DMA API takes care of that both in dma_alloc_* (by + * calling into the hypervisor to make the pages contiguous) and in + * dma_map_* (by bounce buffering). But xe (like i915, see commit + * 78a07fe777c4) ignores the coherency aspects of the DMA API and thus + * can't cope with bounce buffering actually happening, so add a hack + * here to force small allocations and mappings when running in PV + * mode on Xen. + * + * Note this will still break if bounce buffering is required for other + * reasons, like confidential computing hypervisors or PCIe root ports + * with addressing limitations. + */ + if (xen_pv_domain()) + return PAGE_SIZE; + max = min_t(size_t, max, dma_max_mapping_size(dev)); /* From 90f467577ebc28c5bc4ba2f0f1b83a4419fb0740 Mon Sep 17 00:00:00 2001 From: Tangudu Tilak Tirumalesh Date: Wed, 16 Sep 2026 15:35:44 +0530 Subject: [PATCH 2188/2366] drm/xe: harden adjust_idledly() against divide-by-zero and overflow adjust_idledly() has several corner-case issues flagged during review: 1. If xe_gt_clock_init() failed to recognise the crystal clock, gt->info.timestamp_base is 0, which makes idledly_units_ps also 0. The subsequent DIV_ROUND_CLOSEST(..., idledly_units_ps) is then a divide-by-zero and panics the kernel. 2. The tick-to-ns conversions are done in u32: idledly * idledly_units_ps, (maxcnt - 1) * 1000 Both overflow u32 before DIV_ROUND_CLOSEST() sees them. 3. If IDLE_WAIT_TIME reads back as 0, maxcnt evaluates to 0 and the maxcnt - 1 clamp wraps to 0xFFFFFFFF in u32. 4. The register only stores whole ticks, so the clamped ns value has to be converted to ticks and back. DIV_ROUND_CLOSEST() can round that conversion up past maxcnt: maxcnt = 640 ns, one tick = 666664 ps clamp: maxcnt - 1 = 639 ns ns -> ticks: 639000 / 666664 = 0.958 -> rounds to 1 tick tick -> ns: 1 * 666664 / 1000 = 667 ns 667 ns is programmed into RING_IDLEDLY, but 667 >= maxcnt (640), so xe_gt_WARN_ON() fires again on every subsequent init. Return early if timestamp_base is 0 (the unknown-crystal path). Do the conversions in u64 via the *_ULL() helpers so they cannot wrap. Clamp with a floor (DIV_ROUND_DOWN_ULL) so the programmed delay stays strictly below maxcnt, and guard the maxcnt == 0 case with a zero delay while still writing RING_IDLEDLY so INHIBIT_SWITCH_UNTIL_PREEMPTED is cleared. v2: Drop the redundant warn on the timestamp_base == 0 path; xe_gt_clock_init() already warns on an unrecognised crystal clock. Keep the early return to avoid the divide-by-zero. - Vinay v3: Field-mask the RING_IDLEDLY write with REG_FIELD_PREP(IDLE_DELAY, ...) instead of writing the raw tick count, which could clobber INHIBIT_SWITCH_UNTIL_PREEMPTED and reserved bits. Split the inhibit-switch clear from the maxcnt clamp so a set inhibit bit no longer forces a needless delay overwrite when the delay itself is already valid. Use gt_to_xe(gt) instead of gt_to_xe(hwe->gt). Fixes: d2de4410a88f ("drm/xe: Apply Wa_16023105232") Cc: stable@vger.kernel.org Assisted-by: GitHub_Copilot:claude-opus-4.8 Signed-off-by: Tangudu Tilak Tirumalesh Reviewed-by: Vinay Belgaumkar Link: https://patch.msgid.link/20260916100545.779894-2-tilak.tirumalesh.tangudu@intel.com Signed-off-by: Matt Roper (cherry picked from commit d864065ea25e9d12897c175de9176ce46677e176) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_hw_engine.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_hw_engine.c b/drivers/gpu/drm/xe/xe_hw_engine.c index 010499766fce..d868af8e3301 100644 --- a/drivers/gpu/drm/xe/xe_hw_engine.c +++ b/drivers/gpu/drm/xe/xe_hw_engine.c @@ -592,22 +592,37 @@ static void adjust_idledly(struct xe_hw_engine *hwe) u32 idledly_units_ps = 8 * gt->info.timestamp_base; u32 maxcnt_units_ns = 640; bool inhibit_switch = 0; + bool wa_applied = false; + + if (!IS_SRIOV_VF(gt_to_xe(gt)) && XE_GT_WA(gt, 16023105232)) { + /* xe_gt_clock_init() warns and zeroes timestamp_base on unknown crystal clock. */ + if (!idledly_units_ps) + return; - if (!IS_SRIOV_VF(gt_to_xe(hwe->gt)) && XE_GT_WA(gt, 16023105232)) { idledly = xe_mmio_read32(>->mmio, RING_IDLEDLY(hwe->mmio_base)); maxcnt = xe_mmio_read32(>->mmio, RING_PWRCTX_MAXCNT(hwe->mmio_base)); inhibit_switch = idledly & INHIBIT_SWITCH_UNTIL_PREEMPTED; idledly = REG_FIELD_GET(IDLE_DELAY, idledly); - idledly = DIV_ROUND_CLOSEST(idledly * idledly_units_ps, 1000); + idledly = DIV_ROUND_CLOSEST_ULL((u64)idledly * idledly_units_ps, 1000); maxcnt = REG_FIELD_GET(IDLE_WAIT_TIME, maxcnt); maxcnt *= maxcnt_units_ns; - if (xe_gt_WARN_ON(gt, idledly >= maxcnt || inhibit_switch)) { - idledly = DIV_ROUND_CLOSEST(((maxcnt - 1) * 1000), - idledly_units_ps); - xe_mmio_write32(>->mmio, RING_IDLEDLY(hwe->mmio_base), idledly); + /* Clear the inhibit switch without disturbing a valid delay. */ + if (inhibit_switch) + wa_applied = true; + + if (xe_gt_WARN_ON(gt, idledly >= maxcnt)) { + /* Floor below maxcnt; write 0 to still clear the inhibit bit. */ + idledly = maxcnt ? + DIV_ROUND_DOWN_ULL((u64)(maxcnt - 1) * 1000, + idledly_units_ps) : 0; + wa_applied = true; } + + if (wa_applied) + xe_mmio_write32(>->mmio, RING_IDLEDLY(hwe->mmio_base), + REG_FIELD_PREP(IDLE_DELAY, idledly)); } } From bd3a19800dd1ba1ba9a4c307d9dfe50eac443c01 Mon Sep 17 00:00:00 2001 From: Tingmao Wang Date: Sun, 8 Feb 2026 23:54:48 +0000 Subject: [PATCH 2189/2366] landlock: Add counted_by in landlock_domain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For a domain, this array stores the access masks for each layer (of which there are num_layers of them). This annotation serves as useful documentation. Signed-off-by: Tingmao Wang Reviewed-by: Günther Noack Link: https://patch.msgid.link/20260208235449.1124354-1-m@maowtm.org [mic: Rebase on the ruleset/domain split, and update commit message] Signed-off-by: Mickaël Salaün --- security/landlock/domain.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/landlock/domain.h b/security/landlock/domain.h index 5ce2f91488d5..caa3d19d2c43 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -243,7 +243,8 @@ struct landlock_domain { * overlapping access rights. These layers are set once * and never changed for the lifetime of the domain. */ - struct access_masks handled_masks[]; + struct access_masks + handled_masks[] __counted_by(num_layers); }; }; }; From e7e0a54300a896e731dffd3e2e8dae5631d6243d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Tue, 22 Sep 2026 15:26:14 +0200 Subject: [PATCH 2190/2366] landlock: Widen ruleset versions to 64 bits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tracepoint consumers use a ruleset ID and version to identify the successful landlock_add_rule(2) call prefix used to create a domain. LANDLOCK_MAX_NUM_RULES bounds distinct stored rules, not successful calls: re-adding already-present rights for an object or port succeeds without increasing num_rules. Because every successful call increments the version, these calls can wrap the 32-bit counter and give different prefixes the same trace identity. Widen the counter and its trace fields to 64 bits so the counter cannot wrap in practice, while preserving the successful-call semantics. Saturating would alias all subsequent histories, while rejecting a call at the limit would change otherwise valid syscall behavior solely for trace metadata. Cc: Günther Noack Cc: Steven Rostedt Fixes: 63747c94774d ("landlock: Add landlock_add_rule_fs and landlock_add_rule_net tracepoints") Reviewed-by: Günther Noack Link: https://patch.msgid.link/20260922132615.1025945-1-mic@digikod.net Signed-off-by: Mickaël Salaün --- include/trace/events/landlock.h | 20 ++++++++++---------- security/landlock/ruleset.h | 5 +++-- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index 523ba5ea9870..3a43638c9bc2 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -344,7 +344,7 @@ TRACE_EVENT(landlock_create_ruleset, TP_STRUCT__entry( __field( u64, ruleset_id ) - __field( u32, ruleset_version ) + __field( u64, ruleset_version ) __field( access_mask_t, handled_fs ) __field( access_mask_t, handled_net ) __field( access_mask_t, scoped ) @@ -358,7 +358,7 @@ TRACE_EVENT(landlock_create_ruleset, __entry->scoped = ruleset->handled_masks.scope; ), - TP_printk("ruleset=%llx.%u handled_fs=%s handled_net=%s scoped=%s", + TP_printk("ruleset=%llx.%llu handled_fs=%s handled_net=%s scoped=%s", __entry->ruleset_id, __entry->ruleset_version, __print_flags(__entry->handled_fs, "|", _LANDLOCK_ACCESS_FS_NAMES), __print_flags(__entry->handled_net, "|", _LANDLOCK_ACCESS_NET_NAMES), @@ -384,7 +384,7 @@ TRACE_EVENT(landlock_free_ruleset, TP_STRUCT__entry( __field( u64, ruleset_id ) - __field( u32, ruleset_version ) + __field( u64, ruleset_version ) ), TP_fast_assign( @@ -392,7 +392,7 @@ TRACE_EVENT(landlock_free_ruleset, __entry->ruleset_version = ruleset->version; ), - TP_printk("ruleset=%llx.%u", + TP_printk("ruleset=%llx.%llu", __entry->ruleset_id, __entry->ruleset_version) ); @@ -423,7 +423,7 @@ TRACE_EVENT(landlock_add_rule_path_beneath, TP_STRUCT__entry( __field( u64, ruleset_id ) - __field( u32, ruleset_version ) + __field( u64, ruleset_version ) __field( access_mask_t, access_rights ) __field( dev_t, dev ) __field( ino_t, ino ) @@ -444,7 +444,7 @@ TRACE_EVENT(landlock_add_rule_path_beneath, __assign_str(pathname); ), - TP_printk("ruleset=%llx.%u access_rights=%s dev=%u:%u ino=%lu path=%s", + TP_printk("ruleset=%llx.%llu access_rights=%s dev=%u:%u ino=%lu path=%s", __entry->ruleset_id, __entry->ruleset_version, __print_flags(__entry->access_rights, "|", _LANDLOCK_ACCESS_FS_NAMES), MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, @@ -477,7 +477,7 @@ TRACE_EVENT(landlock_add_rule_net_port, TP_STRUCT__entry( __field( u64, ruleset_id ) - __field( u32, ruleset_version ) + __field( u64, ruleset_version ) __field( access_mask_t, access_rights ) __field( u64, port ) ), @@ -490,7 +490,7 @@ TRACE_EVENT(landlock_add_rule_net_port, __entry->port = port; ), - TP_printk("ruleset=%llx.%u access_rights=%s port=%llu", + TP_printk("ruleset=%llx.%llu access_rights=%s port=%llu", __entry->ruleset_id, __entry->ruleset_version, __print_flags(__entry->access_rights, "|", _LANDLOCK_ACCESS_NET_NAMES), __entry->port) @@ -526,7 +526,7 @@ TRACE_EVENT(landlock_create_domain, __field( u64, domain_id ) __field( u64, parent_id ) __field( u64, ruleset_id ) - __field( u32, ruleset_version ) + __field( u64, ruleset_version ) ), TP_fast_assign( @@ -538,7 +538,7 @@ TRACE_EVENT(landlock_create_domain, __entry->ruleset_version = ruleset->version; ), - TP_printk("domain=%llx parent=%llx ruleset=%llx.%u", + TP_printk("domain=%llx parent=%llx ruleset=%llx.%llu", __entry->domain_id, __entry->parent_id, __entry->ruleset_id, __entry->ruleset_version) ); diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index b536fa0425b7..cf77f1806a95 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -171,9 +171,10 @@ struct landlock_ruleset { * @version: Counter incremented on each successful * landlock_add_rule(2), including when it only extends an existing * rule's access rights. Used by tracepoints to correlate a domain with - * the exact ruleset state it was created from. Protected by @lock. + * the exact successful rule history it was created from. Protected by + * @lock. */ - u32 version; + u64 version; /** * @id: Unique identifier for this ruleset, used for tracing. */ From 41112a787f9182c7f2d27122817861e3f22ef928 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 20 Sep 2026 16:35:25 +0200 Subject: [PATCH 2191/2366] PM: hibernate: Freeze kernel threads after image preallocation Commit 783c81098445 ("PM: hibernate: call preallocate_image() after freeze prepare") moved hibernate_preallocate_memory() after dpm_prepare() so that device drivers have the opportunity to release pinned/unswappable memory during their ->prepare() callback before memory is preallocated for the snapshot image. However, that commit also placed hibernate_preallocate_memory() after freeze_kernel_threads(). While it was assumed during review that swap I/O submitted via submit_bio() is synchronous and would not depend on frozen kernel threads, this does not hold in practice. Calling hibernate_preallocate_memory() with kernel threads frozen leads to intermittent deadlocks during hibernation. Inside hibernate_preallocate_memory(), shrink_all_memory() is invoked with .may_writepage = 1 and .may_swap = 1 to aggressively reclaim and swap out pages. Any writeback or swap I/O that relies on freezable kernel threads, block device helpers, or WQ_FREEZABLE workqueues (such as those in storage drivers, device mapper, or filesystems) deadlocks waiting on tasks that are stuck in the refrigerator. Fix this by reordering hibernation_snapshot(): 1. Call dpm_prepare(PMSG_FREEZE) first, allowing device drivers to release pinned resources while kernel threads are still active. 2. Call hibernate_preallocate_memory() second, performing page reclaim and swapout while storage layers, workqueues, and kernel threads are alive. 3. Call freeze_kernel_threads() third, only after all memory preallocation and swap I/O have completed. Additionally, restore the call to swsusp_free() in the cleanup path so that preallocated image memory is properly freed if freeze_kernel_threads() fails or if TEST_FREEZER is enabled. Fixes: 783c81098445 ("PM: hibernate: call preallocate_image() after freeze prepare") Signed-off-by: Florian Schmaus Reviewed-by: Mario Limonciello (AMD) Tested-by: Matthew Leach Reviewed-by: Matthew Leach Link: https://patch.msgid.link/20260920-fix-hibernation-v1-1-f9940c2d7d7f@geekplace.eu Signed-off-by: Rafael J. Wysocki --- kernel/power/hibernate.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index d2479c69d71a..c13f68ab7f6e 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -408,9 +408,18 @@ int hibernation_snapshot(int platform_mode) if (error) goto Close; + error = dpm_prepare(PMSG_FREEZE); + if (error) + goto Complete; + + /* Preallocate image memory before freezing kernel threads and shutting down devices. */ + error = hibernate_preallocate_memory(); + if (error) + goto Complete; + error = freeze_kernel_threads(); if (error) - goto Close; + goto Cleanup; if (hibernation_test(TEST_FREEZER)) { @@ -422,15 +431,6 @@ int hibernation_snapshot(int platform_mode) goto Thaw; } - error = dpm_prepare(PMSG_FREEZE); - if (error) - goto Complete; - - /* Preallocate image memory before shutting down devices. */ - error = hibernate_preallocate_memory(); - if (error) - goto Complete; - console_suspend_all(); pm_restrict_gfp_mask(); @@ -464,10 +464,12 @@ int hibernation_snapshot(int platform_mode) platform_end(platform_mode); return error; - Complete: - dpm_complete(PMSG_RECOVER); Thaw: thaw_kernel_threads(); + Cleanup: + swsusp_free(); + Complete: + dpm_complete(PMSG_RECOVER); goto Close; } From ec0d89150a9381d591344a9f6f5428655c227a7f Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Tue, 22 Sep 2026 17:49:02 +0530 Subject: [PATCH 2192/2366] thermal: gov_step_wise: Fix stale mitigation vote with non-zero lower bounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When two or more thermal zones bind to a common cooling device and one zone uses a non-zero instance->lower value, there is a bug where the instance holds a stale mitigation vote even after its trip is cleared. Problem scenario: - thermal-zone1: Trip at 50°C, cooling-map with lower=0 - thermal-zone2: Trip at 55°C, cooling-map with lower=2 - Both zones share the same cooling device (e.g., CPU) Issue flow: 1. Both trips trigger, zone1 requests state 5, zone2 also mitigates 2. Zone2 trip clears (temp < 53°C due to hysteresis) 3. When throttle=false and trend=THERMAL_TREND_DROPPING: - Current code checks: if (cur_state <= instance->lower) return THERMAL_NO_TARGET - Since cur_state (5) > instance->lower (2), it returns instance->lower (2) - This is the BUG where it returns instance->lower even though trip is cleared 4. Zone2's passive polling stops (tz->passive reaches 0) - no more updates for zone2 5. Zone2's stale vote of 2 persists indefinitely 6. Even when zone1 wants to reduce cooling to state, the cooling device cannot go below state 2 due to zone2's stale vote When a trip is cleared (throttle == false), always return THERMAL_NO_TARGET instead of instance->lower. Remove the unnecessary check comparing cur_state with instance->lower. Since passive polling is already deactivated when the trip is cleared, the instance should always be deactivated regardless of its current cooling state. This ensures that instances with non-zero lower bounds do not retain stale mitigation votes after their trips are cleared. Fixes: 042a3d80f118 ("thermal: core: Move passive polling management to the core") Signed-off-by: Manaf Meethalavalappu Pallikunhi Link: https://patch.msgid.link/20260922-step_wise_multi_zone_stale_vote_fix-v1-1-789f68dab229@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/gov_step_wise.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/gov_step_wise.c b/drivers/thermal/gov_step_wise.c index ea277c466d8d..4fa4377f0d42 100644 --- a/drivers/thermal/gov_step_wise.c +++ b/drivers/thermal/gov_step_wise.c @@ -65,14 +65,12 @@ static unsigned long get_target_state(struct thermal_instance *instance, min(instance->lower + 1, instance->upper), instance->upper); } else if (trend == THERMAL_TREND_DROPPING) { - if (cur_state <= instance->lower) - return THERMAL_NO_TARGET; - /* - * If 'throttle' is false, no mitigation is necessary, so - * request the lower state for this instance. + * If 'throttle' is false, no mitigation is necessary and + * passive polling is already deactivated, so clear this + * instance state by returning THERMAL_NO_TARGET. */ - return instance->lower; + return THERMAL_NO_TARGET; } return instance->target; From ed6eec97b534979dcf28b40c389cee57bd6561d4 Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:18 +0000 Subject: [PATCH 2193/2366] bpf: Fix bounds check for skb-backed dynptrs The skb_pointer_if_linear() function checks whether a memory region of length len starting at offset off into the skb is in the linear area, and returns a pointer to the region if so. The check currently subtracts between skb_headlen and offset of the check, and since skb_headlen is unsigned the subtraction can underflow. This causes the bounds check to spuriously pass and generate an arbitrary pointer of the form *(skb->data + off). The only user of this helper is currently skb-backed BPF dynptr code. Returning the wrong pointer leads to the dynptr erroneously being backed with invalid memory. Ensure the subtraction cannot underflow, and fail the check if it would. Use u64 arithmetic to also prevent overflow when calculating (skb_headlen(skb) - off) since off is unsigned. Fixes: 6f5a630d7c57 ("bpf, net: Introduce skb_pointer_if_linear().") Reported-by: Nicholas Carlini Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20260922172028.6269-2-emil@etsalapatis.com --- include/linux/skbuff.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 421f6fc45451..c8e21903074c 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -4372,7 +4372,10 @@ skb_header_pointer_careful(const struct sk_buff *skb, int offset, static inline void * __must_check skb_pointer_if_linear(const struct sk_buff *skb, int offset, int len) { - if (likely(skb_headlen(skb) - offset >= len)) + unsigned int uoffset = (unsigned int)offset; + + if (likely(uoffset <= skb_headlen(skb) && + (unsigned int)len <= skb_headlen(skb) - uoffset)) return skb->data + offset; return NULL; } From 4fd72eb9f1c939ce33bb714c6f745a125ac5f40c Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:19 +0000 Subject: [PATCH 2194/2366] selftests/bpf: Test dynptr slices past end of skb Add a selftest to ensure dynptr slices cannot include past the end of the linear area of an skb. Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-3-emil@etsalapatis.com --- .../testing/selftests/bpf/prog_tests/dynptr.c | 10 ++++++++++ .../selftests/bpf/progs/dynptr_success.c | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/dynptr.c b/tools/testing/selftests/bpf/prog_tests/dynptr.c index 5fda11590708..4396560365e8 100644 --- a/tools/testing/selftests/bpf/prog_tests/dynptr.c +++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c @@ -9,6 +9,7 @@ enum test_setup_type { SETUP_SYSCALL_SLEEP, SETUP_SKB_PROG, + SETUP_SKB_PROG_NONLINEAR, SETUP_SKB_PROG_TP, SETUP_XDP_PROG, }; @@ -32,6 +33,7 @@ static struct { {"test_ringbuf", SETUP_SYSCALL_SLEEP}, {"test_skb_readonly", SETUP_SKB_PROG}, {"test_dynptr_skb_data", SETUP_SKB_PROG}, + {"test_dynptr_skb_slice_non_linear", SETUP_SKB_PROG_NONLINEAR}, {"test_dynptr_skb_meta_data", SETUP_SKB_PROG}, {"test_dynptr_skb_meta_flags", SETUP_SKB_PROG}, {"test_adjust", SETUP_SYSCALL_SLEEP}, @@ -94,7 +96,9 @@ static void verify_success(const char *prog_name, enum test_setup_type setup_typ bpf_link__destroy(link); break; case SETUP_SKB_PROG: + case SETUP_SKB_PROG_NONLINEAR: { + struct __sk_buff ctx = {}; int prog_fd; char buf[64]; @@ -106,6 +110,12 @@ static void verify_success(const char *prog_name, enum test_setup_type setup_typ .repeat = 1, ); + if (setup_type == SETUP_SKB_PROG_NONLINEAR) { + ctx.data_end = ETH_HLEN + sizeof(struct iphdr); + topts.ctx_in = &ctx; + topts.ctx_size_in = sizeof(ctx); + } + prog_fd = bpf_program__fd(prog); if (!ASSERT_GE(prog_fd, 0, "prog_fd")) goto cleanup; diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c index e0745b6e467e..b668ebd61fc7 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_success.c +++ b/tools/testing/selftests/bpf/progs/dynptr_success.c @@ -10,6 +10,7 @@ #include "errno.h" #define PAGE_SIZE_64K 65536 +#define TEST_SKB_LINEAR_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr)) char _license[] SEC("license") = "GPL"; @@ -211,6 +212,25 @@ int test_dynptr_skb_data(struct __sk_buff *skb) return 1; } +SEC("?tc") +int test_dynptr_skb_slice_non_linear(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr; + void *data; + + if (bpf_dynptr_from_skb(skb, 0, &ptr)) { + err = 1; + return 1; + } + + /* Ensure we cannot read past the end of the buffer. */ + data = bpf_dynptr_slice(&ptr, TEST_SKB_LINEAR_SIZE + 1, NULL, 1); + if (data) + err = 2; + + return 1; +} + SEC("?tc") int test_dynptr_skb_meta_data(struct __sk_buff *skb) { From 4a4852376e3a2727ea40e61143d6d7c22bb6dfad Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:20 +0000 Subject: [PATCH 2195/2366] bpf: Fix bpf_sock context code generation Currently, the ctx access code reads the rx_queue_mapping field with either a 4-byte or 2-byte load. The rest of the bits in the register are marked known zero by the verifier. However, the emitted ctx access code places in the register on certain the special value (-1) using BPF_MOV_IMM64, which gets sign-extended to turn on all the bits in the register. By shifting this value right, the program ends up with a value at runtime above what the verifier assumes is possible. Fix this by ensuring the read value is as wide as the assumed size. Use MOV32 instructions instead of MOV64 instructions to keep the upper bits zero as assumed by the verifier. Also properly report the size of the destination variable (the bpf_sock field, 4 bytes) instead of the source (the socket field, 2 bytes). Fixes: c3c16f2ea6d2 ("bpf: Add rx_queue_mapping to bpf_sock") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20260922172028.6269-4-emil@etsalapatis.com --- net/core/filter.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 532405988fd9..70dc621672f2 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -10566,11 +10566,12 @@ u32 bpf_sock_convert_ctx_access(enum bpf_access_type type, target_size)); *insn++ = BPF_JMP_IMM(BPF_JNE, si->dst_reg, NO_QUEUE_MAPPING, 1); - *insn++ = BPF_MOV64_IMM(si->dst_reg, -1); + *insn++ = BPF_MOV32_IMM(si->dst_reg, -1); #else - *insn++ = BPF_MOV64_IMM(si->dst_reg, -1); - *target_size = 2; + *insn++ = BPF_MOV32_IMM(si->dst_reg, -1); #endif + *target_size = sizeof_field(struct bpf_sock, rx_queue_mapping); + break; } From dec0c209a6808fe6de2e4787538e02786a16a4ef Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:21 +0000 Subject: [PATCH 2196/2366] selftests/bpf: Add selftests for rx_queue_mapping context access Add tests to ensure the verifier properly tracks the 0 bit state and width of the rx_queue_mapping field read from struct sock. Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-5-emil@etsalapatis.com --- .../selftests/bpf/progs/verifier_sock.c | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_sock.c b/tools/testing/selftests/bpf/progs/verifier_sock.c index 4f2f3209eec8..bf9f6fb6582c 100644 --- a/tools/testing/selftests/bpf/progs/verifier_sock.c +++ b/tools/testing/selftests/bpf/progs/verifier_sock.c @@ -88,6 +88,44 @@ l0_%=: r0 = *(u32*)(r1 + %[bpf_sock_family]); \ : __clobber_all); } +SEC("socket") +__description("skb->sk: sk->rx_queue_mapping [no sign extension]") +__success __success_unpriv __retval(0) +__naked void sk_rx_queue_mapping_no_sign_ext(void) +{ + asm volatile (" \ + r1 = *(u64*)(r1 + %[__sk_buff_sk]); \ + if r1 != 0 goto l0_%=; \ + r0 = 0xdead; \ + exit; \ +l0_%=: r0 = *(u32*)(r1 + %[bpf_sock_rx_queue_mapping]); \ + r0 >>= 32; \ + exit; \ +" : + : __imm_const(__sk_buff_sk, offsetof(struct __sk_buff, sk)), + __imm_const(bpf_sock_rx_queue_mapping, offsetof(struct bpf_sock, rx_queue_mapping)) + : __clobber_all); +} + +SEC("socket") +__description("skb->sk: sk->rx_queue_mapping [narrow load mask]") +__success __success_unpriv __retval(0) +__naked void sk_rx_queue_mapping_narrow_load_mask(void) +{ + asm volatile (" \ + r1 = *(u64*)(r1 + %[__sk_buff_sk]); \ + if r1 != 0 goto l0_%=; \ + r0 = 0xdead; \ + exit; \ +l0_%=: r0 = *(u16*)(r1 + %[bpf_sock_rx_queue_mapping]); \ + r0 >>= 16; \ + exit; \ +" : + : __imm_const(__sk_buff_sk, offsetof(struct __sk_buff, sk)), + __imm_const(bpf_sock_rx_queue_mapping, offsetof(struct bpf_sock, rx_queue_mapping)) + : __clobber_all); +} + SEC("cgroup/skb") __description("skb->sk: sk->type [fullsock field]") __failure __msg("invalid sock_common access") From a6c1edfbe240e4377038a0e1d233ad81fde9a21b Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:22 +0000 Subject: [PATCH 2197/2366] bpf: Reject pkt arguments in mutating subprogs The verifier tracks changes in how PTR_TO_PACKET registers' bounds are modified across subprog boundaries. PTR_TO_PACKET registers are actually passed as PTR_TO_MEM, which is assumed valid for the entire call. This is not the case with packet memory, where a pskb_* call may invalidate its memory region. Reject BPF code that passes PTR_TO_PACKET pointers to subprogs that may mutate a packet. We cannot pass the pointer as a true PTR_TO_PACKET because we would also need to somehow pass the PTR_TO_PACKET_META or PTR_TO_PACKET_END to the subprog. Since we cannot avoid representing the pointer in the subprog as PTR_TO_MEM, only permit it if the subprog is guaranteed not to mutate the packet. Fixes: 80f281664f5a ("bpf: Support pointers in global func args") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-6-emil@etsalapatis.com --- kernel/bpf/verifier.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index dd8bb179d39e..7ffbb804184f 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9750,6 +9750,16 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog, if (check_mem_reg(env, reg, argno, arg->mem_size, BPF_READ | BPF_WRITE, NULL, NULL)) return -EINVAL; + /* + * PTR_TO_PACKET get passed as PTR_TO_MEM, preventing + * us from adjusting bounds tracking info. + */ + if ((reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg)) && + sub->changes_pkt_data) { + bpf_log(log, "%s is a packet pointer, but func#%d may change packet data\n", + reg_arg_name(env, argno), subprog); + return -EINVAL; + } if (!(arg->arg_type & PTR_MAYBE_NULL) && (type_may_be_null(reg->type) || bpf_register_is_null(reg))) { bpf_log(log, "%s is expected to be non-NULL\n", From 1ed69a54d31848618131aaf3311a78adbe78ced0 Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:23 +0000 Subject: [PATCH 2198/2366] selftests/bpf: Test rejection of pkt args to mutating subprogs Add a selftests that ensures that PTR_TO_PACKET arguments can only be passed to subprogs that will never adjust the underlying packet memory, and are rejected otherwise. Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-7-emil@etsalapatis.com --- .../bpf/progs/verifier_global_ptr_args.c | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c index a3d2af8dc839..dcc2dd46751a 100644 --- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c +++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c @@ -350,4 +350,57 @@ int anything_to_untrusted_mem(void *ctx) return 0; } +struct pkt_arg { + __u64 x; + __u8 pad[56]; +}; + +__weak int subprog_pkt_ptr_no_change(struct pkt_arg *p) +{ + if (!p) + return 0; + + return p->x; +} + +SEC("?tc") +__success +int pkt_ptr_to_global_mem_arg_no_change(struct __sk_buff *skb) +{ + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + struct pkt_arg *p = data; + + if ((void *)(p + 1) > data_end) + return 0; + + return subprog_pkt_ptr_no_change(p); +} + +__weak int subprog_pkt_ptr_changes_data(struct __sk_buff *skb __arg_ctx, + struct pkt_arg *p) +{ + if (!p) + return 0; + + bpf_skb_pull_data(skb, 0); + return p->x; +} + +SEC("?tc") +__failure __log_level(2) +__msg("R2 is a packet pointer, but func#{{[0-9]+}} may change packet data") +__msg("Caller passes invalid args into func#{{[0-9]+}} ('subprog_pkt_ptr_changes_data')") +int pkt_ptr_to_global_mem_arg_changes_data(struct __sk_buff *skb) +{ + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + struct pkt_arg *p = data; + + if ((void *)(p + 1) > data_end) + return 0; + + return subprog_pkt_ptr_changes_data(skb, p); +} + char _license[] SEC("license") = "GPL"; From f85f5917aa2fbd861c72ea71ecb14a2fa915c3f6 Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:24 +0000 Subject: [PATCH 2199/2366] bpf: Prevent variable arena/non-arena register contents The verifier marks ALU instructions that include at least one arena operand with needs_zext: These instructions are fixed up after verification to be ALU32 instructions to ensure that the result is a valid offset into an arena. However, different code paths may provide two non-arena 64-bit arguments to the same instruction. The result of the operation in that code path is wrong, since it is now unexpectedly truncated to 32 bits and zero-extended. Add logic to the verifier to ensure every instruction either always has at least one PTR_TO_ARENA argument, or never does. Since needs_zext already tracks the first scenario, add a prevent_zext field in bpf_insn_aux to track the latter. Reject instructions that use arena arguments and have prevent_zext set, or do not have arena arguments and have needs_zext set. Fixes: 6082b6c328b5 ("bpf: Recognize addr_space_cast instruction in the verifier.") Reported-by: Nicholas Carlini Suggested-by: Nicholas Carlini Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-8-emil@etsalapatis.com --- include/linux/bpf_verifier.h | 1 + kernel/bpf/verifier.c | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 6fe8e5dc57aa..b83ec99f1a13 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -680,6 +680,7 @@ struct bpf_insn_aux_data { bool nospec_result; /* result is unsafe under speculation, nospec must follow */ bool zext_dst; /* this insn zero extends dst reg */ bool needs_zext; /* alu op needs to clear upper bits */ + bool prevent_zext; /* alu op cannot be zext (already used with 64-bit scalars) */ bool non_sleepable; /* helper/kfunc may be called from non-sleepable context */ bool is_iter_next; /* bpf_iter__next() kfunc call */ bool call_with_percpu_alloc_ptr; /* {this,per}_cpu_ptr() with prog percpu alloc */ diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 7ffbb804184f..41b49c56e123 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -15741,6 +15741,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, struct bpf_reg_state *regs = state->regs, *dst_reg, *src_reg; struct bpf_reg_state *ptr_reg = NULL, off_reg = {0}; bool alu32 = (BPF_CLASS(insn->code) != BPF_ALU64); + struct bpf_insn_aux_data *aux = cur_aux(env); u8 opcode = BPF_OP(insn->code); int err; @@ -15752,12 +15753,23 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, /* Case where at least one operand is an arena. */ if (dst_reg->type == PTR_TO_ARENA || (src_reg && src_reg->type == PTR_TO_ARENA)) { - struct bpf_insn_aux_data *aux = cur_aux(env); if (dst_reg->type != PTR_TO_ARENA) *dst_reg = *src_reg; if (BPF_CLASS(insn->code) == BPF_ALU64) { + /* + * Only arena pointers set needs_zext, but doing so + * modifies the instruction at fixup time to an ALU32 + * and makes it unsuitable for 64-bit scalar args. We + * prevent zext from being set if the instruction has + * been previously called with non-arena registers. + */ + if (aux->prevent_zext) { + verbose(env, "same insn cannot be used with and without arena pointer\n"); + return -EINVAL; + } + /* * 32-bit operations zero upper bits automatically. * 64-bit operations need to be converted to 32. @@ -15770,6 +15782,16 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, return 0; } + /* Prevent the instruction from being used with arena pointers (see above). */ + if (env->prog->aux->arena && BPF_CLASS(insn->code) == BPF_ALU64) { + if (aux->needs_zext) { + verbose(env, "same insn cannot be used with and without arena pointer\n"); + return -EINVAL; + } + + aux->prevent_zext = true; + } + if (dst_reg->type != SCALAR_VALUE) ptr_reg = dst_reg; From a9e86dd9de4f933b69f5cca6293fd4c8919224ec Mon Sep 17 00:00:00 2001 From: Emil Tsalapatis Date: Tue, 22 Sep 2026 17:20:25 +0000 Subject: [PATCH 2200/2366] selftests/bpf: Test for mixed arena/nonarena code paths Add a selftest to confirm the verifier rejects ALU operations that return arena or non-arena results depending on code path. Signed-off-by: Emil Tsalapatis Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260922172028.6269-9-emil@etsalapatis.com --- .../selftests/bpf/progs/verifier_arena.c | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_arena.c b/tools/testing/selftests/bpf/progs/verifier_arena.c index 815f342eb4b0..3e33766547c0 100644 --- a/tools/testing/selftests/bpf/progs/verifier_arena.c +++ b/tools/testing/selftests/bpf/progs/verifier_arena.c @@ -561,6 +561,55 @@ int arena_ptr_add_arena_ptr(void *ctx) return 0; } +SEC("syscall") +__failure __msg("same insn cannot be used with and without arena pointer") +int mixed_arena_scalar_alu64_scalar_first(void *ctx) +{ + volatile register __u64 reg asm("r3"); + __u32 pick_arena = bpf_get_prandom_u32(); + + reg = 1ULL << 32; + + if (pick_arena) { + asm volatile ( + "r9 = %[arena] ll;" + "%[reg] = 0;" + "%[reg] = addr_space_cast(%[reg], 0x0, 0x1);" + : [reg] "=r"(reg) + : __imm_addr(arena) + : "r9" + ); + } + + reg += 1; + + return 0; +} + +SEC("syscall") +__failure __msg("same insn cannot be used with and without arena pointer") +int mixed_arena_scalar_alu64_arena_first(void *ctx) +{ + volatile register __u64 reg asm("r3"); + __u32 pick_scalar = bpf_get_prandom_u32(); + + asm volatile ( + "r9 = %[arena] ll;" + "%[reg] = 0;" + "%[reg] = addr_space_cast(%[reg], 0x0, 0x1);" + : [reg] "=r"(reg) + : __imm_addr(arena) + : "r9" + ); + + if (pick_scalar) + reg = 1ULL << 32; + + reg += 1; + + return 0; +} + SEC("syscall") __success __retval(0) int scalar_xor_arena_ptr(void *ctx) From cc319238e3f6668f867beb381ce93727c69b7317 Mon Sep 17 00:00:00 2001 From: Tangudu Tilak Tirumalesh Date: Wed, 16 Sep 2026 15:35:45 +0530 Subject: [PATCH 2201/2366] drm/xe: Add wa_14025941587 to xe2, xe3 and xe3p platforms Avoid programming the IDLEDLY timer to less than 5 microseconds. Apply wa_14025941587 to Graphics Versions 20.01 to 35.11 and Media Versions 13.01 to 35.03 v2: Use xe_rtp_match_not_sriov_vf, move to local variable Remove warn and other knits - Matt R v3: Add verbose comment - Tejas v4: Restore IDLE_DLY register on engine reset. Add it to GUC save-restore list. -Vivek v5: Extend WA to Media Versions 13.01 to 35.03 - Vinay v6: Avoid clearing inhibit switch - Bala Refactor code accordingly by adding idle_reg_val. v7: Rebased with the divide-by-zero/overflow guards living in a separate hardening patch. v8: Preserve the Wa_16023105232 floor (DIV_ROUND_DOWN_ULL) and the maxcnt == 0 guard from the hardening patch. Round up (DIV_ROUND_UP_ULL) the Wa_14025941587 minimum conversion instead, so the tick-quantized delay cannot round back below 5 us. v9: Evaluate the Wa_16023105232 xe_gt_WARN_ON() against the value read from hardware instead of the Wa_14025941587-bumped value, so it no longer fires on the driver's own floor. Re-check the rounded-up tick value against maxcnt and floor it if tick quantization pushed it back to/above maxcnt, logging via xe_gt_dbg since this is the driver's own value, not a hardware anomaly. Assisted-by: GitHub_Copilot:claude-opus-4.8 Signed-off-by: Tangudu Tilak Tirumalesh Reviewed-by: Vinay Belgaumkar Link: https://patch.msgid.link/20260916100545.779894-3-tilak.tirumalesh.tangudu@intel.com Signed-off-by: Matt Roper (cherry picked from commit 9453c528fc909076468ff10df1c2e334ca5a9b00) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_guc_ads.c | 2 +- drivers/gpu/drm/xe/xe_hw_engine.c | 95 ++++++++++++++++++++++++------ drivers/gpu/drm/xe/xe_wa_oob.rules | 2 + 3 files changed, 80 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c index ff8eee3831aa..58a3f6293ce4 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.c +++ b/drivers/gpu/drm/xe/xe_guc_ads.c @@ -864,7 +864,7 @@ static unsigned int guc_mmio_regset_write(struct xe_guc_ads *ads, } } - if (XE_GT_WA(hwe->gt, 16023105232)) + if (XE_GT_WA(hwe->gt, 16023105232) || XE_GT_WA(hwe->gt, 14025941587)) guc_mmio_regset_write_one(ads, regset_map, RING_IDLEDLY(hwe->mmio_base), count++); diff --git a/drivers/gpu/drm/xe/xe_hw_engine.c b/drivers/gpu/drm/xe/xe_hw_engine.c index d868af8e3301..9680d2a5adec 100644 --- a/drivers/gpu/drm/xe/xe_hw_engine.c +++ b/drivers/gpu/drm/xe/xe_hw_engine.c @@ -585,44 +585,103 @@ static void hw_engine_init_early(struct xe_gt *gt, struct xe_hw_engine *hwe, xe_reg_whitelist_process_engine(hwe); } +static u32 idledly_floor_ticks(u32 idledly_ns, u32 idledly_units_ps) +{ + return DIV_ROUND_DOWN_ULL((u64)idledly_ns * 1000, idledly_units_ps); +} + static void adjust_idledly(struct xe_hw_engine *hwe) { struct xe_gt *gt = hwe->gt; - u32 idledly, maxcnt; + u32 idledly, idledly_hw, idledly_reg_val, maxcnt; u32 idledly_units_ps = 8 * gt->info.timestamp_base; u32 maxcnt_units_ns = 640; - bool inhibit_switch = 0; + bool inhibit_switch = false; bool wa_applied = false; + bool clamped_below_maxcnt = false; + + if ((!IS_SRIOV_VF(gt_to_xe(gt)) && XE_GT_WA(gt, 16023105232)) || + XE_GT_WA(gt, 14025941587)) { + u32 mincnt_idledly_ns = 5000; - if (!IS_SRIOV_VF(gt_to_xe(gt)) && XE_GT_WA(gt, 16023105232)) { /* xe_gt_clock_init() warns and zeroes timestamp_base on unknown crystal clock. */ if (!idledly_units_ps) return; - idledly = xe_mmio_read32(>->mmio, RING_IDLEDLY(hwe->mmio_base)); + idledly_reg_val = xe_mmio_read32(>->mmio, RING_IDLEDLY(hwe->mmio_base)); maxcnt = xe_mmio_read32(>->mmio, RING_PWRCTX_MAXCNT(hwe->mmio_base)); - inhibit_switch = idledly & INHIBIT_SWITCH_UNTIL_PREEMPTED; - idledly = REG_FIELD_GET(IDLE_DELAY, idledly); + inhibit_switch = idledly_reg_val & INHIBIT_SWITCH_UNTIL_PREEMPTED; + idledly = REG_FIELD_GET(IDLE_DELAY, idledly_reg_val); idledly = DIV_ROUND_CLOSEST_ULL((u64)idledly * idledly_units_ps, 1000); + idledly_hw = idledly; maxcnt = REG_FIELD_GET(IDLE_WAIT_TIME, maxcnt); maxcnt *= maxcnt_units_ns; - /* Clear the inhibit switch without disturbing a valid delay. */ - if (inhibit_switch) - wa_applied = true; - - if (xe_gt_WARN_ON(gt, idledly >= maxcnt)) { - /* Floor below maxcnt; write 0 to still clear the inhibit bit. */ - idledly = maxcnt ? - DIV_ROUND_DOWN_ULL((u64)(maxcnt - 1) * 1000, - idledly_units_ps) : 0; + /* + * Wa_14025941587 is applied before Wa_16023105232, which takes + * priority if the two ever conflict (not expected in practice). + */ + if (XE_GT_WA(gt, 14025941587) && + idledly < mincnt_idledly_ns) { + idledly = mincnt_idledly_ns; wa_applied = true; } - if (wa_applied) - xe_mmio_write32(>->mmio, RING_IDLEDLY(hwe->mmio_base), - REG_FIELD_PREP(IDLE_DELAY, idledly)); + if (XE_GT_WA(gt, 16023105232)) { + /* Clear the inhibit switch without disturbing a valid delay. */ + if (inhibit_switch) { + idledly_reg_val &= ~INHIBIT_SWITCH_UNTIL_PREEMPTED; + wa_applied = true; + } + + /* Warn only on the value read from hardware. */ + xe_gt_WARN_ON(gt, idledly_hw >= maxcnt); + + if (idledly >= maxcnt) { + /* maxcnt may be 0 if IDLE_WAIT_TIME is unprogrammed. */ + idledly = maxcnt ? maxcnt - 1 : 0; + clamped_below_maxcnt = true; + wa_applied = true; + } + } + + if (wa_applied) { + u32 idledly_ticks; + + /* + * Wa_16023105232 requires idledly < maxcnt, so floor + * that clamp; otherwise round up to guarantee the + * Wa_14025941587 minimum survives tick quantization. + */ + if (clamped_below_maxcnt) + idledly_ticks = idledly_floor_ticks(idledly, idledly_units_ps); + else + idledly_ticks = DIV_ROUND_UP_ULL((u64)idledly * 1000, + idledly_units_ps); + + /* + * Tick quantization can still push the rounded-up value + * to/above maxcnt; re-floor here so Wa_16023105232 keeps + * priority even in that case. + */ + if (!clamped_below_maxcnt && XE_GT_WA(gt, 16023105232) && + (u64)idledly_ticks * idledly_units_ps >= (u64)maxcnt * 1000) { + xe_gt_dbg(gt, "idledly %s: %u ticks would exceed maxcnt=%u, so flooring\n", + hwe->name, idledly_ticks, maxcnt); + idledly = maxcnt ? maxcnt - 1 : 0; + idledly_ticks = idledly_floor_ticks(idledly, idledly_units_ps); + } + + idledly_reg_val &= ~IDLE_DELAY; + idledly_reg_val |= REG_FIELD_PREP(IDLE_DELAY, idledly_ticks); + xe_gt_dbg(gt, "idledly %s: set %u max=%u inh=%u ts=%u\n", + hwe->name, idledly, maxcnt, + !!inhibit_switch, gt->info.timestamp_base); + xe_mmio_write32(>->mmio, + RING_IDLEDLY(hwe->mmio_base), + idledly_reg_val); + } } } diff --git a/drivers/gpu/drm/xe/xe_wa_oob.rules b/drivers/gpu/drm/xe/xe_wa_oob.rules index dd69ad07f7a9..3001155f8d09 100644 --- a/drivers/gpu/drm/xe/xe_wa_oob.rules +++ b/drivers/gpu/drm/xe/xe_wa_oob.rules @@ -72,3 +72,5 @@ 16029897822 MEDIA_VERSION(3500) GRAPHICS_VERSION(3510) 14027054324 GRAPHICS_VERSION(3511) +14025941587 GRAPHICS_VERSION_RANGE(2001, 3511), FUNC(xe_rtp_match_not_sriov_vf) + MEDIA_VERSION_RANGE(1301, 3503), FUNC(xe_rtp_match_not_sriov_vf) From 814a81c842bd88f6bd8a4ce550d560df071a5d03 Mon Sep 17 00:00:00 2001 From: Zhao Gongyi Date: Thu, 17 Sep 2026 20:10:16 +0800 Subject: [PATCH 2202/2366] bpf, sockmap: Reject max_entries > INT_MAX in sock_map_alloc sock_map_alloc() only rejects max_entries == 0 and otherwise allows any u32 value. sock_map_free() then walks the sks[] array with a signed int iterator: int i; for (i = 0; i < stab->map.max_entries; i++) struct sock **psk = &stab->sks[i]; When a SOCKMAP is created with max_entries = 0xffffffff (UINT_MAX), the allocation of 32 GiB can succeed on large-memory hosts. During free the counter reaches 0x80000000, wraps to INT_MIN, is sign-extended by movslq and turned into a ~16 GiB negative offset from stab->sks, pointing far below the allocation. The faulting access is an xchg() write in sock_map_free(). Without KASAN, the same out-of-bounds write can fault on an unmapped vmalloc page or corrupt an unrelated allocation if that vmalloc address is populated. On a KASAN kernel with CONFIG_KASAN_VMALLOC=y, the shadow check for that address hits an unmapped shadow page and oopses first: BUG: unable to handle page fault for address: fffff521b59c5a00 RIP: 0010:kasan_check_range+0x107/0x190 Call Trace: sock_map_free+0x93/0x190 map_create+0x68d/0xb30 __sys_bpf+0x21e/0x2e70 Vmcore confirmed stab->map.max_entries == 0xffffffff, stab->sks == 0xffffc911ace2d000, and the faulting address sks + (s64)INT_MIN * 8 exactly at 0xffffc90dace2d000. The same buggy path is reached on the normal close()/bpf_map_free_deferred() path whenever such a map is destroyed. sock_map_alloc() used to bound its allocation size through bpf_map_charge_init(), but the bound was dropped when rlimit-based memory accounting was removed. Reject max_entries > INT_MAX at creation time so the signed iterator in sock_map_free() never sees a value that would overflow. Triggered by syzkaller and reproduced on both a 6.6-based KASAN kernel and the upstream v7.3-rc2 kernel. Fixes: 0d2c4f964050 ("bpf: Eliminate rlimit-based memory accounting for sockmap and sockhash maps") Signed-off-by: Zhao Gongyi Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260917121016.48171-1-zhaogongyi@bytedance.com --- net/core/sock_map.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/core/sock_map.c b/net/core/sock_map.c index ca49bc7f8687..38df84284328 100644 --- a/net/core/sock_map.c +++ b/net/core/sock_map.c @@ -41,6 +41,7 @@ static struct bpf_map *sock_map_alloc(union bpf_attr *attr) struct bpf_stab *stab; if (attr->max_entries == 0 || + attr->max_entries > INT_MAX || attr->key_size != 4 || (attr->value_size != sizeof(u32) && attr->value_size != sizeof(u64)) || From c6b51091cafff9ce6c03c1416aa13864d17ab97c Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Tue, 22 Sep 2026 22:26:39 +0900 Subject: [PATCH 2203/2366] firewire: cdev: fix back-transition for iso_resource_auto client resource The todo member of iso_resource_auto structure represents the state of the client resource and normally transitions in the following order: ISO_RES_AUTO_ALLOC -> ISO_RES_AUTO_REALLOC -> ISO_RES_AUTO_DEALLOC However, concurrent access from the work item and the file descriptor release function can cause the state to transition backwards from ISO_RES_AUTO_DEALLOC to ISO_RES_AUTO_REALLOC. Prevent the back-transition by checking the current state before updating it in the work item. Fixes: fcabbf40fae5 ("firewire: core: move allocation/reallocation paths into specific branch after isoc resource management in cdev") Link: https://lore.kernel.org/r/20260922132639.191593-1-o-takashi@sakamocchi.jp Signed-off-by: Takashi Sakamoto --- drivers/firewire/core-cdev.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c index e49d8a58be09..664952a67a11 100644 --- a/drivers/firewire/core-cdev.c +++ b/drivers/firewire/core-cdev.c @@ -1397,8 +1397,10 @@ static void iso_resource_auto_work(struct work_struct *work) } else { // Transit from allocation to reallocation, except if the client requested // deallocation in the meantime. - scoped_guard(spinlock_irq, &client->lock) - r->todo = ISO_RES_AUTO_REALLOC; + scoped_guard(spinlock_irq, &client->lock) { + if (r->todo == ISO_RES_AUTO_ALLOC) + r->todo = ISO_RES_AUTO_REALLOC; + } if (channel >= 0) r->params.channels_mask = BIT_ULL(channel); From 35e6f970f553954d92ba20afa885139a8e7dd0d7 Mon Sep 17 00:00:00 2001 From: Bernardo Soares Date: Fri, 18 Sep 2026 10:59:30 +0100 Subject: [PATCH 2204/2366] net/mlx5: Bridge, don't fail switchdev events of sibling eswitch ports mlx5 registers the bridge offload switchdev notifiers once per eswitch instance, but the notifier chains are global, so every instance sees every event and must filter out the ones that aren't its own. The existing filter, mlx5_esw_bridge_dev_same_hw(), only checks that the event netdevice sits on the same HCA - intentional for merged eswitch, where one bridge can span representors of several eswitches on one HCA - but same-HCA doesn't mean the instance actually has that port: peer ports are only created reactively from NETDEV_CHANGEUPPER, so an instance brought up after a sibling PF's port was already enslaved has none. The port object and attribute handlers claim the event anyway once same-HW passes, then fail the port lookup and return -EINVAL, which gets reported to user space even though the owning instance already handled it (e.g. "bridge vlan add ... RTNETLINK answers: Invalid argument"). Fix by filtering on the tracked port instead. The same gap exists in the generic recursive lower-device walk used by attribute changes on a bridge with more than one representor enslaved directly: mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get() is entered with the bridge master netdevice, falls through to its generic netdev_for_each_lower_dev() loop, and returns as soon as the recursion into any one lower device yields a non-NULL rep - the underlying base case, mlx5_esw_bridge_rep_vport_num_vhca_id_get(), only checks mlx5_esw_bridge_dev_same_hw(), not ownership by the calling instance's br_offloads. mlx5_esw_bridge_lag_rep_get(), used for the LAG-master case, already filters on mlx5_esw_bridge_dev_same_esw() per candidate and so cannot select a sibling's rep; it is not the source of this bug. On a merged-eswitch HCA with a bridge spanning representors of more than one eswitch instance directly, the walk can return a sibling's rep instead of continuing to the one the calling instance actually owns, so the attribute change fails the same way as above. Fix by checking mlx5_esw_bridge_port_exists() at the point each rep is picked, same as the previous fix did for the notifier filter. Fixes: c358ea1741bc ("net/mlx5: Bridge, allow merged eswitch connectivity") Signed-off-by: Bernardo Soares Cc: Vlad Buslov Cc: Saeed Mahameed Reviewed-by: Mark Bloch Link: https://patch.msgid.link/20260918095931.29792-2-bsoares.it@gmail.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/en/rep/bridge.c | 45 +++++++++++++++---- .../ethernet/mellanox/mlx5/core/esw/bridge.c | 6 +++ .../ethernet/mellanox/mlx5/core/esw/bridge.h | 2 + 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/bridge.c b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/bridge.c index baac38bece14..4b7b0a0fc2b2 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/bridge.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/bridge.c @@ -85,9 +85,16 @@ mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get(struct net_device *dev, struct m struct net_device *lower_dev; struct list_head *iter; - if (netif_is_lag_master(dev) || mlx5e_eswitch_rep(dev)) - return mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, esw, vport_num, - esw_owner_vhca_id); + if (netif_is_lag_master(dev) || mlx5e_eswitch_rep(dev)) { + struct net_device *rep; + + rep = mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, esw, vport_num, + esw_owner_vhca_id); + if (rep && !mlx5_esw_bridge_port_exists(*vport_num, *esw_owner_vhca_id, + esw->br_offloads)) + return NULL; + return rep; + } netdev_for_each_lower_dev(dev, lower_dev, iter) { struct net_device *rep; @@ -104,6 +111,28 @@ mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get(struct net_device *dev, struct m return NULL; } +static bool mlx5_esw_bridge_rep_port_lookup(struct net_device *dev, + struct mlx5_esw_bridge_offloads *br_offloads, + u16 *vport_num, u16 *esw_owner_vhca_id) +{ + if (!mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, br_offloads->esw, vport_num, + esw_owner_vhca_id)) + return false; + + return mlx5_esw_bridge_port_exists(*vport_num, *esw_owner_vhca_id, br_offloads); +} + +static bool mlx5_esw_bridge_lower_rep_port_lookup(struct net_device *dev, + struct mlx5_esw_bridge_offloads *br_offloads, + u16 *vport_num, u16 *esw_owner_vhca_id) +{ + if (!mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get(dev, br_offloads->esw, vport_num, + esw_owner_vhca_id)) + return false; + + return mlx5_esw_bridge_port_exists(*vport_num, *esw_owner_vhca_id, br_offloads); +} + static bool mlx5_esw_bridge_is_local(struct net_device *dev, struct net_device *rep, struct mlx5_eswitch *esw) { @@ -218,8 +247,7 @@ mlx5_esw_bridge_port_obj_add(struct net_device *dev, u16 vport_num, esw_owner_vhca_id; int err; - if (!mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, br_offloads->esw, &vport_num, - &esw_owner_vhca_id)) + if (!mlx5_esw_bridge_rep_port_lookup(dev, br_offloads, &vport_num, &esw_owner_vhca_id)) return 0; port_obj_info->handled = true; @@ -251,8 +279,7 @@ mlx5_esw_bridge_port_obj_del(struct net_device *dev, const struct switchdev_obj_port_mdb *mdb; u16 vport_num, esw_owner_vhca_id; - if (!mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, br_offloads->esw, &vport_num, - &esw_owner_vhca_id)) + if (!mlx5_esw_bridge_rep_port_lookup(dev, br_offloads, &vport_num, &esw_owner_vhca_id)) return 0; port_obj_info->handled = true; @@ -283,8 +310,8 @@ mlx5_esw_bridge_port_obj_attr_set(struct net_device *dev, u16 vport_num, esw_owner_vhca_id; int err = 0; - if (!mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get(dev, br_offloads->esw, &vport_num, - &esw_owner_vhca_id)) + if (!mlx5_esw_bridge_lower_rep_port_lookup(dev, br_offloads, &vport_num, + &esw_owner_vhca_id)) return 0; port_attr_info->handled = true; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c index 87b5fd349594..ac90ccda1272 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c @@ -1686,6 +1686,12 @@ int mlx5_esw_bridge_vport_peer_unlink(struct net_device *br_netdev, u16 vport_nu extack); } +bool mlx5_esw_bridge_port_exists(u16 vport_num, u16 esw_owner_vhca_id, + struct mlx5_esw_bridge_offloads *br_offloads) +{ + return mlx5_esw_bridge_port_lookup(vport_num, esw_owner_vhca_id, br_offloads); +} + int mlx5_esw_bridge_port_vlan_add(u16 vport_num, u16 esw_owner_vhca_id, u16 vid, u16 flags, struct mlx5_esw_bridge_offloads *br_offloads, struct netlink_ext_ack *extack) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.h b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.h index d6f539161993..a4e59cc21089 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.h @@ -80,6 +80,8 @@ int mlx5_esw_bridge_vlan_proto_set(u16 vport_num, u16 esw_owner_vhca_id, u16 pro struct mlx5_esw_bridge_offloads *br_offloads); int mlx5_esw_bridge_mcast_set(u16 vport_num, u16 esw_owner_vhca_id, bool enable, struct mlx5_esw_bridge_offloads *br_offloads); +bool mlx5_esw_bridge_port_exists(u16 vport_num, u16 esw_owner_vhca_id, + struct mlx5_esw_bridge_offloads *br_offloads); int mlx5_esw_bridge_port_vlan_add(u16 vport_num, u16 esw_owner_vhca_id, u16 vid, u16 flags, struct mlx5_esw_bridge_offloads *br_offloads, struct netlink_ext_ack *extack); From 2e51097c982b7b22382fda3202ba29f0ea33e8c0 Mon Sep 17 00:00:00 2001 From: Bernardo Soares Date: Fri, 18 Sep 2026 10:59:31 +0100 Subject: [PATCH 2205/2366] net/mlx5: Bridge, don't fail unlink of untracked/unsupported peer ports mlx5_esw_bridge_vport_unlink() returns -EINVAL when the port isn't tracked by this instance's br_offloads. This is reachable on a sibling instance that registered its notifier after the port was already enslaved: it never saw the NETDEV_CHANGEUPPER link event, so peer_link() never created a peer port for it, but it does see the later unlink event and fails. Return 0 instead, and give mlx5_esw_bridge_vport_peer_unlink() the same merged_eswitch capability guard peer_link() already has, since without it peer_link() likewise never creates a port to unlink. This also matters beyond the -EINVAL itself: mlx5_esw_bridge_switchdev_port_event() runs on the per-netns netdev_chain, and notifier_from_errno(-EINVAL) sets NOTIFY_STOP_MASK, which call_netdevice_notifiers_info() checks to stop calling further listeners on that chain - so the old -EINVAL silently dropped the event for any listener registered later on the same chain, even though none of it was visible to user space since __netdev_upper_dev_unlink() discards the return value. Fixes: c358ea1741bc ("net/mlx5: Bridge, allow merged eswitch connectivity") Signed-off-by: Bernardo Soares Reviewed-by: Mark Bloch Link: https://patch.msgid.link/20260918095931.29792-3-bsoares.it@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c index ac90ccda1272..b4cf3c5ac0dd 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c @@ -1649,10 +1649,8 @@ int mlx5_esw_bridge_vport_unlink(struct net_device *br_netdev, u16 vport_num, int err; port = mlx5_esw_bridge_port_lookup(vport_num, esw_owner_vhca_id, br_offloads); - if (!port) { - NL_SET_ERR_MSG_MOD(extack, "Port is not attached to any bridge"); - return -EINVAL; - } + if (!port) + return 0; if (port->bridge->ifindex != br_netdev->ifindex) { NL_SET_ERR_MSG_MOD(extack, "Port is attached to another bridge"); return -EINVAL; @@ -1682,6 +1680,9 @@ int mlx5_esw_bridge_vport_peer_unlink(struct net_device *br_netdev, u16 vport_nu struct mlx5_esw_bridge_offloads *br_offloads, struct netlink_ext_ack *extack) { + if (!MLX5_CAP_ESW(br_offloads->esw->dev, merged_eswitch)) + return 0; + return mlx5_esw_bridge_vport_unlink(br_netdev, vport_num, esw_owner_vhca_id, br_offloads, extack); } From 0bf6bb567f0edaa771e7dd208ef98da50e6a4485 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 11:31:31 +0000 Subject: [PATCH 2206/2366] net/mlx5: Fix rev_entry reference leak in mlx5_tc_ct_shared_counter_get() When the reverse entry is found but its counter is already being released, refcount_inc_not_zero() fails and the reference taken by mlx5_tc_ct_entry_get() is never dropped before falling through to create_counter. Drop it so the reverse entry is not kept alive forever by a shared counter lookup that did not use it. Fixes: 1edae2335adf ("net/mlx5e: CT: Use the same counter for both directions") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260917113131.2149024-1-vulab@iscas.ac.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c index 6c87a1c7db09..c1841ce74d9a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c @@ -1082,6 +1082,9 @@ mlx5_tc_ct_shared_counter_get(struct mlx5_tc_ct_priv *ct_priv, spin_unlock_bh(&ct_priv->ht_lock); + if (rev_entry) + mlx5_tc_ct_entry_put(rev_entry); + create_counter: shared_counter = mlx5_tc_ct_counter_create(ct_priv); From a1259e92e1e892f009bbebf23e1207b02e2d5708 Mon Sep 17 00:00:00 2001 From: ZhangGuoDong Date: Mon, 21 Sep 2026 09:41:28 +0800 Subject: [PATCH 2207/2366] smb: client: update POSIX extension specification references The POSIX extension specifications have now been published: https://smb3posix.org/ Suggested-by: Paulo Alcantara Reviewed-by: ChenXiaoSong Signed-off-by: ZhangGuoDong Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/smb2pdu.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/smb2pdu.h b/fs/smb/client/smb2pdu.h index ab6c667bebc0..b37a1e3941a1 100644 --- a/fs/smb/client/smb2pdu.h +++ b/fs/smb/client/smb2pdu.h @@ -224,8 +224,7 @@ struct smb2_file_id_extd_directory_info { extern char smb2_padding[7]; /* - * See POSIX-SMB2 2.2.14.2.16 - * Link: https://gitlab.com/samba-team/smb3-posix-spec/-/blob/master/smb3_posix_extensions.md + * See POSIX-SMB2 2.1.3.2.1 */ struct create_posix_rsp { u32 nlink; @@ -238,6 +237,7 @@ struct create_posix_rsp { #define SMB2_QUERY_DIRECTORY_IOV_SIZE 2 /* + * See POSIX-FSCC 2.2.1 * SMB2-only POSIX info level for query dir * * See posix_info_sid_size(), posix_info_extra_size() and @@ -256,13 +256,17 @@ struct smb2_posix_info { __le64 Inode; __le32 DeviceId; __le32 Zero; - /* beginning of POSIX Create Context Response */ + /* + * Beginning of POSIX Create Context Response + * See POSIX-SMB2 2.1.3.2.1 + */ __le32 HardLinks; __le32 ReparseTag; __le32 Mode; /* * var sized owner SID * var sized group SID + * End of POSIX Create Context Response * le32 filenamelength * u8 filename[] */ From 031fe051abb3c1f36c2ba6346931fcdb3cddaeae Mon Sep 17 00:00:00 2001 From: Fredric Cover Date: Tue, 22 Sep 2026 10:37:59 -0700 Subject: [PATCH 2208/2366] smb: client: use GFP_KERNEL in get_targets() Currently, get_targets() uses GFP_ATOMIC to allocate the cache target iterator and to duplicate t->name. The callers of get_targets() are in a sleepable context; therefore, switch to GFP_KERNEL to reduce risk of failure and reduce pressure on emergency pools in low-memory scenarios. Signed-off-by: Fredric Cover Signed-off-by: Paulo Alcantara --- fs/smb/client/dfs_cache.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c index 29dfd7595941..26956e33037e 100644 --- a/fs/smb/client/dfs_cache.c +++ b/fs/smb/client/dfs_cache.c @@ -798,13 +798,13 @@ static int get_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl) INIT_LIST_HEAD(head); list_for_each_entry(t, &ce->tlist, list) { - it = kzalloc_obj(*it, GFP_ATOMIC); + it = kzalloc_obj(*it, GFP_KERNEL); if (!it) { rc = -ENOMEM; goto err_free_it; } - it->it_name = kstrdup(t->name, GFP_ATOMIC); + it->it_name = kstrdup(t->name, GFP_KERNEL); if (!it->it_name) { kfree(it); rc = -ENOMEM; From 4498467a8af06cfa3d71cb04bd7c4170dec8f449 Mon Sep 17 00:00:00 2001 From: Aohan Mei Date: Mon, 21 Sep 2026 17:37:04 +0800 Subject: [PATCH 2209/2366] sctp: discard the rest of the packet on a stale-cookie error When an association is in COOKIE-ECHOED state and the peer sends a bundled [ERROR(Stale Cookie)][DATA] packet from one of its non-primary addresses, processing the ERROR chunk takes the non-fatal stale-cookie retry path sctp_sf_do_5_2_6_stale(), which queues SCTP_CMD_DEL_NON_PRIMARY while keeping the association alive. sctp_cmd_del_non_primary() removes every non-primary transport - including the very transport this packet arrived on, which is still referenced by the receive lookup and shared by all chunks of the packet via chunk->transport. sctp_assoc_rm_peer() does redirect asoc->peer.last_data_from away from the removed transport, but right afterwards the bundled DATA chunk makes sctp_assoc_bh_rcv() re-register asoc->peer.last_data_from = chunk->transport unconditionally, undoing the redirection with the just-removed transport. Once the packet is done, the receive reference is dropped and the transport is RCU-freed, while the surviving association keeps the dangling last_data_from. A later FWD-TSN (or the delayed SACK timer) makes sctp_gen_sack() dereference it (->param_flags and friends), and sctp_make_sack()/sctp_outq_select_transport() may write to the freed object and link it into the live transport list. This is a use-after-free triggerable by any malicious SCTP peer (or a local unprivileged user acting as one) with no capabilities required: BUG: KASAN: slab-use-after-free in sctp_do_sm+0x498a/0x5660 Read of size 4 at addr ffff88800e1e356c by task poc/115 Call Trace: sctp_do_sm <- sctp_assoc_bh_rcv <- sctp_inq_push <- sctp_rcv <- ip_protocol_deliver_rcu <- ip_rcv Allocated: sctp_transport_new <- sctp_assoc_add_peer <- sctp_process_init (INIT-ACK processing) Freed: kfree <- sctp_transport_destroy_rcu <- rcu_core (call_rcu queued by sctp_transport_put at end of sctp_rcv) The buggy address is located 364 bytes inside of freed 1024-byte region [ffff88800e1e3400, ffff88800e1e3800), cache kmalloc-1k Note that commit 03a9d10ecf71 ("sctp: drop a chunk if its transport was removed") only covers the window between the receive lookup and the chunk processing (e.g. an ASCONF DEL-IP racing the socket backlog); here the transport is removed *while* the packet is being processed, by an earlier chunk of the same packet, so the drop in sctp_inq_push() does not reach this path. Verified with the bundled [ERROR(Stale Cookie)][DATA] + FWD-TSN reproducer: the KASAN report above still fires with that commit applied, and is gone with this patch on top. Fix it by discarding the rest of the packet on this path, as suggested by Xin. After the stale-cookie ERROR has sent the association back to COOKIE-WAIT and removed the non-primary transports, the remaining chunks of the packet can only run against the restarted handshake while referencing the removed arrival transport through chunk->transport: besides the last_data_from registration above, sctp_cmd_setup_t2() and the sctp_make_*() reply builders would also copy that pointer into association-lifetime state that sctp_assoc_rm_peer() has already sanitized. Let the peer retransmit them, in line with what sctp_inq_push() does for chunks whose transport was removed before processing. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Suggested-by: Xin Long Reported-by: TencentOS Corvus AI Cc: stable@vger.kernel.org Signed-off-by: Aohan Mei Acked-by: Xin Long Link: https://patch.msgid.link/20260921093707.1432184-1-ljp1205831794@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/sm_statefuns.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index c701cff6ea67..8dc65498763c 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -2654,6 +2654,8 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale( sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply)); + sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET, SCTP_NULL()); + return SCTP_DISPOSITION_CONSUME; nomem: From 07e1a9408b6c2f9d0cfb757b67dabb52da7a32b2 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Fri, 18 Sep 2026 20:47:28 -0400 Subject: [PATCH 2210/2366] virtio_net: copy zerocopy frags in start_xmit without NAPI Virtio-net without NAPI frees completed skbs lazily on the next start_xmit. Senders waiting for in-flight zerocopy buffers can deadlock if they cannot transmit more packets, as then no completed packets will be freed. When !use_napi, virtio-net already calls skb_orphan to avoid waiting up for transmitted skbs to be freed. For zerocopy packets that require deep copying on orphan (i.e. those that do not set SKBFL_DONT_ORPHAN, such as PACKET_TX_RING), call skb_orphan_frags before orphaning to release the buffers. This fixes the tpacket_snd slot reuse bug on skb_orphan for virtio-net, and prevents PACKET_TX_RING from running out of slots. This fix also touches vhost_net zerocopy packets, which also do not set SKBFL_DONT_ORPHAN. This is fine: vhost_net packets only encounter virtio-net in nested virtualization, and only if napi_tx is explicitly disabled (it has been default-enabled since Linux 4.12). In that rare case, copying the frags is desirable anyway to prevent holding guest descriptors pinned across unbounded intervals. This is a prerequisite for the next patch, which converts PACKET_TX_RING to standard zerocopy completion. Without this patch first, a bounded ring sender can stall indefinitely behind a virtio-net virtqueue that cannot reclaim. Fixes: 5cd8d46ea156 ("packet: copy user buffers before orphan or clone") Cc: stable@vger.kernel.org Cc: mst@redhat.com Cc: jasowangio@gmail.com Signed-off-by: Willem de Bruijn Link: https://patch.msgid.link/20260919004748.1463985-2-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/virtio_net.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index e34c52d059d3..bf82ef9874ab 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -3349,6 +3349,14 @@ static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) else virtqueue_disable_cb(sq->vq); + if (!use_napi && + unlikely(skb_orphan_frags(skb, GFP_ATOMIC))) { + DEV_STATS_INC(dev, tx_dropped); + dev_kfree_skb_any(skb); + kick = !xmit_more || netif_xmit_stopped(txq); + goto kick_vq; + } + /* timestamp packet in software */ skb_tx_timestamp(skb); @@ -3381,6 +3389,7 @@ static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) kick = use_napi ? __netdev_tx_sent_queue(txq, skb->len, xmit_more) : !xmit_more || netif_xmit_stopped(txq); +kick_vq: if (kick) { if (virtqueue_kick_prepare(sq->vq) && virtqueue_notify(sq->vq)) { u64_stats_update_begin(&sq->stats.syncp); From 9518405613863d0bf0700927a21367f5942cf058 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Fri, 18 Sep 2026 20:47:29 -0400 Subject: [PATCH 2211/2366] packet: use ubuf_info completion for TX_RING packets tpacket_snd sends skbs with frags pointing into its ring slots. Slots are released when skb->destructor is called. A call to skb_orphan calls skb->destructor before the skb is freed. This can cause the slot to be reused while still linked into the skb. Switch to standard zerocopy completion (ubuf_info) so the slot is only released once all references to the payload are freed or copied. Restore skb->destructor to standard sock_wfree. The ubuf_info completion callback can be called with a NULL skb, but only from net_zcopy_put and related API, used by zerocopy implementations that hold their own reference on the uarg, such as MSG_ZEROCOPY. This uarg is only ever completed from skb_zcopy_clear, so skb is always set. To prevent userspace from aliasing in-flight state on shared ring slots, allocate tpacket_uarg per packet, rather than per slot. This adds a small allocation to the transmit path. Use standard kmalloc to allow backporting to stable kernels. The uarg holds an sk_wmem_alloc reference, rather than an sk_refcnt reference. packet_free_tx_ring waits on sk_wmem_alloc before freeing the ring pages. Always allocate vec->deferred for tx_ring so page-backed rings also wait on sk_wmem_alloc when skb_copy_ubufs drops page refs before calling tpacket_ubuf_complete. Drop the tx_ring.pg_vec test that tpacket_destruct_skb performed before accessing the slot. The sk_wmem_alloc reference now guarantees that the slot is valid. The test is also not sufficient by itself, as it reads pg_vec without pg_vec_lock, so it can race with packet_set_ring. As a result a slot is released when its payload is copied, which can be before transmission (e.g., in skb_orphan_frags_rx). If copied before skb_tx_timestamp() is called, no slot timestamp is recorded, similar to when skb_orphan() was called early in the datapath before this patch. Revert the now unused previous skb_zcopy_.._nouarg infra. Depends on commit 992cc9f94ca9 ("net/packet: defer vmalloc TX_RING free until skbs finish"). Reported-by: Katherine Leaver Reported-by: Bjoern Doebel Closes: https://lore.kernel.org/netdev/20260909085542.3370986-1-doebel@amazon.de/ Fixes: 5cd8d46ea156 ("packet: copy user buffers before orphan or clone") Cc: stable@vger.kernel.org Signed-off-by: Willem de Bruijn Link: https://patch.msgid.link/20260919004748.1463985-3-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- include/linux/skbuff.h | 19 +------- net/packet/af_packet.c | 102 ++++++++++++++++++++++++++--------------- 2 files changed, 65 insertions(+), 56 deletions(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 421f6fc45451..b14d6be7370b 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -1834,22 +1834,6 @@ static inline void skb_zcopy_set(struct sk_buff *skb, struct ubuf_info *uarg, } } -static inline void skb_zcopy_set_nouarg(struct sk_buff *skb, void *val) -{ - skb_shinfo(skb)->destructor_arg = (void *)((uintptr_t) val | 0x1UL); - skb_shinfo(skb)->flags |= SKBFL_ZEROCOPY_FRAG; -} - -static inline bool skb_zcopy_is_nouarg(struct sk_buff *skb) -{ - return (uintptr_t) skb_shinfo(skb)->destructor_arg & 0x1UL; -} - -static inline void *skb_zcopy_get_nouarg(struct sk_buff *skb) -{ - return (void *)((uintptr_t) skb_shinfo(skb)->destructor_arg & ~0x1UL); -} - static inline void net_zcopy_put(struct ubuf_info *uarg) { if (uarg) @@ -1872,8 +1856,7 @@ static inline void skb_zcopy_clear(struct sk_buff *skb, bool zerocopy_success) struct ubuf_info *uarg = skb_zcopy(skb); if (uarg) { - if (!skb_zcopy_is_nouarg(skb)) - uarg->ops->complete(skb, uarg, zerocopy_success); + uarg->ops->complete(skb, uarg, zerocopy_success); skb_shinfo(skb)->flags &= ~SKBFL_ALL_ZEROCOPY; } diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 50cae32ae269..64b501db660a 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -2530,26 +2530,6 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev, goto drop_n_restore; } -static void tpacket_destruct_skb(struct sk_buff *skb) -{ - struct packet_sock *po = pkt_sk(skb->sk); - - if (likely(po->tx_ring.pg_vec)) { - void *ph; - __u32 ts; - - ph = skb_zcopy_get_nouarg(skb); - - ts = __packet_set_timestamp(po, ph, skb); - __packet_set_status(po, ph, TP_STATUS_AVAILABLE | ts); - - packet_dec_pending(&po->tx_ring); - complete(&po->skb_completion); - } - - sock_wfree(skb); -} - static int __packet_snd_vnet_parse(struct virtio_net_hdr *vnet_hdr, size_t len) { if ((vnet_hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) && @@ -2589,27 +2569,56 @@ static int packet_snd_vnet_parse(struct msghdr *msg, size_t *len, return 0; } +struct tpacket_uarg { + struct ubuf_info ubuf; + struct packet_sock *po; + void *ph; +}; + +static void tpacket_ubuf_complete(struct sk_buff *skb, struct ubuf_info *uarg, + bool success) +{ + struct tpacket_uarg *tu = container_of(uarg, struct tpacket_uarg, ubuf); + struct packet_sock *po = tu->po; + void *ph = tu->ph; + __u32 ts; + + DEBUG_NET_WARN_ON_ONCE(!skb); + + if (!refcount_dec_and_test(&uarg->refcnt)) + return; + + ts = __packet_set_timestamp(po, ph, skb); + __packet_set_status(po, ph, TP_STATUS_AVAILABLE | ts); + + packet_dec_pending(&po->tx_ring); + complete(&po->skb_completion); + + kfree(tu); + sk_free(&po->sk); +} + +static const struct ubuf_info_ops tpacket_ubuf_ops = { + .complete = tpacket_ubuf_complete, +}; + static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, - void *frame, struct net_device *dev, void *data, int tp_len, + struct net_device *dev, void *data, int tp_len, __be16 proto, unsigned char *addr, int hlen, int copylen, int hard_header_len, const struct sockcm_cookie *sockc) { - union tpacket_uhdr ph; int to_write, offset, len, nr_frags, len_max; struct socket *sock = po->sk.sk_socket; struct page *page; int err; - ph.raw = frame; - skb->protocol = proto; skb->dev = dev; skb->priority = sockc->priority; skb->mark = sockc->mark; skb_set_delivery_type_by_clockid(skb, sockc->transmit_time, po->sk.sk_clockid); skb_setup_tx_timestamp(skb, sockc); - skb_zcopy_set_nouarg(skb, ph.raw); skb_reserve(skb, hlen); skb_reset_network_header(skb); @@ -2749,6 +2758,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) struct virtio_net_hdr vnet_hdr; bool has_vnet_hdr = false; struct sockcm_cookie sockc; + struct tpacket_uarg *uarg; __be16 proto; int err, reserve = 0; void *ph; @@ -2876,7 +2886,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) err = len_sum; goto out_status; } - tp_len = tpacket_fill_skb(po, skb, ph, dev, data, tp_len, proto, + tp_len = tpacket_fill_skb(po, skb, dev, data, tp_len, proto, addr, hlen, copylen, hard_header_len, &sockc); if (likely(tp_len >= 0) && @@ -2908,7 +2918,24 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) virtio_net_hdr_set_proto(skb, &vnet_hdr); } - skb->destructor = tpacket_destruct_skb; + uarg = kmalloc(sizeof(*uarg), GFP_KERNEL); + if (unlikely(!uarg)) { + if (likely(len_sum > 0)) + err = len_sum; + else + err = -ENOMEM; + goto out_status; + } + uarg->po = po; + uarg->ph = ph; + uarg->ubuf.ops = &tpacket_ubuf_ops; + uarg->ubuf.flags = SKBFL_ZEROCOPY_FRAG; + refcount_set(&uarg->ubuf.refcnt, 1); + + /* Hold a sk_wmem_alloc reference until completion */ + refcount_inc(&po->sk.sk_wmem_alloc); + skb_zcopy_init(skb, &uarg->ubuf); + __packet_set_status(po, ph, TP_STATUS_SENDING); packet_inc_pending(&po->tx_ring); @@ -4486,21 +4513,20 @@ static struct pgv *alloc_pg_vec(struct tpacket_req *req, int order, bool tx_ring vec->len = block_nr; pg_vec = vec->pg_vec; + if (tx_ring) { + vec->deferred = kzalloc_obj(*vec->deferred, + GFP_KERNEL | __GFP_NOWARN); + if (!vec->deferred) + goto out_free_pgvec; + vec->deferred->vec = vec; + INIT_DELAYED_WORK(&vec->deferred->work, + packet_free_pg_vec_work); + } + for (i = 0; i < block_nr; i++) { pg_vec[i].buffer = alloc_one_pg_vec_page(order); if (unlikely(!pg_vec[i].buffer)) goto out_free_pgvec; - - if (tx_ring && !vec->deferred && - is_vmalloc_addr(pg_vec[i].buffer)) { - vec->deferred = kzalloc_obj(*vec->deferred, - GFP_KERNEL | __GFP_NOWARN); - if (!vec->deferred) - goto out_free_pgvec; - vec->deferred->vec = vec; - INIT_DELAYED_WORK(&vec->deferred->work, - packet_free_pg_vec_work); - } } out: From cae23ae3f7887a1cf8a75da38edcebeef695040f Mon Sep 17 00:00:00 2001 From: Xin Long Date: Mon, 21 Sep 2026 14:03:45 -0400 Subject: [PATCH 2212/2366] sctp: hold asoc or transport before mod_timer() in timer handlers Take the association or transport reference before rearming a timer in the timer handlers. The existing code calls mod_timer() before taking the reference needed by the rearmed timer without holding the sock lock. This creates a race with timer cleanup: if the timer is deleted after mod_timer() returns but before the reference is taken, the cleanup path can drop the timer's reference and destroy the transport or association. The timer handler then takes a reference on the already freed object and eventually drops it, causing a refcount underflow. Hold the object before mod_timer() and drop the reference if mod_timer() reports that the timer was already pending in timer handlers. Apply the same ordering to the proto-unreachable path, which can rearm a transport timer outside the timer handlers without holding the sock lock. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Tangxin Xie Signed-off-by: Xin Long Link: https://patch.msgid.link/c31b5e3ee2b7274e804f5eba2f21e2412e7eef7a.1790013825.git.lucien.xin@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/input.c | 7 ++++--- net/sctp/sm_sideeffect.c | 37 ++++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/net/sctp/input.c b/net/sctp/input.c index 864741fae418..9494cfa51106 100644 --- a/net/sctp/input.c +++ b/net/sctp/input.c @@ -436,9 +436,10 @@ void sctp_icmp_proto_unreachable(struct sock *sk, if (timer_pending(&t->proto_unreach_timer)) return; else { - if (!mod_timer(&t->proto_unreach_timer, - jiffies + (HZ/20))) - sctp_transport_hold(t); + sctp_transport_hold(t); + if (mod_timer(&t->proto_unreach_timer, + jiffies + (HZ / 20))) + sctp_transport_put(t); } } else { struct net *net = sock_net(sk); diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c index 0d99b7e8c082..35f540fb15fc 100644 --- a/net/sctp/sm_sideeffect.c +++ b/net/sctp/sm_sideeffect.c @@ -244,8 +244,9 @@ void sctp_generate_t3_rtx_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->T3_rtx_timer, jiffies + (HZ/20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->T3_rtx_timer, jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } @@ -280,8 +281,9 @@ static void sctp_generate_timeout_event(struct sctp_association *asoc, timeout_type); /* Try again later. */ - if (!mod_timer(&asoc->timers[timeout_type], jiffies + (HZ/20))) - sctp_association_hold(asoc); + sctp_association_hold(asoc); + if (mod_timer(&asoc->timers[timeout_type], jiffies + (HZ / 20))) + sctp_association_put(asoc); goto out_unlock; } @@ -378,8 +380,9 @@ void sctp_generate_heartbeat_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->hb_timer, jiffies + (HZ/20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->hb_timer, jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } @@ -388,8 +391,9 @@ void sctp_generate_heartbeat_event(struct timer_list *t) timeout = sctp_transport_timeout(transport); if (elapsed < timeout) { elapsed = timeout - elapsed; - if (!mod_timer(&transport->hb_timer, jiffies + elapsed)) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->hb_timer, jiffies + elapsed)) + sctp_transport_put(transport); goto out_unlock; } @@ -422,9 +426,10 @@ void sctp_generate_proto_unreach_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->proto_unreach_timer, - jiffies + (HZ/20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->proto_unreach_timer, + jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } @@ -458,8 +463,9 @@ void sctp_generate_reconf_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->reconf_timer, jiffies + (HZ / 20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->reconf_timer, jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } @@ -495,8 +501,9 @@ void sctp_generate_probe_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->probe_timer, jiffies + (HZ / 20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->probe_timer, jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } From 73b3fc67a493e9b9a8e94a38839f6638d12fe7a6 Mon Sep 17 00:00:00 2001 From: Mina Almasry Date: Mon, 21 Sep 2026 19:54:12 +0000 Subject: [PATCH 2213/2366] net: devmem: document that bind-tx is unprivileged by design Unlike bind-rx, which configures shared NIC RX queues to steer incoming traffic into the caller's dmabuf and requires CAP_NET_ADMIN (uns-admin-perm), bind-tx only DMA-maps the caller's dmabuf so the caller can transmit from it on their own sockets without affecting other traffic or device configuration. Add a comment in netdev.yaml and above netdev_nl_bind_tx_doit() to make it explicit that NETDEV_CMD_BIND_TX is unprivileged by design. Signed-off-by: Mina Almasry Acked-by: Stanislav Fomichev Acked-by: Daniel Borkmann Link: https://patch.msgid.link/20260921195545.493253-1-almasrymina@google.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/netdev.yaml | 2 ++ net/core/netdev-genl.c | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml index 3e3f03bd5c29..0562ba1f567a 100644 --- a/Documentation/netlink/specs/netdev.yaml +++ b/Documentation/netlink/specs/netdev.yaml @@ -851,6 +851,8 @@ operations: name: bind-tx doc: Bind dmabuf to netdev for TX attribute-set: dmabuf + # Intentionally unprivileged (no admin-perm / uns-admin-perm); see + # comment above netdev_nl_bind_tx_doit(). do: request: attributes: diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index cb18db681640..33b9f4eb9565 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -1168,6 +1168,12 @@ netdev_find_netmem_tx_dev(struct net_device *dev) return NULL; } +/* Note: NETDEV_CMD_BIND_TX is intentionally unprivileged (no + * GENL_ADMIN_PERM / GENL_UNS_ADMIN_PERM). Unlike bind-rx, which configures + * shared NIC RX queues, bind-tx only DMA-maps the caller's dmabuf so they can + * transmit from it on their own sockets without affecting other traffic or + * device state. + */ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info) { struct net_devmem_dmabuf_binding *binding; From d68acbf93531abdb5b02b21994cd4c15a3c95b42 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:32 +0200 Subject: [PATCH 2214/2366] net: stmmac: selftests: Support running selftests on DSA conduits Most stmmac selftests rely on dev_add_pack() to add custom handlers, that validate the packets sent to ourselves through MAC loopback. However, when the stmmac-driven interface is a DSA CPU conduit, all frames that are received have ETH_P_XDSA as a protocol, even though they don't actually contain any tag as they come from the loopback and not the switch. This will prevent any incoming packet to match our packet handlers. Let's register a ETH_P_ALL packet handler when we detect that we're a DSA conduit, and use a proxy packet handler to filter the h_proto. As this allows external frames to be received through our .func(), the packet handler is added after the dev->addr field is populated in our selftest attributes. Note that we may still receive incoming packets from the switch, but these frames shouldn't interfere with the very specific frames used for selftests, and stmmac selftests in general aren't safe against external traffic interferences. This was validated on a WPQ864 devkit for IPQ8064, that has the SoC connected to a QCA8k switch. The ARP offload's packet handler is left alone, this feature is just not implemented in stmmac and due for removal. Fixes: 091810dbded9 ("net: stmmac: Introduce selftests support") Reviewed-by: Nicolai Buchwitz Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260917215339.2022523-2-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../stmicro/stmmac/stmmac_selftests.c | 89 +++++++++++++++---- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 6372ec7c3f31..614b5995dec5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -237,6 +238,9 @@ struct stmmac_test_priv { struct stmmac_packet_attrs *packet; struct packet_type pt; struct completion comp; + __be16 packet_type; + int (*func)(struct sk_buff *skb, struct net_device *ndev, + struct packet_type *pt, struct net_device *orig_ndev); int double_vlan; int vlan_id; int ok; @@ -316,6 +320,50 @@ static int stmmac_test_loopback_validate(struct sk_buff *skb, return 0; } +static int stmmac_sft_filter(struct sk_buff *skb, struct net_device *ndev, + struct packet_type *pt, + struct net_device *orig_ndev) +{ + struct stmmac_test_priv *tpriv = pt->af_packet_priv; + struct ethhdr *hdr = eth_hdr(skb); + int ret = 0; + + if (hdr->h_proto == tpriv->packet_type) { + struct sk_buff *nskb = skb_clone(skb, GFP_ATOMIC); + + if (nskb) + ret = tpriv->func(nskb, ndev, pt, orig_ndev); + } + + kfree_skb(skb); + return ret; +} + +static void stmmac_sft_add_pack(struct packet_type *pt) +{ + struct stmmac_test_priv *tpriv = pt->af_packet_priv; + + if (netdev_uses_dsa(tpriv->pt.dev)) { + tpriv->packet_type = tpriv->pt.type; + tpriv->func = tpriv->pt.func; + + /* DSA conduit will report ETH_P_XDSA, so our packet handler + * won't match. Let's register a ETH_P_ALL match and filter + * manually in stmmac_sft_filter. + */ + tpriv->pt.type = htons(ETH_P_ALL); + tpriv->pt.func = stmmac_sft_filter; + tpriv->pt.ignore_outgoing = true; + } + + dev_add_pack(pt); +} + +static void stmmac_sft_remove_pack(struct packet_type *pt) +{ + dev_remove_pack(pt); +} + static int __stmmac_test_loopback(struct stmmac_priv *priv, struct stmmac_packet_attrs *attr) { @@ -337,7 +385,7 @@ static int __stmmac_test_loopback(struct stmmac_priv *priv, tpriv->packet = attr; if (!attr->dont_wait) - dev_add_pack(&tpriv->pt); + stmmac_sft_add_pack(&tpriv->pt); skb = stmmac_test_get_udp_skb(priv, attr); if (!skb) { @@ -360,7 +408,7 @@ static int __stmmac_test_loopback(struct stmmac_priv *priv, cleanup: if (!attr->dont_wait) - dev_remove_pack(&tpriv->pt); + stmmac_sft_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -767,7 +815,7 @@ static int stmmac_test_flowctrl(struct stmmac_priv *priv) tpriv->pt.func = stmmac_test_flowctrl_validate; tpriv->pt.dev = priv->dev; tpriv->pt.af_packet_priv = tpriv; - dev_add_pack(&tpriv->pt); + stmmac_sft_add_pack(&tpriv->pt); /* Compute minimum number of packets to make FIFO full */ pkt_count = rx_fifo_size; @@ -823,7 +871,7 @@ static int stmmac_test_flowctrl(struct stmmac_priv *priv) cleanup: dev_mc_del(priv->dev, paddr); dev_set_promiscuity(priv->dev, -1); - dev_remove_pack(&tpriv->pt); + stmmac_sft_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -928,18 +976,20 @@ static int __stmmac_test_vlanfilt(struct stmmac_priv *priv) * HASH values. */ tpriv->vlan_id = 0x123; - dev_add_pack(&tpriv->pt); ret = vlan_vid_add(priv->dev, htons(ETH_P_8021Q), tpriv->vlan_id); if (ret) goto cleanup; + attr.vlan = 1; + attr.dst = priv->dev->dev_addr; + attr.sport = 9; + attr.dport = 9; + + stmmac_sft_add_pack(&tpriv->pt); + for (i = 0; i < 4; i++) { - attr.vlan = 1; attr.vlan_id_out = tpriv->vlan_id + i; - attr.dst = priv->dev->dev_addr; - attr.sport = 9; - attr.dport = 9; skb = stmmac_test_get_udp_skb(priv, &attr); if (!skb) { @@ -966,9 +1016,9 @@ static int __stmmac_test_vlanfilt(struct stmmac_priv *priv) } vlan_del: + stmmac_sft_remove_pack(&tpriv->pt); vlan_vid_del(priv->dev, htons(ETH_P_8021Q), tpriv->vlan_id); cleanup: - dev_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -1022,18 +1072,20 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) * HASH values. */ tpriv->vlan_id = 0x123; - dev_add_pack(&tpriv->pt); ret = vlan_vid_add(priv->dev, htons(ETH_P_8021AD), tpriv->vlan_id); if (ret) goto cleanup; + attr.vlan = 2; + attr.dst = priv->dev->dev_addr; + attr.sport = 9; + attr.dport = 9; + + stmmac_sft_add_pack(&tpriv->pt); + for (i = 0; i < 4; i++) { - attr.vlan = 2; attr.vlan_id_out = tpriv->vlan_id + i; - attr.dst = priv->dev->dev_addr; - attr.sport = 9; - attr.dport = 9; skb = stmmac_test_get_udp_skb(priv, &attr); if (!skb) { @@ -1060,9 +1112,9 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) } vlan_del: + stmmac_sft_remove_pack(&tpriv->pt); vlan_vid_del(priv->dev, htons(ETH_P_8021AD), tpriv->vlan_id); cleanup: - dev_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -1293,7 +1345,6 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) tpriv->pt.af_packet_priv = tpriv; tpriv->packet = &attr; tpriv->vlan_id = 0x123; - dev_add_pack(&tpriv->pt); ret = vlan_vid_add(priv->dev, htons(proto), tpriv->vlan_id); if (ret) @@ -1301,6 +1352,8 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) attr.dst = priv->dev->dev_addr; + stmmac_sft_add_pack(&tpriv->pt); + skb = stmmac_test_get_udp_skb(priv, &attr); if (!skb) { ret = -ENOMEM; @@ -1318,9 +1371,9 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) ret = tpriv->ok ? 0 : -ETIMEDOUT; vlan_del: + stmmac_sft_remove_pack(&tpriv->pt); vlan_vid_del(priv->dev, htons(proto), tpriv->vlan_id); cleanup: - dev_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } From c8c1795aa8106293020a836d01e127e98f442925 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:33 +0200 Subject: [PATCH 2215/2366] net: stmmac: selftests: Validate EEE based on the actual LPI timer value The EEE selftest is a 2-step test : - It validates that we enter in LPI mode with the irq_tx_path_in_lpi_mode_n counter - It then validates that we exit LPI when sending a frame, with the irq_tx_path_exit_lpi_mode_n counter. The current state of the test lacks 2 main things : - We don't know exactly when was the previous frame sent (it's from the previous selftest) - The timeout is hardcoded, while the LPI is entered after a user-configurable delay. On top of that, the timeout loop uses a pre-decrement iterator (--retries) that actually only iterate nine times, so 900ms while the default LPI value is 1 second. Let's therefore make it more deterministic : - Send a frame at the beginning of the test - Wait for more than the lpi timer value, we timeout after about twice the value, - Then send another frame, and verify that we do go out of LPI, also with a timeout. As LPI timer can get pretty high, bail out if LPI timer is over 5 seconds. Note that the test's goal isn't to validate the LPI timer value itself, only that we enter/leave LPI mode. Fixes: 091810dbded9 ("net: stmmac: Introduce selftests support") Reviewed-by: Nicolai Buchwitz Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260917215339.2022523-3-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../stmicro/stmmac/stmmac_selftests.c | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 614b5995dec5..2f9f7746c40a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -30,6 +30,7 @@ struct stmmachdr { sizeof(struct stmmachdr)) #define STMMAC_TEST_PKT_MAGIC 0xdeadcafecafedeadULL #define STMMAC_LB_TIMEOUT msecs_to_jiffies(200) +#define STMMAC_SFT_MAX_LPI (5 * USEC_PER_SEC) struct stmmac_packet_attrs { int vlan; @@ -462,12 +463,16 @@ static int stmmac_test_mmc(struct stmmac_priv *priv) static int stmmac_test_eee(struct stmmac_priv *priv) { struct stmmac_extra_stats *initial, *final; - int retries = 10; + unsigned long timeout, max_duration; int ret; if (!priv->dma_cap.eee || !priv->eee_active) return -EOPNOTSUPP; + /* Bail out if the configured LPI timer is too long */ + if (priv->tx_lpi_timer > STMMAC_SFT_MAX_LPI) + return -EOPNOTSUPP; + initial = kzalloc_obj(*initial); if (!initial) return -ENOMEM; @@ -478,14 +483,21 @@ static int stmmac_test_eee(struct stmmac_priv *priv) goto out_free_initial; } + /* Snapshot stats, we want to count the in_lpi events. We may enter + * LPI just after the packet was sent. + */ memcpy(initial, &priv->xstats, sizeof(*initial)); + /* Send a frame, then wait to enter LPI */ ret = stmmac_test_mac_loopback(priv); if (ret) goto out_free_final; + max_duration = usecs_to_jiffies(2 * priv->tx_lpi_timer); + /* We have no traffic in the line so, sooner or later it will go LPI */ - while (--retries) { + timeout = jiffies + max_duration; + while (!time_after(jiffies, timeout)) { memcpy(final, &priv->xstats, sizeof(*final)); if (final->irq_tx_path_in_lpi_mode_n > @@ -494,20 +506,38 @@ static int stmmac_test_eee(struct stmmac_priv *priv) msleep(100); } - if (!retries) { + memcpy(final, &priv->xstats, sizeof(*final)); + if (final->irq_tx_path_in_lpi_mode_n <= + initial->irq_tx_path_in_lpi_mode_n) { ret = -ETIMEDOUT; goto out_free_final; } - if (final->irq_tx_path_in_lpi_mode_n <= - initial->irq_tx_path_in_lpi_mode_n) { - ret = -EINVAL; + /* Re-snapshot, as we want to measure exit_lpi events. We should be + * in LPI right now. + */ + memcpy(initial, &priv->xstats, sizeof(*initial)); + + /* TX something so we go out of LPI */ + ret = stmmac_test_mac_loopback(priv); + if (ret) goto out_free_final; + + /* Wait for the exit LPI interrupt */ + timeout = jiffies + max_duration; + while (!time_after(jiffies, timeout)) { + memcpy(final, &priv->xstats, sizeof(*final)); + + if (final->irq_tx_path_exit_lpi_mode_n > + initial->irq_tx_path_exit_lpi_mode_n) + break; + msleep(100); } + memcpy(final, &priv->xstats, sizeof(*final)); if (final->irq_tx_path_exit_lpi_mode_n <= initial->irq_tx_path_exit_lpi_mode_n) { - ret = -EINVAL; + ret = -ETIMEDOUT; goto out_free_final; } From ba804b23d76d278ee475b8427fa7c5623ce5e270 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:34 +0200 Subject: [PATCH 2216/2366] net: stmmac: selftests: Check the dev->features for S-TAG offload testing The S-TAG offload insertion incorrectly checks the dvlan (double vlan) DMA cap, which is different than S-TAG support. Use NETIF_F_HW_VLAN_STAG_TX to check if the feature is supported instead. Note that this flag isn't set in stmmac yet, but contrary to ARP offload, this is a feature that has a chance to get there eventually so let's leave the selftest here for now. It'll report -EOPNOTSUPP in the meantime. Fixes: 091810dbded9 ("net: stmmac: Introduce selftests support") Reviewed-by: Nicolai Buchwitz Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260917215339.2022523-4-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 2f9f7746c40a..de02c0da56dc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -1415,7 +1415,7 @@ static int stmmac_test_vlanoff(struct stmmac_priv *priv) static int stmmac_test_svlanoff(struct stmmac_priv *priv) { - if (!priv->dma_cap.dvlan) + if (!(priv->dev->features & NETIF_F_HW_VLAN_STAG_TX)) return -EOPNOTSUPP; return stmmac_test_vlanoff_common(priv, true); } From 960db6f65788c21249ea04a947d5c01e38d19294 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:35 +0200 Subject: [PATCH 2217/2366] net: stmmac: selftests: Capture all packets for vlan checks While we use vlan_vid_add to trigger the tag filtering machinery in the driver, there's no netdev associated to the VLAN. This causes the skb to arrive with empty skb->vlan_tci fields, as the packet is marked OTHERHOST in __netif_receive_skb_core(), and we fail our validation. Let's use the proxy mechanism introduced for DSA, that registers a ETH_P_ALL packet handler that runs earlier, before the vlan netdev lookup, then filters for the correct ethertype before passing an skb clone to our validation function. As we may receive external frames with the right tag from the outside, let's move the address check in the vlan validation function earlier. Fixes: 091810dbded9 ("net: stmmac: Introduce selftests support") Reviewed-by: Nicolai Buchwitz Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260917215339.2022523-5-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../stmicro/stmmac/stmmac_selftests.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index de02c0da56dc..43b8411c5112 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -242,6 +242,7 @@ struct stmmac_test_priv { __be16 packet_type; int (*func)(struct sk_buff *skb, struct net_device *ndev, struct packet_type *pt, struct net_device *orig_ndev); + bool capture_all; int double_vlan; int vlan_id; int ok; @@ -344,13 +345,15 @@ static void stmmac_sft_add_pack(struct packet_type *pt) { struct stmmac_test_priv *tpriv = pt->af_packet_priv; - if (netdev_uses_dsa(tpriv->pt.dev)) { + if (netdev_uses_dsa(tpriv->pt.dev) || tpriv->capture_all) { tpriv->packet_type = tpriv->pt.type; tpriv->func = tpriv->pt.func; /* DSA conduit will report ETH_P_XDSA, so our packet handler * won't match. Let's register a ETH_P_ALL match and filter - * manually in stmmac_sft_filter. + * manually in stmmac_sft_filter. This is also useful for + * VLAN tests, to capture packets otherwise marked as + * OTHERHOST. */ tpriv->pt.type = htons(ETH_P_ALL); tpriv->pt.func = stmmac_sft_filter; @@ -943,6 +946,11 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, goto out; if (skb_headlen(skb) < (STMMAC_TEST_PKT_SIZE - ETH_HLEN)) goto out; + + ehdr = (struct ethhdr *)skb_mac_header(skb); + if (!ether_addr_equal_unaligned(ehdr->h_dest, tpriv->packet->dst)) + goto out; + if (tpriv->vlan_id) { if (skb->vlan_proto != htons(proto)) goto out; @@ -954,10 +962,6 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, } } - ehdr = (struct ethhdr *)skb_mac_header(skb); - if (!ether_addr_equal_unaligned(ehdr->h_dest, tpriv->packet->dst)) - goto out; - ihdr = ip_hdr(skb); if (tpriv->double_vlan) ihdr = (struct iphdr *)(skb_network_header(skb) + 4); @@ -999,6 +1003,7 @@ static int __stmmac_test_vlanfilt(struct stmmac_priv *priv) tpriv->pt.dev = priv->dev; tpriv->pt.af_packet_priv = tpriv; tpriv->packet = &attr; + tpriv->capture_all = true; /* * As we use HASH filtering, false positives may appear. This is a @@ -1095,6 +1100,7 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) tpriv->pt.dev = priv->dev; tpriv->pt.af_packet_priv = tpriv; tpriv->packet = &attr; + tpriv->capture_all = true; /* * As we use HASH filtering, false positives may appear. This is a @@ -1375,6 +1381,7 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) tpriv->pt.af_packet_priv = tpriv; tpriv->packet = &attr; tpriv->vlan_id = 0x123; + tpriv->capture_all = true; ret = vlan_vid_add(priv->dev, htons(proto), tpriv->vlan_id); if (ret) From b42e7012773a0e81e97a2dda6ef907f5147a6658 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:36 +0200 Subject: [PATCH 2218/2366] net: stmmac: dwmac4: Use the correct bufzise when the len is exactly 8K DMA bufsize selection isn't made on the MTU but the actual frame length, so including the L2 header. On DWMAC4, if the len is exactly BUF_SIZE_8KiB, the next larger size is incorrectly selected. Lets fix the comparison and while at it, rename the parameter from len to mtu. Fixes: c3efed5ad1b0 ("net: stmmac: Enable dwmac4 jumbo frame more than 8KiB"). Signed-off-by: Maxime Chevallier Reviewed-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260917215339.2022523-6-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 4 ++-- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/ring_mode.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index 2994df41ec2c..c6a8f8d73501 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -474,11 +474,11 @@ static void dwmac4_set_sarc(struct dma_desc *p, u32 sarc_type) sarc_type)); } -static int set_16kib_bfsize(int mtu) +static int set_16kib_bfsize(int len) { int ret = 0; - if (unlikely(mtu >= BUF_SIZE_8KiB)) + if (unlikely(len > BUF_SIZE_8KiB)) ret = BUF_SIZE_16KiB; return ret; } diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 9314bcb85c22..857f7562c6c6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -540,7 +540,7 @@ struct stmmac_mode_ops { bool (*is_jumbo_frm)(unsigned int len, bool enh_desc); int (*jumbo_frm)(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, int csum); - int (*set_16kib_bfsize)(int mtu); + int (*set_16kib_bfsize)(int len); void (*init_desc3)(struct dma_desc *p); void (*refill_desc3)(struct stmmac_rx_queue *rx_q, struct dma_desc *p); void (*clean_desc3)(struct stmmac_tx_queue *tx_q, struct dma_desc *p); diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c index f7949419eb9f..d2f0c321661d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c @@ -124,10 +124,10 @@ static void clean_desc3(struct stmmac_tx_queue *tx_q, struct dma_desc *p) p->des3 = 0; } -static int set_16kib_bfsize(int mtu) +static int set_16kib_bfsize(int len) { int ret = 0; - if (unlikely(mtu > BUF_SIZE_8KiB)) + if (unlikely(len > BUF_SIZE_8KiB)) ret = BUF_SIZE_16KiB; return ret; } From b8a26d46c0a4254f8bfe143681adb9d18d020299 Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:37 +0200 Subject: [PATCH 2219/2366] net: stmmac: size the RX buffers from the frame length, not the MTU When picking the buffsize to use based on the MTU, we shouldn't check only the MTU value, but also : - ETH_HLEN for the L2 header, - up to 2 VLAN tags, - the FCS, The default bufsize is 1536 bytes, which is enough to contain all the above so this hasn't surfaced before, but the addition of NET_IP_ALIGN to the start of buffer address tripped the Jumbo selftest, leading to this discovery. With that, we don't need the '>=' checks on the buffer len, we can use more consistent comparison operators in stmmac_set_bfsize. Fixes: 286a83721720 ("stmmac: add CHAINED descriptor mode support (V4)") Reviewed-by: Nicolai Buchwitz Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260917215339.2022523-7-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 1fb5f804ea23..d5a984ad864f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1536,17 +1536,17 @@ static unsigned int stmmac_rx_offset(struct stmmac_priv *priv) return NET_SKB_PAD + NET_IP_ALIGN; } -static int stmmac_set_bfsize(int mtu) +static int stmmac_set_bfsize(int len) { int ret; - if (mtu >= BUF_SIZE_8KiB) + if (len > BUF_SIZE_8KiB) ret = BUF_SIZE_16KiB; - else if (mtu >= BUF_SIZE_4KiB) + else if (len > BUF_SIZE_4KiB) ret = BUF_SIZE_8KiB; - else if (mtu >= BUF_SIZE_2KiB) + else if (len > BUF_SIZE_2KiB) ret = BUF_SIZE_4KiB; - else if (mtu > DEFAULT_BUFSIZE) + else if (len > DEFAULT_BUFSIZE) ret = BUF_SIZE_2KiB; else ret = DEFAULT_BUFSIZE; @@ -4063,7 +4063,7 @@ static struct stmmac_dma_conf * stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu) { struct stmmac_dma_conf *dma_conf; - int bfsize, ret; + int bfsize, len, ret; u8 chan; dma_conf = kzalloc_obj(*dma_conf); @@ -4073,13 +4073,15 @@ stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu) return ERR_PTR(-ENOMEM); } - /* Returns 0 or BUF_SIZE_16KiB if mtu > 8KiB and dwmac4 or ring mode */ - bfsize = stmmac_set_16kib_bfsize(priv, mtu); + len = mtu + ETH_HLEN + 2 * VLAN_HLEN + ETH_FCS_LEN; + + /* Returns 0 or BUF_SIZE_16KiB if len > 8KiB and dwmac4 or ring mode */ + bfsize = stmmac_set_16kib_bfsize(priv, len); if (bfsize < 0) bfsize = 0; if (bfsize < BUF_SIZE_16KiB) - bfsize = stmmac_set_bfsize(mtu); + bfsize = stmmac_set_bfsize(len); dma_conf->dma_buf_sz = bfsize; /* Chose the tx/rx size from the already defined one in the From c4ac6e94eb9423126bda907a7f2933284f7450ff Mon Sep 17 00:00:00 2001 From: Maxime Chevallier Date: Thu, 17 Sep 2026 23:53:38 +0200 Subject: [PATCH 2220/2366] net: stmmac: selftests: Account for alignment shift on dwmac1000 for Jumbo test On dwmac1000, we currently only support single-descriptor frames. The Jumbo test started failing when NET_IP_ALIGN was added to align the IP header, as this tests tries to send the biggest possible frame. On dwmac1000 the DMA transfer is aligned on 4-bytes, so adding a 2-byte shift at the start-of-buffer address means it takes a whole extra 4-byte DMA burst to receive the Jumbo packet, causing it to spill over the next descriptor. This doesn't seem to happen on dwmac4 and xgmac that appear to correctly handle unaligned xfers (only tested on dwmac4) Let's account for that in the Jumbo test, reduce the size of our big packet by the align size. Fixes: 23680bf5f8c6 ("net: stmmac: restore NET_IP_ALIGN in the RX DMA offset") Reviewed-by: Nicolai Buchwitz Signed-off-by: Maxime Chevallier Link: https://patch.msgid.link/20260917215339.2022523-8-maxime.chevallier@bootlin.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 43b8411c5112..c25dc9f89270 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -1789,6 +1789,9 @@ static int __stmmac_test_jumbo(struct stmmac_priv *priv, u16 queue) struct stmmac_packet_attrs attr = { }; int size = priv->dma_conf.dma_buf_sz; + if (!dwmac_is_xmac(priv->plat->core_type)) + size -= NET_IP_ALIGN; + attr.dst = priv->dev->dev_addr; attr.max_size = size - ETH_FCS_LEN; attr.queue_mapping = queue; From 0160953d8eec75c3c55562158c46442ff1fd410b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20T=C3=B6pel?= Date: Fri, 18 Sep 2026 13:46:40 +0200 Subject: [PATCH 2221/2366] eth: fbnic: Avoid rounding zero ring sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit roundup_pow_of_two() is undefined for zero. ethtool permits a zero ring size to reach the driver, where the minimum-size check should reject it. Leave zero unchanged while rounding nonzero ring sizes. The minimum-size check then rejects zero deterministically without changing the established behavior for other values. Fixes: 6cbf18a05c06 ("eth: fbnic: support ring size configuration") Reported-by: Sashiko Link: https://lore.kernel.org/netdev/178971206933.22033.236948278674126701@kernel.org/ Suggested-by: Alexander Duyck Signed-off-by: Björn Töpel Reviewed-by: Joe Damato Link: https://patch.msgid.link/20260918114641.1281172-1-bjorn@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c index 423f179c9d47..76e9a545bb16 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c @@ -313,6 +313,11 @@ fbnic_get_ringparam(struct net_device *netdev, struct ethtool_ringparam *ring, kernel_ring->hds_thresh = fbn->hds_thresh; } +static u32 fbnic_ring_size_pow2(u32 size) +{ + return size ? roundup_pow_of_two(size) : 0; +} + static void fbnic_set_rings(struct fbnic_net *fbn, struct ethtool_ringparam *ring, struct kernel_ethtool_ringparam *kernel_ring) @@ -334,10 +339,10 @@ fbnic_set_ringparam(struct net_device *netdev, struct ethtool_ringparam *ring, struct fbnic_net *clone; int err; - ring->rx_pending = roundup_pow_of_two(ring->rx_pending); - ring->rx_mini_pending = roundup_pow_of_two(ring->rx_mini_pending); - ring->rx_jumbo_pending = roundup_pow_of_two(ring->rx_jumbo_pending); - ring->tx_pending = roundup_pow_of_two(ring->tx_pending); + ring->rx_pending = fbnic_ring_size_pow2(ring->rx_pending); + ring->rx_mini_pending = fbnic_ring_size_pow2(ring->rx_mini_pending); + ring->rx_jumbo_pending = fbnic_ring_size_pow2(ring->rx_jumbo_pending); + ring->tx_pending = fbnic_ring_size_pow2(ring->tx_pending); /* These are absolute minimums allowing the device and driver to operate * but not necessarily guarantee reasonable performance. Settings below From 9c572a83037a7dcd653ba3a9cc468c16b857d0c9 Mon Sep 17 00:00:00 2001 From: Bernard Ladenthin Date: Fri, 18 Sep 2026 15:39:37 +0200 Subject: [PATCH 2222/2366] net/sched: fix potential stack infoleak in em_text_dump() em_text_dump() allocates struct tcf_em_text on the stack without zeroing it. strscpy() writes the algorithm name and a NUL terminator into conf.algo[], leaving the remaining bytes uninitialised. nla_put_nohdr() then copies the full struct to the netlink response. KMSAN on Linux 7.2-rc6 reports two kernel-infoleak splats from this path, one triggered via "tc filter show" and one via a raw RTM_GETTFILTER dump: BUG: KMSAN: kernel-infoleak in _copy_to_iter+0x1c9/0x2620 nla_put_nohdr+0x83/0x130 em_text_dump+0x291/0x550 Local variable conf created at: em_text_dump+0x5d/0x550 Bytes 168-179 of 199 are uninitialized I am not certain whether this constitutes a real security problem in practice: the test was conducted in a controlled KMSAN environment and the leaked stack bytes may or may not carry sensitive data on actual production kernels. I am reporting it because KMSAN flagged it as a kernel-infoleak and the fix is straightforward. I can provide a userspace reproducer on request. The original code used strncpy() which zero-pads to the destination size. Commit b04202d6065c ("net/sched: replace strncpy with strscpy") replaced it with strscpy(), which does not pad, creating this condition. Zero-initialising the struct closes it. Fixes: b04202d6065c ("net/sched: replace strncpy with strscpy") Link: https://lore.kernel.org/netdev/20250327143733.187438-1-richard120310@gmail.com/ Assisted-by: Claude:claude-sonnet-4-6 [KMSAN] Signed-off-by: Bernard Ladenthin Acked-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260918133953.12494-1-bernard.ladenthin@gmail.com Signed-off-by: Jakub Kicinski --- net/sched/em_text.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/em_text.c b/net/sched/em_text.c index 343f1aebeec2..4132f8c3c5fc 100644 --- a/net/sched/em_text.c +++ b/net/sched/em_text.c @@ -113,7 +113,7 @@ static void em_text_destroy(struct tcf_ematch *m) static int em_text_dump(struct sk_buff *skb, struct tcf_ematch *m) { struct text_match *tm = EM_TEXT_PRIV(m); - struct tcf_em_text conf; + struct tcf_em_text conf = {}; strscpy(conf.algo, tm->config->ops->name); conf.from_offset = tm->from_offset; From 6db1ce73e9853f533eb7f413f14ba00f8ec6f80d Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 20 Sep 2026 21:23:04 +0800 Subject: [PATCH 2223/2366] bpf: Reject dev-bound-only programs on other devices __bpf_offload_dev_match() falls back to comparing offdev pointers after an exact netdev mismatch. Bound-only programs normally have NULL offdevs, so unrelated netdevs compare equal. A bound-only program on an offload-registered netdev can instead inherit a real offdev and match a sibling port. With CAP_BPF and CAP_NET_ADMIN, a caller can use bpf(BPF_LINK_CREATE) with a different target ifindex to run metadata kfuncs specialized for the bound driver on the target driver's xdp_buff. Running a veth-bound program on tun reads beyond tun's bare stack xdp_buff as a veth_xdp_buff. Oops: general protection fault, probably for non-canonical address KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017] RIP: 0010:veth_xdp_rx_timestamp (drivers/net/veth.c:1673) Call Trace: ... tun_build_skb (drivers/net/tun.c:1739) tun_get_user (drivers/net/tun.c:1856) tun_chr_write_iter (drivers/net/tun.c:2091) vfs_write (fs/read_write.c:595 fs/read_write.c:687) ksys_write (fs/read_write.c:739) do_syscall_64 (arch/x86/entry/syscall_64.c:84) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Kernel panic - not syncing: Fatal exception in interrupt Restrict non-offloaded programs to exact netdev matches and retain the shared-offdev fallback only for genuinely offloaded multi-port programs. Fixes: 2b3486bc2d23 ("bpf: Introduce device-bound XDP programs") Reported-by: Signed-off-by: Weiming Shi Signed-off-by: Alexei Starovoitov Link: https://lore.kernel.org/bpf/20260917161335.1020405-2-bestswngs@gmail.com/ Link: https://patch.msgid.link/20260920132303.4109240-3-bestswngs@gmail.com --- kernel/bpf/offload.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/bpf/offload.c b/kernel/bpf/offload.c index 0d6f5569588c..d855399812ee 100644 --- a/kernel/bpf/offload.c +++ b/kernel/bpf/offload.c @@ -698,6 +698,8 @@ static bool __bpf_offload_dev_match(struct bpf_prog *prog, return false; if (offload->netdev == netdev) return true; + if (!bpf_prog_is_offloaded(prog->aux)) + return false; ondev1 = bpf_offload_find_netdev(offload->netdev); ondev2 = bpf_offload_find_netdev(netdev); From 76ebb69da677d2a4c5e59bf758b6424d511f5684 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 17 Sep 2026 13:49:03 +0200 Subject: [PATCH 2224/2366] Revert "selftests/filesystems: add mntns cleanup test" This reverts commit 3452eecbcc954ef859b7d19309c121a78d6d0e31. The test checks that the mounts of a destroyed mount namespace stay connected. The commit that made them stay connected is reverted next because it leaks superblocks and loop devices, so drop the test with it. Link: https://patch.msgid.link/20260917-work-put_mnt_ns-revert-v1-1-34d9b8679e68@kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/Makefile | 1 - .../filesystems/mntns_cleanup/.gitignore | 2 - .../filesystems/mntns_cleanup/Makefile | 6 -- .../mntns_cleanup/mntns_cleanup_test.c | 58 ------------------- 4 files changed, 67 deletions(-) delete mode 100644 tools/testing/selftests/filesystems/mntns_cleanup/.gitignore delete mode 100644 tools/testing/selftests/filesystems/mntns_cleanup/Makefile delete mode 100644 tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 330d061f6366..273853937c25 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -49,7 +49,6 @@ TARGETS += filesystems/move_mount TARGETS += filesystems/empty_mntns TARGETS += filesystems/fsmount_ns TARGETS += filesystems/fscontext_ns -TARGETS += filesystems/mntns_cleanup TARGETS += filesystems/xattr TARGETS += firmware TARGETS += fpu diff --git a/tools/testing/selftests/filesystems/mntns_cleanup/.gitignore b/tools/testing/selftests/filesystems/mntns_cleanup/.gitignore deleted file mode 100644 index 493fbcf8d9ec..000000000000 --- a/tools/testing/selftests/filesystems/mntns_cleanup/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -mntns_cleanup_test diff --git a/tools/testing/selftests/filesystems/mntns_cleanup/Makefile b/tools/testing/selftests/filesystems/mntns_cleanup/Makefile deleted file mode 100644 index 0e09e7030a5c..000000000000 --- a/tools/testing/selftests/filesystems/mntns_cleanup/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -TEST_GEN_PROGS := mntns_cleanup_test - -CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) - -include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c b/tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c deleted file mode 100644 index 5209712568b1..000000000000 --- a/tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include - -#include "../../kselftest_harness.h" - -FIXTURE(mntns_cleanup) { -}; - -FIXTURE_SETUP(mntns_cleanup) -{ - if (geteuid() != 0) - SKIP(return, "test requires CAP_SYS_ADMIN"); - - ASSERT_EQ(unshare(CLONE_NEWNS), 0); - ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); - - rmdir("/mnt_dir"); - ASSERT_EQ(mkdir("/mnt_dir", 0755), 0); - ASSERT_EQ(mount("tmpfs", "/mnt_dir", "tmpfs", 0, NULL), 0); - ASSERT_EQ(mkdir("/mnt_dir/hidden", 0755), 0); - ASSERT_EQ(mkdir("/mnt_dir/hidden/secret", 0755), 0); - ASSERT_EQ(mount("tmpfs", "/mnt_dir/hidden", "tmpfs", 0, NULL), 0); -} - -FIXTURE_TEARDOWN(mntns_cleanup) -{ -} - -/* Mounts must stay connected when a mount namespace is cleaned up. */ -TEST_F(mntns_cleanup, keeps_mounts_connected) -{ - int fd, sfd, err; - - fd = open("/mnt_dir", O_PATH | O_DIRECTORY | O_CLOEXEC); - ASSERT_GE(fd, 0); - - /* Destroy the namespace; the fd keeps /mnt_dir alive. */ - ASSERT_EQ(unshare(CLONE_NEWNS), 0); - - sfd = openat(fd, "hidden/secret", O_RDONLY); - err = errno; - if (sfd >= 0) - close(sfd); - close(fd); - - ASSERT_LT(sfd, 0) - TH_LOG("mount namespace teardown revealed what the overmount covered"); - ASSERT_EQ(err, ENOENT); -} - -TEST_HARNESS_MAIN From 2e2142a35d809c3cc726d3b39e7aeee98d9ab71c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 17 Sep 2026 13:49:04 +0200 Subject: [PATCH 2225/2366] Revert "put_mnt_ns(): leave mounts connected" This reverts commit 0342482a4d15358fe6931606caf58968de5d1d38. Keeping every mount of a dying namespace attached to its parent changes who owns it. An attached but unmounted mount is owned by its parent: the parent's final mntput() is what drops the child's reference and takes its superblock down. Before that commit only the namespace root was in that position and every other mount dropped its own reference in namespace_unlock(). That ownership rule turns any reference from a child's superblock back to one of its ancestors into a cycle. The obvious case is a loop device: unshare -m sh -c 'mount -o loop /var/tmp/img /var/tmp/mp' mount(8) opens the image from inside the new namespace, so the loop device's backing file pins that namespace's copy of the root mount. When the namespace dies the loop mount stays attached to that copy and is owned by it. The copy can't reach zero because the backing file holds it. The backing file is only put once the ext4 superblock releases the block device and autoclear runs, and that needs the loop mount to go first. Nothing breaks the cycle. The superblock, the loop device and, since the root copy keeps all of its children. systemd-sysext sets up its loop devices in a private mount namespace and relies on autoclear when that namespace goes away, which is how this was found. Anything that holds a file on an ancestor from a mounted filesystem has the same problem. That includes ecryptfs lower paths, erofs file-backed mounts, fuse passthrough backing files. That's a bigger fix and not an -rc change. Revert. Fixes: 0342482a4d15 ("put_mnt_ns(): leave mounts connected") Reported-by: Michael Vogt Link: https://gist.github.com/mvo5/63ef46482349f3b1c3957d463a0c9c6f Link: https://patch.msgid.link/20260917-work-put_mnt_ns-revert-v1-2-34d9b8679e68@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/namespace.c b/fs/namespace.c index 5e41021eaa63..580877e46b1a 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -6301,7 +6301,7 @@ void put_mnt_ns(struct mnt_namespace *ns) guard(namespace_excl)(); emptied_ns = ns; guard(mount_writer)(); - umount_tree(ns->root, UMOUNT_CONNECTED); + umount_tree(ns->root, 0); } struct vfsmount *kern_mount(struct file_system_type *type) From 1feb5d39b05afd902ed9fc902ec5b15be03a4bdb Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 22 Sep 2026 10:52:28 +0200 Subject: [PATCH 2226/2366] gpio: cdev: fix kernel stack leak to user-space in error path If we fail to acquire the GPIO chip guard in gpio_desc_to_lineinfo(), we return immediately before zeroing the info struct we'll end up passing to the user-space later in lineinfo_get_v1(). This may leak the kernel stack contents. Make gpio_desc_to_lineinfo() return int so that the -ENODEV returned on failure to acquire the guard can be propagated to the callers. While not strictly necessary: move the memset() before trying to acquire the SRCU read lock too for good measure. Fixes: d83cee3d2bb1 ("gpio: protect the pointer to gpio_chip in gpio_device with SRCU") Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260912123529.7951-1-tzungbi%40kernel.org?part=3 Reviewed-by: Kent Gibson Link: https://patch.msgid.link/20260922-gpio-cdev-stack-leak-fixes-v3-1-7a0c7a4299d5@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index d1105b7ae437..5d53bfcdf726 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2172,18 +2172,19 @@ static void gpio_v2_line_info_changed_to_v1( #endif /* CONFIG_GPIO_CDEV_V1 */ -static void gpio_desc_to_lineinfo(struct gpio_desc *desc, - struct gpio_v2_line_info *info, bool atomic) +static int gpio_desc_to_lineinfo(struct gpio_desc *desc, + struct gpio_v2_line_info *info, bool atomic) { u32 debounce_period_us; unsigned long dflags; const char *label; + memset(info, 0, sizeof(*info)); + CLASS(gpio_chip_guard, guard)(desc); if (!guard.gc) - return; + return -ENODEV; - memset(info, 0, sizeof(*info)); info->offset = gpiod_hwgpio(desc); if (desc->name) @@ -2258,6 +2259,8 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc, debounce_period_us; info->num_attrs++; } + + return 0; } struct gpio_chardev_data { @@ -2309,6 +2312,7 @@ static int lineinfo_get_v1(struct gpio_chardev_data *cdev, void __user *ip, struct gpio_desc *desc; struct gpioline_info lineinfo; struct gpio_v2_line_info lineinfo_v2; + int ret; if (copy_from_user(&lineinfo, ip, sizeof(lineinfo))) return -EFAULT; @@ -2326,7 +2330,10 @@ static int lineinfo_get_v1(struct gpio_chardev_data *cdev, void __user *ip, return -EBUSY; } - gpio_desc_to_lineinfo(desc, &lineinfo_v2, false); + ret = gpio_desc_to_lineinfo(desc, &lineinfo_v2, false); + if (ret) + return ret; + gpio_v2_line_info_to_v1(&lineinfo_v2, &lineinfo); if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) { @@ -2344,6 +2351,7 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip, { struct gpio_desc *desc; struct gpio_v2_line_info lineinfo; + int ret; if (copy_from_user(&lineinfo, ip, sizeof(lineinfo))) return -EFAULT; @@ -2363,7 +2371,10 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip, if (test_and_set_bit(lineinfo.offset, cdev->watched_lines)) return -EBUSY; } - gpio_desc_to_lineinfo(desc, &lineinfo, false); + + ret = gpio_desc_to_lineinfo(desc, &lineinfo, false); + if (ret) + return ret; if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) { if (watch) @@ -2489,6 +2500,7 @@ static int lineinfo_changed_notify(struct notifier_block *nb, struct lineinfo_changed_ctx *ctx; struct gpio_desc *desc = data; struct file *fp; + int ret; if (!test_bit(gpiod_hwgpio(desc), cdev->watched_lines)) return NOTIFY_DONE; @@ -2519,7 +2531,13 @@ static int lineinfo_changed_notify(struct notifier_block *nb, ctx->chg.event_type = action; ctx->chg.timestamp_ns = ktime_get_ns(); - gpio_desc_to_lineinfo(desc, &ctx->chg.info, true); + + ret = gpio_desc_to_lineinfo(desc, &ctx->chg.info, true); + if (ret) { + fput(fp); + return NOTIFY_DONE; + } + /* Keep the GPIO device alive until we emit the event. */ ctx->gdev = gpio_device_get(desc->gdev); ctx->cdev = cdev; From 2d2a2d7aa98741b58f54cacc99b52024e4d865f9 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 9 Sep 2026 21:30:34 +0200 Subject: [PATCH 2227/2366] super: make iterate_supers_type() deletion-safe iterate_supers_type() drops sb_lock while invoking the callback and keeps only a passive reference to the current superblock. That reference keeps the object allocated, but does not keep its s_instances node linked. After the iterator releases s_umount, final teardown can unlink the current s_instances node. The iterator then advances through a reinitialized node. With the current hlist it stops without visiting the remaining superblocks. The unlink moved from generic_shutdown_super() to kill_super_notify(), but the cursor lifetime has been unsafe since the helper was introduced. The CIFS DFS lookup can consequently miss a matching superblock and return -EINVAL. Move removal from fs_supers to put_super(), alongside removal from super_blocks, so a passive reference keeps both list nodes linked. Keep the filesystem module reference until then, since unlinking s_instances may touch type->fs_supers. Make sget_fc() skip SB_DEAD superblocks before invoking test(), and set SB_DEAD under sb_lock to serialize with those callbacks. This allows kernfs to free its private information after kill_anon_super() returns. Keep matching SB_DYING superblocks until SB_DEAD is set so concurrent mounts still wait for teardown before retrying. Fixes: 43e15cdbefea ("new helper: iterate_supers_type()") Reported-by: Karl Mehltretter Closes: https://lore.kernel.org/r/20260903013336.92081-1-kmehltretter@gmail.com Suggested-by: Jan Kara Cc: stable@vger.kernel.org Tested-by: Karl Mehltretter [kmehltretter: supplied the commit message] Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260909193034.7467-1-kmehltretter@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/mount.c | 4 ++-- fs/super.c | 39 +++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/fs/kernfs/mount.c b/fs/kernfs/mount.c index f183a96778b9..a57399021c8b 100644 --- a/fs/kernfs/mount.c +++ b/fs/kernfs/mount.c @@ -434,8 +434,8 @@ void kernfs_kill_sb(struct super_block *sb) up_write(&root->kernfs_supers_rwsem); /* - * Remove the superblock from fs_supers/s_instances - * so we can't find it, before freeing kernfs_super_info. + * Mark the superblock dead so sget_fc() can't find it, + * before freeing kernfs_super_info. */ kill_anon_super(sb); kfree(info); diff --git a/fs/super.c b/fs/super.c index 01db6124e409..e41cae9e5c62 100644 --- a/fs/super.c +++ b/fs/super.c @@ -433,15 +433,19 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags, void put_super(struct super_block *s) { if (refcount_dec_and_test(&s->s_passive)) { + struct file_system_type *type = s->s_type; spin_lock(&sb_lock); list_del_init(&s->s_list); + hlist_del_init(&s->s_instances); spin_unlock(&sb_lock); WARN_ON(s->s_dentry_lru.node); WARN_ON(s->s_inode_lru.node); WARN_ON(s->s_mounts); call_rcu(&s->rcu, destroy_super_rcu); + /* The unlink above may touch type->fs_supers, so drop it last. */ + put_filesystem(type); } } @@ -558,17 +562,6 @@ static void kill_super_notify(struct super_block *sb) if (sb->s_flags & SB_DEAD) return; - /* - * Remove it from @fs_supers so it isn't found by new - * sget_fc() walkers anymore. Any concurrent mounter still - * managing to grab a temporary reference is guaranteed to - * already see SB_DYING and will wait until we notify them about - * SB_DEAD. - */ - spin_lock(&sb_lock); - hlist_del_init(&sb->s_instances); - spin_unlock(&sb_lock); - /* Drop sget_fc()'s claim; a never-registered entry stays with the sb. */ if (sb->s_super_dev->sd_dev) { super_dev_put(sb->s_super_dev); @@ -577,11 +570,15 @@ static void kill_super_notify(struct super_block *sb) /* * Let concurrent mounts know that this thing is really dead. - * We don't need @sb->s_umount here as every concurrent caller - * will see SB_DYING and either discard the superblock or wait - * for SB_DEAD. + * sget_fc() skips SB_DEAD superblocks and calls test() under + * sb_lock, so set it under sb_lock: once we return no test() + * runs on this superblock anymore and none will start. Everyone + * else already saw SB_DYING and either discarded the superblock + * or waits for SB_DEAD. */ + spin_lock(&sb_lock); super_wake(sb, SB_DEAD); + spin_unlock(&sb_lock); } /** @@ -608,7 +605,6 @@ void deactivate_locked_super(struct super_block *s) list_lru_destroy(&s->s_dentry_lru); list_lru_destroy(&s->s_inode_lru); - put_filesystem(fs); put_super(s); } else { super_unlock_excl(s); @@ -795,12 +791,12 @@ void generic_shutdown_super(struct super_block *sb) } /* * Broadcast to everyone that grabbed a temporary reference to this - * superblock before we removed it from @fs_supers that the superblock - * is dying. Every walker of @fs_supers outside of sget_fc() will now - * discard this superblock and treat it as dead. + * superblock that it is dying. Every walker of @fs_supers outside + * of sget_fc() will now discard this superblock and treat it as + * dead. * - * We leave the superblock on @fs_supers so it can be found by - * sget_fc() until we passed sb->kill_sb(). + * sget_fc() keeps finding the superblock until SB_DEAD is set, so + * a concurrent mounter waits until we passed sb->kill_sb(). */ super_wake(sb, SB_DYING); super_unlock_excl(sb); @@ -879,6 +875,9 @@ struct super_block *sget_fc(struct fs_context *fc, spin_lock(&sb_lock); if (test) { hlist_for_each_entry(old, &fc->fs_type->fs_supers, s_instances) { + /* Only unlinked at the last passive reference. */ + if (super_flags(old, SB_DEAD)) + continue; if (test(old, fc)) goto share_extant_sb; } From e9438ab5328a177c9c0e5df87eb92a7162841e98 Mon Sep 17 00:00:00 2001 From: Ridham Khurana Date: Tue, 22 Sep 2026 09:20:58 +0000 Subject: [PATCH 2228/2366] gpio: zynq: fix runtime PM leak on request error path pm_runtime_get_sync() leaves the usage counter incremented even when it fails, and zynq_gpio_request() returns the error without dropping it. gpiolib does not call ->free() when ->request() fails, so zynq_gpio_free(), which holds the only matching pm_runtime_put(), never runs. The reference is leaked and the controller can no longer runtime-suspend, so its clock stays enabled. Switch to pm_runtime_resume_and_get(), which only increments the usage counter on success. Fixes: 3242ba117e9b ("gpio: Add driver for Zynq GPIO controller") Cc: stable@vger.kernel.org Signed-off-by: Ridham Khurana Link: https://patch.msgid.link/20260922092102.1053513-1-khurana.ridham222@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-zynq.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-zynq.c b/drivers/gpio/gpio-zynq.c index 15a79d9a2e9e..13e4c5f0e297 100644 --- a/drivers/gpio/gpio-zynq.c +++ b/drivers/gpio/gpio-zynq.c @@ -798,15 +798,7 @@ static int zynq_gpio_runtime_resume(struct device *dev) static int zynq_gpio_request(struct gpio_chip *chip, unsigned int offset) { - int ret; - - ret = pm_runtime_get_sync(chip->parent); - - /* - * If the device is already active pm_runtime_get() will return 1 on - * success, but gpio_request still needs to return 0. - */ - return ret < 0 ? ret : 0; + return pm_runtime_resume_and_get(chip->parent); } static void zynq_gpio_free(struct gpio_chip *chip, unsigned int offset) From cca4980630b3c7a85f53cb43c6018184ce5d4e37 Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Sun, 20 Sep 2026 16:16:39 -0700 Subject: [PATCH 2229/2366] perf/core: Fix a refcount leak in attach_perf_ctx_data() The attach_perf_ctx_data() can race on global and !global cases. The global case is protected by global_ctx_data_rwsem and shares a single reference count using perf_ctx_data.global field. But when it races with !global case, it may miss to set the global field and result in a reference count leak. CPU1 CPU2 ---------------------------------------------------------------- attach_task_ctx_data(.global=1) attach_task_ctx_data(.global=0) cd1 = alloc_perf_ctx_data(); cd2 = alloc_perf_ctx_data(); // { .global = 0, .refcount = 1 }; try_cmpxchg(); // success, // task->perf_ctx_data = cd2 try_cmpxhg(); // fail; old = cd2 refcount_inc_not_zero(&old->refcount); // success // old.refcount = 2 free_perf_ctx_data(cd1); Then later detach_global_ctx_data() will see the data but it's not marked as global, so it won't call detach_task_ctx_data(). Fixes: 506e64e710ff ("perf: attach/detach PMU specific data") Assisted-by: Sashiko.dev:Gemini-3.1-pro Signed-off-by: Namhyung Kim Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260920231639.11910-1-namhyung@kernel.org --- kernel/events/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/events/core.c b/kernel/events/core.c index db7b76d6b68a..e180134bad5e 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -5454,6 +5454,8 @@ attach_task_ctx_data(struct task_struct *task, struct kmem_cache *ctx_cache, } if (refcount_inc_not_zero(&old->refcount)) { + if (global) + old->global = true; free_perf_ctx_data(cd); /* unused */ return 0; } From 36bb85cf36cab15fb611cb44b78a5df06e4e69a2 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 10 Aug 2026 06:35:34 -0700 Subject: [PATCH 2230/2366] perf/core: Fix NULL pmu_ctx passed to pmu->sched_task() perf_pmu_sched_task() returns early when cpuctx->task_ctx is set, and cpc->task_epc is only non-NULL while a task context is scheduled in on this CPU. __perf_pmu_sched_task() therefore always passes NULL: Unable to handle kernel NULL pointer dereference at virtual address 00 pc : armv8pmu_sched_task+0x14/0x50 Call trace: armv8pmu_sched_task+0x14/0x50 (P) perf_pmu_sched_task+0xac/0x108 __perf_event_task_sched_out+0x6c/0xe0 Pass &cpc->epc instead, the CPU-wide context for this PMU, which the function already dereferences a few lines up to find pmu. armv8pmu_sched_task() is the only in-tree implementation that dereferences the argument, and it only reads ->pmu, so the oops needs BRBE, added in v6.17. Fixes: bd2756811766 ("perf: Rewrite core context handling") Signed-off-by: Puranjay Mohan Signed-off-by: Peter Zijlstra (Intel) Tested-by: Yifan Wu Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260810133540.1947118-2-puranjay@kernel.org --- kernel/events/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index e180134bad5e..7ce72f366f6c 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -3914,7 +3914,7 @@ static void __perf_pmu_sched_task(struct perf_cpu_pmu_context *cpc, perf_ctx_lock(cpuctx, cpuctx->task_ctx); perf_pmu_disable(pmu); - pmu->sched_task(cpc->task_epc, task, sched_in); + pmu->sched_task(&cpc->epc, task, sched_in); perf_pmu_enable(pmu); perf_ctx_unlock(cpuctx, cpuctx->task_ctx); From 3d8d74100954a3b17e5c5e37adfe14e16b1db103 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 10 Aug 2026 06:35:35 -0700 Subject: [PATCH 2231/2366] perf/core: Run sched_task() for PMUs with only CPU-wide events perf_pmu_sched_task() returns early when cpuctx->task_ctx is set and leaves the work to perf_ctx_sched_task_cb(), which only walks ctx->pmu_ctx_list. A PMU whose events are all CPU-wide is not on that list, so nothing calls its sched_task(). With perf record -b -e cycles -a -- ls armv8pmu_sched_task() is skipped on every switch to a task that has a perf context but no event on that PMU, and BRBE records leak across the task boundary. intel_pmu_lbr_add() calls perf_sched_cb_inc() unconditionally too, so LBR records leak the same way on x86. Drop the early return and skip only the CPCs that perf_ctx_sched_task_cb() handles. That one needs a gate of its own to make the split exact: it tests cpc->sched_cb_usage, which perf_sched_cb_inc() sets per CPU for every branch stack user, so a task with an event for that PMU pinned to another CPU would be handled twice. On x86 the second __intel_pmu_lbr_restore() finds lbr_stack_state == LBR_NONE and calls intel_pmu_lbr_reset(), throwing away the callstack the first one restored. cpc->task_epc is set only while a task context is scheduled in, and there is one epc per PMU on ctx->pmu_ctx_list, so the two gates are inverses. For the CPCs perf_pmu_sched_task() picks up, the callback now runs outside the perf_ctx_disable() and perf_ctx_enable() pair in perf_event_context_sched_in(). __perf_pmu_sched_task() disables the PMU around the call itself. Fixes: bd2756811766 ("perf: Rewrite core context handling") Signed-off-by: Puranjay Mohan Signed-off-by: Peter Zijlstra (Intel) Tested-by: Yifan Wu Link: https://patch.msgid.link/20260810133540.1947118-3-puranjay@kernel.org Cc: stable@vger.kernel.org --- kernel/events/core.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index 7ce72f366f6c..634d2ccbab82 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -3764,6 +3764,9 @@ static void perf_ctx_sched_task_cb(struct perf_event_context *ctx, list_for_each_entry(pmu_ctx, &ctx->pmu_ctx_list, pmu_ctx_entry) { cpc = this_cpc(pmu_ctx->pmu); + if (cpc->task_epc != pmu_ctx) + continue; + if (cpc->sched_cb_usage && pmu_ctx->pmu->sched_task) pmu_ctx->pmu->sched_task(pmu_ctx, task, sched_in); } @@ -3924,15 +3927,17 @@ static void perf_pmu_sched_task(struct task_struct *prev, struct task_struct *next, bool sched_in) { - struct perf_cpu_context *cpuctx = this_cpu_ptr(&perf_cpu_context); struct perf_cpu_pmu_context *cpc, *cpc2; - /* cpuctx->task_ctx will be handled in perf_event_context_sched_in/out */ - if (prev == next || cpuctx->task_ctx) + if (prev == next) return; - list_for_each_entry_safe(cpc, cpc2, this_cpu_ptr(&sched_cb_list), sched_cb_entry) + list_for_each_entry_safe(cpc, cpc2, this_cpu_ptr(&sched_cb_list), sched_cb_entry) { + if (cpc->task_epc) + continue; + __perf_pmu_sched_task(cpc, sched_in ? next : prev, sched_in); + } } static void perf_event_switch(struct task_struct *task, From 24b620729e53d978b3e425f55bc66efd3bab1f59 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 10 Aug 2026 06:35:36 -0700 Subject: [PATCH 2232/2366] perf/core: Fill branch entries with a single assignment perf_clear_branch_entry_bitfields() clears the bitfields of struct perf_branch_entry one by one and leaves from/to alone, since callers overwrite those straight away. The list has to be kept in sync with the struct by hand and has already fallen behind: new_type and priv were added to perf_branch_entry and never added here. Only BRBE writes those two, and neither for every record. brbe_set_perf_entry_type() leaves new_type alone for a branch type it does not recognise, and priv is not set for source-only records. arm_pmuv3.c allocates the per-CPU branch stack with kmalloc(), so such a record reaches userspace with whatever the slot held: uninitialised kmalloc() data on the first pass over the buffer, the previous record's values after that. Nothing under arch/x86/events/ writes either field, so only arm64 is affected. Assign the whole entry at each site instead. Everything not named is then zero, and there is no list to keep in sync. The bitfields add up to exactly 64 bits, so the struct has no padding to leave undefined. perf_clear_branch_entry_bitfields() has no callers left, so remove it. perf_entry_from_brbe_regset() assigns an empty literal instead, since it fills from/to conditionally. PERF_BR_SPEC_NA is 0, so dropping the explicit spec assignment changes nothing. Fixes: b190bc4ac9e6 ("perf: Extend branch type classification") Fixes: 5402d25aa571 ("perf: Capture branch privilege information") Suggested-by: Peter Zijlstra Signed-off-by: Puranjay Mohan Signed-off-by: Peter Zijlstra (Intel) Tested-by: Yifan Wu Link: https://patch.msgid.link/20260810133540.1947118-4-puranjay@kernel.org --- arch/x86/events/amd/brs.c | 9 +++--- arch/x86/events/amd/lbr.c | 16 +++++----- arch/x86/events/intel/lbr.c | 63 ++++++++++++++++++++----------------- drivers/perf/arm_brbe.c | 2 +- include/linux/perf_event.h | 17 ---------- 5 files changed, 47 insertions(+), 60 deletions(-) diff --git a/arch/x86/events/amd/brs.c b/arch/x86/events/amd/brs.c index dc564688f3d7..54b13faba116 100644 --- a/arch/x86/events/amd/brs.c +++ b/arch/x86/events/amd/brs.c @@ -343,11 +343,10 @@ void amd_brs_drain(void) if (!amd_brs_match_plm(event, from, to)) continue; - perf_clear_branch_entry_bitfields(br+nr); - - br[nr].from = from; - br[nr].to = to; - + br[nr] = (struct perf_branch_entry){ + .from = from, + .to = to, + }; nr++; } empty: diff --git a/arch/x86/events/amd/lbr.c b/arch/x86/events/amd/lbr.c index 9d9c961989d5..a55646fcb846 100644 --- a/arch/x86/events/amd/lbr.c +++ b/arch/x86/events/amd/lbr.c @@ -184,13 +184,6 @@ void amd_pmu_lbr_read(void) entry.to.split.reserved) continue; - perf_clear_branch_entry_bitfields(br + out); - - br[out].from = sign_ext_branch_ip(entry.from.split.ip); - br[out].to = sign_ext_branch_ip(entry.to.split.ip); - br[out].mispred = entry.from.split.mispredict; - br[out].predicted = !br[out].mispred; - /* * Set branch speculation information using the status of * the valid and spec bits. @@ -208,7 +201,14 @@ void amd_pmu_lbr_read(void) * speculative and took the correct path */ idx = (entry.to.split.valid << 1) | entry.to.split.spec; - br[out].spec = lbr_spec_map[idx]; + + br[out] = (struct perf_branch_entry){ + .from = sign_ext_branch_ip(entry.from.split.ip), + .to = sign_ext_branch_ip(entry.to.split.ip), + .mispred = entry.from.split.mispredict, + .predicted = !entry.from.split.mispredict, + .spec = lbr_spec_map[idx], + }; out++; } diff --git a/arch/x86/events/intel/lbr.c b/arch/x86/events/intel/lbr.c index cbe5c762008d..22e2a06d5786 100644 --- a/arch/x86/events/intel/lbr.c +++ b/arch/x86/events/intel/lbr.c @@ -756,10 +756,10 @@ void intel_pmu_lbr_read_32(struct cpu_hw_events *cpuc) rdmsrq(x86_pmu.lbr_from + lbr_idx, msr_lastbranch.lbr); - perf_clear_branch_entry_bitfields(br); - - br->from = msr_lastbranch.from; - br->to = msr_lastbranch.to; + *br = (struct perf_branch_entry){ + .from = msr_lastbranch.from, + .to = msr_lastbranch.to, + }; br++; } cpuc->lbr_stack.nr = i; @@ -847,14 +847,15 @@ void intel_pmu_lbr_read_64(struct cpu_hw_events *cpuc) if (abort && x86_pmu.lbr_double_abort && out > 0) out--; - perf_clear_branch_entry_bitfields(br+out); - br[out].from = from; - br[out].to = to; - br[out].mispred = mis; - br[out].predicted = pred; - br[out].in_tx = in_tx; - br[out].abort = abort; - br[out].cycles = cycles; + br[out] = (struct perf_branch_entry){ + .from = from, + .to = to, + .mispred = mis, + .predicted = pred, + .in_tx = in_tx, + .abort = abort, + .cycles = cycles, + }; out++; } cpuc->lbr_stack.nr = out; @@ -905,6 +906,7 @@ static void intel_pmu_store_lbr(struct cpu_hw_events *cpuc, struct perf_branch_entry *e; struct lbr_entry *lbr; u64 from, to, info; + bool mispred; int i; for (i = 0; i < x86_pmu.lbr_nr; i++) { @@ -921,24 +923,27 @@ static void intel_pmu_store_lbr(struct cpu_hw_events *cpuc, to = rdlbr_to(i, lbr); info = rdlbr_info(i, lbr); - perf_clear_branch_entry_bitfields(e); + mispred = get_lbr_mispred(info); - e->from = from; - e->to = to; - e->mispred = get_lbr_mispred(info); - e->predicted = !e->mispred; - e->in_tx = !!(info & LBR_INFO_IN_TX); - e->abort = !!(info & LBR_INFO_ABORT); - e->cycles = get_lbr_cycles(info); - e->type = get_lbr_br_type(info); - - /* - * Leverage the reserved field of cpuc->lbr_entries[i] to - * temporarily store the branch counters information. - * The later code will decide what content can be disclosed - * to the perf tool. Pleae see intel_pmu_lbr_counters_reorder(). - */ - e->reserved = (info >> LBR_INFO_BR_CNTR_OFFSET) & LBR_INFO_BR_CNTR_FULL_MASK; + *e = (struct perf_branch_entry){ + .from = from, + .to = to, + .mispred = mispred, + .predicted = !mispred, + .in_tx = !!(info & LBR_INFO_IN_TX), + .abort = !!(info & LBR_INFO_ABORT), + .cycles = get_lbr_cycles(info), + .type = get_lbr_br_type(info), + /* + * Leverage the reserved field of + * cpuc->lbr_entries[i] to temporarily store the + * branch counters information. The later code will + * decide what content can be disclosed to the perf + * tool. Pleae see intel_pmu_lbr_counters_reorder(). + */ + .reserved = (info >> LBR_INFO_BR_CNTR_OFFSET) & + LBR_INFO_BR_CNTR_FULL_MASK, + }; } cpuc->lbr_stack.nr = i; diff --git a/drivers/perf/arm_brbe.c b/drivers/perf/arm_brbe.c index ba554e0c846c..254be4da8ae2 100644 --- a/drivers/perf/arm_brbe.c +++ b/drivers/perf/arm_brbe.c @@ -604,7 +604,7 @@ static bool perf_entry_from_brbe_regset(int index, struct perf_branch_entry *ent return false; brbinf = bregs.brbinf; - perf_clear_branch_entry_bitfields(entry); + *entry = (struct perf_branch_entry){ }; if (brbe_record_is_complete(brbinf)) { entry->from = bregs.brbsrc; entry->to = bregs.brbtgt; diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 5842552294c1..915c6fd3f084 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1467,23 +1467,6 @@ static inline u32 perf_sample_data_size(struct perf_sample_data *data, return size; } -/* - * Clear all bitfields in the perf_branch_entry. - * The to and from fields are not cleared because they are - * systematically modified by caller. - */ -static inline void perf_clear_branch_entry_bitfields(struct perf_branch_entry *br) -{ - br->mispred = 0; - br->predicted = 0; - br->in_tx = 0; - br->abort = 0; - br->cycles = 0; - br->type = 0; - br->spec = PERF_BR_SPEC_NA; - br->reserved = 0; -} - extern void perf_output_sample(struct perf_output_handle *handle, struct perf_event_header *header, struct perf_sample_data *data, From 2bc6b218717b9d08f466f88209251d54bc09b207 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 22 Sep 2026 19:14:30 +0100 Subject: [PATCH 2233/2366] arm64/boot: Disable trapping of PMZR_EL0 writes to EL2 __init_el2_fgt2() writes one mask to both HDFGRTR2_EL2 and HDFGWTR2_EL2. PMZR_EL0 is write-only, so its trap bit, nPMZR_EL0, exists only in HDFGWTR2_EL2 and is therefore never set: a PMZR_EL0 write from the host traps to EL2, where the nVHE hypervisor has no handler and BUG()s. The kernel never writes PMZR_EL0, but kernel.perf_user_access=1 has the PMU driver set PMUSERENR_EL0.UEN for a task with a user-read event, so a write from EL0 reaches the trap and takes the host down without a panic message. Accumulate the HDFGWTR2_EL2 bits separately, as __init_el2_fgt() already does for HDFGWTR_EL2, and set nPMZR_EL0 with the other FEAT_PMUv3p9 bits. Fixes: 858c7bfcb35e1 ("arm64/boot: Enable EL2 requirements for FEAT_PMUv3p9") Cc: stable@vger.kernel.org Signed-off-by: Fuad Tabba Reviewed-by: Anshuman Khandual Reviewed-by: Oliver Upton Signed-off-by: Will Deacon --- Documentation/arch/arm64/booting.rst | 1 + arch/arm64/include/asm/el2_setup.h | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Documentation/arch/arm64/booting.rst b/Documentation/arch/arm64/booting.rst index 13ef311dace8..3fea4b14ef7c 100644 --- a/Documentation/arch/arm64/booting.rst +++ b/Documentation/arch/arm64/booting.rst @@ -465,6 +465,7 @@ Before jumping into the kernel, the following conditions must be met: - HDFGWTR2_EL2.nPMICNTR_EL0 (bit 2) must be initialised to 0b1. - HDFGWTR2_EL2.nPMICFILTR_EL0 (bit 3) must be initialised to 0b1. - HDFGWTR2_EL2.nPMUACR_EL1 (bit 4) must be initialised to 0b1. + - HDFGWTR2_EL2.nPMZR_EL0 (bit 21) must be initialised to 0b1. For CPUs with SPE data source filtering (FEAT_SPE_FDS): diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h index aa8ec9df8024..87560d8b254e 100644 --- a/arch/arm64/include/asm/el2_setup.h +++ b/arch/arm64/include/asm/el2_setup.h @@ -418,6 +418,7 @@ b.lt .Lskip_fgt2_\@ mov x0, xzr + mov x2, xzr mrs x1, id_aa64dfr0_el1 ubfx x1, x1, #ID_AA64DFR0_EL1_PMUVer_SHIFT, #4 cmp x1, #ID_AA64DFR0_EL1_PMUVer_V3P9 @@ -426,6 +427,11 @@ orr x0, x0, #HDFGRTR2_EL2_nPMICNTR_EL0 orr x0, x0, #HDFGRTR2_EL2_nPMICFILTR_EL0 orr x0, x0, #HDFGRTR2_EL2_nPMUACR_EL1 + orr x2, x2, #HDFGWTR2_EL2_nPMICNTR_EL0 + orr x2, x2, #HDFGWTR2_EL2_nPMICFILTR_EL0 + orr x2, x2, #HDFGWTR2_EL2_nPMUACR_EL1 + /* PMZR_EL0 is write-only, so it has no read trap to disable */ + orr x2, x2, #HDFGWTR2_EL2_nPMZR_EL0 .Lskip_pmuv3p9_\@: /* If SPE is implemented, */ __spe_vers_imp .Lskip_spefds_\@, ID_AA64DFR0_EL1_PMSVer_IMP, x1 @@ -436,10 +442,11 @@ cbz x1, .Lskip_spefds_\@ /* disable traps of PMSDSFR to EL2. */ orr x0, x0, #HDFGRTR2_EL2_nPMSDSFR_EL1 + orr x2, x2, #HDFGWTR2_EL2_nPMSDSFR_EL1 .Lskip_spefds_\@: msr_s SYS_HDFGRTR2_EL2, x0 - msr_s SYS_HDFGWTR2_EL2, x0 + msr_s SYS_HDFGWTR2_EL2, x2 msr_s SYS_HFGRTR2_EL2, xzr msr_s SYS_HFGWTR2_EL2, xzr msr_s SYS_HFGITR2_EL2, xzr From 4cbe530c0233c7413aaaeb029a4f32dd6aadacbb Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 19 Sep 2026 19:10:58 +0200 Subject: [PATCH 2234/2366] gpio: tps65219: Fix GPIO input value reads TPS65219_MFP_GPIO_STATUS_MASK is already BIT(4). Passing it to BIT() again tests bit 16, which cannot be set in the 8-bit MFP_CTRL register, so GPIO0 is always reported low when configured as an input. Test the register value with the mask directly. Fixes: 57e30e00bd5b ("gpio: tps65219: add GPIO support for TPS65219 PMIC") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Jonathan Cormier Link: https://patch.msgid.link/20260919171100.90430-2-kmehltretter@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tps65219.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-tps65219.c b/drivers/gpio/gpio-tps65219.c index 457fd8a589e8..b25c6f727768 100644 --- a/drivers/gpio/gpio-tps65219.c +++ b/drivers/gpio/gpio-tps65219.c @@ -79,7 +79,7 @@ static int tps65219_gpio_get(struct gpio_chip *gc, unsigned int offset) if (ret) return ret; - ret = !!(val & BIT(TPS65219_MFP_GPIO_STATUS_MASK)); + ret = !!(val & TPS65219_MFP_GPIO_STATUS_MASK); dev_warn(dev, "GPIO%d = %d, MULTI_DEVICE_ENABLE, not a standard GPIO\n", offset, ret); /* From 93cf8cedeaaa05714f709b539cfb976e0b80c830 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 19 Sep 2026 19:10:59 +0200 Subject: [PATCH 2235/2366] gpio: tps65219: Use the variant-specific direction callback The TPS65214 template installs its own get_direction callback because its direction bit is in GENERAL_CONFIG. The shared get and direction callbacks nevertheless call tps65219_gpio_get_direction() directly and interpret the unrelated TPS65219 MFP bit. On TPS65214 this can reject reads from an input and skip the change from input to output. Call the callback selected by the gpio_chip template instead. Fixes: 1b6ab07c0c80 ("gpio: tps65219: Add support for TI TPS65214 PMIC") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260919171100.90430-3-kmehltretter@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tps65219.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-tps65219.c b/drivers/gpio/gpio-tps65219.c index b25c6f727768..479a80ef7654 100644 --- a/drivers/gpio/gpio-tps65219.c +++ b/drivers/gpio/gpio-tps65219.c @@ -87,7 +87,7 @@ static int tps65219_gpio_get(struct gpio_chip *gc, unsigned int offset) * status bit. */ - if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) + if (gc->get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) return -ENOTSUPP; return ret; @@ -176,7 +176,7 @@ static int tps65219_gpio_direction_input(struct gpio_chip *gc, unsigned int offs return -ENOTSUPP; } - if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_IN) + if (gc->get_direction(gc, offset) == GPIO_LINE_DIRECTION_IN) return 0; return gpio->change_dir(gc, offset, GPIO_LINE_DIRECTION_IN); @@ -190,7 +190,7 @@ static int tps65219_gpio_direction_output(struct gpio_chip *gc, unsigned int off if (offset != TPS6521X_GPIO0_IDX) return 0; - if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) + if (gc->get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) return 0; return gpio->change_dir(gc, offset, GPIO_LINE_DIRECTION_OUT); From 270437f3fe62516f16482742a7762a075e7a9457 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 19 Sep 2026 19:11:00 +0200 Subject: [PATCH 2236/2366] gpio: tps65219: Fix TPS65214 GPIO direction programming GPIO_LINE_DIRECTION_OUT and GPIO_LINE_DIRECTION_IN have the values 0 and 1, respectively, while the TPS65214 GPIO_CONFIG field is BIT(1). regmap_update_bits() masks the supplied value, so passing either direction value clears the field and selects input mode. Translate the GPIO direction to the register encoding used by tps65214_gpio_get_direction(), setting GPIO_CONFIG for output and clearing it for input. Fixes: 1b6ab07c0c80 ("gpio: tps65219: Add support for TI TPS65214 PMIC") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260919171100.90430-4-kmehltretter@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tps65219.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-tps65219.c b/drivers/gpio/gpio-tps65219.c index 479a80ef7654..6958466455d0 100644 --- a/drivers/gpio/gpio-tps65219.c +++ b/drivers/gpio/gpio-tps65219.c @@ -158,8 +158,10 @@ static int tps65214_gpio_change_direction(struct gpio_chip *gc, unsigned int off if (ret) dev_err(dev, "GPIO%d configured as VSEL, not GPIO\n", offset); + val = direction == GPIO_LINE_DIRECTION_OUT ? + TPS65214_GPIO0_DIR_MASK : 0; ret = regmap_update_bits(gpio->tps->regmap, TPS65219_REG_GENERAL_CONFIG, - TPS65214_GPIO0_DIR_MASK, direction); + TPS65214_GPIO0_DIR_MASK, val); if (ret) dev_err(dev, "Fail to change direction to %u for GPIO%d.\n", direction, offset); From 5b76268dac968612f7283d59b539036de955b7d9 Mon Sep 17 00:00:00 2001 From: Vineeth Vijayan Date: Tue, 22 Sep 2026 22:48:39 +0200 Subject: [PATCH 2237/2366] s390/cio: Fix NULL pointer dereference in ccw_device_get_util_str() The channel path registry entry associated with a CHPID may be removed while the subchannel's PMCW still references that CHPID. In this case, chpid_to_chp() can return NULL, leading to a NULL pointer dereference. Add the missing NULL check before dereferencing the returned pointer. Fixes: 199652309a4d ("s390/cio: add helper to query utility strings per given ccw device") Cc: stable@vger.kernel.org Signed-off-by: Vineeth Vijayan Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/device_ops.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c index f2f7f8cba410..1f7e83fd5087 100644 --- a/drivers/s390/cio/device_ops.c +++ b/drivers/s390/cio/device_ops.c @@ -517,6 +517,8 @@ u8 *ccw_device_get_util_str(struct ccw_device *cdev, int chp_idx) chp_id_init(&chpid); chpid.id = sch->schib.pmcw.chpid[chp_idx]; chp = chpid_to_chp(chpid); + if (!chp) + return NULL; util_str = kmalloc(sizeof(chp->desc_fmt3.util_str), GFP_KERNEL); if (!util_str) From 883b776abe48fed8ef615f4ff7e91113a6c4dffb Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Tue, 22 Sep 2026 12:41:02 +0200 Subject: [PATCH 2238/2366] isofs: Fix handling of directories with tight blocks Thomas has reported that after commit b2eb2e288604 ("isofs: Drop support of directory entries straddling blocks") some isofs images of Fedora miss some entries in some directories. The problem is triggered when directory entries are packed in a block in such a way that they exactly fit the block (which BTW mkisofs doesn't do so I didn't catch this bug when testing with my images). Fix readdir and lookup code to properly transition to the next block when directory block is tightly packed. Link: https://bugzilla.redhat.com/show_bug.cgi?id=2535353 Reported-by: Thomas Schmitt Reported-by: Matthias Goergens Fixes: b2eb2e288604 ("isofs: Drop support of directory entries straddling blocks") Reviewed-by: Thomas Schmitt Signed-off-by: Jan Kara --- fs/isofs/dir.c | 20 ++++++++------------ fs/isofs/namei.c | 16 +++++++++------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/fs/isofs/dir.c b/fs/isofs/dir.c index c7ca7603e97a..28741251d56e 100644 --- a/fs/isofs/dir.c +++ b/fs/isofs/dir.c @@ -110,25 +110,21 @@ static int do_isofs_readdir(struct inode *inode, struct file *file, return 0; } - de = (struct iso_directory_record *) (bh->b_data + offset); - - de_len = *(unsigned char *)de; - + de = (struct iso_directory_record *)(bh->b_data + offset); /* - * If the length byte is zero, we should move on to the next - * CDROM sector. If we are at the end of the directory, we - * kick out of the while loop. + * If we are at the end of a block (or at its zero-padded + * tail), move on to the next CDROM sector. If we are at the + * end of the directory, we'll abort the while loop. */ - - if (de_len == 0) { + if (offset >= bufsize || de->length[0] == 0) { brelse(bh); bh = NULL; - ctx->pos = (ctx->pos + ISOFS_BLOCK_SIZE) & ~(ISOFS_BLOCK_SIZE - 1); - block = ctx->pos >> bufbits; + block++; + ctx->pos = (loff_t)block << bufbits; offset = 0; continue; } - + de_len = de->length[0]; block_saved = block; offset_saved = offset; offset += de_len; diff --git a/fs/isofs/namei.c b/fs/isofs/namei.c index 010682f5901a..4fba1bf7f016 100644 --- a/fs/isofs/namei.c +++ b/fs/isofs/namei.c @@ -74,18 +74,20 @@ isofs_find_entry(struct inode *dir, struct dentry *dentry, return 0; } - de = (struct iso_directory_record *) (bh->b_data + offset); - - de_len = *(unsigned char *) de; - if (!de_len) { + de = (struct iso_directory_record *)(bh->b_data + offset); + /* + * If we are at the end of the block or at its zero-padded + * tail, move to the next block. + */ + if (offset >= bufsize || de->length[0] == 0) { brelse(bh); bh = NULL; - f_pos = (f_pos + ISOFS_BLOCK_SIZE) & ~(ISOFS_BLOCK_SIZE - 1); - block = f_pos >> bufbits; + block++; + f_pos = block << bufbits; offset = 0; continue; } - + de_len = de->length[0]; block_saved = bh->b_blocknr; offset_saved = offset; offset += de_len; From 4409a85735cdca5c4c400c0dad1feee091872eee Mon Sep 17 00:00:00 2001 From: Liang Luo Date: Wed, 23 Sep 2026 19:07:37 +0800 Subject: [PATCH 2239/2366] sched_ext: Count SCX_EV_SUB_BYPASS_DISPATCH in the dispatch fallback When a descendant scheduler enters bypass mode, its tasks are parked in the bypass DSQs of the nearest non-bypassing ancestor, which is then responsible for running them. On behalf of such a non-bypassing host, scx_dispatch_sched() consumes those bypass DSQs from two places: the attempt made every SCX_BYPASS_HOST_NTH dispatches, and the end-of-dispatch fallback that keeps the CPU from going idle while bypassed descendants still have tasks queued. The former increments SCX_EV_SUB_BYPASS_DISPATCH but the latter does not, even though both perform the same scx_consume_dispatch_q() on the same bypass DSQ. The descendant bypass dispatches done by the fallback are therefore missing from the counter exposed via sysfs, scx_dump_state() and the scx_bpf_events() kfunc, which under-reports the actual number of such dispatches. Add the missing __scx_add_event() so the fallback counts them too. When @sch itself is bypassing, scx_dispatch_sched() takes the earlier self-bypass branch and returns before reaching these host paths; that mode is accounted for by SCX_EV_BYPASS_DISPATCH at enqueue time and is intentionally left unchanged. Fixes: 025b1bd41965 ("sched_ext: Implement hierarchical bypass mode") Signed-off-by: Liang Luo Signed-off-by: Tejun Heo --- kernel/sched/ext/inlines.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/sched/ext/inlines.h b/kernel/sched/ext/inlines.h index ed423bcc26b8..2ff5479334cb 100644 --- a/kernel/sched/ext/inlines.h +++ b/kernel/sched/ext/inlines.h @@ -129,8 +129,10 @@ scx_dispatch_sched(struct scx_sched *sch, struct rq *rq, * scheduler's ops.dispatch() doesn't yield any tasks. */ if (scx_bypass_dsp_enabled(sch) && - scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), 0)) + scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), 0)) { + __scx_add_event(sch, SCX_EV_SUB_BYPASS_DISPATCH, 1); return SCX_DSP_LOCAL; + } return SCX_DSP_NONE; } From b8d1d5b63a8ef532038eebd9d97d406860385668 Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Tue, 22 Sep 2026 13:24:55 +0900 Subject: [PATCH 2240/2366] x86/mce: Fix hardware debug register corruption on task migration In exc_machine_check_user(), local_db_save() and local_db_restore() are invoked in the outer entry stubs (DEFINE_IDTENTRY_MCE_USER, DEFINE_FREDENTRY_MCE, and DEFINE_IDTENTRY_RAW), surrounding exc_machine_check_user(). However, exc_machine_check_user() calls irqentry_exit_to_user_mode(), which handles pending thread work and may schedule() if TIF_NEED_RESCHED is set. If the task migrates to another CPU during schedule(), local_db_restore() runs on the new CPU with the dr7 state saved from the old CPU. This corrupts the new CPU's DR7 hardware debug register and leaves the old CPU's DR7 disabled. In short, local_db_save() and local_db_restore() pair must be run on the same CPU. To fix this, move local_db_save() and local_db_restore() inside exc_machine_check_user() and exc_machine_check_kernel(). In exc_machine_check_user(), DR7 is saved and restored strictly around do_machine_check() to avoid schedule() during migration. In exc_machine_check_kernel(), local_db_save() is called at the entry point to prevent early memory accesses from triggering nested #DB exceptions, and restored on all exits. Fixes: cd840e424f27 ("x86/entry, mce: Disallow #DB during #MC") Assisted-by: LLM Signed-off-by: Masami Hiramatsu (Google) Signed-off-by: Borislav Petkov (AMD) Acked-by: Peter Zijlstra (Intel) Cc: Link: https://patch.msgid.link/179005109564.388919.3937970081044095776.stgit@devnote2 --- arch/x86/kernel/cpu/mce/core.c | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c index ab469605fc89..39f238952e14 100644 --- a/arch/x86/kernel/cpu/mce/core.c +++ b/arch/x86/kernel/cpu/mce/core.c @@ -2108,6 +2108,9 @@ bool filter_mce(struct mce *m) static __always_inline void exc_machine_check_kernel(struct pt_regs *regs) { irqentry_state_t irq_state; + unsigned long dr7; + + dr7 = local_db_save(); WARN_ON_ONCE(user_mode(regs)); @@ -2116,20 +2119,26 @@ static __always_inline void exc_machine_check_kernel(struct pt_regs *regs) * mce_check_crashing_cpu() for details. */ if (mca_cfg.initialized && mce_check_crashing_cpu()) - return; + goto out; irq_state = irqentry_nmi_enter(regs); do_machine_check(regs); irqentry_nmi_exit(regs, irq_state); +out: + local_db_restore(dr7); } static __always_inline void exc_machine_check_user(struct pt_regs *regs) { + unsigned long dr7; + irqentry_enter_from_user_mode(regs); + dr7 = local_db_save(); do_machine_check(regs); + local_db_restore(dr7); irqentry_exit_to_user_mode(regs); } @@ -2138,21 +2147,13 @@ static __always_inline void exc_machine_check_user(struct pt_regs *regs) /* MCE hit kernel mode */ DEFINE_IDTENTRY_MCE(exc_machine_check) { - unsigned long dr7; - - dr7 = local_db_save(); exc_machine_check_kernel(regs); - local_db_restore(dr7); } /* The user mode variant. */ DEFINE_IDTENTRY_MCE_USER(exc_machine_check) { - unsigned long dr7; - - dr7 = local_db_save(); exc_machine_check_user(regs); - local_db_restore(dr7); } #ifdef CONFIG_X86_FRED @@ -2169,28 +2170,20 @@ DEFINE_IDTENTRY_MCE_USER(exc_machine_check) */ DEFINE_FREDENTRY_MCE(exc_machine_check) { - unsigned long dr7; - - dr7 = local_db_save(); if (user_mode(regs)) exc_machine_check_user(regs); else exc_machine_check_kernel(regs); - local_db_restore(dr7); } #endif #else /* 32bit unified entry point */ DEFINE_IDTENTRY_RAW(exc_machine_check) { - unsigned long dr7; - - dr7 = local_db_save(); if (user_mode(regs)) exc_machine_check_user(regs); else exc_machine_check_kernel(regs); - local_db_restore(dr7); } #endif From 4fde448225123442c5796f54b7a4400e2d3cbaf6 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 8 Sep 2026 14:05:59 -0500 Subject: [PATCH 2241/2366] x86/PCI: Disable enhanced atomics on AMD NBIO 7.7 and 7.11 Multiple users report data corruption during 64-bit DMA transfers on systems with AMD NBIO 7.7 and 7.11 controllers. This occurs when BIOS enables AMD "enhanced atomic operations" on PCIe Root Ports. When enhanced atomics are enabled, any 64-bit DMA access may be corrupted. Disable enhanced atomics using SMN for NBIO 7.7 and 7.11 based models. Reported-by: Mikael Etienne Closes: https://lore.kernel.org/178789300872.392066.15963676631650361573@gmail.com/ Reported-by: Arthur Husband Closes: https://lore.kernel.org/20260406222335.379935-1-artmoty@gmail.com/ Reported-by: Alvin Lim Closes: https://lore.kernel.org/20260621100844.1224301-1-alvinwylim@gmail.com/ Signed-off-by: Mario Limonciello [bhelgaas: commit log, s/IOVA/DMA/ in comment] Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org Cc: David Laight Cc: John Smith Cc: Lennert Buytenhek Cc: Niklas Cassel Cc: Roland Waltersson Link: https://patch.msgid.link/20260908190600.226485-2-mario.limonciello@amd.com --- arch/x86/pci/fixup.c | 99 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/arch/x86/pci/fixup.c b/arch/x86/pci/fixup.c index b301c6c8df75..795d81c7a4de 100644 --- a/arch/x86/pci/fixup.c +++ b/arch/x86/pci/fixup.c @@ -886,6 +886,105 @@ static void quirk_clear_strap_no_soft_reset_dev2_f0(struct pci_dev *dev) } } DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x15b8, quirk_clear_strap_no_soft_reset_dev2_f0); + +/* + * Enhanced atomic operations can cause corruption with 64-bit DMA + * on these devices. + */ +#define RX_ENH_ATOMIC_EN BIT(8) + +static const u32 nbio_7_7_pcie_smn_addrs[] = { + 0x111401d0, + 0x111411d0, + 0x111421d0, + 0x111431d0, + 0x111441d0, + 0x112401d0, + 0x112411d0, + 0x112421d0, + 0x112431d0, + 0x112441d0, + 0x112451d0, + 0x113401d0, + 0x114401d0, +}; + +static const u32 nbio_7_11_pcie_smn_addrs[] = { + 0x112401d0, + 0x112411d0, + 0x112421d0, + 0x112431d0, + 0x112441d0, + 0x112451d0, + 0x113401d0, + 0x113411d0, + 0x113421d0, + 0x113431d0, + 0x113441d0, + 0x113451d0, +}; + +static void quirk_amd_nbio_enhanced_atomic(struct pci_dev *host_bridge, + const u32 *smn_addrs, + size_t nr_smn_addrs) +{ + bool changed = false; + size_t i; + u32 data; + int ret; + + for (i = 0; i < nr_smn_addrs; i++) { + ret = amd_smn_read(0, smn_addrs[i], &data); + if (ret) + continue; + if (!(data & RX_ENH_ATOMIC_EN)) + continue; + data = data & ~RX_ENH_ATOMIC_EN; + ret = amd_smn_write(0, smn_addrs[i], data); + if (ret) + continue; + if (changed) + continue; + ret = amd_smn_read(0, smn_addrs[i], &data); + if (ret) + continue; + if (data & RX_ENH_ATOMIC_EN) + continue; + changed = true; + } + + if (changed) + pci_info(host_bridge, "enhanced atomics disabled\n"); +} + +static void quirk_amd_nbio_7_7_disable_enhanced_atomic(struct pci_dev *dev) +{ + quirk_amd_nbio_enhanced_atomic(dev, nbio_7_7_pcie_smn_addrs, + ARRAY_SIZE(nbio_7_7_pcie_smn_addrs)); +} + +static void quirk_amd_nbio_7_11_disable_enhanced_atomic(struct pci_dev *dev) +{ + quirk_amd_nbio_enhanced_atomic(dev, nbio_7_11_pcie_smn_addrs, + ARRAY_SIZE(nbio_7_11_pcie_smn_addrs)); +} + +/* Phoenix, Hawk Point (NBIO 7.7) */ +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x14E8, + quirk_amd_nbio_7_7_disable_enhanced_atomic); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x14E8, + quirk_amd_nbio_7_7_disable_enhanced_atomic); + +/* Strix, Krackan, Strix Halo (NBIO 7.11) */ +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1507, + quirk_amd_nbio_7_11_disable_enhanced_atomic); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1507, + quirk_amd_nbio_7_11_disable_enhanced_atomic); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1122, + quirk_amd_nbio_7_11_disable_enhanced_atomic); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1122, + quirk_amd_nbio_7_11_disable_enhanced_atomic); + #endif /* From 80320b278fea07ffcda3f57b67b61658e0a4e1ca Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 24 Sep 2026 01:52:03 +0800 Subject: [PATCH 2242/2366] ata: libata-scsi: bound the ATA passthru sense descriptor writes When an ATA PASS-THROUGH command to an ATAPI device fails, the sense buffer holds the device's REQUEST SENSE reply, and ata_scsi_set_passthru_sense_fields() trusts its additional length byte, sb[7], when adding the ATA Status Return descriptor. A faulty or malicious device can use that to make the kernel read and write past the 96-byte buffer in three ways: - scsi_sense_desc_find() is passed sb[7] + 8 as the buffer length, so its clamp against sb[7] does nothing and the walk runs off the end. - A type-9 descriptor found near the end is filled in unchecked. - A new descriptor at sb[8 + len] needs len + 22 bytes, not len + 14, so len 75..82 writes up to 8 bytes past the end. Reproduced with KASAN under qemu, with the emulated ATAPI REQUEST SENSE reply patched: BUG: KASAN: slab-out-of-bounds in scsi_sense_desc_find+0x1a5/0x210 BUG: KASAN: slab-out-of-bounds in ata_scsi_qc_complete+0x1a15/0x1a50 Both are gone with this patch, and a valid descriptor is still filled in. Fixes: 97981926224a ("ata: libata-scsi: Do not overwrite valid sense data when CK_COND=1") Cc: stable@vger.kernel.org Reviewed-by: Damien Le Moal Signed-off-by: Matthias Goergens Link: https://lore.kernel.org/r/20260923175203.1576825-1-matthias.goergens@gmail.com Signed-off-by: Niklas Cassel --- drivers/ata/libata-scsi.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index 7e22bbc38238..8f9aa97a519d 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -261,12 +261,18 @@ static void ata_scsi_set_passthru_sense_fields(struct ata_queued_cmd *qc) /* descriptor format */ len = sb[7]; - desc = (char *)scsi_sense_desc_find(sb, len + 8, 9); + desc = (char *)scsi_sense_desc_find(sb, SCSI_SENSE_BUFFERSIZE, 9); if (!desc) { - if (SCSI_SENSE_BUFFERSIZE < len + 14) + /* + * The descriptor is written at sb[8 + len] and is 14 + * bytes long, so it needs len + 22 bytes of buffer. + */ + if (len + 22 > SCSI_SENSE_BUFFERSIZE) return; sb[7] = len + 14; desc = sb + 8 + len; + } else if (desc - sb > SCSI_SENSE_BUFFERSIZE - 14) { + return; } desc[0] = 9; desc[1] = 12; From c5fd4eaad50d620c7e09ac2082b2fb55ee54170e Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 09:49:56 +0000 Subject: [PATCH 2243/2366] drm/amd/display: Fix dc stream excess put in dm_update_crtc_state() In dm_update_crtc_state(), when a modeset is required the newly created stream is stored in dm_new_crtc_state->stream and an extra reference is taken with dc_stream_retain(). The reference returned by create_validate_stream_for_sink() is released as an extra reference at the skip_modeset label, leaving the stream owned by the new CRTC state. If amdgpu_dm_check_crtc_color_mgmt() fails afterwards, the code jumps to the fail label which releases new_stream again. Since the extra reference was already released at skip_modeset, this drops the reference owned by dm_new_crtc_state->stream and the stream is released while the atomic state still points to it, leading to a premature free of the dc stream. Set new_stream to NULL after releasing the extra reference at the skip_modeset label so that a later goto fail cannot release the reference owned by the new CRTC state. Fixes: 7cd4b70091a5 ("drm/amd/display: Rework CRTC color management") Signed-off-by: Wentao Liang Signed-off-by: Alex Deucher (cherry picked from commit 102a47065a62dc8f6bbbb47cf082a2934282eb08) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 36d2f86f000a..91fdf3de7202 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -5583,8 +5583,10 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, skip_modeset: /* Release extra reference */ - if (new_stream) + if (new_stream) { dc_stream_release(new_stream); + new_stream = NULL; + } new_stream = NULL; /* From 3022bdfe3e6d776e9273d6892f7c193138ca0666 Mon Sep 17 00:00:00 2001 From: Prike Liang Date: Fri, 31 Jul 2026 11:44:37 +0800 Subject: [PATCH 2244/2366] drm/amdgpu: move userq fence wait out of signalling section The eviction fence suspend worker waits for every pending userq fence from inside a dma_fence_begin_signalling() critical section. Waiting on another DMA fence while responsible for signalling one violates the cross-driver fence contract and is reported by lockdep as a dma_fence_map dependency. Move the wait before dma_fence_begin_signalling(). Keep userq_mutex held so queue lifetime remains stable while inspecting last_fence. Fixes: fc61df151617 ("drm/amdgpu: annotate eviction fence signaling path") Signed-off-by: Prike Liang Reviewed-by: Vitaly Prosyak Signed-off-by: Alex Deucher (cherry picked from commit 3bd4fbc5ed89621340b5cd249869092691a9c81f) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c | 3 +++ drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 4 +--- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c index f6b7522c3c82..f8652fd0525d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c @@ -68,6 +68,9 @@ amdgpu_eviction_fence_suspend_worker(struct work_struct *work) mutex_lock(&uq_mgr->userq_mutex); + /* Fence waits are not allowed in a fence signalling critical section. */ + amdgpu_userq_wait_for_signal(uq_mgr); + /* * This is intentionally after taking the userq_mutex since we do * allocate memory while holding this lock, but only after ensuring that diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index e43bda0cab3f..280bdeb38970 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -1272,7 +1272,7 @@ amdgpu_userq_evict_all(struct amdgpu_userq_mgr *uq_mgr) return ret; } -static void +void amdgpu_userq_wait_for_signal(struct amdgpu_userq_mgr *uq_mgr) { struct amdgpu_usermode_queue *queue; @@ -1291,8 +1291,6 @@ amdgpu_userq_wait_for_signal(struct amdgpu_userq_mgr *uq_mgr) void amdgpu_userq_evict(struct amdgpu_userq_mgr *uq_mgr) { - /* Wait for any pending userqueue fence work to finish */ - amdgpu_userq_wait_for_signal(uq_mgr); amdgpu_userq_evict_all(uq_mgr); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 6412a7f7b6ef..488dc21d7c81 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -162,6 +162,7 @@ void amdgpu_userq_mgr_cancel_reset_work(struct amdgpu_device *adev); void amdgpu_userq_mgr_cancel_resume(struct amdgpu_userq_mgr *userq_mgr); void amdgpu_userq_mgr_fini(struct amdgpu_userq_mgr *userq_mgr); +void amdgpu_userq_wait_for_signal(struct amdgpu_userq_mgr *uq_mgr); void amdgpu_userq_evict(struct amdgpu_userq_mgr *uq_mgr); void amdgpu_userq_ensure_ev_fence(struct amdgpu_userq_mgr *userq_mgr, From cd195f1616b2bb5fb7765465326c4d5d64a620a0 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 17 Sep 2026 18:56:45 +0530 Subject: [PATCH 2245/2366] drm/amdgpu/userq: fix double jiffies conversion in hang detect timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Function amdgpu_userq_start_hang_detect_work() calls msecs_to_jiffies() on adev->gfx_timeout/compute_timeout/sdma_timeout before arming hang_detect_work. These timeout values already hold jiffies values from amdgpu_device_get_job_timeout_settings() at device init. This silently shrinks the real hang-detect deadline to (2 * HZ) ms instead of the intended timeout. e.g. 500ms instead of the 2000ms default on a CONFIG_HZ=250 kernel, only coincidentally correct at HZ=1000. The shortened window is easily exceeded by ordinary fence-completion latency, causing hang_detect_work to fire and trigger a per-queue or full GPU reset for queues that are not actually hung. Pass the jiffies value directly to queue_delayed_work() instead of converting it a second time. Fixes: fc3336be9c62 ("drm/amd/amdgpu: Add independent hang detect work for user queue fence") Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 13d44ca033cb74756c2aef0ade54a75cdf2f6271) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 280bdeb38970..cc8e7af18834 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -184,27 +184,27 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) void amdgpu_userq_start_hang_detect_work(struct amdgpu_usermode_queue *queue) { struct amdgpu_device *adev; - unsigned long timeout_ms; + unsigned long timeout_jiffies; adev = queue->userq_mgr->adev; /* Determine timeout based on queue type */ switch (queue->queue_type) { case AMDGPU_RING_TYPE_GFX: - timeout_ms = adev->gfx_timeout; + timeout_jiffies = adev->gfx_timeout; break; case AMDGPU_RING_TYPE_COMPUTE: - timeout_ms = adev->compute_timeout; + timeout_jiffies = adev->compute_timeout; break; case AMDGPU_RING_TYPE_SDMA: - timeout_ms = adev->sdma_timeout; + timeout_jiffies = adev->sdma_timeout; break; default: - timeout_ms = adev->gfx_timeout; + timeout_jiffies = adev->gfx_timeout; break; } queue_delayed_work(adev->reset_domain->wq, &queue->hang_detect_work, - msecs_to_jiffies(timeout_ms)); + timeout_jiffies); } void amdgpu_userq_process_fence_irq(struct amdgpu_device *adev, u32 doorbell) From f952ed353a27b46c86c9525a39b7a642850b8139 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 17 Sep 2026 18:56:45 +0530 Subject: [PATCH 2246/2366] drm/amdgpu/vcn5.0.1: fix video_timeout unit mismatch in jpeg reset wait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vcn_v5_0_1_reset_jpeg_pre_helper() passes adev->video_timeout directly to amdgpu_fence_wait_polling(), whose timeout parameter is documented and implemented in usecs (busy-wait loop decrementing by udelay(2)). adev->video_timeout is set in jiffies by amdgpu_device_get_job_timeout_settings(), via msecs_to_jiffies(). Passing it unconverted means the intended ~2s wait for outstanding JPEG fences to complete before the JPEG queue is torn down actually lasts only a couple of microseconds (HZ jiffies interpreted as usecs), so pending jobs are almost never given a real chance to finish before the reset path forces completion in the following helper. Convert the jiffies value to usecs with jiffies_to_usecs() before passing it to amdgpu_fence_wait_polling(). Fixes: fab47d2db5ca ("drm/amdgpu/vcn5.0.1: rework reset handling") Cc: Jesse.Zhang Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit b8334fec8b90ebffcaa01001a23edca9f29a05e9) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c index 1a07c3bf4425..011afc0fdc87 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c @@ -1335,7 +1335,8 @@ static int vcn_v5_0_1_reset_jpeg_pre_helper(struct amdgpu_device *adev, int inst /* if Jobs are still pending after timeout, * We'll handle them in the bottom helper */ - amdgpu_fence_wait_polling(ring, wait_seq, adev->video_timeout); + amdgpu_fence_wait_polling(ring, wait_seq, + jiffies_to_usecs(adev->video_timeout)); } return 0; From 6b13ddbf5bb8deec337f9b4887f579097a953f6a Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 17 Sep 2026 18:56:45 +0530 Subject: [PATCH 2247/2366] drm/amdgpu/vcn4.0.3: fix video_timeout unit mismatch in jpeg reset wait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vcn_v4_0_3_reset_jpeg_pre_helper() passes adev->video_timeout directly to amdgpu_fence_wait_polling(), whose timeout parameter is documented and implemented in usecs (busy-wait loop decrementing by udelay(2)). adev->video_timeout is set in jiffies by amdgpu_device_get_job_timeout_settings(), via msecs_to_jiffies(). Passing it unconverted means the intended ~2s wait for outstanding JPEG fences to complete before the JPEG queue is torn down actually lasts only a couple of microseconds (HZ jiffies interpreted as usecs), so pending jobs are almost never given a real chance to finish before the reset path forces completion in the following helper. Convert the jiffies value to usecs with jiffies_to_usecs() before passing it to amdgpu_fence_wait_polling(). Fixes: d25c67fd9d6f ("drm/amdgpu/vcn4.0.3: rework reset handling") Cc: Jesse.Zhang Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 5feabbd673c10ebee22b880e4d812f08974d2ef7) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c index 179b892fb410..62e2e04314dc 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c @@ -1689,7 +1689,8 @@ static int vcn_v4_0_3_reset_jpeg_pre_helper(struct amdgpu_device *adev, int inst /* if Jobs are still pending after timeout, * We'll handle them in the bottom helper */ - amdgpu_fence_wait_polling(ring, wait_seq, adev->video_timeout); + amdgpu_fence_wait_polling(ring, wait_seq, + jiffies_to_usecs(adev->video_timeout)); } return 0; From c3a31087b1c8df679b653c1a09d9abe5ff7ec8ef Mon Sep 17 00:00:00 2001 From: Asad Kamal Date: Fri, 28 Aug 2026 15:59:21 +0800 Subject: [PATCH 2248/2366] drm/amdkfd: fix use-after-free and multi-container gap in kfd_dev_mapping kfd_dev_mapping caches the address_space of the first /dev/kfd opener so that the GPU reset path can call unmap_mapping_range() to zap all userspace mappings of doorbell and MMIO ranges. This design has two bugs that both manifest under SRIOV with multiple containers: 1. Use-after-free / rwsem deadlock. The cached pointer refers to an inode owned by the first opener's container. When that container exits and its inode is released, kfd_dev_mapping becomes a dangling pointer. A subsequent GPU reset dereferences it inside unmap_mapping_range(), which takes i_mmap_rwsem on the freed inode, causing a hard hang observable as an uninterruptible rwsem wait. 2. Multi-container gap. Only the first opener's address_space is cached; VMAs created by later openers live in a different address_space and are never reached by unmap_mapping_range(). After a GPU reset those stale mappings keep doorbell and MMIO pages accessible to guest userspace with no GPU behind them, risking PCIe transaction timeouts and NMI panics. Fix both bugs with the same approach used by DRM core (drm_drv.c): create a private pseudo-filesystem at module init time and allocate one anonymous inode from it. In kfd_open() redirect every opener's file->f_mapping to that inode's address_space. The inode is module-owned, lives exactly as long as the amdgpu module, and collects VMAs from all openers in one address_space. A single unmap_mapping_range() call in the reset path then correctly reaches every container's mappings with no dangling pointer risk. The hang manifests as an NMI backtrace on the GPU reset workqueue stuck spinning in rwsem_down_read_slowpath() with a corrupted i_mmap_rwsem: Workqueue: amdgpu-reset-dev xgpu_ai_mailbox_flr_work [amdgpu] Call Trace: kvm_wait+0x1f/0x40 __pv_queued_spin_lock_slowpath+0x31d/0x3a0 _raw_spin_lock_irq+0x51/0x80 rwsem_down_read_slowpath+0xb3/0x550 down_read+0x48/0xd0 unmap_mapping_range+0x71/0x140 kfd_dev_unmap_mapping_range+0x5b/0x140 [amdgpu] amdgpu_amdkfd_clear_kfd_mapping+0xd8/0x190 [amdgpu] amdgpu_device_gpu_recover+0x232/0x450 [amdgpu] xgpu_ai_mailbox_flr_work+0xb5/0xc0 [amdgpu] process_one_work+0x18e/0x3e0 worker_thread+0x2e3/0x420 kthread+0x10a/0x230 Fixes: 70cadefcc616 ("drm/amdgpu: unmap all user mappings of framebuffer and doorbell before mode1 reset") Signed-off-by: Asad Kamal Reviewed-by: Lijo Lazar Signed-off-by: Alex Deucher (cherry picked from commit 1128b4a52de1572e87431de837fd9850cb99542c) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 71 +++++++++++++++++++----- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 504a286368eb..344da6c0e96a 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -70,18 +71,54 @@ static const struct class kfd_class = { }; /* - * Cache the address space of the chardev on first open so that the reset - * path can drop all userspace mappings of doorbell and MMIO ranges via - * unmap_mapping_range(). + * Private pseudo-filesystem for KFD, Provides a stable, module-owned + * inode whose address_space is the unmap target for all /dev/kfd + * openers during GPU reset. */ -static struct address_space *kfd_dev_mapping; +static struct vfsmount *kfd_fs_mnt; +static int kfd_fs_cnt; + +static int kfd_fs_init_fs_context(struct fs_context *fc) +{ + return init_pseudo(fc, 0x4b464400 /* "KFD" */) ? 0 : -ENOMEM; +} + +static struct file_system_type kfd_fs_type = { + .name = "kfd", + .init_fs_context = kfd_fs_init_fs_context, + .kill_sb = kill_anon_super, +}; + +static struct inode *kfd_fs_inode_new(void) +{ + struct inode *inode; + int r; + + r = simple_pin_fs(&kfd_fs_type, &kfd_fs_mnt, &kfd_fs_cnt); + if (r < 0) + return ERR_PTR(r); + + inode = alloc_anon_inode(kfd_fs_mnt->mnt_sb); + if (IS_ERR(inode)) + simple_release_fs(&kfd_fs_mnt, &kfd_fs_cnt); + + return inode; +} + +static void kfd_fs_inode_free(struct inode *inode) +{ + if (inode) { + iput(inode); + simple_release_fs(&kfd_fs_mnt, &kfd_fs_cnt); + } +} + +static struct inode *kfd_anon_inode; void kfd_dev_unmap_mapping_range(loff_t const holebegin, loff_t const holelen) { - struct address_space *mapping = READ_ONCE(kfd_dev_mapping); - - if (mapping) - unmap_mapping_range(mapping, holebegin, holelen, 1); + if (kfd_anon_inode) + unmap_mapping_range(kfd_anon_inode->i_mapping, holebegin, holelen, 1); } static inline struct kfd_process_device *kfd_lock_pdd_by_id(struct kfd_process *p, __u32 gpu_id) @@ -107,6 +144,13 @@ int kfd_chardev_init(void) { int err = 0; + kfd_anon_inode = kfd_fs_inode_new(); + if (IS_ERR(kfd_anon_inode)) { + err = PTR_ERR(kfd_anon_inode); + kfd_anon_inode = NULL; + return err; + } + kfd_char_dev_major = register_chrdev(0, kfd_dev_name, &kfd_fops); err = kfd_char_dev_major; if (err < 0) @@ -130,6 +174,8 @@ int kfd_chardev_init(void) err_class_create: unregister_chrdev(kfd_char_dev_major, kfd_dev_name); err_register_chrdev: + kfd_fs_inode_free(kfd_anon_inode); + kfd_anon_inode = NULL; return err; } @@ -138,6 +184,8 @@ void kfd_chardev_exit(void) device_destroy(&kfd_class, MKDEV(kfd_char_dev_major, 0)); class_unregister(&kfd_class); unregister_chrdev(kfd_char_dev_major, kfd_dev_name); + kfd_fs_inode_free(kfd_anon_inode); + kfd_anon_inode = NULL; kfd_device = NULL; } @@ -150,12 +198,7 @@ static int kfd_open(struct inode *inode, struct file *filep) if (iminor(inode) != 0) return -ENODEV; - /* - * /dev/kfd is a single chardev so all opens share one inode. Cache - * its address_space on the first open for use by the reset path. - */ - if (!READ_ONCE(kfd_dev_mapping)) - cmpxchg(&kfd_dev_mapping, NULL, inode->i_mapping); + filep->f_mapping = kfd_anon_inode->i_mapping; is_32bit_user_mode = in_compat_syscall(); From aea841bc62a76242396610d22d8ff40c13065f64 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 10:01:39 +0000 Subject: [PATCH 2249/2366] drm/amdgpu: Fix vmid_wait fence leak in amdgpu_ring_init() amdgpu_ring_init() initializes ring->vmid_wait with a reference to the stub fence taken via dma_fence_get_stub(). When a later step of the initialization fails, e.g. amdgpu_fence_driver_init_ring(), a writeback slot allocation or the ring buffer allocation, the function returns an error without releasing the stub fence reference and the reference is leaked if the ring is torn down without amdgpu_ring_fini(). Move the stub fence assignment to the end of the initialization, right before the ring is registered with the GPU scheduler, where no further failure is possible. The stub fence is only consumed by command submission handling in amdgpu_ids.c once the ring is up and running, so nothing reads it during the error-prone part of the initialization. Fixes: 48e9fbd1a284 ("drm/amdgpu: initialize the vmid_wait with the stub fence") Signed-off-by: Wentao Liang Signed-off-by: Alex Deucher (cherry picked from commit f2b96986851203e9c50ca0d13aaa3581ca3e8ebd) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c index 686c92e96025..5922406d0a03 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c @@ -254,7 +254,6 @@ int amdgpu_ring_init(struct amdgpu_device *adev, struct amdgpu_ring *ring, ring->adev = adev; ring->num_hw_submission = sched_hw_submission; ring->sched_score = sched_score; - ring->vmid_wait = dma_fence_get_stub(); ring->idx = adev->num_rings++; adev->rings[ring->idx] = ring; @@ -374,6 +373,7 @@ int amdgpu_ring_init(struct amdgpu_device *adev, struct amdgpu_ring *ring, ring->max_dw = max_dw; ring->hw_prio = hw_prio; + ring->vmid_wait = dma_fence_get_stub(); if (!ring->no_scheduler && ring->funcs->type < AMDGPU_HW_IP_NUM) { hw_ip = ring->funcs->type; From a997baa61179b450bd55c4810c7ccfed3b753a54 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 09:54:32 +0000 Subject: [PATCH 2250/2366] drm/amdgpu: Fix acpi device leak in amdgpu_acpi_enumerate_xcc() amdgpu_acpi_enumerate_xcc() looks up each XCC ACPI device with acpi_dev_get_first_match_dev(), which takes a reference to the device. The reference is dropped with acpi_dev_put() after the XCC info is initialized, but if the kzalloc_obj() allocation of the XCC info fails the function returns -ENOMEM without releasing the reference, leaking the last reference to the ACPI device. Drop the ACPI device reference on the allocation failure path before returning. Fixes: 4d5275ab0b18 ("drm/amdgpu: Add parsing of acpi xcc objects") Reviewed-by: Lijo Lazar Signed-off-by: Wentao Liang Signed-off-by: Alex Deucher (cherry picked from commit 9211ef48b31ec66999cf55e04d0cbc60cd855fd5) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c index 7f5abb03be1b..8b8acf98fdfe 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c @@ -1167,8 +1167,10 @@ int amdgpu_acpi_enumerate_xcc(void) } xcc_info = kzalloc_obj(struct amdgpu_acpi_xcc_info); - if (!xcc_info) + if (!xcc_info) { + acpi_dev_put(acpi_dev); return -ENOMEM; + } INIT_LIST_HEAD(&xcc_info->list); xcc_info->handle = acpi_device_handle(acpi_dev); From b4f7b4459b1b155e4c4977a6482b5df2cf08758c Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 09:55:36 +0000 Subject: [PATCH 2251/2366] drm/amdgpu: Fix last_update fence leak in amdgpu_vm_init() amdgpu_vm_init() initializes vm->last_update, vm->last_unlocked and vm->last_tlb_flush with references to the stub fence taken via dma_fence_get_stub(). The error label at the end of the function releases the last_unlocked and last_tlb_flush references with dma_fence_put(), but the reference stored in vm->last_update is never dropped, so whenever the page table root creation, the reservation of the root BO or the PASID registration fails, the stub fence reference leaks. Drop the vm->last_update reference together with the other stub fence references on the error path. Fixes: 187916e6ed9d ("drm/amdgpu: install stub fence into potential unused fence pointers") Signed-off-by: Wentao Liang Signed-off-by: Alex Deucher (cherry picked from commit e7979c84fc05a176bdf855ee664871b1648404c9) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index bb04101b0fb5..4a63b472f68e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -2678,6 +2678,7 @@ int amdgpu_vm_init(struct amdgpu_device *adev, struct amdgpu_vm *vm, amdgpu_bo_unref(&root_bo); error_free_delayed: + dma_fence_put(vm->last_update); dma_fence_put(vm->last_tlb_flush); dma_fence_put(vm->last_unlocked); ttm_lru_bulk_move_fini(&adev->mman.bdev, &vm->lru_bulk_move); From 2b86ab1bd6673c525adda88819d7658ba9e784ec Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 09:58:05 +0000 Subject: [PATCH 2252/2366] drm/amdgpu: Fix runtime PM leak in amdgpu_debugfs_test_ib_show() amdgpu_debugfs_test_ib_show() resumes the device with pm_runtime_get_sync() before taking the reset domain semaphore with down_write_killable(). If the write lock acquisition is interrupted, the function returns without calling pm_runtime_put_autosuspend(), leaking the runtime PM reference acquired for the device and keeping the GPU awake. Drop the runtime PM reference on the interrupted down_write_killable() error path before returning. Fixes: 6049db43d6dd ("drm/amdgpu: change reset lock from mutex to rw_semaphore") Signed-off-by: Wentao Liang Signed-off-by: Alex Deucher (cherry picked from commit ec30a576c2d4c0364549e6c04218f50704ef56c8) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index 132d054900b5..aca1a8045afa 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -1780,8 +1780,10 @@ static int amdgpu_debugfs_test_ib_show(struct seq_file *m, void *unused) /* Avoid accidently unparking the sched thread during GPU reset */ r = down_write_killable(&adev->reset_domain->sem); - if (r) + if (r) { + pm_runtime_put_autosuspend(dev->dev); return r; + } /* hold on the scheduler */ for (i = 0; i < AMDGPU_MAX_RINGS; i++) { From 0fd5e9ddf362b1253b3c94b858371f90400e5c4a Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Wed, 23 Sep 2026 11:31:18 -0600 Subject: [PATCH 2253/2366] drm/amd/display: Relax DML frame limit with UBSAN [WHY] UBSAN instrumentation adds checks and handler calls and increases stack usage in the large DML calculation functions, similar to KASAN and KCSAN. With UBSAN enabled these files exceed the default -Wframe-larger-than limit and fail to build when -Werror is in effect. Reproduced with LLVM (make LLVM=1, clang 19.1.1), CONFIG_UBSAN=y, CONFIG_GCOV_PROFILE_ALL=y and CONFIG_DRM_AMDGPU_WERROR=y on x86_64. [HOW] Include CONFIG_UBSAN in the sanitizer check that selects the higher per-file frame warning limit in the dml and dml2_0 Makefiles. Suggested-by: Leo Li Assisted-by: Copilot:Claude-Opus-5.5 Signed-off-by: Alex Hung Signed-off-by: Alex Deucher (cherry picked from commit ebf8b0fd8508b744f85a8eee82b745b1d3502dd0) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/dml/Makefile | 2 +- drivers/gpu/drm/amd/display/dc/dml2_0/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml/Makefile b/drivers/gpu/drm/amd/display/dc/dml/Makefile index 10d4ace04d4f..096520bd27c6 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml/Makefile @@ -29,7 +29,7 @@ dml_ccflags := $(CC_FLAGS_FPU) dml_rcflags := $(CC_FLAGS_NO_FPU) ifneq ($(CONFIG_FRAME_WARN),0) - ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)),y) + ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)$(CONFIG_UBSAN)),y) ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_COMPILE_TEST),yy) frame_warn_limit := 4096 else diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile index 5388bf094fbc..4d682e92df1d 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile @@ -28,7 +28,7 @@ dml2_ccflags := $(CC_FLAGS_FPU) dml2_rcflags := $(CC_FLAGS_NO_FPU) ifneq ($(CONFIG_FRAME_WARN),0) - ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)),y) + ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)$(CONFIG_UBSAN)),y) ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_COMPILE_TEST),yy) frame_warn_limit := 4096 else From 18779dd84515db093fedb4ebaf0998c9b165a5fb Mon Sep 17 00:00:00 2001 From: Ivan Lipski Date: Fri, 21 Aug 2026 00:04:53 -0400 Subject: [PATCH 2254/2366] drm/amd/display: Bump frame warning limit for clang builds of dml [Why&How] When building the DML files with clang without any sanitizer or LTO, the following -Wframe-larger-than errors break the build under CONFIG_WERROR: display_mode_vba_30.c: error: stack frame size (2512) exceeds limit (2048) in 'dml30_ModeSupportAndSystemConfigurationFull' display_mode_vba_31.c: error: stack frame size (2416) exceeds limit (2048) in 'dml31_ModeSupportAndSystemConfigurationFull' display_mode_vba_314.c: error: stack frame size (2392) exceeds limit (2048) in 'dml314_ModeSupportAndSystemConfigurationFull' Clang consistently spills more than gcc, pushing the frame past the 2048 byte limit. Apply an existing approach of increasing the warn stack size to the non-sanitizer path so plain clang builds use a 3072 byte limit. Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5642 Signed-off-by: Ivan Lipski Signed-off-by: Alex Deucher (cherry picked from commit 21711b6e66bb7b41b1aec67b2d99aafe768c8fcb) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/dml/Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml/Makefile b/drivers/gpu/drm/amd/display/dc/dml/Makefile index 096520bd27c6..79eeb3721b99 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml/Makefile @@ -36,7 +36,11 @@ ifneq ($(CONFIG_FRAME_WARN),0) frame_warn_limit := 3072 endif else - frame_warn_limit := 2048 + ifeq ($(CONFIG_CC_IS_CLANG),y) + frame_warn_limit := 3072 + else + frame_warn_limit := 2048 + endif endif ifeq ($(call test-lt, $(CONFIG_FRAME_WARN), $(frame_warn_limit)),y) From 686f942332b1667f13f3b8d6a2f50bcfbf42e277 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 15 Jul 2026 16:43:25 +0800 Subject: [PATCH 2255/2366] nfc: nfcmrvl: validate helper command length before pull The firmware download receive path removes the NCI data header and reads the helper command before validating the remaining packet length. A short frame can therefore reach the data access before the malformed packet is rejected. Validate the complete helper command length before stripping the NCI data header. Fixes: 3194c6870158 ("NFC: nfcmrvl: add firmware download support") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260715084325.40276-1-pengpeng@iscas.ac.cn Signed-off-by: David Heidelberg --- drivers/nfc/nfcmrvl/fw_dnld.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/nfc/nfcmrvl/fw_dnld.c b/drivers/nfc/nfcmrvl/fw_dnld.c index 2b8f401d8fd7..8b9d5257320d 100644 --- a/drivers/nfc/nfcmrvl/fw_dnld.c +++ b/drivers/nfc/nfcmrvl/fw_dnld.c @@ -263,9 +263,14 @@ static int process_state_fw_dnld(struct nfcmrvl_private *priv, * B8..N: payload */ - /* Remove NCI HDR */ - skb_pull(skb, 3); - if (skb->data[0] != HELPER_CMD_PACKET_FORMAT || skb->len != 5) { + if (skb->len != NCI_DATA_HDR_SIZE + 5) { + nfc_err(priv->dev, "bad command"); + return -EINVAL; + } + + /* Remove NCI header */ + skb_pull(skb, NCI_DATA_HDR_SIZE); + if (skb->data[0] != HELPER_CMD_PACKET_FORMAT) { nfc_err(priv->dev, "bad command"); return -EINVAL; } From a653c01ce447f10c36b901646888c0330363af4f Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 15 Jul 2026 16:44:05 +0800 Subject: [PATCH 2256/2366] nfc: st21nfca: validate received frame size st21nfca_hci_i2c_repack() trims a received frame at its EOF marker before removing byte stuffing. It then assumes the truncated frame contains the LLC header and two CRC bytes, and it unconditionally reads the byte after an escape marker. A malformed frame can place EOF immediately after the start marker or can end its data portion with an escape marker. The former leaves too few bytes for check_crc(), while the latter makes the unstuffing loop read past the current skb length. Require the minimum framing bytes both before and after unstuffing. Use separate input and output cursors while removing byte stuffing, and reject an escape marker without its encoded byte. This keeps malformed frames within the received frame boundary before CRC processing. Fixes: 3096e25a3e40 ("NFC: st21nfca: Fix incorrect byte stuffing revocation") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260715084405.41546-1-pengpeng@iscas.ac.cn Signed-off-by: David Heidelberg --- drivers/nfc/st21nfca/i2c.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/drivers/nfc/st21nfca/i2c.c b/drivers/nfc/st21nfca/i2c.c index a4c93ff7c5b0..0f44c783bd04 100644 --- a/drivers/nfc/st21nfca/i2c.c +++ b/drivers/nfc/st21nfca/i2c.c @@ -289,27 +289,36 @@ static int check_crc(u8 *buf, int buflen) */ static int st21nfca_hci_i2c_repack(struct sk_buff *skb) { - int i, j, r, size; + int read, write, r, size; - if (skb->len < 1 || (skb->len > 1 && skb->data[1] != 0)) + if (skb->len < ST21NFCA_FRAME_HEADROOM || + !IS_START_OF_FRAME(skb->data)) return -EBADMSG; size = get_frame_size(skb->data, skb->len); if (size > 0) { + if (size < ST21NFCA_FRAME_HEADROOM + 2) + return -EBADMSG; + skb_trim(skb, size); /* remove ST21NFCA byte stuffing for upper layer */ - for (i = 1, j = 0; i < skb->len; i++) { - if (skb->data[i + j] == + for (read = 1, write = 1; read < skb->len;) { + if (skb->data[read] == (u8) ST21NFCA_ESCAPE_BYTE_STUFFING) { - skb->data[i] = skb->data[i + j + 1] - | ST21NFCA_BYTE_STUFFING_MASK; - i++; - j++; + if (read + 1 == skb->len) + return -EBADMSG; + + skb->data[write++] = skb->data[read + 1] + | ST21NFCA_BYTE_STUFFING_MASK; + read += 2; + } else { + skb->data[write++] = skb->data[read++]; } - skb->data[i] = skb->data[i + j]; } /* remove byte stuffing useless byte */ - skb_trim(skb, i - j); + skb_trim(skb, write); + if (skb->len < ST21NFCA_FRAME_HEADROOM + 2) + return -EBADMSG; /* remove ST21NFCA_SOF_EOF from head */ skb_pull(skb, 1); From bf1460acdf8cf5a07c819f59785d40f20d113099 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Thu, 16 Jul 2026 20:26:57 -0300 Subject: [PATCH 2257/2366] nfc: llcp: Fix list corruption / refcount desync in nfc_llcp_recv_dm() nfc_llcp_recv_dm() handles DM(NOBOUND)/DM(REJ) for a socket that is still linked on local->connecting_sockets: it looks the socket up with nfc_llcp_connecting_sock_get(), sets sk->sk_state = LLCP_CLOSED and returns, without taking the socket lock and without unlinking the socket from the connecting_sockets list. llcp_sock_release() selects the list to unlink from by sk_state: a socket in LLCP_CONNECTING is unlinked from connecting_sockets, otherwise from the sockets list. Because recv_dm left the socket physically on connecting_sockets but in the LLCP_CLOSED state, release() takes the else branch and calls nfc_llcp_sock_unlink(&local->sockets, sk). That runs sk_del_node_init() while holding sockets.lock, i.e. it removes the socket from the connecting_sockets hlist under the wrong lock. A concurrent connect() linking another socket onto connecting_sockets under connecting_sockets.lock then mutates the same hlist unserialized, which corrupts the list and desyncs the sk_add_node()/sk_del_node_init() sock_hold()/__sock_put() pairing. An unprivileged local process holding LLCP sockets, with the DM supplied by the remote peer over an established LLCP link, can drive this to leak kernel sockets without bound (the mis-decrement goes through the non-freeing __sock_put() path, so the object is never released), leading to memory exhaustion / DoS. This is the same class of bug that was fixed in the sibling handler nfc_llcp_recv_cc() by commit b493ea2765cc ("nfc: llcp: Fix use-after-free race in nfc_llcp_recv_cc()"); recv_dm did not receive the equivalent fix. Fix it the same way: take lock_sock(), re-check that the socket is still hashed (release() may have won the race), and for the NOBOUND/REJ case unlink it from connecting_sockets before moving it to LLCP_CLOSED. The unlink drops the connecting_sockets membership reference via sk_del_node_init(), leaving the socket unhashed, so the later nfc_llcp_sock_unlink() in llcp_sock_release() becomes a no-op and no double put occurs. Fixes: a69f32af86e3 ("NFC: Socket linked list") Signed-off-by: Aldo Ariel Panzardo Link: https://patch.msgid.link/20260716232657.203145-1-qwe.aldo@gmail.com Signed-off-by: David Heidelberg --- net/nfc/llcp_core.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index cac1b5487064..bd6361e2efa4 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -1251,6 +1251,7 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, struct nfc_llcp_sock *llcp_sock; struct sock *sk; u8 dsap, ssap, reason; + bool connecting = false; dsap = nfc_llcp_dsap(skb); ssap = nfc_llcp_ssap(skb); @@ -1262,6 +1263,7 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, case LLCP_DM_NOBOUND: case LLCP_DM_REJ: llcp_sock = nfc_llcp_connecting_sock_get(local, dsap); + connecting = true; break; default: @@ -1276,10 +1278,33 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, sk = &llcp_sock->sk; + lock_sock(sk); + + /* Check if socket was destroyed whilst waiting for the lock */ + if (!sk_hashed(sk)) { + release_sock(sk); + nfc_llcp_sock_put(llcp_sock); + return; + } + + /* + * For DM(NOBOUND)/DM(REJ) the socket is still linked on the + * connecting_sockets list. Unlink it here, under the socket lock, + * before moving it to LLCP_CLOSED: llcp_sock_release() selects the + * list to unlink from by sk_state, so leaving a connecting socket + * in the CLOSED state would make it unlink from the wrong list and + * corrupt the connecting_sockets list / desync the socket refcount. + * This mirrors nfc_llcp_recv_cc(). + */ + if (connecting) + nfc_llcp_sock_unlink(&local->connecting_sockets, sk); + sk->sk_err = ENXIO; sk->sk_state = LLCP_CLOSED; sk->sk_state_change(sk); + release_sock(sk); + nfc_llcp_sock_put(llcp_sock); } From 092c6a605cbd6414ef499834c2e0da69c2c3388e Mon Sep 17 00:00:00 2001 From: Doruk Tan Ozturk Date: Sat, 11 Jul 2026 14:36:51 +0200 Subject: [PATCH 2258/2366] nfc: port100: reject frames whose declared length exceeds the received data port100_recv_response() passes the URB transfer buffer to port100_rx_frame_is_valid(), which checksums le16_to_cpu(frame->datalen) bytes of frame->data. datalen is a 16-bit field supplied by the device and is never checked against the number of bytes actually received (urb->actual_length), so a device reporting a datalen larger than the received frame makes port100_data_checksum() read out of bounds past the transfer buffer. Reject a response whose declared frame size does not fit the received length before validating it. Found by 0sec (https://0sec.ai) using automated source analysis; the missing bound is evident from source. Compile-tested. Fixes: 562d4d59b8a1 ("NFC: Sony Port-100 Series driver") Cc: stable@vger.kernel.org Assisted-by: 0sec:claude-opus-4-8 Signed-off-by: Doruk Tan Ozturk Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260711123651.32595-1-doruk@0sec.ai Signed-off-by: David Heidelberg --- drivers/nfc/port100.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/nfc/port100.c b/drivers/nfc/port100.c index b613f5e2fd57..e769a30b8b5c 100644 --- a/drivers/nfc/port100.c +++ b/drivers/nfc/port100.c @@ -636,6 +636,13 @@ static void port100_recv_response(struct urb *urb) in_frame = dev->in_urb->transfer_buffer; + if (urb->actual_length < PORT100_FRAME_HEADER_LEN || + urb->actual_length < port100_rx_frame_size(in_frame)) { + nfc_err(&dev->interface->dev, "Received a truncated frame\n"); + cmd->status = -EIO; + goto sched_wq; + } + if (!port100_rx_frame_is_valid(in_frame)) { nfc_err(&dev->interface->dev, "Received an invalid frame\n"); cmd->status = -EIO; From 3d8afc5243ea2ee803d98e69eb4a01748167ac1b Mon Sep 17 00:00:00 2001 From: Lei Zhu Date: Wed, 29 Jul 2026 15:24:26 +0800 Subject: [PATCH 2259/2366] selftests: nci: Correct pthread_create return value check The pthread_create() functions returns 0 on success and a positive value on failure. Modify the return value check to correctly detect failure cases. Fixes: 72696bd8a09d ("selftests: nci: Extract the start/stop discovery function") Signed-off-by: Lei Zhu Link: https://patch.msgid.link/20260729072426.303484-1-zhulei_szu@163.com Signed-off-by: David Heidelberg --- tools/testing/selftests/nci/nci_dev.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/nci/nci_dev.c b/tools/testing/selftests/nci/nci_dev.c index 312f84ee0444..c053f5cf2745 100644 --- a/tools/testing/selftests/nci/nci_dev.c +++ b/tools/testing/selftests/nci/nci_dev.c @@ -438,7 +438,7 @@ FIXTURE_SETUP(NCI) else rc = pthread_create(&thread_t, NULL, virtual_dev_open, (void *)&self->virtual_nci_fd); - ASSERT_GT(rc, -1); + ASSERT_EQ(rc, 0); rc = send_cmd_with_idx(self->sd, self->fid, self->pid, NFC_CMD_DEV_UP, self->dev_idex); @@ -509,7 +509,7 @@ FIXTURE_TEARDOWN(NCI) rc = pthread_create(&thread_t, NULL, virtual_deinit, (void *)&self->virtual_nci_fd); - ASSERT_GT(rc, -1); + ASSERT_EQ(rc, 0); rc = send_cmd_with_idx(self->sd, self->fid, self->pid, NFC_CMD_DEV_DOWN, self->dev_idex); EXPECT_EQ(rc, 0); @@ -590,7 +590,7 @@ int start_polling(int dev_idx, int proto, int virtual_fd, int sd, int fid, int p rc = pthread_create(&thread_t, NULL, virtual_poll_start, (void *)&virtual_fd); - if (rc < 0) + if (rc) return rc; rc = send_cmd_mt_nla(sd, fid, pid, NFC_CMD_START_POLL, 2, nla_start_poll_type, @@ -610,7 +610,7 @@ int stop_polling(int dev_idx, int virtual_fd, int sd, int fid, int pid) rc = pthread_create(&thread_t, NULL, virtual_poll_stop, (void *)&virtual_fd); - if (rc < 0) + if (rc) return rc; rc = send_cmd_with_idx(sd, fid, pid, @@ -830,6 +830,8 @@ int disconnect_tag(int nfc_sock, int virtual_fd) status = pthread_create(&thread_t, NULL, virtual_deactivate_proc, (void *)&virtual_fd); + if (status) + return status; close(nfc_sock); pthread_join(thread_t, (void **)&status); @@ -874,7 +876,7 @@ TEST_F(NCI, deinit) else rc = pthread_create(&thread_t, NULL, virtual_deinit, (void *)&self->virtual_nci_fd); - ASSERT_GT(rc, -1); + ASSERT_EQ(rc, 0); rc = send_cmd_with_idx(self->sd, self->fid, self->pid, NFC_CMD_DEV_DOWN, self->dev_idex); From c3eef2f988a3db9690369d7cef9a3344dd9788d3 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Wed, 2 Sep 2026 12:30:31 +0000 Subject: [PATCH 2260/2366] nfc: llcp: Fix race condition in accept_queue lifecycle In nfc_llcp_socket_release(), sockets and listener accept queues are walked under the local sockets rwlock and bh_lock_sock(). However, bh_lock_sock() does not synchronise against process-context lock_sock() held by nfc_llcp_accept_dequeue() during accept(). Because socket_release() does not check sock_owned_by_user(), both paths can concurrently unlink and release the same child socket, resulting in use-after-free or a NULL pointer dereference of child->parent in nfc_llcp_accept_unlink(). Fix this synchronisation race by having nfc_llcp_socket_release() use process-context lock_sock() instead of bh_lock_sock(): 1. Pop sockets from the local sockets list under the write lock using nfc_llcp_sock_list_pop() so lock_sock() can be acquired without holding the rwlock. 2. Because lock_sock() can sleep, defer the final release of the nfc_llcp_local structure to a workqueue (release_work). This avoids a sleeping-in-atomic bug when the last local reference is dropped from softirq context. Additionally, hold a single device reference on local from registration until final destruction. 3. In nfc_llcp_local_get(), use kref_get_unless_zero() to prevent resurrecting a local object whose teardown has been scheduled. 4. In llcp_sock_accept(), verify that the listener socket state is still LLCP_LISTEN after waking from schedule_timeout() to prevent hangs if the listener is closed concurrently. 5. When unlinking unaccepted child sockets during listener release, unlink them from local->sockets, call sock_orphan(), and drop their initial sk_alloc creation reference via sock_put(). 6. Make nfc_llcp_accept_unlink() idempotent by guarding parent access with a NULL check. Fixes: 50b78b2a6500 ("NFC: Fix sleeping in atomic when releasing socket") Signed-off-by: Lee Jones Link: https://patch.msgid.link/20260902123033.1169067-1-lee@kernel.org Signed-off-by: David Heidelberg --- net/nfc/llcp.h | 1 + net/nfc/llcp_core.c | 123 +++++++++++++++++++++++++++----------------- net/nfc/llcp_sock.c | 51 +++++++++++++----- 3 files changed, 116 insertions(+), 59 deletions(-) diff --git a/net/nfc/llcp.h b/net/nfc/llcp.h index d8345ed57c95..23ae7a0112d3 100644 --- a/net/nfc/llcp.h +++ b/net/nfc/llcp.h @@ -91,6 +91,7 @@ struct nfc_llcp_local { struct hlist_head pending_sdreqs; struct timer_list sdreq_timer; struct work_struct sdreq_timeout_work; + struct work_struct release_work; u8 sdreq_next_tid; /* sockets array */ diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index bd6361e2efa4..23553e7426ec 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -20,6 +20,8 @@ static LIST_HEAD(llcp_devices); /* Protects llcp_devices list */ static DEFINE_SPINLOCK(llcp_devices_lock); +static struct workqueue_struct *llcp_wq; + static void nfc_llcp_rx_skb(struct nfc_llcp_local *local, struct sk_buff *skb); void nfc_llcp_sock_link(struct llcp_sock_list *l, struct sock *sk) @@ -63,21 +65,33 @@ static void nfc_llcp_socket_purge(struct nfc_llcp_sock *sock) } } +static struct sock *nfc_llcp_sock_list_pop(struct llcp_sock_list *l) +{ + struct sock *sk; + + write_lock(&l->lock); + sk = sk_head(&l->head); + if (sk) { + sock_hold(sk); + sk_del_node_init(sk); + } + write_unlock(&l->lock); + + return sk; +} + static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, int err) { struct sock *sk; - struct hlist_node *tmp; struct nfc_llcp_sock *llcp_sock; skb_queue_purge(&local->tx_queue); - write_lock(&local->sockets.lock); - - sk_for_each_safe(sk, tmp, &local->sockets.head) { + while ((sk = nfc_llcp_sock_list_pop(&local->sockets))) { llcp_sock = nfc_llcp_sock(sk); - bh_lock_sock(sk); + lock_sock(sk); nfc_llcp_socket_purge(llcp_sock); @@ -91,17 +105,27 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, list_for_each_entry_safe(lsk, n, &llcp_sock->accept_queue, accept_queue) { + bool put_creation = false; + accept_sk = &lsk->sk; - bh_lock_sock(accept_sk); + lock_sock_nested(accept_sk, + SINGLE_DEPTH_NESTING); - nfc_llcp_accept_unlink(accept_sk); + if (nfc_llcp_sock(accept_sk)->parent == sk) { + nfc_llcp_accept_unlink(accept_sk); + nfc_llcp_sock_unlink(&local->sockets, accept_sk); - if (err) - accept_sk->sk_err = err; - accept_sk->sk_state = LLCP_CLOSED; - accept_sk->sk_state_change(sk); + if (err) + accept_sk->sk_err = err; + accept_sk->sk_state = LLCP_CLOSED; + accept_sk->sk_state_change(accept_sk); + sock_orphan(accept_sk); + put_creation = true; + } - bh_unlock_sock(accept_sk); + release_sock(accept_sk); + if (put_creation) + sock_put(accept_sk); /* creation ref */ } } @@ -110,23 +134,18 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, sk->sk_state = LLCP_CLOSED; sk->sk_state_change(sk); - bh_unlock_sock(sk); - - sk_del_node_init(sk); + release_sock(sk); + sock_put(sk); } - write_unlock(&local->sockets.lock); - /* If we still have a device, we keep the RAW sockets alive */ if (device == true) return; - write_lock(&local->raw_sockets.lock); - - sk_for_each_safe(sk, tmp, &local->raw_sockets.head) { + while ((sk = nfc_llcp_sock_list_pop(&local->raw_sockets))) { llcp_sock = nfc_llcp_sock(sk); - bh_lock_sock(sk); + lock_sock(sk); nfc_llcp_socket_purge(llcp_sock); @@ -135,26 +154,20 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, sk->sk_state = LLCP_CLOSED; sk->sk_state_change(sk); - bh_unlock_sock(sk); - - sk_del_node_init(sk); + release_sock(sk); + sock_put(sk); } - - write_unlock(&local->raw_sockets.lock); } static struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local) { - /* Since using nfc_llcp_local may result in usage of nfc_dev, whenever - * we hold a reference to local, we also need to hold a reference to - * the device to avoid UAF. - */ - if (!nfc_get_device(local->dev->idx)) + if (!local) return NULL; - kref_get(&local->ref); + if (kref_get_unless_zero(&local->ref)) + return local; - return local; + return NULL; } static void local_cleanup(struct nfc_llcp_local *local) @@ -172,30 +185,34 @@ static void local_cleanup(struct nfc_llcp_local *local) nfc_llcp_free_sdp_tlv_list(&local->pending_sdreqs); } +static void local_release_work(struct work_struct *work) +{ + struct nfc_llcp_local *local; + struct nfc_dev *dev; + + local = container_of(work, struct nfc_llcp_local, release_work); + dev = local->dev; + + local_cleanup(local); + kfree(local); + nfc_put_device(dev); +} + static void local_release(struct kref *ref) { struct nfc_llcp_local *local; local = container_of(ref, struct nfc_llcp_local, ref); - local_cleanup(local); - kfree(local); + queue_work(llcp_wq, &local->release_work); } int nfc_llcp_local_put(struct nfc_llcp_local *local) { - struct nfc_dev *dev; - int ret; - - if (local == NULL) + if (!local) return 0; - dev = local->dev; - - ret = kref_put(&local->ref, local_release); - nfc_put_device(dev); - - return ret; + return kref_put(&local->ref, local_release); } static struct nfc_llcp_sock *nfc_llcp_sock_get(struct nfc_llcp_local *local, @@ -1705,6 +1722,7 @@ int nfc_llcp_register_device(struct nfc_dev *ndev) INIT_WORK(&local->rx_work, nfc_llcp_rx_work); INIT_WORK(&local->timeout_work, nfc_llcp_timeout_work); + INIT_WORK(&local->release_work, local_release_work); rwlock_init(&local->sockets.lock); rwlock_init(&local->connecting_sockets.lock); @@ -1748,10 +1766,23 @@ void nfc_llcp_unregister_device(struct nfc_dev *dev) int __init nfc_llcp_init(void) { - return nfc_llcp_sock_init(); + int ret; + + llcp_wq = alloc_workqueue("nfc_llcp_wq", WQ_UNBOUND, 0); + if (!llcp_wq) + return -ENOMEM; + + ret = nfc_llcp_sock_init(); + if (ret) { + destroy_workqueue(llcp_wq); + return ret; + } + + return 0; } void nfc_llcp_exit(void) { nfc_llcp_sock_exit(); + destroy_workqueue(llcp_wq); } diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c index 5558d8a4d48b..ce6875eb58fb 100644 --- a/net/nfc/llcp_sock.c +++ b/net/nfc/llcp_sock.c @@ -392,11 +392,12 @@ void nfc_llcp_accept_unlink(struct sock *sk) pr_debug("state %d\n", sk->sk_state); - list_del_init(&llcp_sock->accept_queue); - sk_acceptq_removed(llcp_sock->parent); - llcp_sock->parent = NULL; - - sock_put(sk); + if (llcp_sock->parent) { + list_del_init(&llcp_sock->accept_queue); + sk_acceptq_removed(llcp_sock->parent); + llcp_sock->parent = NULL; + sock_put(sk); + } } void nfc_llcp_accept_enqueue(struct sock *parent, struct sock *sk) @@ -423,12 +424,20 @@ struct sock *nfc_llcp_accept_dequeue(struct sock *parent, list_for_each_entry_safe(lsk, n, &llcp_parent->accept_queue, accept_queue) { + struct nfc_llcp_local *local; + sk = &lsk->sk; - lock_sock(sk); + lock_sock_nested(sk, SINGLE_DEPTH_NESTING); if (sk->sk_state == LLCP_CLOSED) { - release_sock(sk); + local = nfc_llcp_sock(sk)->local; + nfc_llcp_accept_unlink(sk); + if (local) + nfc_llcp_sock_unlink(&local->sockets, sk); + sock_orphan(sk); + release_sock(sk); + sock_put(sk); continue; } @@ -464,7 +473,7 @@ static int llcp_sock_accept(struct socket *sock, struct socket *newsock, pr_debug("parent %p\n", sk); - lock_sock_nested(sk, SINGLE_DEPTH_NESTING); + lock_sock(sk); if (sk->sk_state != LLCP_LISTEN) { ret = -EBADFD; @@ -490,7 +499,12 @@ static int llcp_sock_accept(struct socket *sock, struct socket *newsock, release_sock(sk); timeo = schedule_timeout(timeo); - lock_sock_nested(sk, SINGLE_DEPTH_NESTING); + lock_sock(sk); + + if (sk->sk_state != LLCP_LISTEN) { + ret = -EBADFD; + break; + } } __set_current_state(TASK_RUNNING); remove_wait_queue(sk_sleep(sk), &wait); @@ -629,13 +643,24 @@ static int llcp_sock_release(struct socket *sock) list_for_each_entry_safe(lsk, n, &llcp_sock->accept_queue, accept_queue) { - accept_sk = &lsk->sk; - lock_sock(accept_sk); + bool put_creation = false; - nfc_llcp_send_disconnect(lsk); - nfc_llcp_accept_unlink(accept_sk); + accept_sk = &lsk->sk; + lock_sock_nested(accept_sk, SINGLE_DEPTH_NESTING); + + if (nfc_llcp_sock(accept_sk)->parent == sk) { + nfc_llcp_send_disconnect(lsk); + nfc_llcp_accept_unlink(accept_sk); + nfc_llcp_sock_unlink(&local->sockets, accept_sk); + + accept_sk->sk_state = LLCP_CLOSED; + sock_orphan(accept_sk); + put_creation = true; + } release_sock(accept_sk); + if (put_creation) + sock_put(accept_sk); /* creation ref */ } } From eda518d2cdb6074a0bcdfa06af291616bcb5c421 Mon Sep 17 00:00:00 2001 From: Chaithanya Lagisetty Date: Tue, 1 Sep 2026 07:06:18 +0000 Subject: [PATCH 2261/2366] selftests: nci: Fix uninitialized family ID on missing attribute get_family_id() walks the generic netlink CTRL_CMD_GETFAMILY reply looking for the CTRL_ATTR_FAMILY_ID attribute and returns the parsed value in the local variable "id". If the reply does not carry that attribute, the parsing loop never assigns "id" and the function returns an indeterminate stack value, which the caller stores in self->fid and uses for subsequent netlink requests. Initialize "id" to 0 so a missing attribute yields a deterministic (invalid) family ID instead of a garbage value. Fixes: f595cf1242f3 ("selftests: Add nci suite") Signed-off-by: Chaithanya Lagisetty Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260901070618.3299012-1-nagachaithanya9911@gmail.com Signed-off-by: David Heidelberg --- tools/testing/selftests/nci/nci_dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/nci/nci_dev.c b/tools/testing/selftests/nci/nci_dev.c index c053f5cf2745..23fd38acfcf4 100644 --- a/tools/testing/selftests/nci/nci_dev.c +++ b/tools/testing/selftests/nci/nci_dev.c @@ -182,7 +182,7 @@ static int get_family_id(int sd, __u32 pid, __u32 *event_group) } ans; struct nlattr *na; int resp_len; - __u16 id; + __u16 id = 0; int len; int rc; From 6be581aeffc215bfc77939cd59902b0dbc4af23e Mon Sep 17 00:00:00 2001 From: Chris Gellermann Date: Fri, 4 Sep 2026 11:59:15 +0200 Subject: [PATCH 2262/2366] selftests/nci: Fix out-of-bounds store on thread join The NCI test collects the exit status of its helper threads by passing the address of an int to pthread_join(): int status; ... pthread_join(thread_t, (void **) &status); pthread_join() stores a void pointer to the memory location. On 64-bit systems, a void pointer is wider than an int, so the store overruns the 4 bytes of space allocated on the stack for the integer and corrupts the adjacent stack. On our CHERI system, this caused a fault due to a capability bounds violation. Fix this by introducing a helper that joins a thread through a void pointer and converts the result back to an integer, which is what the helper threads return. While here, also fix the logic in disconnect_tag() if the helper thread creation failed. Previously, it would have joined a thread that was never created when pthread_create() failed. Fixes: f595cf1242f3 ("selftests: Add nci suite") Signed-off-by: Chris Gellermann Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260904095915.3372241-1-christian.gellermann@codasip.com Signed-off-by: David Heidelberg --- tools/testing/selftests/nci/nci_dev.c | 31 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/tools/testing/selftests/nci/nci_dev.c b/tools/testing/selftests/nci/nci_dev.c index 23fd38acfcf4..07427fa42888 100644 --- a/tools/testing/selftests/nci/nci_dev.c +++ b/tools/testing/selftests/nci/nci_dev.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -87,6 +88,16 @@ struct msgtemplate { char buf[MAX_MSG_SIZE]; }; +static int join_thread_status(pthread_t thread) +{ + void *thread_ret = NULL; + + if (pthread_join(thread, &thread_ret)) + return -1; + + return (int)(intptr_t)thread_ret; +} + static int create_nl_socket(void) { int fd; @@ -444,7 +455,7 @@ FIXTURE_SETUP(NCI) NFC_CMD_DEV_UP, self->dev_idex); EXPECT_EQ(rc, 0); - pthread_join(thread_t, (void **)&status); + status = join_thread_status(thread_t); ASSERT_EQ(status, 0); self->open_state = true; } @@ -514,7 +525,7 @@ FIXTURE_TEARDOWN(NCI) NFC_CMD_DEV_DOWN, self->dev_idex); EXPECT_EQ(rc, 0); - pthread_join(thread_t, (void **)&status); + status = join_thread_status(thread_t); ASSERT_EQ(status, 0); } @@ -585,7 +596,6 @@ int start_polling(int dev_idx, int proto, int virtual_fd, int sd, int fid, int p void *nla_start_poll_data[2] = {&dev_idx, &proto}; int nla_start_poll_len[2] = {4, 4}; pthread_t thread_t; - int status; int rc; rc = pthread_create(&thread_t, NULL, virtual_poll_start, @@ -598,14 +608,12 @@ int start_polling(int dev_idx, int proto, int virtual_fd, int sd, int fid, int p if (rc != 0) return rc; - pthread_join(thread_t, (void **)&status); - return status; + return join_thread_status(thread_t); } int stop_polling(int dev_idx, int virtual_fd, int sd, int fid, int pid) { pthread_t thread_t; - int status; int rc; rc = pthread_create(&thread_t, NULL, virtual_poll_stop, @@ -618,8 +626,7 @@ int stop_polling(int dev_idx, int virtual_fd, int sd, int fid, int pid) if (rc != 0) return rc; - pthread_join(thread_t, (void **)&status); - return status; + return join_thread_status(thread_t); } TEST_F(NCI, start_poll) @@ -834,8 +841,10 @@ int disconnect_tag(int nfc_sock, int virtual_fd) return status; close(nfc_sock); - pthread_join(thread_t, (void **)&status); - return status; + if (status) + return -1; + + return join_thread_status(thread_t); } TEST_F(NCI, t4t_tag_read) @@ -882,7 +891,7 @@ TEST_F(NCI, deinit) NFC_CMD_DEV_DOWN, self->dev_idex); EXPECT_EQ(rc, 0); - pthread_join(thread_t, (void **)&status); + status = join_thread_status(thread_t); self->open_state = 0; ASSERT_EQ(status, 0); From 51814683e28fc64eceb415962376956c3cfc75a7 Mon Sep 17 00:00:00 2001 From: Chris Gellermann Date: Fri, 4 Sep 2026 18:42:52 +0200 Subject: [PATCH 2263/2366] nfc: virtual_ncidev: Add missing ioctl compat handler The compat handler for ioctls to the virtual nci device is missing. So, nci-specific ioctls of a compat task return with -1 and errno set to ENOTTY. Add a handler. The handling of an ioctl() call of a compat task to get the index of virtual nci device (IOCTL_GET_NCIDEV_IDX) lands in the default case of the ioctl compat handler (see fs/ioctl.c): COMPAT_SYSCALL_DEFINE3(ioctl, ...) { ... default: error = do_vfs_ioctl(fd_file(f), fd, cmd, ...); if (error != -ENOIOCTLCMD) break; if (fd_file(f)->f_op->compat_ioctl) error = fd_file(f)->f_op->compat_ioctl(fd_file(f), cmd, arg); if (error == -ENOIOCTLCMD) error = -ENOTTY; ... } There, do_vfs_ioctl() returns -ENOIOCTLCMD and compat_ioctl is not set for virtual_ncidev_fops, i.e. f_op->compat_ioctl == NULL. So, the ioctl() syscall returns with -1 and errno set to ENOTTY to the compat task. To fix this, use the compat_ptr_ioctl helper for compat handling here. It shall be used for ioctls that "either ignore the argument or pass a pointer to a compatible data type". The driver's sole ioctl takes a user void pointer and copies nfc_dev->idx to it, a 4-byte integer across all ABIs. This issue has been found by running the nci_dev kernel selftest as rv64 binary on top of a CHERI kernel, where the ioctl() ends up in the ioctl compat handler, similar to a 32-bit application on top of a 64-bit kernel. Fixes: e624e6c3e777 ("nfc: Add a virtual nci device driver") Signed-off-by: Chris Gellermann Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260904164252.18351-1-christian.gellermann@codasip.com Signed-off-by: David Heidelberg --- drivers/nfc/virtual_ncidev.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nfc/virtual_ncidev.c b/drivers/nfc/virtual_ncidev.c index 8eeb447ac96e..e51c647b27eb 100644 --- a/drivers/nfc/virtual_ncidev.c +++ b/drivers/nfc/virtual_ncidev.c @@ -195,7 +195,8 @@ static const struct file_operations virtual_ncidev_fops = { .write = virtual_ncidev_write, .open = virtual_ncidev_open, .release = virtual_ncidev_close, - .unlocked_ioctl = virtual_ncidev_ioctl + .unlocked_ioctl = virtual_ncidev_ioctl, + .compat_ioctl = compat_ptr_ioctl, }; static struct miscdevice miscdev = { From 273f9d667cde649f8de9d72b1303cc2f4b658c50 Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Tue, 15 Sep 2026 19:54:27 +0100 Subject: [PATCH 2264/2366] nfc: llcp: drop truncated I/RR/RNR PDUs in nfc_llcp_recv_hdlc() nfc_llcp_recv_hdlc() reads the sequence byte skb->data[2], via nfc_llcp_ns()/nfc_llcp_nr(), before any length check. The receive path only guarantees the two-byte LLCP header -- __nfc_llcp_recv() checks it with pskb_may_pull() and nfc_llcp_recv_agf() admits two-byte inner PDUs -- so a two-byte I, RR or RNR PDU reads one byte of uninitialised skb tailroom. The byte becomes N(R)/N(S); a peer can already set those with a well-formed PDU, so this is acting on uninitialised memory, not new peer control. Guard the read with pskb_may_pull(), as commit 95674f506c63 ("nfc: llcp: reject PDUs shorter than the LLCP header") did for the two-byte header, so the sequence byte is present and linear before it is read. RR and RNR PDUs are LLCP_HEADER_SIZE + LLCP_SEQUENCE_SIZE bytes and an I PDU is longer, so no valid frame is rejected; a truncated PDU is malformed, so return without a DM reply. Fixes: d646960f7986 ("NFC: Initial LLCP support") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Aamir Ahmed Reviewed-by: Simon Horman Link: https://patch.msgid.link/AS8P251MB0001789BBF04B72745C7D96BC8BA2@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM Signed-off-by: David Heidelberg --- net/nfc/llcp_core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index 23553e7426ec..5017f6aa57a0 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -1091,6 +1091,9 @@ static void nfc_llcp_recv_hdlc(struct nfc_llcp_local *local, struct sock *sk; u8 dsap, ssap, ptype, ns, nr; + if (!pskb_may_pull(skb, LLCP_HEADER_SIZE + LLCP_SEQUENCE_SIZE)) + return; + ptype = nfc_llcp_ptype(skb); dsap = nfc_llcp_dsap(skb); ssap = nfc_llcp_ssap(skb); From 66f4300206b82b0b143ef0d9be90cd8d29f23a47 Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Mon, 14 Sep 2026 19:11:29 +0700 Subject: [PATCH 2265/2366] nfc: llcp: fix sdreq TLV list leak on parse/alloc/send failure nfc_genl_llc_sdreq() builds a list of TLV nodes while walking nested netlink attrs, but 3 error paths (nested-attr parse failure, TLV alloc ENOMEM, nfc_llcp_send_snl_sdreq() failure) all skip freeing what was already queued. Route them through a new free_list label, mirroring the SDRES path in the same file which already does this. Harmless on the success path too -- send_snl_sdreq() drains the list as it moves nodes, so it's already empty by the time free_list runs. Fixes: d9b8d8e19b07 ("NFC: llcp: Service Name Lookup netlink interface") Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260914121129.2098606-1-congnt264@gmail.com Signed-off-by: David Heidelberg --- net/nfc/netlink.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/net/nfc/netlink.c b/net/nfc/netlink.c index 0c58824cb150..224bdfa2dd0d 100644 --- a/net/nfc/netlink.c +++ b/net/nfc/netlink.c @@ -1181,7 +1181,7 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) if (rc != 0) { rc = -EINVAL; - goto put_local; + goto free_list; } if (!sdp_attrs[NFC_SDP_ATTR_URI]) @@ -1200,7 +1200,7 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) sdreq = nfc_llcp_build_sdreq_tlv(tid, uri, uri_len); if (sdreq == NULL) { rc = -ENOMEM; - goto put_local; + goto free_list; } tlvs_len += sdreq->tlv_len; @@ -1215,6 +1215,9 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) rc = nfc_llcp_send_snl_sdreq(local, &sdreq_list, tlvs_len); +free_list: + nfc_llcp_free_sdp_tlv_list(&sdreq_list); + put_local: nfc_llcp_local_put(local); From d2acbde7e67df44efa8f0963462d1192e7694ffc Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 00:26:25 -0400 Subject: [PATCH 2266/2366] nfc: trf7970a: power down on startup RX gain failure trf7970a_startup() powers up the device before applying the optional RX gain reduction. If the register read or write fails, it returns without undoing that power-up. Probe's unwind only drops the separate regulator references acquired by probe, leaving the additional VIN enable from startup unbalanced. The system resume caller also has no power-down on this error. Call trf7970a_power_down() before returning the RX gain error to deassert the enable GPIOs, release the startup VIN reference and restore the powered-off state. Runtime PM has not been enabled yet, so the full shutdown helper is not appropriate here. Preserve the original SPI error. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 5d69351820ea ("NFC: trf7970a: Create device-tree parameter for RX gain reduction") Cc: stable@vger.kernel.org Assisted-by: OpenAI:GPT-5.6 Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Paul Geurts Link: https://patch.msgid.link/20260913042625.31296-1-mhun512@gmail.com Signed-off-by: David Heidelberg --- drivers/nfc/trf7970a.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c index 60883001fa5d..ddfc58c29964 100644 --- a/drivers/nfc/trf7970a.c +++ b/drivers/nfc/trf7970a.c @@ -1997,8 +1997,10 @@ static int trf7970a_startup(struct trf7970a *trf) return ret; ret = trf7970a_update_rx_gain_reduction(trf); - if (ret) + if (ret) { + trf7970a_power_down(trf); return ret; + } pm_runtime_set_active(trf->dev); pm_runtime_enable(trf->dev); From dcab71a7011918f6fdba7adcec02d217dcb84b8d Mon Sep 17 00:00:00 2001 From: Luxiao Xu Date: Wed, 9 Sep 2026 13:19:24 +0800 Subject: [PATCH 2267/2366] nfc: fix use-after-free in nfc_get_local_general_bytes Commit 6709d4b7bc2e ("net: nfc: Fix use-after-free caused by nfc_llcp_find_local") attempted to fix a use-after-free (UAF) issue by invoking nfc_llcp_local_put(local) after accessing local->gb. However, if the reference count drops to zero, local is freed immediately, leading to a use-after-free when callers access the returned pointer. Alternative approaches using dynamic allocation (e.g. kmemdup) introduced memory leaks because callers consistently treat the returned pointer as borrowed memory. Fix this properly by refactoring nfc_llcp_general_bytes() and nfc_get_local_general_bytes() to accept a caller-provided output buffer (out_gb) and its maximum length (gb_max_len). The general bytes are safely copied into out_gb before calling nfc_llcp_local_put(local), ensuring safe lifetime management without ownership transfer complications. Update all callers across drivers (microread, pn533, pn544, st21nfca, digital_dep, and nci) to provide their own destination buffers and pass them to nfc_get_local_general_bytes(). Fixes: 6709d4b7bc2e ("net: nfc: Fix use-after-free caused by nfc_llcp_find_local") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Luxiao Xu Signed-off-by: Ren Wei Reviewed-by: Simon Horman Link: https://patch.msgid.link/3cbaac3bee23f8ff3a3284ed32d347696eb1d208.1788841683.git.rakukuip@gmail.com Signed-off-by: David Heidelberg --- drivers/nfc/microread/microread.c | 6 +++--- drivers/nfc/pn533/pn533.c | 14 ++++++++------ drivers/nfc/pn533/pn533.h | 4 +++- drivers/nfc/pn544/pn544.c | 7 +++---- drivers/nfc/st21nfca/core.c | 8 ++++---- include/net/nfc/hci.h | 2 +- include/net/nfc/nfc.h | 3 ++- net/nfc/core.c | 15 +++++++-------- net/nfc/digital_dep.c | 8 ++++---- net/nfc/llcp_core.c | 17 +++++++++++++---- net/nfc/nci/core.c | 10 +++++----- net/nfc/nfc.h | 3 ++- 12 files changed, 55 insertions(+), 42 deletions(-) diff --git a/drivers/nfc/microread/microread.c b/drivers/nfc/microread/microread.c index dfa2490db545..2bfafa94e83d 100644 --- a/drivers/nfc/microread/microread.c +++ b/drivers/nfc/microread/microread.c @@ -251,9 +251,9 @@ static int microread_start_poll(struct nfc_hci_dev *hdev, param[1] |= (1 << 1); if ((im_protocols | tm_protocols) & NFC_PROTO_NFC_DEP_MASK) { - hdev->gb = nfc_get_local_general_bytes(hdev->ndev, - &hdev->gb_len); - if (hdev->gb == NULL || hdev->gb_len == 0) { + nfc_get_local_general_bytes(hdev->ndev, hdev->gb, + sizeof(hdev->gb), &hdev->gb_len); + if (hdev->gb_len == 0) { im_protocols &= ~NFC_PROTO_NFC_DEP_MASK; tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK; } diff --git a/drivers/nfc/pn533/pn533.c b/drivers/nfc/pn533/pn533.c index f5a6a7c20d5a..b0133e51dce9 100644 --- a/drivers/nfc/pn533/pn533.c +++ b/drivers/nfc/pn533/pn533.c @@ -1357,10 +1357,11 @@ static int pn533_poll_dep(struct nfc_dev *nfc_dev) u8 *next, nfcid3[NFC_NFCID3_MAXSIZE]; u8 passive_data[PASSIVE_DATA_LEN] = {0x00, 0xff, 0xff, 0x00, 0x3}; - if (!dev->gb) { - dev->gb = nfc_get_local_general_bytes(nfc_dev, &dev->gb_len); - - if (!dev->gb || !dev->gb_len) { + if (!dev->gb_len) { + nfc_get_local_general_bytes(nfc_dev, dev->gb, + sizeof(dev->gb), + &dev->gb_len); + if (!dev->gb_len) { dev->poll_dep = 0; queue_work(dev->wq, &dev->rf_work); } @@ -1658,8 +1659,9 @@ static int pn533_start_poll(struct nfc_dev *nfc_dev, } if (tm_protocols) { - dev->gb = nfc_get_local_general_bytes(nfc_dev, &dev->gb_len); - if (dev->gb == NULL) + nfc_get_local_general_bytes(nfc_dev, dev->gb, + sizeof(dev->gb), &dev->gb_len); + if (dev->gb_len == 0) tm_protocols = 0; } diff --git a/drivers/nfc/pn533/pn533.h b/drivers/nfc/pn533/pn533.h index 09e35b8693f5..5ab668e05121 100644 --- a/drivers/nfc/pn533/pn533.h +++ b/drivers/nfc/pn533/pn533.h @@ -6,6 +6,8 @@ * Copyright (C) 2012-2013 Tieto Poland */ +#include + #define PN533_DEVICE_STD 0x1 #define PN533_DEVICE_PASORI 0x2 #define PN533_DEVICE_ACR122U 0x3 @@ -166,7 +168,7 @@ struct pn533 { struct timer_list listen_timer; int cancel_listen; - u8 *gb; + u8 gb[NFC_MAX_GT_LEN]; size_t gb_len; u8 tgt_available_prots; diff --git a/drivers/nfc/pn544/pn544.c b/drivers/nfc/pn544/pn544.c index 9d0a16ac465e..c4fa70e45c14 100644 --- a/drivers/nfc/pn544/pn544.c +++ b/drivers/nfc/pn544/pn544.c @@ -377,10 +377,9 @@ static int pn544_hci_start_poll(struct nfc_hci_dev *hdev, return r; if ((im_protocols | tm_protocols) & NFC_PROTO_NFC_DEP_MASK) { - hdev->gb = nfc_get_local_general_bytes(hdev->ndev, - &hdev->gb_len); - pr_debug("generate local bytes %p\n", hdev->gb); - if (hdev->gb == NULL || hdev->gb_len == 0) { + nfc_get_local_general_bytes(hdev->ndev, hdev->gb, + sizeof(hdev->gb), &hdev->gb_len); + if (hdev->gb_len == 0) { im_protocols &= ~NFC_PROTO_NFC_DEP_MASK; tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK; } diff --git a/drivers/nfc/st21nfca/core.c b/drivers/nfc/st21nfca/core.c index fd39a05c9622..6bfeb8e7ed89 100644 --- a/drivers/nfc/st21nfca/core.c +++ b/drivers/nfc/st21nfca/core.c @@ -351,10 +351,10 @@ static int st21nfca_hci_start_poll(struct nfc_hci_dev *hdev, if (r < 0) return r; } else { - hdev->gb = nfc_get_local_general_bytes(hdev->ndev, - &hdev->gb_len); - - if (hdev->gb == NULL || hdev->gb_len == 0) { + nfc_get_local_general_bytes(hdev->ndev, hdev->gb, + sizeof(hdev->gb), + &hdev->gb_len); + if (hdev->gb_len == 0) { im_protocols &= ~NFC_PROTO_NFC_DEP_MASK; tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK; } diff --git a/include/net/nfc/hci.h b/include/net/nfc/hci.h index 756c11084f65..86ed63e5d533 100644 --- a/include/net/nfc/hci.h +++ b/include/net/nfc/hci.h @@ -144,7 +144,7 @@ struct nfc_hci_dev { data_exchange_cb_t async_cb; void *async_cb_context; - u8 *gb; + u8 gb[NFC_MAX_GT_LEN]; size_t gb_len; unsigned long quirks; diff --git a/include/net/nfc/nfc.h b/include/net/nfc/nfc.h index c54df042db6b..bcafab5c53e5 100644 --- a/include/net/nfc/nfc.h +++ b/include/net/nfc/nfc.h @@ -273,7 +273,8 @@ struct sk_buff *nfc_alloc_recv_skb(unsigned int size, gfp_t gfp); int nfc_set_remote_general_bytes(struct nfc_dev *dev, const u8 *gt, u8 gt_len); -u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, size_t *gb_len); +u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, u8 *out_gb, + size_t gb_max_len, size_t *gb_len); int nfc_fw_download_done(struct nfc_dev *dev, const char *firmware_name, u32 result); diff --git a/net/nfc/core.c b/net/nfc/core.c index a92a6566e6a0..f521669293f0 100644 --- a/net/nfc/core.c +++ b/net/nfc/core.c @@ -279,10 +279,10 @@ static struct nfc_target *nfc_find_target(struct nfc_dev *dev, u32 target_idx) int nfc_dep_link_up(struct nfc_dev *dev, int target_index, u8 comm_mode) { - int rc = 0; - u8 *gb; - size_t gb_len; struct nfc_target *target; + u8 gb[NFC_MAX_GT_LEN]; + size_t gb_len = 0; + int rc = 0; pr_debug("dev_name=%s comm %d\n", dev_name(&dev->dev), comm_mode); @@ -301,7 +301,7 @@ int nfc_dep_link_up(struct nfc_dev *dev, int target_index, u8 comm_mode) goto error; } - gb = nfc_llcp_general_bytes(dev, &gb_len); + nfc_get_local_general_bytes(dev, gb, sizeof(gb), &gb_len); if (gb_len > NFC_MAX_GT_LEN) { rc = -EINVAL; goto error; @@ -644,11 +644,10 @@ int nfc_set_remote_general_bytes(struct nfc_dev *dev, const u8 *gb, u8 gb_len) } EXPORT_SYMBOL(nfc_set_remote_general_bytes); -u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, size_t *gb_len) +u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, u8 *out_gb, + size_t gb_max_len, size_t *gb_len) { - pr_debug("dev_name=%s\n", dev_name(&dev->dev)); - - return nfc_llcp_general_bytes(dev, gb_len); + return nfc_llcp_general_bytes(dev, out_gb, gb_max_len, gb_len); } EXPORT_SYMBOL(nfc_get_local_general_bytes); diff --git a/net/nfc/digital_dep.c b/net/nfc/digital_dep.c index 3982fa084737..968547c306a5 100644 --- a/net/nfc/digital_dep.c +++ b/net/nfc/digital_dep.c @@ -1490,14 +1490,14 @@ static int digital_tg_send_atr_res(struct nfc_digital_dev *ddev, struct digital_atr_req *atr_req) { struct digital_atr_res *atr_res; + u8 gb[NFC_MAX_GT_LEN]; struct sk_buff *skb; - u8 *gb, payload_bits; + u8 payload_bits; size_t gb_len; int rc; - gb = nfc_get_local_general_bytes(ddev->nfc_dev, &gb_len); - if (!gb) - gb_len = 0; + nfc_get_local_general_bytes(ddev->nfc_dev, gb, sizeof(gb), + &gb_len); skb = digital_skb_alloc(ddev, sizeof(struct digital_atr_res) + gb_len); if (!skb) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index 5017f6aa57a0..5ce3ce64baf2 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -652,23 +652,32 @@ static int nfc_llcp_build_gb(struct nfc_llcp_local *local) return ret; } -u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, size_t *general_bytes_len) +u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, u8 *out_gb, size_t gb_max_len, + size_t *general_bytes_len) { struct nfc_llcp_local *local; + if (!out_gb || !general_bytes_len) + return NULL; + local = nfc_llcp_find_local(dev); - if (local == NULL) { + if (!local) { *general_bytes_len = 0; return NULL; } nfc_llcp_build_gb(local); - *general_bytes_len = local->gb_len; + if (local->gb_len) { + *general_bytes_len = min_t(size_t, local->gb_len, gb_max_len); + memcpy(out_gb, local->gb, *general_bytes_len); + } else { + *general_bytes_len = 0; + } nfc_llcp_local_put(local); - return local->gb; + return out_gb; } int nfc_llcp_set_remote_gb(struct nfc_dev *dev, const u8 *gb, u8 gb_len) diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c index 5f46c4b5720f..73e3a96470ac 100644 --- a/net/nfc/nci/core.c +++ b/net/nfc/nci/core.c @@ -780,15 +780,15 @@ static int nci_set_local_general_bytes(struct nfc_dev *nfc_dev) { struct nci_dev *ndev = nfc_get_drvdata(nfc_dev); struct nci_set_config_param param; + u8 gb[NFC_MAX_GT_LEN]; int rc; - param.val = nfc_get_local_general_bytes(nfc_dev, ¶m.len); - if ((param.val == NULL) || (param.len == 0)) + nfc_get_local_general_bytes(nfc_dev, gb, sizeof(gb), + ¶m.len); + if (param.len == 0) return 0; - if (param.len > NFC_MAX_GT_LEN) - return -EINVAL; - + param.val = gb; param.id = NCI_PN_ATR_REQ_GEN_BYTES; rc = nci_request(ndev, nci_set_config_req, ¶m, diff --git a/net/nfc/nfc.h b/net/nfc/nfc.h index 0b1e6466f4fb..82c5dfdad10e 100644 --- a/net/nfc/nfc.h +++ b/net/nfc/nfc.h @@ -49,7 +49,8 @@ void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx, int nfc_llcp_register_device(struct nfc_dev *dev); void nfc_llcp_unregister_device(struct nfc_dev *dev); int nfc_llcp_set_remote_gb(struct nfc_dev *dev, const u8 *gb, u8 gb_len); -u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, size_t *general_bytes_len); +u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, u8 *out_gb, size_t gb_max_len, + size_t *general_bytes_len); int nfc_llcp_data_received(struct nfc_dev *dev, struct sk_buff *skb); struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev); int nfc_llcp_local_put(struct nfc_llcp_local *local); From 7f2ea5ed588c03d481f0301e6c3d4240132383fb Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 21:29:58 +0800 Subject: [PATCH 2268/2366] nfc: st21nfca: validate ISO15693 inventory length The ISO15693 inventory helper removes a two-byte prefix without checking that it exists, then accepts a one-byte remainder before reading data[1] as the DSFID. Require the prefix and at least two remaining bytes before copying the UID data and reading the DSFID. Fixes: 7974728094d3 ("NFC: st21nfca: Add ISO15693 Reader/Writer support") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830132958.6397-1-pengpeng@iscas.ac.cn Signed-off-by: David Heidelberg --- drivers/nfc/st21nfca/core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/nfc/st21nfca/core.c b/drivers/nfc/st21nfca/core.c index 6bfeb8e7ed89..b5c1ca3acfbe 100644 --- a/drivers/nfc/st21nfca/core.c +++ b/drivers/nfc/st21nfca/core.c @@ -577,9 +577,7 @@ static int st21nfca_get_iso15693_inventory(struct nfc_hci_dev *hdev, if (r < 0) goto exit; - skb_pull(inventory_skb, 2); - - if (inventory_skb->len == 0 || + if (!skb_pull(inventory_skb, 2) || inventory_skb->len < 2 || inventory_skb->len > NFC_ISO15693_UID_MAXSIZE) { r = -EPROTO; goto exit; From c04981e42d94f39c1dba965cc462a046e946a6c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Mete=20Kaya?= Date: Wed, 9 Sep 2026 15:16:22 +0300 Subject: [PATCH 2269/2366] nfc: llcp: fix -ENOMEM on connect with zero-length service name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When service_name_len is 0, kmemdup() returns ZERO_SIZE_PTR which passes the NULL check, causing nfc_llcp_send_connect() to attempt building a zero-length service name TLV and fail with -ENOMEM. Fix by setting service_name to NULL directly when service_name_len is 0. Fixes: d646960f7986 ("NFC: Initial LLCP support") Signed-off-by: Ömer Mete Kaya Link: https://patch.msgid.link/20260909122029.34081-1-omermetekaya0@gmail.com Signed-off-by: David Heidelberg --- net/nfc/llcp_sock.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c index ce6875eb58fb..1e5ee4bcde68 100644 --- a/net/nfc/llcp_sock.c +++ b/net/nfc/llcp_sock.c @@ -759,12 +759,16 @@ static int llcp_sock_connect(struct socket *sock, struct sockaddr_unsized *_addr llcp_sock->service_name_len = min_t(unsigned int, addr->service_name_len, NFC_LLCP_MAX_SERVICE_NAME); - llcp_sock->service_name = kmemdup(addr->service_name, - llcp_sock->service_name_len, - GFP_KERNEL); - if (!llcp_sock->service_name) { - ret = -ENOMEM; - goto sock_llcp_release; + if (llcp_sock->service_name_len == 0) { + llcp_sock->service_name = NULL; + } else { + llcp_sock->service_name = kmemdup(addr->service_name, + llcp_sock->service_name_len, + GFP_KERNEL); + if (!llcp_sock->service_name) { + ret = -ENOMEM; + goto sock_llcp_release; + } } nfc_llcp_sock_link(&local->connecting_sockets, sk); From 408cff6bd60636df201274d320edfdfde9ed41db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Mete=20Kaya?= Date: Wed, 9 Sep 2026 15:14:31 +0300 Subject: [PATCH 2270/2366] nfc: llcp: fix WKS SAP hijacking via prefix match in nfc_llcp_wks_sap() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nfc_llcp_wks_sap() compares only service_name_len bytes, so a short service_name like "u" matches longer WKS strings like "urn:nfc:sn:snep". Fix by requiring exact length match before strncmp(). Fixes: d646960f7986 ("NFC: Initial LLCP support") Signed-off-by: Ömer Mete Kaya Link: https://patch.msgid.link/20260909121437.33744-1-omermetekaya0@gmail.com Signed-off-by: David Heidelberg --- net/nfc/llcp_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index 5ce3ce64baf2..def712e1b2b2 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -369,7 +369,8 @@ static int nfc_llcp_wks_sap(const char *service_name, size_t service_name_len) if (wks[sap] == NULL) continue; - if (strncmp(wks[sap], service_name, service_name_len) == 0) + if (strlen(wks[sap]) == service_name_len && + !strncmp(wks[sap], service_name, service_name_len)) return sap; } From 7dcf371a35632f035baf77bcf2c129165f772ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Mete=20Kaya?= Date: Tue, 8 Sep 2026 19:18:01 +0300 Subject: [PATCH 2271/2366] nfc: llcp: fix slab-out-of-bounds reads when logging service names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nfc_llcp_wks_sap() and nfc_llcp_build_sdreq_tlv() pass non-null- terminated strings to pr_debug() using the %s format specifier. The buffers are allocated via kmemdup() or come from netlink attributes and are not guaranteed to be null-terminated, causing __dynamic_pr_debug() to read beyond the allocated region: KASAN: slab-out-of-bounds Read in __dynamic_pr_debug Fix both call sites by using %.*s with the explicit length to limit the output to the actual length of the string. Fixes: d9b8d8e19b07 ("NFC: llcp: Service Name Lookup netlink interface") Reported-by: syzbot+1e3df0852e82c21ca418@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1e3df0852e82c21ca418 Signed-off-by: Ömer Mete Kaya Link: https://patch.msgid.link/20260908161952.731468-1-omermetekaya0@gmail.com Signed-off-by: David Heidelberg --- net/nfc/llcp_commands.c | 2 +- net/nfc/llcp_core.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/nfc/llcp_commands.c b/net/nfc/llcp_commands.c index ca89fe967d6a..80a00938c869 100644 --- a/net/nfc/llcp_commands.c +++ b/net/nfc/llcp_commands.c @@ -135,7 +135,7 @@ struct nfc_llcp_sdp_tlv *nfc_llcp_build_sdreq_tlv(u8 tid, const char *uri, { struct nfc_llcp_sdp_tlv *sdreq; - pr_debug("uri: %s, len: %zu\n", uri, uri_len); + pr_debug("uri: %.*s, len: %zu\n", (int)uri_len, uri, uri_len); /* sdreq->tlv_len is u8, takes uri_len, + 3 for header, + 1 for NULL */ if (WARN_ON_ONCE(uri_len > U8_MAX - 4)) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index def712e1b2b2..74bf817007cf 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -358,7 +358,7 @@ static int nfc_llcp_wks_sap(const char *service_name, size_t service_name_len) { int sap, num_wks; - pr_debug("%s\n", service_name); + pr_debug("%.*s\n", (int)service_name_len, service_name); if (service_name == NULL) return -EINVAL; From b61732f47316d45f27706db7812950145d3327b5 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Wed, 23 Sep 2026 09:26:27 +0530 Subject: [PATCH 2272/2366] nfc: pn533: fix OOB read in pn533_acr122_is_rx_frame_valid() frame->ccid.datalen is read directly from the USB response frame and used, unchecked, as an index into frame->data[]. A malicious or malfunctioning device can set this field to an arbitrary value, causing the driver to read far outside the received buffer. Bound ccid.datalen against the maximum possible ACR122 frame size before using it. This replaces the existing datalen == 0 check, since datalen < 2 already covers that case and additionally rejects datalen == 1, which would still underflow the "datalen - 2" offset used below. Fixes: 9815c7cf22da ("NFC: pn533: Separate physical layer from the core implementation") Reported-by: syzbot+1853daab1a47603d4678@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1853daab1a47603d4678 Tested-by: syzbot+1853daab1a47603d4678@syzkaller.appspotmail.com Assisted-by: LLM Signed-off-by: Deepanshu Kartikey Link: https://patch.msgid.link/20260923035627.6210-1-kartikey406@gmail.com Signed-off-by: David Heidelberg --- drivers/nfc/pn533/usb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/nfc/pn533/usb.c b/drivers/nfc/pn533/usb.c index efb07f944fce..972eaac09e59 100644 --- a/drivers/nfc/pn533/usb.c +++ b/drivers/nfc/pn533/usb.c @@ -319,7 +319,9 @@ static bool pn533_acr122_is_rx_frame_valid(void *_frame, struct pn533 *dev) if (frame->ccid.type != 0x83) return false; - if (!frame->ccid.datalen) + if (frame->ccid.datalen < 2 || + frame->ccid.datalen > PN533_ACR122_FRAME_MAX_PAYLOAD_LEN + + PN533_ACR122_RX_FRAME_TAIL_LEN) return false; if (frame->data[frame->ccid.datalen - 2] == 0x63) From cfa165cbfbed9d0f4bbc22fef4309f595a3ab187 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Sun, 20 Sep 2026 14:07:01 -0300 Subject: [PATCH 2273/2366] net/sched: act_gate: budget the per-entry list in get_fill_size tcf_gate_get_fill_size returns only the TCA_GATE_PARMS size, but tcf_gate_dump also emits three 64-bit timestamps, the clock id, flags, priority and the variable-length TCA_GATE_ENTRY_LIST nest. The per-entry nest is unbounded: parse_gate_list places no cap on the number of sched-entries, so a gate with many entries can push the real dump well past the skb that tca_get_fill allocates from this size. RTM_NEWACTION then fails the add-notify with -EINVAL while the action is already committed to the IDR, and a subsequent RTM_GETACTION on the installed gate also returns -EINVAL because its dump no longer fits. Fix this by accounting for the missing fields in tcf_gate_get_fill_size along with all elements in the entries list. Note that sizing the reply from the action lets an oversized gate install cleanly for the first time: with the input unbounded by parse_gate_list, the sized skb can now grow well above NLMSG_GOODSIZE per netlink request (a transient GFP_KERNEL allocation reachable only with namespace-local CAP_NET_ADMIN). Overload from a malicious netns admin is hardening material, not net, per the discussion at https://lore.kernel.org/netdev/20260914191108.55a1a4f1@kernel.org/; a follow-up patch for net-next will cap the sched-entry count. Fixes: 4e76e75d6aba ("net sched actions: calculate add/delete event message size") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260824153903.4143642-1-victor@mojatatu.com Tested-by: hybris Co-developed-by: Jamal Hadi Salim Signed-off-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/QDISC-3BLH.v1.20260914203033@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/act_gate.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/net/sched/act_gate.c b/net/sched/act_gate.c index 5d228a402204..6d6d45e03c07 100644 --- a/net/sched/act_gate.c +++ b/net/sched/act_gate.c @@ -681,7 +681,35 @@ static void tcf_gate_stats_update(struct tc_action *a, u64 bytes, u64 packets, static size_t tcf_gate_get_fill_size(const struct tc_action *act) { - return nla_total_size(sizeof(struct tc_gate)); + struct tcf_gate *gact = to_gate(act); + const struct tcf_gate_params *p; + struct tcfg_gate_entry *entry; + size_t size = nla_total_size(sizeof(struct tc_gate)) /* TCA_GATE_PARMS */ + + 3 * nla_total_size_64bit(sizeof(u64)) /* TCA_GATE_BASE_TIME + * TCA_GATE_CYCLE_TIME + * TCA_GATE_CYCLE_TIME_EXT + */ + + nla_total_size(sizeof(s32)) /* TCA_GATE_CLOCKID */ + + nla_total_size(sizeof(u32)) /* TCA_GATE_FLAGS */ + + nla_total_size(sizeof(s32)) /* TCA_GATE_PRIORITY */ + + nla_total_size(0); /* TCA_GATE_ENTRY_LIST */ + /* TCA_GATE_TM is budgeted by tcf_action_shared_attrs_size() */ + + rcu_read_lock(); + p = rcu_dereference(gact->param); + if (p) { + list_for_each_entry_rcu(entry, &p->entries, list) + /* TCA_GATE_ONE_ENTRY nest and its attributes */ + size += nla_total_size(0) + + nla_total_size(sizeof(u32)) /* TCA_GATE_ENTRY_INDEX */ + + nla_total_size(0) /* TCA_GATE_ENTRY_GATE */ + + nla_total_size(sizeof(u32)) /* TCA_GATE_ENTRY_INTERVAL */ + + nla_total_size(sizeof(s32)) /* TCA_GATE_ENTRY_MAX_OCTETS */ + + nla_total_size(sizeof(s32)); /* TCA_GATE_ENTRY_IPV */ + } + rcu_read_unlock(); + + return size; } static void tcf_gate_entry_destructor(void *priv) From ab1404ac81154a89fb61ac50ae9a04cd8d4834dc Mon Sep 17 00:00:00 2001 From: Fourie Zhang Date: Sun, 20 Sep 2026 19:08:43 +0800 Subject: [PATCH 2274/2366] net: bridge: mdb: restart port group walk after deletion br_mdb_flush_pgs() keeps a pointer-to-pointer cursor while walking mp->ports. br_multicast_del_pg() can re-enter the same MDB entry through br_multicast_sg_del_exclude_ports() and unlink other port groups. If the cursor points into one of those groups, the next iteration dereferences a stale cursor and can leave mp->ports pointing at freed memory. A following RTM_GETMDB exposes the dangling pointer: BUG: KASAN: slab-use-after-free in br_mdb_dump Read of size 8 br_mdb_dump rtnl_mdb_dump rtnl_dumpit netlink_dump Reset the cursor to mp->ports after every deletion. The deletion removes at least the selected group, so the restarted walk always makes progress. Fixes: a6acb535afb2 ("bridge: mdb: Add MDB bulk deletion support") Cc: stable@vger.kernel.org Signed-off-by: Fourie Zhang Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260920110852.60293-1-fouriezhang@tencent.com Signed-off-by: Jakub Kicinski --- net/bridge/br_mdb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c index e0c7020b12f5..a01bd280c722 100644 --- a/net/bridge/br_mdb.c +++ b/net/bridge/br_mdb.c @@ -1523,6 +1523,8 @@ static void br_mdb_flush_pgs(struct net_bridge *br, } br_multicast_del_pg(mp, p, pp); + /* br_multicast_del_pg() can remove other groups from this list. */ + pp = &mp->ports; } } From fdfec06ac1eb5cdbc18d556c70a7b26837208218 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 22 Sep 2026 09:31:40 +0200 Subject: [PATCH 2275/2366] MAINTAINERS: add Nicolai Buchwitz as GENET maintainer I have been contributing to and reviewing the GENET driver for a while now. Florian asked if I would like to formalize this commitment, so add myself as a maintainer. Signed-off-by: Nicolai Buchwitz Acked-by: Florian Fainelli Acked-by: Justin Chen Link: https://patch.msgid.link/20260922073140.1471858-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3b2eb2a7a89a..7200905501b8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5454,6 +5454,7 @@ F: include/linux/brcmphy.h BROADCOM GENET ETHERNET DRIVER M: Doug Berger M: Florian Fainelli +M: Nicolai Buchwitz R: Broadcom internal kernel review list L: netdev@vger.kernel.org S: Maintained From 7e87508b5c4d81210d0a736ed01962e52f5c4c56 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 22 Sep 2026 15:06:38 +0200 Subject: [PATCH 2276/2366] net: bcmgenet: stop Tx NAPI before disabling the queues bcmgenet_netif_stop() and the Wake-on-LAN branch of bcmgenet_suspend() both disable the Tx queues first and stop Tx NAPI several steps later. A completion in flight calls netif_tx_wake_queue() in between, and nothing stops the queue again, so a transmit can reach the rings after they have been freed. Close is safe because dev_deactivate_many() stops the qdisc first. bcmgenet_suspend() does not, so stop Tx NAPI before the queues on both paths. KASAN on a Raspberry Pi CM4, driven from an MTU change because suspend freezes user space before the callback runs: BUG: KASAN: use-after-free in bcmgenet_xmit+0x17f8/0x2258 Write of size 8 at addr ffffff8055844a68 by task ksoftirqd/0/14 bcmgenet_xmit+0x17f8/0x2258 dev_hard_start_xmit+0x13c/0x588 sch_direct_xmit+0x108/0x340 __dev_queue_xmit+0x1190/0x3848 Fixes: 254f3239dd07 ("net: bcmgenet: revise suspend/resume") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260922130639.1660797-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index b916080f4ff1..ca62041efecd 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -3441,6 +3441,8 @@ static void bcmgenet_netif_stop(struct net_device *dev, bool stop_phy) { struct bcmgenet_priv *priv = netdev_priv(dev); + /* Stop completion polling before it can wake a stopped queue */ + bcmgenet_disable_tx_napi(priv); netif_tx_disable(dev); /* Disable MAC receive */ @@ -3455,7 +3457,6 @@ static void bcmgenet_netif_stop(struct net_device *dev, bool stop_phy) /* Disable MAC transmit. TX DMA disabled must be done before this */ umac_enable_set(priv, CMD_TX_EN, false); - bcmgenet_disable_tx_napi(priv); bcmgenet_disable_rx_napi(priv); bcmgenet_intr_disable(priv); @@ -4320,6 +4321,8 @@ static int bcmgenet_suspend(struct device *d) netif_device_detach(dev); if (device_may_wakeup(d) && priv->wolopts) { + /* Stop completion polling before it can wake a stopped queue */ + bcmgenet_disable_tx_napi(priv); netif_tx_disable(dev); /* Suspend non-wake Rx data flows */ @@ -4348,7 +4351,6 @@ static int bcmgenet_suspend(struct device *d) netdev_warn(priv->dev, "Timed out while disabling TX DMA\n"); - bcmgenet_disable_tx_napi(priv); bcmgenet_disable_rx_napi(priv); disable_irq(priv->irq1); bcmgenet_tx_reclaim_all(dev); From 7104a370714346b667712913dc16abf14bbc97ed Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 21 Sep 2026 16:18:56 -0700 Subject: [PATCH 2277/2366] veth: manage XDP program pointers during channel resize veth_set_channels() tears down XDP resources for removed RX queues without clearing rq->xdp_prog. If the program is then detached or replaced, those queues keep the old pointer after bpf_prog_put(). A later channel increase can re-enable NAPI and run the freed program. BUG: unable to handle page fault for address: ffffc90000256048 Oops: Oops: 0000 [#1] SMP KASAN NOPTI RIP: veth_xdp_rcv_skb (include/linux/filter.h:779 include/net/xdp.h:696 drivers/net/veth.c:820) Call Trace: veth_xdp_rcv (drivers/net/veth.c:941) veth_poll (drivers/net/veth.c:986) __napi_poll (net/core/dev.c:7787) net_rx_action (net/core/dev.c:7850 net/core/dev.c:8007) handle_softirqs (kernel/softirq.c:645) Kernel panic - not syncing: Fatal exception in interrupt Fixes: 4752eeb3d891 ("veth: implement support for set_channel ethtool op") Signed-off-by: Weiming Shi Acked-by: Stanislav Fomichev Reviewed-by: Jiayuan Chen Reviewed-by: Jason Xing Link: https://patch.msgid.link/20260921231856.1798630-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/veth.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 6ed3ee81153f..71227d0389aa 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -1054,6 +1054,7 @@ static int __veth_napi_enable_range(struct net_device *dev, int start, int end) for (i = start; i < end; i++) { struct veth_rq *rq = &priv->rq[i]; + rcu_assign_pointer(rq->xdp_prog, priv->_xdp_prog); napi_enable(&rq->xdp_napi); rcu_assign_pointer(priv->rq[i].napi, &priv->rq[i].xdp_napi); } @@ -1088,6 +1089,7 @@ static void veth_napi_del_range(struct net_device *dev, int start, int end) rcu_assign_pointer(priv->rq[i].napi, NULL); napi_disable(&rq->xdp_napi); + rcu_assign_pointer(rq->xdp_prog, NULL); __netif_napi_del(&rq->xdp_napi); } synchronize_net(); From 3b4e0b0c008a8c1b474730248cd5b873026c74bd Mon Sep 17 00:00:00 2001 From: Shihuang Liu Date: Sat, 19 Sep 2026 21:36:04 +0800 Subject: [PATCH 2278/2366] net: skbuff: fix pull-bound underflow in skb_checksum_setup_ipv6() skb_maybe_pull_tail() subtracts skb_headlen(skb) from the unsigned max argument and passes the result to __pskb_pull_tail() as a signed int. The function does not ensure that max is at least skb_headlen(skb). This can happen while parsing IPv6 extension headers when an skb already has a linear area larger than MAX_IPV6_HDR_LEN. Once the parser needs data beyond the linear area, max - skb_headlen(skb) wraps and is converted to a negative delta. __pskb_pull_tail() then passes that negative length to skb_copy_bits(), where it can become a very large copy length. Pass the requested length itself as the pull bound at the three extension-header call sites, so the delta can no longer go negative. Fixes: 1431fb31ecba ("xen-netback: fix fragment detection in checksum setup") Suggested-by: Eric Dumazet Signed-off-by: Shihuang Liu Link: https://patch.msgid.link/20260919133604.50948-1-shlomojune6@gmail.com Signed-off-by: Jakub Kicinski --- net/core/skbuff.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 609f2c7f4a47..b4edbd06655e 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -5977,7 +5977,8 @@ static int skb_checksum_setup_ipv6(struct sk_buff *skb, bool recalculate) err = skb_maybe_pull_tail(skb, off + sizeof(struct ipv6_opt_hdr), - MAX_IPV6_HDR_LEN); + off + + sizeof(struct ipv6_opt_hdr)); if (err < 0) goto out; @@ -5992,7 +5993,8 @@ static int skb_checksum_setup_ipv6(struct sk_buff *skb, bool recalculate) err = skb_maybe_pull_tail(skb, off + sizeof(struct ip_auth_hdr), - MAX_IPV6_HDR_LEN); + off + + sizeof(struct ip_auth_hdr)); if (err < 0) goto out; @@ -6007,7 +6009,8 @@ static int skb_checksum_setup_ipv6(struct sk_buff *skb, bool recalculate) err = skb_maybe_pull_tail(skb, off + sizeof(struct frag_hdr), - MAX_IPV6_HDR_LEN); + off + + sizeof(struct frag_hdr)); if (err < 0) goto out; From ab7aa05c06ae340e5c7530bb78fa8d23794e460b Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Tue, 22 Sep 2026 16:12:39 +0300 Subject: [PATCH 2279/2366] vrf: Stop corrupting skb->csum when capturing CHECKSUM_COMPLETE packets The VRF device is an Ethernet device but it can have non-Ethernet ports such as IP tunnels. Before the cited commit, capturing packets from such ports on the VRF device resulted in these packets being detected as malformed since they lack an Ethernet header. The cited commit fixed it by pushing a dummy Ethernet header to such packets before the capture and pulling it afterwards. In the case of CHECKSUM_COMPLETE packets it also updated skb->csum with the checksum of the dummy Ethernet header. This is wrong as skb->csum should not include the checksum of the Ethernet header ("checksum of the _whole_ packet as seen by netif_rx()"). This also means that L4 protocols receive a corrupted skb->csum and potentially drop the packet, as is the case with UDP packets whose checksum was completed by software. Fix by removing the unnecessary call to skb_postpush_rcsum(). Fixes: 048939088220 ("vrf: add mac header for tunneled packets when sniffer is attached") Reported-by: Stefano Sasso Closes: https://lore.kernel.org/netdev/CALtE316UtL3x7LL6uxfXzx8rW6AbzYPeDOb478hqJCr_-dj=Wg@mail.gmail.com/ Signed-off-by: Ido Schimmel Reviewed-by: David Ahern Reviewed-by: Eric Dumazet Reviewed-by: Andrea Mayer Link: https://patch.msgid.link/20260922131239.2509494-1-idosch@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/vrf.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c index a0557a3a7026..d4dc6d690a75 100644 --- a/drivers/net/vrf.c +++ b/drivers/net/vrf.c @@ -1175,8 +1175,6 @@ static int vrf_prepare_mac_header(struct sk_buff *skb, skb->protocol = eth->h_proto; skb->pkt_type = PACKET_HOST; - skb_postpush_rcsum(skb, skb->data, ETH_HLEN); - skb_pull_inline(skb, ETH_HLEN); return 0; From 2d959c75c27f90e9ec489d18ce5ee6b852ad4741 Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Mon, 21 Sep 2026 04:40:25 +0000 Subject: [PATCH 2280/2366] ipv6: sr: enforce exact attribute length for SEG6_ATTR_DST In seg6_genl_policy, SEG6_ATTR_DST is defined with .type = NLA_BINARY and .len = sizeof(struct in6_addr). For NLA_BINARY, .len only enforces the maximum payload length and permits shorter payloads (e.g., 0 bytes). When seg6_genl_set_tunsrc() copies sizeof(struct in6_addr) bytes via kmemdup(val, sizeof(*val), GFP_KERNEL), a short SEG6_ATTR_DST attribute triggers a 16-byte out-of-bounds read past skb->tail into uninitialized skb->head memory, which is stored in sdata->tun_src and leaked back to userspace via SEG6_CMD_GET_TUNSRC. Switch SEG6_ATTR_DST in seg6_genl_policy to NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)) so that generic netlink validation rejects any attribute whose length is not exactly sizeof(struct in6_addr) with -ERANGE. Tested in QEMU against Linux 7.3.0-rc3 by sending a SEG6_CMD_SET_TUNSRC Generic Netlink message with a 0-byte SEG6_ATTR_DST attribute followed by SEG6_CMD_GET_TUNSRC. On the unfixed kernel, SEG6_CMD_SET_TUNSRC succeeds (err = 0) and SEG6_CMD_GET_TUNSRC leaks 16 bytes of uninitialized kernel heap memory (tun_src = 836a61ecc4d25a1042a8d60411cfb378); with this patch applied, SEG6_CMD_SET_TUNSRC is rejected by netlink policy validation with -ERANGE (-34) and tun_src remains zeroed. Fixes: 915d7e5e5930 ("ipv6: sr: add code base for control plane support of SR-IPv6") Cc: stable@vger.kernel.org Signed-off-by: Hui Peng Reviewed-by: Hangbin Liu Reviewed-by: Justin Iurman Reviewed-by: Andrea Mayer Link: https://patch.msgid.link/20260921044025.1535982-1-benquike@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/seg6.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c index 62a7eb779202..8c2b156c227a 100644 --- a/net/ipv6/seg6.c +++ b/net/ipv6/seg6.c @@ -138,8 +138,8 @@ void seg6_icmp_srh(struct sk_buff *skb, struct inet6_skb_parm *opt) static struct genl_family seg6_genl_family; static const struct nla_policy seg6_genl_policy[SEG6_ATTR_MAX + 1] = { - [SEG6_ATTR_DST] = { .type = NLA_BINARY, - .len = sizeof(struct in6_addr) }, + [SEG6_ATTR_DST] = + NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), [SEG6_ATTR_DSTLEN] = { .type = NLA_S32, }, [SEG6_ATTR_HMACKEYID] = { .type = NLA_U32, }, [SEG6_ATTR_SECRET] = { .type = NLA_BINARY, }, From 6b491af01aa5c0633a580e3b11bc7277adc903b2 Mon Sep 17 00:00:00 2001 From: Nicolo Giuliani Date: Mon, 21 Sep 2026 05:56:29 +0200 Subject: [PATCH 2281/2366] net: dsa: mv88e6xxx: 88E6191X and 88E6193X have no PTP The 88E6191X and 88E6193X are 6393 family devices that share mv88e6393x_ops with the 88E6393X and are marked as ptp_support. Marvell's UMSD driver describes both as parts without AVB (88E6193X: "BGA package - No AVB, No Routing, No Cut-through"), and the register access confirms it on an 88E6193X: the whole indirect AVB register space behind Global 2 registers 0x16 and 0x17 reads zero, for every port, block and address, with the 6390 and with the 6352 command encoding. Writes to the TAI registers, including the clock period register and the TAI global configuration register, read back as zero. Since commit 7e3c18097a70 ("net: dsa: mv88e6xxx: read cycle counter period from hardware") the PTP setup reads the TAI clock period, so the switch fails to probe: mv88e6xxx ...: unexpected cycle counter period of 0 ps Add mv88e6191x_ops, a copy of mv88e6393x_ops without avb_ops and ptp_ops, use it for the 88E6191X and the 88E6193X and stop setting ptp_support for them. The 88E6393X is unchanged. Tested on an 88E6193X (Sophos XGS 107w): the switch probes and the ports work. I do not have an 88E6191X, it is changed because UMSD describes it the same way. Fixes: de776d0d316f ("net: dsa: mv88e6xxx: add support for mv88e6393x family") Suggested-by: Andrew Lunn Signed-off-by: Nicolo Giuliani Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260921-send-net-v2-1-031ad720f140@studio.unibo.it Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mv88e6xxx/chip.c | 68 ++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index 7f68a0c55802..a4a8c7e11bf4 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -5639,6 +5639,68 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = { .pcs_ops = &mv88e6390_pcs_ops, }; +static const struct mv88e6xxx_ops mv88e6191x_ops = { + /* MV88E6XXX_FAMILY_6393 without AVB and PTP: 6191X and 6193X */ + .irl_init_all = mv88e6390_g2_irl_init_all, + .get_eeprom = mv88e6xxx_g2_get_eeprom8, + .set_eeprom = mv88e6xxx_g2_set_eeprom8, + .set_switch_mac = mv88e6xxx_g2_set_switch_mac, + .phy_read = mv88e6xxx_g2_smi_phy_read_c22, + .phy_write = mv88e6xxx_g2_smi_phy_write_c22, + .phy_read_c45 = mv88e6xxx_g2_smi_phy_read_c45, + .phy_write_c45 = mv88e6xxx_g2_smi_phy_write_c45, + .port_set_link = mv88e6xxx_port_set_link, + .port_sync_link = mv88e6xxx_port_sync_link, + .port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay, + .port_set_speed_duplex = mv88e6393x_port_set_speed_duplex, + .port_tag_remap = mv88e6390_port_tag_remap, + .port_set_policy = mv88e6393x_port_set_policy, + .port_set_frame_mode = mv88e6351_port_set_frame_mode, + .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, + .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, + .port_set_ether_type = mv88e6393x_port_set_ether_type, + .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, + .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, + .port_pause_limit = mv88e6390_port_pause_limit, + .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, + .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, + .port_get_cmode = mv88e6352_port_get_cmode, + .port_set_cmode = mv88e6393x_port_set_cmode, + .port_setup_message_port = mv88e6xxx_setup_message_port, + .port_set_upstream_port = mv88e6393x_port_set_upstream_port, + .port_enable_tcam = mv88e6xxx_port_enable_tcam, + .stats_snapshot = mv88e6390_g1_stats_snapshot, + .stats_set_histogram = mv88e6390_g1_stats_set_histogram, + .stats_get_sset_count = mv88e6320_stats_get_sset_count, + .stats_get_strings = mv88e6320_stats_get_strings, + .stats_get_stat = mv88e6390_stats_get_stat, + /* .set_cpu_port is missing because this family does not support a global + * CPU port, only per port CPU port which is set via + * .port_set_upstream_port method. + */ + .set_egress_port = mv88e6393x_set_egress_port, + .watchdog_ops = &mv88e6393x_watchdog_ops, + .mgmt_rsvd2cpu = mv88e6393x_port_mgmt_rsvd2cpu, + .pot_clear = mv88e6xxx_g2_pot_clear, + .hardware_reset_pre = mv88e6xxx_g2_eeprom_wait, + .hardware_reset_post = mv88e6xxx_g2_eeprom_wait, + .reset = mv88e6352_g1_reset, + .rmu_disable = mv88e6390_g1_rmu_disable, + .atu_get_hash = mv88e6165_g1_atu_get_hash, + .atu_set_hash = mv88e6165_g1_atu_set_hash, + .vtu_getnext = mv88e6390_g1_vtu_getnext, + .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, + .stu_getnext = mv88e6390_g1_stu_getnext, + .stu_loadpurge = mv88e6390_g1_stu_loadpurge, + .serdes_get_lane = mv88e6393x_serdes_get_lane, + .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, + /* TODO: serdes stats */ + .gpio_ops = &mv88e6352_gpio_ops, + .phylink_get_caps = mv88e6393x_phylink_get_caps, + .pcs_ops = &mv88e6393x_pcs_ops, + .tcam_ops = &mv88e6393_tcam_ops, +}; + static const struct mv88e6xxx_ops mv88e6393x_ops = { /* MV88E6XXX_FAMILY_6393 */ .irl_init_all = mv88e6390_g2_irl_init_all, @@ -6163,8 +6225,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, - .ptp_support = true, - .ops = &mv88e6393x_ops, + .ops = &mv88e6191x_ops, }, [MV88E6193X] = { @@ -6190,8 +6251,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, - .ptp_support = true, - .ops = &mv88e6393x_ops, + .ops = &mv88e6191x_ops, }, [MV88E6220] = { From d22609f3d13fc5baacd92c222731b03c593401db Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Mon, 21 Sep 2026 04:59:20 +0000 Subject: [PATCH 2282/2366] fou: reject omitted FOU_ATTR_IPPROTO on FOU_ENCAP_DIRECT Commit 7a9bc9e3f423 ("fou: Don't allow 0 for FOU_ATTR_IPPROTO.") added NLA_POLICY_MIN(NLA_U8, 1) to fou_nl_policy[FOU_ATTR_IPPROTO], which rejects an explicitly supplied FOU_ATTR_IPPROTO == 0 attribute with -ERANGE. However, FOU_ATTR_IPPROTO is an optional netlink attribute. When a user sends FOU_CMD_ADD with FOU_ATTR_TYPE set to FOU_ENCAP_DIRECT and omits FOU_ATTR_IPPROTO entirely, nla_policy validation succeeds and parse_nl_config() leaves cfg->protocol as 0 (from memset(cfg, 0, sizeof(*cfg))). fou_create() then creates a FOU_ENCAP_DIRECT socket with fou->protocol == 0. In fou_udp_recv(), returning -fou->protocol to udp_queue_rcv_one_skb() triggers IP protocol resubmission when fou->protocol > 0, whereas returning 0 tells the UDP tunnel layer that the skb was consumed without freeing it. When fou->protocol == 0, every packet received on the socket returns 0 from fou_udp_recv() and leaks the sk_buff. Reject FOU_ENCAP_DIRECT when !cfg->protocol in fou_create() so that creating a direct encapsulation port without FOU_ATTR_IPPROTO fails with -EINVAL while leaving FOU_CMD_DEL and FOU_CMD_GET (which share parse_nl_config()) unaffected. Tested in QEMU against Linux 7.3.0-rc3 by sending a FOU_CMD_ADD Generic Netlink request with FOU_ATTR_PORT = 5555 and FOU_ATTR_TYPE = FOU_ENCAP_DIRECT while omitting FOU_ATTR_IPPROTO. On the unfixed kernel, FOU_CMD_ADD succeeds (err = 0), FOU_CMD_GET reports fou->type = 1 and fou->protocol = 0, and sending 4000 UDP packets to 127.0.0.1:5555 leaks all 4000 sk_buffs (SUnreclaim in /proc/meminfo grows from 41456 kB to 59008 kB, +17552 kB); with this patch applied, FOU_CMD_ADD is rejected with -EINVAL (-22). Fixes: 23461551c006 ("fou: Support for foo-over-udp RX path") Fixes: 7a9bc9e3f423 ("fou: Don't allow 0 for FOU_ATTR_IPPROTO.") Cc: stable@vger.kernel.org Signed-off-by: Hui Peng Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260921045920.1613098-1-benquike@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/fou_core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/ipv4/fou_core.c b/net/ipv4/fou_core.c index 5e867f1b5c1d..076fdca44f51 100644 --- a/net/ipv4/fou_core.c +++ b/net/ipv4/fou_core.c @@ -600,6 +600,10 @@ static int fou_create(struct net *net, struct fou_cfg *cfg, /* Initial for fou type */ switch (cfg->type) { case FOU_ENCAP_DIRECT: + if (!cfg->protocol) { + err = -EINVAL; + goto error; + } tunnel_cfg.encap_rcv = fou_udp_recv; tunnel_cfg.gro_receive = fou_gro_receive; tunnel_cfg.gro_complete = fou_gro_complete; From 89a8a1eef2d441b7825a6c0116ce817235a7ffe6 Mon Sep 17 00:00:00 2001 From: Jonas Jelonek Date: Fri, 18 Sep 2026 21:19:55 +0000 Subject: [PATCH 2283/2366] net: mdio: realtek-rtl9300: fix RTL931x C22 extended page selection The RTL931x indirect access engine has a separate nine-bit extended page field. The driver leaves it at zero, and otto_emdio_run_cmd() therefore programs extended page zero for every Clause 22 transaction. This overrides page selection made through PHY register 30, causing accesses to private PHY pages to hit extended page zero instead. Set the field to its 0x1ff "do not change" value for RTL931x Clause 22 reads and writes. This preserves extended page selection made through PHY register 30 and restores access to its private register pages. Fixes: 5ebdcac59aff ("net: mdio: realtek-rtl9300: Add support for RTL931x") Signed-off-by: Jonas Jelonek Acked-by: Markus Stockhausen Link: https://patch.msgid.link/20260918211955.3955777-1-jonas@jonasjelonek.de Signed-off-by: Jakub Kicinski --- drivers/net/mdio/mdio-realtek-rtl9300.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c index afd52a1cd7f8..9ce2b7807532 100644 --- a/drivers/net/mdio/mdio-realtek-rtl9300.c +++ b/drivers/net/mdio/mdio-realtek-rtl9300.c @@ -88,6 +88,8 @@ #define RTL9310_SMI_INDRT_ACCESS_BC_PHYID_CTRL 0x0c14 #define RTL9310_BC_PORT_ID GENMASK(10, 5) #define RTL9310_SMI_INDRT_ACCESS_CTRL_1 0x0c04 +#define RTL9310_SMI_INDRT_EXT_PAGE GENMASK(8, 0) +#define RTL9310_SMI_INDRT_EXT_PAGE_NO_CHANGE 0x1ff #define RTL9310_SMI_INDRT_ACCESS_CTRL_2_LOW 0x0c08 #define RTL9310_SMI_INDRT_ACCESS_CTRL_2_HIGH 0x0c0c #define RTL9310_SMI_INDRT_ACCESS_CTRL_3 0x0c10 /* I/O fields flipped */ @@ -325,6 +327,8 @@ static int otto_emdio_9310_read_c22(struct mii_bus *bus, int port, int regnum, u .broadcast = FIELD_PREP(RTL9310_BC_PORT_ID, port), .c22_data = FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) | FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)), + .ext_page = FIELD_PREP(RTL9310_SMI_INDRT_EXT_PAGE, + RTL9310_SMI_INDRT_EXT_PAGE_NO_CHANGE), }; return otto_emdio_read_cmd(bus, RTL9310_PHY_CTRL_TYPE_C22, &cmd_data, @@ -337,6 +341,8 @@ static int otto_emdio_9310_write_c22(struct mii_bus *bus, int port, int regnum, struct otto_emdio_cmd_regs cmd_data = { .c22_data = FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) | FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)), + .ext_page = FIELD_PREP(RTL9310_SMI_INDRT_EXT_PAGE, + RTL9310_SMI_INDRT_EXT_PAGE_NO_CHANGE), .io_data = FIELD_PREP(RTL9310_PHY_CTRL_INDATA, value), .port_mask_high = (u32)(BIT_ULL(port) >> 32), .port_mask_low = (u32)(BIT_ULL(port)), From db762fd96be225bd06161c9631160c755d891693 Mon Sep 17 00:00:00 2001 From: Johan Almbladh Date: Wed, 23 Sep 2026 12:51:57 +0200 Subject: [PATCH 2284/2366] bpf: Fix immediate JMP JEQ/JNE on MIPS32 An addu instruction was emitted instead of addiu, causing the immediate value 1 to be interpreted as register $at. This made the comparison result invalid when the immediate operand was negative. Note that $at is mapped to BPF_REG_AX, which is used for constant blinding. Fix the instruction to use the immediate form. Found with test_bpf on MIPS32r1 emulated by QEMU. Fixes: eb63cfcd2ee8 ("mips, bpf: Add eBPF JIT for 32-bit MIPS") Signed-off-by: Johan Almbladh Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260923105158.3514342-1-johan.almbladh@anyfinetworks.com --- arch/mips/net/bpf_jit_comp32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/mips/net/bpf_jit_comp32.c b/arch/mips/net/bpf_jit_comp32.c index 40a878b672f5..15a2a153dc87 100644 --- a/arch/mips/net/bpf_jit_comp32.c +++ b/arch/mips/net/bpf_jit_comp32.c @@ -1111,7 +1111,7 @@ static void emit_jmp_i64(struct jit_context *ctx, emit(ctx, xor, tmp, lo(dst), tmp); } if (imm < 0) { /* Compare sign extension */ - emit(ctx, addu, MIPS_R_T9, hi(dst), 1); + emit(ctx, addiu, MIPS_R_T9, hi(dst), 1); emit(ctx, or, tmp, tmp, MIPS_R_T9); } else { /* Compare zero extension */ emit(ctx, or, tmp, tmp, hi(dst)); From 8110ba09777873443db286b3cbb89b0e6311c554 Mon Sep 17 00:00:00 2001 From: Johan Almbladh Date: Wed, 23 Sep 2026 12:51:58 +0200 Subject: [PATCH 2285/2366] bpf: Fix BSWAP 32 and 16 on MIPS64 The 16/32-bit byteswap implementations for MIPS64r1 and earlier do not have an explicit zero extension afterwards. The input is first sign-extended to 64 bits, and the byteswap sequence can then leave the result sign-extended depending on the value of the low bits. Add the missing zero-extension. Found with test_bpf on MIPS64r1 emulated by QEMU. Fixes: fbc802de6b10 ("mips, bpf: Add new eBPF JIT for 64-bit MIPS") Signed-off-by: Johan Almbladh Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260923105158.3514342-2-johan.almbladh@anyfinetworks.com --- arch/mips/net/bpf_jit_comp64.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/mips/net/bpf_jit_comp64.c b/arch/mips/net/bpf_jit_comp64.c index fa7e9aa37f49..6681ccac9dd9 100644 --- a/arch/mips/net/bpf_jit_comp64.c +++ b/arch/mips/net/bpf_jit_comp64.c @@ -305,8 +305,7 @@ static void emit_bswap_r64(struct jit_context *ctx, u8 dst, u32 width) case 16: emit_sext(ctx, dst, dst); emit_bswap_r(ctx, dst, width); - if (cpu_has_mips64r2 || cpu_has_mips64r6) - emit_zext(ctx, dst); + emit_zext(ctx, dst); break; } clobber_reg(ctx, dst); From d8b6529e80bcb4fb8177121404cbb3377acaebd2 Mon Sep 17 00:00:00 2001 From: Zijie Huang Date: Mon, 21 Sep 2026 01:36:19 +0800 Subject: [PATCH 2286/2366] net: arp: terminate device name before lookup The ARP ioctl copies a user-provided struct arpreq into a stack object. Its arp_dev field may contain IFNAMSIZ bytes without a NUL terminator. Such input is passed to dev_get_by_name_rcu() or __dev_get_by_name(), where strcmp() can read past the end of the stack object when a matching alternative interface name exists. Terminate the field before the lookup to prevent the out-of-bounds read. Fixes: 36fbf1e52bd3 ("net: rtnetlink: add linkprop commands to add and delete alternative ifnames") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zijie Huang Signed-off-by: Ren Wei Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/fabf02a70787d17299e4b3153eadffaf20d154b3.1789910973.git.milkory@outlook.com Signed-off-by: Jakub Kicinski --- net/ipv4/arp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c index d409f606aec0..60009d92e071 100644 --- a/net/ipv4/arp.c +++ b/net/ipv4/arp.c @@ -1278,6 +1278,7 @@ int arp_ioctl(struct net *net, unsigned int cmd, void __user *arg) err = copy_from_user(&r, arg, sizeof(struct arpreq)); if (err) return -EFAULT; + r.arp_dev[IFNAMSIZ - 1] = '\0'; break; default: return -EINVAL; From 4eb3f195ef08c5acaed87958297e41cc49588dde Mon Sep 17 00:00:00 2001 From: Ivan Delalande Date: Fri, 18 Sep 2026 15:47:15 -0700 Subject: [PATCH 2287/2366] tg3: use random MAC address when tg3_get_device_address fails Some of the tg3 NICs we use (BCM57762) reset the SRAM MAC address to the placeholder address on link flaps, tg3_chip_reset, etc. We've typically fixed it from userspace, but since e4c00ba7274b ("tg3: replace placeholder MAC address with device property") was merged, tg3 just fails probe as we don't have a way to get it through the generic device_get_mac_address infrastructure as fallback on our systems. Make the driver assign a random address in this condition instead of being fatal for probe. Set deferred_probe_reason through dev_warn_probe if the address isn't yet available from the provider. Fixes: e4c00ba7274b ("tg3: replace placeholder MAC address with device property") Suggested-by: Jakub Kicinski Link: https://lore.kernel.org/netdev/20260909191751.651aa5c4@kernel.org/ Signed-off-by: Ivan Delalande Link: https://patch.msgid.link/20260918224715.GA654128@visor Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/tg3.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c index caa7a6caa6e2..8b6806a79edf 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c @@ -17915,11 +17915,14 @@ static int tg3_init_one(struct pci_dev *pdev, err = tg3_get_device_address(tp, addr); if (err) { - dev_err(&pdev->dev, - "Could not obtain valid ethernet address, aborting\n"); - goto err_out_apeunmap; + dev_warn_probe(&pdev->dev, err, + "Could not obtain a valid ethernet address\n"); + if (err == -EPROBE_DEFER) + goto err_out_apeunmap; + eth_hw_addr_random(dev); + } else { + eth_hw_addr_set(dev, addr); } - eth_hw_addr_set(dev, addr); intmbx = MAILBOX_INTERRUPT_0 + TG3_64BIT_REG_LOW; rcvmbx = MAILBOX_RCVRET_CON_IDX_0 + TG3_64BIT_REG_LOW; From 0a7822e34a0bfde31b194ac3da3253e5032b44cc Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 21 Sep 2026 16:46:18 +0200 Subject: [PATCH 2288/2366] net: stmmac: clear stale buf->page after recycling on skb build failure In stmmac_rx(), when napi_build_skb() fails the descriptor page is recycled back to the page pool with page_pool_recycle_direct(), but buf->page is left pointing at the recycled page, unlike every other consumption site in the function which clears the pointer after handing the page away. With the stale pointer stmmac_rx_refill() skips the replacement allocation and programs the already-recycled page back into the RX descriptor. Clear buf->page on the napi_build_skb() failure path to keep the buffer lifecycle consistent with the other consumption sites. Fixes: df542f669307 ("net: stmmac: Switch to zero-copy in non-XDP RX path") Signed-off-by: Lorenzo Bianconi Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260921-stmmac-fix-napi-build-skb-error-v1-1-3d54bf6d9bb6@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index d5a984ad864f..3f34d491c959 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -5889,6 +5889,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) if (!skb) { page_pool_recycle_direct(rx_q->page_pool, buf->page); + buf->page = NULL; rx_dropped++; count++; goto drain_data; From 87cd6b717e4069dca34e0866e57eaeb44d3b173e Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Tue, 22 Sep 2026 05:49:56 +0900 Subject: [PATCH 2289/2366] net: ipv6: keep room for the mac header in dst_dev_overhead() The seg6, ioam6 and rpl lwtunnels size their skb_cow_head() request as the length they are about to push plus dst_dev_overhead(), then push the new headers and rebuild the mac header below them with skb_mac_header_rebuild(). That rebuild needs skb->mac_len of headroom, but dst_dev_overhead() leaves LL_RESERVED_SPACE() of the egress device, 16 bytes for plain Ethernet. Where the mac header is longer than that, as it is on ingress through a VLAN device with reorder_hdr off, the rebuild runs out of room: skb_set_mac_header(skb, -skb->mac_len) computes a negative offset, stores it unchecked in the u16 skb->mac_header, and the memmove that follows writes skb->mac_len bytes about 64 KB past skb->head. Forwarding plain ping6 traffic through such a device reproduces it on all five seg6 encapsulation modes and on the rpl and ioam6 inline paths; skb->mac_header comes back as 65534 on a 704-byte head. Return the larger of the two. The helper already returns skb->mac_len when it has no dst, so this only makes the other branch agree, and it covers every caller rather than each call site in turn. Fixes: 40475b63761a ("net: ipv6: seg6_iptunnel: mitigate 2-realloc issue") Fixes: dce525185bc9 ("net: ipv6: ioam6_iptunnel: mitigate 2-realloc issue") Fixes: 985ec6f5e623 ("net: ipv6: rpl_iptunnel: mitigate 2-realloc issue") Suggested-by: Andrea Mayer Signed-off-by: Yuya Kusakabe Reviewed-by: Justin Iurman Reviewed-by: Gabriel Goller Reviewed-by: Eric Dumazet Reviewed-by: Andrea Mayer Link: https://patch.msgid.link/20260922-seg6-maclen-headroom-v3-1-7b2f982ef79d@gmail.com Signed-off-by: Jakub Kicinski --- include/net/dst.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/net/dst.h b/include/net/dst.h index 307073eae7f8..dbedfe72e1fd 100644 --- a/include/net/dst.h +++ b/include/net/dst.h @@ -455,7 +455,8 @@ static inline unsigned int dst_dev_overhead(struct dst_entry *dst, struct sk_buff *skb) { if (likely(dst)) - return LL_RESERVED_SPACE(dst->dev); + return max_t(unsigned int, skb->mac_len, + LL_RESERVED_SPACE(dst->dev)); return skb->mac_len; } From 0e2bec77ea62895416600c90588f593516572bca Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 21 Sep 2026 15:00:17 -0700 Subject: [PATCH 2290/2366] net: bcmgenet: fix 64-bit RTNL stats reading in ethtool on 32-bit systems When bcmgenet was converted to 64-bit statistics, STAT_RTNL members were switched to point into struct rtnl_link_stats64, whose fields are 64-bit (__u64) regardless of architecture. However, bcmgenet_get_ethtool_stats() retained a legacy check: if (sizeof(unsigned long) != sizeof(u32) && s->stat_sizeof == sizeof(unsigned long)) On 32-bit systems, sizeof(unsigned long) == sizeof(u32), causing this condition to evaluate to false. As a result, 64-bit RTNL stats fields were read via *(u32 *)p. On 32-bit Big-Endian systems (such as MIPS BE), this reads the high 32 bits and returns 0 until the counter exceeds 4GB; on 32-bit Little-Endian systems (such as 32-bit ARM), the value is truncated to 32 bits. Fix this by checking if s->stat_sizeof == sizeof(u64) so 64-bit fields are always read as 64-bit values. Fixes: 59aa6e3072aa ("net: bcmgenet: switch to use 64bit statistics") Reviewed-by: Nicolai Buchwitz Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260921220021.281418-2-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index ca62041efecd..47a6c073c8d9 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -1346,9 +1346,8 @@ static void bcmgenet_get_ethtool_stats(struct net_device *dev, p = (char *)&stats64; p += s->stat_offset; - if (sizeof(unsigned long) != sizeof(u32) && - s->stat_sizeof == sizeof(unsigned long)) - data[i] = *(unsigned long *)p; + if (s->stat_sizeof == sizeof(u64)) + data[i] = *(u64 *)p; else data[i] = *(u32 *)p; } From 3aeaa609fda19c09d5298c9fedaaa3b6229601b5 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 21 Sep 2026 15:00:18 -0700 Subject: [PATCH 2291/2366] net: bcmgenet: initialize u64 stats seq counter for all queues bcmgenet_gstrings_stats statically defines ethtool statistics for queues 0 through GENET_MAX_MQ_CNT (4). However, bcmgenet_probe() only initialized the u64_stats_sync seq counter up to priv->hw_params->rx_queues and priv->hw_params->tx_queues. Since priv->hw_params->rx_queues is 0 across all hardware versions (and priv->hw_params->tx_queues is 0 on GENET V1), rings 1..4 have uninitialized u64_stats_sync structures. When ethtool -S is run on 32-bit kernels, bcmgenet_get_ethtool_stats() reads stats from rx_rings[1..4], causing lockdep warnings due to the uninitialized sequence counters. Initialize the sequence counters for all GENET_MAX_MQ_CNT + 1 queues. Fixes: ffc2c8c4a714 ("net: bcmgenet: Initialize u64 stats seq counter") Reviewed-by: Nicolai Buchwitz Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260921220021.281418-3-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 47a6c073c8d9..2ab37bb031ed 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -4135,10 +4135,10 @@ static int bcmgenet_probe(struct platform_device *pdev) priv->rx_rings[i].rx_max_coalesced_frames = 1; /* Initialize u64 stats seq counter for 32bit machines */ - for (i = 0; i <= priv->hw_params->rx_queues; i++) + for (i = 0; i <= GENET_MAX_MQ_CNT; i++) { u64_stats_init(&priv->rx_rings[i].stats64.syncp); - for (i = 0; i <= priv->hw_params->tx_queues; i++) u64_stats_init(&priv->tx_rings[i].stats64.syncp); + } /* libphy will determine the link state */ netif_carrier_off(dev); From cbbc1aee7776c7fa1d89e6cb963a23e58c495dca Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 21 Sep 2026 15:00:19 -0700 Subject: [PATCH 2292/2366] net: bcmgenet: do not skip WoL power up on GENET V1 bcmgenet_power_up() had an early check for bcmgenet_has_ext(priv) before dispatching by power mode. GENET V1 does not have the EXT block (unlike GENET V2+), which causes bcmgenet_power_up() to immediately return 0. As a consequence, when waking up from GENET_POWER_WOL_MAGIC on GENET V1, bcmgenet_wol_power_up_cfg() is never invoked to disable the WoL clock, clear wake event masks, and restore normal PHY and MAC operations. Move the bcmgenet_has_ext() checks to the GENET_POWER_PASSIVE and GENET_POWER_CABLE_SENSE cases where the EXT registers are actually accessed, allowing GENET_POWER_WOL_MAGIC cleanup to execute on all hardware versions. Fixes: c3ae64ae0c08 ("net: bcmgenet: handle GENET_POWER_WOL_MAGIC") Reviewed-by: Nicolai Buchwitz Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260921220021.281418-4-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 2ab37bb031ed..07032e4193ea 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -1762,13 +1762,12 @@ static int bcmgenet_power_up(struct bcmgenet_priv *priv, int ret = 0; u32 reg; - if (!bcmgenet_has_ext(priv)) - return ret; - - reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT); - switch (mode) { case GENET_POWER_PASSIVE: + if (!bcmgenet_has_ext(priv)) + break; + + reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT); reg &= ~(EXT_PWR_DOWN_DLL | EXT_PWR_DOWN_BIAS | EXT_ENERGY_DET_MASK); if (GENET_IS_V5(priv) && !bcmgenet_has_ephy_16nm(priv)) { @@ -1792,8 +1791,12 @@ static int bcmgenet_power_up(struct bcmgenet_priv *priv, break; case GENET_POWER_CABLE_SENSE: + if (!bcmgenet_has_ext(priv)) + break; + /* enable APD */ if (!GENET_IS_V5(priv)) { + reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT); reg |= EXT_PWR_DN_EN_LD; bcmgenet_ext_writel(priv, reg, EXT_EXT_PWR_MGMT); } From 273941c85fc2632cd3e56ddff737b9245de7697d Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 21 Sep 2026 15:00:20 -0700 Subject: [PATCH 2293/2366] net: bcmgenet: validate Ethernet address in bcmgenet_set_mac_addr bcmgenet_set_mac_addr() did not check whether the provided MAC address is a valid Ethernet address before applying it. Userspace could configure an invalid address (such as all zeroes or a multicast address) while the interface is down. Add a call to is_valid_ether_addr() and return -EADDRNOTAVAIL if the MAC address is not valid. Fixes: 1c1008c793fa ("net: bcmgenet: add main driver file") Reviewed-by: Nicolai Buchwitz Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260921220021.281418-5-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 07032e4193ea..011376a1678a 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -3635,6 +3635,9 @@ static int bcmgenet_set_mac_addr(struct net_device *dev, void *p) if (netif_running(dev)) return -EBUSY; + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + eth_hw_addr_set(dev, addr->sa_data); return 0; From d64e277b955be4506931802837499b62c8f3968a Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 21 Sep 2026 15:00:21 -0700 Subject: [PATCH 2294/2366] net: bcmgenet: mask DMA_TIMEOUT_MASK when reading DMA_RING0_TIMEOUT bcmgenet_get_coalesce() reads DMA_RING0_TIMEOUT to calculate rx_coalesce_usecs without masking out bits outside DMA_TIMEOUT_MASK (16 bits). If upper bits are non-zero or contain status/flags, the computed value of rx_coalesce_usecs returned to userspace via ethtool becomes corrupted. Mask the register read with DMA_TIMEOUT_MASK before computing the timeout in microseconds. Fixes: 4a29645bfe6c ("net: bcmgenet: Implement RX coalescing control knobs") Reviewed-by: Nicolai Buchwitz Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260921220021.281418-6-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 011376a1678a..21668e41b696 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -852,7 +852,8 @@ static int bcmgenet_get_coalesce(struct net_device *dev, ec->rx_max_coalesced_frames = bcmgenet_rdma_ring_readl(priv, 0, DMA_MBUF_DONE_THRESH); ec->rx_coalesce_usecs = - bcmgenet_rdma_readl(priv, DMA_RING0_TIMEOUT) * 8192 / 1000; + (bcmgenet_rdma_readl(priv, DMA_RING0_TIMEOUT) & + DMA_TIMEOUT_MASK) * 8192 / 1000; for (i = 0; i <= priv->hw_params->rx_queues; i++) { ring = &priv->rx_rings[i]; From 4bdee8060d1e4581624e68fbd369b1afb14df4bc Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 21 Sep 2026 20:09:14 -0400 Subject: [PATCH 2295/2366] net: airoha: npu: cancel wdt_work after releasing the WDT IRQ airoha_npu_remove() calls cancel_work_sync() on each core's wdt_work, but the watchdog IRQ that queues it is requested with devm_request_irq() and is freed only after .remove() returns. airoha_npu_wdt_handler() can therefore schedule_work() again once the cancel has returned. struct airoha_npu, which contains the work, is devm_kzalloc()'d and is freed in that same unwind, so the late work dereferences freed memory. Register the work with devm_work_autocancel() before devm_request_irq() and drop .remove(). Devres runs in reverse order, so the IRQ is freed before cancel_work_sync(), including when probe fails. A cancel left in .remove() cannot get that order. Initializing the work first also stops a pending watchdog interrupt from queuing an uninitialized work item. Probe currently calls INIT_WORK() only after devm_request_irq(). This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 23290c7bc190 ("net: airoha: Introduce Airoha NPU support") Cc: stable@vger.kernel.org # 6.15+ Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260922000914.542068-1-mhun512@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/airoha/airoha_npu.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_npu.c b/drivers/net/ethernet/airoha/airoha_npu.c index 5bb4817a898d..4d3195eb00f7 100644 --- a/drivers/net/ethernet/airoha/airoha_npu.c +++ b/drivers/net/ethernet/airoha/airoha_npu.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -751,12 +752,15 @@ static int airoha_npu_probe(struct platform_device *pdev) if (irq < 0) return irq; + err = devm_work_autocancel(dev, &core->wdt_work, + airoha_npu_wdt_work); + if (err) + return err; + err = devm_request_irq(dev, irq, airoha_npu_wdt_handler, IRQF_SHARED, "airoha-npu-wdt", core); if (err) return err; - - INIT_WORK(&core->wdt_work, airoha_npu_wdt_work); } /* wlan IRQ lines */ @@ -803,18 +807,8 @@ static int airoha_npu_probe(struct platform_device *pdev) return 0; } -static void airoha_npu_remove(struct platform_device *pdev) -{ - struct airoha_npu *npu = platform_get_drvdata(pdev); - int i; - - for (i = 0; i < ARRAY_SIZE(npu->cores); i++) - cancel_work_sync(&npu->cores[i].wdt_work); -} - static struct platform_driver airoha_npu_driver = { .probe = airoha_npu_probe, - .remove = airoha_npu_remove, .driver = { .name = "airoha-npu", .of_match_table = of_airoha_npu_match, From a3f315be9d30eeb6938d11fa17fd4b32d52f7c42 Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Mon, 21 Sep 2026 11:18:59 +0800 Subject: [PATCH 2296/2366] ip_gre: Reject enabling collect metadata through changelink ipgre_netlink_parms() can enable collect_md on an existing GRE, GRETAP or ERSPAN device. Unlike newlink, changelink does not enforce metadata tunnel uniqueness. Converting a non-metadata device can therefore replace the metadata receive entry for another device of the same type in the same netns. Deleting either device then clears the shared entry, breaking metadata receive lookup for the surviving device. If parameter validation fails after collect_md is set, deleting the modified device can also clear an entry it never owned. Reject enabling metadata mode in both changelink callbacks before any encapsulation or tunnel parameters are modified. Allow requests that repeat the metadata attribute on an existing metadata device. Fixes: 2e15ea390e6f ("ip_gre: Add support to collect tunnel metadata.") Signed-off-by: Xuanqiang Luo Reviewed-by: Ido Schimmel Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260921031859.9283-1-xuanqiang.luo@linux.dev Signed-off-by: Jakub Kicinski --- net/ipv4/ip_gre.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index 82309efd417e..e4878e9aa636 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -1464,6 +1464,12 @@ static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[], if (!rtnl_dev_link_net_capable(dev, t->net)) return -EPERM; + if (data && data[IFLA_GRE_COLLECT_METADATA] && !t->collect_md) { + NL_SET_ERR_MSG(extack, + "Enabling collect_md on an existing device is not supported"); + return -EOPNOTSUPP; + } + err = ipgre_newlink_encap_setup(dev, data); if (err) return err; @@ -1496,6 +1502,12 @@ static int erspan_changelink(struct net_device *dev, struct nlattr *tb[], if (!rtnl_dev_link_net_capable(dev, t->net)) return -EPERM; + if (data && data[IFLA_GRE_COLLECT_METADATA] && !t->collect_md) { + NL_SET_ERR_MSG(extack, + "Enabling collect_md on an existing device is not supported"); + return -EOPNOTSUPP; + } + err = ipgre_newlink_encap_setup(dev, data); if (err) return err; From 0f2fd31f63c65c409bd336af3a42d478a9207c1f Mon Sep 17 00:00:00 2001 From: Gilberto Conde Date: Mon, 21 Sep 2026 10:34:26 +0100 Subject: [PATCH 2297/2366] net: usb: qmi_wwan: add Quectel EG120K-EA Add support for the Quectel EG120K-EA LTE Cat.12 module (USB ID 2c7c:030b). Its QMI interface (interface 4) uses class/subclass/protocol ff/ff/ff like the other recent Quectel modules, so match it the same way. The product ID is shared with the EM060K, which the option driver already knows and uses to claim the serial interfaces. Without a qmi_wwan entry the data interface is left unbound. Tested on a GL.iNet GL-X2000, where the module is soldered down and enumerates at SuperSpeed: cdc-wdm0 and wwan0 appear and ModemManager brings up a data session. T: Bus=02 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=5000 MxCh= 0 D: Ver= 3.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 9 #Cfgs= 1 P: Vendor=2c7c ProdID=030b Rev= 5.04 S: Manufacturer=Quectel S: Product=EG120K-EA S: SerialNumber=45546267 C:* #Ifs= 6 Cfg#= 1 Atr=a0 MxPwr=896mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=01(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=00 Prot=40 Driver=option E: Ad=83(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=82(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=85(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=84(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=87(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=86(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 4 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=qmi_wwan E: Ad=88(I) Atr=03(Int.) MxPS= 8 Ivl=32ms E: Ad=8e(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=0f(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#=12 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=70 Driver=(none) E: Ad=89(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms Signed-off-by: Gilberto Conde Link: https://patch.msgid.link/20260921093426.2870266-1-gilbertorconde@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/qmi_wwan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index f51cf9cb9421..0e2ab567dd40 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -1086,6 +1086,7 @@ static const struct usb_device_id products[] = { {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0125)}, /* Quectel EC25, EC20 R2.0 Mini PCIe */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x013d)}, /* Quectel RG660QB */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0306)}, /* Quectel EP06/EG06/EM06 */ + {QMI_MATCH_FF_FF_FF(0x2c7c, 0x030b)}, /* Quectel EM060K/EG120K-EA */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0512)}, /* Quectel EG12/EM12 */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0620)}, /* Quectel EM160R-GL */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0800)}, /* Quectel RM500Q-GL */ From 481506a756dcd828ef42391cb08f38d8d96d38fc Mon Sep 17 00:00:00 2001 From: Sanghyun Park Date: Fri, 18 Sep 2026 12:26:58 +0900 Subject: [PATCH 2298/2366] vxlan: use one headroom snapshot for neighbour replies vxlan_na_create() samples LL_RESERVED_SPACE() to size the reply skb and then samples it again to reserve headroom. A concurrent vxlan_changelink() can update needed_headroom between the two reads, creating a TOCTOU race. The second value can exceed the allocation and make the Ethernet header write out of bounds. The race is reproducible on the unpatched kernel. It occurred when vxlan_na_create() generated a neighbour reply while vxlan_changelink() changed the link headroom. KASAN caught a four-byte write two bytes beyond a 704-byte skbuff_small_head allocation. Snapshot the headroom once and use that value for both allocation and reservation. Fixes: 4b29dba9c085 ("vxlan: fix nonfunctional neigh_reduce()") Signed-off-by: Sanghyun Park Link: https://patch.msgid.link/20260918032842.502409-2-sanghyun.park.cnu@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index c1d54339fa2b..3390e341d1e5 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -1958,13 +1958,15 @@ static struct sk_buff *vxlan_na_create(struct sk_buff *request, struct ipv6hdr *pip6; u8 *daddr; int na_olen = 8; /* opt hdr + ETH_ALEN for target */ + int headroom; int ns_olen; int i, len; if (dev == NULL || !pskb_may_pull(request, request->len)) return NULL; - len = LL_RESERVED_SPACE(dev) + sizeof(struct ipv6hdr) + + headroom = LL_RESERVED_SPACE(dev); + len = headroom + sizeof(struct ipv6hdr) + sizeof(*na) + na_olen + dev->needed_tailroom; reply = alloc_skb(len, GFP_ATOMIC); if (reply == NULL) @@ -1972,7 +1974,7 @@ static struct sk_buff *vxlan_na_create(struct sk_buff *request, reply->protocol = htons(ETH_P_IPV6); reply->dev = dev; - skb_reserve(reply, LL_RESERVED_SPACE(request->dev)); + skb_reserve(reply, headroom); skb_push(reply, sizeof(struct ethhdr)); skb_reset_mac_header(reply); From 4da3b7b8b50f3e2fde54a4c18a82a8e3f6223910 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Mon, 21 Sep 2026 17:03:57 +0200 Subject: [PATCH 2299/2366] net: xps: reject an out of range traffic class Only the entries below dev->num_tc are valid in dev->tc_to_txq[], and dev->prio_tc_map[] may only name classes below it. netdev_set_num_tc() lowers dev->num_tc without touching either array. netdev_txq_to_tc() walks all TC_MAX_QUEUE slots and netdev_get_prio_tc_map() returns the entry as it stands, so a leftover entry is handed out as a traffic class >= dev->num_tc. Taking that class from netdev_txq_to_tc(), __netif_set_xps_queue() rejects only a negative one and indexes an XPS map sized for dev->num_tc classes: tci = j * num_tc + tc; RCU_INIT_POINTER(new_dev_maps->attr_map[tci], map); attr_map[] holds nr_ids * num_tc entries and j runs over the ids named in the mask, so a class that is not below num_tc pushes tci past the end of the map for the last ids and the store overruns it. Any caller that lowers num_tc leaves such entries behind, and mqprio_destroy() tears down with netdev_set_num_tc(dev, 0) rather than netdev_reset_tc(). After mqprio with 8 classes then 1, tc_to_txq[1..7] still describe txq 1..7. The splat is from an XPS write to txq 2 on a veth with 8 rx queues: attr_map[] has 8 * 1 entries, tci = j + 2, and j == 6 stores one past the end of the 88-byte map: BUG: KASAN: slab-out-of-bounds in __netif_set_xps_queue (net/core/dev.c:2954) Write of size 8 at addr ffff88813016bc58 by task xps_oob/634 __netif_set_xps_queue (net/core/dev.c:2954) xps_rxqs_store (net/core/net-sysfs.c:1880) netdev_queue_attr_store (net/core/net-sysfs.c:1390) Allocated by task 634: __kmalloc_noprof (mm/slub.c:5439) __netif_set_xps_queue (net/core/dev.c:2937) The buggy address is located 0 bytes to the right of allocated 88-byte region [ffff88813016bc00, ffff88813016bc58) Reject a class the map has no room for. Fixes: 184c449f91fe ("net: Add support for XPS with QoS via traffic classes") Signed-off-by: Norbert Szetei Link: https://patch.msgid.link/162DD16F-54C6-444A-9E09-0B8CB3D591F2@doyensec.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/dev.c b/net/core/dev.c index c67900354fa6..0292a16e16c2 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -2901,7 +2901,7 @@ int __netif_set_xps_queue(struct net_device *dev, const unsigned long *mask, dev = netdev_get_tx_queue(dev, index)->sb_dev ? : dev; tc = netdev_txq_to_tc(dev, index); - if (tc < 0) + if (tc < 0 || tc >= num_tc) return -EINVAL; } From a0bb6fac53fa7cf1cadb487b43d4c9276a6b82e3 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Fri, 18 Sep 2026 21:29:15 +0800 Subject: [PATCH 2300/2366] sched/core: Account PSI IRQ time to the execution context, not the scheduling context psi_account_irqtime() has two callers which share rq->psi_irq_time, and they disagree about the context: __schedule() passes the outgoing rq->curr, sched_tick() passes rq->donor. Under proxy execution the donor is blocked on a mutex while rq->curr burns the CPU. The tick charges PSI_IRQ_FULL to the donor's cgroup and advances the timestamp, so the call from __schedule() then finds delta <= 0 and charges nothing. The delta is not counted twice, it lands on the wrong cgroup. Pass rq->curr, which is what the call read before commit af0c8b2bf67b ("sched: Split scheduler and execution contexts") renamed 'curr' to 'donor' across sched_tick(). Without CONFIG_SCHED_PROXY_EXEC the two rq members are a union, so this only changes anything where that option is set, and it depends on EXPERT. Fixes: af0c8b2bf67b ("sched: Split scheduler and execution contexts") Signed-off-by: Zhan Xusheng Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260918132915.1236312-1-zhanxusheng@xiaomi.com --- kernel/sched/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 0b846a13c628..1fe40de6ebe3 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -5798,7 +5798,7 @@ void sched_tick(void) curr = rq->curr; donor = rq->donor; - psi_account_irqtime(rq, donor, NULL); + psi_account_irqtime(rq, curr, NULL); update_rq_clock(rq); hw_pressure = arch_scale_hw_pressure(cpu_of(rq)); From e47a1958e12abc3a17b5231a4f21c8f1bf662e08 Mon Sep 17 00:00:00 2001 From: Yuqi Xu Date: Sat, 19 Sep 2026 16:45:27 +0800 Subject: [PATCH 2301/2366] net: ipconfig: bound DHCP option construction ic_dhcp_init_options() appends the hostname (option 12), vendor-class (option 60) and client-ID (option 61) options into the fixed 312-byte bootp_pkt.exten[] buffer. Only the client-ID branch checked the remaining space; the hostname and vendor-class writes were unbounded. A 64-byte hostname together with the maximum 252-byte dhcpclass= identifier needs 18 + (2 + 64) + (2 + 252) = 338 of the 312 available bytes even before the terminating END marker, so the vendor-class memcpy runs past the end of exten[]. With CONFIG_FORTIFY_SOURCE this is reported as a field-spanning write and, when the kernel is booted with panic_on_warn=1, aborts boot with a panic. Route the optional options through a common helper that makes sure the option, its 2-byte header and the END marker all fit and drops an option that would not. Configurations with short options keep sending exactly the same bytes as before. Fixes: 130c0f47fdf9 ("ipconfig: send host-name in DHCP requests") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Yuqi Xu Reviewed-by: Ren Wei Reviewed-by: Simon Horman Link: https://patch.msgid.link/7808dfbfa2162dfd0b19f59aff5742d6e0db2abb.1789798023.git.xuyuqiabc@gmail.com Signed-off-by: Paolo Abeni --- net/ipv4/ipconfig.c | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/net/ipv4/ipconfig.c b/net/ipv4/ipconfig.c index 155db067eaec..1b8585404a41 100644 --- a/net/ipv4/ipconfig.c +++ b/net/ipv4/ipconfig.c @@ -676,6 +676,24 @@ static const u8 ic_bootp_cookie[4] = { 99, 130, 83, 99 }; #ifdef IPCONFIG_DHCP +static bool __init +ic_dhcp_add_option(u8 **options, const u8 *end, u8 type, const void *value, + int len) +{ + u8 *e = *options; + + /* leave room for the option header and the END marker */ + if (len > U8_MAX || end - e < len + 3) + return false; + + *e++ = type; + *e++ = len; + memcpy(e, value, len); + *options = e + len; + + return true; +} + static void __init ic_dhcp_init_options(u8 *options, struct ic_device *d) { @@ -691,6 +709,7 @@ ic_dhcp_init_options(u8 *options, struct ic_device *d) 42, /* NTP servers */ }; u8 mt = (ic_servaddr == NONE) ? DHCPDISCOVER : DHCPREQUEST; + u8 *end = options + sizeof(((struct bootp_pkt *)0)->exten); u8 *e = options; int len; @@ -721,31 +740,19 @@ ic_dhcp_init_options(u8 *options, struct ic_device *d) e += sizeof(ic_req_params); if (ic_host_name_set) { - *e++ = 12; /* host-name */ len = strlen(utsname()->nodename); - *e++ = len; - memcpy(e, utsname()->nodename, len); - e += len; + ic_dhcp_add_option(&e, end, 12, utsname()->nodename, len); } if (*vendor_class_identifier) { - pr_info("DHCP: sending class identifier \"%s\"\n", - vendor_class_identifier); - *e++ = 60; /* Class-identifier */ len = strlen(vendor_class_identifier); - *e++ = len; - memcpy(e, vendor_class_identifier, len); - e += len; + if (ic_dhcp_add_option(&e, end, 60, vendor_class_identifier, len)) + pr_info("DHCP: sending class identifier \"%s\"\n", + vendor_class_identifier); } len = strlen(dhcp_client_identifier + 1); - /* the minimum length of identifier is 2, include 1 byte type, - * and can not be larger than the length of options - */ - if (len >= 1 && len < 312 - (e - options) - 1) { - *e++ = 61; - *e++ = len + 1; - memcpy(e, dhcp_client_identifier, len + 1); - e += len + 1; - } + /* the minimum length of identifier is 2, include 1 byte type */ + if (len >= 1) + ic_dhcp_add_option(&e, end, 61, dhcp_client_identifier, len + 1); *e++ = 255; /* End of the list */ } From 7b824c293a6b56de8285a97984c507cba56bc4c4 Mon Sep 17 00:00:00 2001 From: Brajesh Gupta Date: Tue, 22 Sep 2026 09:56:55 +0530 Subject: [PATCH 2302/2366] drm/imagination: Propagate map failures correctly from pvr_mmu_map_sgl() Map failure from pvr_mmu_map_sgl() interface was not returned correctly to pvr_mmu_map() interface. This resulted in pvr_mmu_map() interface to continue instead of returning an error to caller. Fix it by returning a proper error code from pvr_mmu_map_sgl() interface. Call stack for crash: [ 1179.286237] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000008 [ 1179.295067] Mem abort info: [ 1179.297877] ESR = 0x0000000096000004 [ 1179.301656] EC = 0x25: DABT (current EL), IL = 32 bits [ 1179.306987] SET = 0, FnV = 0 [ 1179.310048] EA = 0, S1PTW = 0 [ 1179.313198] FSC = 0x04: level 0 translation fault [ 1179.318096] Data abort info: [ 1179.320993] ISV = 0, ISS = 0x00000004, ISS2 = 0x00000000 [ 1179.326483] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 1179.331546] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 1179.336895] user pgtable: 4k pages, 48-bit VAs, pgdp=000000009822a000 [ 1179.343402] [0000000000000008] pgd=0000000000000000, p4d=0000000000000000 [ 1179.350243] Internal error: Oops: 0000000096000004 [#2] SMP [ 1179.355908] Modules linked in: powervr gpu_sched drm_shmem_helper drm_gpuvm drm_exec xhci_plat_hcd xhci_hcd dwc3 usbcore usb_common snd_soc_simple_card snd_soc_simple_card_utils dwc3_am62 at24 sa2ul sha512 libsha512 sha256 authenc sch_fq_codel fuse dm_mod ipv6 [ 1179.378992] CPU: 1 UID: 1000 PID: 680 Comm: deqp-vk Tainted: G D 6.17.0 #1 PREEMPT [ 1179.388120] Tainted: [D]=DIE [ 1179.390994] Hardware name: Texas Instruments AM625 SK (DT) [ 1179.396467] pstate: 00000005 (nzcv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 1179.403415] pc : pvr_mmu_op_context_unmap_curr_page+0x6c/0x134 [powervr] [ 1179.410140] lr : pvr_mmu_op_context_unmap_curr_page+0x58/0x134 [powervr] [ 1179.416848] sp : ffff8000839ab8c0 [ 1179.420153] x29: ffff8000839ab8c0 x28: 0000000000000001 x27: 000000008f386000 [ 1179.427283] x26: ffff000016d1df98 x25: 0000000000247000 x24: 00000000000001e6 [ 1179.434413] x23: 0000000000000002 x22: 000000000000ffff x21: 0000000000000247 [ 1179.441540] x20: 0000000000000245 x19: ffff000016d1df60 x18: 0000000000000002 [ 1179.448668] x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000001 [ 1179.455793] x14: 0000000000060810 x13: ffff80007fffffff x12: ffff000004190480 [ 1179.462921] x11: ffff8000853f7000 x10: ffff8000811ae000 x9 : ffff0000041900b8 [ 1179.470051] x8 : 0000000000000000 x7 : 00000000990c4001 x6 : 0000000000000007 [ 1179.477177] x5 : ffff000016d1df60 x4 : 0000000000000000 x3 : ffff00000a7d8000 [ 1179.484306] x2 : 00000000000001ff x1 : 0000000000000000 x0 : 0000000000000000 [ 1179.491433] Call trace: [ 1179.493872] pvr_mmu_op_context_unmap_curr_page+0x6c/0x134 [powervr] (P) [ 1179.500582] pvr_mmu_map+0x31c/0x388 [powervr] [ 1179.505027] pvr_vm_gpuva_map+0x40/0x88 [powervr] [ 1179.509732] __drm_gpuvm_sm_map+0x250/0x44c [drm_gpuvm] [ 1179.514952] drm_gpuvm_sm_map+0x48/0x5c [drm_gpuvm] [ 1179.519822] pvr_vm_bind_op_exec+0x64/0x70 [powervr] [ 1179.524785] pvr_vm_map+0x1f8/0x2a8 [powervr] [ 1179.529142] pvr_ioctl_vm_map+0x12c/0x188 [powervr] [ 1179.534018] drm_ioctl_kernel+0xb8/0x128 [ 1179.537941] drm_ioctl+0x21c/0x4ec [ 1179.541337] __arm64_sys_ioctl+0xac/0x108 [ 1179.545344] invoke_syscall+0x44/0x100 [ 1179.549091] el0_svc_common.constprop.0+0x40/0xe0 [ 1179.553790] do_el0_svc+0x1c/0x28 [ 1179.557106] el0_svc+0x34/0xf0 [ 1179.560159] el0t_64_sync_handler+0xd0/0xe4 [ 1179.564334] el0t_64_sync+0x198/0x19c [ 1179.567996] Code: 54000300 35000360 f9402261 79409a62 (f9400421) [ 1179.574081] ---[ end trace 0000000000000000 ]--- Fixes: ff5f643de0bf ("drm/imagination: Add GEM and VM related code") Reviewed-by: Alexandru Dadu Reviewed-by: Alessio Belle Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260922-mmu_fix-v4-1-12f1a871456a@imgtec.com Signed-off-by: Brajesh Gupta --- drivers/gpu/drm/imagination/pvr_mmu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/imagination/pvr_mmu.c b/drivers/gpu/drm/imagination/pvr_mmu.c index 3cac482e1034..23261d9ad3fd 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.c +++ b/drivers/gpu/drm/imagination/pvr_mmu.c @@ -12,6 +12,7 @@ #include "pvr_rogue_mmu_defs.h" #include +#include #include #include #include @@ -2553,7 +2554,9 @@ pvr_mmu_map_sgl(struct pvr_mmu_op_context *op_ctx, struct scatterlist *sgl, err_destroy_pages: memcpy(&op_ctx->curr_page, &ptr_copy, sizeof(op_ctx->curr_page)); - err = pvr_mmu_op_context_unmap_curr_page(op_ctx, page); + if (pvr_mmu_op_context_unmap_curr_page(op_ctx, page)) + drm_err(from_pvr_device(op_ctx->mmu_ctx->pvr_dev), + "%s : Failure in unmapping pages\n", __func__); return err; } From 0a8224058a5835297dcf4a46bbcd16f77a9fe424 Mon Sep 17 00:00:00 2001 From: Brajesh Gupta Date: Tue, 22 Sep 2026 09:56:56 +0530 Subject: [PATCH 2303/2366] drm/imagination: Fix page count for page table for map() interface The GPU virtual start address wasn't included in the calculation for the amount of page tables required for mapping a BO object in map() interface. It resulted in map failure later due to not enough pages at L0/L1 level. Update pvr_mmu_op_context_create() interface to pass device address as well to allow correct calculation for page table memory. If L0 tables cover 2MB (0x200000), the range defined by device address 0x80001ff000 (general heap at 2MB - 4KB) and size 0x2000 (two 4KB pages) requires two L0 pages to be mapped, but without the base address a range of 0x2000 computes to a single L0 page which is not enough. Fixes: ff5f643de0bf ("drm/imagination: Add GEM and VM related code") Reviewed-by: Alexandru Dadu Reviewed-by: Alessio Belle Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260922-mmu_fix-v4-2-12f1a871456a@imgtec.com Signed-off-by: Brajesh Gupta --- drivers/gpu/drm/imagination/pvr_mmu.c | 14 ++++++++------ drivers/gpu/drm/imagination/pvr_mmu.h | 2 +- drivers/gpu/drm/imagination/pvr_vm.c | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_mmu.c b/drivers/gpu/drm/imagination/pvr_mmu.c index 23261d9ad3fd..62eae7fcd5a2 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.c +++ b/drivers/gpu/drm/imagination/pvr_mmu.c @@ -2336,6 +2336,7 @@ void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx) * pvr_mmu_op_context_create() - Create an MMU op context. * @ctx: MMU context associated with owning VM context. * @sgt: Scatter gather table containing pages pinned for use by this context. + * @device_addr: Virtual device address at the start of the requested mapping. * @sgt_offset: Start offset of the requested device-virtual memory mapping. * @size: Size in bytes of the requested device-virtual memory mapping. For an * unmapping, this should be zero so that no page tables are allocated. @@ -2347,8 +2348,9 @@ void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx) */ struct pvr_mmu_op_context * pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, - u64 sgt_offset, u64 size) + u64 device_addr, u64 sgt_offset, u64 size) { + u64 start_addr = device_addr + sgt_offset; int err; struct pvr_mmu_op_context *op_ctx = kzalloc_obj(*op_ctx); @@ -2364,16 +2366,16 @@ pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, if (size) { /* * The number of page table objects we need to prealloc is - * indicated by the mapping size, start offset and the sizes + * indicated by the mapping size, start address and the sizes * of the areas mapped per PT or PD. The range calculation is * identical to that for the index into a table for a device * address, so we reuse those functions here. */ - const u32 l1_start_idx = pvr_page_table_l2_idx(sgt_offset); - const u32 l1_end_idx = pvr_page_table_l2_idx(sgt_offset + size); + const u32 l1_start_idx = pvr_page_table_l2_idx(start_addr); + const u32 l1_end_idx = pvr_page_table_l2_idx(start_addr + size); const u32 l1_count = l1_end_idx - l1_start_idx + 1; - const u32 l0_start_idx = pvr_page_table_l1_idx(sgt_offset); - const u32 l0_end_idx = pvr_page_table_l1_idx(sgt_offset + size); + const u32 l0_start_idx = pvr_page_table_l1_idx(start_addr); + const u32 l0_end_idx = pvr_page_table_l1_idx(start_addr + size); const u32 l0_count = l0_end_idx - l0_start_idx + 1; /* diff --git a/drivers/gpu/drm/imagination/pvr_mmu.h b/drivers/gpu/drm/imagination/pvr_mmu.h index a8ecd460168d..2c02d61ba0a2 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.h +++ b/drivers/gpu/drm/imagination/pvr_mmu.h @@ -99,7 +99,7 @@ dma_addr_t pvr_mmu_get_root_table_dma_addr(struct pvr_mmu_context *ctx); void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx); struct pvr_mmu_op_context * pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, - struct sg_table *sgt, u64 sgt_offset, u64 size); + struct sg_table *sgt, u64 device_addr, u64 sgt_offset, u64 size); int pvr_mmu_map(struct pvr_mmu_op_context *op_ctx, u64 size, u64 flags, u64 device_addr); diff --git a/drivers/gpu/drm/imagination/pvr_vm.c b/drivers/gpu/drm/imagination/pvr_vm.c index ceb78694cd98..55cc999f3708 100644 --- a/drivers/gpu/drm/imagination/pvr_vm.c +++ b/drivers/gpu/drm/imagination/pvr_vm.c @@ -276,7 +276,7 @@ pvr_vm_bind_op_map_init(struct pvr_vm_bind_op *bind_op, goto err_bind_op_fini; bind_op->mmu_op_ctx = - pvr_mmu_op_context_create(vm_ctx->mmu_ctx, sgt, offset, size); + pvr_mmu_op_context_create(vm_ctx->mmu_ctx, sgt, device_addr, offset, size); err = PTR_ERR_OR_ZERO(bind_op->mmu_op_ctx); if (err) { bind_op->mmu_op_ctx = NULL; @@ -318,7 +318,7 @@ pvr_vm_bind_op_unmap_init(struct pvr_vm_bind_op *bind_op, } bind_op->mmu_op_ctx = - pvr_mmu_op_context_create(vm_ctx->mmu_ctx, NULL, 0, 0); + pvr_mmu_op_context_create(vm_ctx->mmu_ctx, NULL, device_addr, 0, 0); err = PTR_ERR_OR_ZERO(bind_op->mmu_op_ctx); if (err) { bind_op->mmu_op_ctx = NULL; From 45585c3aa285854face65293acc95eff73063d6d Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 20 Sep 2026 11:43:29 +0800 Subject: [PATCH 2304/2366] drm/imagination: clamp freelist reconstruction requests The firmware reconstruction count controls accesses to the request's fixed freelist ID array and the copy into the fixed response array. Neither access currently bounds the count to those protocol arrays. Clamp the count to the request capacity, which is shared by the response layout, and use that count consistently for reconstruction and response publication. Keep the firmware recovery exchange instead of dropping an oversized request without a response, as discussed with the firmware maintainer. The issue was found by our static-analysis tool. Fixes: 6eedddab733b ("drm/imagination: Implement free list and HWRT create and destroy ioctls") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Reviewed-by: Alessio Belle Link: https://patch.msgid.link/20260920034329.16614-1-hppiscas@163.com Signed-off-by: Brajesh Gupta --- drivers/gpu/drm/imagination/pvr_free_list.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/imagination/pvr_free_list.c b/drivers/gpu/drm/imagination/pvr_free_list.c index e85cac83834c..faf5e586d8dc 100644 --- a/drivers/gpu/drm/imagination/pvr_free_list.c +++ b/drivers/gpu/drm/imagination/pvr_free_list.c @@ -8,6 +8,7 @@ #include "pvr_vm.h" #include +#include #include #include #include @@ -612,13 +613,21 @@ pvr_free_list_process_reconstruct_req(struct pvr_device *pvr_dev, }; struct rogue_fwif_freelists_reconstruction_data *resp = &resp_cmd.cmd_data.free_lists_reconstruction_data; + u32 count = min_t(u32, req->freelist_count, + ARRAY_SIZE(req->freelist_ids)); - for (u32 i = 0; i < req->freelist_count; i++) + if (count != req->freelist_count) { + drm_warn_once(from_pvr_device(pvr_dev), + "Requested reconstruction of %u freelists, limiting to %u\n", + req->freelist_count, count); + } + + for (u32 i = 0; i < count; i++) pvr_free_list_reconstruct(pvr_dev, req->freelist_ids[i]); - resp->freelist_count = req->freelist_count; + resp->freelist_count = count; memcpy(resp->freelist_ids, req->freelist_ids, - req->freelist_count * sizeof(resp->freelist_ids[0])); + count * sizeof(resp->freelist_ids[0])); WARN_ON(pvr_kccb_send_cmd(pvr_dev, &resp_cmd, NULL)); } From 00efbbd40bd5fd92c67b7cf1aab8904fa59a96f6 Mon Sep 17 00:00:00 2001 From: David Dai Date: Fri, 18 Sep 2026 16:11:55 -0500 Subject: [PATCH 2305/2366] bonding: crypto offload enabled, non-offload slave failover, rekey failed Create a bonding device (i.e. bond0) in active-backup mode, 2 slaves. Active slave: offload capable interface (i.e. eth1), primary interface. Backup slave: non-offload capable interface(i.e. eth2). Configure strongswan service swantl.conf child SA "hw_offload = crypto" Start strongswan service IPSec Crytpo Offload is enabled on top of bond0. i.e. ip xfrm state |grep offload crypto offload parameters: dev bond0 dir out mode crypto crypto offload parameters: dev bond0 dir in mode crypto Active slave eth1 takes adavantage of IPSec Crypto Offload capability. If active slave eth1 is down for any reason (i.e. eth1 link down): ip link set down dev eth1 non-offload capable interface eth2 failover to becomes active slave. The existing SAs can continue use software IPsec after failover. Traffic still keeps going properly. However if eth1 link had not recovered yet, strongswan service does new child SA rekey, or uses swanctl command to do new child SA rekey, it will fail because active slave eth2 doesn't support crypto offload. In bond_ipsec_add_sa routine, it returns -EINVAL now, which is treated as fatal error by xfrm_dev_state_add routine in kernel xfrm. To make the non-offload active slave survive the child SA rekey, need to make bond_ipsec_add_sa routine returns -EOPNOTSUPP instead when active slave doesn't support IPsec Crypto offload, the xfrm will gracefully fallback to create new SA using Software IPsec. Network traffic can keep going. After offload capable interface eth1 link is up, becomes active slave, next time strongswan child SA rekey will create a new SA which enables crypto offload again. Fixes: 18cb261afd7b ("bonding: support hardware encryption offload to slaves") Signed-off-by: David Dai Reviewed-by: Hangbin Liu Link: https://patch.msgid.link/20260918211155.1664493-1-zdai@linux.ibm.com Signed-off-by: Paolo Abeni --- drivers/net/bonding/bond_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index a9bff7663eec..de2489c3d9bf 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -490,7 +490,7 @@ static int bond_ipsec_add_sa(struct net_device *bond_dev, !real_dev->xfrmdev_ops->xdo_dev_state_add || netif_is_bond_master(real_dev)) { NL_SET_ERR_MSG_MOD(extack, "Slave does not support ipsec offload"); - err = -EINVAL; + err = -EOPNOTSUPP; goto out; } From 36c2009d90f2210ef92e6f4f2850e8b57b09e754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gajdos=20Tam=C3=A1s?= Date: Mon, 21 Sep 2026 11:13:32 +0200 Subject: [PATCH 2306/2366] net: atl1c: fix soft lockup on out-of-range tpd_cons read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hardware can report an out-of-range tpd_cons (seen as 0xffff) while the PCIe link/MAC is resetting. An out-of-range value can never be reached and the loop below would spin forever. To avoid a soft lockup treat it as "nothing new to clean" instead. Reproduced on two machines, same NIC (Qualcomm Atheros AR8151 v2.0, 4-port), triggered by rebooting a Mikrotik CCR2004 PCIe card that the ports are directly linked to: - Ubuntu 26.04.1 LTS, kernel 7.0.0-31-generic. The link-flap precursor, before the lockup was captured with a full trace elsewhere: atl1c 0000:05:00.0 enp5s0f0: NETDEV WATCHDOG: CPU: 4: transmit queue 2 timed out 489984 ms atl1c 0000:05:00.0: MAC state machine can't be idle since disabled for 10ms second atl1c 0000:05:00.0: atl1c: enp5s0f0 NIC Link is Up<65535 Mbps Full Duplex> 65535 (0xffff) here is the same value tpd_cons reads back once the loop below gets stuck. - Proxmox VE, kernel 7.0.14-11-pve. Same NIC/trigger, this time caught by the soft lockup watchdog with a full stack trace: watchdog: BUG: soft lockup - CPU#12 stuck for 354s! [napi/eth%d-0:329] CPU: 12 UID: 0 PID: 329 Comm: napi/eth%d-0 Tainted: P O L 7.0.14-11-pve #1 PREEMPT(lazy) RIP: 0010:atl1c_clean_tx+0x142/0x2d0 [atl1c] Call Trace: __napi_poll+0x32/0x1e0 napi_threaded_poll_loop+0x286/0x2e0 napi_threaded_poll+0xfd/0x140 kthread+0xf7/0x130 ret_from_fork+0x2da/0x3a0 ret_from_fork_asm+0x1a/0x30 Fixes: 43250ddd75a35d ("atl1c: Atheros L1C Gigabit Ethernet driver") Cc: stable@vger.kernel.org Signed-off-by: Gajdos Tamás Link: https://patch.msgid.link/20260921091334.3571525-2-tamas@rimpianto.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/atheros/atl1c/atl1c_main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c index 7efa3fc257b3..e58f1d2c26bd 100644 --- a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c +++ b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c @@ -1602,6 +1602,9 @@ static int atl1c_clean_tx(struct napi_struct *napi, int budget) AT_READ_REGW(&adapter->hw, atl1c_qregs[tpd_ring->num].tpd_cons, &hw_next_to_clean); + if (unlikely(hw_next_to_clean >= tpd_ring->count)) + hw_next_to_clean = next_to_clean; + while (next_to_clean != hw_next_to_clean) { buffer_info = &tpd_ring->buffer_info[next_to_clean]; if (buffer_info->skb) { From 374bf9e4b90f979e052332c4faca2d745c491a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gajdos=20Tam=C3=A1s?= Date: Mon, 21 Sep 2026 11:13:33 +0200 Subject: [PATCH 2307/2366] net: atl1e: fix soft lockup on out-of-range hw_next_to_clean read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same issue as atl1c (see the first commit in this series, "net: atl1c: fix soft lockup on out-of-range tpd_cons read"): the hardware can report an out-of-range hw_next_to_clean (seen as 0xffff) while the PCIe link/MAC is resetting. An out-of-range value can never be reached and the loop below would spin forever. Treat it as "nothing new to clean" instead. Fixes: a6a5325239c202 ("atl1e: Atheros L1E Gigabit Ethernet driver") Cc: stable@vger.kernel.org Signed-off-by: Gajdos Tamás Link: https://patch.msgid.link/20260921091334.3571525-3-tamas@rimpianto.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/atheros/atl1e/atl1e_main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/atheros/atl1e/atl1e_main.c b/drivers/net/ethernet/atheros/atl1e/atl1e_main.c index 40290028580b..437989edb780 100644 --- a/drivers/net/ethernet/atheros/atl1e/atl1e_main.c +++ b/drivers/net/ethernet/atheros/atl1e/atl1e_main.c @@ -1234,6 +1234,9 @@ static bool atl1e_clean_tx_irq(struct atl1e_adapter *adapter) u16 hw_next_to_clean = AT_READ_REGW(&adapter->hw, REG_TPD_CONS_IDX); u16 next_to_clean = atomic_read(&tx_ring->next_to_clean); + if (unlikely(hw_next_to_clean >= tx_ring->count)) + hw_next_to_clean = next_to_clean; + while (next_to_clean != hw_next_to_clean) { tx_buffer = &tx_ring->tx_buffer[next_to_clean]; if (tx_buffer->dma) { From 43e746821f5f5afbbf68e388bf9fbe221e03bfca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gajdos=20Tam=C3=A1s?= Date: Mon, 21 Sep 2026 11:13:34 +0200 Subject: [PATCH 2308/2366] net: atl1: fix soft lockup on out-of-range cmb_tpd_next_to_clean read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same issue as atl1c (see the first commit in this series, "net: atl1c: fix soft lockup on out-of-range tpd_cons read"): the hardware can report an out-of-range cmb_tpd_next_to_clean (seen as 0xffff) while the PCIe link/MAC is resetting. An out-of-range value can never be reached and the loop below would spin forever. Treat it as "nothing new to clean" instead. Fixes: f3cc28c797604f ("Add Attansic L1 ethernet driver.") Cc: stable@vger.kernel.org Signed-off-by: Gajdos Tamás Link: https://patch.msgid.link/20260921091334.3571525-4-tamas@rimpianto.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/atheros/atlx/atl1.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/atheros/atlx/atl1.c b/drivers/net/ethernet/atheros/atlx/atl1.c index 98a4d089270e..957d5598dda5 100644 --- a/drivers/net/ethernet/atheros/atlx/atl1.c +++ b/drivers/net/ethernet/atheros/atlx/atl1.c @@ -2066,6 +2066,9 @@ static int atl1_intr_tx(struct atl1_adapter *adapter) sw_tpd_next_to_clean = atomic_read(&tpd_ring->next_to_clean); cmb_tpd_next_to_clean = le16_to_cpu(adapter->cmb.cmb->tpd_cons_idx); + if (unlikely(cmb_tpd_next_to_clean >= tpd_ring->count)) + cmb_tpd_next_to_clean = sw_tpd_next_to_clean; + while (cmb_tpd_next_to_clean != sw_tpd_next_to_clean) { buffer_info = &tpd_ring->buffer_info[sw_tpd_next_to_clean]; if (buffer_info->dma) { From 77b1718e39e5c9f6956fb60807326af20baf889d Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 21 Sep 2026 21:46:05 -0400 Subject: [PATCH 2309/2366] bna: prevent IOC timer rearm during teardown bna: prevent IOC timer rearm during teardown bnad_pci_remove() and the probe disable_ioceth path call timer_delete_sync() for ioc_timer, sem_timer and hb_timer, but not for iocpf_timer. bnad_iocpf_timeout() then takes bnad->bna_lock after free_netdev() has freed the struct bnad. Deleting iocpf_timer last does not fix this. sem_timer and iocpf_timer rearm each other: bnad_iocpf_sem_timeout() can arm iocpf_timer, and bnad_iocpf_timeout() arms sem_timer from bfa_ioc_hw_sem_get() when the semaphore is busy. timer_delete_sync() only waits out its own callback. bnad_ioceth_disable() can time out and leave that callback live. Shut all four IOC timers down with timer_shutdown_sync() on both paths, so a later mod_timer() is ignored. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 1d32f7696286 ("bna: IOC failure auto recovery fix") Cc: stable@vger.kernel.org Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260922014605.588040-1-mhun512@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/brocade/bna/bnad.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/brocade/bna/bnad.c b/drivers/net/ethernet/brocade/bna/bnad.c index 8b75004ba7c9..55dfd4896785 100644 --- a/drivers/net/ethernet/brocade/bna/bnad.c +++ b/drivers/net/ethernet/brocade/bna/bnad.c @@ -2571,6 +2571,22 @@ bnad_ioceth_disable(struct bnad *bnad) return err; } +/* + * The IOC timers rearm one another, so deleting one cannot stop a + * sibling callback from arming it again. Shut them down so a later + * mod_timer() is ignored. + */ +static void +bnad_ioc_timers_shutdown(struct bnad *bnad) +{ + struct bfa_ioc *ioc = &bnad->bna.ioceth.ioc; + + timer_shutdown_sync(&ioc->ioc_timer); + timer_shutdown_sync(&ioc->sem_timer); + timer_shutdown_sync(&ioc->hb_timer); + timer_shutdown_sync(&ioc->iocpf_timer); +} + static int bnad_ioceth_enable(struct bnad *bnad) { @@ -3727,9 +3743,7 @@ bnad_pci_probe(struct pci_dev *pdev, bnad_res_free(bnad, &bnad->mod_res_info[0], BNA_MOD_RES_T_MAX); disable_ioceth: bnad_ioceth_disable(bnad); - timer_delete_sync(&bnad->bna.ioceth.ioc.ioc_timer); - timer_delete_sync(&bnad->bna.ioceth.ioc.sem_timer); - timer_delete_sync(&bnad->bna.ioceth.ioc.hb_timer); + bnad_ioc_timers_shutdown(bnad); spin_lock_irqsave(&bnad->bna_lock, flags); bna_uninit(bna); spin_unlock_irqrestore(&bnad->bna_lock, flags); @@ -3770,9 +3784,7 @@ bnad_pci_remove(struct pci_dev *pdev) mutex_lock(&bnad->conf_mutex); bnad_ioceth_disable(bnad); - timer_delete_sync(&bnad->bna.ioceth.ioc.ioc_timer); - timer_delete_sync(&bnad->bna.ioceth.ioc.sem_timer); - timer_delete_sync(&bnad->bna.ioceth.ioc.hb_timer); + bnad_ioc_timers_shutdown(bnad); spin_lock_irqsave(&bnad->bna_lock, flags); bna_uninit(bna); spin_unlock_irqrestore(&bnad->bna_lock, flags); From 58eb1b3325edac42dc6df72c80962bd53a3c8ca7 Mon Sep 17 00:00:00 2001 From: Dongliang Qin Date: Tue, 22 Sep 2026 11:15:45 +0800 Subject: [PATCH 2310/2366] rds: ib: Clear the sg list when mapping an MR fails rds_ib_map_frmr() stores the caller's scatterlist in the MR before DMA mapping and registration can fail. On failure, __rds_rdma_map() unpins the pages and frees the scatterlist, but rds_ib_free_frmr() can still return the MR to the pool with the stale pointer set. This leaves the pool with a dangling scatterlist and can lead to local privilege escalation. KASAN detects the resulting use-after-free when the MR is later torn down: BUG: KASAN: slab-use-after-free in __rds_ib_teardown_mr Read of size 8 Call Trace: __rds_ib_teardown_mr rds_ib_unreg_frmr rds_ib_flush_mr_pool rds_ib_flush_mrs rds_free_mr rds_setsockopt Store the scatterlist in the MR only after DMA mapping succeeds. If DMA mapping fails, return directly while the MR fields remain clear; the caller keeps ownership of the scatterlist and its pinned pages. If a later registration step fails, unmap the scatterlist and clear the MR fields before returning. Fixes: 1659185fb4d0 ("RDS: IB: Support Fastreg MR (FRMR) memory registration mode") Cc: stable@vger.kernel.org Signed-off-by: Dongliang Qin Reviewed-by: Allison Henderson Link: https://patch.msgid.link/20260922031546.3874605-1-cccccccccccc777777@gmail.com Signed-off-by: Paolo Abeni --- net/rds/ib_frmr.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/net/rds/ib_frmr.c b/net/rds/ib_frmr.c index bd861191157b..8397aa4a17ac 100644 --- a/net/rds/ib_frmr.c +++ b/net/rds/ib_frmr.c @@ -204,19 +204,16 @@ static int rds_ib_map_frmr(struct rds_ib_device *rds_ibdev, */ rds_ib_teardown_mr(ibmr); - ibmr->sg = sg; - ibmr->sg_len = sg_len; - ibmr->sg_dma_len = 0; frmr->sg_byte_len = 0; - WARN_ON(ibmr->sg_dma_len); - ibmr->sg_dma_len = ib_dma_map_sg(dev, ibmr->sg, ibmr->sg_len, + ibmr->sg_dma_len = ib_dma_map_sg(dev, sg, sg_len, DMA_BIDIRECTIONAL); if (unlikely(!ibmr->sg_dma_len)) { pr_warn("RDS/IB: %s failed!\n", __func__); return -EBUSY; } - frmr->sg_byte_len = 0; + ibmr->sg = sg; + ibmr->sg_len = sg_len; frmr->dma_npages = 0; len = 0; @@ -264,6 +261,8 @@ static int rds_ib_map_frmr(struct rds_ib_device *rds_ibdev, ib_dma_unmap_sg(rds_ibdev->dev, ibmr->sg, ibmr->sg_len, DMA_BIDIRECTIONAL); ibmr->sg_dma_len = 0; + ibmr->sg = NULL; + ibmr->sg_len = 0; return ret; } From c2de369c5c5b8599ca10fd5ca8d11fcd845c1331 Mon Sep 17 00:00:00 2001 From: Haseeb Malik Date: Mon, 21 Sep 2026 16:40:30 -0400 Subject: [PATCH 2311/2366] macsec: initialize SecY before registering the netdevice Creating a MACsec device with MAC offload over an LRO-capable lower device triggers a warning in rtmsg_ifinfo_build_skb() when IPv4 forwarding is enabled by default. register_netdevice() invokes inetdev_init(), which disables LRO and emits a NETDEV_FEAT_CHANGE notification. This reaches macsec_fill_info() before macsec_add_dev() initializes the SecY. key_len is still zero, so macsec_fill_info() returns -EMSGSIZE and trips the WARN_ON in rtmsg_ifinfo_build_skb(), even though the skb has enough space. Even without the warning, notifications during registration can report uninitialized SecY attributes, including the SCI. This ordering has existed since the driver was introduced. Initialize the SecY and apply the new-link attributes before registration. Move MAC address inheritance into macsec_newlink() so the SCI can also be initialized before registration-time notifications report it. Move the per-CPU statistics and metadata destination allocation into ndo_init(), and release partial allocations on failure. Fixes: c09440f7dcb3 ("macsec: introduce IEEE 802.1AE driver") Reported-by: syzbot+f2f6312ad1b5a0bfe316@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f2f6312ad1b5a0bfe316 Suggested-by: Sabrina Dubroca Link: https://lists.openwall.net/linux-kernel/2026/08/19/552 Signed-off-by: Haseeb Malik Reviewed-by: Sabrina Dubroca Link: https://patch.msgid.link/20260921-fix-macsec-net-v3-1-accf94f93f5e@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/macsec.c | 84 +++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c index 6f9f3aceffaa..78a19b134632 100644 --- a/drivers/net/macsec.c +++ b/drivers/net/macsec.c @@ -3539,6 +3539,22 @@ static int macsec_dev_init(struct net_device *dev) if (err) return err; + err = -ENOMEM; + macsec->stats = netdev_alloc_pcpu_stats(struct pcpu_secy_stats); + if (!macsec->stats) + goto destroy_gro_cells; + + macsec->secy.tx_sc.stats = + netdev_alloc_pcpu_stats(struct pcpu_tx_sc_stats); + if (!macsec->secy.tx_sc.stats) + goto free_secy_stats; + + macsec->secy.tx_sc.md_dst = metadata_dst_alloc(0, METADATA_MACSEC, + GFP_KERNEL); + if (!macsec->secy.tx_sc.md_dst) + goto free_tx_sc_stats; + macsec->secy.tx_sc.md_dst->u.macsec_info.sci = macsec->secy.sci; + macsec_inherit_tso_max(dev); dev->hw_features = real_dev->hw_features & MACSEC_OFFLOAD_FEATURES; @@ -3551,8 +3567,6 @@ static int macsec_dev_init(struct net_device *dev) macsec_set_head_tail_room(dev); - if (is_zero_ether_addr(dev->dev_addr)) - eth_hw_addr_inherit(dev, real_dev); if (is_zero_ether_addr(dev->broadcast)) memcpy(dev->broadcast, real_dev->broadcast, dev->addr_len); @@ -3560,6 +3574,14 @@ static int macsec_dev_init(struct net_device *dev) netdev_hold(real_dev, &macsec->dev_tracker, GFP_KERNEL); return 0; + +free_tx_sc_stats: + free_percpu(macsec->secy.tx_sc.stats); +free_secy_stats: + free_percpu(macsec->stats); +destroy_gro_cells: + gro_cells_destroy(&macsec->gro_cells); + return err; } static void macsec_dev_uninit(struct net_device *dev) @@ -4116,26 +4138,11 @@ static sci_t dev_to_sci(struct net_device *dev, __be16 port) return make_sci(dev->dev_addr, port); } -static int macsec_add_dev(struct net_device *dev, sci_t sci, u8 icv_len) +static void macsec_init_secy(struct net_device *dev, sci_t sci, u8 icv_len) { struct macsec_dev *macsec = macsec_priv(dev); struct macsec_secy *secy = &macsec->secy; - macsec->stats = netdev_alloc_pcpu_stats(struct pcpu_secy_stats); - if (!macsec->stats) - return -ENOMEM; - - secy->tx_sc.stats = netdev_alloc_pcpu_stats(struct pcpu_tx_sc_stats); - if (!secy->tx_sc.stats) - return -ENOMEM; - - secy->tx_sc.md_dst = metadata_dst_alloc(0, METADATA_MACSEC, GFP_KERNEL); - if (!secy->tx_sc.md_dst) - /* macsec and secy percpu stats will be freed when unregistering - * net_device in macsec_free_netdev() - */ - return -ENOMEM; - if (sci == MACSEC_UNDEF_SCI) sci = dev_to_sci(dev, MACSEC_PORT_ES); @@ -4149,15 +4156,12 @@ static int macsec_add_dev(struct net_device *dev, sci_t sci, u8 icv_len) secy->xpn = DEFAULT_XPN; secy->sci = sci; - secy->tx_sc.md_dst->u.macsec_info.sci = sci; secy->tx_sc.active = true; secy->tx_sc.encoding_sa = DEFAULT_ENCODING_SA; secy->tx_sc.encrypt = DEFAULT_ENCRYPT; secy->tx_sc.send_sci = DEFAULT_SEND_SCI; secy->tx_sc.end_station = false; secy->tx_sc.scb = false; - - return 0; } static struct lock_class_key macsec_netdev_addr_lock_key; @@ -4220,6 +4224,24 @@ static int macsec_newlink(struct net_device *dev, if (rx_handler && rx_handler != macsec_handle_frame) return -EBUSY; + if (is_zero_ether_addr(dev->dev_addr)) + eth_hw_addr_inherit(dev, real_dev); + + if (data && data[IFLA_MACSEC_SCI]) + sci = nla_get_sci(data[IFLA_MACSEC_SCI]); + else if (data && data[IFLA_MACSEC_PORT]) + sci = dev_to_sci(dev, nla_get_be16(data[IFLA_MACSEC_PORT])); + else + sci = dev_to_sci(dev, MACSEC_PORT_ES); + + /* Registration can notify listeners before returning. */ + macsec_init_secy(dev, sci, icv_len); + if (data) { + err = macsec_changelink_common(dev, data); + if (err) + return err; + } + err = register_netdevice(dev); if (err < 0) return err; @@ -4232,31 +4254,11 @@ static int macsec_newlink(struct net_device *dev, if (err < 0) goto unregister; - /* need to be already registered so that ->init has run and - * the MAC addr is set - */ - if (data && data[IFLA_MACSEC_SCI]) - sci = nla_get_sci(data[IFLA_MACSEC_SCI]); - else if (data && data[IFLA_MACSEC_PORT]) - sci = dev_to_sci(dev, nla_get_be16(data[IFLA_MACSEC_PORT])); - else - sci = dev_to_sci(dev, MACSEC_PORT_ES); - if (rx_handler && sci_exists(real_dev, sci)) { err = -EBUSY; goto unlink; } - err = macsec_add_dev(dev, sci, icv_len); - if (err) - goto unlink; - - if (data) { - err = macsec_changelink_common(dev, data); - if (err) - goto del_dev; - } - /* If h/w offloading is available, propagate to the device */ if (macsec_is_offloaded(macsec)) { const struct macsec_ops *ops; From 15814c01ac57643edf1662a076d2961332f277a5 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Wed, 23 Sep 2026 21:44:30 -0400 Subject: [PATCH 2312/2366] drm/amd/display: Bump frame warning limit for all builds of dml Some configs with gcc are also now affected. Signed-off-by: Alex Deucher (cherry picked from commit 76b9706e7fa1a6e374703170128b1be2f590cda7) --- drivers/gpu/drm/amd/display/dc/dml/Makefile | 6 +----- drivers/gpu/drm/amd/display/dc/dml2_0/Makefile | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml/Makefile b/drivers/gpu/drm/amd/display/dc/dml/Makefile index 79eeb3721b99..91465ac05c97 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml/Makefile @@ -36,11 +36,7 @@ ifneq ($(CONFIG_FRAME_WARN),0) frame_warn_limit := 3072 endif else - ifeq ($(CONFIG_CC_IS_CLANG),y) - frame_warn_limit := 3072 - else - frame_warn_limit := 2048 - endif + frame_warn_limit := 3072 endif ifeq ($(call test-lt, $(CONFIG_FRAME_WARN), $(frame_warn_limit)),y) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile index 4d682e92df1d..39ee2d1999dd 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile @@ -35,7 +35,7 @@ ifneq ($(CONFIG_FRAME_WARN),0) frame_warn_limit := 3072 endif else - frame_warn_limit := 2056 + frame_warn_limit := 3072 endif ifeq ($(call test-lt, $(CONFIG_FRAME_WARN), $(frame_warn_limit)),y) From d6ec384c87cc851cfd13bb18c99ce351ccee6192 Mon Sep 17 00:00:00 2001 From: Fang Xieyan Date: Mon, 21 Sep 2026 20:54:41 +0800 Subject: [PATCH 2313/2366] net/sched: act_ife: validate metadata length before decoding skbmark_decode(), skbprio_decode() and skbtcindex_decode() read fixed-size values from the TLV payload without validating its length. A malformed IFE frame can declare a shorter payload, causing the decoders to consume bytes beyond the declared metadata value: [TLV type=IFE_META_SKBMARK len=4] -> dlen == 0, but decode reads 4 bytes The decoder may therefore set skb metadata from unintended input. Validate the payload length before decoding and return -EINVAL for invalid lengths. Read the values with get_unaligned_be32() and get_unaligned_be16(), as TLV payloads are not guaranteed to be aligned. Teach tcf_ife_decode() to log a decoder error separately from an unknown metaid; both are counted as overlimits and decoding continues with the remaining metadata. The metadata length issue was found by an automated audit of the IFE decode path at v6.18-rc7 and reproduced with a userspace sanitizer model of the decode path. Compile-tested on x86_64 with defconfig and NET_ACT_IFE=y: act_ife.o and the three act_meta_*.o build warning-free. Fixes: 084e2f6566d2 ("Support to encoding decoding skb mark on IFE action") Fixes: 200e10f46936 ("Support to encoding decoding skb prio on IFE action") Fixes: 408fbc22ef1e ("net sched ife action: Introduce skb tcindex metadata encap decap") Assisted-by: Hawkeye:GLM-5.3-flash Assisted-by: Qoder:Qwen3.8-Max Signed-off-by: Fang Xieyan Link: https://patch.msgid.link/20260921125441.81459-1-fangxy@xiaopeng.com Signed-off-by: Paolo Abeni --- net/sched/act_ife.c | 17 ++++++++++++----- net/sched/act_meta_mark.c | 6 ++++-- net/sched/act_meta_skbprio.c | 6 ++++-- net/sched/act_meta_skbtcindex.c | 6 ++++-- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c index 9cea71fc1db3..2afd68983ece 100644 --- a/net/sched/act_ife.c +++ b/net/sched/act_ife.c @@ -737,6 +737,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a, u8 *curr_data; u16 mtype; u16 dlen; + int ret; curr_data = ife_tlv_meta_decode(tlv_data, ifehdr_end, &mtype, &dlen, NULL); @@ -745,13 +746,19 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a, return TC_ACT_SHOT; } - if (find_decode_metaid(skb, p, mtype, dlen, curr_data)) { - /* abuse overlimits to count when we receive metadata - * but dont have an ops for it + ret = find_decode_metaid(skb, p, mtype, dlen, curr_data); + if (ret < 0) { + /* abuse overlimits to count metadata we cannot + * decode: no ops for it, or the decoder rejected it */ - pr_info_ratelimited("Unknown metaid %d dlen %d\n", - mtype, dlen); qstats_cpu_overlimit_inc(ife->common.cpu_qstats); + + if (ret == -ENOENT) + pr_info_ratelimited("Unknown metaid %d dlen %d\n", + mtype, dlen); + else + pr_info_ratelimited("Failed to decode metaid %d dlen %d err %d\n", + mtype, dlen, ret); } } diff --git a/net/sched/act_meta_mark.c b/net/sched/act_meta_mark.c index ea0573cb8b2d..e2f61b22bf0f 100644 --- a/net/sched/act_meta_mark.c +++ b/net/sched/act_meta_mark.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -28,9 +29,10 @@ static int skbmark_encode(struct sk_buff *skb, void *skbdata, static int skbmark_decode(struct sk_buff *skb, void *data, u16 len) { - u32 ifemark = *(u32 *)data; + if (len != sizeof(u32)) + return -EINVAL; - skb->mark = ntohl(ifemark); + skb->mark = get_unaligned_be32(data); return 0; } diff --git a/net/sched/act_meta_skbprio.c b/net/sched/act_meta_skbprio.c index 2df3133ce5ad..5cdb57931eab 100644 --- a/net/sched/act_meta_skbprio.c +++ b/net/sched/act_meta_skbprio.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -33,9 +34,10 @@ static int skbprio_encode(struct sk_buff *skb, void *skbdata, static int skbprio_decode(struct sk_buff *skb, void *data, u16 len) { - u32 ifeprio = *(u32 *)data; + if (len != sizeof(u32)) + return -EINVAL; - skb->priority = ntohl(ifeprio); + skb->priority = get_unaligned_be32(data); return 0; } diff --git a/net/sched/act_meta_skbtcindex.c b/net/sched/act_meta_skbtcindex.c index 44547caead46..8803710c0905 100644 --- a/net/sched/act_meta_skbtcindex.c +++ b/net/sched/act_meta_skbtcindex.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -28,9 +29,10 @@ static int skbtcindex_encode(struct sk_buff *skb, void *skbdata, static int skbtcindex_decode(struct sk_buff *skb, void *data, u16 len) { - u16 ifetc_index = *(u16 *)data; + if (len != sizeof(u16)) + return -EINVAL; - skb->tc_index = ntohs(ifetc_index); + skb->tc_index = get_unaligned_be16(data); return 0; } From 7c9f391ec89cb621d7af375ace2eb9a6248e5b9d Mon Sep 17 00:00:00 2001 From: Christian Lamparter Date: Mon, 21 Sep 2026 18:28:17 +0200 Subject: [PATCH 2314/2366] net: emac: move setting of netops to fix crash fixes the following crash on driver initialization: |BUG: Kernel NULL pointer dereference on read at 0x00000158 |Faulting instruction address: 0xc0566b40 |Oops: Kernel access of bad area, sig: 11 [#1] |BE PAGE_SIZE=4K PowerPC 44x Platform |Modules linked in: |CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Tainted: GW 7.3.0-rc3+ #1 |Tainted: [W]=WARN |Hardware name: MyBook Live APM821XX 0x12c41c83 PowerPC 44x Platform |NIP: c0566b40 LR: c05648f8 CTR: c04c1e9c |REGS: c1053a20 TRAP: 0300 Tainted: GW (7.3.0-rc3+) |MSR: 0002b000 CR: 24008808 XER: 00000000 |DEAR: 00000158 ESR: 00000000 |GPR00: c05648f8 c1053b10 c1063600 c1030000 c5ab3000 00000000 [...] |GPR08: 00000002 00000000 00000000 c1053b40 84002808 00000000 [...] |GPR16: cfffd210 00000002 c0beafcc cfffc960 00000000 c1030644 [...] |GPR24: c0beafbc c1037000 00000000 0000000a 00000000 c1030000 [...] |NIP [c0566b40] phy_link_topo_add_phy+0x2c/0x1d0 |LR [c05648f8] phy_attach_direct+0x1a4/0x368 |Call Trace: |[c1053b10] [c0811e04] klist_put+0x54/0xb4 (unreliable) |[c1053b40] [c05648f8] phy_attach_direct+0x1a4/0x368 |[c1053b70] [c0564ae8] phy_connect_direct+0x2c/0x60 |[c1053b90] [c056c7a8] of_phy_connect+0x50/0x74 |[c1053bc0] [c0572a88] emac_probe+0xd50/0x119c |[c1053c90] [c04cb770] platform_probe+0x74/0xa4 |[c1053cb0] [c04c8f08] really_probe+0x120/0x2b0 |[c1053cd0] [c04c9254] __driver_probe_device+0x1bc/0x1fc |[c1053d00] [c04c9334] driver_probe_device+0x38/0xa8 |[c1053d30] [c04c9560] __driver_attach+0xf4/0x10c |[c1053d50] [c04c6af8] bus_for_each_dev+0x68/0xd0 |[c1053d90] [c04c7c34] bus_add_driver+0xcc/0x1ec |[c1053dc0] [c04c9f6c] driver_register+0xcc/0x110 |[c1053de0] [c0aa3f40] emac_init+0x1c4/0x200 This bug showed up starting with v7.3-rc1. At the NIP in phy_link_topo_add_phy() is a netdev_need_ops_lock() check. This was added by the following commit ded86da4bbb7 ("net: ethtool: relax ethnl_req_get_phydev() locking assertion") The bug shows up because at the time of_phy_connect() was called, the netdev_ops were *not yet* determined. My fix is to move the code that sets netdev_ops+commac.ops+ethtool_ops further up as emac_init_config() derives that by looking at the device-tree and sets the required dev->phy_mode accordingly. During review, the Sashiko bot's AI stated that the commac assignment became a dead store. Great catch! To keep the original behavior as-is, one mentioned option "set dev->commac.ops = &emac_commac_ops only in the non-gige case?" sounded like a great plan. So the dev->commac.ops assignment for the non-gige-case moves into the else block. This patch was tested on a WD MyBook Live (RGMII). the device now works again. Fixes: ded86da4bbb7 ("net: ethtool: relax ethnl_req_get_phydev() locking assertion") Signed-off-by: Christian Lamparter Reviewed-by: Nicolai Buchwitz Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/49cd7343bc0e507c022071f3e2b5662b053dca73.1790007431.git.chunkeey@gmail.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/ibm/emac/core.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/ibm/emac/core.c b/drivers/net/ethernet/ibm/emac/core.c index 1d46cf6c2c12..e7043523457c 100644 --- a/drivers/net/ethernet/ibm/emac/core.c +++ b/drivers/net/ethernet/ibm/emac/core.c @@ -3044,6 +3044,15 @@ static int emac_probe(struct platform_device *ofdev) if (err) goto err_gone; + if (emac_phy_supports_gige(dev->phy_mode)) { + ndev->netdev_ops = &emac_gige_netdev_ops; + dev->commac.ops = &emac_commac_sg_ops; + } else { + ndev->netdev_ops = &emac_netdev_ops; + dev->commac.ops = &emac_commac_ops; + } + ndev->ethtool_ops = &emac_ethtool_ops; + dev->emacp = devm_platform_ioremap_resource(ofdev, 0); if (IS_ERR(dev->emacp)) { err = PTR_ERR(dev->emacp); @@ -3076,7 +3085,6 @@ static int emac_probe(struct platform_device *ofdev) dev->mdio_instance = platform_get_drvdata(dev->mdio_dev); /* Register with MAL */ - dev->commac.ops = &emac_commac_ops; dev->commac.dev = dev; dev->commac.tx_chan_mask = MAL_CHAN_MASK(dev->mal_tx_chan); dev->commac.rx_chan_mask = MAL_CHAN_MASK(dev->mal_rx_chan); @@ -3144,12 +3152,6 @@ static int emac_probe(struct platform_device *ofdev) ndev->features |= ndev->hw_features | NETIF_F_RXCSUM; } ndev->watchdog_timeo = 5 * HZ; - if (emac_phy_supports_gige(dev->phy_mode)) { - ndev->netdev_ops = &emac_gige_netdev_ops; - dev->commac.ops = &emac_commac_sg_ops; - } else - ndev->netdev_ops = &emac_netdev_ops; - ndev->ethtool_ops = &emac_ethtool_ops; /* MTU range: 46 - 1500 or whatever is in OF */ ndev->min_mtu = EMAC_MIN_MTU; From e66cf1625ec4a3fe68346119f371def713fd0a4d Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 23 Sep 2026 20:25:49 +0900 Subject: [PATCH 2315/2366] smb: client: use finish_no_open() for non-regular inodes An O_CREAT open can find an existing symlink or another non-regular inode. cifs_atomic_open() calls finish_open() on it and attaches a cifsFileInfo. Symlink inodes have no CIFS release operation, so the dentry reference held by cifsFileInfo is leaked. FMODE_OPENED also prevents the VFS from following the symlink. Track whether cifs_do_create() returned an open server handle. For non-regular inodes, close the handle if present, remove the pending open, and call finish_no_open() so the VFS can continue the lookup. Do not set FMODE_CREATED unless a regular file was opened. For O_NOFOLLOW with __O_REGULAR, return -ELOOP before the VFS's -EFTYPE check. Defer closing a legacy POSIX handle on a non-regular inode until after inode lookup. This avoids closing it again if lookup fails. Fixes: d2c127197dfc ("cifs: implement i_op->atomic_open()") Signed-off-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: stable@vger.kernel.org --- fs/smb/client/dir.c | 52 +++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 6fa6d48fdfd3..1a56fa4d0e89 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -199,7 +199,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, struct tcon_link *tlink, unsigned int oflags, umode_t mode, __u32 *oplock, struct cifs_fid *fid, struct cifs_open_info_data *buf, - struct inode **inode) + struct inode **inode, bool *opened) { int rc = -ENOENT; int create_options = CREATE_NOT_DIR; @@ -216,6 +216,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, __le32 lease_flags = 0; *inode = NULL; + *opened = false; *oplock = 0; if (tcon->ses->server->oplocks) *oplock = REQ_OPLOCK; @@ -232,6 +233,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, oflags, oplock, &fid->netfid, xid); switch (rc) { case 0: + *opened = true; if (newinode == NULL) { /* query inode info */ goto cifs_create_get_file_info; @@ -253,11 +255,9 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, /* * The server may allow us to open things like * FIFOs, but the client isn't set up to deal - * with that. If it's not a regular file, just - * close it and proceed as if it were a normal - * lookup. + * with that. Keep the handle until the caller + * can finish the lookup. */ - CIFSSMBClose(xid, tcon, fid->netfid); goto cifs_create_get_file_info; } /* success, no need to query */ @@ -384,6 +384,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, } return rc; } + *opened = true; if (rdwr_for_fscache == 2) cifs_invalidate_cache(dir, FSCACHE_INVAL_DIO_WRITE); @@ -475,7 +476,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, return rc; out_err: - if (server->ops->close) + if (*opened && server->ops->close) server->ops->close(xid, tcon, fid); if (newinode) iput(newinode); @@ -487,7 +488,7 @@ static int cifs_do_create(struct inode *dir, struct dentry *direntry, unsigned int oflags, umode_t mode, __u32 *oplock, struct cifs_fid *fid, struct cifs_open_info_data *buf, - struct inode **inode) + struct inode **inode, bool *opened) { void *page = alloc_dentry_path(); const char *full_path; @@ -496,10 +497,11 @@ static int cifs_do_create(struct inode *dir, struct dentry *direntry, full_path = build_path_from_dentry(direntry, page); if (IS_ERR(full_path)) { rc = PTR_ERR(full_path); + *opened = false; } else { rc = __cifs_do_create(dir, direntry, full_path, xid, tlink, oflags, mode, oplock, - fid, buf, inode); + fid, buf, inode, opened); } free_dentry_path(page); return rc; @@ -529,6 +531,8 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry, struct inode *inode; unsigned int xid; __u32 oplock; + bool is_regular; + bool opened; int rc; if (unlikely(cifs_forced_shutdown(cifs_sb))) @@ -581,12 +585,26 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry, cifs_add_pending_open(&fid, tlink, &open); rc = cifs_do_create(dir, direntry, xid, tlink, oflags, mode, - &oplock, &fid, &buf, &inode); + &oplock, &fid, &buf, &inode, &opened); if (rc) { cifs_del_pending_open(&open); goto out; } + is_regular = S_ISREG(inode->i_mode); + if (!is_regular || !opened) { + if (opened && server->ops->close) + server->ops->close(xid, tcon, &fid); + cifs_del_pending_open(&open); + if (S_ISLNK(inode->i_mode) && + (oflags & (O_NOFOLLOW | __O_REGULAR)) == + (O_NOFOLLOW | __O_REGULAR) && !(oflags & O_EXCL)) { + iput(inode); + rc = -ELOOP; + goto out; + } + } + if (d_in_lookup(direntry)) { alias = d_splice_alias(inode, direntry); if (!IS_ERR_OR_NULL(alias)) @@ -595,9 +613,15 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry, d_instantiate(direntry, inode); } - if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) + if (is_regular && opened && + (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) file->f_mode |= FMODE_CREATED; + if (!is_regular || !opened) { + rc = finish_no_open(file, NULL); + goto out; + } + rc = finish_open(file, direntry, generic_file_open); if (rc) { if (server->ops->close) @@ -660,6 +684,7 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *dir, struct inode *inode; struct cifs_fid fid; __u32 oplock; + bool opened; struct cifs_open_info_data buf = {}; cifs_dbg(FYI, "cifs_create parent inode = 0x%p name is: %pd and dentry = 0x%p\n", @@ -682,10 +707,10 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *dir, server->ops->new_lease_key(&fid); rc = cifs_do_create(dir, direntry, xid, tlink, oflags, - mode, &oplock, &fid, &buf, &inode); + mode, &oplock, &fid, &buf, &inode, &opened); if (!rc) { d_instantiate(direntry, inode); - if (server->ops->close) + if (opened && server->ops->close) server->ops->close(xid, tcon, &fid); } @@ -1078,6 +1103,7 @@ int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, struct inode *inode; unsigned int xid; __u32 oplock; + bool opened; int namelen; int rc; @@ -1116,7 +1142,7 @@ int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, namelen = scnprintf(name, namesize, CIFS_TMPNAME_PREFIX "%x", atomic_inc_return(&cifs_tmpcounter)); rc = __cifs_do_create(dir, dentry, path, xid, tlink, oflags, - mode, &oplock, &fid, NULL, &inode); + mode, &oplock, &fid, NULL, &inode, &opened); if (!rc) { rc = d_mark_tmpfile_name(file, &QSTR_LEN(name, namelen)); if (rc) { From c3a66e5f5bab3912e9f84223c5a982bf4333d5a1 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Thu, 24 Sep 2026 19:23:20 +0900 Subject: [PATCH 2316/2366] bpf: Zero-fill other CPUs when BPF_F_CPU creates a per-cpu hash element pcpu_init_value() initializes the per-cpu area of a newly created [lru_]percpu_hash element. The area is recycled, so when the value comes from a BPF program (onallcpus == false) it writes the running CPU's slot and zeroes the rest. bpf_percpu_hash_update() passes onallcpus == true, which delegates to pcpu_copy_value(). pcpu_copy_value() writes only the CPU named in map_flags when BPF_F_CPU is set, so on the create path the other slots keep the recycled element's values: update(k1, 0xdeadc0de, BPF_F_ALL_CPUS) every CPU holds 0xdeadc0de delete(k1) element back on the freelist update(k2, 0xc0ffee, BPF_F_CPU | 0) creates, writes CPU 0 only lookup(k2) CPU 0 0xc0ffee, rest 0xdeadc0de Zero-fill the other CPUs on that arm too. Fixes: c6936161fd55 ("bpf: Add BPF_F_CPU and BPF_F_ALL_CPUS flags support for percpu_hash and lru_percpu_hash maps") Signed-off-by: Donggeun Yoo Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260924102321.2120434-2-donggeunyoo.kernel@gmail.com --- kernel/bpf/hashtab.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index 4f495dcbf670..f9464e566f10 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -1054,14 +1054,17 @@ static void pcpu_init_value(struct bpf_htab *htab, void __percpu *pptr, /* When not setting the initial value on all cpus, zero-fill element * values for other cpus. Otherwise, bpf program has no way to ensure * known initial values for cpus other than current one - * (onallcpus=false always when coming from bpf prog). + * (onallcpus=false always when coming from bpf prog, + * map_flags & BPF_F_CPU when coming from syscall but setting + * only one cpu). */ - if (!onallcpus) { - int current_cpu = raw_smp_processor_id(); + if (!onallcpus || (map_flags & BPF_F_CPU)) { + int init_cpu = (map_flags & BPF_F_CPU) ? map_flags >> 32 : + raw_smp_processor_id(); int cpu; for_each_possible_cpu(cpu) { - if (cpu == current_cpu) + if (cpu == init_cpu) copy_map_value(&htab->map, per_cpu_ptr(pptr, cpu), value); else /* Since elem is preallocated, we cannot touch special fields */ zero_map_value(&htab->map, per_cpu_ptr(pptr, cpu)); From 3422808f4e959266ea7790101ac90b8341f48226 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Thu, 24 Sep 2026 19:23:21 +0900 Subject: [PATCH 2317/2366] selftests/bpf: Test per-cpu initialization of a BPF_F_CPU created element The existing cpu_flag subtests always prime a key with BPF_F_ALL_CPUS before any BPF_F_CPU write, so the create path is never covered. Add a subtest that creates the element with BPF_F_CPU on a map with max_entries 1, so the key can only reuse the element the previous key released, and check that the CPUs the update did not name read back zero. Run it for PERCPU_HASH preallocated and BPF_F_NO_PREALLOC, whose per-cpu areas come from different allocators, and for LRU_PERCPU_HASH. Under BPF_F_NO_PREALLOC the reuse is only guaranteed on the cpu that ran the delete, so pin the thread across the pair, and name a cpu other than that one in map_flags. Signed-off-by: Donggeun Yoo Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260924102321.2120434-3-donggeunyoo.kernel@gmail.com --- .../selftests/bpf/prog_tests/percpu_alloc.c | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c b/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c index a72ae0b29f6e..7b4a1e24363b 100644 --- a/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c +++ b/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c @@ -1,4 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include #include #include "cgroup_helpers.h" #include "percpu_alloc_array.skel.h" @@ -350,6 +352,103 @@ static void test_lru_percpu_hash_cpu_flag(void) test_percpu_map_cpu_flag(BPF_MAP_TYPE_LRU_PERCPU_HASH); } +/* + * A BPF_F_CPU update that creates an element must zero the value on the other + * cpus, rather than leave them holding whatever the recycled element last + * contained. max_entries is 1 so the second key can only reuse the element + * the first one released. + */ +static void test_percpu_map_cpu_flag_create(enum bpf_map_type map_type, __u32 map_flags) +{ + LIBBPF_OPTS(bpf_map_create_opts, opts, .map_flags = map_flags); + const u32 stale = 0xDEADC0DE, fresh = 0xC0FFEE; + int nr_cpus, cpu, map_fd, err, key; + int pinned_cpu, value_cpu; + cpu_set_t old_mask, new_mask; + bool restore_mask = false; + u32 value; + u64 flags; + + nr_cpus = libbpf_num_possible_cpus(); + if (!ASSERT_GT(nr_cpus, 0, "libbpf_num_possible_cpus")) + return; + + if (nr_cpus < 2) { + test__skip(); + return; + } + + map_fd = bpf_map_create(map_type, "cpu_flag_create", sizeof(key), sizeof(value), 1, &opts); + if (!ASSERT_GE(map_fd, 0, "bpf_map_create")) + return; + + /* NO_PREALLOC recycles per cpu, so keep the delete and the create on one cpu. */ + err = sched_getaffinity(0, sizeof(old_mask), &old_mask); + if (!ASSERT_OK(err, "sched_getaffinity")) + goto out; + + pinned_cpu = sched_getcpu(); + if (!ASSERT_GE(pinned_cpu, 0, "sched_getcpu")) + goto out; + + CPU_ZERO(&new_mask); + CPU_SET(pinned_cpu, &new_mask); + err = sched_setaffinity(0, sizeof(new_mask), &new_mask); + if (!ASSERT_OK(err, "sched_setaffinity")) + goto out; + restore_mask = true; + + value_cpu = pinned_cpu ? 0 : 1; + + key = 1; + value = stale; + err = bpf_map_update_elem(map_fd, &key, &value, BPF_F_ALL_CPUS); + if (!ASSERT_OK(err, "bpf_map_update_elem all_cpus")) + goto out; + + err = bpf_map_delete_elem(map_fd, &key); + if (!ASSERT_OK(err, "bpf_map_delete_elem")) + goto out; + + key = 2; + value = fresh; + flags = (u64)value_cpu << 32 | BPF_F_CPU; + err = bpf_map_update_elem(map_fd, &key, &value, flags); + if (!ASSERT_OK(err, "bpf_map_update_elem specified cpu")) + goto out; + + for (cpu = 0; cpu < nr_cpus; cpu++) { + value = 0; + flags = (u64)cpu << 32 | BPF_F_CPU; + err = bpf_map_lookup_elem_flags(map_fd, &key, &value, flags); + if (!ASSERT_OK(err, "bpf_map_lookup_elem_flags specified cpu")) + goto out; + if (!ASSERT_EQ(value, cpu == value_cpu ? fresh : 0, "value on specified cpu")) + goto out; + } + +out: + if (restore_mask) + sched_setaffinity(0, sizeof(old_mask), &old_mask); + close(map_fd); +} + +static void test_percpu_hash_cpu_flag_create(void) +{ + test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_PERCPU_HASH, 0); +} + +static void test_percpu_hash_cpu_flag_create_malloc(void) +{ + test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_PERCPU_HASH, BPF_F_NO_PREALLOC); +} + +static void test_lru_percpu_hash_cpu_flag_create(void) +{ + /* lru without prealloc is -ENOTSUPP, so there is no malloc variant */ + test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_LRU_PERCPU_HASH, 0); +} + static void test_percpu_cgroup_storage_cpu_flag(void) { struct percpu_alloc_array *skel = NULL; @@ -454,6 +553,12 @@ void test_percpu_alloc(void) test_percpu_hash_cpu_flag(); if (test__start_subtest("cpu_flag_lru_percpu_hash")) test_lru_percpu_hash_cpu_flag(); + if (test__start_subtest("cpu_flag_create_percpu_hash")) + test_percpu_hash_cpu_flag_create(); + if (test__start_subtest("cpu_flag_create_percpu_hash_malloc")) + test_percpu_hash_cpu_flag_create_malloc(); + if (test__start_subtest("cpu_flag_create_lru_percpu_hash")) + test_lru_percpu_hash_cpu_flag_create(); if (test__start_subtest("cpu_flag_percpu_cgroup_storage")) test_percpu_cgroup_storage_cpu_flag(); if (test__start_subtest("cpu_flag_array")) From c2cdef41e0b4d8ed23a5b41e6ad4e64594e055e4 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 18 Sep 2026 04:50:19 +0300 Subject: [PATCH 2318/2366] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621 The core and io supplies are only requested for ID_MT7530: both the devm_regulator_get() in probe and the regulator_enable() in mt7530_setup() are guarded by the switch id, but mt7530_remove() disables them unconditionally. On an MT7621 or an MT7531 both pointers are still NULL from devm_kzalloc(), so rmmod or a sysfs unbind calls regulator_disable() on NULL. Fixes: ddda1ac116c8 ("net: dsa: mt7530: support the 7530 switch on the Mediatek MT7621 SoC") Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260918015020.2518315-2-f@lex.la Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530-mdio.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/net/dsa/mt7530-mdio.c b/drivers/net/dsa/mt7530-mdio.c index 784dd58a7158..de42f70afcfa 100644 --- a/drivers/net/dsa/mt7530-mdio.c +++ b/drivers/net/dsa/mt7530-mdio.c @@ -227,15 +227,17 @@ mt7530_remove(struct mdio_device *mdiodev) if (!priv) return; - ret = regulator_disable(priv->core_pwr); - if (ret < 0) - dev_err(priv->dev, - "Failed to disable core power: %d\n", ret); + if (priv->id == ID_MT7530) { + ret = regulator_disable(priv->core_pwr); + if (ret < 0) + dev_err(priv->dev, + "Failed to disable core power: %d\n", ret); - ret = regulator_disable(priv->io_pwr); - if (ret < 0) - dev_err(priv->dev, "Failed to disable io pwr: %d\n", - ret); + ret = regulator_disable(priv->io_pwr); + if (ret < 0) + dev_err(priv->dev, "Failed to disable io pwr: %d\n", + ret); + } mt7530_remove_common(priv); From 0d80ba0a204c6a16bd7778b50de578dff107c0fe Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 18 Sep 2026 04:50:20 +0300 Subject: [PATCH 2319/2366] net: dsa: mt7530: leave the MDIO IRQ mappings to regmap-irq mt7530_remove_common() disposes the per-PHY interrupt mappings from .remove, but the regmap-irq chip that owns the domain is devm-registered, so its parent interrupt is only freed once .remove has returned. The switch's own regmap-irq thread can therefore still dispatch on a mapping that is already gone: irq_find_mapping() returns 0, irq_to_desc() returns NULL and handle_nested_irq() locks desc->lock without checking it. The attached PHYs have not given those interrupts back yet either, which the kernel warns about a moment before the fault. regmap_del_irq_chip() disposes the same mappings itself, after freeing the parent interrupt and before removing the domain, so there is nothing left for the driver to do here. Until it runs the descriptors stay alive, and a late dispatch on one of them is harmless: dsa_unregister_switch() has freed the PHY handlers by then, so handle_nested_irq() finds no action and returns. Fixes: 254f6b272e3b ("dsa: mt7530: Utilize REGMAP_IRQ for interrupt handling") Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260918015020.2518315-3-f@lex.la Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mt7530.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 3e61eb3c2b1e..96832852c65a 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -3593,9 +3593,6 @@ EXPORT_SYMBOL_GPL(mt7530_probe_common); void mt7530_remove_common(struct mt7530_priv *priv) { - if (priv->irq_domain) - mt7530_free_mdio_irq(priv); - dsa_unregister_switch(priv->ds); mutex_destroy(&priv->reg_mutex); From 26cc0e69cce062cd3aa6fae33074684669c35a71 Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Mon, 21 Sep 2026 05:10:01 +0000 Subject: [PATCH 2320/2366] mctp: route: iterate socket tag list in mctp_lookup_prealloc_tag() When a socket transmits a packet with MCTP_TAG_PREALLOC set, mctp_lookup_prealloc_tag() iterates over the per-netns &mns->keys list and matches netid, req_tag, peer_addr, and manual_alloc, without checking whether tmp->sk == &msk->sk. This allows any MCTP socket in the same network namespace to use and consume another socket's preallocated tag. Iterate the socket's own tag list (&msk->keys via sklist) instead of the namespace-wide &mns->keys list in mctp_lookup_prealloc_tag(), ensuring that only tags allocated by msk are matched. Tested in QEMU against Linux 7.3.0-rc3 by allocating a manual tag (0x18) on socket A via SIOCMCTPALLOCTAG for peer EID 9 and sending a 4-byte message with MCTP_TAG_PREALLOC from socket B in the same network namespace. On the unfixed kernel, sendto(sock_b) using socket A's preallocated tag succeeds (ret = 4); with this patch applied, sendto(sock_b) fails with -ENOENT (errno = 2) while sendto(sock_a) succeeds (ret = 4). Fixes: 63ed1aab3d40 ("mctp: Add SIOCMCTP{ALLOC,DROP}TAG ioctls for tag control") Suggested-by: Jeremy Kerr Cc: stable@vger.kernel.org Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260921051002.1656692-1-benquike@gmail.com Signed-off-by: Jakub Kicinski --- net/mctp/route.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mctp/route.c b/net/mctp/route.c index b19c63a5691a..e7c95eeacb48 100644 --- a/net/mctp/route.c +++ b/net/mctp/route.c @@ -825,7 +825,7 @@ static struct mctp_sk_key *mctp_lookup_prealloc_tag(struct mctp_sock *msk, spin_lock_irqsave(&mns->keys_lock, flags); - hlist_for_each_entry(tmp, &mns->keys, hlist) { + hlist_for_each_entry(tmp, &msk->keys, sklist) { if (tmp->net != netid) continue; From fe99bbeee5c5dbd3abc30721a8079ced59649d97 Mon Sep 17 00:00:00 2001 From: Yilin Zhang Date: Thu, 24 Sep 2026 12:49:00 +0800 Subject: [PATCH 2321/2366] tcp: fix use-after-free of retransmit_skb_hint in tcp_send_synack() When tcp_send_synack() replaces the cloned SYN skb at the head of the retransmit queue with a copy, it frees the original with tcp_rtx_queue_unlink_and_free() and only repairs tp->highest_sack. tp->retransmit_skb_hint keeps pointing at the freed skbuff_fclone_cache object. The dangling hint is read in tcp_verify_retransmit_hint() and used as the root of the rbtree walk in tcp_xmit_retransmit_queue(). An unprivileged TFO client (sendmsg(MSG_FASTOPEN)) can arm the hint with an attacker-supplied ICMP fragmentation-needed message, after which a simultaneous open frees the armed SYN skb: BUG: KASAN: slab-use-after-free in tcp_mark_skb_lost (net/ipv4/tcp_input.c:1316) Read of size 4 at addr ffff88800604d928 by task swapper/1/0 Call Trace: tcp_mark_skb_lost (net/ipv4/tcp_input.c:1316) tcp_simple_retransmit (net/ipv4/tcp_input.c:3158) tcp_v4_err (net/ipv4/tcp_ipv4.c:587) Sync the hint to the copy. Fixes: c31b70c9968f ("tcp: Add logic to check for SYN w/ data in tcp_simple_retransmit") Reported-by: Kimi Security Team Tested-by: Weiming Shi Signed-off-by: Yilin Zhang Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/8a9dff4063a2745653b7e88ceb745d75efa16e68.1790224474.git.yilinzhang@moonshot.ai Signed-off-by: Jakub Kicinski --- net/ipv4/tcp_output.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index d960e3de7d50..e0c392e29de5 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -3886,6 +3886,7 @@ void tcp_send_active_reset(struct sock *sk, enum sk_rst_reason reason) */ int tcp_send_synack(struct sock *sk) { + struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *skb; skb = tcp_rtx_queue_head(sk); @@ -3903,6 +3904,8 @@ int tcp_send_synack(struct sock *sk) if (!nskb) return -ENOMEM; INIT_LIST_HEAD(&nskb->tcp_tsorted_anchor); + if (skb == tp->retransmit_skb_hint) + tp->retransmit_skb_hint = nskb; tcp_highest_sack_replace(sk, skb, nskb); tcp_rtx_queue_unlink_and_free(skb, sk); __skb_header_release(nskb); From 1765a153d985c231357145e26798f9408db10e42 Mon Sep 17 00:00:00 2001 From: Guopeng Zhang Date: Thu, 24 Sep 2026 17:15:36 +0800 Subject: [PATCH 2322/2366] cgroup/pids: Restore pids.events notifications in local mode A fork rejected by the pids controller increments the counter reported by pids.events. When local event accounting is selected, however, pids_event() returns after notifying only events_local_file, leaving pids.events pollers asleep. On legacy hierarchies, pids.events.local does not exist. With pids_localevents, pids.events reports the same local counter. In both cases, pids.events changes without generating a notification. This can be reproduced with a pids_localevents mount: mkdir /tmp/test mount -t cgroup2 -o pids_localevents none /tmp/test mkdir /tmp/test/t echo 1 > /tmp/test/t/pids.max cat /tmp/test/t/pids.events # max 0 timeout 3 inotifywait -e modify /tmp/test/t/pids.events & sh -c 'echo $$ > /tmp/test/t/cgroup.procs; (true &)' 2>/dev/null wait cat /tmp/test/t/pids.events # max 1 Without this patch, inotifywait times out without reporting an event. Notify pids.events before returning from the local event path. Fixes: 3f26a885a068 ("cgroup/pids: Add pids.events.local") Cc: stable@vger.kernel.org # v6.11+ Signed-off-by: Guopeng Zhang Signed-off-by: Tejun Heo --- kernel/cgroup/pids.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kernel/cgroup/pids.c b/kernel/cgroup/pids.c index ecbb839d2acb..78cdc0558d0c 100644 --- a/kernel/cgroup/pids.c +++ b/kernel/cgroup/pids.c @@ -253,6 +253,11 @@ static void pids_event(struct pids_cgroup *pids_forking, } if (!cgroup_subsys_on_dfl(pids_cgrp_subsys) || cgrp_dfl_root.flags & CGRP_ROOT_PIDS_LOCAL_EVENTS) { + /* + * pids.events reports the local counter on legacy hierarchies + * and when pids_localevents is enabled. + */ + cgroup_file_notify(&p->events_file); cgroup_file_notify(&p->events_local_file); return; } From f75f21ef36285e5f56ee0c428bd2909ee81165b9 Mon Sep 17 00:00:00 2001 From: Ming Wang Date: Sun, 20 Sep 2026 15:44:59 +0800 Subject: [PATCH 2323/2366] net: usb: cdc_mbim: add MeiG Smart SRM821 to ZLP whitelist The MeiG Smart SRM821 5G module (0x2dee:0x4d53) crashes and drops off the USB bus when it receives a Zero Length Packet (ZLP) after sending or receiving an NTB of exactly 16384 bytes (tx_max). According to the MBIM specification, devices do not require a ZLP if the NTB size is exactly dwNtbOutMaxSize. However, the cdc_mbim driver defaults to sending ZLPs for devices not explicitly whitelisted to accommodate non-conformant hardware. This default behavior breaks the strictly conformant MeiG SRM821 module. Add this device to the ZLP conformance whitelist (cdc_mbim_info) so the driver will pad the NTB to avoid sending ZLPs, preventing the device firmware from crashing. Cc: stable@vger.kernel.org Signed-off-by: Ming Wang Link: https://patch.msgid.link/20260920074500.826121-1-wangming01@loongson.cn Signed-off-by: Jakub Kicinski --- drivers/net/usb/cdc_mbim.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/usb/cdc_mbim.c b/drivers/net/usb/cdc_mbim.c index 877fb0ed7d3d..a7010a0664c7 100644 --- a/drivers/net/usb/cdc_mbim.c +++ b/drivers/net/usb/cdc_mbim.c @@ -635,6 +635,11 @@ static const struct usb_device_id mbim_devs[] = { .driver_info = (unsigned long)&cdc_mbim_info, }, + /* MeiG Smart SRM821 ZLP conformance */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d53, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&cdc_mbim_info, + }, + /* Some Huawei devices, ME906s-158 (12d1:15c1) and E3372 * (12d1:157d), are known to fail unless the NDP is placed * after the IP packets. Applying the quirk to all Huawei From a940003f44e7e441c228151dd212642152700ec8 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 21 Sep 2026 01:20:44 +0300 Subject: [PATCH 2324/2366] net: phylink: record the PHY only once bringup cannot fail phylink_bringup_phy() stores the PHY in pl->phydev before its last fallible step: on a MAC whose phylink ops implement LPI, phy_eee_rx_clock_stop() can fail with a real MDIO error. The callers unwind with phy_detach(), which knows nothing about pl->phydev, so a pointer to a PHY that is no longer attached outlives the failed connect. What that costs depends on how the caller got here. phylink_connect_phy() goes through phylink_attach_phy(), which refuses to attach while pl->phydev is set, turning a transient MDIO error into a permanent -EBUSY. The SFP path is worse than that: sfp_sm_probe_phy() answers the failure with phy_device_remove() and phy_device_free(), and it assigns sfp->mod_phy only past that error return, so nothing clears pl->phydev and it is left pointing at a freed phy_device that phylink_resolve() and the ethtool helpers go on reading. phylink_fwnode_phy_connect() has no such check, so a later connect overwrites the stale pointer and hides the problem. A disconnect does not: phylink_disconnect_phy() hands that pointer to phy_disconnect(), and the second phy_detach() on the same PHY drops references the first one already released. Found while making a DSA port survive a PHY whose driver arrives after the switch probes: keeping the port across a failed connect and retrying is what makes this window reachable. Publish the pointer after the last call that can fail instead of unwinding it afterwards. Nothing between the two points reads pl->phydev, and the registration that follows cannot fail: phy_request_interrupt() falls back to polling on its own. The PHY-side state keeps the order it had, so no MDIO operation moves relative to another. Fixes: 03abf2a7c654 ("net: phylink: add EEE management") Signed-off-by: Aleksei Sviridkin Link: https://patch.msgid.link/20260920222044.1752860-1-f@lex.la Signed-off-by: Jakub Kicinski --- drivers/net/phy/phylink.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index a1458da8111b..1bbcf46c8356 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -2129,7 +2129,6 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, mutex_lock(&pl->phydev_mutex); mutex_lock(&phy->lock); mutex_lock(&pl->state_mutex); - pl->phydev = phy; pl->phy_state.interface = interface; pl->phy_state.pause = MLO_PAUSE_NONE; pl->phy_state.speed = SPEED_UNKNOWN; @@ -2196,10 +2195,25 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, ret = 0; } - if (ret == 0 && phy_interrupt_is_valid(phy)) + if (ret) + return ret; + + /* Nothing below can fail, so the PHY can be recorded now. Doing it + * here rather than above keeps a failed bringup from leaving + * pl->phydev pointing at a PHY the caller is about to detach. + */ + mutex_lock(&pl->phydev_mutex); + mutex_lock(&phy->lock); + mutex_lock(&pl->state_mutex); + pl->phydev = phy; + mutex_unlock(&pl->state_mutex); + mutex_unlock(&phy->lock); + mutex_unlock(&pl->phydev_mutex); + + if (phy_interrupt_is_valid(phy)) phy_request_interrupt(phy); - return ret; + return 0; } static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy, From 3173cba1170131816972ed2b6185cb970ba8b747 Mon Sep 17 00:00:00 2001 From: Jiawen Wu Date: Mon, 21 Sep 2026 15:15:49 +0800 Subject: [PATCH 2325/2366] net: libwx: fix races in Tx timestamp handling wx->ptp_tx_skb is shared between the Tx path, the PTP auxiliary worker and the timestamp cleanup paths. The WX_STATE_PTP_TX_IN_PROGRESS bit prevents multiple Tx paths from submitting timestamp requests, but does not serialize the worker against cleanup. As a result, wx_ptp_clear_tx_timestamp() can free an skb after wx_ptp_tx_hwtstamp_work() has obtained its pointer. The worker may then pass the freed skb to skb_tstamp_tx() and release the same reference again. The cleanup path may also clear the in-progress bit while the worker is still processing the old skb. This allows the Tx path to publish a new skb which the worker can subsequently overwrite with NULL, leaking its reference. Add a dedicated spinlock to protect publication and consumption of the Tx timestamp skb. Detach the skb and clear the in-progress bit while holding the lock, then deliver the timestamp and release the skb after dropping it. Use the same locked cleanup in the quiesce path, but keep the detach there free of register accesses: quiesce runs during PCIe error recovery, where MMIO is not reliable, and it deliberately did not touch the device before. The lock is taken with interrupts disabled, because netpoll can call ndo_start_xmit() with hard interrupts already off. When handling a Tx DMA mapping failure, keep the transmit path reference until after comparing the skb under the lock. This prevents skb address reuse from making the error path mistake a newer timestamp request for the failed one. Fixes: 06e75161b9d4 ("net: wangxun: Add support for PTP clock") Reported-by: Sashiko Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/6C7EC12D69217315%2B20260818074721.45536-1-jiawenwu%40trustnetic.com Signed-off-by: Jiawen Wu Link: https://patch.msgid.link/77431AF9A0E369F3+20260921071549.1141804-1-jiawenwu@trustnetic.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wangxun/libwx/wx_hw.c | 1 + drivers/net/ethernet/wangxun/libwx/wx_lib.c | 47 ++++-- drivers/net/ethernet/wangxun/libwx/wx_ptp.c | 144 ++++++++++++------- drivers/net/ethernet/wangxun/libwx/wx_type.h | 2 + 4 files changed, 133 insertions(+), 61 deletions(-) diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c b/drivers/net/ethernet/wangxun/libwx/wx_hw.c index 122c4952d203..113552586be7 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c @@ -2518,6 +2518,7 @@ int wx_sw_init(struct wx *wx) } spin_lock_init(&wx->hw_stats_lock); + spin_lock_init(&wx->ptp_tx_lock); mutex_init(&wx->reset_lock); bitmap_zero(wx->state, WX_STATE_NBITS); bitmap_zero(wx->flags, WX_PF_FLAGS_NBITS); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c index ed5aad7857bd..dcbf5811046e 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c @@ -1200,9 +1200,11 @@ static int wx_tx_map(struct wx_ring *tx_ring, i--; } - dev_kfree_skb_any(first->skb); - first->skb = NULL; - + /* first->skb is released by the caller, which keeps a reference on it + * until the PTP cleanup has compared it against wx->ptp_tx_skb. That + * prevents the address from being reused by a newer request while the + * comparison is pending. + */ tx_ring->next_to_use = i; return -ENOMEM; @@ -1649,9 +1651,11 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb, if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && wx->ptp_clock) { + unsigned long flags; + + spin_lock_irqsave(&wx->ptp_tx_lock, flags); if (wx->tstamp_config.tx_type == HWTSTAMP_TX_ON && - !test_and_set_bit_lock(WX_STATE_PTP_TX_IN_PROGRESS, - wx->state)) { + !test_and_set_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state)) { skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; tx_flags |= WX_TX_FLAGS_TSTAMP; wx->ptp_tx_skb = skb_get(skb); @@ -1659,6 +1663,7 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb, } else { wx->tx_hwtstamp_skipped++; } + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); } /* record initial flags and protocol */ @@ -1677,19 +1682,35 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb, wx->atr(tx_ring, first, ptype); if (wx_tx_map(tx_ring, first, hdr_len)) - goto cleanup_tx_tstamp; + goto out_drop; return NETDEV_TX_OK; out_drop: + /* The frame never reached the hardware, so no timestamp will ever be + * reported for it and the request has to be cancelled. The slot is + * shared, though: wx_ptp_clear_tx_timestamp() or wx_ptp_tx_hang() may + * have dropped our request already, and a transmit on another queue + * can have claimed the slot since. Only cancel it while it is still + * ours, otherwise we would free somebody else's skb and release their + * in-progress bit. + */ + if (unlikely(tx_flags & WX_TX_FLAGS_TSTAMP)) { + struct sk_buff *ptp_tx_skb = NULL; + unsigned long flags; + + spin_lock_irqsave(&wx->ptp_tx_lock, flags); + if (wx->ptp_tx_skb == skb) { + ptp_tx_skb = wx->ptp_tx_skb; + wx->ptp_tx_skb = NULL; + clear_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + wx->tx_hwtstamp_errors++; + } + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + + dev_kfree_skb_any(ptp_tx_skb); + } dev_kfree_skb_any(first->skb); first->skb = NULL; -cleanup_tx_tstamp: - if (unlikely(tx_flags & WX_TX_FLAGS_TSTAMP)) { - dev_kfree_skb_any(wx->ptp_tx_skb); - wx->ptp_tx_skb = NULL; - wx->tx_hwtstamp_errors++; - clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); - } return NETDEV_TX_OK; } diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c index 4708e7f3958f..65b8937f6e94 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c @@ -129,6 +129,34 @@ static int wx_ptp_settime64(struct ptp_clock_info *ptp, return 0; } +/** + * __wx_ptp_detach_tx_skb - detach the skb tracking the Tx timestamp request + * @wx: the private board structure + * + * Detach the skb of the outstanding request and release the in-progress bit, + * so that a new request can be submitted. + * + * This performs no register access. Callers that need a timestamp the hardware + * may have left latched must unlatch it themselves, while the device is known + * to be alive. wx_ptp_quiesce() runs during PCIe error recovery, where MMIO is + * not reliable, and therefore deliberately skips the unlatch. + * + * Context: Expects wx->ptp_tx_lock to be held by the caller. + * Return: the detached skb, or NULL if no request was outstanding. The caller + * owns the returned reference and must release it once the lock is dropped. + */ +static struct sk_buff *__wx_ptp_detach_tx_skb(struct wx *wx) +{ + struct sk_buff *skb = wx->ptp_tx_skb; + + lockdep_assert_held(&wx->ptp_tx_lock); + + wx->ptp_tx_skb = NULL; + clear_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + + return skb; +} + /** * wx_ptp_clear_tx_timestamp - utility function to clear Tx timestamp state * @wx: the private board structure @@ -139,12 +167,16 @@ static int wx_ptp_settime64(struct ptp_clock_info *ptp, */ static void wx_ptp_clear_tx_timestamp(struct wx *wx) { + struct sk_buff *skb; + unsigned long flags; + + spin_lock_irqsave(&wx->ptp_tx_lock, flags); + /* Unlatch a timestamp the hardware may have left pending. */ rd32ptp(wx, WX_TSC_1588_STMPH); - if (wx->ptp_tx_skb) { - dev_kfree_skb_any(wx->ptp_tx_skb); - wx->ptp_tx_skb = NULL; - } - clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + skb = __wx_ptp_detach_tx_skb(wx); + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + + dev_kfree_skb_any(skb); } /** @@ -175,49 +207,54 @@ static void wx_ptp_convert_to_hwtstamp(struct wx *wx, } /** - * wx_ptp_tx_hwtstamp - utility function which checks for TX time stamp + * wx_ptp_tx_hwtstamp_work - check for a pending Tx time stamp * @wx: the private board struct * - * if the timestamp is valid, we convert it into the timecounter ns - * value, then store that result into the shhwtstamps structure which - * is passed up the network stack + * If a Tx timestamp request is outstanding and the hardware has latched a + * valid value, we convert it into the timecounter ns value, then store that + * result into the shhwtstamps structure which is passed up the network stack. + * + * Return: 0 when there is nothing left to poll for, -1 when the timestamp is + * not available yet and the caller should poll again. */ -static void wx_ptp_tx_hwtstamp(struct wx *wx) -{ - struct skb_shared_hwtstamps shhwtstamps; - struct sk_buff *skb = wx->ptp_tx_skb; - u64 regval = 0; - - regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPL); - regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPH) << 32; - - wx_ptp_convert_to_hwtstamp(wx, &shhwtstamps, regval); - - wx->ptp_tx_skb = NULL; - clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); - skb_tstamp_tx(skb, &shhwtstamps); - dev_kfree_skb_any(skb); - wx->tx_hwtstamp_pkts++; -} - static int wx_ptp_tx_hwtstamp_work(struct wx *wx) { + struct skb_shared_hwtstamps shhwtstamps; + unsigned long flags; + struct sk_buff *skb; u32 tsynctxctl; + u64 regval = 0; + + spin_lock_irqsave(&wx->ptp_tx_lock, flags); /* we have to have a valid skb to poll for a timestamp */ if (!wx->ptp_tx_skb) { - wx_ptp_clear_tx_timestamp(wx); + rd32ptp(wx, WX_TSC_1588_STMPH); + __wx_ptp_detach_tx_skb(wx); + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); return 0; } /* stop polling once we have a valid timestamp */ tsynctxctl = rd32ptp(wx, WX_TSC_1588_CTL); - if (tsynctxctl & WX_TSC_1588_CTL_VALID) { - wx_ptp_tx_hwtstamp(wx); - return 0; + if (!(tsynctxctl & WX_TSC_1588_CTL_VALID)) { + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + return -1; } - return -1; + regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPL); + regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPH) << 32; + skb = wx->ptp_tx_skb; + wx->ptp_tx_skb = NULL; + clear_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + + wx_ptp_convert_to_hwtstamp(wx, &shhwtstamps, regval); + skb_tstamp_tx(skb, &shhwtstamps); + dev_kfree_skb_any(skb); + wx->tx_hwtstamp_pkts++; + + return 0; } /** @@ -296,24 +333,29 @@ static void wx_ptp_rx_hang(struct wx *wx) */ static void wx_ptp_tx_hang(struct wx *wx) { - bool timeout = time_is_before_jiffies(wx->ptp_tx_start + - WX_PTP_TX_TIMEOUT); + struct sk_buff *skb = NULL; + unsigned long flags; - if (!wx->ptp_tx_skb) - return; - - if (!test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state)) - return; + spin_lock_irqsave(&wx->ptp_tx_lock, flags); /* If we haven't received a timestamp within the timeout, it is * reasonable to assume that it will never occur, so we can unlock the * timestamp bit when this occurs. */ - if (timeout) { - wx_ptp_clear_tx_timestamp(wx); - wx->tx_hwtstamp_timeouts++; - dev_warn(&wx->pdev->dev, "clearing Tx timestamp hang\n"); + if (wx->ptp_tx_skb && + test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state) && + time_is_before_jiffies(wx->ptp_tx_start + WX_PTP_TX_TIMEOUT)) { + rd32ptp(wx, WX_TSC_1588_STMPH); + skb = __wx_ptp_detach_tx_skb(wx); } + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + + if (!skb) + return; + + dev_kfree_skb_any(skb); + wx->tx_hwtstamp_timeouts++; + dev_warn(&wx->pdev->dev, "clearing Tx timestamp hang\n"); } static long wx_ptp_do_aux_work(struct ptp_clock_info *ptp) @@ -841,6 +883,9 @@ EXPORT_SYMBOL(wx_ptp_stop); void wx_ptp_quiesce(struct wx *wx) { + struct sk_buff *skb; + unsigned long flags; + if (!test_and_clear_bit(WX_STATE_PTP_RUNNING, wx->state)) return; @@ -849,11 +894,14 @@ void wx_ptp_quiesce(struct wx *wx) if (wx->ptp_clock) ptp_cancel_worker_sync(wx->ptp_clock); - if (wx->ptp_tx_skb) { - dev_kfree_skb_any(wx->ptp_tx_skb); - wx->ptp_tx_skb = NULL; - } - clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + /* Drop a pending Tx timestamp request. Do not touch the registers + * here: quiesce runs during PCIe error recovery, where the device may + * already be gone and MMIO is not reliable. + */ + spin_lock_irqsave(&wx->ptp_tx_lock, flags); + skb = __wx_ptp_detach_tx_skb(wx); + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + dev_kfree_skb_any(skb); if (wx->ptp_clock) { ptp_clock_unregister(wx->ptp_clock); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h index 9454e90258d8..afd980dbb793 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h @@ -1430,6 +1430,8 @@ struct wx { unsigned long last_overflow_check; unsigned long last_rx_ptp_check; unsigned long ptp_tx_start; + /* protects ptp_tx_skb, ptp_tx_start and the in-progress state bit */ + spinlock_t ptp_tx_lock; seqlock_t hw_tc_lock; /* seqlock for ptp */ struct cyclecounter hw_cc; struct timecounter hw_tc; From 26b2bd70d22457556e2fa01cbf1192cb1a94d619 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 21 Sep 2026 16:55:43 +0200 Subject: [PATCH 2326/2366] net: openvswitch: conntrack: avoid modifying shared unconfirmed ct entry In a case where skb with an unconfirmed ct entry gets cloned, we may end up committing both but with different sets of extensions. The series of events: 1. The first clone wants to commit and runs the helpers wiring up the extension pointer into the expectation list. 2. Then it looses the confirmation keeping the entry unconfirmed. 3. Second clone now wants to commit labels and adds the new extension for that breaking the pointer in the expectation list causing UAF on the destruction path later. While this is possible to trigger, there should be no practical network pipeline where committing both clones without modifications into the same zone is needed. So, let's just reset the entry in case for some reason we got an skb with a shared one during commit. This doesn't affect any known use cases, but avoids any potential problems with sharing and modification of the unconfirmed ct entry. The fixes tag points to the introduction of helpers, since that's the main UAF trigger for the sharing. Fixes: cae3a2627520 ("openvswitch: Allow attaching helpers to ct action") Cc: stable@vger.kernel.org Reported-by: Axel Mierczuk Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260921145655.3167436-2-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- include/net/netfilter/nf_conntrack.h | 5 +++++ net/openvswitch/conntrack.c | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/include/net/netfilter/nf_conntrack.h b/include/net/netfilter/nf_conntrack.h index bc42dd0e10e6..c39425e54d87 100644 --- a/include/net/netfilter/nf_conntrack.h +++ b/include/net/netfilter/nf_conntrack.h @@ -185,6 +185,11 @@ static inline void nf_ct_put(struct nf_conn *ct) nf_ct_destroy(&ct->ct_general); } +static inline bool nf_ct_shared(const struct nf_conn *ct) +{ + return refcount_read(&ct->ct_general.use) > 1; +} + /* load module; enable/disable conntrack in this namespace */ int nf_ct_netns_get(struct net *net, u8 nfproto); void nf_ct_netns_put(struct net *net, u8 nfproto); diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index 0f433688e17b..a733029c28dd 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -734,6 +734,18 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, enum ip_conntrack_info ctinfo; struct nf_conn *ct; + /* If the ct entry is not confirmed and shared with some other skb, + * e.g., a cloned one, we can't just modify it with the commit as we + * must not modify the extension set. Reset. + */ + if (cached && info->commit) { + ct = nf_ct_get(skb, &ctinfo); + if (ct && !nf_ct_is_confirmed(ct) && nf_ct_shared(ct)) { + nf_reset_ct(skb); + cached = false; + } + } + if (!cached) { struct nf_hook_state state = { .hook = NF_INET_PRE_ROUTING, From 5e6c14dd42a1c1fe938e573dc6c9098145b2b0c4 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 21 Sep 2026 16:55:44 +0200 Subject: [PATCH 2327/2366] net: openvswitch: conntrack: remove 'add_helper' dead code This variable can only become 'true' when the connection is not confirmed, but it is only checked when it is confirmed. So, it can be treated as being always false and just removed. Fixes: 3c1860543fcc ("openvswitch: add nf_ct_is_confirmed check before assigning the helper") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260921145655.3167436-3-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/openvswitch/conntrack.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index a733029c28dd..c20f096eef40 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -778,8 +778,6 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, ct = nf_ct_get(skb, &ctinfo); if (ct) { - bool add_helper = false; - /* Packets starting a new connection must be NATted before the * helper, so that the helper knows about the NAT. We enforce * this by delaying both NAT and helper calls for unconfirmed @@ -811,7 +809,6 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, GFP_ATOMIC); if (err) return err; - add_helper = true; /* helper installed, add seqadj if NAT is required */ if (info->nat && !nfct_seqadj(ct)) { @@ -821,13 +818,10 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, } /* Call the helper only if: - * - nf_conntrack_in() was executed above ("!cached") or a - * helper was just attached ("add_helper") for a confirmed - * connection, or + * - nf_conntrack_in() was executed above ("!cached"), or * - When committing an unconfirmed connection. */ - if ((nf_ct_is_confirmed(ct) ? !cached || add_helper : - info->commit)) { + if ((nf_ct_is_confirmed(ct) ? !cached : info->commit)) { int err = nf_ct_helper(skb, ct, ctinfo, info->family); err = verdict_to_errno(err); From 1a4151e6be57b098b7a5ebfbde58585e83200cdc Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 21 Sep 2026 16:55:45 +0200 Subject: [PATCH 2328/2366] net: openvswitch: conntrack: fix helper UAF due to extensions realloc While calling the helpers, a raw pointer to the extensions area is wired into expectations list: -> nf_ct_helper() -> helper->help() -> nf_ct_expect_related_report() -> nf_ct_expect_insert() -> hlist_add_head_rcu(&exp->lnode, &master_help->expectations) In case the connection is not confirmed yet, more extensions can be added afterwards with *_ext_add() calls reallocating the extension space and leaving the now invalid pointer in the expectations list that is later accessed while removing the expectation. Make sure that helpers are called at the end after all the other extensions are already added. Note that the helper rejection now leaves the mark and labels set, but that's not different from how the NAT was handled before or how the mark and the labels were handled on confirmation failure. And there are no atomicity guarantees provided by the API anyway. Fixes: cae3a2627520 ("openvswitch: Allow attaching helpers to ct action") Cc: stable@vger.kernel.org Reported-by: Axel Mierczuk Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260921145655.3167436-4-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/openvswitch/conntrack.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index c20f096eef40..d3326edcabf7 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -817,11 +817,14 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, } } - /* Call the helper only if: - * - nf_conntrack_in() was executed above ("!cached"), or - * - When committing an unconfirmed connection. + /* Call the helper only if nf_conntrack_in() was executed + * above ("!cached"). + * + * For unconfirmed connections it will be called later during + * commit as we need to have all the other extensions allocated + * before the call. */ - if ((nf_ct_is_confirmed(ct) ? !cached : info->commit)) { + if (nf_ct_is_confirmed(ct) && !cached) { int err = nf_ct_helper(skb, ct, ctinfo, info->family); err = verdict_to_errno(err); @@ -1025,6 +1028,14 @@ static int ovs_ct_commit(struct net *net, struct sw_flow_key *key, return err; nf_conn_act_ct_ext_add(skb, ct, ctinfo); + + /* Call the helpers now. We couldn't do this before as + * all the extensions must be allocated before the call. + */ + err = nf_ct_helper(skb, ct, ctinfo, info->family); + err = verdict_to_errno(err); + if (err) + return err; } else if (IS_ENABLED(CONFIG_NF_CONNTRACK_LABELS) && labels_nonzero(&info->labels.mask)) { err = ovs_ct_set_labels(ct, key, &info->labels.value, From f85009dfcd65e5969526b0db7a49b5413746e630 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 21 Sep 2026 16:55:46 +0200 Subject: [PATCH 2329/2366] net/sched: act_ct: avoid modifying shared unconfirmed ct entry In a case where skb with an unconfirmed ct entry gets cloned, we may end up processing both again but with different sets of extensions. The series of events: 1. The first clone wants to commit and runs the helpers wiring up the extension pointer into the expectation list. 2. Then it looses the confirmation keeping the entry unconfirmed. 3. Second clone now wants to commit labels or run NAT and adds the new extension for that breaking the pointer in the expectation list causing UAF on the destruction path later. While this is possible to trigger, there should be no practical network pipeline where we need to process both clones without modifications in the same zone. So, let's just reset the entry in case for some reason we got an skb with a shared one. This doesn't affect any known use cases, but avoids any potential problems with sharing and modification of the unconfirmed ct entry. Unlike openvswitch module, act_ct allows for NAT without commit. Changing that would be a uAPI break. So, act_ct needs to reset on NAT regardless of the commit flag to avoid reallocation of the extension space. This, however, doesn't really change the picture for sensible networking cases as there should be no need to run the same packet twice (before and after the clone) through conntrack without packet header or zone changes and without commit. The fixes tag points to the introduction of helpers, since that's the main UAF trigger for the sharing. Fixes: a21b06e73191 ("net: sched: add helper support in act_ct") Cc: stable@vger.kernel.org Reported-by: Axel Mierczuk Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Reviewed-by: Xin Long Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260921145655.3167436-5-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/sched/act_ct.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index 55f3521edb4c..e72143d36b11 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -979,11 +979,11 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, struct tcf_result *res) { struct net *net = dev_net(skb->dev); + bool cached, commit, clear, nat; enum ip_conntrack_info ctinfo; struct tcf_ct *c = to_ct(a); struct nf_conn *tmpl = NULL; struct nf_hook_state state; - bool cached, commit, clear; int nh_ofs, err, retval; struct tcf_ct_params *p; bool add_helper = false; @@ -998,6 +998,7 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, retval = p->action; commit = p->ct_action & TCA_CT_ACT_COMMIT; clear = p->ct_action & TCA_CT_ACT_CLEAR; + nat = p->ct_action & TCA_CT_ACT_NAT; tmpl = p->tmpl; tcf_lastuse_update(&c->tcf_tm); @@ -1046,6 +1047,19 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, * different zone. */ cached = tcf_ct_skb_nfct_cached(net, skb, p); + + /* If the ct entry is not confirmed and shared with some other skb, + * e.g., a cloned one, we can't just modify it with a commit or nat + * as we must not modify the extension set. Reset. + */ + if (cached && (commit || nat)) { + ct = nf_ct_get(skb, &ctinfo); + if (ct && !nf_ct_is_confirmed(ct) && nf_ct_shared(ct)) { + nf_reset_ct(skb); + cached = false; + } + } + if (!cached) { if (tcf_ct_flow_table_lookup(p, skb, family)) { skip_add = true; @@ -1083,7 +1097,7 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, if (err) goto drop; add_helper = true; - if (p->ct_action & TCA_CT_ACT_NAT && !nfct_seqadj(ct)) { + if (nat && !nfct_seqadj(ct)) { if (!nfct_seqadj_ext_add(ct)) goto drop; } From 00df72e39f306e2f7adb68528a5c92109da6a0a9 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 21 Sep 2026 16:55:47 +0200 Subject: [PATCH 2330/2366] net/sched: act_ct: remove 'add_helper' dead code This variable can only become 'true' when the connection is not confirmed, but it is only checked when it is confirmed. So, it can be treated as being always false and just removed. Fixes: a21b06e73191 ("net: sched: add helper support in act_ct") Cc: stable@vger.kernel.org Signed-off-by: Ilya Maximets Reviewed-by: Aaron Conole Reviewed-by: Xin Long Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260921145655.3167436-6-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/sched/act_ct.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index e72143d36b11..f62051ec9d57 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -986,7 +986,6 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, struct nf_hook_state state; int nh_ofs, err, retval; struct tcf_ct_params *p; - bool add_helper = false; bool skb_is_ours = false; bool skip_add = false; bool defrag = false; @@ -1096,14 +1095,14 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, err = __nf_ct_try_assign_helper(ct, p->tmpl, GFP_ATOMIC); if (err) goto drop; - add_helper = true; + if (nat && !nfct_seqadj(ct)) { if (!nfct_seqadj_ext_add(ct)) goto drop; } } - if (nf_ct_is_confirmed(ct) ? ((!cached && !skip_add) || add_helper) : commit) { + if (nf_ct_is_confirmed(ct) ? (!cached && !skip_add) : commit) { err = nf_ct_helper(skb, ct, ctinfo, family); if (err != NF_ACCEPT) goto nf_error; From dad19b59da050cb60d3f7023dac2a042a84bf0bd Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Mon, 21 Sep 2026 16:55:48 +0200 Subject: [PATCH 2331/2366] net/sched: act_ct: fix helper UAF due to extensions realloc While calling the helpers, a raw pointer to the extensions area is wired into expectations list: -> nf_ct_helper() -> helper->help() -> nf_ct_expect_related_report() -> nf_ct_expect_insert() -> hlist_add_head_rcu(&exp->lnode, &master_help->expectations) In case the connection is not confirmed yet, more extensions can be added afterwards with *_ext_add() calls reallocating the extension space and leaving the now invalid pointer in the expectations list that is later accessed while removing the expectation. Make sure that helpers are called at the end after all the other extensions are already added. Note that the helper rejection now leaves the mark and labels set, but that's not different from how the NAT was handled before or how the mark and the labels were handled on confirmation failure. And there are no atomicity guarantees provided by the API anyway. Fixes: a21b06e73191 ("net: sched: add helper support in act_ct") Cc: stable@vger.kernel.org Reported-by: Axel Mierczuk Signed-off-by: Ilya Maximets Reviewed-by: Xin Long Reviewed-by: Jamal Hadi Salim Reviewed-by: Aaron Conole Link: https://patch.msgid.link/20260921145655.3167436-7-i.maximets@ovn.org Signed-off-by: Jakub Kicinski --- net/sched/act_ct.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index f62051ec9d57..411e3dd92d07 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -1102,6 +1102,18 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, } } + if (commit) { + tcf_ct_act_set_mark(ct, p->mark, p->mark_mask); + tcf_ct_act_set_labels(ct, p->labels, p->labels_mask); + + if (!nf_ct_is_confirmed(ct)) + nf_conn_act_ct_ext_add(skb, ct, ctinfo); + } + + /* Run helpers for the connection if nf_conntrack_in() was executed + * or if we're about to commit. This has to be done after all the + * extensions are already added. + */ if (nf_ct_is_confirmed(ct) ? (!cached && !skip_add) : commit) { err = nf_ct_helper(skb, ct, ctinfo, family); if (err != NF_ACCEPT) @@ -1109,12 +1121,6 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, } if (commit) { - tcf_ct_act_set_mark(ct, p->mark, p->mark_mask); - tcf_ct_act_set_labels(ct, p->labels, p->labels_mask); - - if (!nf_ct_is_confirmed(ct)) - nf_conn_act_ct_ext_add(skb, ct, ctinfo); - /* This will take care of sending queued events * even if the connection is already confirmed. */ From 0958ea4355e2e9220ad4e13da3b7d94f365ed34f Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 21 Sep 2026 23:42:01 +0800 Subject: [PATCH 2332/2366] net: ena: fix PHC cleanup on probe failure ena_probe() initializes the PHC as part of ena_device_init(), but the probe failure path does not destroy it before freeing the PHC private data. The normal removal path calls ena_phc_destroy() through ena_destroy_device() before ena_phc_free(). However, if probe fails after ena_device_init() succeeds, the error path reaches ena_phc_free() without unregistering the PTP clock or destroying the device PHC resources. Call ena_phc_destroy() in the probe error path before freeing the PHC private data. This issue was found by manual code inspection. Cc: stable@vger.kernel.org tags and describe this as a consistency cleanup Fixes: e0ea34158ee8 ("net: ena: Add PHC support in the ENA driver") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Cc: stable Link: https://patch.msgid.link/20260921154202.471662-2-lgs201920130244@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amazon/ena/ena_netdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/amazon/ena/ena_netdev.c b/drivers/net/ethernet/amazon/ena/ena_netdev.c index ea89619039d8..5f0864d16dd3 100644 --- a/drivers/net/ethernet/amazon/ena/ena_netdev.c +++ b/drivers/net/ethernet/amazon/ena/ena_netdev.c @@ -4122,6 +4122,7 @@ static int ena_probe(struct pci_dev *pdev, const struct pci_device_id *ent) err_device_destroy: ena_com_delete_host_info(ena_dev); ena_com_admin_destroy(ena_dev); + ena_phc_destroy(adapter); ena_devlink_destroy: ena_devlink_free(devlink); err_metrics_destroy: From 9476b4468862927297c94c440863cd8ed1e7cc83 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 21 Sep 2026 23:42:02 +0800 Subject: [PATCH 2333/2366] net: ena: fix MMIO read buffer leak on probe failure ena_device_init() initializes the MMIO read mechanism with ena_com_mmio_reg_read_request_init(), which allocates a coherent DMA buffer for MMIO read responses. The normal removal path releases this buffer through ena_com_mmio_reg_read_request_destroy(). However, if ena_probe() fails after ena_device_init() succeeds, the error path destroys the admin resources and eventually frees ena_dev without destroying the MMIO read request, leaving the coherent DMA buffer allocated. Call ena_com_mmio_reg_read_request_destroy() in the probe error path before releasing the remaining device resources. This issue was found by manual code inspection. Fixes: 1738cd3ed342 ("net: ena: Add a driver for Amazon Elastic Network Adapters (ENA)") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260921154202.471662-3-lgs201920130244@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amazon/ena/ena_netdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/amazon/ena/ena_netdev.c b/drivers/net/ethernet/amazon/ena/ena_netdev.c index 5f0864d16dd3..7eb6456ed0d5 100644 --- a/drivers/net/ethernet/amazon/ena/ena_netdev.c +++ b/drivers/net/ethernet/amazon/ena/ena_netdev.c @@ -4123,6 +4123,7 @@ static int ena_probe(struct pci_dev *pdev, const struct pci_device_id *ent) ena_com_delete_host_info(ena_dev); ena_com_admin_destroy(ena_dev); ena_phc_destroy(adapter); + ena_com_mmio_reg_read_request_destroy(ena_dev); ena_devlink_destroy: ena_devlink_free(devlink); err_metrics_destroy: From 1a983a4e14c635c40354be110cd9a1a5c94e01e6 Mon Sep 17 00:00:00 2001 From: Sang-Hoon Choi Date: Tue, 22 Sep 2026 03:15:59 +0900 Subject: [PATCH 2334/2366] nfp: hold IPsec RX state under the XArray lock nfp_net_ipsec_rx() drops the XArray lock before taking a reference to the xfrm_state it found. The delete path can erase the entry and drop the last state reference in that interval. RX can then try to increment a zero refcount after the state has been queued for destruction. The driver queues firmware invalidation asynchronously; the delete path does not wait for the command to complete or drain pending RX processing. The XFRM garbage collector waits for an RCU grace period before freeing the state. That delays reclamation but does not make acquiring a reference from zero valid. Take the xfrm_state reference before releasing the XArray lock so xa_erase() cannot run between lookup and reference acquisition. Fixes: 57f273adbcd4 ("nfp: add framework to support ipsec offloading") Reported-by: Changyul Lee Signed-off-by: Sang-Hoon Choi Link: https://patch.msgid.link/179001455912.44752.17153022439349797877.idr-bug-92@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/netronome/nfp/crypto/ipsec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/netronome/nfp/crypto/ipsec.c b/drivers/net/ethernet/netronome/nfp/crypto/ipsec.c index 9e7c285eaa6b..960d7513aa8d 100644 --- a/drivers/net/ethernet/netronome/nfp/crypto/ipsec.c +++ b/drivers/net/ethernet/netronome/nfp/crypto/ipsec.c @@ -625,11 +625,12 @@ int nfp_net_ipsec_rx(struct nfp_meta_parsed *meta, struct sk_buff *skb) xa_lock(&nn->xa_ipsec); x = xa_load(&nn->xa_ipsec, saidx); + if (x) + xfrm_state_hold(x); xa_unlock(&nn->xa_ipsec); if (!x) return -EINVAL; - xfrm_state_hold(x); sp->xvec[sp->len++] = x; sp->olen++; xo = xfrm_offload(skb); From ba3d1f480c7a3fba963e7867ad6cc557c197acbc Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Mon, 21 Sep 2026 14:50:27 -0700 Subject: [PATCH 2335/2366] net/rds: size a connection's path set by the transport it ends up with __rds_conn_create() computes npaths from the caller's transport before it decides whether a connection to one of the host's own addresses is to be handled by the loopback transport instead. That substitution is what an RDS/TCP socket sending to a local address gets, and after it the path init loop still runs for the TCP transport's RDS_MPATH_WORKERS paths and allocates an ordered workqueue for each, while rds_loop_conn_alloc() only ever provides transport data for path 0. rds_conn_destroy() sizes its teardown from c_trans, by then the loopback transport, so it visits path 0 only - and rds_conn_path_destroy() would skip the other paths anyway, since it returns before destroy_workqueue() for a path without transport data. kfree(c_path) then drops the last pointers to seven workqueues. That repeats for every such connection, on every netns teardown or module unload, and every distinct local destination address is a separate connection. Recompute npaths once the transport is final, so that creation and destruction agree on the set of paths. The c_path array stays sized for the caller's transport; the unused entries are freed with it. Fixes: 4716af3897e9 ("net/rds: Give each connection path its own workqueue") Signed-off-by: Allison Henderson Link: https://patch.msgid.link/20260921215027.174657-1-achender@kernel.org Signed-off-by: Jakub Kicinski --- net/rds/connection.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/rds/connection.c b/net/rds/connection.c index b6c4beb50eaf..c752a8623cfc 100644 --- a/net/rds/connection.c +++ b/net/rds/connection.c @@ -276,6 +276,12 @@ static struct rds_connection *__rds_conn_create(struct net *net, conn->c_trans = trans; + /* The transport may just have been swapped for loopback; size the + * set of paths - which is also what rds_conn_destroy() tears down + * again - by the transport the connection actually uses. + */ + npaths = (trans->t_mp_capable ? RDS_MPATH_WORKERS : 1); + init_waitqueue_head(&conn->c_hs_waitq); for (i = 0; i < npaths; i++) { __rds_conn_path_init(conn, &conn->c_path[i], From 61cb282fe97b3b0ba32ca09417a693162bf4ae3f Mon Sep 17 00:00:00 2001 From: Sidraya Jayagond Date: Tue, 22 Sep 2026 09:31:49 +0200 Subject: [PATCH 2336/2366] net/smc: fix UAF on lgr list traversal in smcr_port_err() smcr_port_err() traverses smc_lgr_list.list without holding smc_lgr_list.lock, allowing a concurrent smc_lgr_terminate_sched() to free an lgr while it is still being dereferenced. Hold smc_lgr_list.lock across the traversal. Update smc_ib_gid_check() to call smcr_port_err() after releasing the lock. Fixes: 541afa10c126 ("net/smc: add smcr_port_err() and smcr_link_down() processing") Reviewed-by: Mahanta Jambigi Signed-off-by: Sidraya Jayagond Reviewed-by: Dust Li Link: https://patch.msgid.link/20260922073149.474762-1-sidraya@linux.ibm.com Signed-off-by: Jakub Kicinski --- net/smc/smc_core.c | 2 ++ net/smc/smc_ib.c | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c index 04aedd957543..9974149659c2 100644 --- a/net/smc/smc_core.c +++ b/net/smc/smc_core.c @@ -1849,6 +1849,7 @@ void smcr_port_err(struct smc_ib_device *smcibdev, u8 ibport) struct smc_link_group *lgr, *n; int i; + spin_lock_bh(&smc_lgr_list.lock); list_for_each_entry_safe(lgr, n, &smc_lgr_list.list, list) { if (strncmp(smcibdev->pnetid[ibport - 1], lgr->pnet_id, SMC_MAX_PNETID_LEN)) @@ -1863,6 +1864,7 @@ void smcr_port_err(struct smc_ib_device *smcibdev, u8 ibport) smcr_link_down_cond_sched(lnk); } } + spin_unlock_bh(&smc_lgr_list.lock); } static void smc_link_down_work(struct work_struct *work) diff --git a/net/smc/smc_ib.c b/net/smc/smc_ib.c index 9bb495707445..daaa8a72da90 100644 --- a/net/smc/smc_ib.c +++ b/net/smc/smc_ib.c @@ -333,6 +333,7 @@ static bool smc_ib_check_link_gid(u8 gid[SMC_GID_SIZE], bool smcrv2, static void smc_ib_gid_check(struct smc_ib_device *smcibdev, u8 ibport) { struct smc_link_group *lgr; + bool stale_gid = false; int i; spin_lock_bh(&smc_lgr_list.lock); @@ -348,11 +349,16 @@ static void smc_ib_gid_check(struct smc_ib_device *smcibdev, u8 ibport) continue; if (!smc_ib_check_link_gid(lgr->lnk[i].gid, lgr->smc_version == SMC_V2, - smcibdev, ibport)) - smcr_port_err(smcibdev, ibport); + smcibdev, ibport)) { + stale_gid = true; + goto out; + } } } +out: spin_unlock_bh(&smc_lgr_list.lock); + if (stale_gid) + smcr_port_err(smcibdev, ibport); } static int smc_ib_remember_port_attr(struct smc_ib_device *smcibdev, u8 ibport) From b94773dc4df7026a6f29b2c65e96e88d29cdb576 Mon Sep 17 00:00:00 2001 From: Alexander Sverdlin Date: Tue, 22 Sep 2026 09:52:46 +0200 Subject: [PATCH 2337/2366] net: phy: intel-xway: workaround 100BASE-TX Link-Up issue MaxLinear GSW12x/GSW14x Ethernet Switch Errata Sheet states: "An issue has been sporadically observed after device power-on on the first link-up attempt in 100BASE-TX mode resulting in either the link-up taking a long time, or failing to link-up altogether... Workaround: After power-on, enable Cable Diagnostic Mode for all ports and disable it..." Implement the proposed workaround unconditionally in the Intel XWAY driver (MaxLinear GSW1xx switches incorporate Intel XWAY PHYs) because the diagnostic bits have the same meaning even in older integral PHYs such as GPY111/PEF7071/PHY11G. So it's not clear how to distinguish the affected newer integrated PHYs, but the workaround should not hurt the older PHYs. Cc: stable@vger.kernel.org Fixes: 22335939ec90 ("net: dsa: add driver for MaxLinear GSW1xx switch family") Signed-off-by: Alexander Sverdlin Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260922075251.23386-1-alexander.sverdlin@siemens.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/intel-xway.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/intel-xway.c b/drivers/net/phy/intel-xway.c index afbcec711744..3cee31bb931f 100644 --- a/drivers/net/phy/intel-xway.c +++ b/drivers/net/phy/intel-xway.c @@ -16,6 +16,11 @@ #define XWAY_MDIO_ISTAT 0x1A /* interrupt status */ #define XWAY_MDIO_LED 0x1B /* led control */ +#define XWAY_MDIO_GCTRL_TM_MASK GENMASK(15, 13) +#define XWAY_MDIO_GCTRL_TM(mode) FIELD_PREP(XWAY_MDIO_GCTRL_TM_MASK, (mode)) +#define XWAY_MDIO_GCTRL_TM_NOP XWAY_MDIO_GCTRL_TM(0) /* Normal operation */ +#define XWAY_MDIO_GCTRL_TM_CDIAG XWAY_MDIO_GCTRL_TM(6) /* Cable diagnostics */ + #define XWAY_MDIO_ERRCNT_SEL GENMASK(11, 8) #define XWAY_MDIO_ERRCNT_COUNT GENMASK(7, 0) #define XWAY_MDIO_ERRCNT_SEL_RXERR 0 @@ -326,6 +331,28 @@ static int xway_gphy_probe(struct phy_device *phydev) return 0; } +static int xway_11g_int_config_init(struct phy_device *phydev) +{ + int err; + + /* An issue has been sporadically observed after device power-on on the + * first link-up attempt in 100BASE-TX mode resulting in either the + * link-up taking a long time, or failing to link-up altogether. + * + * Workaround: + * After power-on, enable Cable Diagnostic Mode for all ports and + * disable it. + */ + err = phy_modify(phydev, MII_CTRL1000, XWAY_MDIO_GCTRL_TM_MASK, XWAY_MDIO_GCTRL_TM_CDIAG); + if (err) + return err; + err = phy_modify(phydev, MII_CTRL1000, XWAY_MDIO_GCTRL_TM_MASK, XWAY_MDIO_GCTRL_TM_NOP); + if (err) + return err; + + return xway_gphy_config_init(phydev); +} + static int xway_gphy14_config_aneg(struct phy_device *phydev) { int reg, err; @@ -735,7 +762,7 @@ static struct phy_driver xway_gphy[] = { .phy_id_mask = 0xffffffff, .name = "Intel XWAY PHY11G (xRX v1.2 integrated)", /* PHY_GBIT_FEATURES */ - .config_init = xway_gphy_config_init, + .config_init = xway_11g_int_config_init, .probe = xway_gphy_probe, .handle_interrupt = xway_gphy_handle_interrupt, .config_intr = xway_gphy_config_intr, From 8e1937fed6738460554ec123c64839e2445e7d53 Mon Sep 17 00:00:00 2001 From: Ginger Li Date: Tue, 22 Sep 2026 16:09:09 +0800 Subject: [PATCH 2338/2366] tipc: Fix a data race on mon->peer_cnt in mon_timeout() mon_timeout() evaluates dom_size(mon->peer_cnt) before it takes mon->lock, while mon->peer_cnt is updated under that lock by tipc_mon_add_peer() and tipc_mon_remove_peer(). The value can therefore be stale, and the decision whether the local domain has to be recomputed can be based on an outdated member count. Read mon->peer_cnt inside the write_lock_bh(&mon->lock) protected region. Fixes: 35c55c9877f8 ("tipc: add neighbor monitoring framework") Signed-off-by: Ginger Li Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/20260922080909.21123-1-ginger.jzllee@gmail.com Signed-off-by: Jakub Kicinski --- net/tipc/monitor.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/tipc/monitor.c b/net/tipc/monitor.c index a94b9b36a700..1a438e312d66 100644 --- a/net/tipc/monitor.c +++ b/net/tipc/monitor.c @@ -632,9 +632,10 @@ static void mon_timeout(struct timer_list *t) { struct tipc_monitor *mon = timer_container_of(mon, t, timer); struct tipc_peer *self; - int best_member_cnt = dom_size(mon->peer_cnt) - 1; + int best_member_cnt; write_lock_bh(&mon->lock); + best_member_cnt = dom_size(mon->peer_cnt) - 1; self = mon->self; if (self && (best_member_cnt != self->applied)) { mon_update_local_domain(mon); From 56d82862a0a243ac14ba11b6d7b57ddc2d064b95 Mon Sep 17 00:00:00 2001 From: Dairui Zhang Date: Wed, 23 Sep 2026 13:01:01 +0800 Subject: [PATCH 2339/2366] af_packet: fix integer overflow in prb_calc_retire_blk_tmo() prb_calc_retire_blk_tmo() computes in 32-bit int arithmetic: mbits = (blk_size_in_bytes * 8) / (1024 * 1024); If I'm reading the validation right, tp_block_size is user controlled and packet_set_ring() only rejects values that are <= 0 as int or not page aligned, so a 256MiB block goes right through (and alloc_one_pg_vec_page() even has a vzalloc fallback for it). 0x10000000 * 8 wraps to INT_MIN, and on a NIC reporting 1 Gbps (div == 1) the function ends up returning -2047. The condition is actually (8 * size) mod 2^32 >= 2^31 && div == 1, so the trigger set is [256,512), [768,1024), [1280,1536) and [1792,2048) MiB. Other sizes wrap to non-negative values and faster links divide the unsigned value back below 2^31, which is why this doesn't blow up for everyone. What makes it fatal is what happens next in init_prb_bdqc(): p1->interval_ktime = ms_to_ktime(prb_calc_retire_blk_tmo(...)); hrtimer_start(&p1->retire_blk_timer, p1->interval_ktime, HRTIMER_MODE_REL_SOFT); A negative relative timeout expires immediately. The callback unconditionally returns HRTIMER_RESTART, and hrtimer_forward() turns the negative interval into hrtimer_resolution: if (interval < hrtimer_resolution) interval = hrtimer_resolution; So the SOFT timer re-fires at the maximum rate forever, holding sk_receive_queue.lock each pass. One CPU spins in softirq until the socket is closed. Repeat with more rings and the machine is gone. The overflow itself is ancient - it was introduced together with TPACKET_V3 in f6fb8f100b80 ("af-packet: TPACKET_V3 flexible buffer implementation."). Its effect prior to f7460d2989fa ("net: af_packet: Use hrtimer to do the retire operation", v6.18) was not as clear-cut, though: the return value was stored into an unsigned short retire_blk_tov, so a negative result was truncated, and a 0-jiffy delay loop could be programmed as well. Neither is nearly as detrimental as the immediate maximum-rate spin the hrtimer conversion turned it into. (Unrelated to CVE-2019-20812 - that one was the ethtool failure path returning 0, which now returns DEFAULT_PRB_RETIRE_TOV.) Reproducer, needs CAP_NET_RAW (a --network host container has it by default) and a 1 Gbps NIC (QEMU e1000 works): int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); bind(fd, ...); int v = TPACKET_V3; setsockopt(fd, SOL_PACKET, PACKET_VERSION, &v, sizeof(v)); struct tpacket_req3 req = { .tp_block_size = 0x10000000, .tp_block_nr = 1, .tp_frame_size = 2048, .tp_frame_nr = 0x10000000 / 2048, .tp_retire_blk_tov = 0, }; setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req)); Compute in 64 bits instead. The operands are already bounded by the existing validation, so nothing else changes. If you'd prefer a different fix, just say so and I'll respin. Fixes: f6fb8f100b80 ("af-packet: TPACKET_V3 flexible buffer implementation.") Cc: stable@vger.kernel.org Signed-off-by: Dairui Zhang Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260923050101.1510064-1-zhangdairui@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 64b501db660a..7c83e01526ed 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -617,7 +617,7 @@ static int prb_calc_retire_blk_tmo(struct packet_sock *po, return DEFAULT_PRB_RETIRE_TOV; div = ecmd.base.speed / 1000; - mbits = (blk_size_in_bytes * 8) / (1024 * 1024); + mbits = (u64)blk_size_in_bytes * 8 / (1024 * 1024); if (div) mbits /= div; From 8db67bb6a1fffa4df68fbbc22e39943aeeff9178 Mon Sep 17 00:00:00 2001 From: Coia Prant Date: Wed, 23 Sep 2026 20:37:13 +0800 Subject: [PATCH 2340/2366] net: ethernet: stmmac: dwmac-rk: fix bulk clock leak when the PHY clock fails gmac_clk_enable() enables the bulk clocks first and then the optional PHY clock. If clk_prepare_enable() on the PHY clock fails, the function returns without rolling back the bulk clocks, and bsp_priv->clk_enabled stays false, so the later gmac_clk_enable(bsp_priv, false) becomes a no-op and the bulk clock references are leaked. Add the missing clk_bulk_disable_unprepare() on that failure path. Fixes: ea449f7fa0bf ("net: ethernet: stmmac: dwmac-rk: rework optional clock handling") Reviewed-by: Maxime Chevallier Reviewed-by: Heiko Stuebner Acked-by: Lorenzo Bianconi Signed-off-by: Coia Prant Link: https://patch.msgid.link/20260923123713.3137146-1-coiaprant@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c index 8d7042e68926..72bdbcb5e863 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c @@ -1162,8 +1162,11 @@ static int gmac_clk_enable(struct rk_priv_data *bsp_priv, bool enable) return ret; ret = clk_prepare_enable(bsp_priv->clk_phy); - if (ret) + if (ret) { + clk_bulk_disable_unprepare(bsp_priv->num_clks, + bsp_priv->clks); return ret; + } rk_configure_io_clksel(bsp_priv); rk_ungate_rmii_clock(bsp_priv); From 06e3f54e8b22040ada01a28343badf1990b4dd7d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 23 Sep 2026 13:03:18 +0000 Subject: [PATCH 2341/2366] net: flush skb_defer_nodes in dev_cpu_dead() When a CPU goes offline, dev_cpu_dead() drains its softnet queues (completion_queue, output_queue, poll_list, process_queue, and input_pkt_queue), but leaves net_hotdata.skb_defer_nodes untouched. If oldcpu goes offline while holding pending skbs in its skb_defer_nodes lists (e.g. below the sysctl_skb_defer_max >> 1 IPI threshold, or if the IPI races with CPU teardown), those skbs remain stranded until oldcpu is brought back online. If any of these skbs hold page_pool fragments, page_pool_destroy() will stall indefinitely waiting for inflight pages to be returned when a netdev or driver is torn down while oldcpu is offline. Additionally, if smp_call_function_single_async() fails in kick_defer_list_purge() because the target CPU went offline, reset defer_ipi_scheduled to 0 so future IPI kicks are not blocked when the CPU comes back online. Also, if oldcpu was the last online CPU on its NUMA node, drain that node's slot across all CPUs so no skbs deferred from that node remain stranded on idle remote CPUs (or if the node itself is subsequently offlined). Finally, in skb_attempt_defer_free(), re-check cpu_online(cpu) and whether the caller migrated CPUs after llist_add(), flushing the node list if so, to close the preemption TOCTOU race against CPU/node teardown. Fixes: 68822bdf76f1 ("net: generalize skb freeing deferral to per-cpu lists") Fixes: 5628f3fe3b16 ("net: add NUMA awareness to skb_attempt_defer_free()") Closes: https://lore.kernel.org/netdev/20260916003430.3612956-1-kris.pan@intel.com/ Signed-off-by: Eric Dumazet Cc: Kris Pan Link: https://patch.msgid.link/20260923130318.607255-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 47 +++++++++++++++++++++++++++++++++++------------ net/core/dev.h | 2 ++ net/core/skbuff.c | 12 +++++++++--- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index 0292a16e16c2..f660fccfc0db 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -5376,7 +5376,8 @@ void kick_defer_list_purge(unsigned int cpu) backlog_unlock_irq_restore(sd, flags); } else if (!cmpxchg(&sd->defer_ipi_scheduled, 0, 1)) { - smp_call_function_single_async(cpu, &sd->defer_csd); + if (smp_call_function_single_async(cpu, &sd->defer_csd)) + WRITE_ONCE(sd->defer_ipi_scheduled, 0); } } @@ -6900,25 +6901,35 @@ bool napi_complete_done(struct napi_struct *n, int work_done) } EXPORT_SYMBOL(napi_complete_done); -static void skb_defer_free_flush(void) +static void __skb_defer_free_flush(struct skb_defer_node *sdn, int budget) { struct llist_node *free_list; struct sk_buff *skb, *next; + + if (llist_empty(&sdn->defer_list)) + return; + atomic_long_set(&sdn->defer_count, 0); + free_list = llist_del_all(&sdn->defer_list); + + llist_for_each_entry_safe(skb, next, free_list, ll_node) { + prefetch(next); + napi_consume_skb(skb, budget); + } +} + +void skb_defer_node_flush(struct skb_defer_node *sdn) +{ + __skb_defer_free_flush(sdn, 0); +} + +static void skb_defer_free_flush(void) +{ struct skb_defer_node *sdn; int node; for_each_node(node) { sdn = this_cpu_ptr(net_hotdata.skb_defer_nodes) + node; - - if (llist_empty(&sdn->defer_list)) - continue; - atomic_long_set(&sdn->defer_count, 0); - free_list = llist_del_all(&sdn->defer_list); - - llist_for_each_entry_safe(skb, next, free_list, ll_node) { - prefetch(next); - napi_consume_skb(skb, 1); - } + __skb_defer_free_flush(sdn, 1); } } @@ -12897,6 +12908,7 @@ static int dev_cpu_dead(unsigned int oldcpu) struct sk_buff **list_skb; struct sk_buff *skb; unsigned int cpu; + int node; struct softnet_data *sd, *oldsd, *remsd = NULL; local_irq_disable(); @@ -12957,6 +12969,17 @@ static int dev_cpu_dead(unsigned int oldcpu) rps_input_queue_head_incr(oldsd); } + for_each_node(node) + skb_defer_node_flush(per_cpu_ptr(net_hotdata.skb_defer_nodes, + oldcpu) + node); + node = cpu_to_node(oldcpu); + if (node_possible(node) && + !cpumask_intersects(cpumask_of_node(node), cpu_online_mask)) { + for_each_possible_cpu(cpu) + skb_defer_node_flush(per_cpu_ptr(net_hotdata.skb_defer_nodes, + cpu) + node); + } + return 0; } diff --git a/net/core/dev.h b/net/core/dev.h index b757faead4d1..04fb0e9a571e 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -399,6 +399,8 @@ static inline void napi_assert_will_not_race(const struct napi_struct *napi) WARN_ON(READ_ONCE(napi->list_owner) != -1); } +struct skb_defer_node; +void skb_defer_node_flush(struct skb_defer_node *sdn); void kick_defer_list_purge(unsigned int cpu); int dev_set_hwtstamp_phylib(struct net_device *dev, diff --git a/net/core/skbuff.c b/net/core/skbuff.c index b4edbd06655e..c3042d822afa 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -7360,8 +7360,8 @@ void skb_attempt_defer_free(struct sk_buff *skb) struct skb_defer_node *sdn; unsigned long defer_count; unsigned int defer_max; + int cpu, my_cpu; bool kick; - int cpu; if (static_branch_unlikely(&skb_defer_disable_key)) goto nodefer; @@ -7371,7 +7371,8 @@ void skb_attempt_defer_free(struct sk_buff *skb) goto nodefer; cpu = skb->alloc_cpu; - if (cpu == raw_smp_processor_id() || + my_cpu = raw_smp_processor_id(); + if (cpu == my_cpu || WARN_ON_ONCE(cpu >= nr_cpu_ids) || !cpu_online(cpu)) { nodefer: kfree_skb_napi_cache(skb); @@ -7382,7 +7383,7 @@ nodefer: kfree_skb_napi_cache(skb); DEBUG_NET_WARN_ON_ONCE(skb->destructor); DEBUG_NET_WARN_ON_ONCE(skb_nfct(skb)); - sdn = per_cpu_ptr(net_hotdata.skb_defer_nodes, cpu) + numa_node_id(); + sdn = per_cpu_ptr(net_hotdata.skb_defer_nodes, cpu) + cpu_to_node(my_cpu); defer_max = READ_ONCE(net_hotdata.sysctl_skb_defer_max); defer_count = atomic_long_inc_return(&sdn->defer_count); @@ -7392,6 +7393,11 @@ nodefer: kfree_skb_napi_cache(skb); llist_add(&skb->ll_node, &sdn->defer_list); + if (unlikely(!cpu_online(cpu) || my_cpu != raw_smp_processor_id())) { + skb_defer_node_flush(sdn); + return; + } + /* Send an IPI every time queue reaches half capacity. */ kick = (defer_count - 1) == (defer_max >> 1); From 83769c23fb1879edc916a526ba424285033baf2d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 23 Sep 2026 14:59:42 +0000 Subject: [PATCH 2342/2366] gve: DQO: fix header length used by gve_can_send_tso() for UDP GSO gve_can_send_tso() computes how many buffers each segment of a GSO packet would span, and for this it needs the length of the headers that the device replicates in front of every segment. It unconditionally uses skb_tcp_all_headers(), which reads the doff field of the TCP header. SKB_GSO_UDP_L4 packets have no TCP header: tcp_hdrlen() then reads one byte of the UDP payload, and header_len can be anything in [0, 60] instead of the transport offset plus the eight bytes of the UDP header that gve_prep_tso() programs into the TSO context descriptor. A wrong header length shifts all the segment boundaries computed in the loop, so the number of buffers per segment can be over or under estimated. In the first case, GSO is needlessly disabled for this packet by gve_features_check_dqo() and the stack has to segment it. In the second case, the driver hands the device a packet whose segments span more than GVE_TX_MAX_DATA_DESCS buffers. Use the UDP header length for SKB_GSO_UDP_L4 packets, matching what gve_prep_tso() does. Fixes: 014c607f86ab ("gve: add support for UDP GSO for DQO format") Closes: https://lore.kernel.org/netdev/CANn89i+MS4L60sFQ49=-f-mibeveUfcrpVkD5X+Qy6SOnEpd6w@mail.gmail.com/ Signed-off-by: Eric Dumazet Cc: Ankit Garg Cc: Harshitha Ramamurthy Cc: Joshua Washington Cc: Willem de Bruijn Reviewed-by: Ankit Garg Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260923145942.731365-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_tx_dqo.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/google/gve/gve_tx_dqo.c b/drivers/net/ethernet/google/gve/gve_tx_dqo.c index 80ab0a449ff5..0f6f7c5dbb2e 100644 --- a/drivers/net/ethernet/google/gve/gve_tx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_tx_dqo.c @@ -918,13 +918,19 @@ static bool gve_can_send_tso(const struct sk_buff *skb) { const int max_bufs_per_seg = GVE_TX_MAX_DATA_DESCS - 1; const struct skb_shared_info *shinfo = skb_shinfo(skb); - const int header_len = skb_tcp_all_headers(skb); const int gso_size = shinfo->gso_size; int cur_seg_num_bufs; int prev_frag_size; int cur_seg_size; + int header_len; int i; + /* Must match the header length programmed by gve_prep_tso(). */ + if (skb_is_gso_tcp(skb)) + header_len = skb_tcp_all_headers(skb); + else + header_len = skb_transport_offset(skb) + sizeof(struct udphdr); + cur_seg_size = skb_headlen(skb) - header_len; prev_frag_size = skb_headlen(skb); cur_seg_num_bufs = cur_seg_size > 0; From 3b430ea6234087957b0d3cd181e3116722b59819 Mon Sep 17 00:00:00 2001 From: Eddie Phillips Date: Thu, 24 Sep 2026 00:42:51 +0000 Subject: [PATCH 2343/2366] gve: fix TX drop when GSO MSS is too small for hw The device has a strict requirement that the minimum MSS (gso_size) for TSO/GSO packets must be at least 88 bytes. If a packet below this threshold is pushed to the hardware, it can cause hardware to silently drop the packet, leading to increased latency and retransmissions. Currently, this is validated too late in the transmit pipeline (gve_prep_tso), leading to silent drops. Fix this by moving the validation into the .ndo_features_check callback (gve_features_check_dqo). If we detect a GSO packet with a gso_size smaller than GVE_TX_MIN_TSO_MSS_DQO, we clear the GSO feature flags for this packet. Fixes: a57e5de476be ("gve: DQO: Add TX path") Signed-off-by: Eddie Phillips Signed-off-by: Eric Dumazet Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260924004252.1196328-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_tx_dqo.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_tx_dqo.c b/drivers/net/ethernet/google/gve/gve_tx_dqo.c index 0f6f7c5dbb2e..e5fe17b04798 100644 --- a/drivers/net/ethernet/google/gve/gve_tx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_tx_dqo.c @@ -577,17 +577,6 @@ static int gve_prep_tso(struct sk_buff *skb) int header_len; int err; - /* Note: HW requires MSS (gso_size) to be <= 9728 and the total length - * of the TSO to be <= 262143. - * - * However, we don't validate these because: - * - Hypervisor enforces a limit of 9K MTU - * - Kernel will not produce a TSO larger than 64k - */ - - if (unlikely(shinfo->gso_size < GVE_TX_MIN_TSO_MSS_DQO)) - return -1; - /* Needed because we will modify header. */ err = skb_cow_head(skb, 0); if (err < 0) @@ -925,6 +914,9 @@ static bool gve_can_send_tso(const struct sk_buff *skb) int header_len; int i; + if (unlikely(gso_size < GVE_TX_MIN_TSO_MSS_DQO)) + return false; + /* Must match the header length programmed by gve_prep_tso(). */ if (skb_is_gso_tcp(skb)) header_len = skb_tcp_all_headers(skb); From 296c83b5ccc808c080865eb20fd7a477b0355bb7 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 24 Sep 2026 00:42:52 +0000 Subject: [PATCH 2344/2366] gve: DQO: reject TSO packets with an out of range MSS gve_prep_tso() notes that the device requires the MSS to be <= 9728, but does not enforce it, assuming the 9K MTU enforced by the hypervisor and the 64KB limit on TSO sizes are enough. This does not hold for packets that were not generated locally. A guest behind a tap, or any packet socket user, can provide an arbitrary gso_size in virtio_net_hdr. Layer 2 forwarding does not check the MTU for GSO packets (is_skb_forwardable()), and gso_features_check() only bounds skb->len and gso_segs, never gso_size. Such a packet reaches gve_tx_fill_tso_ctx_desc(), which puts gso_size into the mss field of the TSO context descriptor. This field is 14 bits wide, so a gso_size of 16384 is silently turned into an MSS of zero. Drop these packets from gve_prep_tso(), and make sure that gve_features_check_dqo() leaves their GSO bits alone: skb_segment() splits at gso_size regardless of the MTU, so falling back to software segmentation would give the device non TSO packets bigger than the 9728 bytes it supports. Note that the device can still be given oversized non TSO packets when the stack segments in software for other reasons, for instance after TSO has been disabled with ethtool. This is a generic issue, because the MTU check is skipped for GSO packets in the forwarding path, and is addressed separately. Fixes: a57e5de476be ("gve: DQO: Add TX path") Signed-off-by: Eric Dumazet Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260924004252.1196328-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/google/gve/gve_desc_dqo.h | 5 ++++ drivers/net/ethernet/google/gve/gve_tx_dqo.c | 26 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/google/gve/gve_desc_dqo.h b/drivers/net/ethernet/google/gve/gve_desc_dqo.h index f7786b03c744..d2c86c8eeae2 100644 --- a/drivers/net/ethernet/google/gve/gve_desc_dqo.h +++ b/drivers/net/ethernet/google/gve/gve_desc_dqo.h @@ -14,6 +14,11 @@ #define GVE_TX_MAX_HDR_SIZE_DQO 255 #define GVE_TX_MIN_TSO_MSS_DQO 88 +/* HW limit. This also has to fit in the 14 bits of the mss field of + * struct gve_tx_tso_context_desc_dqo. + */ +#define GVE_TX_MAX_TSO_MSS_DQO 9728 + #ifndef __LITTLE_ENDIAN_BITFIELD #error "Only little endian supported" #endif diff --git a/drivers/net/ethernet/google/gve/gve_tx_dqo.c b/drivers/net/ethernet/google/gve/gve_tx_dqo.c index e5fe17b04798..616c1921aebe 100644 --- a/drivers/net/ethernet/google/gve/gve_tx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_tx_dqo.c @@ -577,6 +577,20 @@ static int gve_prep_tso(struct sk_buff *skb) int header_len; int err; + /* Note: HW requires the total length of the TSO to be <= 262143, + * this is enforced by netif_set_tso_max_size(). + * + * MSS (gso_size) can not be trusted: packets forwarded from a tap or + * injected by a packet socket can carry an arbitrary value, while the + * mss field of the TSO context descriptor is only 14 bits wide. + * + * A too big MSS is dropped here instead of being rejected from + * gve_features_check_dqo(), because software segmentation would + * produce packets larger than the device can send. + */ + if (unlikely(shinfo->gso_size > GVE_TX_MAX_TSO_MSS_DQO)) + return -1; + /* Needed because we will modify header. */ err = skb_cow_head(skb, 0); if (err < 0) @@ -964,7 +978,17 @@ netdev_features_t gve_features_check_dqo(struct sk_buff *skb, struct net_device *dev, netdev_features_t features) { - if (skb_is_gso(skb) && !gve_can_send_tso(skb)) + if (!skb_is_gso(skb)) + return features; + + /* Keep the GSO bits for a too big MSS, so that gve_prep_tso() drops + * the packet: software segmentation would give packets larger than + * the device can send. + */ + if (skb_shinfo(skb)->gso_size > GVE_TX_MAX_TSO_MSS_DQO) + return features; + + if (!gve_can_send_tso(skb)) return features & ~NETIF_F_GSO_MASK; return features; From 72b5b9a28b996e09b8b5b944370c79851bb68f52 Mon Sep 17 00:00:00 2001 From: Zixuan Chai Date: Thu, 24 Sep 2026 09:26:05 +0800 Subject: [PATCH 2345/2366] llc: reserve device headroom for allocated frames llc_alloc_frame() reserves link-layer headroom using the device type. This is insufficient for stacked Ethernet devices such as VLAN devices, where vlan_dev_hard_header() pushes a VLAN header before the lower device's Ethernet header. An LLC response on such a device can therefore underflow skb headroom in eth_header(). Use LL_RESERVED_SPACE() to account for the device's actual required headroom while preserving the existing LLC device-type check. Fixes: bf9ae5386bca ("llc: use dev_hard_header") Cc: stable@vger.kernel.org Reported-by: VEGA Signed-off-by: Zixuan Chai Signed-off-by: Ren Wei Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260924012613.2533934-1-weir@nebusec.ai Signed-off-by: Jakub Kicinski --- net/llc/llc_sap.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/llc/llc_sap.c b/net/llc/llc_sap.c index 1bd446a21092..3904a1b4ba84 100644 --- a/net/llc/llc_sap.c +++ b/net/llc/llc_sap.c @@ -19,12 +19,12 @@ #include #include -static int llc_mac_header_len(unsigned short devtype) +static int llc_mac_header_len(struct net_device *dev) { - switch (devtype) { + switch (dev->type) { case ARPHRD_ETHER: case ARPHRD_LOOPBACK: - return sizeof(struct ethhdr); + return LL_RESERVED_SPACE(dev); } return 0; } @@ -45,7 +45,7 @@ struct sk_buff *llc_alloc_frame(struct sock *sk, struct net_device *dev, int hlen = type == LLC_PDU_TYPE_U ? 3 : 4; struct sk_buff *skb; - hlen += llc_mac_header_len(dev->type); + hlen += llc_mac_header_len(dev); skb = alloc_skb(hlen + data_size, GFP_ATOMIC); if (skb) { From 72f9dd522f8d6c5a00be9695c7bb74631eb5069e Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 24 Sep 2026 08:29:48 +0000 Subject: [PATCH 2346/2366] llc: fix skb UAF and leaks on llc_mac_hdr_init() failure In llc_conn_ac_resend_i_xxx_x_set_0_or_send_rr(), if llc_mac_hdr_init() fails, kfree_skb(skb) is called instead of kfree_skb(nskb). This leaks the newly allocated nskb, reads from the freed skb via LLC_I_GET_NR(pdu), and double-frees skb when llc_conn_state_process() drops its reference. In llc_sap_action_send_xid_r() and llc_sap_action_send_test_r(), nskb is leaked if llc_mac_hdr_init() returns an error. Free nskb in all three error paths. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Closes: https://lore.kernel.org/netdev/179022851638.2160803.1808206741379444999@kernel.org/ Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260924082951.1599377-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/llc/llc_c_ac.c | 2 +- net/llc/llc_s_ac.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/net/llc/llc_c_ac.c b/net/llc/llc_c_ac.c index 724ecd741d4c..1aa7fe28acdd 100644 --- a/net/llc/llc_c_ac.c +++ b/net/llc/llc_c_ac.c @@ -437,7 +437,7 @@ int llc_conn_ac_resend_i_xxx_x_set_0_or_send_rr(struct sock *sk, if (likely(!rc)) llc_conn_send_pdu(sk, nskb); else - kfree_skb(skb); + kfree_skb(nskb); } if (rc) { nr = LLC_I_GET_NR(pdu); diff --git a/net/llc/llc_s_ac.c b/net/llc/llc_s_ac.c index 98deee560373..831998211b52 100644 --- a/net/llc/llc_s_ac.c +++ b/net/llc/llc_s_ac.c @@ -121,6 +121,8 @@ int llc_sap_action_send_xid_r(struct llc_sap *sap, struct sk_buff *skb) rc = llc_mac_hdr_init(nskb, mac_sa, mac_da); if (likely(!rc)) rc = dev_queue_xmit(nskb); + else + kfree_skb(nskb); out: return rc; } @@ -170,6 +172,8 @@ int llc_sap_action_send_test_r(struct llc_sap *sap, struct sk_buff *skb) rc = llc_mac_hdr_init(nskb, mac_sa, mac_da); if (likely(!rc)) rc = dev_queue_xmit(nskb); + else + kfree_skb(nskb); out: return rc; } From ac704ff08e511c87643799c385f55ecd69b85e03 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 24 Sep 2026 08:29:49 +0000 Subject: [PATCH 2347/2366] bridge: check llc_mac_hdr_init() return value in br_send_bpdu() If llc_mac_hdr_init() fails (for instance if the port device type does not support LLC or dev_hard_header() fails), br_send_bpdu() should drop the skb instead of resetting the mac header to the LLC payload and transmitting a malformed frame. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Closes: https://lore.kernel.org/netdev/179022851638.2160803.1808206741379444999@kernel.org/ Cc: Nikolay Aleksandrov Cc: Ido Schimmel Cc: bridge@lists.linux.dev Signed-off-by: Eric Dumazet Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260924082951.1599377-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/bridge/br_stp_bpdu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/bridge/br_stp_bpdu.c b/net/bridge/br_stp_bpdu.c index 74ec42ba1e7d..21d092f5acbb 100644 --- a/net/bridge/br_stp_bpdu.c +++ b/net/bridge/br_stp_bpdu.c @@ -52,7 +52,10 @@ static void br_send_bpdu(struct net_bridge_port *p, LLC_SAP_BSPAN, LLC_PDU_CMD); llc_pdu_init_as_ui_cmd(skb); - llc_mac_hdr_init(skb, p->dev->dev_addr, p->br->group_addr); + if (llc_mac_hdr_init(skb, p->dev->dev_addr, p->br->group_addr)) { + kfree_skb(skb); + return; + } skb_reset_mac_header(skb); From 907b978e82cb4c1c245fc2985bb27c5d5c88c8f6 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 24 Sep 2026 08:29:50 +0000 Subject: [PATCH 2348/2366] net/sched: sch_teql: fix shadowed err in __teql_resolve() __teql_resolve() declares an inner 'int err;' inside the 'if (neigh_event_send(n, skb_res) == 0)' block, shadowing the outer 'int err = 0;'. As a result, a negative return from dev_hard_header() is written to the inner variable and __teql_resolve() still returns 0. Remove the shadowed variable and set the outer err to -EINVAL when dev_hard_header() returns a negative error. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Closes: https://lore.kernel.org/netdev/179022851638.2160803.1808206741379444999@kernel.org/ Cc: Jamal Hadi Salim Cc: Jiri Pirko Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260924082951.1599377-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/sched/sch_teql.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c index 9e52afc2d980..409ce50cc0db 100644 --- a/net/sched/sch_teql.c +++ b/net/sched/sch_teql.c @@ -265,14 +265,11 @@ __teql_resolve(struct sk_buff *skb, struct sk_buff *skb_res, } if (neigh_event_send(n, skb_res) == 0) { - int err; char haddr[MAX_ADDR_LEN]; neigh_ha_snapshot(haddr, n, dev); - err = dev_hard_header(skb, dev, ntohs(skb_protocol(skb, false)), - haddr, NULL, skb->len); - - if (err < 0) + if (dev_hard_header(skb, dev, ntohs(skb_protocol(skb, false)), + haddr, NULL, skb->len) < 0) err = -EINVAL; } else { err = (skb_res == NULL) ? -EAGAIN : 1; From cd5dd68267c4238795fadaf02b3575ca3f8a6500 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 24 Sep 2026 08:29:51 +0000 Subject: [PATCH 2349/2366] vlan: ensure sufficient headroom in vlan_dev_hard_header() Callers that only reserve ETH_HLEN or less (such as llc_alloc_frame()), or skbs allocated before dynamic device/headroom changes (e.g. toggling VLAN_FLAG_REORDER_HDR or bonding/team switching slaves), can reach vlan_dev_hard_header() with insufficient headroom and trigger skb_under_panic(). Use skb_cow_head() in vlan_dev_hard_header() when VLAN_FLAG_REORDER_HDR is not set to ensure sufficient headroom for the VLAN header(s) and the underlying device hard header. Use READ_ONCE() to read dev->hard_header_len and dev->needed_headroom as they can be updated concurrently under RTNL (e.g. in vlan_transfer_features()) while vlan_dev_hard_header() runs locklessly on the transmit path. Also avoid LL_RESERVED_SPACE(dev) here so that the extra HH_DATA_MOD alignment padding does not trigger unnecessary pskb_expand_head() reallocations on inner stacked VLAN devices after the outer VLAN header has been pushed. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Zixuan Chai Closes: https://lore.kernel.org/netdev/cover.1789987105.git.petalzu987@gmail.com/ Link: https://lore.kernel.org/netdev/179022851638.2160803.1808206741379444999@kernel.org/ Cc: Hangbin Liu Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260924082951.1599377-5-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/8021q/vlan_dev.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c index 2859cbac3f26..c949c6a82945 100644 --- a/net/8021q/vlan_dev.c +++ b/net/8021q/vlan_dev.c @@ -55,6 +55,11 @@ static int vlan_dev_hard_header(struct sk_buff *skb, struct net_device *dev, int rc; if (!(vlan->flags & VLAN_FLAG_REORDER_HDR)) { + unsigned int hlen = READ_ONCE(dev->hard_header_len) + + READ_ONCE(dev->needed_headroom); + + if (skb_cow_head(skb, hlen) < 0) + return -ENOMEM; vhdr = skb_push(skb, VLAN_HLEN); vlan_tci = vlan->vlan_id; From fc6d80eb504458d6416b75a94188b268c95c6533 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Thu, 24 Sep 2026 11:44:12 -0400 Subject: [PATCH 2350/2366] tcp: prevent collapsing skbs across boundary in rtx queue tcp_write_collapse_fence() sets TCP_SKB_CB(skb)->eor = 1 on tcp_write_queue_tail(sk) to prevent skbs queued after a switch to device encryption from being collapsed into earlier skbs. The fence is a no-op if all earlier data has already been transmitted when the switch happens: sk->sk_write_queue is empty. The not yet acknowledged earlier skbs wait in sk->tcp_rtx_queue with eor 0. On a subsequent retransmit or SACK shift, tcp_retrans_try_collapse() or tcp_shift_skb_data() can then merge an skb queued after the switch into one queued before it. Both users of the fence are affected: - psp: devices only encrypt skbs with skb->decrypted set. The merged skb keeps decrypted = 0 from the earlier skb, so merged data sent after psp_sock_assoc_set_tx() is retransmitted in cleartext. - tls device offload: the merged skb straddles the start marker set in tls_set_device_offload(). The software fallback (fill_sg_in() returns -EINVAL) and the mlx5, nfp and funeth drivers cannot handle such an skb and drop it. Every retransmit rebuilds the same skb, so the connection stalls. Fix this in two places, for defense in depth: 1. Fall back to tcp_rtx_queue_tail(sk) in tcp_write_collapse_fence() when tcp_write_queue_tail(sk) is NULL. 2. Check !skb_cmp_decrypted(to, from) in tcp_skb_can_collapse(), as tcp_skb_can_collapse_rx() does on receive. skb_shift(), which both collapse paths call, already has a DEBUG_NET_WARN_ON_ONCE() for this condition. Fixes: e8f69799810c ("net/tls: Add generic NIC offload infrastructure") Cc: stable@vger.kernel.org Signed-off-by: Willem de Bruijn Reviewed-by: Eric Dumazet Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/20260924154427.953800-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- include/net/tcp.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/net/tcp.h b/include/net/tcp.h index 436495ff2271..4416cdf9bf30 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -1232,9 +1232,9 @@ static inline bool tcp_skb_can_collapse_to(const struct sk_buff *skb) static inline bool tcp_skb_can_collapse(const struct sk_buff *to, const struct sk_buff *from) { - /* skb_cmp_decrypted() not needed, use tcp_write_collapse_fence() */ return likely(tcp_skb_can_collapse_to(to) && mptcp_skb_can_collapse(to, from) && + !skb_cmp_decrypted(to, from) && skb_pure_zcopy_same(to, from) && skb_frags_readable(to) == skb_frags_readable(from)); } @@ -2327,7 +2327,7 @@ static inline void tcp_rtx_queue_unlink_and_free(struct sk_buff *skb, struct soc static inline void tcp_write_collapse_fence(struct sock *sk) { - struct sk_buff *skb = tcp_write_queue_tail(sk); + struct sk_buff *skb = tcp_write_queue_tail(sk) ?: tcp_rtx_queue_tail(sk); if (skb) TCP_SKB_CB(skb)->eor = 1; From cb97bf3d4f91453b881acaf8e9f0cc47bb40b604 Mon Sep 17 00:00:00 2001 From: Viken Dadhaniya Date: Mon, 21 Sep 2026 17:17:01 +0530 Subject: [PATCH 2351/2366] i2c: qcom-geni: Fix hardcoded clock index in SE_GENI_CLK_SEL qcom_geni_i2c_conf() writes a hardcoded 0 to SE_GENI_CLK_SEL, which selects an index from the hardware clock performance table. This always picks the first table entry regardless of the actual source clock configuration. On platforms where the matching entry is not at index 0, the wrong source clock divider is active and the I2C bus runs at an incorrect frequency. Use geni_se_clk_freq_match() in geni_i2c_clk_map_idx() to find the performance table index for the source clock (32 MHz or 19.2 MHz). Store the resolved index in a new clk_idx field in geni_i2c_dev and write it to SE_GENI_CLK_SEL instead of the hardcoded 0. Fixes: 37692de5d523 ("i2c: i2c-qcom-geni: Add bus driver for the Qualcomm GENI I2C controller") Signed-off-by: Viken Dadhaniya Cc: # v4.19+ Reviewed-by: Mukesh Kumar Savaliya Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260921-i2c-fix-se-clk-conf-v2-1-8b5537ceff2d@oss.qualcomm.com --- drivers/i2c/busses/i2c-qcom-geni.c | 38 ++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index e9e41a174c20..6a39e21d5ed4 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -82,6 +82,9 @@ enum geni_i2c_err_code { #define XFER_TIMEOUT HZ #define RST_TIMEOUT HZ +#define GENI_SE_CLK_32MHZ (32 * HZ_PER_MHZ) +#define GENI_SE_CLK_19P2MHZ 19200000UL + struct geni_i2c_desc { bool no_dma_support; unsigned int tx_fifo_depth; @@ -127,6 +130,7 @@ struct geni_i2c_dev { spinlock_t lock; u32 clk_freq_out; const struct geni_i2c_clk_fld *clk_fld; + u32 clk_idx; void *dma_buf; size_t xfer_len; dma_addr_t dma_addr; @@ -197,19 +201,44 @@ static const struct geni_i2c_clk_fld geni_i2c_clk_map_32mhz[] = { static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c) { const struct geni_i2c_clk_fld *itr; + unsigned long res_freq; - if (clk_get_rate(gi2c->se.clk) == 32 * HZ_PER_MHZ) + /* + * Frequency counter tables are calibrated for a specific source + * clock frequency and are not valid for any multiple of it + * (e.g. 64 MHz, 128 MHz). + * Use exact=true and verify res_freq matches req_freq literally + * to reject harmonics: a 64 MHz clock that divides evenly to + * 32 MHz would pass exact matching but produce double the intended + * I2C frequency with these counter values. + */ + if (!geni_se_clk_freq_match(&gi2c->se, GENI_SE_CLK_32MHZ, + &gi2c->clk_idx, &res_freq, true) && + res_freq == GENI_SE_CLK_32MHZ) { itr = geni_i2c_clk_map_32mhz; - else + } else if (!geni_se_clk_freq_match(&gi2c->se, GENI_SE_CLK_19P2MHZ, + &gi2c->clk_idx, &res_freq, true) && + res_freq == GENI_SE_CLK_19P2MHZ) { itr = geni_i2c_clk_map_19p2mhz; + } else { + dev_err(gi2c->se.dev, + "Unsupported SE source clock: must be exactly 32 MHz or 19.2 MHz\n"); + return -EINVAL; + } while (itr->clk_freq_out != 0) { if (itr->clk_freq_out == gi2c->clk_freq_out) { gi2c->clk_fld = itr; + dev_dbg(gi2c->se.dev, + "I2C clk selected: freq: %u Hz, clk_idx: %u\n", + gi2c->clk_freq_out, gi2c->clk_idx); return 0; } itr++; } + + dev_err(gi2c->se.dev, "Unsupported I2C output frequency %u Hz\n", gi2c->clk_freq_out); + return -EINVAL; } @@ -219,7 +248,7 @@ static int qcom_geni_i2c_conf(struct geni_se *se, unsigned long freq) const struct geni_i2c_clk_fld *itr = gi2c->clk_fld; u32 val; - writel_relaxed(0, gi2c->se.base + SE_GENI_CLK_SEL); + writel_relaxed(gi2c->clk_idx, gi2c->se.base + SE_GENI_CLK_SEL); val = (itr->clk_div << CLK_DIV_SHFT) | SER_CLK_EN; writel_relaxed(val, gi2c->se.base + GENI_SER_M_CLK_CFG); @@ -1111,8 +1140,7 @@ static int geni_i2c_resources_init(struct geni_se *se) ret = geni_i2c_clk_map_idx(gi2c); if (ret) - return dev_err_probe(gi2c->se.dev, ret, "Invalid clk frequency %d Hz\n", - gi2c->clk_freq_out); + return ret; return geni_icc_set_bw_ab(&gi2c->se, GENI_DEFAULT_BW, GENI_DEFAULT_BW, Bps_to_icc(gi2c->clk_freq_out)); From 113dcdfadf30ea11fbbdfcd4f6ea87687655cc5b Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 25 Sep 2026 13:23:29 +0800 Subject: [PATCH 2352/2366] MAINTAINERS: name the libata/linux for-next branch The T: entry for LIBATA SUBSYSTEM (Serial and Parallel ATA drivers) names libata/linux without a branch. The repository's HEAD pointer points to branch master, which has no active development. Active development is on the for-next branch. Name the branch so the entry identifies where development happens. Documentation/process/submitting-patches.rst sends contributors to the T: entry to find the tree to prepare patches against, so a branch-less entry whose HEAD is already in mainline points them to the wrong branch. Reviewed-by: Damien Le Moal Signed-off-by: Matthias Goergens Link: https://lore.kernel.org/r/20260925052329.2683619-1-matthias.goergens@gmail.com Signed-off-by: Niklas Cassel --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..3e7ede95c7cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14911,7 +14911,7 @@ M: Damien Le Moal M: Niklas Cassel L: linux-ide@vger.kernel.org S: Maintained -T: git git://git.kernel.org/pub/scm/linux/kernel/git/libata/linux.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/libata/linux.git for-next F: Documentation/ABI/testing/sysfs-ata F: Documentation/devicetree/bindings/ata/ F: drivers/ata/ From ae146bc1abdeb4607abf2975b858c053024e8ac1 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Mon, 21 Sep 2026 12:40:13 +0200 Subject: [PATCH 2353/2366] ovl: fix UAF in ovl_do_mkdir() debug print ovl_do_mkdir() prints the input dentry with %pd after vfs_mkdir(). Since commit fe497f0759e0 ("VFS: change vfs_mkdir() to unlock on failure."), vfs_mkdir() calls end_creating() on the input dentry on failure and may replace it on success, so the post-call %pd can use-after-free the dentry when CONFIG_OVERLAY_FS_DEBUG is enabled. Print the dentry before the call and only the result afterward. Reported-by: syzbot+ced26b784bf977d223dd@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ced26b784bf977d223dd Fixes: fe497f0759e0 ("VFS: change vfs_mkdir() to unlock on failure.") Signed-off-by: Amir Goldstein Link: https://patch.msgid.link/20260921104013.40475-1-amir73il@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/overlayfs/overlayfs.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index e0d8c6152e9f..7f3558372c59 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -254,8 +254,10 @@ static inline struct dentry *ovl_do_mkdir(struct ovl_fs *ofs, { struct dentry *ret; + /* vfs_mkdir() drops @dentry on failure and may replace it on success */ + pr_debug("mkdir(%pd2, 0%o)\n", dentry, mode); ret = vfs_mkdir(ovl_upper_mnt_idmap(ofs), dir, dentry, mode, NULL); - pr_debug("mkdir(%pd2, 0%o) = %i\n", dentry, mode, PTR_ERR_OR_ZERO(ret)); + pr_debug("...mkdir = %i\n", PTR_ERR_OR_ZERO(ret)); return ret; } From 76d8e697242e7e4e30dd08eff7c278728f9dd8e2 Mon Sep 17 00:00:00 2001 From: Drif Abdelmalek Mohamed Said Date: Fri, 18 Sep 2026 23:42:04 +0100 Subject: [PATCH 2354/2366] dcache: unpoison the inline name buffer in __d_alloc() syzbot reported: BUG: KMSAN: uninit-value in dentry_string_cmp fs/dcache.c:291 [inline] BUG: KMSAN: uninit-value in dentry_cmp fs/dcache.c:322 [inline] BUG: KMSAN: uninit-value in __d_lookup_rcu+0x37d/0x5e0 fs/dcache.c:2522 dentry_string_cmp fs/dcache.c:291 [inline] dentry_cmp fs/dcache.c:322 [inline] __d_lookup_rcu+0x37d/0x5e0 fs/dcache.c:2522 lookup_fast+0x194/0xa40 fs/namei.c:1854 lookup_fast_for_open fs/namei.c:4545 [inline] open_last_lookups fs/namei.c:4579 [inline] path_openat+0x9ef/0x6540 fs/namei.c:4856 do_file_open+0x2aa/0x680 fs/namei.c:4888 do_sys_openat2+0x17c/0x390 fs/open.c:1395 do_sys_open fs/open.c:1401 [inline] __do_sys_openat fs/open.c:1417 [inline] __se_sys_openat fs/open.c:1412 [inline] __x64_sys_openat+0x240/0x300 fs/open.c:1412 x64_sys_call+0x2445/0x3ea0 arch/x86/include/generated/asm/syscalls_64.h:258 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x15d/0x3c0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Uninit was stored to memory at: copy_name fs/dcache.c:3031 [inline] __d_move+0xd29/0x21f0 fs/dcache.c:3099 d_move+0x71/0xf0 fs/dcache.c:3147 vfs_rename+0x2619/0x2770 fs/namei.c:6085 filename_renameat2+0xa59/0x1230 fs/namei.c:6188 __do_sys_rename fs/namei.c:6232 [inline] __se_sys_rename+0xc5/0x5c0 fs/namei.c:6228 __x64_sys_rename+0x78/0xb0 fs/namei.c:6228 x64_sys_call+0x329/0x3ea0 arch/x86/include/generated/asm/syscalls_64.h:83 do_syscall_x64 arch/x86/entry/syscall_64.c:63 do_syscall_64+0x15d/0x3c0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Uninit was created at: slab_post_alloc_hook mm/slub.c:4617 [inline] slab_alloc_node mm/slub.c:4939 [inline] kmem_cache_alloc_lru_noprof+0x376/0x1230 mm/s __d_alloc+0x52/0x9f0 fs/dcache.c:1902 d_alloc+0x57/0x300 fs/dcache.c:1981 lookup_one_qstr_excl+0x19d/0x7a0 fs/namei.c:1806 __start_renaming+0x341/0x850 fs/namei.c:3888 filename_renameat2+0x625/0x1230 fs/namei.c:6163 __do_sys_rename fs/namei.c:6232 [inline] __se_sys_rename+0xc5/0x5c0 fs/namei.c:6228 __x64_sys_rename+0x78/0xb0 fs/namei.c:6228 x64_sys_call+0x329/0x3ea0 arch/x86/include/generated/asm/syscalls_64.h:83 do_syscall_x64 arch/x86/entry/syscall_64.c:63 do_syscall_64+0x15d/0x3c0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f The race is between a concurrent open() and rename() of the same path. __d_alloc() only stores the name itself and its terminating NUL, so the rest of the inline buffer (d_shortname, DNAME_INLINE_LEN bytes) is left uninitialized. copy_name(), called from rename(), copies that buffer as a whole, so the uninitialized tail is propagated into the dentry that is being moved. Meanwhile __d_lookup_rcu(), called from open(), is an optimistic lockless lookup: it checks d_name.hash_len first and leaves the seqcount retry to its caller, so it can end up comparing against a dentry whose name a rename is rewriting in place, using a stale (longer) length. The comparison then runs past the terminating NUL and reads bytes of the uninitialized tail, which KMSAN reports. The read is harmless by design: it stays inside the buffer, the name is still NUL-terminated, and the result is thrown away by the seqcount retry. It is not specific to KMSAN either - with CONFIG_DCACHE_WORD_ACCESS enabled the very same bytes are read by read_word_at_a_time(), which is __no_sanitize_or_inline and therefore invisible to KMSAN. KMSAN builds only see the instrumented byte-at-a-time dentry_string_cmp() because CONFIG_DCACHE_WORD_ACCESS is disabled when KMSAN is enabled on x86: commit 7cf8f44a5a1c ("x86: fs: kmsan: disable CONFIG_DCACHE_WORD_ACCESS") Zeroing the inline buffer would hide the report, but it would add a memset() to a hot allocation path just to initialize bytes that are never used as part of a name. Instead, tell KMSAN the inline buffer is initialized: kmsan_unpoison_memory() compiles to nothing unless CONFIG_KMSAN is set, and doing it at allocation time is enough for every dentry, because copy_name() and swap_names() copy the whole buffer and thus propagate its shadow. Reported-by: syzbot+7ff3adde89dd795ad4c4@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7ff3adde89dd795ad4c4 Signed-off-by: Drif Abdelmalek Mohamed Said Changes in v3: - Annotate for KMSAN instead of zeroing, as suggested in review: the read is harmless, so unpoison the inline buffer in __d_alloc() with kmsan_unpoison_memory() (a no-op unless CONFIG_KMSAN) rather than adding a memset() to the dentry allocation path. - Document why only KMSAN builds report this at all: with CONFIG_DCACHE_WORD_ACCESS the same read goes through read_word_at_a_time(), which KMSAN does not instrument. - Rewrite the commit message; the previous one had several truncated lines. Link: https://patch.msgid.link/20260918224204.3056-1-drifabdelmalekmohamedsaid@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/dcache.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/dcache.c b/fs/dcache.c index 1b1a81f10da6..a66be85f9d01 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1916,6 +1916,10 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name) * be overwriting an internal NUL character */ dentry->d_shortname.string[DNAME_INLINE_LEN-1] = 0; + + /* Racy __d_lookup_rcu() walk may read past the NUL; harmless */ + kmsan_unpoison_memory(dentry->d_shortname.string, DNAME_INLINE_LEN); + if (unlikely(!name)) { name = &slash_name; dname = dentry->d_shortname.string; From 5bfa9f1a9dcb6ecb607adbc1c0226605c972935b Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Thu, 24 Sep 2026 11:21:39 +0200 Subject: [PATCH 2355/2366] kprobes: Fix permanent hang when flushing the kprobe optimizer Writing 0 to /proc/sys/debug/kprobes-optimization while a kprobe is jump-optimized never returns. The writer sleeps in D state forever with kprobe_sysctl_mutex held, so any later read or write of that sysctl hangs as well. For example, with vfs_read+9 as an optimizable address in this build: # cd /sys/kernel/tracing # echo 'p:myprobe vfs_read+9' >> kprobe_events # echo 1 > events/kprobes/myprobe/enable # # wait until /sys/kernel/debug/kprobes/list shows [OPTIMIZED] # echo 0 > /proc/sys/debug/kprobes-optimization INFO: task sh:246 blocked for more than 10 seconds. Call Trace: __schedule+0x1176/0x4f70 schedule+0xdc/0x2c0 schedule_timeout+0x17b/0x260 wait_for_completion+0x173/0x3c0 wait_for_kprobe_optimizer_locked+0xbc/0x130 proc_kprobes_optimization_handler+0x156/0x1b0 proc_sys_call_handler+0x324/0x490 vfs_write+0x52d/0xfe0 ksys_write+0xff/0x200 do_syscall_64+0x106/0x630 entry_SYSCALL_64_after_hwframe+0x77/0x7f ... INFO: task cat:265 is blocked on a mutex likely owned by task sh:246. wait_for_kprobe_optimizer_locked() reinitializes optimizer_completion, asks the optimizer thread to flush and sleeps in wait_for_completion(). The thread drains the (un)optimizing lists, but calls complete() only if completion_done() is true, i.e. if the completion is already done, which never happens while someone waits. disarm_all_kprobes() and kprobe_trace_self_tests_init() wait the same way. Calling complete() unconditionally would not be enough: the waiter drops kprobe_mutex while it sleeps, and nothing else serializes the sysctl handler against the debugfs "enabled" file. A second flusher that still finds the lists non-empty, e.g. because a disabled probe is queued for unoptimizing, reinitializes the completion under the first: sysctl write debugfs "enabled" write unoptimize_all_kprobes() wait_for_kprobe_optimizer_locked() init_completion(c) mutex_unlock(&kprobe_mutex) wait_for_completion(c) disarm_all_kprobes() wait_for_kprobe_optimizer_locked() init_completion(c) // c->wait is reset, the first // waiter is off the queue mutex_unlock(&kprobe_mutex) wait_for_completion(c) kprobe_optimizer() complete(c) // wakes the debugfs writer only where c is &optimizer_completion. Lining up the two writes during an optimizer pass loses the sysctl writer this way. Replace the completion with a counter of optimizer passes, bumped at the end of each pass and signalled with wake_up_var_locked(), both under kprobe_mutex. A flusher samples the count and waits with wait_var_event_mutex(), which drops kprobe_mutex only while sleeping, so a new count means a whole pass ran in the meantime. Nothing is reinitialized, so several flushers can sleep in the wait at once. Link: https://lore.kernel.org/all/20260924092142.199198-1-parri.andrea@gmail.com/ Fixes: 73c12f209462 ("kprobes: Use dedicated kthread for kprobe optimizer") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Andrea Parri Signed-off-by: Masami Hiramatsu (Google) --- kernel/kprobes.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/kernel/kprobes.c b/kernel/kprobes.c index 6337da5cab9e..4edd8ca5c657 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -526,7 +527,8 @@ enum { OPTIMIZER_ST_FLUSHING = 2, }; -static DECLARE_COMPLETION(optimizer_completion); +/* Bumped at the end of each kprobe_optimizer() pass, under 'kprobe_mutex' */ +static unsigned long optimizer_passes; #define OPTIMIZE_DELAY 5 @@ -654,9 +656,9 @@ static void kprobe_optimizer(void) do_free_cleaned_kprobes(); } - /* Step 5: Kick optimizer again if needed. But if there is a flush requested, */ - if (completion_done(&optimizer_completion)) - complete(&optimizer_completion); + /* Step 5: Wake up flushers, and kick optimizer again if needed. */ + optimizer_passes++; + wake_up_var_locked(&optimizer_passes, &kprobe_mutex); if (!list_empty(&optimizing_list) || !list_empty(&unoptimizing_list)) kick_kprobe_optimizer(); /*normal kick*/ @@ -708,7 +710,8 @@ static void wait_for_kprobe_optimizer_locked(void) lockdep_assert_held(&kprobe_mutex); while (!list_empty(&optimizing_list) || !list_empty(&unoptimizing_list)) { - init_completion(&optimizer_completion); + unsigned long passes = optimizer_passes; + /* * Set state to OPTIMIZER_ST_FLUSHING and wake up the thread if it's * idle. If it's already kicked, it will see the state change. @@ -717,9 +720,12 @@ static void wait_for_kprobe_optimizer_locked(void) OPTIMIZER_ST_FLUSHING) != OPTIMIZER_ST_FLUSHING) wake_up(&kprobe_optimizer_wait); - mutex_unlock(&kprobe_mutex); - wait_for_completion(&optimizer_completion); - mutex_lock(&kprobe_mutex); + /* + * kprobe_optimizer() holds 'kprobe_mutex' for a whole pass, which + * this drops while sleeping, so a new count means a full pass ran. + */ + wait_var_event_mutex(&optimizer_passes, + optimizer_passes != passes, &kprobe_mutex); } } From aa5e44b29ffe4eaa08cc2237fd65bc2596bc023e Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 20:48:08 +0000 Subject: [PATCH 2356/2366] autofs: fix sbi->pipe file reference leak in autofs_kill_sb() When autofs_fill_super() fails before clearing AUTOFS_SBI_CATATONIC (for example, when find_get_pid() fails on an invalid pgrp mount option, or when an fs_context is closed before mounting), deactivate_locked_super() invokes autofs_kill_sb() -> autofs_catatonic_mode(sbi). Because AUTOFS_SBI_CATATONIC is still set in sbi->flags, autofs_catatonic_mode() returns early without calling fput(sbi->pipe), permanently leaking the pipe struct file reference. Explicitly release sbi->pipe in autofs_kill_sb() if it is still non-NULL after autofs_catatonic_mode(). Fixes: ebc921ca9b92 ("autofs: copy autofs4 to autofs") Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260919204808.2812930-1-benquike@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/autofs/inode.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/autofs/inode.c b/fs/autofs/inode.c index 6b15a3717ba7..066c16f2ea56 100644 --- a/fs/autofs/inode.c +++ b/fs/autofs/inode.c @@ -51,6 +51,10 @@ void autofs_kill_sb(struct super_block *sb) if (sbi) { /* Free wait queues, close pipe */ autofs_catatonic_mode(sbi); + if (sbi->pipe) { + fput(sbi->pipe); + sbi->pipe = NULL; + } put_pid(sbi->oz_pgrp); } From 35d442ed1f86465e49df3119fb898f985186db13 Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Tue, 22 Sep 2026 16:55:30 +0200 Subject: [PATCH 2357/2366] bpf: fs/xattr: don't assume the inode is locked in path_unlink/path_rmdir bpf_lsm_has_d_inode_locked() makes the verifier rewrite bpf_[set|remove]_dentry_xattr() to the _locked variants, which assume that the caller already holds the inode's i_rwsem. The path_unlink and path_rmdir hooks are listed, but security_path_unlink() and security_path_rmdir() run before vfs_unlink()/vfs_rmdir() take the victim inode's i_rwsem, so a sleepable BPF LSM program attached to either hook mutates the victim's xattrs without the lock held. Drop the two path hooks from d_inode_locked_hooks so that the verifier keeps the locking bpf_[set|remove]_dentry_xattr() variants, which take the lock themselves. Fixes: 56467292794b8 ("bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs") Cc: stable@vger.kernel.org Signed-off-by: Andrea Parri Link: https://patch.msgid.link/20260922145530.369775-1-parri.andrea@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/bpf_fs_kfuncs.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c index 6cb877267978..357a379ef92a 100644 --- a/fs/bpf_fs_kfuncs.c +++ b/fs/bpf_fs_kfuncs.c @@ -472,10 +472,6 @@ BTF_ID(func, bpf_lsm_inode_rmdir) BTF_ID(func, bpf_lsm_inode_setattr) BTF_ID(func, bpf_lsm_inode_setxattr) BTF_ID(func, bpf_lsm_inode_unlink) -#ifdef CONFIG_SECURITY_PATH -BTF_ID(func, bpf_lsm_path_unlink) -BTF_ID(func, bpf_lsm_path_rmdir) -#endif /* CONFIG_SECURITY_PATH */ BTF_SET_END(d_inode_locked_hooks) bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog) From b78b728e21c32ec4c330b299f657fb1eb02dffc2 Mon Sep 17 00:00:00 2001 From: Hao Ge Date: Wed, 23 Sep 2026 14:37:59 +0800 Subject: [PATCH 2358/2366] netfs: Fix missing alloc tagging of direct mempool allocations Commit 1d78d56c43ef ("netfs: Fix folio_queue ENOMEM in writeback by adding a mempool") added a mempool for the folio_queues and made the request, subrequest and folio_queue allocations distinguish between writeback and everything else. Writeback is part of memory reclaim and must not fail due to ENOMEM, so it allocates under GFP_NOFS through mempool_alloc(), which may dip into the pool's reserve and, if that runs empty, wait for elements to be returned. The GFP_KERNEL paths, which can return -ENOMEM to their callers, invoke the pool's ->alloc() callback directly instead. The direct call, however, skips the alloc_hooks() wrapper that the mempool_alloc() macro provides. The pool callbacks, mempool_alloc_slab() and mempool_kmalloc(), call kmem_cache_alloc_noprof() and kmalloc_noprof() and rely on current->alloc_tag having been set by the caller. With CONFIG_MEM_ALLOC_PROFILING_DEBUG=y this leads to current->alloc_tag not set WARNING: ./include/linux/alloc_tag.h:161 at __alloc_tagging_slab_alloc_hook alloc_tag was not set WARNING: ./include/linux/alloc_tag.h:166 at __alloc_tagging_slab_free_hook at allocation and free time respectively, as reported when reading files on a CIFS mount. The allocations are also missing from /proc/allocinfo. Wrap the direct ->alloc() invocations in alloc_hooks() with a new mempool_alloc_noreserve() helper in include/linux/mempool.h, next to the other alloc_hooks()-wrapped macros such as mempool_alloc(). The GFP_KERNEL paths keep their failable allocation semantics, they just get tagged now. Fixes: 1d78d56c43ef ("netfs: Fix folio_queue ENOMEM in writeback by adding a mempool") Reported-by: Erhard Furtner Closes: https://lore.kernel.org/all/0b004319-9ef7-437c-a4dd-174d6a9a83db@mailbox.org/ Tested-by: Erhard Furtner Suggested-by: Suren Baghdasaryan Cc: stable@vger.kernel.org Signed-off-by: Hao Ge Link: https://patch.msgid.link/20260923063759.34667-1-hao.ge@linux.dev Acked-by: Vlastimil Babka (SUSE) Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/objects.c | 4 ++-- fs/netfs/rolling_buffer.c | 2 +- include/linux/mempool.h | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index 7f6a3e912602..ad549daa9c79 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -34,7 +34,7 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, rreq = mempool_alloc(mempool, gfp); } else { - rreq = mempool->alloc(gfp, mempool->pool_data); + rreq = mempool_alloc_noreserve(mempool, gfp); if (!rreq) return ERR_PTR(-ENOMEM); } @@ -214,7 +214,7 @@ struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq struct kmem_cache *cache = mempool->pool_data; if (rreq->gfp == GFP_KERNEL) - subreq = mempool->alloc(rreq->gfp, mempool->pool_data); + subreq = mempool_alloc_noreserve(mempool, rreq->gfp); else subreq = mempool_alloc(mempool, rreq->gfp); if (!subreq) diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c index 424e77a9a109..d30d5ef6d86e 100644 --- a/fs/netfs/rolling_buffer.c +++ b/fs/netfs/rolling_buffer.c @@ -29,7 +29,7 @@ struct folio_queue *netfs_folioq_alloc(unsigned int rreq_id, gfp_t gfp, struct folio_queue *fq; if (gfp == GFP_KERNEL) - fq = netfs_folioq_pool.alloc(gfp, netfs_folioq_pool.pool_data); + fq = mempool_alloc_noreserve(&netfs_folioq_pool, gfp); else fq = mempool_alloc(&netfs_folioq_pool, gfp); if (fq) { diff --git a/include/linux/mempool.h b/include/linux/mempool.h index a0fa6d43e0dc..6da502aef2f7 100644 --- a/include/linux/mempool.h +++ b/include/linux/mempool.h @@ -70,6 +70,13 @@ int mempool_alloc_bulk_noprof(struct mempool *pool, void **elem, #define mempool_alloc_bulk(...) \ alloc_hooks(mempool_alloc_bulk_noprof(__VA_ARGS__)) +/* + * Allocate a new element without dipping into the pool's reserves or + * waiting. Returns NULL on failure. + */ +#define mempool_alloc_noreserve(_pool, _gfp) \ + alloc_hooks((_pool)->alloc(_gfp, (_pool)->pool_data)) + void *mempool_alloc_preallocated(struct mempool *pool) __malloc; void mempool_free(void *element, struct mempool *pool); unsigned int mempool_free_bulk(struct mempool *pool, void **elem, From 9814077275eca36ebf8d510d2f076d235ff9f51a Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 22 Sep 2026 20:13:48 -0700 Subject: [PATCH 2359/2366] ipe: fix use-after-free when auditing a newly loaded policy new_policy() audits the policy after ipe_new_policyfs_node() publishes it and drops the new directory's inode lock. A concurrent delete can free the policy while ipe_audit_policy_load() is still using it. Audit the successful load under that lock. Fixes: f44554b5067b ("audit,ipe: add IPE auditing support") Cc: stable@vger.kernel.org Assisted-by: LLM [FW: remove model name according to latest guideline] Signed-off-by: Fan Wu --- security/ipe/fs.c | 8 +++----- security/ipe/policy_fs.c | 3 +++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/security/ipe/fs.c b/security/ipe/fs.c index 076c111c85c8..847a76afb93d 100644 --- a/security/ipe/fs.c +++ b/security/ipe/fs.c @@ -159,18 +159,16 @@ static ssize_t new_policy(struct file *f, const char __user *data, } rc = ipe_new_policyfs_node(p); - if (rc) - goto out; out: kfree(copy); if (rc < 0) { ipe_free_policy(p); ipe_audit_policy_load(ERR_PTR(rc)); - } else { - ipe_audit_policy_load(p); + return rc; } - return (rc < 0) ? rc : len; + + return len; } static const struct file_operations np_fops = { diff --git a/security/ipe/policy_fs.c b/security/ipe/policy_fs.c index 9d92d8a14b13..a7aeb57483c6 100644 --- a/security/ipe/policy_fs.c +++ b/security/ipe/policy_fs.c @@ -481,6 +481,9 @@ int ipe_new_policyfs_node(struct ipe_policy *p) inode_lock(root); p->policyfs = policyfs; root->i_private = p; + /* Only audit signed policies from userspace */ + if (p->pkcs7) + ipe_audit_policy_load(p); inode_unlock(root); return 0; From 2776e9c28513a1c855a94792b292cbcc533418c8 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 22 Sep 2026 20:13:49 -0700 Subject: [PATCH 2360/2366] ipe: protect the dm-verity root hash with RCU ipe_bdev_setintegrity() frees the old root hash when dm-verity publishes a new one on ->preresume, while policy evaluation can still be dereferencing it. Protect the root hash with RCU. The evaluation path already runs under rcu_read_lock(). Fixes: e155858dd995 ("ipe: add support for dm-verity as a trust provider") Cc: stable@vger.kernel.org Assisted-by: LLM [FW: remove model name according to latest guideline] Signed-off-by: Fan Wu --- security/ipe/eval.c | 12 ++++++++---- security/ipe/eval.h | 2 +- security/ipe/hooks.c | 22 +++++++++++++++++----- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/security/ipe/eval.c b/security/ipe/eval.c index 21439c5be336..4a7be96c84f0 100644 --- a/security/ipe/eval.c +++ b/security/ipe/eval.c @@ -134,10 +134,14 @@ static bool evaluate_boot_verified(const struct ipe_eval_ctx *const ctx) static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx, struct ipe_prop *p) { - return !!ctx->ipe_bdev && - !!ctx->ipe_bdev->root_hash && - ipe_digest_eval(p->value, - ctx->ipe_bdev->root_hash); + const struct digest_info *root_hash; + + if (!ctx->ipe_bdev) + return false; + + root_hash = rcu_dereference(ctx->ipe_bdev->root_hash); + + return root_hash && ipe_digest_eval(p->value, root_hash); } #else static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx, diff --git a/security/ipe/eval.h b/security/ipe/eval.h index fef65a36468c..b26d1147d070 100644 --- a/security/ipe/eval.h +++ b/security/ipe/eval.h @@ -27,7 +27,7 @@ struct ipe_bdev { #ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE bool dm_verity_signed; #endif /* CONFIG_IPE_PROP_DM_VERITY_SIGNATURE */ - struct digest_info *root_hash; + struct digest_info __rcu *root_hash; }; #endif /* CONFIG_IPE_PROP_DM_VERITY */ diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c index 0ae54a880405..d878b52ceec8 100644 --- a/security/ipe/hooks.c +++ b/security/ipe/hooks.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "ipe.h" #include "hooks.h" @@ -232,7 +233,20 @@ void ipe_bdev_free_security(struct block_device *bdev) { struct ipe_bdev *blob = ipe_bdev(bdev); - ipe_digest_free(blob->root_hash); + ipe_digest_free(rcu_access_pointer(blob->root_hash)); +} + +static void ipe_set_dmverity_roothash(struct ipe_bdev *blob, + struct digest_info *info) +{ + struct digest_info *old; + + /* Protected by device-mapper's md->suspend_lock */ + old = rcu_replace_pointer(blob->root_hash, info, true); + if (old) { + synchronize_rcu(); + ipe_digest_free(old); + } } #ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE @@ -280,8 +294,7 @@ int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type typ return -EINVAL; if (!value) { - ipe_digest_free(blob->root_hash); - blob->root_hash = NULL; + ipe_set_dmverity_roothash(blob, NULL); return 0; } @@ -301,8 +314,7 @@ int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type typ info->digest_len = digest->digest_len; - ipe_digest_free(blob->root_hash); - blob->root_hash = info; + ipe_set_dmverity_roothash(blob, info); return 0; err: From 12c1f6e03f944e399bd2c88441dca5dc702b95a5 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Wed, 23 Sep 2026 09:37:20 -0700 Subject: [PATCH 2361/2366] KVM: SEV: Free have_run_cpus during VM destruction even if VM is no longer SEV Unconditionally free SEV's "have run CPUs" cpumask in the VM destroy path, i.e. even for what appear to be non-SEV VMs, as an SEV VM becomes a non-SEV VM if its state is intra-host migrated. Alternatively, the mask could be freed in sev_migrate_from() when "converting" the source VM, but that gets annoying because ideally KVM would nullify the mask to guard against UAF, and nullifying the mask would need be conditioned on CPUMASK_OFFSTACK=y. Freeing the mask during sev_migrate_from() is also not robust against other KVM bugs, though that's kind of a moot point since any such bugs would show up even if sev->active is never set. I.e. KVM must get that side of things correct. But, that's not a great reason to add more code just to make things marginally less robust. Fixes: 6f38f8c57464 ("KVM: SVM: Flush cache only on CPUs running SEV guest") Cc: stable@vger.kernel.org Reported-by: Stefan Teodorescu Signed-off-by: Sean Christopherson Message-ID: <20260923163721.1584779-2-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/sev.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 5705723f1f41..cdc1c04f60da 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -2980,13 +2980,17 @@ void sev_vm_destroy(struct kvm *kvm) struct list_head *head = &sev->regions_list; struct list_head *pos, *q; + /* + * Free the mask even if the VM is not *currently* an SEV VM, as it may + * have been an SEV VM prior to intra-host migration. + */ + free_cpumask_var(sev->have_run_cpus); + if (!sev_guest(kvm)) return; WARN_ON(!list_empty(&sev->mirror_vms)); - free_cpumask_var(sev->have_run_cpus); - /* * If this is a mirror VM, remove it from the owner's list of a mirrors * and skip ASID cleanup (the ASID is tied to the lifetime of the owner). From 93de2a6a4b91b72607136dd656edf03fb399d27f Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Wed, 23 Sep 2026 09:37:21 -0700 Subject: [PATCH 2362/2366] KVM: SEV: Do cache maintenance on the source VM during intra-host migration Manually perform cache maintenance on the source VM during intra-host migration to ensure no stale data is left in CPU caches after the VM is destroyed. Because the source VM is "converted" to a non-SEV VM, KVM's memory reclaim flows won't trigger cache maintenance, e.g. when all guest memory is reclaimed in response to detaching from the mmu_notifier. Note, relying on the destination VM to do cache maintenance isn't an option as KVM doesn't require identical guest memory configurations, i.e. the source VM may have access to memory that the destination VM does not. Enforcing equivalent memory configurations is infeasible, as it would require a *deep* comparison of memslots, e.g. to verify that not only are the memslot identical, but what the memslots point at is also identical. Fixes: b56639318bb2 ("KVM: SEV: Add support for SEV intra host migration") Cc: stable@vger.kernel.org Reported-by: Stefan Teodorescu Signed-off-by: Sean Christopherson Message-ID: <20260923163721.1584779-3-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/sev.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index cdc1c04f60da..63eb2155a774 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -2048,6 +2048,12 @@ static void sev_migrate_from(struct kvm *dst_kvm, struct kvm *src_kvm) src->pages_locked = 0; src->es_active = false; + /* + * Do cache maintenance on the source VM as it is no longer an SEV VM, + * i.e. memory reclaim flows won't trigger cache maintenance on the VM. + */ + sev_writeback_caches(src_kvm); + list_cut_before(&dst->regions_list, &src->regions_list, &src->regions_list); mutex_lock(&sev_mirror_lock); @@ -2187,6 +2193,10 @@ int sev_vm_move_enc_context_from(struct kvm *kvm, unsigned int source_fd) * the set of CPUs from the source. If a CPU was used to run a vCPU in * the source VM but is never used for the destination VM, then the CPU * can only have cached memory that was accessible to the source VM. + * Furthermore, KVM *must* perform cache maintenance on the source VM, + * as the source VM may have access to memory that the destination VM + * does not, i.e. KVM could skip flushes if memory is reclaimed from + * the old VM but not the new VM. */ if (!zalloc_cpumask_var(&dst_sev->have_run_cpus, GFP_KERNEL_ACCOUNT)) { ret = -ENOMEM; From 31c88350b7dd1522792f726f79607f31bb55c50f Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Thu, 24 Sep 2026 04:27:27 +0000 Subject: [PATCH 2363/2366] cgroup/cpuset: Return PERR_NOCPUS in remote_partition_enable() on subpartitions_cpus conflict When a remote partition is created underneath an existing local partition via a non-partition (PRS_MEMBER) intermediate cgroup, update_prstate() sees parent->partition_root_state == PRS_MEMBER and calls remote_partition_enable(). Commit 86888c7bd117 ("cgroup/cpuset: Add warnings to catch inconsistency in exclusive CPUs") replaced the cpumask_intersects(tmp->new_cpus, subpartitions_cpus) error check in remote_partition_enable() with WARN_ON_ONCE(). As a result, remote_partition_enable() emits a warning and proceeds to enable the remote partition on CPUs that are already owned by the ancestor local partition in subpartitions_cpus. This can be reproduced on Linux 7.3.0-rc3 with: mkdir -p /tmp/cg1 mount -t cgroup2 none /tmp/cg1 echo "+cpuset" > /tmp/cg1/cgroup.subtree_control mkdir /tmp/cg1/A echo 1 > /tmp/cg1/A/cpuset.cpus echo 1 > /tmp/cg1/A/cpuset.cpus.exclusive echo root > /tmp/cg1/A/cpuset.cpus.partition echo "+cpuset" > /tmp/cg1/A/cgroup.subtree_control mkdir /tmp/cg1/A/B echo 1 > /tmp/cg1/A/B/cpuset.cpus echo 1 > /tmp/cg1/A/B/cpuset.cpus.exclusive echo "+cpuset" > /tmp/cg1/A/B/cgroup.subtree_control mkdir /tmp/cg1/A/B/D echo 1 > /tmp/cg1/A/B/D/cpuset.cpus echo 1 > /tmp/cg1/A/B/D/cpuset.cpus.exclusive echo root > /tmp/cg1/A/B/D/cpuset.cpus.partition which triggers: WARNING: kernel/cgroup/cpuset.c:1594 at remote_partition_enable+0x1c1/0x300 and leaves both /tmp/cg1/A and /tmp/cg1/A/B/D as active root partitions claiming exclusive CPU 1. Fix this by returning PERR_NOCPUS when tmp->new_cpus intersects subpartitions_cpus in remote_partition_enable(), matching the error code used by remote_cpus_update() for the same subpartitions_cpus conflict, and add a regression test case to tools/testing/selftests/cgroup/test_cpuset_prs.sh. Tested in QEMU on Linux 7.3.0-rc3 using the reproducer above and tools/testing/selftests/cgroup/test_cpuset_prs.sh. Fixes: 86888c7bd117 ("cgroup/cpuset: Add warnings to catch inconsistency in exclusive CPUs") Suggested-by: Guopeng Zhang Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Hui Peng Reviewed-by: Waiman Long Signed-off-by: Tejun Heo --- kernel/cgroup/cpuset.c | 3 ++- tools/testing/selftests/cgroup/test_cpuset_prs.sh | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 8f24171b6055..1fcec89a28b9 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -1591,10 +1591,11 @@ static int remote_partition_enable(struct cpuset *cs, int new_prs, * above it or remote partition root underneath it is not allowed. */ compute_excpus(cs, tmp->new_cpus); - WARN_ON_ONCE(cpumask_intersects(tmp->new_cpus, subpartitions_cpus)); if (!cpumask_intersects(tmp->new_cpus, cpu_active_mask) || cpumask_subset(top_cpuset.effective_cpus, tmp->new_cpus)) return PERR_INVCPUS; + if (cpumask_intersects(tmp->new_cpus, subpartitions_cpus)) + return PERR_NOCPUS; if (((new_prs == PRS_ISOLATED) && !isolated_cpus_can_update(tmp->new_cpus, NULL)) || prstate_housekeeping_conflict(new_prs, tmp->new_cpus)) diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh index 131d8b4551ef..7efd5e645767 100755 --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh @@ -298,6 +298,8 @@ TEST_MATRIX=( " C0-4:X2-4 C1-4:X2-4:P2 C2-4:X4:P1 \ . . . X1 . 0 A1:0-1|A2:2-4|A3:2-4 \ A1:P0|A2:P2|A3:P-1 2-4" + " CX1-3:P1 CX1-3 CX1-3 . . . P1 . 0 A1:1-3|A2:1-3|A3:1-3 \ + A1:P1|A2:P0|A3:P-1" # Remote partition offline tests " C0-3 C1-3 C2-3 . X2-3 X2-3 X2-3:P2:O2=0 . 0 A1:0-1|A2:1|A3:3 A1:P0|A3:P2 2-3" From 94480606a677deb68d5622cf0ded88626514b3f2 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Sat, 26 Sep 2026 09:23:02 -1000 Subject: [PATCH 2364/2366] sched_ext: Add a size argument to scx_bpf_cid_topo() so struct scx_cid_topo can grow scx_bpf_cid_topo() copies struct scx_cid_topo into a buffer the BPF program sized from its own vmlinux.h while the verifier sizes the write from the running kernel's BTF. The struct may grow and each growth then breaks every scheduler built against the older layout, rejected at load or written past its buffer. This is the usual hole for a struct handed to BPF, closed elsewhere with a size argument, and it was missed here. Take the buffer size, copy the smaller of it and the kernel's struct and set the rest to -1. Accesses to the copy are CO-RE relocated, so the struct can grow by appending fields, which its comment now states. The kfunc changes in place: the cid interface is still being finalized and no released scheduler uses the current form. Fixes: e9b55af47edf ("sched_ext: Add topological CPU IDs (cids)") Cc: stable@vger.kernel.org # v7.2+ Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/cid.c | 26 +++++++++++++++--------- kernel/sched/ext/types.h | 4 ++++ tools/sched_ext/include/scx/common.bpf.h | 2 +- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/kernel/sched/ext/cid.c b/kernel/sched/ext/cid.c index 39f88deb94bc..d4d5f537832a 100644 --- a/kernel/sched/ext/cid.c +++ b/kernel/sched/ext/cid.c @@ -912,30 +912,36 @@ bool scx_cmask_empty(const struct scx_cmask *m) /** * scx_bpf_cid_topo - Copy out per-cid topology info * @cid: cid to look up - * @out__uninit: where to copy the topology info; fully written by this call + * @out: where to copy the topology info + * @out__sz: size of @out, the program's sizeof(struct scx_cid_topo) * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * - * Fill @out__uninit with the topology info for @cid. Trigger scx_error() if - * @cid is out of range. If @cid is valid but in the no-topo section, all fields - * are set to -1. All fields are also set to -1 when no cid tables have been - * published yet, which a program may observe while racing the root enable. + * Fill @out with the topology info for @cid. Trigger scx_error() if @cid is out + * of range. If @cid is valid but in the no-topo section, all fields are set to + * -1. All fields are also set to -1 when no cid tables have been published yet, + * which a program may observe while racing the root enable. + * + * The program's struct may be older or newer than the kernel's. The smaller of + * @out__sz and the kernel's size is copied and the rest of @out is set to -1. */ -__bpf_kfunc void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out__uninit, +__bpf_kfunc void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out, size_t out__sz, const struct bpf_prog_aux *aux) { + size_t len = min(out__sz, sizeof(*out)); struct scx_cid_topo *topo; struct scx_sched *sch; + /* the error cases and fields the kernel lacks read as -1 */ + memset(out, 0xff, out__sz); + guard(rcu)(); sch = scx_prog_sched(aux); topo = rcu_dereference(scx_cid_topo); - if (unlikely(!sch) || !cid_valid(sch, cid) || unlikely(!topo)) { - *out__uninit = SCX_CID_TOPO_NEG; + if (unlikely(!sch) || !cid_valid(sch, cid) || unlikely(!topo)) return; - } - *out__uninit = topo[cid]; + memcpy(out, &topo[cid], len); } __bpf_kfunc_end_defs(); diff --git a/kernel/sched/ext/types.h b/kernel/sched/ext/types.h index 943d8d429a2c..139176cf9fc6 100644 --- a/kernel/sched/ext/types.h +++ b/kernel/sched/ext/types.h @@ -70,6 +70,10 @@ enum scx_consts { * smaller shards if the LLC exceeds the target size. No-topo cids are packed * into their own max-sized shards. * + * New fields are appended, never inserted: scx_bpf_cid_topo() copies this + * struct out sized by the program's own layout, and an older program's copy + * must stay a prefix of the kernel's. + * * @core_cid: first cid of this cid's core (smt-sibling group) * @core_idx: global index of that core, in [0, nr_cores_at_init) * @llc_cid: first cid of this cid's LLC diff --git a/tools/sched_ext/include/scx/common.bpf.h b/tools/sched_ext/include/scx/common.bpf.h index 2ddb01a059fd..22f24ebef8a9 100644 --- a/tools/sched_ext/include/scx/common.bpf.h +++ b/tools/sched_ext/include/scx/common.bpf.h @@ -106,7 +106,7 @@ u64 scx_bpf_now(void) __ksym __weak; void scx_bpf_events(struct scx_event_stats *events, size_t events__sz) __ksym __weak; s32 scx_bpf_cpu_to_cid(s32 cpu) __ksym __weak; s32 scx_bpf_cid_to_cpu(s32 cid) __ksym __weak; -void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out) __ksym __weak; +void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out, size_t out__sz) __ksym __weak; void scx_bpf_kick_cid(s32 cid, u64 flags) __ksym __weak; s32 scx_bpf_task_cid(const struct task_struct *p) __ksym __weak; s32 scx_bpf_this_cid(void) __ksym __weak; From db6365ced4d5855e321f772b240c0e473bcfcdd5 Mon Sep 17 00:00:00 2001 From: Pavankumar Kondeti Date: Fri, 25 Sep 2026 15:25:12 +0530 Subject: [PATCH 2365/2366] workqueue: Fix NULL current_pwq deref in flush dependency check check_flush_dependency() uses current_wq_worker() to determine whether the caller is a workqueue worker and then dereferences worker->current_pwq to test whether the current workqueue is WQ_MEM_RECLAIM. current_wq_worker() only means that %current has PF_WQ_WORKER set. A kworker can reach check_flush_dependency() while it is not executing a work item. One such path is worker_thread() acting as the pool manager, where create_worker() does GFP_KERNEL allocation and the allocation path invokes the OOM notifier. In that state worker->current_pwq is NULL because current_pwq is set only by process_one_work() and cleared again after the work function returns. [ 416.760634][ T375] Call trace: [ 416.760638][ T375] check_flush_dependency+0x80/0x120 (P) [ 416.760648][ T375] __flush_work+0x98/0x224 [ 416.760657][ T375] flush_work+0x30/0x44 [ 416.760665][ T375] ... [ 416.760710][ T375] blocking_notifier_call_chain+0x58/0xa0 [ 416.760719][ T375] out_of_memory+0xb4/0x458 [ 416.760730][ T375] __alloc_pages_may_oom+0x11c/0x1a8 [ 416.760739][ T375] __alloc_pages_slowpath+0x314/0x46c [ 416.760746][ T375] __alloc_frozen_pages_noprof+0x110/0x1a4 [ 416.760753][ T375] new_slab+0x12c/0x484 [ 416.760759][ T375] ___slab_alloc+0x7a8/0xc7c [ 416.760765][ T375] __slab_alloc+0x74/0xd8 [ 416.760772][ T375] __kmalloc_cache_node_noprof+0x2ac/0x304 [ 416.760779][ T375] alloc_worker+0x28/0x60 [ 416.760785][ T375] create_worker+0x4c/0x20c [ 416.760790][ T375] worker_thread+0xe8/0x2b8 [ 416.760796][ T375] kthread+0x1a8/0x200 [ 416.760805][ T375] ret_from_fork+0x10/0x20 Guard the WQ_MEM_RECLAIM-worker warning with worker->current_pwq. If the kworker is not currently executing a work item, there is no current workqueue to diagnose with that warning. The PF_MEMALLOC warning is left unchanged so explicit reclaim context flushing a !WQ_MEM_RECLAIM target is still reported. Fixes: fca839c00a12 ("workqueue: warn if memory reclaim tries to flush !WQ_MEM_RECLAIM workqueue") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Pavankumar Kondeti Signed-off-by: Tejun Heo --- kernel/workqueue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index b8bec1689b7a..4a73d305d88a 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -3918,7 +3918,7 @@ static void check_flush_dependency(struct workqueue_struct *target_wq, WARN_ONCE(current->flags & PF_MEMALLOC, "workqueue: PF_MEMALLOC task %d(%s) is flushing !WQ_MEM_RECLAIM %s:%ps", current->pid, current->comm, target_wq->name, target_func); - WARN_ONCE(worker && ((worker->current_pwq->wq->flags & + WARN_ONCE(worker && worker->current_pwq && ((worker->current_pwq->wq->flags & (WQ_MEM_RECLAIM | __WQ_LEGACY)) == WQ_MEM_RECLAIM), "workqueue: WQ_MEM_RECLAIM %s:%ps is flushing !WQ_MEM_RECLAIM %s:%ps", worker->current_pwq->wq->name, worker->current_func, From 72d3fcf802c45d00b300f25b848a93c3a2bd7c7e Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 27 Sep 2026 13:55:01 -0700 Subject: [PATCH 2366/2366] Linux 7.3-rc5 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 751a08643bf8..6b8b812f23b9 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc4 +EXTRAVERSION = -rc5 NAME = Baby Opossum Posse # *DOCUMENTATION*