| From bippy-5f407fcff5a0 Mon Sep 17 00:00:00 2001 |
| From: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| To: <linux-cve-announce@vger.kernel.org> |
| Reply-to: <cve@kernel.org>, <linux-kernel@vger.kernel.org> |
| Subject: CVE-2024-26991: KVM: x86/mmu: x86: Don't overflow lpage_info when checking attributes |
| |
| Description |
| =========== |
| |
| In the Linux kernel, the following vulnerability has been resolved: |
| |
| KVM: x86/mmu: x86: Don't overflow lpage_info when checking attributes |
| |
| Fix KVM_SET_MEMORY_ATTRIBUTES to not overflow lpage_info array and trigger |
| KASAN splat, as seen in the private_mem_conversions_test selftest. |
| |
| When memory attributes are set on a GFN range, that range will have |
| specific properties applied to the TDP. A huge page cannot be used when |
| the attributes are inconsistent, so they are disabled for those the |
| specific huge pages. For internal KVM reasons, huge pages are also not |
| allowed to span adjacent memslots regardless of whether the backing memory |
| could be mapped as huge. |
| |
| What GFNs support which huge page sizes is tracked by an array of arrays |
| 'lpage_info' on the memslot, of ‘kvm_lpage_info’ structs. Each index of |
| lpage_info contains a vmalloc allocated array of these for a specific |
| supported page size. The kvm_lpage_info denotes whether a specific huge |
| page (GFN and page size) on the memslot is supported. These arrays include |
| indices for unaligned head and tail huge pages. |
| |
| Preventing huge pages from spanning adjacent memslot is covered by |
| incrementing the count in head and tail kvm_lpage_info when the memslot is |
| allocated, but disallowing huge pages for memory that has mixed attributes |
| has to be done in a more complicated way. During the |
| KVM_SET_MEMORY_ATTRIBUTES ioctl KVM updates lpage_info for each memslot in |
| the range that has mismatched attributes. KVM does this a memslot at a |
| time, and marks a special bit, KVM_LPAGE_MIXED_FLAG, in the kvm_lpage_info |
| for any huge page. This bit is essentially a permanently elevated count. |
| So huge pages will not be mapped for the GFN at that page size if the |
| count is elevated in either case: a huge head or tail page unaligned to |
| the memslot or if KVM_LPAGE_MIXED_FLAG is set because it has mixed |
| attributes. |
| |
| To determine whether a huge page has consistent attributes, the |
| KVM_SET_MEMORY_ATTRIBUTES operation checks an xarray to make sure it |
| consistently has the incoming attribute. Since level - 1 huge pages are |
| aligned to level huge pages, it employs an optimization. As long as the |
| level - 1 huge pages are checked first, it can just check these and assume |
| that if each level - 1 huge page contained within the level sized huge |
| page is not mixed, then the level size huge page is not mixed. This |
| optimization happens in the helper hugepage_has_attrs(). |
| |
| Unfortunately, although the kvm_lpage_info array representing page size |
| 'level' will contain an entry for an unaligned tail page of size level, |
| the array for level - 1 will not contain an entry for each GFN at page |
| size level. The level - 1 array will only contain an index for any |
| unaligned region covered by level - 1 huge page size, which can be a |
| smaller region. So this causes the optimization to overflow the level - 1 |
| kvm_lpage_info and perform a vmalloc out of bounds read. |
| |
| In some cases of head and tail pages where an overflow could happen, |
| callers skip the operation completely as KVM_LPAGE_MIXED_FLAG is not |
| required to prevent huge pages as discussed earlier. But for memslots that |
| are smaller than the 1GB page size, it does call hugepage_has_attrs(). In |
| this case the huge page is both the head and tail page. The issue can be |
| observed simply by compiling the kernel with CONFIG_KASAN_VMALLOC and |
| running the selftest “private_mem_conversions_test”, which produces the |
| output like the following: |
| |
| BUG: KASAN: vmalloc-out-of-bounds in hugepage_has_attrs+0x7e/0x110 |
| Read of size 4 at addr ffffc900000a3008 by task private_mem_con/169 |
| Call Trace: |
| dump_stack_lvl |
| print_report |
| ? __virt_addr_valid |
| ? hugepage_has_attrs |
| ? hugepage_has_attrs |
| kasan_report |
| ? hugepage_has_attrs |
| hugepage_has_attrs |
| kvm_arch_post_set_memory_attributes |
| kvm_vm_ioctl |
| |
| It is a little ambiguous whether the unaligned head page (in the bug case |
| also the tail page) should be expected to have KVM_LPAGE_MIXED_FLAG set. |
| It is not functionally required, as the unaligned head/tail pages will |
| already have their kvm_lpage_info count incremented. The comments imply |
| not setting it on unaligned head pages is intentional, so fix the callers |
| to skip trying to set KVM_LPAGE_MIXED_FLAG in this case, and in doing so |
| not call hugepage_has_attrs(). |
| |
| The Linux kernel CVE team has assigned CVE-2024-26991 to this issue. |
| |
| |
| Affected and fixed versions |
| =========================== |
| |
| Issue introduced in 6.8 with commit 90b4fe17981e155432c4dbc490606d0c2e9c2199 and fixed in 6.8.8 with commit 048cc4a028e635d339687ed968985d2d1669494c |
| Issue introduced in 6.8 with commit 90b4fe17981e155432c4dbc490606d0c2e9c2199 and fixed in 6.9 with commit 992b54bd083c5bee24ff7cc35991388ab08598c4 |
| |
| 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-2024-26991 |
| 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: |
| arch/x86/kvm/mmu/mmu.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/048cc4a028e635d339687ed968985d2d1669494c |
| https://git.kernel.org/stable/c/992b54bd083c5bee24ff7cc35991388ab08598c4 |