iommu/s390: Fix NULL dereference in iova_to_phys() with ZPCI_TABLE_TYPE_RFX
Subsystems (3)
Niklas Schnelle
maintainerPATCH 1/1 iommu/s390: Fix NULL dereference in iova_to_phys() with ZPCI_TABLE_TYPE_RFX
When using a 5-level translation table via ZPCI_TABLE_TYPE_RFX get_rso_from_iova() returns NULL when the region-first entry is invalid. Yet in get_rto_from_iova() the region-second origin rso is not checked to be non-NULL before accessing rso[rsx] leading to a NULL pointer dereference instead of a NULL return when iova_to_phys() is called on a unmapped IOVA. Fix this by adding the missing NULL check.
--- drivers/iommu/s390-iommu.c | 2 ++ 1 file changed, 2 insertions(+)
drivers/iommu/s390-iommu.c +2 -0
@@ -974,6 +974,8 @@ static unsigned long *get_rto_from_iova(struct s390_domain *domain,case ZPCI_TABLE_TYPE_RFX :case ZPCI_TABLE_TYPE_RSX : rso =get_rso_from_iova (domain, iova); +if (!rso) +return NULL ; rsx =calc_rsx (iova); rse =READ_ONCE (rso[rsx]);if (!reg_entry_isvalid (rse))
---
base-commit: 8d3ae59288f1e7d58d76558a6ee96d533bc5019f
change-id: 20260818-iommu_fix_iova_to_phys-5879275b95ec
Best regards,
Signature · 1 line
Niklas Schnelle
[email protected]
AI reviewThank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Intermediate IOMMU translation tables are dynamically allocated during map/unmap but never freed when empty, allowing an attacker to exhaust kernel memory.
- [High] IOMMU unmap operations needlessly allocate page tables with GFP_ATOMIC when unmapping unmapped regions, which wastes memory and causes unmap failures if allocation fails. --
--- Patch [1]: [PATCH] iommu/s390: Fix NULL dereference in iova_to_phys() with ZPCI_TABLE_TYPE_RFX --- commit 87f13b9b4d2f1a6021028f52263833bccd842526
Author: Niklas Schnelle <[email protected]>
iommu/s390: Fix NULL dereference in iova_to_phys() with ZPCI_TABLE_TYPE_RFX
When using a 5-level translation table via ZPCI_TABLE_TYPE_RFX, get_rso_from_iova() can return NULL when the region-first entry is invalid. This patch prevents a NULL pointer dereference by adding a missing NULL check for the region-second origin before accessing it.
drivers/iommu/s390-iommu.c static unsigned long *get_rto_from_iova(struct s390_domain *domain, · line 981
Quoted 13 lines
drivers/iommu/s390-iommu.c +2
-0@@ -974,6 +974,8 @@ static unsigned long *get_rto_from_iova(struct s390_domain *domain,case ZPCI_TABLE_TYPE_RFX :case ZPCI_TABLE_TYPE_RSX : rso =get_rso_from_iova (domain, iova); +if (!rso) +return NULL ; rsx =calc_rsx (iova); rse =READ_ONCE (rso[rsx]);if (!reg_entry_isvalid (rse))
[Severity: High]
This is a pre-existing issue, but is it possible to exhaust kernel memory with intermediate IOMMU translation tables?
Looking at dma_walk_cpu_trans() in drivers/iommu/s390-iommu.c:
drivers/iommu/s390-iommu.c:dma_walk_cpu_trans() {
...
sto = dma_get_seg_table_origin(&rto[rtx], gfp);
if (!sto)
return NULL;
sx = calc_sx(dma_addr);
pto = dma_get_page_table_origin(&sto[sx], gfp);
...
}These intermediate tables are dynamically allocated unconditionally, but they appear to only be freed during domain destruction:
drivers/iommu/s390-iommu.c:dma_cleanup_tables() {
...
for (rtx = 0; rtx < ZPCI_TABLE_ENTRIES; rtx++)
if (reg_entry_isvalid(domain->dma_table[rtx]))
dma_free_seg_table(domain->dma_table[rtx]);
...
}Can an unprivileged user with VFIO access repeatedly map and unmap pages at large strides across a huge IOVA aperture to leak these dynamically allocated intermediate tables and cause an out of memory condition?
[Severity: High]
This is a pre-existing issue, but does unmapping an unmapped region needlessly allocate page tables?
Looking at s390_iommu_invalidate_trans() in drivers/iommu/s390-iommu.c:
drivers/iommu/s390-iommu.c:s390_iommu_invalidate_trans() {
...
for (i = 0; i < nr_pages; i++) {
entry = dma_walk_cpu_trans(s390_domain, dma_addr, GFP_ATOMIC);
if (unlikely(!entry)) {
rc = -EINVAL;
break;
}
...
}When encountering an unmapped hole, dma_get_seg_table_origin() unconditionally allocates missing page table directories:
drivers/iommu/s390-iommu.c:dma_get_seg_table_origin() {
...
rte = READ_ONCE(*rtep);
if (reg_entry_isvalid(rte)) {
sto = get_rt_sto(rte);
} else {
sto = dma_alloc_cpu_table(gfp);
...
}If this atomic allocation fails under memory pressure during a bulk unmap, would the loop abort and return an error? Could this leave any remaining pages in the requested range fully mapped in the IOMMU while the kernel potentially frees the underlying physical memory?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Quoted 7 lines · Niklas Schnelle
When using a 5-level translation table via
ZPCI_TABLE_TYPE_RFXget_rso_from_iova()returns NULL when the region-first entry is invalid. Yet inget_rto_from_iova()the region-second origin rso is not checked to be non-NULL before accessing rso[rsx] leading to a NULL pointer dereference instead of a NULL return wheniova_to_phys()is called on a unmapped IOVA. Fix this by adding the missing NULL check.
Quoted 6 lines
Fixes 81244074b518 ("iommu/s390: allow larger region tables")Signed-off-by Niklas Schnelle<[email protected]>--- drivers/iommu/s390-iommu.c | 2 ++ 1 file changed, 2 insertions(+)
Quoted 10 lines · Niklas Schnelle
When using a 5-level translation table via
ZPCI_TABLE_TYPE_RFXget_rso_from_iova()returns NULL when the region-first entry is invalid. Yet inget_rto_from_iova()the region-second origin rso is not checked to be non-NULL before accessing rso[rsx] leading to a NULL pointer dereference instead of a NULL return wheniova_to_phys()is called on a unmapped IOVA. Fix this by adding the missing NULL check.Fixes 81244074b518 ("iommu/s390: allow larger region tables")Signed-off-by Niklas Schnelle <[email protected]>
Quoted 23 lines
--- drivers/iommu/s390-iommu.c | 2 ++ 1 file changed, 2 insertions(+)drivers/iommu/s390-iommu.c +2
-1@@ -974,6 +974,8 @@ static unsigned long *get_rto_from_iova(struct s390_domain *domain,case ZPCI_TABLE_TYPE_RFX :case ZPCI_TABLE_TYPE_RSX : rso =get_rso_from_iova (domain, iova); +if (!rso) +return NULL ; rsx =calc_rsx (iova); rse =READ_ONCE (rso[rsx]);if (!reg_entry_isvalid (rse)) ---base-commit:
8d3ae59288f1e7d58d76558a6ee96d533bc5019f
change-id: 20260818-iommu_fix_iova_to_phys-5879275b95ecBest regards,
Quoted 19 lines · Niklas Schnelle
When using a 5-level translation table via
ZPCI_TABLE_TYPE_RFXget_rso_from_iova()returns NULL when the region-first entry is invalid. Yet inget_rto_from_iova()the region-second origin rso is not checked to be non-NULL before accessing rso[rsx] leading to a NULL pointer dereference instead of a NULL return wheniova_to_phys()is called on a unmapped IOVA. Fix this by adding the missing NULL check.Fixes 81244074b518 ("iommu/s390: allow larger region tables")Signed-off-by Niklas Schnelle <[email protected]>--- drivers/iommu/s390-iommu.c | 2 ++ 1 file changed, 2 insertions(+)drivers/iommu/s390-iommu.c +0
-0
drivers/iommu/s390-iommu.c static unsigned long *get_rto_from_iova(struct s390_domain *domain, · line 981
Quoted 8 lines
drivers/iommu/s390-iommu.c +2
-0@@ -974,6 +974,8 @@ static unsigned long *get_rto_from_iova(struct s390_domain *domain,case ZPCI_TABLE_TYPE_RFX :case ZPCI_TABLE_TYPE_RSX : rso =get_rso_from_iova (domain, iova); +if (!rso) +return NULL ; rsx =calc_rsx (iova); rse =READ_ONCE (rso[rsx]);if (!reg_entry_isvalid (rse))
Looks good to me!
Signature · 4 lines
Best Regards, Benjamin Block / Linux on IBM Z Kernel Development IBM Deutschland Research & Development GmbH / https://www.ibm.com/privacy Vors. Aufs.-R.: Wolfgang Wendt / Geschäftsführung: David Faller Sitz der Ges.: Ehningen / Registergericht: AmtsG Stuttgart, HRB 243294
Quoted 7 lines · Niklas Schnelle
When using a 5-level translation table via
ZPCI_TABLE_TYPE_RFXget_rso_from_iova()returns NULL when the region-first entry is invalid. Yet inget_rto_from_iova()the region-second origin rso is not checked to be non-NULL before accessing rso[rsx] leading to a NULL pointer dereference instead of a NULL return wheniova_to_phys()is called on a unmapped IOVA. Fix this by adding the missing NULL check.
Quoted 6 lines
Fixes 81244074b518 ("iommu/s390: allow larger region tables")Signed-off-by Niklas Schnelle <[email protected]>--- drivers/iommu/s390-iommu.c | 2 ++ 1 file changed, 2 insertions(+)
Applied, thank you!
Vasily Gorbik
maintainerQuoted 8 lines · Vasily Gorbik
Quoted 7 lines · Niklas Schnelle
When using a 5-level translation table via
ZPCI_TABLE_TYPE_RFXget_rso_from_iova()returns NULL when the region-first entry is invalid. Yet inget_rto_from_iova()the region-second origin rso is not checked to be non-NULL before accessing rso[rsx] leading to a NULL pointer dereference instead of a NULL return wheniova_to_phys()is called on a unmapped IOVA. Fix this by adding the missing NULL check.
Quoted 8 lines
Quoted 6 lines
Fixes 81244074b518 ("iommu/s390: allow larger region tables")Signed-off-by Niklas Schnelle <[email protected]>--- drivers/iommu/s390-iommu.c | 2 ++ 1 file changed, 2 insertions(+)Applied, thank you!
Oh, I was too eager here. Please disregard my previous "Applied" message. This usually goes via IOMMU subsystem tree and not s390 tree.
Joerg, Will, I'll leave this one to you. Sorry for the confusion.
Quoted 6 lines · Niklas Schnelle
Fixes 81244074b518 ("iommu/s390: allow larger region tables")Signed-off-by Niklas Schnelle <[email protected]>--- drivers/iommu/s390-iommu.c | 2 ++ 1 file changed, 2 insertions(+)
Queued for -rc, thanks.