mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Guilherme Giacomo Simoes <trintaeoitogc@gmail.com>
To: trintaeoitogc@gmail.com
Cc: akpm@linux-foundation.org, david@kernel.org, liam@infradead.org,
	linux-kernel@vger.kernel.org, linux-mm@kvack.org, ljs@kernel.org,
	mhocko@suse.com, rppt@kernel.org, surenb@google.com,
	vbabka@kernel.org
Subject: Re: [PATCH] mm: clear pmd in pmdp_invalidate()
Date: Tue, 27 Oct 2026 11:50:15 -0300	[thread overview]
Message-ID: <20261027145015.1338865-1-trintaeoitogc@gmail.com> (raw)
In-Reply-To: <20261027140950.1337359-1-trintaeoitogc@gmail.com>

Guilherme Giacomo Simoes <trintaeoitogc@gmail.com> wrote:
> "David Hildenbrand (Arm)" <david@kernel.org> wrote:
> > > when attempt to do a madvise in a chunk of memory allocated by mmap, the
> > > folio_split is called to split a portion of memory, and despite the path
> > > leads to pmd_mkinvalid() they do not clear the pmd and clearer wording,
> > > leading to a BUG_ON inside page_table_check_set() because the map count
> > > is more than 1.
> > >
> > > Call the page_table_check_pmd_clear() inside pmdp_invalidate() generic
> > > function to ensure that the pmd will have they map count decrement.
> > >
> >
> > The subject + description is rather confusing: you are not "clearing the pmd",
> > you tell page-table-check that the PMD was cleared.
> >
> > This is all about apge-table-check.
> Yes, you are right, i'm not clearing the pmd, I will improve this.
> 
> >
> > Can you post the actual kernel splat you ran into? Or is this generated by an LLM?
> Of course, the stack trace of error is (already decoded):
> page_table_check_set (mm/page_table_check.c:118)
> __page_table_check_ptes_set (mm/page_table_check.c:223)
> remove_migration_pte (include/linux/page_table_check.h:83 arch/riscv/include/asm/pgtable.h:642 mm/migrate.c:448)
> rmap_walk_anon (mm/rmap.c:3053)
> rmap_walk_locked (mm/rmap.c:3169 mm/rmap.c:3164)
> remove_migration_ptes (mm/migrate.c:483)
> __folio_split (mm/huge_memory.c:3640 mm/huge_memory.c:4289)
> split_folio_to_list (mm/huge_memory.c:4435 include/linux/huge_mm.h:416 mm/huge_memory.c:4499)
> madvise_cold_or_pageout_pte_range (mm/madvise.c:420)
> walk_pgd_range (mm/pagewalk.c:147 mm/pagewalk.c:237 mm/pagewalk.c:278 mm/pagewalk.c:319)
> __walk_page_range (mm/pagewalk.c:427)
> walk_page_range_vma_unsafe (mm/pagewalk.c:737)
> walk_page_range_vma (mm/pagewalk.c:747)
> madvise_cold (mm/madvise.c:594 mm/madvise.c:614)
> madvise_vma_behavior (mm/madvise.c:1364)
> madvise_walk_vmas (mm/madvise.c:1721)
> madvise_do_behavior (mm/madvise.c:1916)
> do_madvise (mm/madvise.c:2014)
> __riscv_sys_madvise (mm/madvise.c:2023 mm/madvise.c:2021 mm/madvise.c:2021)
> syscall_handler (arch/riscv/include/asm/syscall.h:112)
> do_trap_ecall_u (arch/riscv/kernel/traps.c:341)
> handle_exception (arch/riscv/kernel/entry.S:237)
> 
> And the repro.c that I writed to reproduce bug:
> ```
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <unistd.h>
> #include <sys/mman.h>
> #include <sys/syscall.h>
> #include <errno.h>
> #include <linux/mman.h>
> 
> #ifndef MADV_COLD
> #define MADV_COLD 20
> #endif
> 
> #define HPAGE_PMD_SIZE (2 * 1024 * 1024)  // 2MB huge page on RISC-V
> 
> int main(void)
> {
>         char *addr;
>         size_t len = HPAGE_PMD_SIZE * 2;  // 4MB - two huge pages
>         int ret;
> 
>         printf("Testing page_table_check bug with madvise(MADV_COLD)...\n");
>         printf("Huge page size: %zu MB\n", HPAGE_PMD_SIZE / (1024*1024));
> 
>         // Fallback: try without MAP_HUGETLB (kernel will use THP)
>         printf("starting THP...\n");
>         addr = mmap(NULL, len, PROT_READ | PROT_WRITE,
>                     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
>         if (addr == MAP_FAILED) {
>                 perror("mmap failed");
>                 return 1;
>         }
>         // Enable THP for this mapping
>         ret = madvise(addr, len, MADV_HUGEPAGE);
>         if (ret) {
>                 perror("madvise(MADV_HUGEPAGE) failed");
>                 return 1;
>         }
> 
>         printf("Allocated memory at %p\n", addr);
> 
>         // Write to the pages to ensure they're mapped
>         memset(addr, 'A', len);
>         memset(addr + HPAGE_PMD_SIZE, 'B', HPAGE_PMD_SIZE);
> 
>         // Touch pages to ensure huge page allocation
>         for (size_t i = 0; i < len; i += HPAGE_PMD_SIZE) {
>                 addr[i] = 'X';
>         }
> 
>         printf("Memory initialized, calling madvise(MADV_COLD) on partial range...\n");
> 
>         // Call madvise(MADV_COLD) on a partial range of the first huge page
>         // This should trigger the huge page split and the bug
>         ret = madvise(addr + 4096, HPAGE_PMD_SIZE - 8192, MADV_COLD);
>         if (ret) {
>                 perror("madvise(MADV_COLD) failed");
>                 // Don't return - the kernel might have crashed
>         }
> 
>         printf("madvise returned: %d (errno: %d)\n", ret, errno);
> 
>         // If we get here without kernel crash, the bug might be fixed
>         // or the conditions weren't met
>         printf("Test completed (if kernel didn't crash, bug may be fixed or not triggered)\n");
> 
>         munmap(addr, len);
>         return 0;
> }
> ```
> 
> I see this BUG here:
> https://lore.kernel.org/all/69851c62.a00a0220.37c87e.002e.GAE@google.com/T/
> 
> Yes, I'd should mencioned "Reported-by: syzbot+227179d5a8a87e9df90d@syzkaller.appspotmail.com", my bad

At first, I suspected of pmd_user_accessible_pages() and I started analyze this
function. In a brief search I found a CVE that fix similar bug:
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=21e5eca0ac9046da9918a919bc92b7b5a78d27e7
They do this:
```
-	return pmd_leaf(pmd) && (pmd_user(pmd) || pmd_user_exec(pmd));
+	return pmd_leaf(pmd) && !pmd_present_invalid(pmd) && (pmd_user(pmd) || pmd_user_exec(pmd));
```

but, inside riscv-dependent code (where this fix happen), the pmd_user_accessible_page() is:
```
static inline bool pmd_user_accessible_page(struct mm_struct *mm, unsigned long addr, pmd_t pmd)
{
	return pmd_leaf(pmd) && pmd_user(pmd);
}
```

and the pmd_leaf() do:
```
	return pmd_present(pmd) && (pmd_val(pmd) & _PAGE_LEAF);
```

and pmd_present, do:
```
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
	(pmd_val(pmd) & (_PAGE_PRESENT | _PAGE_PROT_NONE | _PAGE_LEAF))
#else
	(pmd_val(pmd) & (_PAGE_PRESENT | _PAGE_PROT_NONE));
```

So , the pmd_leaf() looks like good enough to check if the pmd is accessible to
page_table_check.

... Too , I suspected of PAGE_PROT_NONE, because the doc above they declaration say:
```
/*
 * _PAGE_PROT_NONE is set on not-present pages (and ignored by the hardware) to
 * distinguish them from swapped out pages
 */
```
And this lead me to think "if set a not-present pages, so maybe this not should
return true inside pmd_user_accessible_page".... 

So I check the `git log` to see when and why this is introduced. But how is
mentioned by git commit message:
```
commit e3613bb8afc2a9474c9214d65c8326c5ac02135e
Author: Stefan O'Rear <sorear2@gmail.com>
Date:   Sun Dec 16 13:03:36 2018 -0500

    riscv: Add pte bit to distinguish swap from invalid
    
    Previously, invalid PTEs and swap PTEs had the same binary
    representation, causing errors when attempting to unmap PROT_NONE
    mappings, including implicit unmap on exit.

    ...
```

So , they is introduced to distinguish between swap and invalid ptes.

So , I concluded that this is not the problem, but maybe the documentation is
not good enought about the PAGE_PROT_NONE flag.

And, after a lot of thoughts, I try to call page_table_check_pmd_clear() ,
inside pmdp_invalidate(). I attempt to do this because, I see any architecture
dependent pmdp_invalidate() calling this function and think that probably this
is the solution... 
I apply , test with my repro.c and , this WORK. 

I implement this inside generic pmdp_invalidate() because, in my mind this is a
generic problem and can happen in any architecture not inside riscv specific.


thanks,
guilherme

  reply	other threads:[~2026-10-27 14:50 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-27 11:38 Guilherme Giacomo Simoes
2026-10-27 12:11 ` David Hildenbrand (Arm)
2026-10-27 14:09   ` Guilherme Giacomo Simoes
2026-10-27 14:50     ` Guilherme Giacomo Simoes [this message]
2026-10-27 23:10 ` [syzbot ci] " syzbot ci

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261027145015.1338865-1-trintaeoitogc@gmail.com \
    --to=trintaeoitogc@gmail.com \
    --cc=akpm@linux-foundation.org \
    --cc=david@kernel.org \
    --cc=liam@infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=mhocko@suse.com \
    --cc=rppt@kernel.org \
    --cc=surenb@google.com \
    --cc=vbabka@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®