Skip to content

Commit eb791aa

Browse files
committed
iommu/amd: Fix race in increase_address_space()/fetch_pte()
The 'pt_root' and 'mode' struct members of 'struct protection_domain' need to be get/set atomically, otherwise the page-table of the domain can get corrupted. Merge the fields into one atomic64_t struct member which can be get/set atomically. Fixes: 92d420e ("iommu/amd: Relax locking in dma_ops path") Reported-by: Qian Cai <[email protected]> Signed-off-by: Joerg Roedel <[email protected]> Tested-by: Qian Cai <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Joerg Roedel <[email protected]>
1 parent 0e698df commit eb791aa

File tree

2 files changed

+112
-37
lines changed

2 files changed

+112
-37
lines changed

drivers/iommu/amd_iommu.c

Lines changed: 105 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,26 @@ static struct protection_domain *to_pdomain(struct iommu_domain *dom)
151151
return container_of(dom, struct protection_domain, domain);
152152
}
153153

154+
static void amd_iommu_domain_get_pgtable(struct protection_domain *domain,
155+
struct domain_pgtable *pgtable)
156+
{
157+
u64 pt_root = atomic64_read(&domain->pt_root);
158+
159+
pgtable->root = (u64 *)(pt_root & PAGE_MASK);
160+
pgtable->mode = pt_root & 7; /* lowest 3 bits encode pgtable mode */
161+
}
162+
163+
static u64 amd_iommu_domain_encode_pgtable(u64 *root, int mode)
164+
{
165+
u64 pt_root;
166+
167+
/* lowest 3 bits encode pgtable mode */
168+
pt_root = mode & 7;
169+
pt_root |= (u64)root;
170+
171+
return pt_root;
172+
}
173+
154174
static struct iommu_dev_data *alloc_dev_data(u16 devid)
155175
{
156176
struct iommu_dev_data *dev_data;
@@ -1397,13 +1417,18 @@ static struct page *free_sub_pt(unsigned long root, int mode,
13971417

13981418
static void free_pagetable(struct protection_domain *domain)
13991419
{
1400-
unsigned long root = (unsigned long)domain->pt_root;
1420+
struct domain_pgtable pgtable;
14011421
struct page *freelist = NULL;
1422+
unsigned long root;
1423+
1424+
amd_iommu_domain_get_pgtable(domain, &pgtable);
1425+
atomic64_set(&domain->pt_root, 0);
14021426

1403-
BUG_ON(domain->mode < PAGE_MODE_NONE ||
1404-
domain->mode > PAGE_MODE_6_LEVEL);
1427+
BUG_ON(pgtable.mode < PAGE_MODE_NONE ||
1428+
pgtable.mode > PAGE_MODE_6_LEVEL);
14051429

1406-
freelist = free_sub_pt(root, domain->mode, freelist);
1430+
root = (unsigned long)pgtable.root;
1431+
freelist = free_sub_pt(root, pgtable.mode, freelist);
14071432

14081433
free_page_list(freelist);
14091434
}
@@ -1417,24 +1442,28 @@ static bool increase_address_space(struct protection_domain *domain,
14171442
unsigned long address,
14181443
gfp_t gfp)
14191444
{
1445+
struct domain_pgtable pgtable;
14201446
unsigned long flags;
14211447
bool ret = false;
1422-
u64 *pte;
1448+
u64 *pte, root;
14231449

14241450
spin_lock_irqsave(&domain->lock, flags);
14251451

1426-
if (address <= PM_LEVEL_SIZE(domain->mode) ||
1427-
WARN_ON_ONCE(domain->mode == PAGE_MODE_6_LEVEL))
1452+
amd_iommu_domain_get_pgtable(domain, &pgtable);
1453+
1454+
if (address <= PM_LEVEL_SIZE(pgtable.mode) ||
1455+
WARN_ON_ONCE(pgtable.mode == PAGE_MODE_6_LEVEL))
14281456
goto out;
14291457

14301458
pte = (void *)get_zeroed_page(gfp);
14311459
if (!pte)
14321460
goto out;
14331461

1434-
*pte = PM_LEVEL_PDE(domain->mode,
1435-
iommu_virt_to_phys(domain->pt_root));
1436-
domain->pt_root = pte;
1437-
domain->mode += 1;
1462+
*pte = PM_LEVEL_PDE(pgtable.mode, iommu_virt_to_phys(pgtable.root));
1463+
1464+
root = amd_iommu_domain_encode_pgtable(pte, pgtable.mode + 1);
1465+
1466+
atomic64_set(&domain->pt_root, root);
14381467

14391468
ret = true;
14401469

@@ -1451,16 +1480,22 @@ static u64 *alloc_pte(struct protection_domain *domain,
14511480
gfp_t gfp,
14521481
bool *updated)
14531482
{
1483+
struct domain_pgtable pgtable;
14541484
int level, end_lvl;
14551485
u64 *pte, *page;
14561486

14571487
BUG_ON(!is_power_of_2(page_size));
14581488

1459-
while (address > PM_LEVEL_SIZE(domain->mode))
1489+
amd_iommu_domain_get_pgtable(domain, &pgtable);
1490+
1491+
while (address > PM_LEVEL_SIZE(pgtable.mode)) {
14601492
*updated = increase_address_space(domain, address, gfp) || *updated;
1493+
amd_iommu_domain_get_pgtable(domain, &pgtable);
1494+
}
1495+
14611496

1462-
level = domain->mode - 1;
1463-
pte = &domain->pt_root[PM_LEVEL_INDEX(level, address)];
1497+
level = pgtable.mode - 1;
1498+
pte = &pgtable.root[PM_LEVEL_INDEX(level, address)];
14641499
address = PAGE_SIZE_ALIGN(address, page_size);
14651500
end_lvl = PAGE_SIZE_LEVEL(page_size);
14661501

@@ -1536,16 +1571,19 @@ static u64 *fetch_pte(struct protection_domain *domain,
15361571
unsigned long address,
15371572
unsigned long *page_size)
15381573
{
1574+
struct domain_pgtable pgtable;
15391575
int level;
15401576
u64 *pte;
15411577

15421578
*page_size = 0;
15431579

1544-
if (address > PM_LEVEL_SIZE(domain->mode))
1580+
amd_iommu_domain_get_pgtable(domain, &pgtable);
1581+
1582+
if (address > PM_LEVEL_SIZE(pgtable.mode))
15451583
return NULL;
15461584

1547-
level = domain->mode - 1;
1548-
pte = &domain->pt_root[PM_LEVEL_INDEX(level, address)];
1585+
level = pgtable.mode - 1;
1586+
pte = &pgtable.root[PM_LEVEL_INDEX(level, address)];
15491587
*page_size = PTE_LEVEL_PAGE_SIZE(level);
15501588

15511589
while (level > 0) {
@@ -1806,6 +1844,7 @@ static void dma_ops_domain_free(struct protection_domain *domain)
18061844
static struct protection_domain *dma_ops_domain_alloc(void)
18071845
{
18081846
struct protection_domain *domain;
1847+
u64 *pt_root, root;
18091848

18101849
domain = kzalloc(sizeof(struct protection_domain), GFP_KERNEL);
18111850
if (!domain)
@@ -1814,12 +1853,14 @@ static struct protection_domain *dma_ops_domain_alloc(void)
18141853
if (protection_domain_init(domain))
18151854
goto free_domain;
18161855

1817-
domain->mode = PAGE_MODE_3_LEVEL;
1818-
domain->pt_root = (void *)get_zeroed_page(GFP_KERNEL);
1819-
domain->flags = PD_DMA_OPS_MASK;
1820-
if (!domain->pt_root)
1856+
pt_root = (void *)get_zeroed_page(GFP_KERNEL);
1857+
if (!pt_root)
18211858
goto free_domain;
18221859

1860+
root = amd_iommu_domain_encode_pgtable(pt_root, PAGE_MODE_3_LEVEL);
1861+
atomic64_set(&domain->pt_root, root);
1862+
domain->flags = PD_DMA_OPS_MASK;
1863+
18231864
if (iommu_get_dma_cookie(&domain->domain) == -ENOMEM)
18241865
goto free_domain;
18251866

@@ -1843,14 +1884,17 @@ static bool dma_ops_domain(struct protection_domain *domain)
18431884
static void set_dte_entry(u16 devid, struct protection_domain *domain,
18441885
bool ats, bool ppr)
18451886
{
1887+
struct domain_pgtable pgtable;
18461888
u64 pte_root = 0;
18471889
u64 flags = 0;
18481890
u32 old_domid;
18491891

1850-
if (domain->mode != PAGE_MODE_NONE)
1851-
pte_root = iommu_virt_to_phys(domain->pt_root);
1892+
amd_iommu_domain_get_pgtable(domain, &pgtable);
1893+
1894+
if (pgtable.mode != PAGE_MODE_NONE)
1895+
pte_root = iommu_virt_to_phys(pgtable.root);
18521896

1853-
pte_root |= (domain->mode & DEV_ENTRY_MODE_MASK)
1897+
pte_root |= (pgtable.mode & DEV_ENTRY_MODE_MASK)
18541898
<< DEV_ENTRY_MODE_SHIFT;
18551899
pte_root |= DTE_FLAG_IR | DTE_FLAG_IW | DTE_FLAG_V | DTE_FLAG_TV;
18561900

@@ -2375,20 +2419,23 @@ static struct protection_domain *protection_domain_alloc(void)
23752419
static struct iommu_domain *amd_iommu_domain_alloc(unsigned type)
23762420
{
23772421
struct protection_domain *pdomain;
2422+
u64 *pt_root, root;
23782423

23792424
switch (type) {
23802425
case IOMMU_DOMAIN_UNMANAGED:
23812426
pdomain = protection_domain_alloc();
23822427
if (!pdomain)
23832428
return NULL;
23842429

2385-
pdomain->mode = PAGE_MODE_3_LEVEL;
2386-
pdomain->pt_root = (void *)get_zeroed_page(GFP_KERNEL);
2387-
if (!pdomain->pt_root) {
2430+
pt_root = (void *)get_zeroed_page(GFP_KERNEL);
2431+
if (!pt_root) {
23882432
protection_domain_free(pdomain);
23892433
return NULL;
23902434
}
23912435

2436+
root = amd_iommu_domain_encode_pgtable(pt_root, PAGE_MODE_3_LEVEL);
2437+
atomic64_set(&pdomain->pt_root, root);
2438+
23922439
pdomain->domain.geometry.aperture_start = 0;
23932440
pdomain->domain.geometry.aperture_end = ~0ULL;
23942441
pdomain->domain.geometry.force_aperture = true;
@@ -2406,7 +2453,7 @@ static struct iommu_domain *amd_iommu_domain_alloc(unsigned type)
24062453
if (!pdomain)
24072454
return NULL;
24082455

2409-
pdomain->mode = PAGE_MODE_NONE;
2456+
atomic64_set(&pdomain->pt_root, PAGE_MODE_NONE);
24102457
break;
24112458
default:
24122459
return NULL;
@@ -2418,6 +2465,7 @@ static struct iommu_domain *amd_iommu_domain_alloc(unsigned type)
24182465
static void amd_iommu_domain_free(struct iommu_domain *dom)
24192466
{
24202467
struct protection_domain *domain;
2468+
struct domain_pgtable pgtable;
24212469

24222470
domain = to_pdomain(dom);
24232471

@@ -2435,7 +2483,9 @@ static void amd_iommu_domain_free(struct iommu_domain *dom)
24352483
dma_ops_domain_free(domain);
24362484
break;
24372485
default:
2438-
if (domain->mode != PAGE_MODE_NONE)
2486+
amd_iommu_domain_get_pgtable(domain, &pgtable);
2487+
2488+
if (pgtable.mode != PAGE_MODE_NONE)
24392489
free_pagetable(domain);
24402490

24412491
if (domain->flags & PD_IOMMUV2_MASK)
@@ -2518,10 +2568,12 @@ static int amd_iommu_map(struct iommu_domain *dom, unsigned long iova,
25182568
gfp_t gfp)
25192569
{
25202570
struct protection_domain *domain = to_pdomain(dom);
2571+
struct domain_pgtable pgtable;
25212572
int prot = 0;
25222573
int ret;
25232574

2524-
if (domain->mode == PAGE_MODE_NONE)
2575+
amd_iommu_domain_get_pgtable(domain, &pgtable);
2576+
if (pgtable.mode == PAGE_MODE_NONE)
25252577
return -EINVAL;
25262578

25272579
if (iommu_prot & IOMMU_READ)
@@ -2541,8 +2593,10 @@ static size_t amd_iommu_unmap(struct iommu_domain *dom, unsigned long iova,
25412593
struct iommu_iotlb_gather *gather)
25422594
{
25432595
struct protection_domain *domain = to_pdomain(dom);
2596+
struct domain_pgtable pgtable;
25442597

2545-
if (domain->mode == PAGE_MODE_NONE)
2598+
amd_iommu_domain_get_pgtable(domain, &pgtable);
2599+
if (pgtable.mode == PAGE_MODE_NONE)
25462600
return 0;
25472601

25482602
return iommu_unmap_page(domain, iova, page_size);
@@ -2553,9 +2607,11 @@ static phys_addr_t amd_iommu_iova_to_phys(struct iommu_domain *dom,
25532607
{
25542608
struct protection_domain *domain = to_pdomain(dom);
25552609
unsigned long offset_mask, pte_pgsize;
2610+
struct domain_pgtable pgtable;
25562611
u64 *pte, __pte;
25572612

2558-
if (domain->mode == PAGE_MODE_NONE)
2613+
amd_iommu_domain_get_pgtable(domain, &pgtable);
2614+
if (pgtable.mode == PAGE_MODE_NONE)
25592615
return iova;
25602616

25612617
pte = fetch_pte(domain, iova, &pte_pgsize);
@@ -2708,16 +2764,26 @@ EXPORT_SYMBOL(amd_iommu_unregister_ppr_notifier);
27082764
void amd_iommu_domain_direct_map(struct iommu_domain *dom)
27092765
{
27102766
struct protection_domain *domain = to_pdomain(dom);
2767+
struct domain_pgtable pgtable;
27112768
unsigned long flags;
2769+
u64 pt_root;
27122770

27132771
spin_lock_irqsave(&domain->lock, flags);
27142772

2773+
/* First save pgtable configuration*/
2774+
amd_iommu_domain_get_pgtable(domain, &pgtable);
2775+
27152776
/* Update data structure */
2716-
domain->mode = PAGE_MODE_NONE;
2777+
pt_root = amd_iommu_domain_encode_pgtable(NULL, PAGE_MODE_NONE);
2778+
atomic64_set(&domain->pt_root, pt_root);
27172779

27182780
/* Make changes visible to IOMMUs */
27192781
update_domain(domain);
27202782

2783+
/* Restore old pgtable in domain->ptroot to free page-table */
2784+
pt_root = amd_iommu_domain_encode_pgtable(pgtable.root, pgtable.mode);
2785+
atomic64_set(&domain->pt_root, pt_root);
2786+
27212787
/* Page-table is not visible to IOMMU anymore, so free it */
27222788
free_pagetable(domain);
27232789

@@ -2908,9 +2974,11 @@ static u64 *__get_gcr3_pte(u64 *root, int level, int pasid, bool alloc)
29082974
static int __set_gcr3(struct protection_domain *domain, int pasid,
29092975
unsigned long cr3)
29102976
{
2977+
struct domain_pgtable pgtable;
29112978
u64 *pte;
29122979

2913-
if (domain->mode != PAGE_MODE_NONE)
2980+
amd_iommu_domain_get_pgtable(domain, &pgtable);
2981+
if (pgtable.mode != PAGE_MODE_NONE)
29142982
return -EINVAL;
29152983

29162984
pte = __get_gcr3_pte(domain->gcr3_tbl, domain->glx, pasid, true);
@@ -2924,9 +2992,11 @@ static int __set_gcr3(struct protection_domain *domain, int pasid,
29242992

29252993
static int __clear_gcr3(struct protection_domain *domain, int pasid)
29262994
{
2995+
struct domain_pgtable pgtable;
29272996
u64 *pte;
29282997

2929-
if (domain->mode != PAGE_MODE_NONE)
2998+
amd_iommu_domain_get_pgtable(domain, &pgtable);
2999+
if (pgtable.mode != PAGE_MODE_NONE)
29303000
return -EINVAL;
29313001

29323002
pte = __get_gcr3_pte(domain->gcr3_tbl, domain->glx, pasid, false);

drivers/iommu/amd_iommu_types.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,15 +468,20 @@ struct protection_domain {
468468
iommu core code */
469469
spinlock_t lock; /* mostly used to lock the page table*/
470470
u16 id; /* the domain id written to the device table */
471-
int mode; /* paging mode (0-6 levels) */
472-
u64 *pt_root; /* page table root pointer */
471+
atomic64_t pt_root; /* pgtable root and pgtable mode */
473472
int glx; /* Number of levels for GCR3 table */
474473
u64 *gcr3_tbl; /* Guest CR3 table */
475474
unsigned long flags; /* flags to find out type of domain */
476475
unsigned dev_cnt; /* devices assigned to this domain */
477476
unsigned dev_iommu[MAX_IOMMUS]; /* per-IOMMU reference count */
478477
};
479478

479+
/* For decocded pt_root */
480+
struct domain_pgtable {
481+
int mode;
482+
u64 *root;
483+
};
484+
480485
/*
481486
* Structure where we save information about one hardware AMD IOMMU in the
482487
* system.

0 commit comments

Comments
 (0)