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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.