* [PATCH v3 for-6.16/block 1/3] brd: protect page with rcu
2025-05-06 6:17 [PATCH v3 for-6.16/block 0/3] brd: discard bugfix Yu Kuai
@ 2025-05-06 6:17 ` Yu Kuai
2025-05-06 6:27 ` Christoph Hellwig
2025-05-06 6:17 ` [PATCH v3 for-6.16/block 2/3] brd: fix aligned_sector from brd_do_discard() Yu Kuai
` (2 subsequent siblings)
3 siblings, 1 reply; 6+ messages in thread
From: Yu Kuai @ 2025-05-06 6:17 UTC (permalink / raw)
To: hch, axboe, kbusch
Cc: linux-block, linux-kernel, yukuai3, yukuai1, yi.zhang, yangerkun,
johnny.chenyi
From: Yu Kuai <yukuai3@huawei.com>
Currently, after fetching the page by xa_load() in IO path, there is no
protection and page can be freed concurrently by discard:
cpu0
brd_submit_bio
brd_do_bvec
page = brd_lookup_page
cpu1
brd_submit_bio
brd_do_discard
page = __xa_erase()
__free_page()
// page UAF
Fix the problem by protecting page with rcu.
Meanwhile, if page is already freed, also prevent BUG_ON() by skipping
the write, and user will get zero data later if there is no page.
Fixes: 9ead7efc6f3f ("brd: implement discard support")
Signed-off-by: Yu Kuai <yukuai3@huawei.com>
---
drivers/block/brd.c | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/drivers/block/brd.c b/drivers/block/brd.c
index fa1290992a7f..fc793d48a9c6 100644
--- a/drivers/block/brd.c
+++ b/drivers/block/brd.c
@@ -132,12 +132,18 @@ static bool brd_rw_bvec(struct brd_device *brd, struct bio *bio)
}
}
+ rcu_read_lock();
page = brd_lookup_page(brd, sector);
kaddr = bvec_kmap_local(&bv);
if (op_is_write(opf)) {
- BUG_ON(!page);
- memcpy_to_page(page, offset, kaddr, bv.bv_len);
+ /*
+ * Page can be removed by concurrent discard, it's fine to skip
+ * the write and user will read zero data if page does not
+ * exist.
+ */
+ if (page)
+ memcpy_to_page(page, offset, kaddr, bv.bv_len);
} else {
if (page)
memcpy_from_page(kaddr, page, offset, bv.bv_len);
@@ -145,11 +151,19 @@ static bool brd_rw_bvec(struct brd_device *brd, struct bio *bio)
memset(kaddr, 0, bv.bv_len);
}
kunmap_local(kaddr);
+ rcu_read_unlock();
bio_advance_iter_single(bio, &bio->bi_iter, bv.bv_len);
return true;
}
+static void brd_free_one_page(struct rcu_head *head)
+{
+ struct page *page = container_of(head, struct page, rcu_head);
+
+ __free_page(page);
+}
+
static void brd_do_discard(struct brd_device *brd, sector_t sector, u32 size)
{
sector_t aligned_sector = (sector + PAGE_SECTORS) & ~PAGE_SECTORS;
@@ -160,7 +174,7 @@ static void brd_do_discard(struct brd_device *brd, sector_t sector, u32 size)
while (size >= PAGE_SIZE && aligned_sector < rd_size * 2) {
page = __xa_erase(&brd->brd_pages, aligned_sector >> PAGE_SECTORS_SHIFT);
if (page) {
- __free_page(page);
+ call_rcu(&page->rcu_head, brd_free_one_page);
brd->brd_nr_pages--;
}
aligned_sector += PAGE_SECTORS;
--
2.39.2
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v3 for-6.16/block 2/3] brd: fix aligned_sector from brd_do_discard()
2025-05-06 6:17 [PATCH v3 for-6.16/block 0/3] brd: discard bugfix Yu Kuai
2025-05-06 6:17 ` [PATCH v3 for-6.16/block 1/3] brd: protect page with rcu Yu Kuai
@ 2025-05-06 6:17 ` Yu Kuai
2025-05-06 6:17 ` [PATCH v3 for-6.16/block 3/3] brd: fix discard end sector Yu Kuai
2025-05-06 13:48 ` [PATCH v3 for-6.16/block 0/3] brd: discard bugfix Jens Axboe
3 siblings, 0 replies; 6+ messages in thread
From: Yu Kuai @ 2025-05-06 6:17 UTC (permalink / raw)
To: hch, axboe, kbusch
Cc: linux-block, linux-kernel, yukuai3, yukuai1, yi.zhang, yangerkun,
johnny.chenyi
From: Yu Kuai <yukuai3@huawei.com>
The calculation is just wrong, fix it by round_up().
Fixes: 9ead7efc6f3f ("brd: implement discard support")
Signed-off-by: Yu Kuai <yukuai3@huawei.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
drivers/block/brd.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/block/brd.c b/drivers/block/brd.c
index fc793d48a9c6..2753fb21410b 100644
--- a/drivers/block/brd.c
+++ b/drivers/block/brd.c
@@ -166,7 +166,7 @@ static void brd_free_one_page(struct rcu_head *head)
static void brd_do_discard(struct brd_device *brd, sector_t sector, u32 size)
{
- sector_t aligned_sector = (sector + PAGE_SECTORS) & ~PAGE_SECTORS;
+ sector_t aligned_sector = round_up(sector, PAGE_SECTORS);
struct page *page;
size -= (aligned_sector - sector) * SECTOR_SIZE;
--
2.39.2
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v3 for-6.16/block 3/3] brd: fix discard end sector
2025-05-06 6:17 [PATCH v3 for-6.16/block 0/3] brd: discard bugfix Yu Kuai
2025-05-06 6:17 ` [PATCH v3 for-6.16/block 1/3] brd: protect page with rcu Yu Kuai
2025-05-06 6:17 ` [PATCH v3 for-6.16/block 2/3] brd: fix aligned_sector from brd_do_discard() Yu Kuai
@ 2025-05-06 6:17 ` Yu Kuai
2025-05-06 13:48 ` [PATCH v3 for-6.16/block 0/3] brd: discard bugfix Jens Axboe
3 siblings, 0 replies; 6+ messages in thread
From: Yu Kuai @ 2025-05-06 6:17 UTC (permalink / raw)
To: hch, axboe, kbusch
Cc: linux-block, linux-kernel, yukuai3, yukuai1, yi.zhang, yangerkun,
johnny.chenyi
From: Yu Kuai <yukuai3@huawei.com>
brd_do_discard() just aligned start sector to page, this can only work
if the discard size if at least one page. For example:
blkdiscard /dev/ram0 -o 5120 -l 1024
In this case, size = (1024 - (8192 - 5120)), which is a huge value.
Fix the problem by round_down() the end sector.
Fixes: 9ead7efc6f3f ("brd: implement discard support")
Signed-off-by: Yu Kuai <yukuai3@huawei.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
---
drivers/block/brd.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/drivers/block/brd.c b/drivers/block/brd.c
index 2753fb21410b..a3725673cf16 100644
--- a/drivers/block/brd.c
+++ b/drivers/block/brd.c
@@ -167,18 +167,21 @@ static void brd_free_one_page(struct rcu_head *head)
static void brd_do_discard(struct brd_device *brd, sector_t sector, u32 size)
{
sector_t aligned_sector = round_up(sector, PAGE_SECTORS);
+ sector_t aligned_end = round_down(
+ sector + (size >> SECTOR_SHIFT), PAGE_SECTORS);
struct page *page;
- size -= (aligned_sector - sector) * SECTOR_SIZE;
+ if (aligned_end <= aligned_sector)
+ return;
+
xa_lock(&brd->brd_pages);
- while (size >= PAGE_SIZE && aligned_sector < rd_size * 2) {
+ while (aligned_sector < aligned_end && aligned_sector < rd_size * 2) {
page = __xa_erase(&brd->brd_pages, aligned_sector >> PAGE_SECTORS_SHIFT);
if (page) {
call_rcu(&page->rcu_head, brd_free_one_page);
brd->brd_nr_pages--;
}
aligned_sector += PAGE_SECTORS;
- size -= PAGE_SIZE;
}
xa_unlock(&brd->brd_pages);
}
--
2.39.2
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH v3 for-6.16/block 0/3] brd: discard bugfix
2025-05-06 6:17 [PATCH v3 for-6.16/block 0/3] brd: discard bugfix Yu Kuai
` (2 preceding siblings ...)
2025-05-06 6:17 ` [PATCH v3 for-6.16/block 3/3] brd: fix discard end sector Yu Kuai
@ 2025-05-06 13:48 ` Jens Axboe
3 siblings, 0 replies; 6+ messages in thread
From: Jens Axboe @ 2025-05-06 13:48 UTC (permalink / raw)
To: hch, kbusch, Yu Kuai
Cc: linux-block, linux-kernel, yukuai3, yi.zhang, yangerkun,
johnny.chenyi
On Tue, 06 May 2025 14:17:53 +0800, Yu Kuai wrote:
> changes in v3:
> - rebase on the top of for-6.16/block
> - add comments in patch 1 about NULL page.
>
> changes in v2:
> - rebase on the top of the other patchset:
> https://lore.kernel.org/all/20250421072641.1311040-1-hch@lst.de/
> - merge top 2 patches from v1 into one patch;
> - add reviewed-by for patch 2,3
>
> [...]
Applied, thanks!
[1/3] brd: protect page with rcu
commit: 0e8acffc1be10d53e909b3aa43831d6c2d25a579
[2/3] brd: fix aligned_sector from brd_do_discard()
commit: d4099f8893b057ad7e8d61df76bdeaf807ebd679
[3/3] brd: fix discard end sector
commit: a26a339a654b9403f0ee1004f1db4c2b2a355460
Best regards,
--
Jens Axboe
^ permalink raw reply [flat|nested] 6+ messages in thread