From: sashiko-bot@kernel.org
To: "Edward Adam Davis" <eadavis@sina.com>, linux-mm@kvack.org
Subject: Re: [PATCH for Sashiko] hugetlb: supports unmapping private mappings
Date: Wed, 28 Oct 2026 14:33:50 +0000 [thread overview]
Message-ID: <sashiko-outbox-187992@kernel.org> (raw)
In-Reply-To: <20261028054620.275525-1-eadavis@sina.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] hugetlbfs: use-after-free and unbalanced unlock in hugetlb_unmap_file_folio()
- [Critical] hugetlbfs: unvalidated private vma use-after-free in hugetlb_unmap_file_folio()
- [Critical] hugetlbfs: resv_map use-after-free on unlock in hugetlb_unmap_file_folio()
--
commit cc572a12212d775135723329431694314f130c15
Author: Edward Adam Davis <eadavis@sina.com>
hugetlb: supports unmapping private mappings
This commit updates hugetlb_unmap_file_folio() to take a reference on a
private mapping's resv_map instead of assuming vma->vm_private_data points
to a struct hugetlb_vma_lock when hugetlb_vma_trylock_write() fails.
> diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
> index 01c4d6f44ebe0..37be669653835 100644
> --- a/fs/hugetlbfs/inode.c
> +++ b/fs/hugetlbfs/inode.c
> @@ -381,6 +381,7 @@ static void hugetlb_unmap_file_folio(struct hstate *h,
> struct folio *folio, pgoff_t index)
> {
> struct hugetlb_vma_lock *vma_lock;
> + struct resv_map *resv_map;
> unsigned long pfn = folio_pfn(folio);
> struct vm_area_struct *vma;
> unsigned long v_start;
> @@ -393,6 +394,7 @@ static void hugetlb_unmap_file_folio(struct hstate *h,
> i_mmap_lock_write(mapping);
> retry:
> vma_lock = NULL;
> + resv_map = NULL;
> mapping_rmap_tree_foreach(vma, mapping, start, end - 1) {
> v_start = vma_offset_start(vma, start);
> v_end = vma_offset_end(vma, end);
> @@ -401,15 +403,21 @@ static void hugetlb_unmap_file_folio(struct hstate *h,
> continue;
>
> if (!hugetlb_vma_trylock_write(vma)) {
> - vma_lock = vma->vm_private_data;
> - /*
> - * If we can not get vma lock, we need to drop
> - * immap_sema and take locks in order. First,
> - * take a ref on the vma_lock structure so that
> - * we can be guaranteed it will not go away when
> - * dropping immap_sema.
> - */
> - kref_get(&vma_lock->refs);
> + if (vma->vm_flags & VM_MAYSHARE) {
> + vma_lock = vma->vm_private_data;
> + /*
> + * If we can not get vma lock, we need to drop
> + * immap_sema and take locks in order. First,
> + * take a ref on the vma_lock structure so that
> + * we can be guaranteed it will not go away when
> + * dropping immap_sema.
> + */
> + kref_get(&vma_lock->refs);
> + } else {
> + resv_map = vma_resv_map(vma);
> + kref_get(&resv_map->refs);
> + }
> +
> break;
> }
>
> @@ -420,25 +428,28 @@ static void hugetlb_unmap_file_folio(struct hstate *h,
>
> i_mmap_unlock_write(mapping);
>
> - if (vma_lock) {
> + if (vma_lock || resv_map) {
> /*
> * Wait on vma_lock. We know it is still valid as we have
> * a reference. We must 'open code' vma locking as we do
> * not know if vma_lock is still attached to vma.
> */
> - down_write(&vma_lock->rw_sema);
> + hugetlb_vma_lock_write(vma);
[Severity: Critical]
Does calling hugetlb_vma_lock_write(vma) after i_mmap_unlock_write(mapping)
cause a use-after-free on vma and an unbalanced up_write(&vma_lock->rw_sema)?
In hugetlb_unmap_file_folio(), the caller does not hold vma->vm_mm->mmap_lock,
so vma is only stabilized while i_mmap_lock_write(mapping) is held. Taking a
reference on vma_lock->refs or resv_map->refs does not pin struct
vm_area_struct. Once i_mmap_unlock_write(mapping) drops i_mmap_rwsem, a
concurrent munmap(), exit_mmap(), or mremap() can unlink vma from
mapping->i_mmap and free it via vm_area_free(vma) before
hugetlb_vma_lock_write(vma) reads vma->vm_flags and vma->vm_private_data:
mm/hugetlb.c:hugetlb_vma_lock_write() {
if (__vma_shareable_lock(vma)) {
struct hugetlb_vma_lock *vma_lock = vma->vm_private_data;
down_write(&vma_lock->rw_sema);
} else if (__vma_private_lock(vma)) {
struct resv_map *resv_map = vma_resv_map(vma);
down_write(&resv_map->rw_sema);
}
}
Furthermore, for a shared VMA (vma_lock != NULL), a concurrent final unmap in
__hugetlb_zap_end() -> __hugetlb_vma_unlock_write_free() ->
__hugetlb_vma_unlock_write_put() clears both vma_lock->vma and
vma->vm_private_data before releasing vma_lock->rw_sema:
mm/hugetlb.c:__hugetlb_vma_unlock_write_put() {
struct vm_area_struct *vma = vma_lock->vma;
vma_lock->vma = NULL;
vma->vm_private_data = NULL;
up_write(&vma_lock->rw_sema);
kref_put(&vma_lock->refs, hugetlb_vma_lock_release);
}
If vma->vm_private_data is cleared before hugetlb_vma_lock_write(vma) runs,
__vma_shareable_lock(vma) sees vma->vm_private_data == NULL and returns false
without calling down_write(&vma_lock->rw_sema) (or dereferences NULL if
vma->vm_private_data is cleared right after __vma_shareable_lock(vma) checks
it). Then hugetlb_unmap_file_folio() sees vma_lock->vma == NULL and calls
up_write(&vma_lock->rw_sema) on a semaphore it never locked.
> i_mmap_lock_write(mapping);
>
> - vma = vma_lock->vma;
> - if (!vma) {
> - /*
> - * If lock is no longer attached to vma, then just
> - * unlock, drop our reference and retry looking for
> - * other vmas.
> - */
> - up_write(&vma_lock->rw_sema);
> - kref_put(&vma_lock->refs, hugetlb_vma_lock_release);
> - goto retry;
> + if (vma_lock) {
> + vma = vma_lock->vma;
> + if (!vma) {
> + /*
> + * If lock is no longer attached to vma, then
> + * just unlock, drop our reference and retry
> + * looking for other vmas.
> + */
> + up_write(&vma_lock->rw_sema);
> + kref_put(&vma_lock->refs,
> + hugetlb_vma_lock_release);
> + goto retry;
> + }
> }
>
> /*
> @@ -451,7 +462,10 @@ static void hugetlb_unmap_file_folio(struct hstate *h,
> unmap_hugepage_range(vma, v_start, v_end, NULL,
> ZAP_FLAG_DROP_MARKER);
[Severity: Critical]
When unmapping a private VMA (resv_map != NULL and vma_lock == NULL), what
guarantees that vma is still valid after reacquiring i_mmap_lock_write(mapping)?
Unlike struct hugetlb_vma_lock, struct resv_map has no backpointer to vma, and
__hugetlb_vma_unlock_write_free() only unlocks resv_map->rw_sema:
mm/hugetlb.c:__hugetlb_vma_unlock_write_free() {
if (__vma_shareable_lock(vma)) {
struct hugetlb_vma_lock *vma_lock = vma->vm_private_data;
__hugetlb_vma_unlock_write_put(vma_lock);
} else if (__vma_private_lock(vma)) {
struct resv_map *resv_map = vma_resv_map(vma);
/* no free for anon vmas, but still need to unlock */
up_write(&resv_map->rw_sema);
}
}
While i_mmap_rwsem is dropped, a concurrent munmap(), exit_mmap(), or mremap()
can finish unmapping the private VMA, unlink it from mapping->i_mmap, free its
page tables, and free vma (and potentially vma->vm_mm). When
hugetlb_unmap_file_folio() reacquires i_mmap_lock_write(mapping), the
if (vma_lock) check is skipped and vma is passed directly to
vma_offset_start(), vma_offset_end(), hugetlb_vma_maps_pfn(),
unmap_hugepage_range(), and hugetlb_vma_unlock_write() without checking
whether vma was unmapped and freed.
In addition, if a concurrent mremap() runs fixup_hugetlb_reservations() ->
clear_vma_resv_huge_pages(vma) under i_mmap_lock_write() before
hugetlb_unmap_file_folio() reacquires i_mmap_lock_write(mapping),
vma->vm_private_data on the old VMA is cleared to NULL while resv_map is
moved to the new VMA. The subsequent hugetlb_vma_unlock_write(vma) will see
__vma_private_lock(vma) == false and skip up_write(&resv_map->rw_sema),
leaving resv_map->rw_sema permanently write-locked.
>
> - kref_put(&vma_lock->refs, hugetlb_vma_lock_release);
> + if (vma_lock)
> + kref_put(&vma_lock->refs, hugetlb_vma_lock_release);
> + else
> + kref_put(&resv_map->refs, resv_map_release);
> hugetlb_vma_unlock_write(vma);
[Severity: Critical]
Can kref_put(&resv_map->refs, resv_map_release) free resv_map before
hugetlb_vma_unlock_write(vma) unlocks resv_map->rw_sema?
For private VMAs, hugetlb_vm_op_close() (and clear_vma_resv_huge_pages()) can
drop the VMA's reference on resv_map->refs while i_mmap_rwsem was dropped,
without taking resv_map->rw_sema:
mm/hugetlb.c:hugetlb_vm_op_close() {
...
if (reservations) {
kref_put(&reservations->refs, resv_map_release);
...
}
If the VMA's reference was dropped concurrently, the reference held by
hugetlb_unmap_file_folio() is the last reference on resv_map->refs. Calling
kref_put(&resv_map->refs, resv_map_release) before
hugetlb_vma_unlock_write(vma) will free resv_map via resv_map_release() ->
kfree(resv_map), and hugetlb_vma_unlock_write(vma) will then call
up_write(&resv_map->rw_sema) on the freed resv_map.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261028054620.275525-1-eadavis@sina.com?part=1
prev parent reply other threads:[~2026-10-28 14:33 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 5:46 [PATCH for Sashiko] hugetlb: supports unmapping private mappings Edward Adam Davis
2026-10-28 14:33 ` sashiko-bot [this message]
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=sashiko-outbox-187992@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=eadavis@sina.com \
--cc=linux-mm@kvack.org \
--cc=sashiko-reviews@lists.linux.dev \
/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