diff mbox series

btrfs: change the set_page_extent_mapped() call into an ASSERT()

Message ID 20210727013942.83531-1-wqu@suse.com (mailing list archive)
State New, archived
Headers show
Series btrfs: change the set_page_extent_mapped() call into an ASSERT() | expand

Commit Message

Qu Wenruo July 27, 2021, 1:39 a.m. UTC
Btrfs uses set_page_extent_mapped() to properly setup a page.

That function would set PagePrivate, and populate needed structure for
subpage.
The timing of calling set_page_extent_mapped() happens before reading a
page or dirtying a page.
Thus when we got a page to write back, if it doesn't have PagePrivate,
it is a big problem in code logic.

Calling set_page_extent_mapped() for such page would just mask the
problem.
Furthermore, for subpage case, we call subpage error helper to clear the
page error bit before calling set_page_extent_mapped().
If we really got a page without Private bit, it can call kernel NULL
pointer dereference.

So change the set_page_extent_mapped() call to an ASSERT(), and move the
check before any page status update call.

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 fs/btrfs/extent_io.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

Comments

Qu Wenruo July 27, 2021, 10:29 a.m. UTC | #1
On 2021/7/27 上午9:39, Qu Wenruo wrote:
> Btrfs uses set_page_extent_mapped() to properly setup a page.
> 
> That function would set PagePrivate, and populate needed structure for
> subpage.
> The timing of calling set_page_extent_mapped() happens before reading a
> page or dirtying a page.
> Thus when we got a page to write back, if it doesn't have PagePrivate,
> it is a big problem in code logic.
> 
> Calling set_page_extent_mapped() for such page would just mask the
> problem.
> Furthermore, for subpage case, we call subpage error helper to clear the
> page error bit before calling set_page_extent_mapped().
> If we really got a page without Private bit, it can call kernel NULL
> pointer dereference.
> 
> So change the set_page_extent_mapped() call to an ASSERT(), and move the
> check before any page status update call.
> 
> Signed-off-by: Qu Wenruo <wqu@suse.com>

Please discard the patch.

Although I haven't hit any problem testing the patch, it's still 
possible that we have a special page that would need cow fixup.

Such page will be:

- Dirty

- Not Private
   Thus no page->private, this could still cause problem for subpage case
   though

- No EXTENT_DELALLOC flags set for any range inside the page
   Thus writepage_delalloc() will not find a delalloc range inside the
   page.

Such page will be caught by btrfs_writepage_cow_fixup(), but it will 
trigger the ASSERT() added by this patch.

Thanks,
Qu

> ---
>   fs/btrfs/extent_io.c | 12 ++++++------
>   1 file changed, 6 insertions(+), 6 deletions(-)
> 
> diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
> index 62f0ed2de2b9..219add264acf 100644
> --- a/fs/btrfs/extent_io.c
> +++ b/fs/btrfs/extent_io.c
> @@ -4099,6 +4099,12 @@ static int __extent_writepage(struct page *page, struct writeback_control *wbc,
>   
>   	WARN_ON(!PageLocked(page));
>   
> +	/*
> +	 * All dirty page to be written should have PagePrivate set by
> +	 * set_page_extent_mapped() when creating the page.
> +	 */
> +	ASSERT(PagePrivate(page));
> +
>   	btrfs_page_clear_error(btrfs_sb(inode->i_sb), page,
>   			       page_offset(page), PAGE_SIZE);
>   
> @@ -4115,12 +4121,6 @@ static int __extent_writepage(struct page *page, struct writeback_control *wbc,
>   		flush_dcache_page(page);
>   	}
>   
> -	ret = set_page_extent_mapped(page);
> -	if (ret < 0) {
> -		SetPageError(page);
> -		goto done;
> -	}
> -
>   	if (!epd->extent_locked) {
>   		ret = writepage_delalloc(BTRFS_I(inode), page, wbc,
>   					 &nr_written);
>
David Sterba July 27, 2021, 2:23 p.m. UTC | #2
On Tue, Jul 27, 2021 at 06:29:08PM +0800, Qu Wenruo wrote:
> 
> 
> On 2021/7/27 上午9:39, Qu Wenruo wrote:
> > Btrfs uses set_page_extent_mapped() to properly setup a page.
> > 
> > That function would set PagePrivate, and populate needed structure for
> > subpage.
> > The timing of calling set_page_extent_mapped() happens before reading a
> > page or dirtying a page.
> > Thus when we got a page to write back, if it doesn't have PagePrivate,
> > it is a big problem in code logic.
> > 
> > Calling set_page_extent_mapped() for such page would just mask the
> > problem.
> > Furthermore, for subpage case, we call subpage error helper to clear the
> > page error bit before calling set_page_extent_mapped().
> > If we really got a page without Private bit, it can call kernel NULL
> > pointer dereference.
> > 
> > So change the set_page_extent_mapped() call to an ASSERT(), and move the
> > check before any page status update call.
> > 
> > Signed-off-by: Qu Wenruo <wqu@suse.com>
> 
> Please discard the patch.
> 
> Although I haven't hit any problem testing the patch, it's still 
> possible that we have a special page that would need cow fixup.
> 
> Such page will be:
> 
> - Dirty
> 
> - Not Private
>    Thus no page->private, this could still cause problem for subpage case
>    though
> 
> - No EXTENT_DELALLOC flags set for any range inside the page
>    Thus writepage_delalloc() will not find a delalloc range inside the
>    page.
> 
> Such page will be caught by btrfs_writepage_cow_fixup(), but it will 
> trigger the ASSERT() added by this patch.

Hm yeah the mismatch between dirty/private/delalloc sounds exactly like
work for the cow fixup.
diff mbox series

Patch

diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 62f0ed2de2b9..219add264acf 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -4099,6 +4099,12 @@  static int __extent_writepage(struct page *page, struct writeback_control *wbc,
 
 	WARN_ON(!PageLocked(page));
 
+	/*
+	 * All dirty page to be written should have PagePrivate set by
+	 * set_page_extent_mapped() when creating the page.
+	 */
+	ASSERT(PagePrivate(page));
+
 	btrfs_page_clear_error(btrfs_sb(inode->i_sb), page,
 			       page_offset(page), PAGE_SIZE);
 
@@ -4115,12 +4121,6 @@  static int __extent_writepage(struct page *page, struct writeback_control *wbc,
 		flush_dcache_page(page);
 	}
 
-	ret = set_page_extent_mapped(page);
-	if (ret < 0) {
-		SetPageError(page);
-		goto done;
-	}
-
 	if (!epd->extent_locked) {
 		ret = writepage_delalloc(BTRFS_I(inode), page, wbc,
 					 &nr_written);