| From bippy-1.2.0 Mon Sep 17 00:00:00 2001 |
| From: Greg Kroah-Hartman <gregkh@kernel.org> |
| To: <linux-cve-announce@vger.kernel.org> |
| Reply-to: <cve@kernel.org>, <linux-kernel@vger.kernel.org> |
| Subject: CVE-2025-22034: mm/gup: reject FOLL_SPLIT_PMD with hugetlb VMAs |
| |
| Description |
| =========== |
| |
| In the Linux kernel, the following vulnerability has been resolved: |
| |
| mm/gup: reject FOLL_SPLIT_PMD with hugetlb VMAs |
| |
| Patch series "mm: fixes for device-exclusive entries (hmm)", v2. |
| |
| Discussing the PageTail() call in make_device_exclusive_range() with |
| Willy, I recently discovered [1] that device-exclusive handling does not |
| properly work with THP, making the hmm-tests selftests fail if THPs are |
| enabled on the system. |
| |
| Looking into more details, I found that hugetlb is not properly fenced, |
| and I realized that something that was bugging me for longer -- how |
| device-exclusive entries interact with mapcounts -- completely breaks |
| migration/swapout/split/hwpoison handling of these folios while they have |
| device-exclusive PTEs. |
| |
| The program below can be used to allocate 1 GiB worth of pages and making |
| them device-exclusive on a kernel with CONFIG_TEST_HMM. |
| |
| Once they are device-exclusive, these folios cannot get swapped out |
| (proc$pid/smaps_rollup will always indicate 1 GiB RSS no matter how much |
| one forces memory reclaim), and when having a memory block onlined to |
| ZONE_MOVABLE, trying to offline it will loop forever and complain about |
| failed migration of a page that should be movable. |
| |
| # echo offline > /sys/devices/system/memory/memory136/state |
| # echo online_movable > /sys/devices/system/memory/memory136/state |
| # ./hmm-swap & |
| ... wait until everything is device-exclusive |
| # echo offline > /sys/devices/system/memory/memory136/state |
| [ 285.193431][T14882] page: refcount:2 mapcount:0 mapping:0000000000000000 |
| index:0x7f20671f7 pfn:0x442b6a |
| [ 285.196618][T14882] memcg:ffff888179298000 |
| [ 285.198085][T14882] anon flags: 0x5fff0000002091c(referenced|uptodate| |
| dirty|active|owner_2|swapbacked|node=1|zone=3|lastcpupid=0x7ff) |
| [ 285.201734][T14882] raw: ... |
| [ 285.204464][T14882] raw: ... |
| [ 285.207196][T14882] page dumped because: migration failure |
| [ 285.209072][T14882] page_owner tracks the page as allocated |
| [ 285.210915][T14882] page last allocated via order 0, migratetype |
| Movable, gfp_mask 0x140dca(GFP_HIGHUSER_MOVABLE|__GFP_COMP|__GFP_ZERO), |
| id 14926, tgid 14926 (hmm-swap), ts 254506295376, free_ts 227402023774 |
| [ 285.216765][T14882] post_alloc_hook+0x197/0x1b0 |
| [ 285.218874][T14882] get_page_from_freelist+0x76e/0x3280 |
| [ 285.220864][T14882] __alloc_frozen_pages_noprof+0x38e/0x2740 |
| [ 285.223302][T14882] alloc_pages_mpol+0x1fc/0x540 |
| [ 285.225130][T14882] folio_alloc_mpol_noprof+0x36/0x340 |
| [ 285.227222][T14882] vma_alloc_folio_noprof+0xee/0x1a0 |
| [ 285.229074][T14882] __handle_mm_fault+0x2b38/0x56a0 |
| [ 285.230822][T14882] handle_mm_fault+0x368/0x9f0 |
| ... |
| |
| This series fixes all issues I found so far. There is no easy way to fix |
| without a bigger rework/cleanup. I have a bunch of cleanups on top (some |
| previous sent, some the result of the discussion in v1) that I will send |
| out separately once this landed and I get to it. |
| |
| I wish we could just use some special present PROT_NONE PTEs instead of |
| these (non-present, non-none) fake-swap entries; but that just results in |
| the same problem we keep having (lack of spare PTE bits), and staring at |
| other similar fake-swap entries, that ship has sailed. |
| |
| With this series, make_device_exclusive() doesn't actually belong into |
| mm/rmap.c anymore, but I'll leave moving that for another day. |
| |
| I only tested this series with the hmm-tests selftests due to lack of HW, |
| so I'd appreciate some testing, especially if the interaction between two |
| GPUs wanting a device-exclusive entry works as expected. |
| |
| <program> |
| #include <stdio.h> |
| #include <fcntl.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/ioctl.h> |
| #include <linux/types.h> |
| #include <linux/ioctl.h> |
| |
| #define HMM_DMIRROR_EXCLUSIVE _IOWR('H', 0x05, struct hmm_dmirror_cmd) |
| |
| struct hmm_dmirror_cmd { |
| __u64 addr; |
| __u64 ptr; |
| __u64 npages; |
| __u64 cpages; |
| __u64 faults; |
| }; |
| |
| const size_t size = 1 * 1024 * 1024 * 1024ul; |
| const size_t chunk_size = 2 * 1024 * 1024ul; |
| |
| int main(void) |
| { |
| struct hmm_dmirror_cmd cmd; |
| size_t cur_size; |
| int fd, ret; |
| char *addr, *mirror; |
| |
| fd = open("/dev/hmm_dmirror1", O_RDWR, 0); |
| if (fd < 0) { |
| perror("open failed\n"); |
| exit(1); |
| } |
| |
| addr = mmap(NULL, size, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| if (addr == MAP_FAILED) { |
| perror("mmap failed\n"); |
| exit(1); |
| } |
| madvise(addr, size, MADV_NOHUGEPAGE); |
| memset(addr, 1, size); |
| |
| mirror = malloc(chunk_size); |
| |
| for (cur_size = 0; cur_size < size; cur_size += chunk_size) { |
| cmd.addr = (uintptr_t)addr + cur_size; |
| cmd.ptr = (uintptr_t)mirror; |
| cmd.npages = chunk_size / getpagesize(); |
| ret = ioctl(fd, HMM_DMIRROR_EXCLUSIVE, &cmd); |
| if (ret) { |
| perror("ioctl failed\n"); |
| exit(1); |
| } |
| } |
| pause(); |
| return 0; |
| } |
| </program> |
| |
| [1] https://lkml.kernel.org/r/25e02685-4f1d-47fa-be5b-01ff85bb0ce2@redhat.com |
| |
| |
| This patch (of 17): |
| |
| We only have two FOLL_SPLIT_PMD users. While uprobe refuses hugetlb |
| early, make_device_exclusive_range() can end up getting called on hugetlb |
| VMAs. |
| |
| Right now, this means that with a PMD-sized hugetlb page, we can end up |
| calling split_huge_pmd(), because pmd_trans_huge() also succeeds with |
| hugetlb PMDs. |
| |
| For example, using a modified hmm-test selftest one can trigger: |
| |
| [ 207.017134][T14945] ------------[ cut here ]------------ |
| [ 207.018614][T14945] kernel BUG at mm/page_table_check.c:87! |
| [ 207.019716][T14945] Oops: invalid opcode: 0000 [#1] PREEMPT SMP KASAN NOPTI |
| [ 207.021072][T14945] CPU: 3 UID: 0 PID: ... |
| [ 207.023036][T14945] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-2.fc40 04/01/2014 |
| [ 207.024834][T14945] RIP: 0010:page_table_check_clear.part.0+0x488/0x510 |
| [ 207.026128][T14945] Code: ... |
| [ 207.029965][T14945] RSP: 0018:ffffc9000cb8f348 EFLAGS: 00010293 |
| [ 207.031139][T14945] RAX: 0000000000000000 RBX: 00000000ffffffff RCX: ffffffff8249a0cd |
| [ 207.032649][T14945] RDX: ffff88811e883c80 RSI: ffffffff8249a357 RDI: ffff88811e883c80 |
| [ 207.034183][T14945] RBP: ffff888105c0a050 R08: 0000000000000005 R09: 0000000000000000 |
| [ 207.035688][T14945] R10: 00000000ffffffff R11: 0000000000000003 R12: 0000000000000001 |
| [ 207.037203][T14945] R13: 0000000000000200 R14: 0000000000000001 R15: dffffc0000000000 |
| [ 207.038711][T14945] FS: 00007f2783275740(0000) GS:ffff8881f4980000(0000) knlGS:0000000000000000 |
| [ 207.040407][T14945] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 |
| [ 207.041660][T14945] CR2: 00007f2782c00000 CR3: 0000000132356000 CR4: 0000000000750ef0 |
| [ 207.043196][T14945] PKRU: 55555554 |
| [ 207.043880][T14945] Call Trace: |
| [ 207.044506][T14945] <TASK> |
| [ 207.045086][T14945] ? __die+0x51/0x92 |
| [ 207.045864][T14945] ? die+0x29/0x50 |
| [ 207.046596][T14945] ? do_trap+0x250/0x320 |
| [ 207.047430][T14945] ? do_error_trap+0xe7/0x220 |
| [ 207.048346][T14945] ? page_table_check_clear.part.0+0x488/0x510 |
| [ 207.049535][T14945] ? handle_invalid_op+0x34/0x40 |
| [ 207.050494][T14945] ? page_table_check_clear.part.0+0x488/0x510 |
| [ 207.051681][T14945] ? exc_invalid_op+0x2e/0x50 |
| [ 207.052589][T14945] ? asm_exc_invalid_op+0x1a/0x20 |
| [ 207.053596][T14945] ? page_table_check_clear.part.0+0x1fd/0x510 |
| [ 207.054790][T14945] ? page_table_check_clear.part.0+0x487/0x510 |
| [ 207.055993][T14945] ? page_table_check_clear.part.0+0x488/0x510 |
| [ 207.057195][T14945] ? page_table_check_clear.part.0+0x487/0x510 |
| [ 207.058384][T14945] __page_table_check_pmd_clear+0x34b/0x5a0 |
| [ 207.059524][T14945] ? __pfx___page_table_check_pmd_clear+0x10/0x10 |
| [ 207.060775][T14945] ? __pfx___mutex_unlock_slowpath+0x10/0x10 |
| [ 207.061940][T14945] ? __pfx___lock_acquire+0x10/0x10 |
| [ 207.062967][T14945] pmdp_huge_clear_flush+0x279/0x360 |
| [ 207.064024][T14945] split_huge_pmd_locked+0x82b/0x3750 |
| ... |
| |
| Before commit 9cb28da54643 ("mm/gup: handle hugetlb in the generic |
| follow_page_mask code"), we would have ignored the flag; instead, let's |
| simply refuse the combination completely in check_vma_flags(): the caller |
| is likely not prepared to handle any hugetlb folios. |
| |
| We'll teach make_device_exclusive_range() separately to ignore any hugetlb |
| folios as a future-proof safety net. |
| |
| The Linux kernel CVE team has assigned CVE-2025-22034 to this issue. |
| |
| |
| Affected and fixed versions |
| =========================== |
| |
| Issue introduced in 6.10 with commit 9cb28da54643ad464c47585cd5866c30b0218e67 and fixed in 6.12.23 with commit 2e877ff3492267def06dd50cb165dc9ab8838e7d |
| Issue introduced in 6.10 with commit 9cb28da54643ad464c47585cd5866c30b0218e67 and fixed in 6.13.11 with commit 48d28417c66cce2f3b0ba773fcb6695a56eff220 |
| Issue introduced in 6.10 with commit 9cb28da54643ad464c47585cd5866c30b0218e67 and fixed in 6.14.2 with commit fd900832e8440046627b60697687ab5d04398008 |
| Issue introduced in 6.10 with commit 9cb28da54643ad464c47585cd5866c30b0218e67 and fixed in 6.15 with commit 8977752c8056a6a094a279004a49722da15bace3 |
| |
| Please see https://www.kernel.org for a full list of currently supported |
| kernel versions by the kernel community. |
| |
| Unaffected versions might change over time as fixes are backported to |
| older supported kernel versions. The official CVE entry at |
| https://cve.org/CVERecord/?id=CVE-2025-22034 |
| will be updated if fixes are backported, please check that for the most |
| up to date information about this issue. |
| |
| |
| Affected files |
| ============== |
| |
| The file(s) affected by this issue are: |
| mm/gup.c |
| |
| |
| Mitigation |
| ========== |
| |
| The Linux kernel CVE team recommends that you update to the latest |
| stable kernel version for this, and many other bugfixes. Individual |
| changes are never tested alone, but rather are part of a larger kernel |
| release. Cherry-picking individual commits is not recommended or |
| supported by the Linux kernel community at all. If however, updating to |
| the latest release is impossible, the individual changes to resolve this |
| issue can be found at these commits: |
| https://git.kernel.org/stable/c/2e877ff3492267def06dd50cb165dc9ab8838e7d |
| https://git.kernel.org/stable/c/48d28417c66cce2f3b0ba773fcb6695a56eff220 |
| https://git.kernel.org/stable/c/fd900832e8440046627b60697687ab5d04398008 |
| https://git.kernel.org/stable/c/8977752c8056a6a094a279004a49722da15bace3 |