Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
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


      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