From 749c5102487b558cc7d73961a6d0da2fceaa022d Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Fri, 12 Jun 2026 08:35:50 -0700 Subject: [PATCH 001/562] 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 002/562] 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 003/562] 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 004/562] 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 005/562] 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 006/562] 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 007/562] 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 008/562] 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 009/562] 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 010/562] 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 011/562] 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 012/562] 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 013/562] 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 014/562] 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 015/562] 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 016/562] 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 017/562] 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 018/562] 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 019/562] 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 020/562] 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 021/562] 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 022/562] 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 023/562] 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 024/562] 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 5f01293930d18f8473681b378bb483b7087fc0dd Mon Sep 17 00:00:00 2001 From: Changwoo Min Date: Wed, 19 Aug 2026 01:04:29 +0900 Subject: [PATCH 025/562] 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 026/562] 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 027/562] 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 028/562] 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 029/562] 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 030/562] 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 031/562] 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 032/562] 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 033/562] 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 4fb8d6379d2c7ceecb2b3e111954d29089d59492 Mon Sep 17 00:00:00 2001 From: Liang Luo Date: Wed, 19 Aug 2026 11:12:44 +0800 Subject: [PATCH 034/562] 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 035/562] 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 036/562] 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 037/562] 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 038/562] 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 039/562] 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 040/562] 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 041/562] 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 042/562] 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 043/562] 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 044/562] 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 41a28c865d1d5843f8cb9e0af17a8f4d9e2961ff Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Fri, 31 Jul 2026 11:10:10 +0800 Subject: [PATCH 045/562] 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 046/562] 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 047/562] 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 048/562] 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 049/562] 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 050/562] 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 051/562] 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 052/562] 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 053/562] 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 054/562] 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 055/562] 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 056/562] 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 057/562] 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 058/562] 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 059/562] 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 060/562] 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 445fcd33c501be4be41806715f5b7ec80200f9f7 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Fri, 21 Aug 2026 11:29:51 +0200 Subject: [PATCH 061/562] 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 062/562] 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 063/562] 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 064/562] 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 065/562] 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 066/562] 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 cca061dccf563907061766191b2ce3f66b7c285a Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Fri, 21 Aug 2026 09:05:52 -1000 Subject: [PATCH 067/562] 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 909a3f0e9d8b0d8001cf6e99808f88eca8e44328 Mon Sep 17 00:00:00 2001 From: Cheng Lingfei Date: Mon, 24 Aug 2026 20:45:46 +0800 Subject: [PATCH 068/562] 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 069/562] 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 070/562] 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 071/562] 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 072/562] 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 073/562] 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 1c942462c3969b287a86ab6dbb143324289d564e Mon Sep 17 00:00:00 2001 From: Vadim Klishko Date: Mon, 27 Jul 2026 22:17:18 -0600 Subject: [PATCH 074/562] 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 075/562] 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 a8e04f3f894ccb52cfcd7e60125a9f35da4a616d Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Mon, 13 Jul 2026 11:34:14 +0200 Subject: [PATCH 076/562] 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 3d3de2aee17d1431694aa085039479b5679e5ad4 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Fri, 21 Aug 2026 13:32:30 +0800 Subject: [PATCH 077/562] 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 078/562] 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 079/562] 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 080/562] 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 081/562] 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 082/562] 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 083/562] 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 084/562] 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 085/562] 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 086/562] 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 087/562] 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 088/562] 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 089/562] 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 090/562] 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 53676a5e28231186c9f56d87b7998b640699bc15 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Sun, 23 Aug 2026 18:26:46 -0500 Subject: [PATCH 091/562] 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 d3ef6c097ba078e1f8c7239d76a0ce8b61e75095 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Wed, 26 Aug 2026 11:18:44 -0700 Subject: [PATCH 092/562] 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 093/562] 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 094/562] 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 095/562] 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 096/562] 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 097/562] 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 2f3536bff8823d3c5fdbbe15e17bfca696cc2b2e Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Thu, 27 Aug 2026 15:48:23 -0700 Subject: [PATCH 098/562] 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 099/562] 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 100/562] 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 101/562] 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 102/562] 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 103/562] 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 104/562] 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 105/562] 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 106/562] 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 107/562] 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 108/562] 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 109/562] 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 110/562] 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 111/562] 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 112/562] 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 113/562] 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 114/562] 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 115/562] 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 116/562] 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 117/562] 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 118/562] 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 119/562] 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 120/562] 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 121/562] 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 122/562] 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 123/562] 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 124/562] 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 125/562] 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 126/562] 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 127/562] 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 128/562] 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 129/562] 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 130/562] 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 131/562] 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 132/562] 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 133/562] 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 134/562] 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 135/562] 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 136/562] 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 137/562] 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 138/562] 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 139/562] 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 140/562] 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 141/562] 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 142/562] 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 143/562] 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 144/562] 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 145/562] 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 146/562] 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 147/562] 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 148/562] 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 149/562] 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 077304dfa7d1c641fbc10310c831ba8505887bd2 Mon Sep 17 00:00:00 2001 From: Will Porter Date: Thu, 27 Aug 2026 18:21:38 -0500 Subject: [PATCH 150/562] 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 151/562] 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 152/562] 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 153/562] 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 154/562] 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 155/562] 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 7b9b202f154488d06d8de89bfb17f2b68b1ed4cc Mon Sep 17 00:00:00 2001 From: feng liu Date: Fri, 28 Aug 2026 14:34:56 +0800 Subject: [PATCH 156/562] 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 157/562] 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 158/562] 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 159/562] 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 160/562] 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 161/562] 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 162/562] 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 1376afc7660bad2a1a5ee0876898312a486cf8bd Mon Sep 17 00:00:00 2001 From: Kiran Kumar K Date: Tue, 25 Aug 2026 10:47:25 +0530 Subject: [PATCH 163/562] 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 164/562] 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 165/562] 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 166/562] 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 167/562] 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 168/562] 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 169/562] 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 170/562] 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 171/562] 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 172/562] 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 173/562] 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 174/562] 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 175/562] 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 176/562] 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 399aa12450a61a5c73dc77e73f069ece9687c95d Mon Sep 17 00:00:00 2001 From: Aleksandr Khromov Date: Mon, 24 Aug 2026 13:22:46 +0300 Subject: [PATCH 177/562] 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 178/562] 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 179/562] 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 180/562] 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 181/562] 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 182/562] 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 183/562] 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 184/562] 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 185/562] 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 186/562] 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 187/562] 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 188/562] 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 189/562] 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 190/562] 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 191/562] 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 192/562] 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 ca1f4a5ecab084af7f405baa902edbed171b57e6 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 13 Aug 2026 15:25:05 +0200 Subject: [PATCH 193/562] 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 194/562] 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 195/562] 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 196/562] 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 197/562] 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 198/562] 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 199/562] 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 200/562] 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 201/562] 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 202/562] 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 203/562] 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 204/562] 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 205/562] 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 8e359920216689b3b79e0fe8961a77fe312a511f Mon Sep 17 00:00:00 2001 From: Etienne Perot Date: Fri, 28 Aug 2026 21:52:51 +0000 Subject: [PATCH 206/562] 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 207/562] 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 cd3b9cea675bbfebc223f007dc2f4e79524fa54c Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Thu, 27 Aug 2026 21:16:17 +0000 Subject: [PATCH 208/562] 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 23761359861ca4bb087540937dfea8b0716914c2 Mon Sep 17 00:00:00 2001 From: Wanwu Li Date: Thu, 27 Aug 2026 16:07:36 +0800 Subject: [PATCH 209/562] 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 210/562] 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 211/562] 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 212/562] 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 213/562] 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 214/562] 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 215/562] 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 216/562] 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 217/562] 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 218/562] 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 219/562] 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 220/562] 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 221/562] 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 222/562] 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 223/562] 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 224/562] 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 93e257938aa67a6c957217db94091d9d9e5d403f Mon Sep 17 00:00:00 2001 From: Aaron Tomlin Date: Mon, 31 Aug 2026 14:15:53 -0400 Subject: [PATCH 225/562] 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 fa5acd038ea657ad5033713d6916214cbd349151 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 28 Aug 2026 14:07:47 -0500 Subject: [PATCH 226/562] 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 227/562] 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 228/562] 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 229/562] 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 230/562] 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 231/562] 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 232/562] 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 dc0df5a0c62ccea1d0e08d39a4dc9064de81d411 Mon Sep 17 00:00:00 2001 From: Florian Schauer Date: Fri, 28 Aug 2026 08:08:22 +0200 Subject: [PATCH 233/562] 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 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 234/562] 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 235/562] 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 236/562] 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 2987ee196c88dbde0463dc87d5fb209c684e34a2 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 27 Aug 2026 16:06:56 +0000 Subject: [PATCH 237/562] 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 238/562] 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 239/562] 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 738ef4cd82818b13f492fd5ddec7e00f177ffe60 Mon Sep 17 00:00:00 2001 From: Andi Kleen Date: Mon, 31 Aug 2026 08:04:37 -0700 Subject: [PATCH 240/562] 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 93b49239840b91313adbd77b8b52993eff2d08c1 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 28 Aug 2026 08:45:27 +0000 Subject: [PATCH 241/562] 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 242/562] 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 243/562] 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 244/562] 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 245/562] 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 246/562] 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 cd51b74bdd0b75aedf255dc67306a16dd057f7ee Mon Sep 17 00:00:00 2001 From: Ido Schimmel Date: Fri, 28 Aug 2026 22:23:41 +0300 Subject: [PATCH 247/562] 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 248/562] 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 249/562] 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 250/562] 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 251/562] 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 252/562] 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 253/562] 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 254/562] 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 255/562] 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 73e594c19b4f815d8343461cec7074c4713bbde7 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sun, 30 Aug 2026 18:09:12 +0000 Subject: [PATCH 256/562] 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 c037915f80c4db47f7d061d68e703ffd551b1a34 Mon Sep 17 00:00:00 2001 From: Kaiwen Shi Date: Sun, 30 Aug 2026 07:05:51 +0800 Subject: [PATCH 257/562] 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 f576944a59f31bcffff121117ebf452c5dd162b7 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Fri, 7 Aug 2026 23:09:55 +0800 Subject: [PATCH 258/562] 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 259/562] 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 260/562] 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 261/562] 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 bc93419130bb70fabf6561e197054caae85c160c Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 31 Aug 2026 08:10:27 +0000 Subject: [PATCH 262/562] 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 263/562] 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 264/562] 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 265/562] 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 266/562] 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 267/562] 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 268/562] 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 269/562] 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 270/562] 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 148845aa1921d95ef5dc851c76e6a284f6657df6 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Wed, 26 Aug 2026 00:25:33 +0000 Subject: [PATCH 271/562] 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 272/562] 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 273/562] 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 274/562] 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 275/562] 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 1d2929d0850fff683b8aff051275945e65f082c8 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Sat, 29 Aug 2026 18:56:18 +0200 Subject: [PATCH 276/562] 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 277/562] 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 cc7cd2a9228175c975f62ad56ed7c767701cb4fa Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Tue, 1 Sep 2026 16:30:31 +0500 Subject: [PATCH 278/562] 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 67bfe48a29fbddfff77e13d4d327e49fca2c2be5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 25 Aug 2026 11:55:11 +0200 Subject: [PATCH 279/562] 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 280/562] 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 281/562] 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 282/562] 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 283/562] 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 284/562] 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 285/562] 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 286/562] 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 287/562] 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 f0efaf1872949e96d213c8e910fd9517f7d7c406 Mon Sep 17 00:00:00 2001 From: Aleksandr Nogikh Date: Wed, 29 Jul 2026 09:04:54 +0000 Subject: [PATCH 288/562] 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 e24e3370356bddb65d667985a332b5f8aeeb5f97 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Thu, 13 Aug 2026 20:16:15 +0200 Subject: [PATCH 289/562] 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 290/562] 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 291/562] 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 292/562] 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 293/562] 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 294/562] 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 295/562] 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 296/562] 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 297/562] 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 298/562] 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 299/562] 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 300/562] 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 301/562] 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 302/562] 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 303/562] 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 304/562] 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 305/562] 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 306/562] 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 307/562] 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 308/562] 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 2b0ac85512b7f67479127b2713254490662eb13d Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Fri, 21 Aug 2026 15:57:28 +0800 Subject: [PATCH 309/562] 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 310/562] 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 8d7b3e41ffecc69388a566ecc52093d292074c2a Mon Sep 17 00:00:00 2001 From: Sophon Zhang Date: Tue, 1 Sep 2026 01:09:53 +0800 Subject: [PATCH 311/562] 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 312/562] 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 6db237eb516774a76b0d6ab8b4090185f6f2f956 Mon Sep 17 00:00:00 2001 From: Russ Weight Date: Tue, 18 Aug 2026 13:46:46 -0600 Subject: [PATCH 313/562] 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 314/562] 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 315/562] 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 9cdc7e6dc7a99ad7311ad5e7c145f2b9ce4e24b0 Mon Sep 17 00:00:00 2001 From: Shen Yongchao Date: Mon, 3 Aug 2026 22:31:57 +0800 Subject: [PATCH 316/562] 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 4a819ee5f2834330656d6ac168c4c8cf27fdeec2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 31 Aug 2026 22:07:10 +0200 Subject: [PATCH 317/562] 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 f4a771cc684c7354b6200147f7252c58d17408ff Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Fri, 28 Aug 2026 09:41:53 -0400 Subject: [PATCH 318/562] 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 319/562] 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 320/562] 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 321/562] 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 322/562] 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 323/562] 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 324/562] 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 325/562] 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 326/562] 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 f4825922d2fb371e2b969697d792077f1b62b62c Mon Sep 17 00:00:00 2001 From: Sujal Tuladhar Date: Sat, 1 Aug 2026 21:30:00 +0545 Subject: [PATCH 327/562] 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 328/562] 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 329/562] 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 330/562] 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 331/562] 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 332/562] 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 7b8a8ae4dd176a232e973017d2aa3c536a7275e2 Mon Sep 17 00:00:00 2001 From: Sourav Panda Date: Tue, 11 Aug 2026 05:29:09 +0000 Subject: [PATCH 333/562] 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 334/562] 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 335/562] 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 336/562] 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 337/562] 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 338/562] 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 339/562] 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 340/562] 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 341/562] 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 342/562] 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 343/562] 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 344/562] 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 345/562] 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 346/562] 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 347/562] 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 348/562] 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 1719d035a6fa90b7467b6daf45a573f5180013b2 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Sat, 22 Aug 2026 18:59:30 +0800 Subject: [PATCH 349/562] 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 350/562] 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 351/562] 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 352/562] 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 353/562] 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 354/562] 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 355/562] 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 356/562] 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 357/562] 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 358/562] 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 f695390ea63941a9e412bf1f3afe65ab245fc681 Mon Sep 17 00:00:00 2001 From: Sunil Goutham Date: Fri, 28 Aug 2026 14:49:45 +0530 Subject: [PATCH 359/562] 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 360/562] 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 361/562] 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 362/562] 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 363/562] 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 364/562] 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 a61c6ae1dae2611082b831b4aaa780878099c012 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 24 Aug 2026 18:47:07 +0200 Subject: [PATCH 365/562] 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 366/562] 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 367/562] 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 544d85de4dc22c01badfd8cefa59829ce35c4858 Mon Sep 17 00:00:00 2001 From: Chris Lew Date: Thu, 27 Aug 2026 17:48:46 +0530 Subject: [PATCH 368/562] 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 f4a23e17d84fd2a152d9e12369761934e1af0ee8 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Wed, 2 Sep 2026 21:50:58 +0900 Subject: [PATCH 369/562] 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 370/562] 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 371/562] 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 372/562] 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 373/562] 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 374/562] 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 375/562] 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 376/562] 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 af8c27375733fb6a6df9fa484cda77cc3dd0cb80 Mon Sep 17 00:00:00 2001 From: Thomas Lamprecht Date: Thu, 27 Aug 2026 19:24:24 +0200 Subject: [PATCH 377/562] 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 8ba27b90095a4c7fcc878dd013969cd6b100cea7 Mon Sep 17 00:00:00 2001 From: Oleksandr Kovalov Date: Wed, 2 Sep 2026 20:13:05 +0300 Subject: [PATCH 378/562] 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 379/562] 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 380/562] 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 f2951ebd15c36a1ea4820a7f0cbb0b5f1c028b73 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 2 Sep 2026 12:19:18 -0400 Subject: [PATCH 381/562] 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 33ce0aa4c57611c3a3485ec7c01ad67b3751447d Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Fri, 14 Aug 2026 12:24:05 -0400 Subject: [PATCH 382/562] 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 383/562] 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 384/562] 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 385/562] 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 386/562] 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 387/562] 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 388/562] 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 389/562] 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 390/562] 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 391/562] 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 392/562] 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 393/562] 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 394/562] 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 395/562] 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 396/562] 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 397/562] 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 398/562] 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 399/562] 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 400/562] 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 401/562] 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 402/562] 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 403/562] 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 404/562] 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 405/562] 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 406/562] 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 407/562] 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 408/562] 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 409/562] 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 410/562] 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 411/562] 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 412/562] 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 413/562] 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 414/562] 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 f36d94a20ca185bcadef3a10b980cd2cfd72d53a Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Tue, 1 Sep 2026 23:21:37 +0900 Subject: [PATCH 415/562] 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 416/562] 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 417/562] 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 418/562] 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 419/562] 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 420/562] 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 421/562] 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 422/562] 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 423/562] 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 424/562] 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 425/562] 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 426/562] 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 427/562] 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 428/562] 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 429/562] 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 430/562] 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 431/562] 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 432/562] 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 433/562] 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 7db28abbea0f7dc1ec4fdfdc149db5fbd9e4c994 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 30 Aug 2026 14:28:27 +0200 Subject: [PATCH 434/562] 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 435/562] 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 66817a9794263cd2a5dc4e99bf8e5fcc5ff7181e Mon Sep 17 00:00:00 2001 From: HW He Date: Tue, 1 Sep 2026 16:23:12 +0800 Subject: [PATCH 436/562] 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 437/562] 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 438/562] 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 439/562] 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 440/562] 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 6d81700ad7c4871f94fb72e469cb0f3f55843ef7 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Wed, 2 Sep 2026 11:08:28 +0900 Subject: [PATCH 441/562] 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 442/562] 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 8efd5f623c63584c2e284a837a7795d95a0491cb Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Thu, 3 Sep 2026 21:38:32 +0900 Subject: [PATCH 443/562] 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 444/562] 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 0895a0c0734703be5532f3883c42db95615fd98b Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Tue, 1 Sep 2026 18:47:35 +0800 Subject: [PATCH 445/562] 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 446/562] 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 447/562] 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 448/562] 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 449/562] 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 450/562] 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 451/562] 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 452/562] 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 453/562] 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 454/562] 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 455/562] 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 456/562] 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 457/562] 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 458/562] 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 459/562] 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 70ded7a57443f41075625f29b9eb88dca154decb Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 27 Aug 2026 09:20:56 +0200 Subject: [PATCH 460/562] 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 461/562] 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 462/562] 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 463/562] 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 464/562] 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 465/562] 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 466/562] =?UTF-8?q?MAINTAINERS,=20mailmap:=20update=20emai?= =?UTF-8?q?l=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 467/562] 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 468/562] 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 469/562] 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 470/562] 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 471/562] 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 472/562] 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 473/562] 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 474/562] 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 475/562] 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 476/562] 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 477/562] 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 478/562] 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 479/562] 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 480/562] 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 4814ed6406f3493bd554ad046da5f7fc04833571 Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Thu, 3 Sep 2026 10:15:39 -0700 Subject: [PATCH 481/562] 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 482/562] 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 483/562] 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 484/562] 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 485/562] 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 369f4ce734570bdfedaa4b5ca50e2a3f6a892728 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Thu, 3 Sep 2026 23:47:47 +0200 Subject: [PATCH 486/562] 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 487/562] 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 488/562] 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 489/562] 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 490/562] 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 491/562] 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 492/562] 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 493/562] 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 494/562] 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 495/562] 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 496/562] 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 497/562] 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 67b529f521a6676cdfc78b91b0217d7eaa84216b Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 01:33:18 -0700 Subject: [PATCH 498/562] 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 499/562] 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 500/562] 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 501/562] 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 502/562] 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 503/562] 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 504/562] 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 505/562] 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 63b6a48c951d63bf39d44603ada48a987ccf66eb Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Fri, 4 Sep 2026 21:44:23 +0800 Subject: [PATCH 506/562] 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 507/562] 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 508/562] 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 509/562] 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 510/562] 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 511/562] 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 512/562] 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 513/562] 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 514/562] 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 515/562] 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 516/562] 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 517/562] 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 518/562] 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 519/562] 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 e67091609cf85962f64391c1b0f93d4cbfcd4e22 Mon Sep 17 00:00:00 2001 From: caina Date: Fri, 21 Aug 2026 17:17:20 +0800 Subject: [PATCH 520/562] 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 521/562] 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 522/562] 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 523/562] 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 524/562] 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 525/562] 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 526/562] 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 527/562] 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 528/562] 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 529/562] 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 d7dbdd2ee01e12211046d4a535623ac732b749fb Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Fri, 4 Sep 2026 08:25:04 +0900 Subject: [PATCH 530/562] 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 90feea391c64fc43bf44184fcf2b243ab991ce47 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 3 Sep 2026 11:05:58 -0700 Subject: [PATCH 531/562] 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 5e8c349bc8d790fe031a4332e502f5d4f9878644 Mon Sep 17 00:00:00 2001 From: Jiayuan Chen Date: Fri, 4 Sep 2026 15:37:39 +0800 Subject: [PATCH 532/562] 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 6903878d4654bdef4e08e38cdf1ae306ce7de5f9 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Fri, 4 Sep 2026 12:09:11 +0100 Subject: [PATCH 533/562] 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 534/562] 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 535/562] 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 536/562] 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 537/562] 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 538/562] 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 539/562] 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 540/562] 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 541/562] 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 542/562] 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 543/562] 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 544/562] 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 545/562] 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 546/562] 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 1a3a10b030c96ea88868ccc060a16827c01eaa5a Mon Sep 17 00:00:00 2001 From: Eduard Zingerman Date: Fri, 4 Sep 2026 17:05:52 -0700 Subject: [PATCH 547/562] 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 548/562] 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 549/562] 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 550/562] 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 551/562] 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 552/562] 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 553/562] 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 554/562] 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 555/562] 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 556/562] 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 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 2 Sep 2026 15:31:14 -0700 Subject: [PATCH 557/562] 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 b144dc5a24149ba9a0cb2197001973a74b8c93b2 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 10 Aug 2026 09:40:00 -0700 Subject: [PATCH 558/562] 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 c3fd8e5fd100f122bad503bdc0e9277219533253 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Sat, 5 Sep 2026 03:47:33 +0200 Subject: [PATCH 559/562] 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 560/562] 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 536b523b407397c8d3967c020ce7aad70a0ea030 Mon Sep 17 00:00:00 2001 From: Chen Pei Date: Wed, 2 Sep 2026 14:14:51 +0800 Subject: [PATCH 561/562] 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 562/562] 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*