]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.18-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 13 Jul 2026 17:41:12 +0000 (19:41 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 13 Jul 2026 17:41:12 +0000 (19:41 +0200)
added patches:
iommu-vt-d-fix-race-condition-during-pasid-entry-replacement.patch

queue-6.18/iommu-vt-d-fix-race-condition-during-pasid-entry-replacement.patch [new file with mode: 0644]
queue-6.18/series

diff --git a/queue-6.18/iommu-vt-d-fix-race-condition-during-pasid-entry-replacement.patch b/queue-6.18/iommu-vt-d-fix-race-condition-during-pasid-entry-replacement.patch
new file mode 100644 (file)
index 0000000..1453047
--- /dev/null
@@ -0,0 +1,368 @@
+From c3b1edea3791fa91ab7032faa90355913ad9451b Mon Sep 17 00:00:00 2001
+From: Lu Baolu <baolu.lu@linux.intel.com>
+Date: Thu, 22 Jan 2026 09:48:56 +0800
+Subject: iommu/vt-d: Fix race condition during PASID entry replacement
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Lu Baolu <baolu.lu@linux.intel.com>
+
+commit c3b1edea3791fa91ab7032faa90355913ad9451b upstream.
+
+The Intel VT-d PASID table entry is 512 bits (64 bytes). When replacing
+an active PASID entry (e.g., during domain replacement), the current
+implementation calculates a new entry on the stack and copies it to the
+table using a single structure assignment.
+
+        struct pasid_entry *pte, new_pte;
+
+        pte = intel_pasid_get_entry(dev, pasid);
+        pasid_pte_config_first_level(iommu, &new_pte, ...);
+        *pte = new_pte;
+
+Because the hardware may fetch the 512-bit PASID entry in multiple
+128-bit chunks, updating the entire entry while it is active (Present
+bit set) risks a "torn" read. In this scenario, the IOMMU hardware
+could observe an inconsistent state — partially new data and partially
+old data — leading to unpredictable behavior or spurious faults.
+
+Fix this by removing the unsafe "replace" helpers and following the
+"clear-then-update" flow, which ensures the Present bit is cleared and
+the required invalidation handshake is completed before the new
+configuration is applied.
+
+Fixes: 7543ee63e811 ("iommu/vt-d: Add pasid replace helpers")
+Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
+Reviewed-by: Samiullah Khawaja <skhawaja@google.com>
+Reviewed-by: Kevin Tian <kevin.tian@intel.com>
+Link: https://lore.kernel.org/r/20260120061816.2132558-4-baolu.lu@linux.intel.com
+Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
+[vineeth: adjusted for 6.18.y: the removed intel_pasid_replace_second_level()
+ in 6.18 still computed pgd/pgd_val locally and used the older
+ pasid_pte_config_second_level() signature; since the function is deleted
+ entirely, the resolution only affects removed lines. The prerequisite
+ "Clear Present bit" patches are already in 6.18.y.
+ Built and tested on 6.18.38: PASID replace, iommufd RID replace and DMA
+ sanity on QEMU VT-d in scalable mode (FS and SS), no regressions.]
+Signed-off-by: Vineeth Pillai (Google) <vineeth@bitbyteword.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/iommu/intel/iommu.c  |   29 ++----
+ drivers/iommu/intel/nested.c |    9 --
+ drivers/iommu/intel/pasid.c  |  190 -------------------------------------------
+ drivers/iommu/intel/pasid.h  |   14 ---
+ 4 files changed, 16 insertions(+), 226 deletions(-)
+
+--- a/drivers/iommu/intel/iommu.c
++++ b/drivers/iommu/intel/iommu.c
+@@ -1734,12 +1734,10 @@ int __domain_setup_first_level(struct in
+                              ioasid_t pasid, u16 did, phys_addr_t fsptptr,
+                              int flags, struct iommu_domain *old)
+ {
+-      if (!old)
+-              return intel_pasid_setup_first_level(iommu, dev, fsptptr, pasid,
+-                                                   did, flags);
+-      return intel_pasid_replace_first_level(iommu, dev, fsptptr, pasid, did,
+-                                             iommu_domain_did(old, iommu),
+-                                             flags);
++      if (old)
++              intel_pasid_tear_down_entry(iommu, dev, pasid, false);
++
++      return intel_pasid_setup_first_level(iommu, dev, fsptptr, pasid, did, flags);
+ }
+ static int domain_setup_second_level(struct intel_iommu *iommu,
+@@ -1747,23 +1745,20 @@ static int domain_setup_second_level(str
+                                    struct device *dev, ioasid_t pasid,
+                                    struct iommu_domain *old)
+ {
+-      if (!old)
+-              return intel_pasid_setup_second_level(iommu, domain,
+-                                                    dev, pasid);
+-      return intel_pasid_replace_second_level(iommu, domain, dev,
+-                                              iommu_domain_did(old, iommu),
+-                                              pasid);
++      if (old)
++              intel_pasid_tear_down_entry(iommu, dev, pasid, false);
++
++      return intel_pasid_setup_second_level(iommu, domain, dev, pasid);
+ }
+ static int domain_setup_passthrough(struct intel_iommu *iommu,
+                                   struct device *dev, ioasid_t pasid,
+                                   struct iommu_domain *old)
+ {
+-      if (!old)
+-              return intel_pasid_setup_pass_through(iommu, dev, pasid);
+-      return intel_pasid_replace_pass_through(iommu, dev,
+-                                              iommu_domain_did(old, iommu),
+-                                              pasid);
++      if (old)
++              intel_pasid_tear_down_entry(iommu, dev, pasid, false);
++
++      return intel_pasid_setup_pass_through(iommu, dev, pasid);
+ }
+ static int domain_setup_first_level(struct intel_iommu *iommu,
+--- a/drivers/iommu/intel/nested.c
++++ b/drivers/iommu/intel/nested.c
+@@ -141,11 +141,10 @@ static int domain_setup_nested(struct in
+                              struct device *dev, ioasid_t pasid,
+                              struct iommu_domain *old)
+ {
+-      if (!old)
+-              return intel_pasid_setup_nested(iommu, dev, pasid, domain);
+-      return intel_pasid_replace_nested(iommu, dev, pasid,
+-                                        iommu_domain_did(old, iommu),
+-                                        domain);
++      if (old)
++              intel_pasid_tear_down_entry(iommu, dev, pasid, false);
++
++      return intel_pasid_setup_nested(iommu, dev, pasid, domain);
+ }
+ static int intel_nested_set_dev_pasid(struct iommu_domain *domain,
+--- a/drivers/iommu/intel/pasid.c
++++ b/drivers/iommu/intel/pasid.c
+@@ -417,50 +417,6 @@ int intel_pasid_setup_first_level(struct
+       return 0;
+ }
+-int intel_pasid_replace_first_level(struct intel_iommu *iommu,
+-                                  struct device *dev, phys_addr_t fsptptr,
+-                                  u32 pasid, u16 did, u16 old_did,
+-                                  int flags)
+-{
+-      struct pasid_entry *pte, new_pte;
+-
+-      if (!ecap_flts(iommu->ecap)) {
+-              pr_err("No first level translation support on %s\n",
+-                     iommu->name);
+-              return -EINVAL;
+-      }
+-
+-      if ((flags & PASID_FLAG_FL5LP) && !cap_fl5lp_support(iommu->cap)) {
+-              pr_err("No 5-level paging support for first-level on %s\n",
+-                     iommu->name);
+-              return -EINVAL;
+-      }
+-
+-      pasid_pte_config_first_level(iommu, &new_pte, fsptptr, did, flags);
+-
+-      spin_lock(&iommu->lock);
+-      pte = intel_pasid_get_entry(dev, pasid);
+-      if (!pte) {
+-              spin_unlock(&iommu->lock);
+-              return -ENODEV;
+-      }
+-
+-      if (!pasid_pte_is_present(pte)) {
+-              spin_unlock(&iommu->lock);
+-              return -EINVAL;
+-      }
+-
+-      WARN_ON(old_did != pasid_get_domain_id(pte));
+-
+-      *pte = new_pte;
+-      spin_unlock(&iommu->lock);
+-
+-      intel_pasid_flush_present(iommu, dev, pasid, old_did, pte);
+-      intel_iommu_drain_pasid_prq(dev, pasid);
+-
+-      return 0;
+-}
+-
+ /*
+  * Set up the scalable mode pasid entry for second only translation type.
+  */
+@@ -528,57 +484,6 @@ int intel_pasid_setup_second_level(struc
+       return 0;
+ }
+-int intel_pasid_replace_second_level(struct intel_iommu *iommu,
+-                                   struct dmar_domain *domain,
+-                                   struct device *dev, u16 old_did,
+-                                   u32 pasid)
+-{
+-      struct pasid_entry *pte, new_pte;
+-      struct dma_pte *pgd;
+-      u64 pgd_val;
+-      u16 did;
+-
+-      /*
+-       * If hardware advertises no support for second level
+-       * translation, return directly.
+-       */
+-      if (!ecap_slts(iommu->ecap)) {
+-              pr_err("No second level translation support on %s\n",
+-                     iommu->name);
+-              return -EINVAL;
+-      }
+-
+-      pgd = domain->pgd;
+-      pgd_val = virt_to_phys(pgd);
+-      did = domain_id_iommu(domain, iommu);
+-
+-      pasid_pte_config_second_level(iommu, &new_pte, pgd_val,
+-                                    domain->agaw, did,
+-                                    domain->dirty_tracking);
+-
+-      spin_lock(&iommu->lock);
+-      pte = intel_pasid_get_entry(dev, pasid);
+-      if (!pte) {
+-              spin_unlock(&iommu->lock);
+-              return -ENODEV;
+-      }
+-
+-      if (!pasid_pte_is_present(pte)) {
+-              spin_unlock(&iommu->lock);
+-              return -EINVAL;
+-      }
+-
+-      WARN_ON(old_did != pasid_get_domain_id(pte));
+-
+-      *pte = new_pte;
+-      spin_unlock(&iommu->lock);
+-
+-      intel_pasid_flush_present(iommu, dev, pasid, old_did, pte);
+-      intel_iommu_drain_pasid_prq(dev, pasid);
+-
+-      return 0;
+-}
+-
+ /*
+  * Set up dirty tracking on a second only or nested translation type.
+  */
+@@ -691,38 +596,6 @@ int intel_pasid_setup_pass_through(struc
+       return 0;
+ }
+-int intel_pasid_replace_pass_through(struct intel_iommu *iommu,
+-                                   struct device *dev, u16 old_did,
+-                                   u32 pasid)
+-{
+-      struct pasid_entry *pte, new_pte;
+-      u16 did = FLPT_DEFAULT_DID;
+-
+-      pasid_pte_config_pass_through(iommu, &new_pte, did);
+-
+-      spin_lock(&iommu->lock);
+-      pte = intel_pasid_get_entry(dev, pasid);
+-      if (!pte) {
+-              spin_unlock(&iommu->lock);
+-              return -ENODEV;
+-      }
+-
+-      if (!pasid_pte_is_present(pte)) {
+-              spin_unlock(&iommu->lock);
+-              return -EINVAL;
+-      }
+-
+-      WARN_ON(old_did != pasid_get_domain_id(pte));
+-
+-      *pte = new_pte;
+-      spin_unlock(&iommu->lock);
+-
+-      intel_pasid_flush_present(iommu, dev, pasid, old_did, pte);
+-      intel_iommu_drain_pasid_prq(dev, pasid);
+-
+-      return 0;
+-}
+-
+ /*
+  * Set the page snoop control for a pasid entry which has been set up.
+  */
+@@ -852,69 +725,6 @@ int intel_pasid_setup_nested(struct inte
+       return 0;
+ }
+-
+-int intel_pasid_replace_nested(struct intel_iommu *iommu,
+-                             struct device *dev, u32 pasid,
+-                             u16 old_did, struct dmar_domain *domain)
+-{
+-      struct iommu_hwpt_vtd_s1 *s1_cfg = &domain->s1_cfg;
+-      struct dmar_domain *s2_domain = domain->s2_domain;
+-      u16 did = domain_id_iommu(domain, iommu);
+-      struct pasid_entry *pte, new_pte;
+-
+-      /* Address width should match the address width supported by hardware */
+-      switch (s1_cfg->addr_width) {
+-      case ADDR_WIDTH_4LEVEL:
+-              break;
+-      case ADDR_WIDTH_5LEVEL:
+-              if (!cap_fl5lp_support(iommu->cap)) {
+-                      dev_err_ratelimited(dev,
+-                                          "5-level paging not supported\n");
+-                      return -EINVAL;
+-              }
+-              break;
+-      default:
+-              dev_err_ratelimited(dev, "Invalid stage-1 address width %d\n",
+-                                  s1_cfg->addr_width);
+-              return -EINVAL;
+-      }
+-
+-      if ((s1_cfg->flags & IOMMU_VTD_S1_SRE) && !ecap_srs(iommu->ecap)) {
+-              pr_err_ratelimited("No supervisor request support on %s\n",
+-                                 iommu->name);
+-              return -EINVAL;
+-      }
+-
+-      if ((s1_cfg->flags & IOMMU_VTD_S1_EAFE) && !ecap_eafs(iommu->ecap)) {
+-              pr_err_ratelimited("No extended access flag support on %s\n",
+-                                 iommu->name);
+-              return -EINVAL;
+-      }
+-
+-      pasid_pte_config_nestd(iommu, &new_pte, s1_cfg, s2_domain, did);
+-
+-      spin_lock(&iommu->lock);
+-      pte = intel_pasid_get_entry(dev, pasid);
+-      if (!pte) {
+-              spin_unlock(&iommu->lock);
+-              return -ENODEV;
+-      }
+-
+-      if (!pasid_pte_is_present(pte)) {
+-              spin_unlock(&iommu->lock);
+-              return -EINVAL;
+-      }
+-
+-      WARN_ON(old_did != pasid_get_domain_id(pte));
+-
+-      *pte = new_pte;
+-      spin_unlock(&iommu->lock);
+-
+-      intel_pasid_flush_present(iommu, dev, pasid, old_did, pte);
+-      intel_iommu_drain_pasid_prq(dev, pasid);
+-
+-      return 0;
+-}
+ /*
+  * Interfaces to setup or teardown a pasid table to the scalable-mode
+--- a/drivers/iommu/intel/pasid.h
++++ b/drivers/iommu/intel/pasid.h
+@@ -315,20 +315,6 @@ int intel_pasid_setup_pass_through(struc
+                                  struct device *dev, u32 pasid);
+ int intel_pasid_setup_nested(struct intel_iommu *iommu, struct device *dev,
+                            u32 pasid, struct dmar_domain *domain);
+-int intel_pasid_replace_first_level(struct intel_iommu *iommu,
+-                                  struct device *dev, phys_addr_t fsptptr,
+-                                  u32 pasid, u16 did, u16 old_did, int flags);
+-int intel_pasid_replace_second_level(struct intel_iommu *iommu,
+-                                   struct dmar_domain *domain,
+-                                   struct device *dev, u16 old_did,
+-                                   u32 pasid);
+-int intel_pasid_replace_pass_through(struct intel_iommu *iommu,
+-                                   struct device *dev, u16 old_did,
+-                                   u32 pasid);
+-int intel_pasid_replace_nested(struct intel_iommu *iommu,
+-                             struct device *dev, u32 pasid,
+-                             u16 old_did, struct dmar_domain *domain);
+-
+ void intel_pasid_tear_down_entry(struct intel_iommu *iommu,
+                                struct device *dev, u32 pasid,
+                                bool fault_ignore);
index 2482fc092047bdf2da8ffbccb635b845af63eeb5..65d7bf22c79382b12b18acbddab1015143ba96e0 100644 (file)
@@ -206,3 +206,4 @@ bluetooth-hci_uart-clear-hci_uart_sending-when-write_work-is-canceled.patch
 bluetooth-iso-avoid-null-deref-of-conn-in-iso_conn_big_sync.patch
 bluetooth-l2cap-cancel-pending_rx_work-before-taking-conn-lock.patch
 bluetooth-l2cap-validate-option-length-before-reading-conf-opt-value.patch
+iommu-vt-d-fix-race-condition-during-pasid-entry-replacement.patch