lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CADhLXY4Pk60+sSLtOOuR2QdTKbYXUAjwhgb7nH8qugf4DROT7w@mail.gmail.com>
Date: Wed, 3 Dec 2025 13:13:35 +0530
From: Deepanshu Kartikey <kartikey406@...il.com>
To: Zhang Yi <yi.zhang@...weicloud.com>
Cc: linux-ext4@...r.kernel.org, linux-kernel@...r.kernel.org, 
	syzbot+b0a0670332b6b3230a0a@...kaller.appspotmail.com, tytso@....edu, 
	adilger.kernel@...ger.ca, djwong@...nel.org
Subject: Re: [PATCH v2] ext4: check folio uptodate state in ext4_page_mkwrite()

On Wed, Dec 3, 2025 at 12:22 PM Zhang Yi <yi.zhang@...weicloud.com> wrote:
>
> On 12/3/2025 9:37 AM, Deepanshu Kartikey wrote:
> > On Tue, Dec 2, 2025 at 5:54 PM Zhang Yi <yi.zhang@...weicloud.com> wrote:
> >>
> >> Hi Deepanshu!
> >>
> >> On 11/30/2025 10:06 AM, Deepanshu Kartikey wrote:
> >>> On Sat, Nov 22, 2025 at 7:27 AM Deepanshu Kartikey
> >>> <kartikey406@...il.com> wrote:
> >>>>
> >>>> When delayed block allocation fails due to filesystem corruption,
> >>>> ext4's writeback error handling invalidates affected folios by calling
> >>>> mpage_release_unused_pages() with invalidate=true, which explicitly
> >>>> clears the uptodate flag:
> >>>>
> >>>>     static void mpage_release_unused_pages(..., bool invalidate)
> >>>>     {
> >>>>         ...
> >>>>         if (invalidate) {
> >>>>             block_invalidate_folio(folio, 0, folio_size(folio));
> >>>>             folio_clear_uptodate(folio);
> >>>>         }
> >>>>     }
> >>>>
> >>>> If ext4_page_mkwrite() is subsequently called on such a non-uptodate
> >>>> folio, it can proceed to mark the folio dirty without checking its
> >>>> state. This triggers a warning in __folio_mark_dirty():
> >>>>
> >>>>     WARNING: CPU: 0 PID: 5 at mm/page-writeback.c:2960
> >>>>     __folio_mark_dirty+0x578/0x880
> >>>>
> >>>>     Call Trace:
> >>>>      fault_dirty_shared_page+0x16e/0x2d0
> >>>>      do_wp_page+0x38b/0xd20
> >>>>      handle_pte_fault+0x1da/0x450
> >>>>      __handle_mm_fault+0x652/0x13b0
> >>>>      handle_mm_fault+0x22a/0x6f0
> >>>>      do_user_addr_fault+0x200/0x8a0
> >>>>      exc_page_fault+0x81/0x1b0
> >>>>
> >>>> This scenario occurs when:
> >>>> 1. A write with delayed allocation marks a folio dirty (uptodate=1)
> >>>> 2. Writeback attempts block allocation but detects filesystem corruption
> >>>> 3. Error handling calls mpage_release_unused_pages(invalidate=true),
> >>>>    which clears the uptodate flag via folio_clear_uptodate()
> >>>> 4. A subsequent ftruncate() triggers ext4_truncate()
> >>>> 5. ext4_block_truncate_page() attempts to zero the page tail
> >>>> 6. This triggers a write fault on the mmap'd page
> >>>> 7. ext4_page_mkwrite() is called with the non-uptodate folio
> >>>> 8. Without checking uptodate, it proceeds to mark the folio dirty
> >>>> 9. __folio_mark_dirty() triggers: WARN_ON_ONCE(!folio_test_uptodate())
> >>
> >> Thank you a lot for analyzing this issue and the fix patch. As I was
> >> going through the process of understanding this issue, I had one
> >> question. Is the code flow that triggers the warning as follows?
> >>
> >> wp_page_shared()
> >>   do_page_mkwrite()
> >>     ext4_page_mkwrite()
> >>       block_page_mkwrite()   //The default delalloc path
> >>         block_commit_write()
> >>           mark_buffer_dirty()
> >>             __folio_mark_dirty(0)  //'warn' is false, doesn't trigger warning
> >>         folio_mark_dirty()
> >>           ext4_dirty_folio()
> >>             block_dirty_folio  //newly_dirty is false, doesn't call __folio_mark_dirty()
> >>   fault_dirty_shared_page()
> >>     folio_mark_dirty()  //Trigger warning ?
> >>
> >> This folio has been marked as dirty. How was this warning triggered?
> >> Am I missing something?
> >>
> >> Thanks,
> >> Yi.
> >>
> >
> > Hi Yi,
> >
> > Thank you for your question about the exact flow that triggers the warning.
> >
>
> Thank you for the clarification, but there are still some details that
> are unclear.
>
> > You're correct that the code paths within ext4_page_mkwrite() and
> > block_page_mkwrite() call __folio_mark_dirty() with warn=0, so no
> > warning occurs there. The warning actually triggers later, in
> > fault_dirty_shared_page() after page_mkwrite returns.
> >
> > Here's the complete flow:
> >
> >   wp_page_shared()
> >     ↓
> >     do_page_mkwrite()
> >       ↓
> >       ext4_page_mkwrite()
> >         ↓
> >         block_page_mkwrite()
> >           ↓
> >           mark_buffer_dirty() → __folio_mark_dirty(warn=0)  // No warning
>
>              ↓
>              if (!folio_test_set_dirty(folio))
>                         //The folio will be mark as dirty --- 1
>
> >         ↓
> >         Returns success
> >     ↓
> >     fault_dirty_shared_page(vma, folio)  ← Warning triggers here
> >       ↓
> >       folio_mark_dirty(folio)
> >         ↓
> >         ext4_dirty_folio()
> >           ↓
> >           block_dirty_folio()
> >             ↓
> >             if (!folio_test_set_dirty(folio))  // Folio not already dirty
>
>               This makes me confused, this folio should be set to dirty
>               at position 1. If it is not dirty here, who cleared the dirty
>               flag for this folio?
>

Hi Yi,

Excellent question! You're absolutely right that mark_buffer_dirty()
marks the folio dirty at position 1. The key is that the folio dirty
flag is cleared later by the error handling code.

When delayed allocation fails and mpage_release_unused_pages() is
called with invalidate=true, it calls:

  block_invalidate_folio(folio, 0, folio_size(folio));

This function not only invalidates the folio but also clears the dirty
flag. Looking at the code path:

  block_invalidate_folio()
    → do_invalidate_folio()
      → Clears the dirty flag
      → Detaches buffer heads

So the sequence is:

1. First wp_page_shared(): folio becomes dirty=1, uptodate=1 (via
mark_buffer_dirty)
2. Writeback fails due to corruption
3. mpage_release_unused_pages(invalidate=true):
   - block_invalidate_folio() clears dirty flag
   - folio_clear_uptodate() clears uptodate flag
   - Result: folio is now dirty=0, uptodate=0
4. Second wp_page_shared(): called with folio dirty=0, uptodate=0

This is confirmed by my debug logs:

  [22.400513] WP_PAGE_SHARED: ENTER folio=... uptodate=0 dirty=0

The folio is BOTH non-uptodate AND non-dirty when the second
wp_page_shared() is called.

Without my fix:
- ext4_page_mkwrite() succeeds (doesn't check uptodate)
- block_page_mkwrite() tries to mark the folio dirty again
- fault_dirty_shared_page() is reached
- block_dirty_folio() finds folio not dirty (dirty=0)
- Calls __folio_mark_dirty(warn=1) with uptodate=0
- WARNING triggers

With my fix:
- ext4_page_mkwrite() checks uptodate and returns VM_FAULT_SIGBUS
- Never reaches fault_dirty_shared_page()
- No warning

Does this answer your question?

Best regards,
Deepanshu

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ