From 02a3178319639f650428164d4d003de4dc03e326 Mon Sep 17 00:00:00 2001 From: Naomi Chu Date: Wed, 9 Sep 2026 17:10:45 +0800 Subject: scsi: ufs: core: Hold a clock reference across the probe Clock gating becomes possible as soon as ufshcd_init_clk_gating() has run, and from that point on the probe keeps accessing host registers without ever taking a clock reference. This has been safe only because of the state check in __ufshcd_release(): gate_work is not queued unless hba->ufshcd_state is UFSHCD_STATE_OPERATIONAL, and the promotion to that state used to happen after the last register access of the probe, at the end of ufshcd_probe_hba(). That is fragile: it only works while the promotion happens after the register accesses. Commit a390e6677f41 ("scsi: ufs: core: Expand the ufshcd_device_init(hba, true) call") changed that ordering by moving the promotion into ufshcd_init(), which schedules ufshcd_async_scan() afterwards. ufshcd_probe_hba() therefore now runs with the state already promoted, and it accesses host registers without holding a clock reference: - On hosts with UFSHCD_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH it calls ufshcd_hba_stop() and ufshcd_hba_enable() before ufshcd_device_init() sets the state back to UFSHCD_STATE_RESET, and both read REG_CONTROLLER_ENABLE, so gated clocks stall there instead of just losing a write. - It ends with an ufshcd_configure_auto_hibern8() write, which its other callers do take a clock reference for. Take a clock reference as soon as clock gating has been initialised and keep it until the probe is over. It then does not matter who drops a clock reference while the probe is running, and the register accesses of the probe no longer depend on hba->ufshcd_state. The reference is dropped by ufshcd_async_scan() once the scan has finished, or by the new out_release label if the probe fails after it was taken. Fixes: a390e6677f41 ("scsi: ufs: core: Expand the ufshcd_device_init(hba, true) call") Signed-off-by: Naomi Chu Reviewed-by: Peter Wang Link: https://patch.msgid.link/20260909091045.1134956-1-naomi.chu@mediatek.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 54f4e7d7de02..79b6a2d9b522 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -9556,6 +9556,7 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie) ret = ufshcd_add_lus(hba); out: + ufshcd_release(hba); pm_runtime_put_sync(hba->dev); if (ret) @@ -11278,6 +11279,9 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) ufshcd_init_clk_gating(hba); + /* Released by ufshcd_async_scan(), or by out_release on failure. */ + ufshcd_hold(hba); + ufshcd_init_clk_scaling(hba); /* @@ -11298,7 +11302,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) err = devm_request_irq(dev, irq, ufshcd_intr, IRQF_SHARED, UFSHCD, hba); if (err) { dev_err(hba->dev, "request irq failed\n"); - goto out_disable; + goto out_release; } else { hba->is_irq_enabled = true; } @@ -11314,7 +11318,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) dev_err(hba->dev, "Host controller enable failed\n"); ufshcd_print_evt_hist(hba); ufshcd_print_host_state(hba); - goto out_disable; + goto out_release; } INIT_DELAYED_WORK(&hba->rpm_dev_flush_recheck_work, ufshcd_rpm_dev_flush_recheck_work); @@ -11328,7 +11332,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) err = ufshcd_add_scsi_host(hba); if (err) - goto out_disable; + goto out_release; /* Hold auto suspend until async scan completes */ pm_runtime_get_sync(dev); @@ -11348,7 +11352,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) err = ufshcd_link_startup(hba); if (err) - goto out_disable; + goto out_release; if (hba->mcq_enabled) ufshcd_config_mcq(hba); @@ -11365,23 +11369,23 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) /* Verify device initialization by sending NOP OUT UPIU */ err = ufshcd_verify_dev_init(hba); if (err) - goto out_disable; + goto out_release; /* Initiate UFS initialization, and waiting until completion */ err = ufshcd_complete_dev_init(hba); if (err) - goto out_disable; + goto out_release; err = ufshcd_device_params_init(hba); if (err) - goto out_disable; + goto out_release; err = ufshcd_post_device_init(hba); initialized: ufshcd_process_probe_result(hba, probe_start, err); if (err) - goto out_disable; + goto out_release; ufs_sysfs_add_nodes(hba->dev); hba->dme_qos_sysfs_handle = sysfs_get_dirent(hba->dev->kobj.sd, @@ -11392,6 +11396,8 @@ initialized: ufshcd_pm_qos_init(hba); return 0; +out_release: + ufshcd_release(hba); out_disable: hba->is_irq_enabled = false; ufshcd_hba_exit(hba); -- cgit v1.2.3 From 85de8b3a5eaa23d153aff4c7c83dddbc949064c9 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Thu, 10 Sep 2026 17:24:05 +0800 Subject: scsi: qedi: Initialize callback state before registration qedi_get_protocol_tlv_data() can be called asynchronously by QED after qedi_ops->register_ops(). It takes stats_lock and reads ll2_mtu, but __qedi_probe() currently registers the callback before initializing the mutex and assigning the default MTU on the normal probe path. Initialize the callback-visible state before registering qedi_cb_ops. Keep the recovery path from reinitializing state because it reuses the existing qedi context. Fixes: 3cc5746e5ad7 ("scsi: qedi: Initialize the stats mutex lock") Cc: stable@vger.kernel.org Assisted-by: LLM Codex Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260910092405.1300129-1-runyu.xiao@seu.edu.cn Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/qedi/qedi_main.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/scsi/qedi/qedi_main.c b/drivers/scsi/qedi/qedi_main.c index 227ff7bd1bdc..f394fcbb97e7 100644 --- a/drivers/scsi/qedi/qedi_main.c +++ b/drivers/scsi/qedi/qedi_main.c @@ -2652,8 +2652,6 @@ retry_probe: snprintf(host_buf, sizeof(host_buf), "host_%d", qedi->shost->host_no); qedi_ops->common->set_name(qedi->cdev, host_buf); - qedi_ops->register_ops(qedi->cdev, &qedi_cb_ops, qedi); - memset(¶ms, 0, sizeof(params)); params.mtu = DEF_PATH_MTU + IPV6_HDR_LEN + TCP_HDR_LEN; qedi->ll2_mtu = DEF_PATH_MTU; @@ -2670,6 +2668,7 @@ retry_probe: spin_lock_init(&qedi->task_idx_lock); mutex_init(&qedi->stats_lock); } + qedi_ops->register_ops(qedi->cdev, &qedi_cb_ops, qedi); qedi_ops->ll2->register_cb_ops(qedi->cdev, &qedi_ll2_cb_ops, qedi); qedi_ops->ll2->start(qedi->cdev, ¶ms); -- cgit v1.2.3 From 2f15518682c779b1014552cb2ccb4a2052f85f27 Mon Sep 17 00:00:00 2001 From: Ridham Khurana Date: Wed, 30 Sep 2026 10:53:25 +0000 Subject: regulator: tps6594-regulator: Fix n_linear_ranges for TPS65224 BUCK2-4 The TPS65224 BUCK2, BUCK3 and BUCK4 descriptors pass 4 as n_linear_ranges, but tps65224_bucks_2_3_4_ranges[] only has two entries, covering selectors 0x00-0x45. The linear range helpers loop over n_linear_ranges entries, so any lookup of a selector above 0x45 reads past the end of the array and treats whatever follows it as two more ranges. This happens on the normal registration path when the core checks the voltage constraints against every selector. TPS652G1 uses the same descriptors and is affected too. Use ARRAY_SIZE() so the count always matches the table. Fixes: 00c826525fba ("regulator: tps6594-regulator: Add TI TPS65224 PMIC regulators") Cc: stable@vger.kernel.org Signed-off-by: Ridham Khurana Link: https://patch.msgid.link/20260930105333.3522412-1-khurana.ridham222@gmail.com Signed-off-by: Mark Brown --- drivers/regulator/tps6594-regulator.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/regulator/tps6594-regulator.c b/drivers/regulator/tps6594-regulator.c index 31a5218d5510..503b89a2f5f8 100644 --- a/drivers/regulator/tps6594-regulator.c +++ b/drivers/regulator/tps6594-regulator.c @@ -241,21 +241,21 @@ static const struct regulator_desc tps65224_buck_regs[] = { TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_CTRL(1), TPS6594_BIT_BUCK_EN, 0, 0, tps65224_bucks_2_3_4_ranges, - 4, 0, 0, NULL, 0, 0), + ARRAY_SIZE(tps65224_bucks_2_3_4_ranges), 0, 0, NULL, 0, 0), TPS6594_REGULATOR("BUCK3", "buck3", TPS6594_BUCK_3, REGULATOR_VOLTAGE, tps6594_bucks_ops, TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_VOUT_1(2), TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_CTRL(2), TPS6594_BIT_BUCK_EN, 0, 0, tps65224_bucks_2_3_4_ranges, - 4, 0, 0, NULL, 0, 0), + ARRAY_SIZE(tps65224_bucks_2_3_4_ranges), 0, 0, NULL, 0, 0), TPS6594_REGULATOR("BUCK4", "buck4", TPS6594_BUCK_4, REGULATOR_VOLTAGE, tps6594_bucks_ops, TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_VOUT_1(3), TPS65224_MASK_BUCKS_VSET, TPS6594_REG_BUCKX_CTRL(3), TPS6594_BIT_BUCK_EN, 0, 0, tps65224_bucks_2_3_4_ranges, - 4, 0, 0, NULL, 0, 0), + ARRAY_SIZE(tps65224_bucks_2_3_4_ranges), 0, 0, NULL, 0, 0), }; static const struct tps6594_regulator_irq_type tps6594_buck1_irq_types[] = { -- cgit v1.2.3 From d99bc5b456fd63be46c8bda20dbf6ede93d2fc11 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Sun, 20 Sep 2026 11:34:59 +0800 Subject: nvmet: return Invalid Field for invalid NSIDs feature 82h The Reservation Notification Mask feature (FID 82h) defines its own status code for namespaces that do not support reservations: "If a Set Features command or a Get Features command attempts to access the Reservation Notification Mask on a namespace that does not support reservations or is invalid, then that command is aborted with status code of Invalid Field in Command." -- NVMe Base Specification, section 5.2.26.1.33 The Set Features handler routes the namespace lookup through nvmet_req_find_ns(), which returns Invalid Namespace or Format for an unknown NSID. That is the correct status for the generic namespace-scoped admin commands, but not for this feature: a Set Features 82h naming a nonexistent NSID is aborted with Invalid Namespace or Format instead of Invalid Field in Command. Translate the lookup failure to Invalid Field in Command. The broadcast NSID, the namespace-without-reservations case and everything else keep their current behavior. Fixes: 5a47c2080a73 ("nvmet: support reservation feature") Reviewed-by: Christoph Hellwig Reviewed-by: Nilay Shroff Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/pr.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/nvme/target/pr.c b/drivers/nvme/target/pr.c index 09d8c63f5680..81f48cb00aee 100644 --- a/drivers/nvme/target/pr.c +++ b/drivers/nvme/target/pr.c @@ -51,6 +51,8 @@ u16 nvmet_set_feat_resv_notif_mask(struct nvmet_req *req, u32 mask) if (nsid != U32_MAX) { status = nvmet_req_find_ns(req); + if (status == (NVME_SC_INVALID_NS | NVME_STATUS_DNR)) + return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; if (status) return status; if (!req->ns->pr.enable) @@ -75,6 +77,8 @@ u16 nvmet_get_feat_resv_notif_mask(struct nvmet_req *req) u16 status; status = nvmet_req_find_ns(req); + if (status == (NVME_SC_INVALID_NS | NVME_STATUS_DNR)) + return NVME_SC_INVALID_FIELD | NVME_STATUS_DNR; if (status) return status; -- cgit v1.2.3 From 564e46d9f6e87912effa03f123d37a511d520f09 Mon Sep 17 00:00:00 2001 From: Karl Bonde Torp Date: Tue, 29 Sep 2026 16:25:00 +0200 Subject: nvme: fix swapped ZRASF zone state values for full and read only The Zone Management Receive command Zone Receive Action Specific (ZRASF) reporting option values for the Full and Read Only zone states are swapped compared to the NVM Express Zoned Namespace Command Set Specification, Revision 1.5, Figure 35: - 5h lists the zones in the ZSF:Full state. - 6h lists the zones in the ZSRO:Read Only state. The nvmet ZNS backend uses these constants to map the ZRASF value received from the host to a block layer zone condition when filtering zones in Report Zones. Because of the swap, a host asking for full zones was given the read only zones, and a host asking for read only zones was given the full zones. Swap the values of NVME_ZRASF_ZONE_STATE_FULL and NVME_ZRASF_ZONE_STATE_READONLY to match the specification. Fixes: aaf2e048af27 ("nvmet: add ZBD over ZNS backend support") Cc: stable@vger.kernel.org Reviewed-by: Damien Le Moal Signed-off-by: Karl Bonde Torp Signed-off-by: Andreas Hindborg Signed-off-by: Keith Busch --- include/linux/nvme.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/nvme.h b/include/linux/nvme.h index 91ce434a7e8d..6248711784b3 100644 --- a/include/linux/nvme.h +++ b/include/linux/nvme.h @@ -1266,8 +1266,8 @@ enum { NVME_ZRASF_ZONE_STATE_IMP_OPEN = 0x02, NVME_ZRASF_ZONE_STATE_EXP_OPEN = 0x03, NVME_ZRASF_ZONE_STATE_CLOSED = 0x04, - NVME_ZRASF_ZONE_STATE_READONLY = 0x05, - NVME_ZRASF_ZONE_STATE_FULL = 0x06, + NVME_ZRASF_ZONE_STATE_FULL = 0x05, + NVME_ZRASF_ZONE_STATE_READONLY = 0x06, NVME_ZRASF_ZONE_STATE_OFFLINE = 0x07, NVME_REPORT_ZONE_PARTIAL = 1, }; -- cgit v1.2.3 From b3ce7572130f7bdc71581d798df3fbfc3b64e7a5 Mon Sep 17 00:00:00 2001 From: Palla Raghunath Date: Wed, 30 Sep 2026 07:26:47 +0100 Subject: nvme-multipath: revalidate head zones after unfreezing the head queue When a namespace on a multipath controller is updated, nvme_update_ns_info() freezes the head disk queue, commits the new limits, and then calls nvme_mpath_revalidate_zones() before it unfreezes the queue again. That is the wrong way round for blk_revalidate_disk_zones(). It starts a limits update, which takes q->limits_lock, and it freezes the queue itself while updating the zone resources. The block layer takes limits_lock before freezing the queue, never the other way around, so calling it with the head queue already frozen reverses that order. syzbot has hit this twice. One report goes through q->limits_lock. The other one is on linux-next, where blk_revalidate_disk_zones() also takes disk->zone_revalidate_mutex and holds it across alloc_workqueue() the first time a disk's zone resources are set up. Lockdep then sees: q_usage_counter(io) (frozen head queue, nvme_update_ns_info()) --> &disk->zone_revalidate_mutex --> wq_pool_mutex --> fs_reclaim --> q_usage_counter(io) WARNING: possible circular locking dependency detected kworker/u8:10/3352 is trying to acquire lock: (&disk->zone_revalidate_mutex){+.+.}-{4:4}, at: blk_revalidate_disk_zones+0x1c5/0x1650 but task is already holding lock: (&q->q_usage_counter(io)#75){++++}-{0:0}, at: nvme_update_ns_info+0x3ac/0x1200 ... blk_revalidate_disk_zones+0x1c5/0x1650 block/blk-zoned.c:2560 nvme_mpath_revalidate_zones+0x106/0x1c0 drivers/nvme/host/multipath.c:301 nvme_update_ns_info+0x984/0x1200 drivers/nvme/host/core.c:2620 The rest of the driver already does this correctly: nvme_update_ns_info_block() unfreezes ns->disk->queue before calling blk_revalidate_disk_zones(), and nvme_mpath_set_live() revalidates the head zones without freezing the queue. Do the same here, and only revalidate the head zones once the queue is unfrozen and the limits update has succeeded. Fixes: 224041412693 ("nvme-multipath: revalidate zones for namespace heads") Reported-by: syzbot+b0910be96b7c31314822@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=b0910be96b7c31314822 Reported-by: syzbot+2e02ccadb3c5522a5c59@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=2e02ccadb3c5522a5c59 Link: https://lore.kernel.org/all/2bfc96f2-7d0d-47e0-936e-8810abb31a9f@acm.org/ Cc: Shuah Khan Cc: Brigham Campbell Reviewed-by: Damien Le Moal Reviewed-by: Yao Sang Tested-by: Yao Sang Signed-off-by: Palla Raghunath Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index ee7d09030c18..5d7dfd7a63d4 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -2617,10 +2617,19 @@ static int nvme_update_ns_info(struct nvme_ns *ns, struct nvme_ns_info *info) set_capacity_and_notify(ns->head->disk, get_capacity(ns->disk)); set_disk_ro(ns->head->disk, nvme_ns_is_readonly(ns, info)); nvme_mpath_revalidate_paths(ns->head); - ret = nvme_mpath_revalidate_zones(ns->head); unfreeze_head_queue: blk_mq_unfreeze_queue(ns->head->disk->queue, memflags); + + /* + * Wait until the head queue is unfrozen before revalidating + * its zones. blk_revalidate_disk_zones() takes the queue limits + * lock and then freezes the queue on its own, so it must not be + * called with the queue already frozen. This is also what + * nvme_update_ns_info_block() does for ns->disk. + */ + if (!ret) + ret = nvme_mpath_revalidate_zones(ns->head); } return ret; -- cgit v1.2.3 From 4aa9c0f377a4de5e8fddce76b34ee4529639a1ff Mon Sep 17 00:00:00 2001 From: Sang-Hoon Choi Date: Wed, 30 Sep 2026 16:08:58 +0900 Subject: nvmet: use the local P2P device on metadata allocation failure nvmet_req_alloc_p2pmem_sgls() assigns req->p2p_dev only after the data and metadata SGL allocations succeed. If the data allocation succeeds but the metadata allocation fails, out_free_sg passes req->p2p_dev to pci_p2pmem_free_sgl() before that assignment has taken place. Pass the local p2p_dev argument instead, so cleanup uses the same PCI device as the successful data allocation. Fixes: bcd9a0797d73 ("nvmet: fix freeing unallocated p2pmem") Reported-by: Changyul Lee Reviewed-by: Maurizio Lombardi Signed-off-by: Sang-Hoon Choi Signed-off-by: Keith Busch --- drivers/nvme/target/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 8eea0a504308..07a74c2e1f58 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -1325,7 +1325,7 @@ static int nvmet_req_alloc_p2pmem_sgls(struct pci_dev *p2p_dev, return 0; out_free_sg: - pci_p2pmem_free_sgl(req->p2p_dev, req->sg); + pci_p2pmem_free_sgl(p2p_dev, req->sg); out_err: return -ENOMEM; } -- cgit v1.2.3 From e2151c29cec73f6cc70abf1c7cbc7b9b202489f5 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 30 Sep 2026 20:13:03 +0400 Subject: regulator: of: fill in supply names in of_regulator_bulk_get_all() of_regulator_bulk_get_all() returns an array it allocated itself, and fills in only the consumer of each entry. Every other way of getting a bulk array has the supply name set, because the caller provides it, and the core expects it to be there: regulator_bulk_enable() prints it when a supply fails to enable, so a caller that hands such an array to it dereferences uninitialised memory on that path. Copy each name into the array's own allocation, right behind the entries, so that it shares the array's lifetime and callers still have nothing extra to free. That also retires the fixed 64 byte stack buffer the names were assembled in, which is_supply_name() never bounded the copy against. Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260928-b4-rk3576-reboot-mode-v1-0-65486b03bd41@flipper.net?part=3 Fixes: 27b9ecc7a9ba ("regulator: Add of_regulator_bulk_get_all") Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260930-regulator-get-all-v2-1-ca82070d22f2@flipper.net Signed-off-by: Mark Brown --- drivers/regulator/of_regulator.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c index c0cc6cc0afd8..785b7a11dfc6 100644 --- a/drivers/regulator/of_regulator.c +++ b/drivers/regulator/of_regulator.c @@ -935,15 +935,15 @@ static int is_supply_name(const char *name) int of_regulator_bulk_get_all(struct device *dev, struct device_node *np, struct regulator_bulk_data **consumers) { - int num_consumers = 0; + int num_consumers = 0, names_len = 0; struct regulator *tmp; struct regulator_bulk_data *_consumers = NULL; struct property *prop; + char *names; int i, n = 0, ret; - char name[64]; /* - * first pass: get numbers of xxx-supply + * first pass: get numbers of xxx-supply and the room their names take * second pass: fill consumers */ restart: @@ -953,16 +953,19 @@ restart: continue; if (!_consumers) { num_consumers++; + names_len += i + 1; continue; } else { - memcpy(name, prop->name, i); - name[i] = '\0'; - tmp = regulator_get(dev, name); + memcpy(names, prop->name, i); + names[i] = '\0'; + tmp = regulator_get(dev, names); if (IS_ERR(tmp)) { ret = PTR_ERR(tmp); goto error; } + _consumers[n].supply = names; _consumers[n].consumer = tmp; + names += i + 1; n++; continue; } @@ -973,9 +976,16 @@ restart: } if (num_consumers == 0) return 0; - _consumers = kmalloc_objs(struct regulator_bulk_data, num_consumers); + /* + * The supply names are kept in the same allocation as the array, so + * that they share its lifetime and the caller has nothing extra to + * free. + */ + _consumers = kzalloc(size_add(size_mul(num_consumers, sizeof(*_consumers)), + names_len), GFP_KERNEL); if (!_consumers) return -ENOMEM; + names = (char *)(_consumers + num_consumers); goto restart; error: -- cgit v1.2.3 From b35ff7bd7dd56915a511eec56c8763ee8f9f7e9f Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 30 Sep 2026 20:13:04 +0400 Subject: regulator: of: state who owns the array from of_regulator_bulk_get_all() of_regulator_bulk_get_all() allocates the consumer array and hands it to the caller, but never said so. Its own error path frees the array, while regulator_bulk_free() only puts the regulators, so both callers release the regulators and leak the array. Spell the contract out in the kerneldoc. Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260930-regulator-get-all-v2-2-ca82070d22f2@flipper.net Signed-off-by: Mark Brown --- drivers/regulator/of_regulator.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c index 785b7a11dfc6..09e26febfc83 100644 --- a/drivers/regulator/of_regulator.c +++ b/drivers/regulator/of_regulator.c @@ -929,6 +929,11 @@ static int is_supply_name(const char *name) * before returning to the caller, and @consumers will not be * changed. * + * On success the array is allocated here and handed to the caller, which + * owns it from then on: release the regulators with regulator_bulk_free() + * and free the array itself with kfree(). The supply names live in the + * same allocation, so they are gone once the array is freed. + * * Return: Number of regulators on success, or a negative error number * on failure. */ -- cgit v1.2.3 From 94b6df050433875f493fb8298b58d7b0dc32eb9c Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Mon, 28 Sep 2026 19:53:11 +0800 Subject: spi: spi-nxp-fspi: exit stop mode before waiting for DLL lock For 8D-8D-8D (octal DTR) mode the FlexSPI samples read data using MCR0[RXCLKSRC] = 3 (flash-memory-provided read strobe on the DQS pad), which requires the DLL to be locked. Per the i.MX95 Reference Manual (FlexSPI chapter, "DLL configuration for sampling"), when the serial root clock is above 100 MHz the DLL is enabled (DLLxCR[DLLEN] = 1, OVRDEN = 0) and software must then wait for STS2[xREFLOCK] and STS2[xSLVLOCK] to be asserted before starting a read or write. This DLL lock is relative to the serial root clock. While the module is in stop mode (MCR0[MDIS] = 1) the AHB and serial clocks are gated off internally, so the serial root clock the DLL locks against is not running. nxp_fspi_dll_calibration() both programs DLLxCR and then polls STS2 for lock. When it runs entirely in stop mode the STS2 REF/SLV lock bits never assert (observed as "DLL lock failed" with STS2 = 0). The MCR0 bit fields (RXCLKSRC) and the serial root clock rate still have to be reconfigured in stop mode, so keep entering stop mode around that. Only move the exit from stop mode (clearing MCR0[MDIS]) ahead of the DLL calibration/override step, so that the serial root clock is running when the DLL is calibrated and STS2 can report lock. Fixes: b4bde5cfff8e ("spi: spi-nxp-fspi: enter stop mode before reconfiguring MCR0 and DLL") Signed-off-by: Haibo Chen Link: https://patch.msgid.link/20260928-fspi-fix-v1-1-a0f9288cd5c8@nxp.com Signed-off-by: Mark Brown --- drivers/spi/spi-nxp-fspi.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-nxp-fspi.c b/drivers/spi/spi-nxp-fspi.c index 67d991dd249d..66cb58f95fdb 100644 --- a/drivers/spi/spi-nxp-fspi.c +++ b/drivers/spi/spi-nxp-fspi.c @@ -898,10 +898,10 @@ static int nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi, dev_dbg(f->dev, "Target device [CS:%x] selected\n", spi_get_chipselect(spi, 0)); /* - * Per the FlexSPI reference manual (initialization sequence), MCR0 and - * the DLL control registers should be configured while the module is in - * stop mode (MCR0[MDIS] = 1). Enter stop mode before reconfiguring the - * RX sample clock source and the DLL, then exit stop mode afterwards. + * Per the FlexSPI reference manual (initialization sequence), MCR0 + * should be configured while the module is in stop mode (MCR0[MDIS] = 1). + * Enter stop mode before reconfiguring the RX sample clock source, then + * exit stop mode afterwards. */ reg = fspi_readl(f, f->iobase + FSPI_MCR0); fspi_writel(f, reg | FSPI_MCR0_MDIS, f->iobase + FSPI_MCR0); @@ -936,6 +936,16 @@ static int nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi, if (ret) return ret; + /* + * Exit stop mode before running the DLL calibration. The DLL + * reference clock is derived from the module clock domain, which is + * gated while MCR0[MDIS] = 1. Calibrating the DLL in stop mode leaves + * the STS2 lock bits stuck at 0 (the DLL never runs), so clear MDIS + * first and let the module and reference clock run. + */ + reg = fspi_readl(f, f->iobase + FSPI_MCR0); + fspi_writel(f, reg & ~FSPI_MCR0_MDIS, f->iobase + FSPI_MCR0); + /* * If clock rate > 100MHz, then switch from DLL override mode to * DLL calibration mode. @@ -945,10 +955,6 @@ static int nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi, else nxp_fspi_dll_override(f); - /* Exit stop mode now that MCR0 and the DLL have been reconfigured. */ - reg = fspi_readl(f, f->iobase + FSPI_MCR0); - fspi_writel(f, reg & ~FSPI_MCR0_MDIS, f->iobase + FSPI_MCR0); - f->pre_op_rate = op->max_freq; f->selected = spi_get_chipselect(spi, 0); -- cgit v1.2.3 From fd99864b0ac936e870c7ae28354c5cd3dad5efd0 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Thu, 1 Oct 2026 17:14:44 +0200 Subject: spi: cadence-qspi: Fix status polling with octal DTR chips Most SPI NOR chips today use 0x05 as ReaD Status Register (RDSR) opcode. After almost every transaction, the core will wait for the said transation to be digested by the chip by polling its status register. This mechanism has been automated by certain chips, such as the Cadence QSPI controller. Unfortunately, the feature has been disabled for various (relevant) reasons. Disabling this feature has been done by setting WRITE_COMPLETION_CTRL_REG.DISABLE_POLLING_FLD, keeping the rest of the register intact. In this same register, there is also a field named WRITE_COMPLETION_CTRL_REG.OPCODE_FLD, which by default also contains 0x05, as primary RDSR opcode. As a result, both the SPI NOR and SPI NAND cores, primary users through spi-mem of the spi_mem_ops interface, do send their own RDSR. In this driver, such command goes through one of the fifth possible data path specifically crafted for short register-like commands, named STIG. In STIG mode, the opcode of the operation is written to OSPI_FLASH_CMD_CTRL_REG. This means that the opcode field of STIG configuration and the opcode field of the automatic polling configuration contain the same value. Unfortunately, the TRM explains in two different places that this configuration is forbidden, without explaining why it is problematic: $12.4.2.4.11 OSPI Software-Triggered Instruction Generator (STIG) ``` OSPI_FLASH_CMD_CTRL_REG[31-24] CMD_OPCODE_FLD bits should be set different than OSPI_DEV_INSTR_RD_CONFIG_REG[7-0] RD_OPCODE_NON_XIP_FLD and OSPI_DEV_INSTR_WR_CONFIG_REG[7-0] WR_OPCODE_FLD. ``` and $12.4.2.5.2 Configuring the OSPI Controller for Optimal Use ``` When using the OSPI Controller, the opcodes in OSPI_DEV_INSTR_RD_CONFIG_REG[7-0] RD_OPCODE_NON_XIP_FLD, OSPI_DEV_INSTR_WR_CONFIG_REG[7-0] WR_OPCODE_FLD and OSPI_WRITE_COMPLETION_CTRL_REG[7-0] OPCODE_FLD bit fields shall not match the opcode in the OSPI_FLASH_CMD_CTRL_REG[31-24] CMD_OPCODE_FLD bit field. ``` For some reasons, this is only unveiled now when testing in ODTR mode. Plain SDR modes work flawlessly with the same chips. One possible reason for this, is that the internal data path in ODTR mode is very different due to the extra opcode extension/repetition which may be interpreted differently (this is a wild guess). For sure there is an interference, as even the TRM states that there is some magic happening: $12.4.2.4.13 OSPI Command Translation ``` The WREN and the RDSR device instructions are the only ones that are sent by the controller under the hood. ``` I cannot probe the bus when the problem arises, so I don't know exactly what is sent on the bus. Practically speaking, the chip was driving the lines low (reading all 0s), which is not the usual default state when the chip is not driving the lines (all 1s). Other people on the mailing list claimed successfully using ODTR chips with this controller. My take here is that either they were using a variant of the IP without write completion at all (there is a quirk for it), or they were using chips slightly more flexible about their opcode sequency or maybe with different addr/dummy requirements. Clearing the WRITE_COMPLETION_CTRL_REG.OPCODE_FLD while disabling write completion polling entirely seems the best compromise. Signed-off-by: Miquel Raynal Link: https://patch.msgid.link/20261001-winbond-spi-next-cadence-rdsr-fix-v1-1-ec5ff594ba5d@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-cadence-quadspi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index ecb0be394696..5a5f1a852cbe 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -1056,8 +1056,7 @@ static int cqspi_write_setup(struct cqspi_flash_pdata *f_pdata, * care of polling the status register. */ if (cqspi->wr_completion) { - reg = readl(reg_base + CQSPI_REG_WR_COMPLETION_CTRL); - reg |= CQSPI_REG_WR_DISABLE_AUTO_POLL; + reg = CQSPI_REG_WR_DISABLE_AUTO_POLL; writel(reg, reg_base + CQSPI_REG_WR_COMPLETION_CTRL); /* * DAC mode require auto polling as flash needs to be polled -- cgit v1.2.3 From 58304e139cb3fa640da74bc02101f0926d0607e2 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 16 Sep 2026 15:56:57 +0900 Subject: zloop: zero out only the unread tail of short reads zloop_complete_rq() handles a short read from a zone's backing file by zero-filling every bio in the request. This is correct when the entire request is beyond the written part of the zone. When a read request straddles the boundary between written and unwritten data, read_iter() returns a partial byte count and zloop then zeroes the whole request even for the partially read area with valid data. Such an unexpected zero data read was observed with fio test script t/zbd/test-zbd-support and its test case 69, failing with "bad magic header 0". Avoid the unexpected zero data read by zeroing out only the unread tail part. When read data size cmd->ret is smaller than the request size, call the new helper function zloop_fill_zero_rq() that walks through each bio_vec and fills zeros from the specified start offset. Fixes: eb0570c7df23 ("block: new zoned loop block device driver") Reviewed-by: Damien Le Moal Reviewed-by: Christoph Hellwig Signed-off-by: Shin'ichiro Kawasaki Link: https://patch.msgid.link/20260916065657.62724-1-shinichiro.kawasaki@opensource.wdc.com Signed-off-by: Jens Axboe --- drivers/block/zloop.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c index f0ca221524db..394dc2408ed7 100644 --- a/drivers/block/zloop.c +++ b/drivers/block/zloop.c @@ -800,6 +800,27 @@ static void zloop_cmd_workfn(struct work_struct *work) current->flags = orig_flags; } +static void zloop_fill_zero_rq(struct request *rq, unsigned int zero_from) +{ + struct req_iterator iter; + struct bio_vec bv; + unsigned int pos = 0; + unsigned int end, off; + + rq_for_each_segment(bv, rq, iter) { + end = pos + bv.bv_len; + if (end > zero_from) { + if (pos < zero_from) + off = zero_from - pos; + else + off = 0; + memzero_page(bv.bv_page, bv.bv_offset + off, + bv.bv_len - off); + } + pos = end; + } +} + static void zloop_complete_rq(struct request *rq) { struct zloop_cmd *cmd = blk_mq_rq_to_pdu(rq); @@ -814,13 +835,9 @@ static void zloop_complete_rq(struct request *rq) pr_err("Zone %u: failed read sector %llu, %llu sectors\n", zone_no, cmd->sector, cmd->nr_sectors); - if (cmd->ret >= 0 && cmd->ret != blk_rq_bytes(rq)) { - /* short read */ - struct bio *bio; - - __rq_for_each_bio(bio, rq) - zero_fill_bio(bio); - } + /* Short read. Zero out the unread tail beyond cmd->ret. */ + if (cmd->ret >= 0 && cmd->ret != blk_rq_bytes(rq)) + zloop_fill_zero_rq(rq, cmd->ret); break; case REQ_OP_WRITE: case REQ_OP_ZONE_APPEND: -- cgit v1.2.3 From 5755f1bd0217ffe3c5cf85467bf6b0680f398265 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Mon, 5 Oct 2026 04:18:24 -0700 Subject: Revert "nvme: do not reset controllers in NVME_CTRL_NEW state" This reverts commit c20bba234f3e1dcd81f7a2ec21ea7bf01a4cf35f. The standard transport drivers were okay with the patch, but the Apple driver needs a bit of work before it can handle the state transition requirement. Link: https://lore.kernel.org/linux-nvme/94912d46-381d-4988-800a-6ef3b595233f@cyberchaos.dev/ Reported-by: Yureka Lilian Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 5d7dfd7a63d4..08f45905e103 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -598,6 +598,7 @@ bool nvme_change_ctrl_state(struct nvme_ctrl *ctrl, break; case NVME_CTRL_RESETTING: switch (old_state) { + case NVME_CTRL_NEW: case NVME_CTRL_LIVE: changed = true; atomic_long_inc(&ctrl->nr_reset); -- cgit v1.2.3 From 87db7795cbaaf636ae22ae031f4906db7bb77803 Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Fri, 2 Oct 2026 14:16:17 -0700 Subject: x86/CPU/AMD: Fix AMD TLBI Erratum #1718 The processor may fail to invalidate a TLB entry while executing an INVLPGB/TLBSYNC instruction pair. Detect the erratum and apply a software workaround when a microcode fix is not present. Fixes: 82378c6c2f43 ("x86/mm: Use INVLPGB for kernel TLB flushes") Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Rik van Riel Acked-by: Lorenzo Stoakes (ARM) Tested-by: Lorenzo Stoakes (ARM) Tested-by: Rik van Riel Cc: stable@kernel.org Link: https://patch.msgid.link/20261002211617.1001617-1-bp@kernel.org --- arch/x86/include/asm/cpufeatures.h | 3 +++ arch/x86/include/asm/tlb.h | 6 ++++++ arch/x86/kernel/cpu/amd.c | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h index f70ee74b5f92..fe84cf4e97d3 100644 --- a/arch/x86/include/asm/cpufeatures.h +++ b/arch/x86/include/asm/cpufeatures.h @@ -477,6 +477,8 @@ #define X86_FEATURE_NO_NESTED_DATA_BP (20*32+ 0) /* No Nested Data Breakpoints */ #define X86_FEATURE_WRMSR_XX_BASE_NS (20*32+ 1) /* WRMSR to {FS,GS,KERNEL_GS}_BASE is non-serializing */ #define X86_FEATURE_LFENCE_RDTSC (20*32+ 2) /* LFENCE always serializing / synchronizes RDTSC */ + +#define X86_FEATURE_TLBSYNC_SFW_NO (20*32+ 4) /* TLBI Erratum #1718 fixed in microcode */ #define X86_FEATURE_VERW_CLEAR (20*32+ 5) /* The memory form of VERW mitigates TSA */ #define X86_FEATURE_NULL_SEL_CLR_BASE (20*32+ 6) /* Null Selector Clears Base */ @@ -587,4 +589,5 @@ #define X86_BUG_TSA X86_BUG( 1*32+ 9) /* "tsa" CPU is affected by Transient Scheduler Attacks */ #define X86_BUG_VMSCAPE X86_BUG( 1*32+10) /* "vmscape" CPU is affected by VMSCAPE attacks from guests */ #define X86_BUG_SEAMRET_INVD_VMCS X86_BUG( 1*32+11) /* "seamret_invd_vmcs" SEAMRET from P-SEAMLDR clears the current VMCS */ +#define X86_BUG_TLBI X86_BUG( 1*32+12) /* "tlbi" AMD TLBI Erratum #1718 */ #endif /* _ASM_X86_CPUFEATURES_H */ diff --git a/arch/x86/include/asm/tlb.h b/arch/x86/include/asm/tlb.h index 866ea78ba156..eba925c85f52 100644 --- a/arch/x86/include/asm/tlb.h +++ b/arch/x86/include/asm/tlb.h @@ -110,6 +110,12 @@ static inline void __tlbsync(void) /* TLBSYNC: supported in binutils >= 0.36. */ asm volatile(".byte 0x0f, 0x01, 0xff" ::: "memory"); + + if (!static_cpu_has_bug(X86_BUG_TLBI)) + return; + + /* TLBSYNC: supported in binutils >= 0.36. */ + asm volatile(".byte 0x0f, 0x01, 0xff" ::: "memory"); } #else /* Some compilers (I'm looking at you clang!) simply can't do DCE */ diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c index 54e14ed276b5..40463bad1078 100644 --- a/arch/x86/kernel/cpu/amd.c +++ b/arch/x86/kernel/cpu/amd.c @@ -1049,6 +1049,10 @@ static void init_amd_zen5(struct cpuinfo_x86 *c) msr_clear_bit(MSR_AMD64_CPUID_FN_7, 18); pr_emerg_once("RDSEED32 is broken. Disabling the corresponding CPUID bit.\n"); } + + if (cpu_has(c, X86_FEATURE_INVLPGB) && + !cpu_has(c, X86_FEATURE_TLBSYNC_SFW_NO)) + set_cpu_bug(c, X86_BUG_TLBI); } static void init_amd(struct cpuinfo_x86 *c) -- cgit v1.2.3 From edba6017c19f97ecc718fad6b50e0908392247ac Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Mon, 5 Oct 2026 19:02:37 -0600 Subject: riscv: hwprobe: use _BITULL() rather than BIT() in MIPS vendor uapi header BIT() is a kernel-internal macro that is not available to userspace, but the MIPS vendor extension uapi header uses it without defining or including it. Any userspace program that includes this header and uses RISCV_HWPROBE_VENDOR_EXT_XMIPSEXECTL fails to build. Use _BITULL(0) from linux/const.h instead, which keeps the value at 1, so there is no ABI change. Fixes: bb4b0f8a1bcb ("riscv: hwprobe: Add MIPS vendor extension probing") Cc: stable@vger.kernel.org Signed-off-by: Zongmin Zhou Reviewed-by: Jesse Taube Link: https://patch.msgid.link/20260918073650.42586-1-min_halo@163.com Signed-off-by: Paul Walmsley --- arch/riscv/include/uapi/asm/vendor/mips.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/riscv/include/uapi/asm/vendor/mips.h b/arch/riscv/include/uapi/asm/vendor/mips.h index e65ab268b265..a31c23afe74f 100644 --- a/arch/riscv/include/uapi/asm/vendor/mips.h +++ b/arch/riscv/include/uapi/asm/vendor/mips.h @@ -1,3 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -#define RISCV_HWPROBE_VENDOR_EXT_XMIPSEXECTL BIT(0) +#include + +#define RISCV_HWPROBE_VENDOR_EXT_XMIPSEXECTL _BITULL(0) -- cgit v1.2.3 From 756724133bd3b89e097c92110221b44cafe0d06a Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 5 Oct 2026 19:02:37 -0600 Subject: riscv: limit sifive errata to CONFIG_64BIT There are two errata for this vendor, and they both individually depend on CONFIG_64BIT already. However, an 32-bit allmodconfig produces this warning from clang for a condition that can never be true. arch/riscv/errata/sifive/errata.c:29:14: error: result of comparison of constant 9223372036854775815 with expression of type 'unsigned long' is always true [-Werror,-Wtautological-constant-out-of-range-compare] 29 | if (arch_id != 0x8000000000000007 || | ~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~ arch/riscv/errata/sifive/errata.c:42:14: error: result of comparison of constant 9223372036854775815 with expression of type 'unsigned long' is always true [-Werror,-Wtautological-constant-out-of-range-compare] 42 | if (arch_id != 0x8000000000000007 && arch_id != 0x1) | ~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~ Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260916083752.47310-1-arnd@kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/Kconfig.errata | 2 +- arch/riscv/Kconfig.socs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/riscv/Kconfig.errata b/arch/riscv/Kconfig.errata index 3c945d086c7d..38ade0ea4e9c 100644 --- a/arch/riscv/Kconfig.errata +++ b/arch/riscv/Kconfig.errata @@ -46,7 +46,7 @@ config ERRATA_MIPS_P8700_PAUSE_OPCODE config ERRATA_SIFIVE bool "SiFive errata" - depends on RISCV_ALTERNATIVE + depends on RISCV_ALTERNATIVE && 64BIT help All SiFive errata Kconfig depend on this Kconfig. Disabling this Kconfig will disable all SiFive errata. Please say "Y" diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs index 429e07589306..cecbce0c4983 100644 --- a/arch/riscv/Kconfig.socs +++ b/arch/riscv/Kconfig.socs @@ -34,6 +34,7 @@ config ARCH_RENESAS config ARCH_SIFIVE bool "SiFive SoCs" select ERRATA_SIFIVE + depends on 64BIT help This enables support for SiFive SoC platform hardware. -- cgit v1.2.3 From 572b25f3a6453d8e7fc28cc0cefe41ed15be166c Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Mon, 5 Oct 2026 19:02:37 -0600 Subject: riscv: vector: Fix data pointer constraints in context save/restore The standard vector save/restore asm advances datap but declares it as input-only. An inlined caller reusing the original pointer may therefore use the advanced address instead. Declare datap as read-write so the compiler can preserve the original pointer when needed. Fixes: 03c3fcd9941a ("riscv: Introduce struct/helpers to save/restore per-task Vector state") Signed-off-by: Troy Mitchell Reviewed-by: GUO Ren (XuanTie) Reviewed-by: Aurelien Jarno Reviewed-by: Andy Chiu Link: https://patch.msgid.link/20260908-riscv-vector-asm-fix-v1-1-147f314efb2b@linux.dev Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/vector.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/riscv/include/asm/vector.h b/arch/riscv/include/asm/vector.h index fffe72a77208..c7fd6d50a7a4 100644 --- a/arch/riscv/include/asm/vector.h +++ b/arch/riscv/include/asm/vector.h @@ -230,7 +230,7 @@ static inline void __riscv_v_vstate_save(struct __riscv_v_ext_state *save_to, "add %1, %1, %0\n\t" "vse8.v v24, (%1)\n\t" ".option pop\n\t" - : "=&r" (vl) : "r" (datap) : "memory"); + : "=&r" (vl), "+r" (datap) : : "memory"); } riscv_v_disable(); } @@ -266,7 +266,7 @@ static inline void __riscv_v_vstate_restore(struct __riscv_v_ext_state *restore_ "add %1, %1, %0\n\t" "vle8.v v24, (%1)\n\t" ".option pop\n\t" - : "=&r" (vl) : "r" (datap) : "memory"); + : "=&r" (vl), "+r" (datap) : : "memory"); } __vstate_csr_restore(restore_from); riscv_v_disable(); -- cgit v1.2.3 From 55b1276ef9c1c27e8a2b2c5e8ef0515b5ad0332c Mon Sep 17 00:00:00 2001 From: Aurelien Jarno Date: Mon, 5 Oct 2026 19:02:37 -0600 Subject: riscv: vector: Fix output operands in context save The __vstate_csr_save() inline assembly declares vcsr as an output operand while the assembly only writes vstart, vtype, and vl. Remove it as it is later saved by the C code. Fixes: d863910eabaf ("riscv: vector: Support xtheadvector save/restore") Signed-off-by: Aurelien Jarno Link: https://patch.msgid.link/20260911194218.3379486-1-aurelien@aurel32.net Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/vector.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/riscv/include/asm/vector.h b/arch/riscv/include/asm/vector.h index c7fd6d50a7a4..1f8dcae3af51 100644 --- a/arch/riscv/include/asm/vector.h +++ b/arch/riscv/include/asm/vector.h @@ -137,8 +137,8 @@ static __always_inline void __vstate_csr_save(struct __riscv_v_ext_state *dest) "csrr %0, " __stringify(CSR_VSTART) "\n\t" "csrr %1, " __stringify(CSR_VTYPE) "\n\t" "csrr %2, " __stringify(CSR_VL) "\n\t" - : "=r" (dest->vstart), "=r" (dest->vtype), "=r" (dest->vl), - "=r" (dest->vcsr) : :); + : "=r" (dest->vstart), "=r" (dest->vtype), "=r" (dest->vl) + : :); if (has_xtheadvector()) { unsigned long status; -- cgit v1.2.3 From 148ab873d8194cb44f29474da3b4428fa2e100d1 Mon Sep 17 00:00:00 2001 From: Yong-Xuan Wang Date: Mon, 5 Oct 2026 19:02:38 -0600 Subject: RISC-V: errata: Add SiFive MAL-9092 workaround SiFive cores with MAL-9092 require fence.rw instructions around HLV/HLVX/HSV hypervisor instructions to ensure proper memory ordering when accessing guest memory. Note that HSTATUS.HU is cleared during KVM initialization, restricting hypervisor instructions to HS-mode only. If future changes enable HSTATUS.HU for U-mode access, additional errata considerations will be needed. Signed-off-by: Yong-Xuan Wang Reviewed-by: Samuel Holland Link: https://patch.msgid.link/20260817-sifive-errata-v1-1-6a80f1a2743d@sifive.com Signed-off-by: Paul Walmsley --- arch/riscv/Kconfig.errata | 14 ++++++++++++++ arch/riscv/errata/sifive/errata.c | 20 ++++++++++++++++++++ arch/riscv/include/asm/errata_list_vendors.h | 3 ++- arch/riscv/include/asm/insn-def.h | 16 +++++++++++++--- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/arch/riscv/Kconfig.errata b/arch/riscv/Kconfig.errata index 38ade0ea4e9c..a6aac8774f25 100644 --- a/arch/riscv/Kconfig.errata +++ b/arch/riscv/Kconfig.errata @@ -96,6 +96,20 @@ config ERRATA_STARFIVE_JH7100 Say "Y" if you want to support the BeagleV Starlight and/or StarFive VisionFive V1 boards. +config ERRATA_SIFIVE_MAL_9092 + bool "Apply SiFive HLV/HLVX/HSV fence errata" + depends on ERRATA_SIFIVE && 64BIT + default y + help + This will apply the SiFive MAL-9092 errata to add fence.rw + instructions around all HLV/HLVX/HSV instructions. + + Note: HSTATUS.HU is cleared during KVM initialization. If future + changes enable HSTATUS.HU to allow hypervisor instructions in + U-mode, additional errata handling may be required. + + If you don't know what to do here, say "Y". + config ERRATA_THEAD bool "T-HEAD errata" depends on RISCV_ALTERNATIVE diff --git a/arch/riscv/errata/sifive/errata.c b/arch/riscv/errata/sifive/errata.c index df80c9614df1..1a5d223ebf35 100644 --- a/arch/riscv/errata/sifive/errata.c +++ b/arch/riscv/errata/sifive/errata.c @@ -51,6 +51,22 @@ static bool errata_cip_1200_check_func(unsigned long arch_id, unsigned long imp return true; } +static bool errata_mal_9092_check_func(unsigned long arch_id, unsigned long impid) +{ + /* + * Affected cores: + * Architecture ID: 0x8000000000000109 + * Implementation ID: 0x19251031, 0x19251217, 0x19260320 + */ + + if (arch_id != 0x8000000000000109) + return false; + if (impid != 0x19251031 && impid != 0x19251217 && impid != 0x19260320) + return false; + + return true; +} + static struct errata_info_t errata_list[ERRATA_SIFIVE_NUMBER] = { { .name = "cip-453", @@ -60,6 +76,10 @@ static struct errata_info_t errata_list[ERRATA_SIFIVE_NUMBER] = { .name = "cip-1200", .check_func = errata_cip_1200_check_func }, + { + .name = "mal-9092", + .check_func = errata_mal_9092_check_func + }, }; static u32 __init_or_module sifive_errata_probe(unsigned long archid, diff --git a/arch/riscv/include/asm/errata_list_vendors.h b/arch/riscv/include/asm/errata_list_vendors.h index ec7eba373437..c62a82f60468 100644 --- a/arch/riscv/include/asm/errata_list_vendors.h +++ b/arch/riscv/include/asm/errata_list_vendors.h @@ -11,7 +11,8 @@ #ifdef CONFIG_ERRATA_SIFIVE #define ERRATA_SIFIVE_CIP_453 0 #define ERRATA_SIFIVE_CIP_1200 1 -#define ERRATA_SIFIVE_NUMBER 2 +#define ERRATA_SIFIVE_MAL_9092 2 +#define ERRATA_SIFIVE_NUMBER 3 #endif #ifdef CONFIG_ERRATA_THEAD diff --git a/arch/riscv/include/asm/insn-def.h b/arch/riscv/include/asm/insn-def.h index 7c6daf116756..8fcc848c196e 100644 --- a/arch/riscv/include/asm/insn-def.h +++ b/arch/riscv/include/asm/insn-def.h @@ -192,18 +192,28 @@ INSN_R(OPCODE_SYSTEM, FUNC3(0), FUNC7(49), \ __RD(0), RS1(gaddr), RS2(vmid)) +#define ALT_SIFIVE_MAL_9092_FENCE \ +ALTERNATIVE("nop", "fence rw, rw", SIFIVE_VENDOR_ID, \ + ERRATA_SIFIVE_MAL_9092, CONFIG_ERRATA_SIFIVE_MAL_9092) + #define HLVX_HU(dest, addr) \ + ALT_SIFIVE_MAL_9092_FENCE "\n" \ INSN_R(OPCODE_SYSTEM, FUNC3(4), FUNC7(50), \ - RD(dest), RS1(addr), __RS2(3)) + RD(dest), RS1(addr), __RS2(3)) "\n" \ + ALT_SIFIVE_MAL_9092_FENCE #define HLV_W(dest, addr) \ + ALT_SIFIVE_MAL_9092_FENCE "\n" \ INSN_R(OPCODE_SYSTEM, FUNC3(4), FUNC7(52), \ - RD(dest), RS1(addr), __RS2(0)) + RD(dest), RS1(addr), __RS2(0)) "\n" \ + ALT_SIFIVE_MAL_9092_FENCE #ifdef CONFIG_64BIT #define HLV_D(dest, addr) \ + ALT_SIFIVE_MAL_9092_FENCE "\n" \ INSN_R(OPCODE_SYSTEM, FUNC3(4), FUNC7(54), \ - RD(dest), RS1(addr), __RS2(0)) + RD(dest), RS1(addr), __RS2(0)) "\n" \ + ALT_SIFIVE_MAL_9092_FENCE #else #define HLV_D(dest, addr) \ __ASM_STR(.error "hlv.d requires 64-bit support") -- cgit v1.2.3 From 6694612317e5225086e39ca1ba97ada2b1942e48 Mon Sep 17 00:00:00 2001 From: Yong-Xuan Wang Date: Mon, 5 Oct 2026 19:02:38 -0600 Subject: RISC-V: Clear HSTATUS.HU on CPU initialization The RISC-V privileged specification does not mandate HSTATUS reset values, leaving the HU bit potentially set after hardware reset. When HU=1, hypervisor instructions (HLV/HLVX/HSV) can execute in U-mode to access guest memory, which may cause unintended behavior if not explicitly controlled. Clear HSTATUS.HU during CPU initialization to ensure hypervisor instructions are only available in HS-mode, preventing unexpected guest memory access from U-mode code. Signed-off-by: Yong-Xuan Wang Reviewed-by: Samuel Holland Link: https://patch.msgid.link/20260924-hstatus_hu-v2-1-7e970f5f1d8d@sifive.com Cc: stable@vger.kernel.org [pjw@kernel.org: capitalized RISCV_ISA_EXT_h to fix build; added stable cc] Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/cpufeature.h | 2 ++ arch/riscv/kernel/cpufeature.c | 12 ++++++++++++ arch/riscv/kernel/setup.c | 2 ++ arch/riscv/kernel/smpboot.c | 2 ++ arch/riscv/kernel/suspend.c | 2 ++ 5 files changed, 20 insertions(+) diff --git a/arch/riscv/include/asm/cpufeature.h b/arch/riscv/include/asm/cpufeature.h index 739fcc84bf7b..5efa72823475 100644 --- a/arch/riscv/include/asm/cpufeature.h +++ b/arch/riscv/include/asm/cpufeature.h @@ -40,6 +40,8 @@ extern u32 thead_vlenb_of; void __init riscv_user_isa_enable(void); +void riscv_clear_hypervisor_csr(void); + #define _RISCV_ISA_EXT_DATA(_name, _id, _subset_exts, _subset_exts_size, _validate) { \ .name = #_name, \ .property = #_name, \ diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c index 61d21f714830..7329792978cc 100644 --- a/arch/riscv/kernel/cpufeature.c +++ b/arch/riscv/kernel/cpufeature.c @@ -1233,6 +1233,18 @@ void __init riscv_user_isa_enable(void) pr_warn("Zicbop disabled as it is unavailable on some harts\n"); } +void riscv_clear_hypervisor_csr(void) +{ + if (!riscv_has_extension_unlikely(RISCV_ISA_EXT_H)) + return; + + /* + * Clear HSTATUS.HU to restrict hypervisor instructions to HS-mode. + * This prevents user-mode from executing HLV/HSV instructions. + */ + csr_clear(CSR_HSTATUS, HSTATUS_HU); +} + #ifdef CONFIG_RISCV_ALTERNATIVE /* * Alternative patch sites consider 48 bits when determining when to patch diff --git a/arch/riscv/kernel/setup.c b/arch/riscv/kernel/setup.c index a32344bb220d..d79f99b86033 100644 --- a/arch/riscv/kernel/setup.c +++ b/arch/riscv/kernel/setup.c @@ -366,6 +366,8 @@ void __init setup_arch(char **cmdline_p) if (!IS_ENABLED(CONFIG_RISCV_ISA_ZBB) || !riscv_isa_extension_available(NULL, ZBB)) static_branch_disable(&efficient_ffs_key); + + riscv_clear_hypervisor_csr(); } bool arch_cpu_is_hotpluggable(int cpu) diff --git a/arch/riscv/kernel/smpboot.c b/arch/riscv/kernel/smpboot.c index f6ef57930b50..a9de2dae804c 100644 --- a/arch/riscv/kernel/smpboot.c +++ b/arch/riscv/kernel/smpboot.c @@ -244,6 +244,8 @@ asmlinkage __visible void smp_callin(void) numa_add_cpu(curr_cpuid); + riscv_clear_hypervisor_csr(); + pr_debug("CPU%u: Booted secondary hartid %lu\n", curr_cpuid, cpuid_to_hartid_map(curr_cpuid)); diff --git a/arch/riscv/kernel/suspend.c b/arch/riscv/kernel/suspend.c index 3efbf7874f3b..db220966f782 100644 --- a/arch/riscv/kernel/suspend.c +++ b/arch/riscv/kernel/suspend.c @@ -43,6 +43,8 @@ void suspend_save_csrs(struct suspend_context *context) void suspend_restore_csrs(struct suspend_context *context) { + riscv_clear_hypervisor_csr(); + csr_write(CSR_SCRATCH, 0); if (riscv_has_extension_unlikely(RISCV_ISA_EXT_XLINUXENVCFG)) csr_write(CSR_ENVCFG, context->envcfg); -- cgit v1.2.3 From a510e1ae8c9030750c2edf7b0bc9b186cd8c7a06 Mon Sep 17 00:00:00 2001 From: Wei-Jie Hung Date: Mon, 5 Oct 2026 19:02:38 -0600 Subject: riscv: patch: fix handling of bpf-jit execmem addresses Commit 8718e5a3090b ("riscv: patch: skip fixmap mapping when kernel text is already writable") gated the core_kernel_text() branch of patch_map() on CONFIG_STRICT_KERNEL_RWX, and explicitly left the vmalloc branch unchanged. That branch has a separate problem. That branch only creates a temporary writable fixmap alias when CONFIG_STRICT_MODULE_RWX is enabled, and otherwise assumes the target is directly writable and returns the address unchanged. That assumption no longer holds: bpf_prog_pack_alloc calls set_memory_rox() on every pack unconditionally in alloc_new_pack(), independently of any CONFIG_STRICT_*_RWX option, so BPF text in the vmalloc area is read-only regardless of what CONFIG_STRICT_MODULE_RWX says. With CONFIG_STRICT_MODULE_RWX=n, patch_map() returns the read-only address directly, the subsequent copy_to_kernel_nofault() takes a page fault and returns -EFAULT. bpf_arch_text_copy() turns that into -EINVAL, which propagates through bpf_jit_binary_pack_finalize() to the WARN_ON() in bpf_int_jit_compile() and leaves the program un-JITed. Note that BPF cannot be fixed the way kprobes was in commit bdc46e507b59 ("riscv: mm: make EXECMEM_KPROBES writable without CONFIG_STRICT_MODULE_RWX"). EXECMEM_BPF is already PAGE_KERNEL, i.e. writable at allocation time; the read-only mapping is established afterwards by generic code in alloc_new_pack(), which arch code cannot influence. The only place this can be handled is patch_map(). This is the same problem that was fixed on arm64 by commit b1480ed230ac ("arm64: patching: fix handling of execmem addresses"), and the fix is the same: CONFIG_EXECMEM is what actually tracks whether the vmalloc area can hold executable memory that needs a temporary alias to be written. CONFIG_BPF_JIT, CONFIG_KPROBES and CONFIG_MODULES all select it. Note that this is not limited to CONFIG_MODULES=n. Unlike arm64, riscv selects CONFIG_ARCH_OPTIONAL_KERNEL_RWX, so CONFIG_STRICT_MODULE_RWX can be disabled with CONFIG_MODULES=y as well, and the bug is reachable in that configuration too. The !CONFIG_MMU build is unaffected: it has its own __patch_insn_set() and __patch_insn_write() implementations and never calls patch_map(). Fixes: 2c9e5d4a0082 ("bpf: remove CONFIG_BPF_JIT dependency on CONFIG_MODULES of") Signed-off-by: Wei-Jie Hung Reviewed-by: Pu Lehui Tested-by: Pu Lehui Link: https://patch.msgid.link/20260914115607.350097-1-imbigking12@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/kernel/patch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/riscv/kernel/patch.c b/arch/riscv/kernel/patch.c index 2239c28981bc..2b5adf01c550 100644 --- a/arch/riscv/kernel/patch.c +++ b/arch/riscv/kernel/patch.c @@ -48,7 +48,7 @@ static __always_inline void *patch_map(void *addr, const unsigned int fixmap) if (!IS_ENABLED(CONFIG_STRICT_KERNEL_RWX)) return addr; phys = __pa_symbol(addr); - } else if (IS_ENABLED(CONFIG_STRICT_MODULE_RWX)) { + } else if (IS_ENABLED(CONFIG_EXECMEM)) { struct page *page = vmalloc_to_page(addr); BUG_ON(!page); -- cgit v1.2.3 From 282f4a8aa95c5a01b96e55eb5da297560e5f2986 Mon Sep 17 00:00:00 2001 From: Shao Mingyin Date: Mon, 5 Oct 2026 19:02:38 -0600 Subject: riscv: lib: Fix ZBB strnlen wrap-around regression on huge counts The aligned scan boundary is derived from the last valid byte, (s + count - 1). When count is huge (e.g. SIZE_MAX, which FORTIFY strcat/strlcat pass when the destination size is not known at compile time), s + count wraps around and the boundary lands before s, so the ZBB path returns a bogus length. The original implementation (5ba15d419fab) had the same wrap-around in its (s + count) & ~7 boundary computation; after 5d588c684833 the wrapped boundary is caught by the pre-loop guard "bgeu t0, t4, 2f", which then always exits for aligned strings of 8 or more characters and strnlen() returns 8 instead of the real length. This silently truncates strings built by fortified strcat: the dm sysfs name attribute shows "live-bas" instead of "live-base", the truncated name pollutes the udev database, and blivet/anaconda (as well as LVM/dm-crypt/multipath userspace) break on RISC-V systems. Detect the wrap-around and saturate the boundary to the top of the address space, making the scan equivalent to strlen(). The saturation clamps the increment to ~s, so it stays branchless and wrap-free: s + min(count - 1, ~s) == saturating_add(s, count - 1) Normal counts are unaffected. Fixes: 5ba15d419fab ("riscv: lib: add strnlen() implementation") Cc: stable@vger.kernel.org Suggested-by: David Laight Suggested-by: Qingfang Deng Signed-off-by: Shao Mingyin Acked-by: Michael Neuling Tested-by: Troy Mitchell Reviewed-by: Aurelien Jarno Link: https://patch.msgid.link/20260915152656708z04s4oSYY2BGj34F36RZa@zte.com.cn Signed-off-by: Paul Walmsley --- arch/riscv/lib/strnlen.S | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/arch/riscv/lib/strnlen.S b/arch/riscv/lib/strnlen.S index a8911605c248..5b3bbf0a3098 100644 --- a/arch/riscv/lib/strnlen.S +++ b/arch/riscv/lib/strnlen.S @@ -87,9 +87,25 @@ strnlen_zbb: * Aligned boundary. Use the address of the last valid byte * (s + count - 1) to avoid loading a word past the count * boundary in the loop below. count == 0 is handled above. + * + * Saturate the boundary when s + count would wrap around (very + * large counts, e.g. SIZE_MAX passed by FORTIFY strcat/strlcat + * with a destination whose size is unknown at compile time). + * Without this, the wrapped boundary lands before s and the + * pre-loop guard below always exits, returning a truncated + * length. + * + * Clamping the increment to ~s (== SIZE_MAX - s) keeps the + * computation branchless and wrap-free: + * + * s + min(count - 1, ~s) == saturating_add(s, count - 1) + * + * Saturating makes the scan equivalent to strlen(). */ - add t4, a0, a1 - addi t4, t4, -1 + addi t4, a1, -1 /* count - 1 */ + not t1, a0 /* SIZE_MAX - s */ + minu t4, t4, t1 /* clamp, so s + t4 can never wrap */ + add t4, a0, t4 /* saturated s + count - 1 */ andi t4, t4, -SZREG /* Get the first word. */ -- cgit v1.2.3 From 78e04810542da416d3bead707b60cec773f1524d Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 20 Sep 2026 11:49:53 +0800 Subject: scsi: ufs: mediatek: Handle mPHY power-on failures The MediaTek helper enables VA09 before powering on the mPHY, but ignores the PHY result. A failure can leave VA09 enabled and publish mphy_powered_on as true, so a later power-on attempt is skipped. Check phy_power_on(), attempt to unwind VA09 on failure, and leave the state flag unchanged. Propagate the helper result from host initialization as well as its existing resume caller. Keep the original PHY error if the supply rollback also fails, and report that rollback separately. The issue was found by our static-analysis tool. Fixes: 561e3a8726b2 ("scsi: ufs-mediatek: Fix unbalanced clock on/off") Fixes: cf137b3ea49a ("scsi: ufs-mediatek: Support VA09 regulator operations") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Reviewed-by: Peter Wang Reviewed-by: Stanley Jhu Link: https://patch.msgid.link/20260920034953.19535-1-hppiscas@163.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/host/ufs-mediatek.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/ufs/host/ufs-mediatek.c b/drivers/ufs/host/ufs-mediatek.c index 814c1b7343b9..12a6d4138e44 100644 --- a/drivers/ufs/host/ufs-mediatek.c +++ b/drivers/ufs/host/ufs-mediatek.c @@ -509,6 +509,7 @@ static int ufs_mtk_mphy_power_on(struct ufs_hba *hba, bool on) struct ufs_mtk_host *host = ufshcd_get_variant(hba); struct phy *mphy = host->mphy; struct arm_smccc_res res; + int err; int ret = 0; if (!mphy || !(on ^ host->mphy_powered_on)) @@ -523,7 +524,18 @@ static int ufs_mtk_mphy_power_on(struct ufs_hba *hba, bool on) usleep_range(200, 210); ufs_mtk_va09_pwr_ctrl(res, 1); } - phy_power_on(mphy); + ret = phy_power_on(mphy); + if (ret) { + if (ufs_mtk_is_va09_supported(hba)) { + ufs_mtk_va09_pwr_ctrl(res, 0); + err = regulator_disable(host->reg_va09); + if (err) + dev_err(hba->dev, + "failed to unwind va09: %d\n", err); + } + dev_err(hba->dev, "failed to enable mphy: %d\n", ret); + return ret; + } } else { phy_power_off(mphy); if (ufs_mtk_is_va09_supported(hba)) { @@ -1295,7 +1307,9 @@ static int ufs_mtk_init(struct ufs_hba *hba) * * Enable phy clocks specifically here. */ - ufs_mtk_mphy_power_on(hba, true); + err = ufs_mtk_mphy_power_on(hba, true); + if (err) + goto out_variant_clear; if (ufs_mtk_is_rtff_mtcmos(hba)) { /* First Restore here, to avoid backup unexpected value */ -- cgit v1.2.3 From 27c0f718d3b3c99e6d315308e95d987d0382a8a8 Mon Sep 17 00:00:00 2001 From: Ahmed Abdelhaleem Ahmed Date: Tue, 22 Sep 2026 15:46:46 +0000 Subject: scsi: ch: Do not keep references to data transfer element devices ch_readconfig() looks up the scsi_device of every data transfer element whose SCSI id the changer reports in READ ELEMENT STATUS, and stores it in ch->dt[]. scsi_device_lookup() takes a reference, and nothing ever drops it: ch_destroy() frees the array with kfree(). ch->dt[] is read nowhere else - it only supplies the vendor, model and revision printed in the same loop. Once such a drive is removed, its scsi_device can never be released. It stays on the host's device list at its address, so a new device there is refused by anything that walks the list - target_core_pscsi reports "scsi_device_get() failed for H:C:T:L" - and the low-level driver's module can no longer be unloaded. Only a reboot recovers. It shows with any changer that reports its drives' ids; with the mhvtl virtual library (IBM 3573-TL personality), each create and remove of a library with four drives leaves four references behind, counted by the module's use count in lsmod. With ch not bound the count is unchanged, and with this patch applied it is unchanged too. Drop the reference as soon as the name has been printed, and remove the now unused dt[] array. That also removes the array leaked when ch_probe() fails after ch_readconfig(). The same leak was reported with an RFC patch in 2022, which was not merged. Fixes: daa6eda65a53 ("[SCSI] add scsi changer driver") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/linux-scsi/20220719075442.6215-1-yanghao_ht@163.com/ Signed-off-by: Ahmed Abdelhaleem Ahmed Reviewed-by: Laurence Oberman Fixes: daa6eda65a53 ("[SCSI] add scsi changer driver") Link: https://patch.msgid.link/20260929-ch-dt-leak-v2-1-ddda8d635dca@gmail.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/scsi/ch.c | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/drivers/scsi/ch.c b/drivers/scsi/ch.c index 4010fdbf813c..c36539c83ed1 100644 --- a/drivers/scsi/ch.c +++ b/drivers/scsi/ch.c @@ -112,7 +112,6 @@ typedef struct { int minor; char name[8]; struct scsi_device *device; - struct scsi_device **dt; /* ptrs to data transfer elements */ u_int firsts[CH_TYPES]; u_int counts[CH_TYPES]; u_int voltags; @@ -363,15 +362,10 @@ ch_readconfig(scsi_changer *ch) vendor_labels[i]); } - /* look up the devices of the data transfer elements */ - ch->dt = kzalloc_objs(*ch->dt, ch->counts[CHET_DT]); - - if (!ch->dt) { - kfree(buffer); - return -ENOMEM; - } - + /* report the devices of the data transfer elements */ for (elem = 0; elem < ch->counts[CHET_DT]; elem++) { + struct scsi_device *sdev; + id = -1; lun = 0; if (elem < CH_DT_MAX && -1 != dt_id[elem]) { @@ -387,10 +381,8 @@ ch_readconfig(scsi_changer *ch) VPRINTK(KERN_INFO, "dt 0x%x: ",elem+ch->firsts[CHET_DT]); if (data[6] & 0x80) { VPRINTK(KERN_CONT, "not this SCSI bus\n"); - ch->dt[elem] = NULL; } else if (0 == (data[6] & 0x30)) { VPRINTK(KERN_CONT, "ID/LUN unknown\n"); - ch->dt[elem] = NULL; } else { id = ch->device->id; lun = 0; @@ -400,18 +392,16 @@ ch_readconfig(scsi_changer *ch) } if (-1 != id) { VPRINTK(KERN_CONT, "ID %i, LUN %i, ",id,lun); - ch->dt[elem] = - scsi_device_lookup(ch->device->host, - ch->device->channel, - id,lun); - if (!ch->dt[elem]) { + sdev = scsi_device_lookup(ch->device->host, + ch->device->channel, + id, lun); + if (!sdev) { /* should not happen */ VPRINTK(KERN_CONT, "Huh? device not found!\n"); } else { VPRINTK(KERN_CONT, "name: %8.8s %16.16s %4.4s\n", - ch->dt[elem]->vendor, - ch->dt[elem]->model, - ch->dt[elem]->rev); + sdev->vendor, sdev->model, sdev->rev); + scsi_device_put(sdev); } } } @@ -575,7 +565,6 @@ static void ch_destroy(struct kref *ref) scsi_changer *ch = container_of(ref, scsi_changer, ref); ch->device = NULL; - kfree(ch->dt); kfree(ch); } -- cgit v1.2.3 From 375f3a5691dd46b5d05b90a8968872ec8b2248c5 Mon Sep 17 00:00:00 2001 From: Stanley Jhu Date: Mon, 28 Sep 2026 11:58:15 +0800 Subject: scsi: ufs: core: Avoid unsafe MMIO reads in ufshcd_mcq_compl_all_cqes_lock() During MCQ host reset, ufshcd_host_reset_and_restore() stops the host controller via ufshcd_hba_stop() (HCE = 0) before calling ufshcd_complete_requests(hba, true) -> ufshcd_mcq_compl_pending_transfer(hba, true) -> ufshcd_mcq_force_compl_one() -> ufshcd_mcq_compl_all_cqes_lock(). Because ufshcd_mcq_force_compl_one() is its sole caller, ufshcd_mcq_compl_all_cqes_lock() always runs with HCE = 0. Despite the comment above ufshcd_mcq_compl_all_cqes_lock() stating that reading CQTPy may not be safe with the controller disabled, the function still calls ufshcd_mcq_update_cq_tail_slot() at the end of its sweep: 1. Unsafe CQTPy MMIO read: Calling ufshcd_mcq_update_cq_tail_slot() at the end of the sweep reads CQTPy over MMIO while HCE = 0, directly contradicting the function's documented contract (commit 1373df88d535 ("scsi: ufs: core: Add a comment block above ufshcd_mcq_compl_all_cqes_lock()")) that reading CQTPy may not be safe with the controller disabled. 2. Spurious error logs on empty slots: Sweeping all max_entries slots visits empty entries where command_desc_base_addr is 0, causing ufshcd_mcq_process_cqe() to log unguarded dev_err(hba->dev, "Abnormal CQ entry!\n") messages. Fix both issues in ufshcd_mcq_compl_all_cqes_lock(): - Remove the ufshcd_mcq_update_cq_tail_slot() call and the redundant hwq->cq_head_slot = hwq->cq_tail_slot assignment without replacement. The two indices are already equal after the sweep: they are equal when the sweep starts, since ufshcd_mcq_poll_cqe_lock() consumes entries until cq_head_slot reaches cq_tail_slot, and the sweep advances cq_head_slot by exactly one full ring. Both indices are also reinitialized before the queue is reused. - Extract ufshcd_mcq_compl_cqe() and invoke it only on non-empty slots during full-ring sweeps, keeping "Abnormal CQ entry!" logging strictly for unexpected empty entries in ufshcd_mcq_poll_cqe_lock(). Fixes: ab248643d3d6 ("scsi: ufs: core: Add error handling for MCQ mode") Cc: stable@vger.kernel.org Reviewed-by: Peter Wang Signed-off-by: Stanley Jhu Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260928035816.1294326-2-stanleyjhu@google.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufs-mcq.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/drivers/ufs/core/ufs-mcq.c b/drivers/ufs/core/ufs-mcq.c index 8106d55f4041..df293b66350d 100644 --- a/drivers/ufs/core/ufs-mcq.c +++ b/drivers/ufs/core/ufs-mcq.c @@ -312,20 +312,24 @@ static int ufshcd_mcq_get_tag(struct ufs_hba *hba, struct cq_entry *cqe) UFSHCD_NUM_RESERVED; } +static void ufshcd_mcq_compl_cqe(struct ufs_hba *hba, struct cq_entry *cqe) +{ + int tag = ufshcd_mcq_get_tag(hba, cqe); + + ufshcd_compl_one_cqe(hba, tag, cqe); + /* After processing the CQE, mark it as an empty (invalid) entry. */ + cqe->command_desc_base_addr = 0; +} + static void ufshcd_mcq_process_cqe(struct ufs_hba *hba, struct ufs_hw_queue *hwq) { struct cq_entry *cqe = ufshcd_mcq_cur_cqe(hwq); - if (cqe->command_desc_base_addr) { - int tag = ufshcd_mcq_get_tag(hba, cqe); - - ufshcd_compl_one_cqe(hba, tag, cqe); - /* After processed the cqe, mark it empty (invalid) entry */ - cqe->command_desc_base_addr = 0; - } else { + if (cqe->command_desc_base_addr) + ufshcd_mcq_compl_cqe(hba, cqe); + else dev_err(hba->dev, "Abnormal CQ entry!\n"); - } } /* @@ -333,7 +337,7 @@ static void ufshcd_mcq_process_cqe(struct ufs_hba *hba, * controller disabled (HCE = 0). Reading host controller registers, e.g. the * CQ tail pointer (CQTPy), may not be safe with the host controller disabled. * Hence, iterate over all completion queue entries. This won't result in - * double completions because ufshcd_mcq_process_cqe() clears a CQE after it + * double completions because ufshcd_mcq_compl_cqe() clears a CQE after it * has been processed. */ void ufshcd_mcq_compl_all_cqes_lock(struct ufs_hba *hba, @@ -344,13 +348,13 @@ void ufshcd_mcq_compl_all_cqes_lock(struct ufs_hba *hba, spin_lock_irqsave(&hwq->cq_lock, flags); while (entries > 0) { - ufshcd_mcq_process_cqe(hba, hwq); + struct cq_entry *cqe = ufshcd_mcq_cur_cqe(hwq); + + if (cqe->command_desc_base_addr) + ufshcd_mcq_compl_cqe(hba, cqe); ufshcd_mcq_inc_cq_head_slot(hwq); entries--; } - - ufshcd_mcq_update_cq_tail_slot(hwq); - hwq->cq_head_slot = hwq->cq_tail_slot; spin_unlock_irqrestore(&hwq->cq_lock, flags); } -- cgit v1.2.3 From 469fa055664bfd8717b087a80d59c03055789c17 Mon Sep 17 00:00:00 2001 From: Stanley Jhu Date: Mon, 28 Sep 2026 11:58:16 +0800 Subject: scsi: ufs: core: Decouple CQ sweep from request iterator in MCQ In MCQ mode, ufshcd_mcq_compl_pending_transfer() uses blk_mq_tagset_busy_iter() to iterate over busy requests during error recovery and host reset. However, both iterator callbacks perform whole-queue operations redundantly for each visited request: - force_compl == true: ufshcd_mcq_force_compl_one() calls ufshcd_mcq_compl_all_cqes_lock() on every busy request, sweeping the entire completion ring (hwq->max_entries slots) once per active request under spin_lock_irqsave even though the first sweep already cleared all completion entries. - force_compl == false: ufshcd_mcq_compl_one() acquires cq_lock and polls CQTPy over MMIO via ufshcd_mcq_poll_cqe_lock() for every busy request without doing any per-request work. Sweep or poll each hardware queue (hba->uhq[i]) once at the start of ufshcd_mcq_compl_pending_transfer(). When force_compl is true, run blk_mq_tagset_busy_iter() afterward to complete residual in-flight requests with DID_REQUEUE, and remove the now-unused ufshcd_mcq_compl_one() callback. Fixes: ab248643d3d6 ("scsi: ufs: core: Add error handling for MCQ mode") Cc: stable@vger.kernel.org Reviewed-by: Bart Van Assche Signed-off-by: Stanley Jhu Reviewed-by: Peter Wang Link: https://patch.msgid.link/20260928035816.1294326-3-stanleyjhu@google.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 79b6a2d9b522..ca8329052cbc 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -6044,8 +6044,6 @@ static bool ufshcd_mcq_force_compl_one(struct request *rq, void *priv) if (blk_mq_is_reserved_rq(rq) || !hwq) return true; - ufshcd_mcq_compl_all_cqes_lock(hba, hwq); - /* * For those cmds of which the cqes are not present in the cq, complete * them explicitly. @@ -6061,19 +6059,6 @@ static bool ufshcd_mcq_force_compl_one(struct request *rq, void *priv) return true; } -static bool ufshcd_mcq_compl_one(struct request *rq, void *priv) -{ - struct scsi_device *sdev = rq->q->queuedata; - struct Scsi_Host *shost = sdev->host; - struct ufs_hba *hba = shost_priv(shost); - struct ufs_hw_queue *hwq = ufshcd_mcq_req_to_hwq(hba, rq); - - if (!blk_mq_is_reserved_rq(rq) && hwq) - ufshcd_mcq_poll_cqe_lock(hba, hwq); - - return true; -} - /** * ufshcd_mcq_compl_pending_transfer - MCQ mode function. It is * invoked from the error handler context or ufshcd_host_reset_and_restore() @@ -6088,10 +6073,18 @@ static bool ufshcd_mcq_compl_one(struct request *rq, void *priv) static void ufshcd_mcq_compl_pending_transfer(struct ufs_hba *hba, bool force_compl) { - blk_mq_tagset_busy_iter(&hba->host->tag_set, - force_compl ? ufshcd_mcq_force_compl_one : - ufshcd_mcq_compl_one, - NULL); + int i; + + for (i = 0; i < hba->nr_hw_queues; i++) { + if (force_compl) + ufshcd_mcq_compl_all_cqes_lock(hba, &hba->uhq[i]); + else + ufshcd_mcq_poll_cqe_lock(hba, &hba->uhq[i]); + } + + if (force_compl) + blk_mq_tagset_busy_iter(&hba->host->tag_set, + ufshcd_mcq_force_compl_one, NULL); } /** -- cgit v1.2.3 From e130c54fbb603f9aeacf5029789b4a2c8c48f416 Mon Sep 17 00:00:00 2001 From: Han Gao Date: Thu, 1 Oct 2026 02:22:50 +0800 Subject: spi: sg2044-nor: Return transfer errors The transfer helpers return errors, but both dispatchers discard them. Return these errors to the SPI memory layer instead of reporting success. Fixes: de16c322eefb ("spi: sophgo: add SG2044 SPI NOR controller driver") Signed-off-by: Han Gao Link: https://patch.msgid.link/20261001-fix-sg204xspi-v2-1-014dbaf22b5e@iscas.ac.cn Signed-off-by: Mark Brown --- drivers/spi/spi-sg2044-nor.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/spi/spi-sg2044-nor.c b/drivers/spi/spi-sg2044-nor.c index f3bcb8a1b92b..ff026ca0f766 100644 --- a/drivers/spi/spi-sg2044-nor.c +++ b/drivers/spi/spi-sg2044-nor.c @@ -307,15 +307,15 @@ static ssize_t sg2044_spifmc_tran_cmd(struct sg2044_spifmc *spifmc, return 0; } -static void sg2044_spifmc_trans(struct sg2044_spifmc *spifmc, - const struct spi_mem_op *op) +static int sg2044_spifmc_trans(struct sg2044_spifmc *spifmc, + const struct spi_mem_op *op) { if (op->data.dir == SPI_MEM_DATA_IN) - sg2044_spifmc_read(spifmc, op); + return sg2044_spifmc_read(spifmc, op); else if (op->data.dir == SPI_MEM_DATA_OUT) - sg2044_spifmc_write(spifmc, op); + return sg2044_spifmc_write(spifmc, op); else - sg2044_spifmc_tran_cmd(spifmc, op); + return sg2044_spifmc_tran_cmd(spifmc, op); } static ssize_t sg2044_spifmc_trans_reg(struct sg2044_spifmc *spifmc, @@ -389,19 +389,20 @@ static int sg2044_spifmc_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { struct sg2044_spifmc *spifmc; + int ret; spifmc = spi_controller_get_devdata(mem->spi->controller); mutex_lock(&spifmc->lock); if (op->addr.nbytes == 0) - sg2044_spifmc_trans_reg(spifmc, op); + ret = sg2044_spifmc_trans_reg(spifmc, op); else - sg2044_spifmc_trans(spifmc, op); + ret = sg2044_spifmc_trans(spifmc, op); mutex_unlock(&spifmc->lock); - return 0; + return ret; } static const struct spi_controller_mem_ops sg2044_spifmc_mem_ops = { -- cgit v1.2.3 From 61d44a2c07b2ad3f7db167f17ff2461ffe78441c Mon Sep 17 00:00:00 2001 From: Han Gao Date: Thu, 1 Oct 2026 02:22:51 +0800 Subject: spi: sg2044-nor: Honor SPI clock limits The fixed divider ignores device and operation clock limits. Calculate the divider before each transfer so SCK does not exceed either limit. Advertise the controller maximum speed so the SPI core supplies a default when the device omits its maximum speed. Use the fastest divider when no speed limit is supplied. Fixes: de16c322eefb ("spi: sophgo: add SG2044 SPI NOR controller driver") Signed-off-by: Han Gao Link: https://patch.msgid.link/20261001-fix-sg204xspi-v2-2-014dbaf22b5e@iscas.ac.cn Signed-off-by: Mark Brown --- drivers/spi/spi-sg2044-nor.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/drivers/spi/spi-sg2044-nor.c b/drivers/spi/spi-sg2044-nor.c index ff026ca0f766..bc9a8ee9bce4 100644 --- a/drivers/spi/spi-sg2044-nor.c +++ b/drivers/spi/spi-sg2044-nor.c @@ -385,6 +385,29 @@ static ssize_t sg2044_spifmc_trans_reg(struct sg2044_spifmc *spifmc, return 0; } +static int sg2044_spifmc_set_speed(struct sg2044_spifmc *spifmc, + unsigned int speed_hz) +{ + unsigned long rate = clk_get_rate(spifmc->clk); + u64 div; + u32 reg; + + if (!rate) + return -EINVAL; + + /* SCK = HCLK / (2 * (SckDiv + 1)); round down the resulting SCK. */ + div = speed_hz ? DIV_ROUND_UP_ULL(rate, 2ULL * speed_hz) : 1; + if (div > SPIFMC_CTRL_SCK_DIV_MASK + 1) + return -EINVAL; + + reg = readl(spifmc->io_base + SPIFMC_CTRL); + reg &= ~SPIFMC_CTRL_SCK_DIV_MASK; + reg |= div - 1; + writel(reg, spifmc->io_base + SPIFMC_CTRL); + + return 0; +} + static int sg2044_spifmc_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { @@ -395,11 +418,18 @@ static int sg2044_spifmc_exec_op(struct spi_mem *mem, mutex_lock(&spifmc->lock); + ret = sg2044_spifmc_set_speed(spifmc, op->max_freq ? + min(op->max_freq, mem->spi->max_speed_hz) : + mem->spi->max_speed_hz); + if (ret) + goto out_unlock; + if (op->addr.nbytes == 0) ret = sg2044_spifmc_trans_reg(spifmc, op); else ret = sg2044_spifmc_trans(spifmc, op); +out_unlock: mutex_unlock(&spifmc->lock); return ret; @@ -409,6 +439,10 @@ static const struct spi_controller_mem_ops sg2044_spifmc_mem_ops = { .exec_op = sg2044_spifmc_exec_op, }; +static const struct spi_controller_mem_caps sg2044_spifmc_mem_caps = { + .per_op_freq = true, +}; + static void sg2044_spifmc_init(struct sg2044_spifmc *spifmc) { u32 tran_csr; @@ -448,6 +482,10 @@ static int sg2044_spifmc_probe(struct platform_device *pdev) if (IS_ERR(spifmc->clk)) return dev_err_probe(dev, PTR_ERR(spifmc->clk), "Cannot get and enable AHB clock\n"); + ctrl->max_speed_hz = clk_get_rate(spifmc->clk) / 2; + if (!ctrl->max_speed_hz) + return dev_err_probe(dev, -EINVAL, "Invalid AHB clock rate\n"); + spifmc->dev = &pdev->dev; spifmc->ctrl = ctrl; @@ -459,6 +497,7 @@ static int sg2044_spifmc_probe(struct platform_device *pdev) ctrl->bits_per_word_mask = SPI_BPW_MASK(8); ctrl->auto_runtime_pm = false; ctrl->mem_ops = &sg2044_spifmc_mem_ops; + ctrl->mem_caps = &sg2044_spifmc_mem_caps; ctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL | SPI_RX_QUAD | SPI_TX_QUAD; ret = devm_mutex_init(dev, &spifmc->lock); -- cgit v1.2.3 From 28c2388ee24cdbefbcc458fa41ef7bffaaa1cc83 Mon Sep 17 00:00:00 2001 From: Abhishek Ojha Date: Tue, 1 Sep 2026 21:07:01 -0400 Subject: regulator: ltc3676: don't read the write-only HRST and CLIRQ registers HRST (0x1e) and CLIRQ (0x1f) are write-only command strobes. Writing HRST triggers a hard reset of the chip and writing CLIRQ clears the interrupt; reading them returns nothing useful, and the read itself fires the command. The result is that a plain register dump resets the board: cat /sys/kernel/debug/regmap/2-003c/registers walks every readable register, hits HRST, and the board reboots. Split the callback back in two so HRST and CLIRQ stay writable but are no longer readable. The readable range is left unchanged (LTC3676_BUCK1 ... LTC3676_IRQSTAT). Fixes: 37b918a034fe ("regulator: Add LTC3676 support") Cc: stable@vger.kernel.org Signed-off-by: Abhishek Ojha Link: https://patch.msgid.link/20260902010701.3360310-1-Abhishek.ojha@savoirfairelinux.com Signed-off-by: Mark Brown --- drivers/regulator/ltc3676.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/regulator/ltc3676.c b/drivers/regulator/ltc3676.c index 307c6f7a155d..d67cee4e0be0 100644 --- a/drivers/regulator/ltc3676.c +++ b/drivers/regulator/ltc3676.c @@ -232,7 +232,7 @@ static const struct regulator_desc ltc3676_regulators[LTC3676_NUM_REGULATORS] = LTC3676_FIXED_REG(LDO4, ldo4, LDOB, 2), }; -static bool ltc3676_readable_writeable_reg(struct device *dev, unsigned int reg) +static bool ltc3676_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { case LTC3676_BUCK1 ... LTC3676_IRQSTAT: @@ -243,6 +243,15 @@ static bool ltc3676_readable_writeable_reg(struct device *dev, unsigned int reg) return false; } +static bool ltc3676_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTC3676_BUCK1 ... LTC3676_IRQSTAT: + return true; + } + return false; +} + static bool ltc3676_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -255,8 +264,8 @@ static bool ltc3676_volatile_reg(struct device *dev, unsigned int reg) static const struct regmap_config ltc3676_regmap_config = { .reg_bits = 8, .val_bits = 8, - .writeable_reg = ltc3676_readable_writeable_reg, - .readable_reg = ltc3676_readable_writeable_reg, + .writeable_reg = ltc3676_writeable_reg, + .readable_reg = ltc3676_readable_reg, .volatile_reg = ltc3676_volatile_reg, .max_register = LTC3676_CLIRQ, .use_single_read = true, -- cgit v1.2.3 From 405290be962b32eec842776fbe3bdf81f15b1676 Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Tue, 6 Oct 2026 14:02:00 +0000 Subject: riscv: futex: untag the user pointer before the atomic access access_ok() checks untagged_addr(uaddr), but arch_futex_atomic_op_inuser() and futex_atomic_cmpxchg_inatomic() hand the raw uaddr to the inline asm amoswap/amoadd/.../lr.w+sc.w through the "+m" (*uaddr) operand. When the tagged address ABI is enabled (CONFIG_RISCV_ISA_SUPM, prctl PR_SET_TAGGED_ADDR_CTRL with PMLEN != 0), those two addresses differ: a user pointer whose top PMLEN bits hold a tag passes access_ok() after untagging, but the atomic is then performed on the still-tagged raw address. Supervisor-mode data accesses are not subject to the U-mode pointer masking (that is governed by menvcfg.PMM, which the kernel does not set), so hardware does not strip the tag for the kernel's own access. With Sv57 and PMLEN=16 the tag bits overlap the canonical-address bits, so a tagged pointer can name a canonical kernel virtual address (e.g. in the linear map) whose untagged form is a valid user address. An unprivileged process can thus make FUTEX_WAKE_OP perform an atomic read-modify-write on an arbitrary kernel address, with the matching FUTEX_OP_CMP_* result serving as a read oracle. get_user()/put_user()/raw_copy_{to,from}_user() already untag the pointer after the access_ok() check; do the same in the futex helpers so the atomic operates on the address that was actually validated. Fixes: 2e1743085887 ("riscv: Add support for the tagged address ABI") Reviewed-by: Samuel Holland Signed-off-by: Ziyi Guo Link: https://patch.msgid.link/20261006140200.877263-1-guoziyi114@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/futex.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/riscv/include/asm/futex.h b/arch/riscv/include/asm/futex.h index 90c86b115e00..6f3bc9bdb85e 100644 --- a/arch/riscv/include/asm/futex.h +++ b/arch/riscv/include/asm/futex.h @@ -40,6 +40,7 @@ arch_futex_atomic_op_inuser(int op, int oparg, int *oval, u32 __user *uaddr) if (!access_ok(uaddr, sizeof(u32))) return -EFAULT; + uaddr = untagged_addr(uaddr); switch (op) { case FUTEX_OP_SET: @@ -82,6 +83,7 @@ futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr, if (!access_ok(uaddr, sizeof(u32))) return -EFAULT; + uaddr = untagged_addr(uaddr); __enable_user_access(); __asm__ __volatile__ ( -- cgit v1.2.3 From 298510bbc7c3017c0faf481c6ea66f1065b657b4 Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Thu, 17 Sep 2026 15:48:43 +0800 Subject: riscv: errata: select RISCV_ALTERNATIVE_EARLY for THEAD GHOSTWRITE errata_probe_ghostwrite() only marks the CPU as vulnerable when it is called at the RISCV_ALTERNATIVES_EARLY_BOOT stage, and the mitigation in ghostwrite_enable_mitigation() only takes effect if the vulnerable state was recorded before the boot alternatives are patched. The early boot stage is only executed when CONFIG_RISCV_ALTERNATIVE_EARLY is enabled, which currently is only selected by ERRATA_THEAD_MAE. In a kernel built with ERRATA_THEAD_GHOSTWRITE=y but ERRATA_THEAD_MAE=n, the early alternatives never run, so ghostwrite_state is never set to VULNERABLE: ghostwrite_enable_mitigation() returns false, xtheadvector remains enabled, and sysfs reports "Not affected" on a vulnerable T-Head C9xx CPU. Select RISCV_ALTERNATIVE_EARLY from ERRATA_THEAD_GHOSTWRITE so that the vulnerability is detected in the early boot stage and the default mitigation is applied. Fixes: 4bf97069239b ("riscv: Add ghostwrite vulnerability") Signed-off-by: Zongmin Zhou Link: https://patch.msgid.link/20260917074843.52716-1-min_halo@163.com Cc: stable@vger.kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/Kconfig.errata | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/riscv/Kconfig.errata b/arch/riscv/Kconfig.errata index a6aac8774f25..622ade116a2d 100644 --- a/arch/riscv/Kconfig.errata +++ b/arch/riscv/Kconfig.errata @@ -160,6 +160,7 @@ config ERRATA_THEAD_PMU config ERRATA_THEAD_GHOSTWRITE bool "Apply T-Head Ghostwrite errata" depends on ERRATA_THEAD && RISCV_ISA_XTHEADVECTOR + select RISCV_ALTERNATIVE_EARLY default y help The T-Head C9xx cores have a vulnerability in the xtheadvector -- cgit v1.2.3 From 07e23d0b6f58eec351e52058b9a0a8af7b019c09 Mon Sep 17 00:00:00 2001 From: Leo Yu-Chi Liang Date: Tue, 6 Oct 2026 21:52:29 -0700 Subject: riscv: fix NR_CPUS range leaking 64-bit default onto 32-bit builds rv32_defconfig is built in two stages: - the generic (64-bit-oriented) "defconfig" target runs first and resolves a concrete NR_CPUS value using the 64BIT default. - then 32-bit.config is merged on top via merge_config.sh, which does not touch NR_CPUS. The final "make olddefconfig" pass sees NR_CPUS=256 is still within the "2 512" range therefore never recalculates it against the 32BIT default of 32, silently keeping the leaked 64-bit-stage value. The NR_CPUS-scaled regions (percpu areas, ...etc) of the resulting CONFIG_NR_CPUS=256 on a 32-bit build bloats and does not fit in the percpu/stack layout for rv32 and hangs early boot on QEMU's virt machine. Fix this by explicitly specify the range for 32/64 and SBI_V01/!SBI_V01. Fixes: 0fa749771993 ("riscv: Raise default NR_CPUS for 64BIT to 256") Signed-off-by: Leo Yu-Chi Liang Link: https://patch.msgid.link/20261007045229.36114-1-leo.liang@sifive.com Signed-off-by: Paul Walmsley --- arch/riscv/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index d6c2dbf8455c..5874ca30f4f6 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -453,9 +453,10 @@ config SMP config NR_CPUS int "Maximum number of CPUs (2-512)" depends on SMP - range 2 512 if !RISCV_SBI_V01 - range 2 32 if RISCV_SBI_V01 && 32BIT + range 2 512 if !RISCV_SBI_V01 && 64BIT + range 2 128 if !RISCV_SBI_V01 && 32BIT range 2 64 if RISCV_SBI_V01 && 64BIT + range 2 32 if RISCV_SBI_V01 && 32BIT default "32" if 32BIT default "64" if RISCV_SBI_V01 && 64BIT default "256" if !RISCV_SBI_V01 && 64BIT -- cgit v1.2.3 From adadf6df0bd55aa40ccbc6519fb443133e103227 Mon Sep 17 00:00:00 2001 From: Chen Pei Date: Thu, 20 Aug 2026 14:32:33 +0800 Subject: riscv: ptrace: reject CFI regset access when extensions are absent riscv_cfi_get() and riscv_cfi_set() do not check whether the Zicfilp or Zicfiss extensions are present. On systems without them, PTRACE_GETREGSET on REGSET_CFI still succeeds and returns a zeroed user_cfi_state, misleading debuggers into believing the register set is available, and PTRACE_SETREGSET silently accepts writes that have no effect (e.g. clearing SR_ELP). Reject the access with -EINVAL when neither branch landing pads nor shadow stack is available to userspace, using the same availability helpers as the prctl path. Fixes: 2af7c9cf021c ("riscv/ptrace: expose riscv CFI status and state via ptrace and in core files") Signed-off-by: Chen Pei Link: https://patch.msgid.link/20260820063233.2567-1-cp0613@linux.alibaba.com Cc: stable@vger.kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/kernel/ptrace.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/riscv/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c index f336a183667e..566856e42f65 100644 --- a/arch/riscv/kernel/ptrace.c +++ b/arch/riscv/kernel/ptrace.c @@ -299,6 +299,9 @@ static int riscv_cfi_get(struct task_struct *target, struct user_cfi_state user_cfi; struct pt_regs *regs; + if (!is_user_lpad_enabled() && !is_user_shstk_enabled()) + return -EINVAL; + memset(&user_cfi, 0, sizeof(user_cfi)); regs = task_pt_regs(target); @@ -337,6 +340,9 @@ static int riscv_cfi_set(struct task_struct *target, struct user_cfi_state user_cfi; struct pt_regs *regs; + if (!is_user_lpad_enabled() && !is_user_shstk_enabled()) + return -EINVAL; + regs = task_pt_regs(target); ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &user_cfi, 0, -1); -- cgit v1.2.3 From 59052a88ba5fe4a7bb34b3a0a42aa5d453d3bb97 Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Wed, 26 Aug 2026 19:57:11 +0800 Subject: riscv: ptrace: Zero-initialize regset buffers before copyin user_regset_copyin() only copies `count` bytes, so a PTRACE_SETREGSET request with a short iov_len leaves the rest of the stack buffer uninitialized. In riscv_vr_set() that garbage is validated and copied into the target's vstate, and in riscv_cfi_set() it can be stored as the target's shadow stack pointer. Zero both buffers, as the get path already does. Fixes: 9300f0043974 ("RISC-V: Add ptrace support for vectors") Fixes: 2af7c9cf021c ("riscv/ptrace: expose riscv CFI status and state via ptrace and in core files") Signed-off-by: Zongmin Zhou Link: https://patch.msgid.link/20260826115711.86861-1-min_halo@163.com Signed-off-by: Paul Walmsley --- arch/riscv/kernel/ptrace.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/riscv/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c index 566856e42f65..a0055743d3ba 100644 --- a/arch/riscv/kernel/ptrace.c +++ b/arch/riscv/kernel/ptrace.c @@ -230,6 +230,7 @@ static int riscv_vr_set(struct task_struct *target, return -ENODATA; /* Copy rest of the vstate except datap */ + memset(&ptrace_vstate, 0, sizeof(ptrace_vstate)); ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &ptrace_vstate, 0, sizeof(struct __riscv_v_regset_state)); if (unlikely(ret)) @@ -345,6 +346,7 @@ static int riscv_cfi_set(struct task_struct *target, regs = task_pt_regs(target); + memset(&user_cfi, 0, sizeof(user_cfi)); ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &user_cfi, 0, -1); if (ret) return ret; -- cgit v1.2.3 From 4d0697f192975dab5985e921989be1d85cf580e9 Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Thu, 1 Oct 2026 13:57:11 +0000 Subject: riscv: entry: gate shadow call stack reload on sstatus.SPP, not tp Under CONFIG_SHADOW_CALL_STACK, handle_exception() reloads the kernel shadow call stack pointer (gp) only when the trap came from userspace. It made that decision with scs_load_current_if_task_changed, which compares the trap-time tp (read back from sscratch into s5) against the current tp: beq \prev, tp, _skip_scs scs_load_current _skip_scs: tp is a user-writable register. A user task that sets tp to the linear-map address of its own task_struct (¤t) before trapping makes the comparison succeed (can achieve by brute force with KASLR enable under our test), so the kernel skips scs_load_current and keeps running with the gp that userspace left in place. Under CONFIG_SHADOW_CALL_STACK gp is the shadow call stack pointer and load_global_pointer is a no-op, so the compiler-emitted "sd ra, -8(gp)" / "ld ra, -8(gp)" in every non-leaf function then write to, and return through, an attacker-controlled address -- an unprivileged kernel arbitrary-write and control-flow-hijack primitive. The poisoned gp is saved to task_struct.scs_sp on return, so the reload is skipped on every subsequent entry as well. Decide "came from userspace" from the hardware sstatus.SPP bit (already in s1 at this point) instead of from tp, mirroring the check that ret_from_exception already uses a few instructions later. SPP is written by hardware on trap entry and cannot be forged from U-mode. Legitimate behaviour is unchanged: user entries (SPP=0) reload the task shadow call stack, nested kernel entries (SPP=1) skip it. Fixes: d1584d791a29 ("riscv: Implement Shadow Call Stack") Signed-off-by: Ziyi Guo Reviewed-by: Sami Tolvanen Link: https://patch.msgid.link/20261001135711.934152-1-guoziyi114@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Paul Walmsley --- arch/riscv/include/asm/scs.h | 17 +++++++++++++---- arch/riscv/kernel/entry.S | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/arch/riscv/include/asm/scs.h b/arch/riscv/include/asm/scs.h index 023a412fe38d..49e40eef4ed7 100644 --- a/arch/riscv/include/asm/scs.h +++ b/arch/riscv/include/asm/scs.h @@ -4,6 +4,7 @@ #ifdef __ASSEMBLER__ #include +#include #ifdef CONFIG_SHADOW_CALL_STACK @@ -22,9 +23,17 @@ REG_L gp, TASK_TI_SCS_SP(tp) .endm -/* Load task_scs_sp(current) to gp, but only if tp has changed. */ -.macro scs_load_current_if_task_changed prev - beq \prev, tp, _skip_scs +/* + * Load task_scs_sp(current) to gp, but only when the trap came from U-mode. + * The source privilege level is taken from the saved sstatus.SPP bit (written + * by hardware on trap entry), not by comparing tp. tp is a user-writable + * register, so gating the shadow call stack reload on it let a user task that + * set tp == ¤t skip the reload and run the kernel with an attacker + * controlled gp (the shadow call stack pointer). + */ +.macro scs_load_current_if_from_user status, tmp + andi \tmp, \status, SR_SPP + bnez \tmp, _skip_scs scs_load_current _skip_scs: .endm @@ -42,7 +51,7 @@ _skip_scs: .endm .macro scs_load_current .endm -.macro scs_load_current_if_task_changed prev +.macro scs_load_current_if_from_user status, tmp .endm .macro scs_save_current .endm diff --git a/arch/riscv/kernel/entry.S b/arch/riscv/kernel/entry.S index d799c4e56f80..61b7a7716697 100644 --- a/arch/riscv/kernel/entry.S +++ b/arch/riscv/kernel/entry.S @@ -208,7 +208,7 @@ SYM_CODE_START(handle_exception) load_global_pointer /* Load the kernel shadow call stack pointer if coming from userspace */ - scs_load_current_if_task_changed s5 + scs_load_current_if_from_user s1, s5 #ifdef CONFIG_RISCV_ISA_V_PREEMPTIVE move a0, sp -- cgit v1.2.3 From d4f25b097e46d82718bb737d4540c99b7ab0a408 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 7 Sep 2026 21:07:50 +0200 Subject: riscv: fix load_unaligned_zeropad() fixup for RV32 The fixup for load_unaligned_zeropad() runs when REG_L crosses into an unmapped page. REG_L is lw on RV32 and ld on RV64, but the fixup assumes an 8-byte load: it rounds the address down to 8 bytes and derives the shift from three address bits. On RV32, a load starting in the last three bytes of a mapped page selects the word four bytes earlier and shifts it by 40, 48, or 56 bits, more than the register width. The caller gets earlier bytes instead of the string tail followed by zeroes. This affects RV32 MMU kernels built with RISCV_EFFICIENT_UNALIGNED_ACCESS=y, which selects DCACHE_WORD_ACCESS and lets dcache name hashing and comparison and strscpy() use this path. Derive the alignment and offset masks from sizeof(data) instead of a fixed 8. This makes RV32 use the correct source word. RV64 code is unchanged: the complete extable.o is byte-for-byte identical before and after. Fixes: d0fdc20b0429 ("riscv: select DCACHE_WORD_ACCESS for efficient unaligned access HW") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Jisheng Zhang Link: https://patch.msgid.link/20260907190750.26303-1-kmehltretter@gmail.com Signed-off-by: Paul Walmsley --- arch/riscv/mm/extable.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/riscv/mm/extable.c b/arch/riscv/mm/extable.c index dd1530af3ef1..e252eba55ede 100644 --- a/arch/riscv/mm/extable.c +++ b/arch/riscv/mm/extable.c @@ -68,8 +68,8 @@ ex_handler_load_unaligned_zeropad(const struct exception_table_entry *ex, addr = regs_get_gpr(regs, reg_addr * sizeof(unsigned long)); - offset = addr & 0x7UL; - addr &= ~0x7UL; + offset = addr & (sizeof(data) - 1); + addr &= ~(sizeof(data) - 1); data = *(unsigned long *)addr >> (offset * 8); -- cgit v1.2.3 From 00765a3467b992d0eb56704a28887256317eb4bd Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 8 Oct 2026 09:00:58 +0800 Subject: spi: dt-bindings: snps,dw-apb-ssi: Add Synaptics sl2610 spi Add the compatible for the Synaptics SL2610 SoC SPI controllers, which are compatible with the Synopsys DesignWare APB SSI. Signed-off-by: Jisheng Zhang Acked-by: Conor Dooley Link: https://patch.msgid.link/20261008010058.28382-1-jszhang@kernel.org Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml index e8d6d5d858b6..41ec69f5dceb 100644 --- a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml +++ b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml @@ -77,6 +77,7 @@ properties: - mscc,ocelot-spi - mscc,jaguar2-spi - sophgo,sg2042-spi + - syna,sl2610-spi - thead,th1520-spi - ultrarisc,dp1000-spi - const: snps,dw-apb-ssi -- cgit v1.2.3 From b59cabf9b4acb65f3f9968087ca0e18010d85076 Mon Sep 17 00:00:00 2001 From: Hengyu Liang Date: Tue, 6 Oct 2026 08:57:32 -0400 Subject: io_uring: do not charge the SQ/CQ rings to RLIMIT_MEMLOCK Commit 8078486e1d53 ("io_uring: use region api for SQ") and commit 81a4058e0cd0 ("io_uring: use region api for CQ") made io_uring_setup() allocate the rings with io_create_region(). However, io_create_region() charges the memory to RLIMIT_MEMLOCK, and the rings had been exempt from that limit since commit 26bfa89e25f4 ("io_uring: place ring SQ/CQ arrays under memcg memory limits"). As of now, a user without CAP_IPC_LOCK gets ENOMEM from io_uring_setup() when their rings exceed the limit, which is 8 MiB by default. PostgreSQL developers have already hit this in their io_uring tests [1]. The issue can be reproduced with a simple liburing program, run as an unprivileged user: #include #include int main(void) { static struct io_uring ring[64]; int i; for (i = 0; i < 64; i++) if (io_uring_queue_init(4096, &ring[i], 0) < 0) break; printf("%d rings\n", i); return 0; } Before those commits (v6.13), it prints "64 rings". After those commits (v6.14), it prints "21 rings". This patch makes io_create_region() take the user to charge, and passes no user for the SQ/CQ rings. Link: https://www.postgresql.org/message-id/flat/667f7dcf-404b-4898-a426-57ca6e0617f6@gmail.com [1] Fixes: 8078486e1d53 ("io_uring: use region api for SQ") Fixes: 81a4058e0cd0 ("io_uring: use region api for CQ") Cc: stable@vger.kernel.org Signed-off-by: Hengyu Liang Link: https://lore.kernel.org/io-uring/20261006125732.3425762-1-hengyul@cs.unc.edu/ Signed-off-by: Jens Axboe --- io_uring/io_uring.c | 10 ++++++---- io_uring/kbuf.c | 2 +- io_uring/memmap.c | 10 +++++----- io_uring/memmap.h | 2 +- io_uring/register.c | 12 +++++++----- io_uring/zcrx.c | 2 +- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index c2ce83c7c1f1..79c40e0abf17 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -2070,8 +2070,9 @@ int io_submit_sqes(struct io_ring_ctx *ctx, unsigned int nr) static void io_rings_free(struct io_ring_ctx *ctx) { - io_free_region(ctx->user, &ctx->sq_region); - io_free_region(ctx->user, &ctx->ring_region); + /* ring memory is not charged to RLIMIT_MEMLOCK, hence no user */ + io_free_region(NULL, &ctx->sq_region); + io_free_region(NULL, &ctx->ring_region); ctx->rings = NULL; RCU_INIT_POINTER(ctx->rings_rcu, NULL); ctx->sq_sqes = NULL; @@ -2735,7 +2736,8 @@ static __cold int io_allocate_scq_urings(struct io_ring_ctx *ctx, rd.user_addr = p->cq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &ctx->ring_region, &rd, IORING_OFF_CQ_RING); + /* ring memory is not charged to RLIMIT_MEMLOCK, hence no user */ + ret = io_create_region(NULL, &ctx->ring_region, &rd, IORING_OFF_CQ_RING); if (ret) return ret; ctx->rings = rings = io_region_get_ptr(&ctx->ring_region); @@ -2749,7 +2751,7 @@ static __cold int io_allocate_scq_urings(struct io_ring_ctx *ctx, rd.user_addr = p->sq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &ctx->sq_region, &rd, IORING_OFF_SQES); + ret = io_create_region(NULL, &ctx->sq_region, &rd, IORING_OFF_SQES); if (ret) { io_rings_free(ctx); return ret; diff --git a/io_uring/kbuf.c b/io_uring/kbuf.c index 7c309173dd19..7c59ab9cfc8b 100644 --- a/io_uring/kbuf.c +++ b/io_uring/kbuf.c @@ -676,7 +676,7 @@ int io_register_pbuf_ring(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = reg.ring_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &bl->region, &rd, mmap_offset); + ret = io_create_region(ctx->user, &bl->region, &rd, mmap_offset); if (ret) goto fail; br = io_region_get_ptr(&bl->region); diff --git a/io_uring/memmap.c b/io_uring/memmap.c index 48c0eb012412..d4d716946971 100644 --- a/io_uring/memmap.c +++ b/io_uring/memmap.c @@ -204,7 +204,7 @@ done: return 0; } -int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, +int io_create_region(struct user_struct *user, struct io_mapped_region *mr, struct io_uring_region_desc *reg, unsigned long mmap_offset) { @@ -230,8 +230,8 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, return -EOVERFLOW; nr_pages = reg->size >> PAGE_SHIFT; - if (ctx->user) { - ret = __io_account_mem(ctx->user, nr_pages); + if (user) { + ret = __io_account_mem(user, nr_pages); if (ret) return ret; } @@ -240,7 +240,7 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, if (reg->flags & IORING_MEM_REGION_TYPE_USER) ret = io_region_pin_pages(mr, reg); else - ret = io_region_allocate_pages(mr, reg, mmap_offset, ctx->user); + ret = io_region_allocate_pages(mr, reg, mmap_offset, user); if (ret) goto out_free; @@ -249,7 +249,7 @@ int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, goto out_free; return 0; out_free: - io_free_region(ctx->user, mr); + io_free_region(user, mr); return ret; } diff --git a/io_uring/memmap.h b/io_uring/memmap.h index f4cfbb6b9a1f..0714bb5a9616 100644 --- a/io_uring/memmap.h +++ b/io_uring/memmap.h @@ -18,7 +18,7 @@ unsigned long io_uring_get_unmapped_area(struct file *file, unsigned long addr, int io_uring_mmap(struct file *file, struct vm_area_struct *vma); void io_free_region(struct user_struct *user, struct io_mapped_region *mr); -int io_create_region(struct io_ring_ctx *ctx, struct io_mapped_region *mr, +int io_create_region(struct user_struct *user, struct io_mapped_region *mr, struct io_uring_region_desc *reg, unsigned long mmap_offset); diff --git a/io_uring/register.c b/io_uring/register.c index 02bc103bcc9d..d11a53d68431 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -480,8 +480,9 @@ struct io_ring_ctx_rings { static void io_register_free_rings(struct io_ring_ctx *ctx, struct io_ring_ctx_rings *r) { - io_free_region(ctx->user, &r->sq_region); - io_free_region(ctx->user, &r->ring_region); + /* ring memory is not charged to RLIMIT_MEMLOCK, hence no user */ + io_free_region(NULL, &r->sq_region); + io_free_region(NULL, &r->ring_region); } #define swap_old(ctx, o, n, field) \ @@ -529,7 +530,7 @@ static int io_register_resize_rings(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = p->cq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &n.ring_region, &rd, IORING_OFF_CQ_RING); + ret = io_create_region(NULL, &n.ring_region, &rd, IORING_OFF_CQ_RING); if (ret) return ret; @@ -559,7 +560,7 @@ static int io_register_resize_rings(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = p->sq_off.user_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx, &n.sq_region, &rd, IORING_OFF_SQES); + ret = io_create_region(NULL, &n.sq_region, &rd, IORING_OFF_SQES); if (ret) { io_register_free_rings(ctx, &n); return ret; @@ -730,7 +731,8 @@ static int io_register_mem_region(struct io_ring_ctx *ctx, void __user *uarg) !(ctx->flags & IORING_SETUP_R_DISABLED)) return -EINVAL; - ret = io_create_region(ctx, ®ion, &rd, IORING_MAP_OFF_PARAM_REGION); + ret = io_create_region(ctx->user, ®ion, &rd, + IORING_MAP_OFF_PARAM_REGION); if (ret) return ret; if (copy_to_user(rd_uptr, &rd, sizeof(rd))) { diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index beb35077f3ce..939885985333 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -432,7 +432,7 @@ static int io_allocate_rbuf_ring(struct io_ring_ctx *ctx, mmap_offset = IORING_MAP_OFF_ZCRX_REGION; mmap_offset += (u64)id << IORING_OFF_ZCRX_SHIFT; - ret = io_create_region(ctx, &ifq->rq_region, rd, mmap_offset); + ret = io_create_region(ctx->user, &ifq->rq_region, rd, mmap_offset); if (ret < 0) return ret; -- cgit v1.2.3 From cde56daf4b93f474dc256dbcc5205035223ae6f5 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 8 Oct 2026 13:07:38 -0600 Subject: io_uring/kbuf: don't charge provided buffer rings to RLIMIT_MEMLOCK Just like with the SQ and CQ rings, the provided buffer rings were also not charged against the memlock limit prior to the below mentioned commit. Restore that behavior. Fixes: ef62de3c4ad5 ("io_uring/kbuf: use region api for pbuf rings") Reported-by: Hengyu Liang Cc: stable@vger.kernel.org Tested-by: Hengyu Liang Signed-off-by: Jens Axboe --- io_uring/kbuf.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/io_uring/kbuf.c b/io_uring/kbuf.c index 7c59ab9cfc8b..6e2facf34bfa 100644 --- a/io_uring/kbuf.c +++ b/io_uring/kbuf.c @@ -459,7 +459,7 @@ static int io_remove_buffers_legacy(struct io_ring_ctx *ctx, static void io_put_bl(struct io_ring_ctx *ctx, struct io_buffer_list *bl) { if (bl->flags & IOBL_BUF_RING) - io_free_region(ctx->user, &bl->region); + io_free_region(NULL, &bl->region); else io_remove_buffers_legacy(ctx, bl, -1U); @@ -676,7 +676,7 @@ int io_register_pbuf_ring(struct io_ring_ctx *ctx, void __user *arg) rd.user_addr = reg.ring_addr; rd.flags |= IORING_MEM_REGION_TYPE_USER; } - ret = io_create_region(ctx->user, &bl->region, &rd, mmap_offset); + ret = io_create_region(NULL, &bl->region, &rd, mmap_offset); if (ret) goto fail; br = io_region_get_ptr(&bl->region); @@ -709,7 +709,7 @@ int io_register_pbuf_ring(struct io_ring_ctx *ctx, void __user *arg) if (!ret) return 0; fail: - io_free_region(ctx->user, &bl->region); + io_free_region(NULL, &bl->region); kfree(bl); return ret; } -- cgit v1.2.3 From 6da8f402e362b2f546e2efb7167fc9597aef6faf Mon Sep 17 00:00:00 2001 From: Eugene Gvozdetsky Date: Fri, 9 Oct 2026 01:08:49 +0200 Subject: io_uring/register: don't exempt disabled rings from task restrictions A ring created with IORING_SETUP_R_DISABLED is exempt from its register restrictions until it is enabled. This lets the creator register the restrictions and then enable the ring with IORING_REGISTER_ENABLE_RINGS, whether or not that opcode is in the allowed set. Since commit ed82f35b926b ("io_uring: allow registration of per-task restrictions"), a ring also inherits restrictions from the task that creates it, and the same exemption applies to those. A task whose register opcodes are restricted can therefore create a disabled ring, issue any register opcode on it, and then enable it. Mark rings whose restrictions were inherited from the task, and keep checking register opcodes for them while the ring is disabled. Enabling such a ring now requires IORING_REGISTER_ENABLE_RINGS to be in the task's allowed set. Per-ring restrictions registered on a disabled ring work as before. Fixes: ed82f35b926b ("io_uring: allow registration of per-task restrictions") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Eugene Gvozdetsky Link: https://patch.msgid.link/20261009-iouring-task-restrict-fix-v1-1-a49daf55a12c@gmail.com [axboe: amend with inheritance flag fixup] Signed-off-by: Jens Axboe --- include/linux/io_uring_types.h | 2 ++ io_uring/io_uring.c | 3 ++- io_uring/register.c | 9 ++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h index 6b9e9c669bb3..f9aab8d3f861 100644 --- a/include/linux/io_uring_types.h +++ b/include/linux/io_uring_types.h @@ -317,6 +317,8 @@ enum { IO_RING_F_DRAIN_DISABLED = BIT(10), IO_RING_F_COMPAT = BIT(11), IO_RING_F_IOWQ_LIMITS_SET = BIT(12), + /* restrictions were inherited from the task that created the ring */ + IO_RING_F_RESTRICT_INHERITED = BIT(13), }; struct iou_ctx {}; diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 79c40e0abf17..c994157173a2 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -2962,7 +2962,8 @@ static void io_ctx_restriction_clone(struct io_ring_ctx *ctx, if (dst->op_registered) ctx->int_flags |= IO_RING_F_OP_RESTRICTED; if (dst->reg_registered) - ctx->int_flags |= IO_RING_F_REG_RESTRICTED; + ctx->int_flags |= IO_RING_F_REG_RESTRICTED | + IO_RING_F_RESTRICT_INHERITED; } static __cold int io_uring_create(struct io_ctx_config *config) diff --git a/io_uring/register.c b/io_uring/register.c index d11a53d68431..dab3eb4258df 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -766,7 +766,14 @@ static int __io_uring_register(struct io_ring_ctx *ctx, unsigned opcode, if (ctx->submitter_task && ctx->submitter_task != current) return -EEXIST; - if ((ctx->int_flags & IO_RING_F_REG_RESTRICTED) && !(ctx->flags & IORING_SETUP_R_DISABLED)) { + /* + * A disabled ring is exempt from its own register restrictions, so + * that the creator can set them up and then enable the ring. That + * does not apply to restrictions inherited from the task. + */ + if ((ctx->int_flags & IO_RING_F_REG_RESTRICTED) && + (!(ctx->flags & IORING_SETUP_R_DISABLED) || + (ctx->int_flags & IO_RING_F_RESTRICT_INHERITED))) { opcode = array_index_nospec(opcode, IORING_REGISTER_LAST); if (!test_bit(opcode, ctx->restrictions.register_op)) return -EACCES; -- cgit v1.2.3 From aa5d5f8e502318e783545ba5d04bb40130b83e20 Mon Sep 17 00:00:00 2001 From: Natasha Klaus Date: Fri, 9 Oct 2026 16:38:07 +0300 Subject: io_uring/net: populate CONNECT filter fields for 24-byte sockaddr_in6 io_connect_bpf_populate() fills connect.port and connect.v6_addr for AF_INET6 only when addr_len >= sizeof(struct sockaddr_in6), i.e. 28 bytes. The IPv6 connect paths accept SIN6_LEN_RFC2133 (24 bytes), which already holds sin6_port and sin6_addr. io_connect() passes addr_len to the socket layer unchanged. At addr_len 24 the filter sees family AF_INET6 with port and address zero, while the kernel connects to the real destination. A filter that denies an IPv6 port or address is bypassed. Denying connect.port == P rejects [::1]:P with -EACCES at addr_len 28 but connects at addr_len 24. Use SIN6_LEN_RFC2133 as the threshold. sin6_scope_id stays zero when absent. The threshold mismatch was found by an AI coding assistant (Claude Code) comparing this hook against the net/ipv6 length checks on my instructions; it also drafted the reproducer and this patch. I verified the bypass and the fix in a VM on 7.3-rc6 and that 7.2 has the same code. Fixes: 899bea8248ce ("io_uring/net: allow filtering on IORING_OP_CONNECT") Cc: stable@vger.kernel.org # 7.2 Signed-off-by: Natasha Klaus Link: https://patch.msgid.link/20261009133807.730724-1-natalie.klaus@runtimeverification.com Signed-off-by: Jens Axboe --- io_uring/net.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/io_uring/net.c b/io_uring/net.c index 050ed274170a..f5f4f3ce6ef3 100644 --- a/io_uring/net.c +++ b/io_uring/net.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -1741,7 +1742,8 @@ void io_connect_bpf_populate(struct io_uring_bpf_ctx *bctx, struct io_kiocb *req case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; - if (conn->addr_len < (int)sizeof(*sin6)) + /* The IPv6 stack accepts SIN6_LEN_RFC2133; port and addr are in it. */ + if (conn->addr_len < SIN6_LEN_RFC2133) break; bctx->connect.port = sin6->sin6_port; memcpy(bctx->connect.v6_addr, &sin6->sin6_addr, -- cgit v1.2.3 From fddcf4c5321b3ea5f72166c3fd36a9d1c34f80c7 Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft Security FORGE Labs)" Date: Thu, 8 Oct 2026 15:16:02 -0400 Subject: io_uring/cancel: don't spin on local work the task can't run io_uring_cancel_generic() refuses to sleep while any ring on the exiting task's tctx has local work pending. It assumes the task is that ring's submitter and that the next io_uring_try_cancel_requests() pass will run the work. This does not hold for a ring that was still IORING_SETUP_R_DISABLED when the task got its tctx node from io_uring_create() or io_ringfd_register(). The submitter is set later by IORING_REGISTER_ENABLE_RINGS, possibly from another task, and only the submitter may run local work on an IORING_SETUP_DEFER_TASKRUN ring. To the exiting task the work stays pending forever, the loop never reaches schedule(), and the task trips the WARN_ON_ONCE() once and then spins in do_exit(). An unprivileged user can set this up at will by creating a disabled ring, having a child enable it and stop, posting to the ring with IORING_OP_MSG_RING and exiting with a request in flight that only the child can complete. The exiting thread burns a CPU until the child runs or dies, and with panic_on_warn set the WARN panics the kernel. Fix by skipping the sleep only when io_allowed_defer_tw_run() allows the task to run the work. This is reasonable since work it cannot run cannot complete any of its requests either, so nothing is lost by sleeping. Drop the WARN_ON_ONCE() since handing a disabled ring to another thread is a supported scenario, not a bug state. Fixes: 360cd42c4e95 ("io_uring: optimise io_req_local_work_add") Reported-by: AutonomousCodeSecurity@microsoft.com Signed-off-by: Cen Zhang (Microsoft Security FORGE Labs) Link: https://patch.msgid.link/20261008191602.45101-1-cenzhang@linux.microsoft.com Signed-off-by: Jens Axboe --- io_uring/cancel.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/io_uring/cancel.c b/io_uring/cancel.c index 7d7820eab878..4f706aff14fc 100644 --- a/io_uring/cancel.c +++ b/io_uring/cancel.c @@ -634,12 +634,11 @@ __cold void io_uring_cancel_generic(bool cancel_all, struct io_sq_data *sqd) prepare_to_wait(&tctx->wait, &wait, TASK_INTERRUPTIBLE); io_run_task_work(); io_uring_drop_tctx_refs(current); + /* only skip the sleep for local work this task may run */ xa_for_each(&tctx->xa, index, node) { - if (io_local_work_pending(node->ctx)) { - WARN_ON_ONCE(node->ctx->submitter_task && - node->ctx->submitter_task != current); + if (io_local_work_pending(node->ctx) && + io_allowed_defer_tw_run(node->ctx)) goto end_wait; - } } /* * If we've seen completions, retry without waiting. This -- cgit v1.2.3 From f321cc430c49b6c1be0d53f2ef19d8342d8029cc Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 9 Oct 2026 10:48:25 -0700 Subject: Input: ati_remote2 - fix type confusion in device attribute handlers Previously, the channel_mask and mode_mask attributes were registered on the parent usb_device via sysfs_create_group(). When commit b20d6bf8014b ("Input: ati-remote2 - use driver core to instantiate device attributes") converted the driver to let the driver core manage them via dev_groups in struct usb_driver, the attributes moved to the bound usb_interface devices, but the show and store callbacks were not updated accordingly. As a result, the callbacks still invoked to_usb_device() on a device pointer belonging to a struct usb_interface, producing an invalid usb_device pointer. Use to_usb_interface() directly in the attribute handlers to obtain the interface and its driver data. Fixes: b20d6bf8014b ("Input: ati-remote2 - use driver core to instantiate device attributes") Assisted-by: DeepSeek-V4.1-Flash Signed-off-by: Haotian Zhang Link: https://patch.msgid.link/20261009040302.3087205-1-vulab@iscas.ac.cn Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/misc/ati_remote2.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/input/misc/ati_remote2.c b/drivers/input/misc/ati_remote2.c index 8db2dca84975..3e0f874e9b16 100644 --- a/drivers/input/misc/ati_remote2.c +++ b/drivers/input/misc/ati_remote2.c @@ -672,8 +672,7 @@ static ssize_t ati_remote2_show_channel_mask(struct device *dev, struct device_attribute *attr, char *buf) { - struct usb_device *udev = to_usb_device(dev); - struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct usb_interface *intf = to_usb_interface(dev); struct ati_remote2 *ar2 = usb_get_intfdata(intf); return sprintf(buf, "0x%04x\n", ar2->channel_mask); @@ -683,8 +682,7 @@ static ssize_t ati_remote2_store_channel_mask(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct usb_device *udev = to_usb_device(dev); - struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct usb_interface *intf = to_usb_interface(dev); struct ati_remote2 *ar2 = usb_get_intfdata(intf); unsigned int mask; int r; @@ -720,8 +718,7 @@ static ssize_t ati_remote2_show_mode_mask(struct device *dev, struct device_attribute *attr, char *buf) { - struct usb_device *udev = to_usb_device(dev); - struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct usb_interface *intf = to_usb_interface(dev); struct ati_remote2 *ar2 = usb_get_intfdata(intf); return sprintf(buf, "0x%02x\n", ar2->mode_mask); @@ -731,8 +728,7 @@ static ssize_t ati_remote2_store_mode_mask(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct usb_device *udev = to_usb_device(dev); - struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct usb_interface *intf = to_usb_interface(dev); struct ati_remote2 *ar2 = usb_get_intfdata(intf); unsigned int mask; int err; -- cgit v1.2.3 From f9282b8aa85a8c97c50d656db75feb6d76fc7cbe Mon Sep 17 00:00:00 2001 From: Krzysztof Furman Date: Fri, 9 Oct 2026 15:47:10 +0100 Subject: Input: xpad - add support for Corsair Novablade Pro Add device IDs for the Corsair Novablade Pro leverless fight controller in both wired (0x2b1f) and 2.4 GHz receiver (0x2b2b) modes, and add Corsair's vendor ID to the Xbox 360 vendor match list. Outside of its PS4/PS5 modes the controller uses the Xbox 360 protocol (vendor-specific class, subclass 93, protocol 1) on interface 0, which xpad does not currently bind, so no gamepad device is created. The PS4/PS5 modes present a standard HID gamepad and already work with hid-generic. Tested on 7.2.5 with the 2.4 GHz receiver and wired. Assisted-by: Claude Opus 5.5 Signed-off-by: Krzysztof Furman Link: https://patch.msgid.link/20261009-xpad-corsair-novablade-v1-1-e58746bef99f@proton.me Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 6406ceab988a..9dc350b4321a 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -302,6 +302,8 @@ static const struct xpad_device { { 0x17ef, 0x6182, "Lenovo Legion Controller for Windows", 0, XTYPE_XBOX360 }, { 0x1949, 0x041a, "Amazon Game Controller", 0, XTYPE_XBOX360 }, { 0x1a86, 0xe310, "Legion Go S", 0, XTYPE_XBOX360 }, + { 0x1b1c, 0x2b1f, "Corsair Novablade Pro (Wired)", 0, XTYPE_XBOX360 }, + { 0x1b1c, 0x2b2b, "Corsair Novablade Pro (2.4 GHz Receiver)", 0, XTYPE_XBOX360 }, { 0x1bad, 0x0002, "Harmonix Rock Band Guitar", 0, XTYPE_XBOX360 }, { 0x1bad, 0x0003, "Harmonix Rock Band Drumkit", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 }, { 0x1bad, 0x0130, "Ion Drum Rocker", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 }, @@ -545,6 +547,7 @@ static const struct usb_device_id xpad_table[] = { XPAD_XBOX360_VENDOR(0x17ef), /* Lenovo */ XPAD_XBOX360_VENDOR(0x1949), /* Amazon controllers */ XPAD_XBOX360_VENDOR(0x1a86), /* Nanjing Qinheng Microelectronics (WCH) */ + XPAD_XBOX360_VENDOR(0x1b1c), /* Corsair controllers */ XPAD_XBOX360_VENDOR(0x1bad), /* Harmonix Rock Band guitar and drums */ XPAD_XBOX360_VENDOR(0x1ee9), /* ZOTAC Technology Limited */ XPAD_XBOX360_VENDOR(0x20bc), /* BETOP wireless dongles */ -- cgit v1.2.3 From 41363c27b8509bd4efcba244803edac9ba25d1b5 Mon Sep 17 00:00:00 2001 From: Bihaan Sen Date: Fri, 25 Sep 2026 17:14:38 +0530 Subject: Input: xpad - add support for 8BitDo Pro 2 Wired Controller for Xbox The 8BitDo Pro 2 Wired Controller for Xbox is already matched by the vendor-wide XPAD_XBOXONE_VENDOR(0x2dc8) entry, but a later hardware revision reports product ID 0x201e, which has no entry in the xpad_device table. The device therefore binds and functions but is exposed to userspace as "Generic X-Box pad". Add the VID/PID pair so the controller is named correctly, and set MAP_SHARE_BUTTON to expose the share button as KEY_RECORD. Signed-off-by: Bihaan Sen Link: https://patch.msgid.link/20260925114459.110253-1-stevewholikesthings@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 9dc350b4321a..810e52782ae8 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -381,6 +381,7 @@ static const struct xpad_device { { 0x2993, 0x2001, "TECNO Pocket Go", 0, XTYPE_XBOX360 }, { 0x2dc8, 0x2000, "8BitDo Pro 2 Wired Controller fox Xbox", 0, XTYPE_XBOXONE }, { 0x2dc8, 0x200f, "8BitDo Ultimate 3-mode Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, + { 0x2dc8, 0x201e, "8BitDo Pro 2 Wired Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, { 0x2dc8, 0x3106, "8BitDo Ultimate Wireless / Pro 2 Wired Controller", 0, XTYPE_XBOX360 }, { 0x2dc8, 0x3109, "8BitDo Ultimate Wireless Bluetooth", 0, XTYPE_XBOX360 }, { 0x2dc8, 0x310a, "8BitDo Ultimate 2C Wireless Controller", 0, XTYPE_XBOX360 }, -- cgit v1.2.3 From d84d6c1a7f1e2de9e601703a326a4c1b7b421943 Mon Sep 17 00:00:00 2001 From: Norbert Szetei Date: Wed, 7 Oct 2026 16:43:22 +0200 Subject: xfs: stop exchanging reflink flags when exchanging mappings When an exchange covers both files from offset 0 to i_disk_size, xmi_can_exchange_reflink_flags() assumes every mapping moved and hands the reflink flag over to the other file. That assumption is wrong. A file can still own shared blocks past i_disk_size, e.g. after EXCHANGE_RANGE_TO_EOF sets a smaller size without unmapping anything. The flag is cleared anyway, and the next write to such a block happens in place instead of CoW, corrupting the other file that shares it. Commit a23eca88448e ("xfs: fix exchange-range reflink flag clearing issue with INO1_WRITTEN") fixed one instance of this. Rather than add another special case, stop moving the flags. xfs_exchmaps_ensure_reflink() already sets the flag on both inodes before the exchange, so both just keep it. Nothing sets CLEAR_INO{1,2}_REFLINK anymore. Intents logged by older kernels may still carry them; recovery already masks them off, so they stay in the log format mask and are ignored. XFS_IOC_SWAPEXT has the same problem: it moves the reflink flag from one inode to the other along with the extents. Just removing that is not enough here: the exchange path flags both inodes up front in xfs_exchmaps_ensure_reflink(), but swapext has no such step, so the inode receiving the shared extents would be left without the flag. Set it on both inodes instead, and make sure both have a CoW fork, since an inode with the reflink flag is expected to have one. The flag now stays until FALLOC_FL_UNSHARE_RANGE, xfs_scrub, or a truncate to zero removes it. Fixes: 966ceafc7a43 ("xfs: create deferred log items for file mapping exchanges") Fixes: f0bc4d134b46 ("xfs: swap inode reflink flags when swapping inode extents") Cc: stable@vger.kernel.org # v6.10 Assisted-by: LLM Signed-off-by: Norbert Szetei Reviewed-by: Dave Chinner Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_exchmaps.c | 80 ++---------------------------------------- fs/xfs/libxfs/xfs_log_format.h | 4 +-- fs/xfs/xfs_bmap_util.c | 15 ++++---- 3 files changed, 10 insertions(+), 89 deletions(-) diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 6a66b6075e0a..7af8ee35c89d 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -49,7 +49,7 @@ struct xfs_exchmaps_adjacent { .right2 = { .br_startblock = HOLESTARTBLOCK }, \ } -/* Information to reset reflink flag / CoW fork state after an exchange. */ +/* Information to reset CoW fork state after an exchange. */ /* * If the reflink flag is set on either inode, make sure it has an incore CoW @@ -127,9 +127,7 @@ xmi_has_more_exchange_work(const struct xfs_exchmaps_intent *xmi) static inline bool xmi_has_postop_work(const struct xfs_exchmaps_intent *xmi) { - return xmi->xmi_flags & (XFS_EXCHMAPS_CLEAR_INO1_REFLINK | - XFS_EXCHMAPS_CLEAR_INO2_REFLINK | - __XFS_EXCHMAPS_INO2_SHORTFORM); + return xmi->xmi_flags & __XFS_EXCHMAPS_INO2_SHORTFORM; } /* Check all mappings to make sure we can actually exchange them. */ @@ -525,18 +523,6 @@ free: return error; } -/* Clear the reflink flag after an exchange. */ -static inline void -xfs_exchmaps_clear_reflink( - struct xfs_trans *tp, - struct xfs_inode *ip) -{ - trace_xfs_reflink_unset_inode_flag(ip); - - ip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; - xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); -} - /* Finish whatever work might come after an exchange operation. */ static int xfs_exchmaps_do_postop_work( @@ -557,16 +543,6 @@ xfs_exchmaps_do_postop_work( return error; } - if (xmi->xmi_flags & XFS_EXCHMAPS_CLEAR_INO1_REFLINK) { - xfs_exchmaps_clear_reflink(tp, xmi->xmi_ip1); - xmi->xmi_flags &= ~XFS_EXCHMAPS_CLEAR_INO1_REFLINK; - } - - if (xmi->xmi_flags & XFS_EXCHMAPS_CLEAR_INO2_REFLINK) { - xfs_exchmaps_clear_reflink(tp, xmi->xmi_ip2); - xmi->xmi_flags &= ~XFS_EXCHMAPS_CLEAR_INO2_REFLINK; - } - return 0; } @@ -948,46 +924,13 @@ xfs_exchmaps_intent_destroy_cache(void) } /* - * Decide if we will exchange the reflink flags between the two files after the - * exchange. The only time we want to do this is if we're exchanging all - * mappings under EOF and the inode reflink flags have different states. + * Allocate and initialize a new incore intent item from a request. */ -static inline bool -xmi_can_exchange_reflink_flags( - const struct xfs_exchmaps_req *req, - unsigned int reflink_state) -{ - struct xfs_mount *mp = req->ip1->i_mount; - - /* - * The INO1_WRITTEN optimization can skip exchanging hole and - * unwritten mappings, which means we cannot guarantee that all - * shared extents actually moved to the other file. Clearing the - * reflink flag of an inode that still holds shared extents breaks - * the CoW write path, so refuse to exchange the flags in that case. - */ - if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN) - return false; - - if (hweight32(reflink_state) != 1) - return false; - if (req->startoff1 != 0 || req->startoff2 != 0) - return false; - if (req->blockcount != XFS_B_TO_FSB(mp, req->ip1->i_disk_size)) - return false; - if (req->blockcount != XFS_B_TO_FSB(mp, req->ip2->i_disk_size)) - return false; - return true; -} - - -/* Allocate and initialize a new incore intent item from a request. */ struct xfs_exchmaps_intent * xfs_exchmaps_init_intent( const struct xfs_exchmaps_req *req) { struct xfs_exchmaps_intent *xmi; - unsigned int rs = 0; xmi = kmem_cache_zalloc(xfs_exchmaps_intent_cache, GFP_NOFS | __GFP_NOFAIL); @@ -1011,23 +954,6 @@ xfs_exchmaps_init_intent( xmi->xmi_isize2 = req->ip1->i_disk_size; } - /* Record the state of each inode's reflink flag before the op. */ - if (xfs_is_reflink_inode(req->ip1)) - rs |= 1; - if (xfs_is_reflink_inode(req->ip2)) - rs |= 2; - - /* - * Figure out if we're clearing the reflink flags (which effectively - * exchanges them) after the operation. - */ - if (xmi_can_exchange_reflink_flags(req, rs)) { - if (rs & 1) - xmi->xmi_flags |= XFS_EXCHMAPS_CLEAR_INO1_REFLINK; - if (rs & 2) - xmi->xmi_flags |= XFS_EXCHMAPS_CLEAR_INO2_REFLINK; - } - if (S_ISDIR(VFS_I(xmi->xmi_ip2)->i_mode) || S_ISLNK(VFS_I(xmi->xmi_ip2)->i_mode)) xmi->xmi_flags |= __XFS_EXCHMAPS_INO2_SHORTFORM; diff --git a/fs/xfs/libxfs/xfs_log_format.h b/fs/xfs/libxfs/xfs_log_format.h index a4e1b3eb425c..6e620380d5dc 100644 --- a/fs/xfs/libxfs/xfs_log_format.h +++ b/fs/xfs/libxfs/xfs_log_format.h @@ -927,10 +927,8 @@ struct xfs_xmi_log_format { */ #define XFS_EXCHMAPS_INO1_WRITTEN (1ULL << 2) -/* Clear the reflink flag from inode1 after the operation. */ +/* No longer set, but older intents may carry them; keep them valid. */ #define XFS_EXCHMAPS_CLEAR_INO1_REFLINK (1ULL << 3) - -/* Clear the reflink flag from inode2 after the operation. */ #define XFS_EXCHMAPS_CLEAR_INO2_REFLINK (1ULL << 4) #define XFS_EXCHMAPS_LOGGED_FLAGS (XFS_EXCHMAPS_ATTR_FORK | \ diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 268d159339d0..865aec3ae5ab 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -1544,7 +1544,6 @@ xfs_swap_extents( struct xfs_bstat *sbp = &sxp->sx_stat; int src_log_flags, target_log_flags; int error = 0; - uint64_t f; int resblks = 0; unsigned int flags = 0; struct timespec64 ctime, mtime; @@ -1700,14 +1699,12 @@ xfs_swap_extents( if (error) goto out_trans_cancel; - /* Do we have to swap reflink flags? */ - if ((ip->i_diflags2 & XFS_DIFLAG2_REFLINK) ^ - (tip->i_diflags2 & XFS_DIFLAG2_REFLINK)) { - f = ip->i_diflags2 & XFS_DIFLAG2_REFLINK; - ip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; - ip->i_diflags2 |= tip->i_diflags2 & XFS_DIFLAG2_REFLINK; - tip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK; - tip->i_diflags2 |= f & XFS_DIFLAG2_REFLINK; + /* Either inode may now own shared blocks; flag both, with CoW forks. */ + if ((ip->i_diflags2 | tip->i_diflags2) & XFS_DIFLAG2_REFLINK) { + ip->i_diflags2 |= XFS_DIFLAG2_REFLINK; + tip->i_diflags2 |= XFS_DIFLAG2_REFLINK; + xfs_ifork_init_cow(ip); + xfs_ifork_init_cow(tip); } /* Swap the cow forks. */ -- cgit v1.2.3 From 7dfb218c829c1acb626e0fe92c5fddb9a26a03e9 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Wed, 7 Oct 2026 10:02:27 -0700 Subject: xfs: fix exchange-range-to-eof file size exchange Norbert Szetei posted a patch containing an incomplete description of a bug in XFS_IOC_EXCHANGE_RANGE's TO_EOF flag. The bug tricks exchange-range into modifying a file so that it has shared blocks starting beyond EOF, which is never allowed because files never have written data beyond EOF, and you can only share written data blocks. This is key to all the incorrect behavior that follows. Eventually I got from Norbert a description of what's going wrong: > Fair. Here it is with physical block numbers, 4k blocks. peer is the file > that gets rewritten and file1 is the one that ends up with the flag > cleared. > > Start, nothing shared: > > peer size 4096 -> [100] > file1 size 12288 -> [200] [201] [202] > donor1 size 4096 -> [300] > donor2 size 4096 -> [400] > > 1. FICLONERANGE peer's block into file1's middle slot: > > peer size 4096 -> [100] > file1 size 12288 -> [200] [100] [202] reflink flag set > ^^^^^ both files own block 100 > > 2. XFS_IOC_EXCHANGE_RANGE file1 against donor1, file1_offset 0, > file2_offset 8192, length 0, TO_EOF. One block is exchanged, and the sizes > are exchanged with it: > > file1 size 4096 -> [200] [100] [300] reflink flag set > ^^^^^^^^^^^ still owned, now past the size > donor1 size 12288 -> [202] > > file1 says it is one block long while it still owns three. Nothing was > unmapped. file2_offset is block 2, so this exchange cannot clear a flag. > > 3. XFS_IOC_EXCHANGE_RANGE file1 against donor2, both offsets 0, length 4096. > By i_disk_size this is two one-block files exchanging everything, so > xmi_can_exchange_reflink_flags() agrees and the flag is cleared: > > file1 size 4096 -> [400] [100] [300] reflink flag CLEARED > donor2 size 4096 -> [200] > > Block 100 is still shared with peer. > > 4. pwrite(file1, 4096, 4096), which is file1's second slot, block 100. > xfs_is_cow_inode(file1) is false, so no CoW: > > peer size 4096 -> [100] same block, new contents > > Block 100 is the only one that matters here. PoC below, it reads a file > called peer in the current directory. The filesystem needs reflink and > exchange-range, which mkfs.xfs 7.x turns on by default. So yes, TO_EOF is screwing up the file size even if it's actually exchanging the data block correctly. The following is the intended behavior of XFS_EXCHANGE_RANGE_TO_EOF, as described by its manpage: "Ignore the length parameter. All bytes in file1_fd from file1_offset to EOF are moved to file2_fd, and file2's size is set to (file2_offset+(file1_length-file1_offset)). Meanwhile, all bytes in file2 from file2_offset to EOF are moved to file1 and file1's size is set to (file1_offset+(file2_length-file2_offset))." In other words, if we only exchange a portion of the file data, then we should only exchange the portion of the file size that relates to the exchanged data. Unfortunately, the code swaps the ondisk file size and the incore file sizes, which is incorrect if the caller passes in a nonzero offset. Having confused the file sizes, the PoC uses a second exchange-range call on what looks like a non-reflinked single-block file. Because the file size is set incorrectly, exchange-range thinks it's exchanging the full contents of two files and clears the reflink flag on the broken file. That enables an extending write of the broken file to rewrite the shared block that's just past EOF. This has become known colloquially as refluxfs. Once the file sizes are set correctly, the second exchange-range no longer thinks that it's doing a full-contents swap, so it won't clear the reflink flag on either of its file arguments. Norbert followed up to an earlier version of this patch with a second complaint about us not handling the i_disk_size state correctly: > The offsets are bounded against the incore i_size but subtracted from > the ondisk i_disk_size, so a file that has not been written back gives > xmi_isize1 = 0 + (0 - 12288). > > For instance, you can do: > > $ xfs_io -f -c "pwrite -q 0 4096" -c fsync file1 > $ xfs_io -f -c "pwrite -q 0 12288" \ > -c "exchangerange -s 0 -d 12288 file1" dirty > $ stat -c %s file1 > 0 > $ stat -c %i file1 > 131 > $ sudo xfs_io -r -c "bulkstat_single 131" . | grep bs_size > bs_size = 18446744073709539328 That's wrong, and the new file size update logic depends on the ondisk i_disk_size matching the incore i_size, so flush the pagecache as needed to push it upwards. Link: https://lore.kernel.org/linux-xfs/1E196589-DEBE-40AC-AFEA-D420DAAB067F@doyensec.com/ Reported-by: Norbert Szetei Cc: stable@vger.kernel.org # v6.10 Fixes: 966ceafc7a4371 ("xfs: create deferred log items for file mapping exchanges") Signed-off-by: Darrick J. Wong Reviewed-by: Dave Chinner Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_exchmaps.c | 13 +++++++++-- fs/xfs/xfs_exchrange.c | 51 ++++++++++++++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 7af8ee35c89d..f527fce46def 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -931,6 +931,7 @@ xfs_exchmaps_init_intent( const struct xfs_exchmaps_req *req) { struct xfs_exchmaps_intent *xmi; + struct xfs_mount *mp = req->ip1->i_mount; xmi = kmem_cache_zalloc(xfs_exchmaps_intent_cache, GFP_NOFS | __GFP_NOFAIL); @@ -948,10 +949,18 @@ xfs_exchmaps_init_intent( return xmi; } + /* + * If the caller wanted, set each file's size to that file's exchange + * offset + length exchanged from the other file because the ranges in + * each file might be different lengths. + */ if (req->flags & XFS_EXCHMAPS_SET_SIZES) { + loff_t off1 = XFS_FSB_TO_B(mp, xmi->xmi_startoff1); + loff_t off2 = XFS_FSB_TO_B(mp, xmi->xmi_startoff2); + xmi->xmi_flags |= XFS_EXCHMAPS_SET_SIZES; - xmi->xmi_isize1 = req->ip2->i_disk_size; - xmi->xmi_isize2 = req->ip1->i_disk_size; + xmi->xmi_isize1 = off1 + (req->ip2->i_disk_size - off2); + xmi->xmi_isize2 = off2 + (req->ip1->i_disk_size - off1); } if (S_ISDIR(VFS_I(xmi->xmi_ip2)->i_mode) || diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index fafb4e3f065c..935b561dfd3c 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -290,17 +290,20 @@ retry: goto out_unlock; /* - * If the caller wanted us to exchange the contents of two complete - * files of unequal length, exchange the incore sizes now. This should - * be safe because we flushed both files' page caches, exchanged all - * the mappings, and updated the ondisk sizes. + * If the caller wanted, set each file's size to that file's exchange + * offset + length exchanged from the other file because the ranges in + * each file might be different lengths. This should be safe because + * we flushed both files' page caches, exchanged all the mappings, and + * updated the ondisk sizes. */ if (fxr->flags & XFS_EXCHANGE_RANGE_TO_EOF) { - loff_t temp; + loff_t old_ip1_size = i_size_read(VFS_I(ip1)); + loff_t old_ip2_size = i_size_read(VFS_I(ip2)); - temp = i_size_read(VFS_I(ip2)); - i_size_write(VFS_I(ip2), i_size_read(VFS_I(ip1))); - i_size_write(VFS_I(ip1), temp); + i_size_write(VFS_I(ip2), fxr->file2_offset + + (old_ip1_size - fxr->file1_offset)); + i_size_write(VFS_I(ip1), fxr->file1_offset + + (old_ip2_size - fxr->file2_offset)); } out_unlock: @@ -445,6 +448,30 @@ xfs_exchange_range_checks( return blen == fxr->length ? 0 : -EINVAL; } +/* Flush all relevant dirty pagecache near the range to be exchanged. */ +static inline int +xfs_exchange_range_flush( + struct xfs_inode *ip, + const struct xfs_exchrange *fxr, + loff_t offset) +{ + /* + * If TO_EOF is set, the ondisk file size update logic depends on the + * ondisk file size matching the incore file size. Flush everything. + */ + if (fxr->flags & XFS_EXCHANGE_RANGE_TO_EOF) + return filemap_write_and_wait(VFS_I(ip)->i_mapping); + + /* + * Because we're updating the ondisk mappings, flush all dirty data + * between the ondisk size and the exchange offset to reduce the chance + * of zeroed file contents in that gap after a crash. + */ + return filemap_write_and_wait_range(VFS_I(ip)->i_mapping, + min(offset, ip->i_disk_size), + offset + fxr->length - 1); +} + /* * Check that the two inodes are eligible for range exchanges, the ranges make * sense, and then flush all dirty data. Caller must ensure that the inodes @@ -470,15 +497,11 @@ xfs_exchange_range_prep( if (!same_inode) inode_dio_wait(inode2); - error = filemap_write_and_wait_range(inode1->i_mapping, - fxr->file1_offset, - fxr->file1_offset + fxr->length - 1); + error = xfs_exchange_range_flush(XFS_I(inode1), fxr, fxr->file1_offset); if (error) return error; - error = filemap_write_and_wait_range(inode2->i_mapping, - fxr->file2_offset, - fxr->file2_offset + fxr->length - 1); + error = xfs_exchange_range_flush(XFS_I(inode2), fxr, fxr->file2_offset); if (error) return error; -- cgit v1.2.3 From 3d8cc52db2f86b624308321c58513019f6b21eec Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Sat, 10 Oct 2026 23:44:49 +0700 Subject: landlock: Drop __counted_by from landlock_domain.handled_masks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With clang 22 and CONFIG_FORTIFY_SOURCE, the second landlock_restrict_self() call hits a bogus fortify report in inherit_ruleset(): memcpy: detected buffer overflow: 4 byte write of buffer size 0 Call Trace: __fortify_panic landlock_merge_ruleset __se_sys_landlock_restrict_self handled_masks[] lives in an anonymous struct inside the anonymous union that overlays work_free. For such a flexible array, clang computes the address of the __counted_by counter as the start of the array instead of &num_layers. The bound is then read from handled_masks[0], i.e. from the first layer's access masks, which is not a count. The same layout crashes clang itself in a standalone test. The annotation was added as documentation, so drop it rather than restructure the union. Keep the information in the member comment. Reported-by: syzbot+a0eecb22de4d821a9e5d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a0eecb22de4d821a9e5d Fixes: bd3a19800dd1 ("landlock: Add counted_by in landlock_domain") Signed-off-by: Nguyen Ngoc Thang Link: https://lore.kernel.org/r/20261010164450.194439-1-ngocthang2710.1999@gmail.com Reviewed-by: Günther Noack Reported-by: Konrad Dybcio Reported-by: Mike Bommarito Closes: https://lore.kernel.org/r/20261009115108.17819-1-michael.bommarito@gmail.com [mic: Rebased on master, where the array is handled_masks[], updated the fortify report, added the clang bug link and the other reports] Signed-off-by: Mickaël Salaün --- security/landlock/domain.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/security/landlock/domain.h b/security/landlock/domain.h index caa3d19d2c43..428c1d9890db 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -242,9 +242,14 @@ struct landlock_domain { * to properly handle merged rulesets without * overlapping access rights. These layers are set once * and never changed for the lifetime of the domain. + * + * Not annotated with __counted_by(num_layers): clang + * miscomputes the counter address for a FAM nested in + * an anonymous struct within a union, which trips + * FORTIFY: + * https://github.com/llvm/llvm-project/pull/228309 */ - struct access_masks - handled_masks[] __counted_by(num_layers); + struct access_masks handled_masks[]; }; }; }; -- cgit v1.2.3 From 24a19b953e2a3d0ea5a8c4951a0ab8f32720c907 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 11 Oct 2026 20:04:55 -0700 Subject: Linux 7.3-rc7 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7c855e6fe544..0dc1a9f4e63e 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc6 +EXTRAVERSION = -rc7 NAME = Baby Opossum Posse # *DOCUMENTATION* -- cgit v1.2.3 From 73b40395c71c510b82fd6104416ecde1e5ac6418 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Tue, 6 Oct 2026 23:23:56 -0700 Subject: mount: add OPEN_TREE_DROP_MNTNS_MOUNTS A recursive open_tree(OPEN_TREE_CLONE) copies the nsfs mounts of any mount namespaces pinned below the source. When the clone is attached inside a mount namespace younger than a pinned one, move_mount(2) fails with ELOOP from check_for_nsfs_mounts(), per the rule from commit 8823c079ba71 ("vfs: Add setns support for the mount namespace") that prevents mount namespace reference loops. This breaks container runtimes that use OPEN_TREE_NAMESPACE, such as crun [1]. The new namespace is younger than everything else, so bind mounting e.g. the host root fails on any host that pins a mount namespace (snapd does, under /run/snapd/ns). By the time it fails, setns() has already run and userspace cannot recover: the host tree is out of reach, and the offending mounts cannot be unmounted from the detached copy. copy_mnt_ns() and create_new_namespace() already leave these mounts behind; only get_detached_copy() copies them. Add an open_tree() flag to drop them from the clone, as suggested by Aleksa [2]. Locked nsfs mounts are dropped the same way copy_mnt_ns() does it, so nothing new is exposed. Keep it opt-in: open_tree(OPEN_TREE_CLONE) plus move_mount(2) is how mount --rbind works through a file descriptor, and in the caller's own or an older namespace such mounts remain usable. The flag requires AT_RECURSIVE, since a non-recursive clone has no submounts to drop, and OPEN_TREE_CLONE or OPEN_TREE_NAMESPACE. With the latter it is a no-op, so a runtime can pass it with any recursive open_tree() call. Link: https://github.com/containers/crun/issues/2262 [1] Link: https://lore.kernel.org/all/2026-01-07-oldest-grim-captions-spills-ywC2O3@cyphar.com/ [2] Signed-off-by: Kir Kolyshkin Link: https://patch.msgid.link/20261006-nsfs-prune-rfc-v3-1-adb97ea52868@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 16 ++++++++++++++-- include/uapi/linux/mount.h | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 3ed3766f3a18..15c3c68b7f6b 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -3165,7 +3165,8 @@ static struct mnt_namespace *get_detached_copy(const struct path *path, unsigned ns->seq_origin = src_mnt_ns->ns.ns_id; } - mnt = __do_loopback(path, (flags & AT_RECURSIVE), CL_COPY_MNT_NS_FILE); + mnt = __do_loopback(path, (flags & AT_RECURSIVE), + (flags & OPEN_TREE_DROP_MNTNS_MOUNTS) ? 0 : CL_COPY_MNT_NS_FILE); if (IS_ERR(mnt)) { emptied_ns = ns; return ERR_CAST(mnt); @@ -3317,7 +3318,18 @@ static struct file *vfs_open_tree(int dfd, const char __user *filename, unsigned if (flags & ~(AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_RECURSIVE | AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLONE | - OPEN_TREE_CLOEXEC | OPEN_TREE_NAMESPACE)) + OPEN_TREE_CLOEXEC | OPEN_TREE_NAMESPACE | + OPEN_TREE_DROP_MNTNS_MOUNTS)) + return ERR_PTR(-EINVAL); + + /* + * Only meaningful when a tree is copied recursively: a non-recursive + * clone has no submounts to drop. OPEN_TREE_NAMESPACE never copies + * pinned mount namespaces, so there the flag is a no-op. + */ + if ((flags & OPEN_TREE_DROP_MNTNS_MOUNTS) && + (!(flags & AT_RECURSIVE) || + !(flags & (OPEN_TREE_CLONE | OPEN_TREE_NAMESPACE)))) return ERR_PTR(-EINVAL); if ((flags & (AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_NAMESPACE)) == diff --git a/include/uapi/linux/mount.h b/include/uapi/linux/mount.h index 2204708dbf7a..ac865edac517 100644 --- a/include/uapi/linux/mount.h +++ b/include/uapi/linux/mount.h @@ -63,6 +63,7 @@ */ #define OPEN_TREE_CLONE (1 << 0) /* Clone the target tree and attach the clone */ #define OPEN_TREE_NAMESPACE (1 << 1) /* Clone the target tree into a new mount namespace */ +#define OPEN_TREE_DROP_MNTNS_MOUNTS (1 << 2) /* Drop mntns mounts from the clone */ #define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */ /* -- cgit v1.2.3 From e6e54e7117f6eee4ae42c9c24d95a1eb775bdbeb Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Tue, 6 Oct 2026 23:23:57 -0700 Subject: selftests/filesystems: test OPEN_TREE_DROP_MNTNS_MOUNTS Pin a mount namespace below a tmpfs mount, the way snapd does under /run/snapd/ns, and attach a recursive clone of that tmpfs inside a younger mount namespace created with OPEN_TREE_NAMESPACE. Without OPEN_TREE_DROP_MNTNS_MOUNTS move_mount(2) fails with ELOOP; with it, it succeeds. Also check that the flag is rejected without OPEN_TREE_CLONE or OPEN_TREE_NAMESPACE, and accepted with the latter. Signed-off-by: Kir Kolyshkin Link: https://patch.msgid.link/20261006-nsfs-prune-rfc-v3-2-adb97ea52868@gmail.com Signed-off-by: Christian Brauner (Amutable) --- .../filesystems/open_tree_ns/open_tree_ns_test.c | 193 +++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c index 82f3c8c02c9a..438054ab6a6a 100644 --- a/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c +++ b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,10 @@ #define OPEN_TREE_NAMESPACE (1 << 1) #endif +#ifndef OPEN_TREE_DROP_MNTNS_MOUNTS +#define OPEN_TREE_DROP_MNTNS_MOUNTS (1 << 2) +#endif + static int get_mnt_ns_id(int fd, uint64_t *mnt_ns_id) { if (ioctl(fd, NS_GET_MNTNS_ID, mnt_ns_id) < 0) @@ -1004,4 +1009,192 @@ TEST_F(open_tree_ns_unbindable, recursive_skips_on_unbindable) close(fd); } +/* + * Pin a mount namespace at @where, the way snapd does under /run/snapd/ns. + * The namespace has to be younger than ours, so a child unshares and the + * parent binds the child's namespace file: binding one's own namespace is + * refused by the very check this test is about. + */ +static int pin_mount_namespace(const char *where) +{ + int pipefd[2], status, ret = -1; + char path[PATH_MAX]; + pid_t pid; + char c; + + if (pipe(pipefd)) + return -1; + + pid = fork(); + if (pid < 0) + goto out; + if (pid == 0) { + close(pipefd[0]); + if (unshare(CLONE_NEWNS)) + _exit(1); + /* Tell the parent the namespace exists, then hold it open. */ + if (write(pipefd[1], "x", 1) != 1) + _exit(1); + pause(); + _exit(0); + } + + close(pipefd[1]); + pipefd[1] = -1; + if (read(pipefd[0], &c, 1) != 1) + goto out_kill; + + snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid); + if (mount(path, where, NULL, MS_BIND, NULL)) + goto out_kill; + + ret = 0; + +out_kill: + kill(pid, SIGKILL); + waitpid(pid, &status, 0); +out: + close(pipefd[0]); + if (pipefd[1] >= 0) + close(pipefd[1]); + return ret; +} + +FIXTURE(open_tree_ns_drop_mntns) +{ + char dir[64]; + char pin[PATH_MAX]; + bool mounted; +}; + +FIXTURE_SETUP(open_tree_ns_drop_mntns) +{ + int fd, ret; + + self->mounted = false; + snprintf(self->dir, sizeof(self->dir), "/tmp/open_tree_ns_drop_mntns.XXXXXX"); + + ret = sys_open_tree(-1, NULL, 0); + if (ret == -1 && errno == ENOSYS) + SKIP(return, "open_tree() syscall not supported"); + + /* + * Work in a private mount namespace, so whatever is mounted here, + * the pinned namespace included, goes away with the test process. + */ + if (unshare(CLONE_NEWNS)) + SKIP(return, "unshare(CLONE_NEWNS) failed: %s", strerror(errno)); + ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + ASSERT_NE(mkdtemp(self->dir), NULL); + if (mount("tmpfs", self->dir, "tmpfs", 0, NULL)) + SKIP(return, "Failed to mount tmpfs"); + self->mounted = true; + + fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_CLONE | OPEN_TREE_DROP_MNTNS_MOUNTS | + AT_RECURSIVE | OPEN_TREE_CLOEXEC); + if (fd < 0 && errno == EINVAL) + SKIP(return, "OPEN_TREE_DROP_MNTNS_MOUNTS not supported"); + ASSERT_GE(fd, 0); + close(fd); + + snprintf(self->pin, sizeof(self->pin), "%s/ns", self->dir); + fd = open(self->pin, O_CREAT | O_RDONLY | O_CLOEXEC, 0600); + ASSERT_GE(fd, 0); + close(fd); + ASSERT_EQ(pin_mount_namespace(self->pin), 0); +} + +FIXTURE_TEARDOWN(open_tree_ns_drop_mntns) +{ + if (self->mounted) + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); +} + +/* Attach @clone_fd at "/" inside the namespace @ns_fd; returns errno. */ +static int move_into_namespace(int clone_fd, int ns_fd) +{ + pid_t pid; + int status; + + pid = fork(); + if (pid < 0) + return -1; + if (pid == 0) { + int target; + + if (setns(ns_fd, CLONE_NEWNS)) + _exit(255); + target = open("/", O_PATH | O_DIRECTORY | O_CLOEXEC); + if (target < 0) + _exit(255); + if (sys_move_mount(clone_fd, "", target, "", + MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH)) + _exit(errno); + _exit(0); + } + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +TEST_F(open_tree_ns_drop_mntns, move_into_younger_namespace) +{ + int clone_fd, ns_fd; + + ns_fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC); + ASSERT_GE(ns_fd, 0); + + /* + * Without the flag the clone carries the pinned namespace along, + * and move_mount() refuses it from inside a younger namespace. + */ + clone_fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_CLONE | AT_RECURSIVE | OPEN_TREE_CLOEXEC); + ASSERT_GE(clone_fd, 0); + EXPECT_EQ(move_into_namespace(clone_fd, ns_fd), ELOOP); + close(clone_fd); + + /* With the flag the pinned namespace is left out. */ + clone_fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_CLONE | OPEN_TREE_DROP_MNTNS_MOUNTS | + AT_RECURSIVE | OPEN_TREE_CLOEXEC); + ASSERT_GE(clone_fd, 0); + EXPECT_EQ(move_into_namespace(clone_fd, ns_fd), 0); + close(clone_fd); + + close(ns_fd); +} + +TEST_F(open_tree_ns_drop_mntns, flag_combinations) +{ + int fd; + + /* Nothing is copied, so there is nothing to drop. */ + EXPECT_LT(sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_DROP_MNTNS_MOUNTS | OPEN_TREE_CLOEXEC), 0); + EXPECT_EQ(errno, EINVAL); + + /* A non-recursive clone has no submounts, so there is nothing to drop. */ + EXPECT_LT(sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_CLONE | OPEN_TREE_DROP_MNTNS_MOUNTS | + OPEN_TREE_CLOEXEC), 0); + EXPECT_EQ(errno, EINVAL); + EXPECT_LT(sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_NAMESPACE | OPEN_TREE_DROP_MNTNS_MOUNTS | + OPEN_TREE_CLOEXEC), 0); + EXPECT_EQ(errno, EINVAL); + + /* OPEN_TREE_NAMESPACE never copies them; the flag is a no-op there. */ + fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_NAMESPACE | OPEN_TREE_DROP_MNTNS_MOUNTS | + AT_RECURSIVE | OPEN_TREE_CLOEXEC); + EXPECT_GE(fd, 0); + if (fd >= 0) + close(fd); +} + TEST_HARNESS_MAIN -- cgit v1.2.3 From 290ebc69ec41e2fe26f6cee74a9efe953b08853f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 12 Oct 2026 11:38:22 +0200 Subject: fixup! selftests/filesystems: test OPEN_TREE_DROP_MNTNS_MOUNTS FIXTURE_TEARDOWN() isn't run when FIXTURE_SETUP() skips. So remove the temporary directory before skipping instead of leaking it. Signed-off-by: Christian Brauner (Amutable) --- .../selftests/filesystems/open_tree_ns/open_tree_ns_test.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c index 438054ab6a6a..d96b995cb1cd 100644 --- a/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c +++ b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c @@ -1087,15 +1087,20 @@ FIXTURE_SETUP(open_tree_ns_drop_mntns) ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); ASSERT_NE(mkdtemp(self->dir), NULL); - if (mount("tmpfs", self->dir, "tmpfs", 0, NULL)) + if (mount("tmpfs", self->dir, "tmpfs", 0, NULL)) { + rmdir(self->dir); SKIP(return, "Failed to mount tmpfs"); + } self->mounted = true; fd = sys_open_tree(AT_FDCWD, self->dir, OPEN_TREE_CLONE | OPEN_TREE_DROP_MNTNS_MOUNTS | AT_RECURSIVE | OPEN_TREE_CLOEXEC); - if (fd < 0 && errno == EINVAL) + if (fd < 0 && errno == EINVAL) { + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); SKIP(return, "OPEN_TREE_DROP_MNTNS_MOUNTS not supported"); + } ASSERT_GE(fd, 0); close(fd); -- cgit v1.2.3