From: "David Hildenbrand (Arm)" <david@kernel.org>
To: Muchun Song <muchun.song@linux.dev>,
Oscar Salvador <osalvador@suse.de>,
Naoya Horiguchi <nao.horiguchi@gmail.com>,
Andrew Morton <akpm@linux-foundation.org>,
Mel Gorman <mgorman@suse.de>, Andy Whitcroft <apw@canonical.com>,
Miaohe Lin <linmiaohe@huawei.com>,
Michal Hocko <mhocko@suse.com>, Rik van Riel <riel@surriel.com>,
Hugh Dickins <hughd@google.com>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
Vivek Kasireddy <vivek.kasireddy@intel.com>,
Dave Airlie <airlied@redhat.com>, Jason Gunthorpe <jgg@ziepe.ca>,
Lorenzo Stoakes <ljs@kernel.org>,
"Liam R. Howlett" <liam@infradead.org>,
Vlastimil Babka <vbabka@kernel.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>,
Randy Dunlap <rdunlap@infradead.org>,
Peter Xu <peterx@redhat.com>, Breno Leitao <leitao@debian.org>,
Will Deacon <will@kernel.org>,
"Aneesh Kumar K.V" <aneesh.kumar@kernel.org>,
Nick Piggin <npiggin@gmail.com>,
Peter Zijlstra <peterz@infradead.org>,
Heiko Carstens <hca@linux.ibm.com>,
Vasily Gorbik <gor@linux.ibm.com>,
Alexander Gordeev <agordeev@linux.ibm.com>,
Christian Borntraeger <borntraeger@linux.ibm.com>,
Sven Schnelle <svens@linux.ibm.com>,
Arnd Bergmann <arnd@arndb.de>
Cc: linux-mm@kvack.org, linux-kernel@vger.kernel.org,
Gerd Hoffmann <kraxel@redhat.com>,
linux-doc@vger.kernel.org, linux-arch@vger.kernel.org,
linux-s390@vger.kernel.org,
"David Hildenbrand (Arm)" <david@kernel.org>,
stable@kernel.org, Josef Bacik <josef@toxicpanda.com>
Subject: [PATCH 08/15] mm/hugetlb: serialize page-cache allocations with truncation
Date: Wed, 28 Oct 2026 21:13:36 +0100 [thread overview]
Message-ID: <20261028-fixes_hugetlb-v1-8-a0a033db180f@kernel.org> (raw)
In-Reply-To: <20261028-fixes_hugetlb-v1-0-a0a033db180f@kernel.org>
Truncation can remove an inode reservation while a fault is still
allocating its folio. UFFDIO_COPY and memfd pinning can also consume
reservations beyond the new i_size. This can underflow resv_huge_pages
and leave folios beyond EOF.
The main issue is that truncation takes the fault mutex for folios in
the page-cache, but it has no clue about faults that are still being
processed concurrently.
Matthew Wilcox raised mapping->invalidate_lock in a different upstream
hugetlb locking discussion:
"although I wonder if we still need the fault mutex array, given that we
now have mapping->invalidate_lock?"
While it likely cannot remove the mutex array entirely (concurrent page
faults have to sync ... ), we can at least use it to fix this problem.
Hold mapping->invalidate_lock shared across the size check, allocation
and page cache insertion or reservation rollback. Truncation takes it
exclusively around i_size_write() only for now: it waits for concurrent
allocations to finish updating the page-cache or rolling back reservations
before changing the size. Subsequent allocations see the updated i_size.
Let truncation release the lock for now before unmapping and removing
folios to preserve the existing lock ordering. Cached-folio faults and
private allocations do not acquire the new lock.
Use it to synchronize with truncation, keeping the fault mutexes to
serialize allocations for the same index.
Locking order is now:
fault mutex -> hugetlb VMA lock (read) -> invalidate lock (read) ->
folio lock -> PTE lock
And on truncation:
i_rwsem (write) -> invalidate lock (write)
Add hugetlb_pgoff_valid() to not repeat the same size check multiple
times. Memfd needs a bit of care, as it traditionally allowed partial
folios to be pinned: the size check in memfd_pin_folios() allows for
that.
Fixes: 1c9e8def43a3 ("userfaultfd: hugetlbfs: add UFFDIO_COPY support for shared mappings")
Fixes: 188a39725ad7 ("hugetlbfs: revert use i_mmap_rwsem to address page fault/truncate race")
Fixes: 89c1905d9c14 ("mm/gup: introduce memfd_pin_folios() for pinning memfd folios")
Cc: stable@kernel.org
Reported-by: Josef Bacik <josef@toxicpanda.com>
Link: https://lore.kernel.org/all/ajMfdQf-Fspwmstr@casper.infradead.org/
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
fs/hugetlbfs/inode.c | 15 +++++++++++----
include/linux/hugetlb.h | 12 ++++++++++++
mm/hugetlb.c | 45 +++++++++++++++++++++++++++++++++------------
mm/memfd.c | 8 ++++++++
4 files changed, 64 insertions(+), 16 deletions(-)
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index fa367fc9e019..174455e10d0f 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -545,10 +545,9 @@ static void remove_inode_single_folio(struct hstate *h, struct inode *inode,
* truncation is indicated by end of range being LLONG_MAX
* In this case, we first scan the range and release found pages.
* After releasing pages, hugetlb_unreserve_pages cleans up region/reserve
- * maps and global counts. Page faults can race with truncation.
- * During faults, hugetlb_no_page() checks i_size before page allocation,
- * and again after obtaining page table lock. It will 'back out'
- * allocations in the truncated range.
+ * maps and global counts. hugetlb_vmtruncate() updates i_size under
+ * invalidate_lock to synchronize with new page cache allocations from
+ * page faults, uffd, etc.
* hole punch is indicated if end is not LLONG_MAX
* In the hole punch case we scan the range and release found pages.
* Only when releasing a page is the associated region/reserve map
@@ -622,7 +621,15 @@ static void hugetlb_vmtruncate(struct inode *inode, loff_t offset)
BUG_ON(offset & ~huge_page_mask(h));
pgoff = offset >> PAGE_SHIFT;
+ filemap_invalidate_lock(mapping);
i_size_write(inode, offset);
+ /*
+ * The size check now prevents new page-cache allocations in the
+ * truncated range. Existing folios and reservations are cleaned up
+ * below.
+ */
+ filemap_invalidate_unlock(mapping);
+
i_mmap_lock_write(mapping);
if (mapping_mapped(mapping))
hugetlb_vmdelete_list(mapping, pgoff, 0, ZAP_FLAG_DROP_MARKER);
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index d49cb15d13e6..2a3fde411d74 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -823,6 +823,18 @@ static inline unsigned huge_page_shift(struct hstate *h)
return h->order + PAGE_SHIFT;
}
+static inline bool hugetlb_pgoff_valid(struct address_space *mapping,
+ pgoff_t idx, bool partial)
+{
+ struct hstate *h = hstate_inode(mapping->host);
+ u64 size = i_size_read(mapping->host);
+
+ if (partial)
+ size = round_up(size, huge_page_size(h));
+
+ return idx < (size >> huge_page_shift(h));
+}
+
/**
* hugetlb_linear_page_index() - linear_page_index() but in hugetlb
* page size granularity.
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 2fdd72897db8..7f240cd7dea0 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -5869,6 +5869,7 @@ static bool hugetlb_pte_stable(struct hstate *h, struct mm_struct *mm, unsigned
static vm_fault_t hugetlb_no_page(struct address_space *mapping,
struct vm_fault *vmf)
{
+ const bool pagecache_folio = vmf->vma->vm_flags & VM_MAYSHARE;
u32 hash = hugetlb_fault_mutex_hash(mapping, vmf->pgoff);
bool new_folio, new_anon_folio = false;
struct vm_area_struct *vma = vmf->vma;
@@ -5877,7 +5878,6 @@ static vm_fault_t hugetlb_no_page(struct address_space *mapping,
vm_fault_t ret = VM_FAULT_SIGBUS;
bool folio_locked = true;
struct folio *folio;
- unsigned long size;
pte_t new_pte;
/*
@@ -5899,8 +5899,7 @@ static vm_fault_t hugetlb_no_page(struct address_space *mapping,
new_folio = false;
folio = filemap_lock_hugetlb_folio(h, mapping, vmf->pgoff);
if (IS_ERR(folio)) {
- size = i_size_read(mapping->host) >> huge_page_shift(h);
- if (vmf->pgoff >= size)
+ if (!hugetlb_pgoff_valid(mapping, vmf->pgoff, /* partial= */ false))
goto out;
/* Check for page in userfault range */
if (userfaultfd_missing(vma)) {
@@ -5930,10 +5929,17 @@ static vm_fault_t hugetlb_no_page(struct address_space *mapping,
VM_UFFD_MISSING);
}
- if (!(vma->vm_flags & VM_MAYSHARE)) {
+ if (!pagecache_folio) {
ret = __vmf_anon_prepare(vmf);
if (unlikely(ret))
goto out;
+ } else {
+ /* Recheck EOF before consuming an inode reservation. */
+ filemap_invalidate_lock_shared(mapping);
+ if (!hugetlb_pgoff_valid(mapping, vmf->pgoff, /* partial= */ false)) {
+ filemap_invalidate_unlock_shared(mapping);
+ goto out;
+ }
}
folio = alloc_hugetlb_folio(vma, vmf->address, false);
@@ -5954,13 +5960,15 @@ static vm_fault_t hugetlb_no_page(struct address_space *mapping,
ret = vmf_error(PTR_ERR(folio));
else
ret = 0;
+ if (pagecache_folio)
+ filemap_invalidate_unlock_shared(mapping);
goto out;
}
folio_zero_user(folio, vmf->real_address);
__folio_mark_uptodate(folio);
new_folio = true;
- if (vma->vm_flags & VM_MAYSHARE) {
+ if (pagecache_folio) {
int err = hugetlb_add_to_page_cache(folio, mapping,
vmf->pgoff);
if (err) {
@@ -5975,8 +5983,10 @@ static vm_fault_t hugetlb_no_page(struct address_space *mapping,
folio);
folio_put(folio);
ret = VM_FAULT_SIGBUS;
+ filemap_invalidate_unlock_shared(mapping);
goto out;
}
+ filemap_invalidate_unlock_shared(mapping);
} else {
new_anon_folio = true;
folio_lock(folio);
@@ -6401,14 +6411,15 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
uffd_flags_t flags,
struct folio **foliop)
{
+ const int vm_shared = dst_vma->vm_flags & VM_SHARED;
+ const bool is_continue = uffd_flags_mode_is(flags, MFILL_ATOMIC_CONTINUE);
+ const bool alloc_pagecache_folio = vm_shared && !is_continue;
struct mm_struct *dst_mm = dst_vma->vm_mm;
- bool is_continue = uffd_flags_mode_is(flags, MFILL_ATOMIC_CONTINUE);
bool wp_enabled = (flags & MFILL_ATOMIC_WP);
struct hstate *h = hstate_vma(dst_vma);
struct address_space *mapping = dst_vma->vm_file->f_mapping;
pgoff_t idx = vma_hugecache_offset(h, dst_vma, dst_addr);
unsigned long size = huge_page_size(h);
- int vm_shared = dst_vma->vm_flags & VM_SHARED;
pte_t _dst_pte;
spinlock_t *ptl;
int ret = -ENOMEM;
@@ -6435,6 +6446,18 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
return 0;
}
+ if (alloc_pagecache_folio) {
+ filemap_invalidate_lock_shared(mapping);
+ if (!hugetlb_pgoff_valid(mapping, idx, /* partial= */ false)) {
+ if (*foliop) {
+ folio_put(*foliop);
+ *foliop = NULL;
+ }
+ ret = -EFAULT;
+ goto out;
+ }
+ }
+
if (is_continue) {
ret = -EFAULT;
folio = filemap_lock_hugetlb_folio(h, mapping, idx);
@@ -6532,11 +6555,7 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
WARN_ON_ONCE(!folio_test_uptodate(folio));
/* Add shared, newly allocated pages to the page cache. */
- if (vm_shared && !is_continue) {
- ret = -EFAULT;
- if (idx >= (i_size_read(mapping->host) >> huge_page_shift(h)))
- goto out_release_nounlock;
-
+ if (alloc_pagecache_folio) {
/*
* Serialization between remove_inode_hugepages() and
* hugetlb_add_to_page_cache() below happens through the
@@ -6607,6 +6626,8 @@ int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
spin_unlock(ptl);
ret = 0;
out:
+ if (alloc_pagecache_folio)
+ filemap_invalidate_unlock_shared(mapping);
return ret;
out_release_unlock:
spin_unlock(ptl);
diff --git a/mm/memfd.c b/mm/memfd.c
index 0f6fff004f5e..4650a69aa7a6 100644
--- a/mm/memfd.c
+++ b/mm/memfd.c
@@ -96,6 +96,12 @@ struct folio *memfd_alloc_folio(struct file *memfd, pgoff_t idx)
hash = hugetlb_fault_mutex_hash(memfd->f_mapping, idx);
mutex_lock(&hugetlb_fault_mutex_table[hash]);
+ filemap_invalidate_lock_shared(memfd->f_mapping);
+ if (!hugetlb_pgoff_valid(memfd->f_mapping, idx, /* partial= */ true)) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
nr_resv = hugetlb_reserve_pages(inode, idx, idx + 1, NULL, EMPTY_VMA_FLAGS);
if (nr_resv < 0) {
err = nr_resv;
@@ -131,6 +137,7 @@ struct folio *memfd_alloc_folio(struct file *memfd, pgoff_t idx)
hugetlb_set_folio_subpool(folio, subpool_inode(inode));
folio_unlock(folio);
+ filemap_invalidate_unlock_shared(memfd->f_mapping);
mutex_unlock(&hugetlb_fault_mutex_table[hash]);
return folio;
}
@@ -138,6 +145,7 @@ struct folio *memfd_alloc_folio(struct file *memfd, pgoff_t idx)
if (nr_resv > 0)
hugetlb_unreserve_pages(inode, idx, idx + 1, 0);
out_unlock:
+ filemap_invalidate_unlock_shared(memfd->f_mapping);
mutex_unlock(&hugetlb_fault_mutex_table[hash]);
return ERR_PTR(err);
}
--
2.43.0
next prev parent reply other threads:[~2026-10-28 20:15 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 20:13 [PATCH 00/15] mm/hugetlb: various fixes David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 01/15] hugetlbfs: fix integer overflow in fallocate() David Hildenbrand (Arm)
2026-10-29 3:37 ` Muchun Song
2026-10-29 9:05 ` Oscar Salvador (SUSE)
2026-10-29 20:53 ` Ackerley Tng
2026-10-28 20:13 ` [PATCH 02/15] mm/hugetlb: compare against persistent count when freeing lowmem folios David Hildenbrand (Arm)
2026-10-29 3:39 ` Muchun Song
2026-10-28 20:13 ` [PATCH 03/15] mm/hugetlb: preserve reservations when shrinking on HIGHMEM David Hildenbrand (Arm)
2026-10-29 3:41 ` Muchun Song
2026-10-28 20:13 ` [PATCH 04/15] mm/hugetlb: account temporary folios separately David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 05/15] mm/hugetlb: keep surplus folios when the pool shrinks David Hildenbrand (Arm)
2026-10-29 3:51 ` Muchun Song
2026-10-28 20:13 ` [PATCH 06/15] mm/hugetlb: fix mremap() racing with page migration David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 07/15] hugetlbfs: fix unmapping folios from private VMAs David Hildenbrand (Arm)
2026-10-28 20:13 ` David Hildenbrand (Arm) [this message]
2026-10-28 20:13 ` [PATCH 09/15] mm/memfd: recheck the page cache before allocating a hugetlb folio David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 10/15] mm/hugetlb: count again if the reserve map shrank David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 11/15] hugetlbfs: do not grow a sealed file in mmap() David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 12/15] mm/hugetlb: keep surplus folios that are reserved David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 13/15] mm/hugetlb: fix reservation leak on min_size= mounts David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 14/15] RFC: mm/hugetlb: account restored reservations before freeing folios David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 15/15] RFC: mm/hugetlb: delay anon folio rmap removal until after the TLB flush David Hildenbrand (Arm)
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=20261028-fixes_hugetlb-v1-8-a0a033db180f@kernel.org \
--to=david@kernel.org \
--cc=agordeev@linux.ibm.com \
--cc=airlied@redhat.com \
--cc=akpm@linux-foundation.org \
--cc=aneesh.kumar@kernel.org \
--cc=apw@canonical.com \
--cc=arnd@arndb.de \
--cc=baolin.wang@linux.alibaba.com \
--cc=borntraeger@linux.ibm.com \
--cc=corbet@lwn.net \
--cc=gor@linux.ibm.com \
--cc=hca@linux.ibm.com \
--cc=hughd@google.com \
--cc=jgg@ziepe.ca \
--cc=josef@toxicpanda.com \
--cc=kraxel@redhat.com \
--cc=leitao@debian.org \
--cc=liam@infradead.org \
--cc=linmiaohe@huawei.com \
--cc=linux-arch@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-s390@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=mgorman@suse.de \
--cc=mhocko@suse.com \
--cc=muchun.song@linux.dev \
--cc=nao.horiguchi@gmail.com \
--cc=npiggin@gmail.com \
--cc=osalvador@suse.de \
--cc=peterx@redhat.com \
--cc=peterz@infradead.org \
--cc=rdunlap@infradead.org \
--cc=riel@surriel.com \
--cc=rppt@kernel.org \
--cc=skhan@linuxfoundation.org \
--cc=stable@kernel.org \
--cc=surenb@google.com \
--cc=svens@linux.ibm.com \
--cc=vbabka@kernel.org \
--cc=vivek.kasireddy@intel.com \
--cc=will@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®