Commit 3adf7ae18bf4 for kernel

commit 3adf7ae18bf42601246031002287c103a27df307
Author: Zizhi Wo <wozizhi@huawei.com>
Date:   Sat Apr 18 14:06:34 2026 +0800

    fs: aio: reject partial mremap to avoid Null-pointer-dereference error

    [BUG]
    Recently, our internal syzkaller testing uncovered a null pointer
    dereference issue:
    BUG: kernel NULL pointer dereference, address: 0000000000000000
    ...
    [   51.111664]  filemap_read_folio+0x25/0xe0
    [   51.112410]  filemap_fault+0xad7/0x1250
    [   51.113112]  __do_fault+0x4b/0x460
    [   51.113699]  do_pte_missing+0x5bc/0x1db0
    [   51.114250]  ? __pte_offset_map+0x23/0x170
    [   51.114822]  __handle_mm_fault+0x9f8/0x1680
    ...
    Crash analysis showed the file involved was an AIO ring file. The
    phenomenon triggered is the same as the issue described in [1].

    [CAUSE]
    Consider the following scenario: userspace sets up an AIO context via
    io_setup(), which creates a VMA covering the entire ring buffer. Then
    userspace calls mremap() with the AIO ring address as the source, a smaller
    old_len (less than the full ring size), MREMAP_MAYMOVE set, and without
    MREMAP_DONTUNMAP. The kernel will relocate the requested portion to a new
    destination address.

    During this move, __split_vma() splits the original AIO ring VMA. The
    requested portion is unmapped from the source and re-established at the
    destination, while the remainder stays at the original source address as
    an orphan VMA. The aio_ring_mremap() callback fires on the new destination
    VMA, updating ctx->mmap_base to the destination address. But the callback
    is unaware that only a partial region was moved and that an orphan VMA
    still exists at the source:

      source(AIO):
      +-------------------+---------------------+
      |  moved to dest    |  orphan VMA (AIO)   |
      +-------------------+---------------------+
      A                 A+partial_len        A+ctx->mmap_size

      dest:
      +-------------------+
      |  moved VMA (AIO)  |
      +-------------------+
      B                 B+partial_len

    Later, io_destroy() calls vm_munmap(ctx->mmap_base, ctx->mmap_size), which
    unmaps the destination. This not only fails to unmap the orphan VMA at the
    source, but also overshoots the destination VMA and may unmap unrelated
    mappings adjacent to it! After put_aio_ring_file() calls truncate_setsize()
    to remove all pages from the pagecache, any subsequent access to the orphan
    VMA triggers filemap_fault(), which calls a_ops->read_folio(). Since aio
    does not implement read_folio, this results in a NULL pointer dereference.

    [FIX]
    Note that expanding mremap (new_len > old_len) is already rejected because
    AIO ring VMAs are created with VM_DONTEXPAND. The only problematic case is
    a partial move where "old_len == new_len" but both are smaller than the
    full ring size.

    Fix this by checking in aio_ring_mremap() that the new VMA covers the
    entire ring. This ensures the AIO ring is always moved as a whole,
    preventing orphan VMAs and the subsequent crash.

    [1]: https://lore.kernel.org/all/20260413010814.548568-1-wozizhi@huawei.com/

    Signed-off-by: Zizhi Wo <wozizhi@huaweicloud.com>
    Link: https://patch.msgid.link/20260418060634.3713620-1-wozizhi@huaweicloud.com
    Reviewed-by: Jan Kara <jack@suse.cz>
    Signed-off-by: Christian Brauner <brauner@kernel.org>

diff --git a/fs/aio.c b/fs/aio.c
index d7910c7c93a6..722476560848 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -422,7 +422,8 @@ static int aio_ring_mremap(struct vm_area_struct *vma)

 		ctx = rcu_dereference(table->table[i]);
 		if (ctx && ctx->aio_ring_file == file) {
-			if (!atomic_read(&ctx->dead)) {
+			if (!atomic_read(&ctx->dead) &&
+			    (ctx->mmap_size == (vma->vm_end - vma->vm_start))) {
 				ctx->user_id = ctx->mmap_base = vma->vm_start;
 				res = 0;
 			}