[PATCH 2/2] swiotlb: Enforce page alignment in swiotlb_alloc()

Will Deacon posted 2 patches 1 year, 11 months ago
There is a newer version of this series
[PATCH 2/2] swiotlb: Enforce page alignment in swiotlb_alloc()
Posted by Will Deacon 1 year, 11 months ago
When allocating pages from a restricted DMA pool in swiotlb_alloc(),
the buffer address is blindly converted to a 'struct page *' that is
returned to the caller. In the unlikely event of an allocation bug,
page-unaligned addresses are not detected and slots can silently be
double-allocated.

Add a simple check of the buffer alignment in swiotlb_alloc() to make
debugging a little easier if something has gone wonky.

Cc: Christoph Hellwig <hch@lst.de>
Cc: Marek Szyprowski <m.szyprowski@samsung.com>
Cc: Robin Murphy <robin.murphy@arm.com>
Cc: Petr Tesarik <petr.tesarik1@huawei-partners.com>
Cc: Dexuan Cui <decui@microsoft.com>
Signed-off-by: Will Deacon <will@kernel.org>
---
 kernel/dma/swiotlb.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
index 25febb9e670c..92433ea9f2d2 100644
--- a/kernel/dma/swiotlb.c
+++ b/kernel/dma/swiotlb.c
@@ -1647,6 +1647,12 @@ struct page *swiotlb_alloc(struct device *dev, size_t size)
 		return NULL;
 
 	tlb_addr = slot_addr(pool->start, index);
+	if (!PAGE_ALIGNED(tlb_addr)) {
+		dev_WARN_ONCE(dev, 1, "Cannot return 'struct page *' for non page-aligned swiotlb addr 0x%pa.\n",
+			      &tlb_addr);
+		swiotlb_release_slots(dev, tlb_addr);
+		return NULL;
+	}
 
 	return pfn_to_page(PFN_DOWN(tlb_addr));
 }
-- 
2.43.0.429.g432eaa2c6b-goog
Re: [PATCH 2/2] swiotlb: Enforce page alignment in swiotlb_alloc()
Posted by Christoph Hellwig 1 year, 11 months ago
On Fri, Jan 26, 2024 at 03:19:56PM +0000, Will Deacon wrote:
> diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
> index 25febb9e670c..92433ea9f2d2 100644
> --- a/kernel/dma/swiotlb.c
> +++ b/kernel/dma/swiotlb.c
> @@ -1647,6 +1647,12 @@ struct page *swiotlb_alloc(struct device *dev, size_t size)
>  		return NULL;
>  
>  	tlb_addr = slot_addr(pool->start, index);
> +	if (!PAGE_ALIGNED(tlb_addr)) {
> +		dev_WARN_ONCE(dev, 1, "Cannot return 'struct page *' for non page-aligned swiotlb addr 0x%pa.\n",
> +			      &tlb_addr);
> +		swiotlb_release_slots(dev, tlb_addr);
> +		return NULL;
> +	}
>  
>  	return pfn_to_page(PFN_DOWN(tlb_addr));

So PFN_DOWN aligns the address and thus per se converting the unaligned
address isn't a problem.  That being said swiotlb obviously should never
allocate unaligned addresses, but the placement of this check feels
odd to me.  Also because it only catches swiotlb_alloc and not the
map side.

Maybe just throw a WARN_ON_ONCE into slot_addr() ?

>  }
> -- 
> 2.43.0.429.g432eaa2c6b-goog
---end quoted text---
Re: [PATCH 2/2] swiotlb: Enforce page alignment in swiotlb_alloc()
Posted by Will Deacon 1 year, 10 months ago
On Mon, Jan 29, 2024 at 07:08:53AM +0100, Christoph Hellwig wrote:
> On Fri, Jan 26, 2024 at 03:19:56PM +0000, Will Deacon wrote:
> > diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
> > index 25febb9e670c..92433ea9f2d2 100644
> > --- a/kernel/dma/swiotlb.c
> > +++ b/kernel/dma/swiotlb.c
> > @@ -1647,6 +1647,12 @@ struct page *swiotlb_alloc(struct device *dev, size_t size)
> >  		return NULL;
> >  
> >  	tlb_addr = slot_addr(pool->start, index);
> > +	if (!PAGE_ALIGNED(tlb_addr)) {
> > +		dev_WARN_ONCE(dev, 1, "Cannot return 'struct page *' for non page-aligned swiotlb addr 0x%pa.\n",
> > +			      &tlb_addr);
> > +		swiotlb_release_slots(dev, tlb_addr);
> > +		return NULL;
> > +	}
> >  
> >  	return pfn_to_page(PFN_DOWN(tlb_addr));
> 
> So PFN_DOWN aligns the address and thus per se converting the unaligned
> address isn't a problem.

Hmm, I'm not sure I follow why it isn't a problem. If the first 2KiB slot
of the 4KiB page has already been allocated to somebody else, isn't it a
big problem to align down like that? Maybe I should word the warning
message a bit better -- how about:

  "Cannot allocate pages from non page-aligned swiotlb addr 0x%pa.\n"

?

> That being said swiotlb obviously should never
> allocate unaligned addresses, but the placement of this check feels
> odd to me.  Also because it only catches swiotlb_alloc and not the
> map side.
> 
> Maybe just throw a WARN_ON_ONCE into slot_addr() ?

Everything is slot-aligned, so I don't think slot_addr() can detect
this. I put the check in swiotlb_alloc() because I think that's the only
place where we assume that a slot address is page-aligned. I don't think
the map path particularly cares, but if you prefer to have the warning
there too then I think we'd have to stick it at the end of
swiotlb_search_pool_area() (effectively just checking that the returned
slot is consistent with the 'alloc_align_mask' parameter).

Will
Re: [PATCH 2/2] swiotlb: Enforce page alignment in swiotlb_alloc()
Posted by Christoph Hellwig 1 year, 10 months ago
On Mon, Jan 29, 2024 at 07:49:40PM +0000, Will Deacon wrote:
> > >  	return pfn_to_page(PFN_DOWN(tlb_addr));
> > 
> > So PFN_DOWN aligns the address and thus per se converting the unaligned
> > address isn't a problem.
> 
> Hmm, I'm not sure I follow why it isn't a problem. If the first 2KiB slot
> of the 4KiB page has already been allocated to somebody else, isn't it a
> big problem to align down like that? Maybe I should word the warning
> message a bit better -- how about:

But the problem is that it's used, not that we can't create a page
from a non-aligned address.

> 
>   "Cannot allocate pages from non page-aligned swiotlb addr 0x%pa.\n"

That sounds better.
Re: [PATCH 2/2] swiotlb: Enforce page alignment in swiotlb_alloc()
Posted by Petr Tesařík 1 year, 11 months ago
On Mon, 29 Jan 2024 07:08:53 +0100
Christoph Hellwig <hch@lst.de> wrote:

> On Fri, Jan 26, 2024 at 03:19:56PM +0000, Will Deacon wrote:
> > diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
> > index 25febb9e670c..92433ea9f2d2 100644
> > --- a/kernel/dma/swiotlb.c
> > +++ b/kernel/dma/swiotlb.c
> > @@ -1647,6 +1647,12 @@ struct page *swiotlb_alloc(struct device *dev, size_t size)
> >  		return NULL;
> >  
> >  	tlb_addr = slot_addr(pool->start, index);
> > +	if (!PAGE_ALIGNED(tlb_addr)) {
> > +		dev_WARN_ONCE(dev, 1, "Cannot return 'struct page *' for non page-aligned swiotlb addr 0x%pa.\n",
> > +			      &tlb_addr);
> > +		swiotlb_release_slots(dev, tlb_addr);
> > +		return NULL;
> > +	}
> >  
> >  	return pfn_to_page(PFN_DOWN(tlb_addr));  
> 
> So PFN_DOWN aligns the address and thus per se converting the unaligned
> address isn't a problem.  That being said swiotlb obviously should never
> allocate unaligned addresses, but the placement of this check feels
> odd to me.  Also because it only catches swiotlb_alloc and not the
> map side.

We may have to rethink how alignment constraints are interpreted. See
also my reply to PATCH 1/2.

> Maybe just throw a WARN_ON_ONCE into slot_addr() ?

Yes.

Or, what if I write a KUnit test suite for swiotlb to combat this
constant stream of various regressions?

Petr T
Re: [PATCH 2/2] swiotlb: Enforce page alignment in swiotlb_alloc()
Posted by Christoph Hellwig 1 year, 11 months ago
On Mon, Jan 29, 2024 at 08:43:26AM +0100, Petr Tesařík wrote:
> > So PFN_DOWN aligns the address and thus per se converting the unaligned
> > address isn't a problem.  That being said swiotlb obviously should never
> > allocate unaligned addresses, but the placement of this check feels
> > odd to me.  Also because it only catches swiotlb_alloc and not the
> > map side.
> 
> We may have to rethink how alignment constraints are interpreted. See
> also my reply to PATCH 1/2.
> 
> > Maybe just throw a WARN_ON_ONCE into slot_addr() ?
> 
> Yes.
> 
> Or, what if I write a KUnit test suite for swiotlb to combat this
> constant stream of various regressions?

Both sounds good to me.

Re: [PATCH 2/2] swiotlb: Enforce page alignment in swiotlb_alloc()
Posted by Petr Tesařík 1 year, 11 months ago
On Fri, 26 Jan 2024 15:19:56 +0000
Will Deacon <will@kernel.org> wrote:

> When allocating pages from a restricted DMA pool in swiotlb_alloc(),
> the buffer address is blindly converted to a 'struct page *' that is
> returned to the caller. In the unlikely event of an allocation bug,
> page-unaligned addresses are not detected and slots can silently be
> double-allocated.
> 
> Add a simple check of the buffer alignment in swiotlb_alloc() to make
> debugging a little easier if something has gone wonky.
> 
> Cc: Christoph Hellwig <hch@lst.de>
> Cc: Marek Szyprowski <m.szyprowski@samsung.com>
> Cc: Robin Murphy <robin.murphy@arm.com>
> Cc: Petr Tesarik <petr.tesarik1@huawei-partners.com>
> Cc: Dexuan Cui <decui@microsoft.com>
> Signed-off-by: Will Deacon <will@kernel.org>
> ---
>  kernel/dma/swiotlb.c | 6 ++++++
>  1 file changed, 6 insertions(+)
> 
> diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
> index 25febb9e670c..92433ea9f2d2 100644
> --- a/kernel/dma/swiotlb.c
> +++ b/kernel/dma/swiotlb.c
> @@ -1647,6 +1647,12 @@ struct page *swiotlb_alloc(struct device *dev, size_t size)
>  		return NULL;
>  
>  	tlb_addr = slot_addr(pool->start, index);
> +	if (!PAGE_ALIGNED(tlb_addr)) {
> +		dev_WARN_ONCE(dev, 1, "Cannot return 'struct page *' for non page-aligned swiotlb addr 0x%pa.\n",
> +			      &tlb_addr);
> +		swiotlb_release_slots(dev, tlb_addr);
> +		return NULL;
> +	}

Is there a reason not to use BUG_ON()? If yes, I would at least go for:

+	if (unlikely(!PAGE_ALIGNED(tlb_addr))) {

Other than that, yes, such cheap sanity checking looks like a good idea.

Petr T
Re: [PATCH 2/2] swiotlb: Enforce page alignment in swiotlb_alloc()
Posted by Will Deacon 1 year, 10 months ago
On Fri, Jan 26, 2024 at 05:23:55PM +0100, Petr Tesařík wrote:
> On Fri, 26 Jan 2024 15:19:56 +0000
> Will Deacon <will@kernel.org> wrote:
> 
> > When allocating pages from a restricted DMA pool in swiotlb_alloc(),
> > the buffer address is blindly converted to a 'struct page *' that is
> > returned to the caller. In the unlikely event of an allocation bug,
> > page-unaligned addresses are not detected and slots can silently be
> > double-allocated.
> > 
> > Add a simple check of the buffer alignment in swiotlb_alloc() to make
> > debugging a little easier if something has gone wonky.
> > 
> > Cc: Christoph Hellwig <hch@lst.de>
> > Cc: Marek Szyprowski <m.szyprowski@samsung.com>
> > Cc: Robin Murphy <robin.murphy@arm.com>
> > Cc: Petr Tesarik <petr.tesarik1@huawei-partners.com>
> > Cc: Dexuan Cui <decui@microsoft.com>
> > Signed-off-by: Will Deacon <will@kernel.org>
> > ---
> >  kernel/dma/swiotlb.c | 6 ++++++
> >  1 file changed, 6 insertions(+)
> > 
> > diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
> > index 25febb9e670c..92433ea9f2d2 100644
> > --- a/kernel/dma/swiotlb.c
> > +++ b/kernel/dma/swiotlb.c
> > @@ -1647,6 +1647,12 @@ struct page *swiotlb_alloc(struct device *dev, size_t size)
> >  		return NULL;
> >  
> >  	tlb_addr = slot_addr(pool->start, index);
> > +	if (!PAGE_ALIGNED(tlb_addr)) {
> > +		dev_WARN_ONCE(dev, 1, "Cannot return 'struct page *' for non page-aligned swiotlb addr 0x%pa.\n",
> > +			      &tlb_addr);
> > +		swiotlb_release_slots(dev, tlb_addr);
> > +		return NULL;
> > +	}
> 
> Is there a reason not to use BUG_ON()? If yes, I would at least go for:
> 
> +	if (unlikely(!PAGE_ALIGNED(tlb_addr))) {
> 
> Other than that, yes, such cheap sanity checking looks like a good idea.

BUG_ON() is generally frowned upon unless there's really no way to proceed.
Since we can fail the allocation here, I think that's the best bet (and hope
that whoever wanted the buffer isn't all that important).

I'll add the unlikely() in v2, although it sounds like Christoph wants
this moving anyway.

Will