ea72ce5da2
iounmap() on x86 occasionally fails to unmap because the provided valid
ioremap address is not below high_memory. It turned out that this
happens due to KASLR.
KASLR uses the full address space between PAGE_OFFSET and vaddr_end to
randomize the starting points of the direct map, vmalloc and vmemmap
regions. It thereby limits the size of the direct map by using the
installed memory size plus an extra configurable margin for hot-plug
memory. This limitation is done to gain more randomization space
because otherwise only the holes between the direct map, vmalloc,
vmemmap and vaddr_end would be usable for randomizing.
The limited direct map size is not exposed to the rest of the kernel, so
the memory hot-plug and resource management related code paths still
operate under the assumption that the available address space can be
determined with MAX_PHYSMEM_BITS.
request_free_mem_region() allocates from (1 << MAX_PHYSMEM_BITS) - 1
downwards. That means the first allocation happens past the end of the
direct map and if unlucky this address is in the vmalloc space, which
causes high_memory to become greater than VMALLOC_START and consequently
causes iounmap() to fail for valid ioremap addresses.
MAX_PHYSMEM_BITS cannot be changed for that because the randomization
does not align with address bit boundaries and there are other places
which actually require to know the maximum number of address bits. All
remaining usage sites of MAX_PHYSMEM_BITS have been analyzed and found
to be correct.
Cure this by exposing the end of the direct map via PHYSMEM_END and use
that for the memory hot-plug and resource management related places
instead of relying on MAX_PHYSMEM_BITS. In the KASLR case PHYSMEM_END
maps to a variable which is initialized by the KASLR initialization and
otherwise it is based on MAX_PHYSMEM_BITS as before.
To prevent future hickups add a check into add_pages() to catch callers
trying to add memory above PHYSMEM_END.
Fixes: 0483e1fa6e ("x86/mm: Implement ASLR for kernel memory regions")
Reported-by: Max Ramanouski <max8rr8@gmail.com>
Reported-by: Alistair Popple <apopple@nvidia.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Tested-By: Max Ramanouski <max8rr8@gmail.com>
Tested-by: Alistair Popple <apopple@nvidia.com>
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Reviewed-by: Alistair Popple <apopple@nvidia.com>
Reviewed-by: Kees Cook <kees@kernel.org>
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/all/87ed6soy3z.ffs@tglx
104 lines
2.9 KiB
C
104 lines
2.9 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef _ASM_X86_PAGE_64_H
|
|
#define _ASM_X86_PAGE_64_H
|
|
|
|
#include <asm/page_64_types.h>
|
|
|
|
#ifndef __ASSEMBLY__
|
|
#include <asm/cpufeatures.h>
|
|
#include <asm/alternative.h>
|
|
|
|
#include <linux/kmsan-checks.h>
|
|
|
|
/* duplicated to the one in bootmem.h */
|
|
extern unsigned long max_pfn;
|
|
extern unsigned long phys_base;
|
|
|
|
extern unsigned long page_offset_base;
|
|
extern unsigned long vmalloc_base;
|
|
extern unsigned long vmemmap_base;
|
|
extern unsigned long physmem_end;
|
|
|
|
static __always_inline unsigned long __phys_addr_nodebug(unsigned long x)
|
|
{
|
|
unsigned long y = x - __START_KERNEL_map;
|
|
|
|
/* use the carry flag to determine if x was < __START_KERNEL_map */
|
|
x = y + ((x > y) ? phys_base : (__START_KERNEL_map - PAGE_OFFSET));
|
|
|
|
return x;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_VIRTUAL
|
|
extern unsigned long __phys_addr(unsigned long);
|
|
extern unsigned long __phys_addr_symbol(unsigned long);
|
|
#else
|
|
#define __phys_addr(x) __phys_addr_nodebug(x)
|
|
#define __phys_addr_symbol(x) \
|
|
((unsigned long)(x) - __START_KERNEL_map + phys_base)
|
|
#endif
|
|
|
|
#define __phys_reloc_hide(x) (x)
|
|
|
|
void clear_page_orig(void *page);
|
|
void clear_page_rep(void *page);
|
|
void clear_page_erms(void *page);
|
|
|
|
static inline void clear_page(void *page)
|
|
{
|
|
/*
|
|
* Clean up KMSAN metadata for the page being cleared. The assembly call
|
|
* below clobbers @page, so we perform unpoisoning before it.
|
|
*/
|
|
kmsan_unpoison_memory(page, PAGE_SIZE);
|
|
alternative_call_2(clear_page_orig,
|
|
clear_page_rep, X86_FEATURE_REP_GOOD,
|
|
clear_page_erms, X86_FEATURE_ERMS,
|
|
"=D" (page),
|
|
"D" (page)
|
|
: "cc", "memory", "rax", "rcx");
|
|
}
|
|
|
|
void copy_page(void *to, void *from);
|
|
|
|
#ifdef CONFIG_X86_5LEVEL
|
|
/*
|
|
* User space process size. This is the first address outside the user range.
|
|
* There are a few constraints that determine this:
|
|
*
|
|
* On Intel CPUs, if a SYSCALL instruction is at the highest canonical
|
|
* address, then that syscall will enter the kernel with a
|
|
* non-canonical return address, and SYSRET will explode dangerously.
|
|
* We avoid this particular problem by preventing anything
|
|
* from being mapped at the maximum canonical address.
|
|
*
|
|
* On AMD CPUs in the Ryzen family, there's a nasty bug in which the
|
|
* CPUs malfunction if they execute code from the highest canonical page.
|
|
* They'll speculate right off the end of the canonical space, and
|
|
* bad things happen. This is worked around in the same way as the
|
|
* Intel problem.
|
|
*
|
|
* With page table isolation enabled, we map the LDT in ... [stay tuned]
|
|
*/
|
|
static __always_inline unsigned long task_size_max(void)
|
|
{
|
|
unsigned long ret;
|
|
|
|
alternative_io("movq %[small],%0","movq %[large],%0",
|
|
X86_FEATURE_LA57,
|
|
"=r" (ret),
|
|
[small] "i" ((1ul << 47)-PAGE_SIZE),
|
|
[large] "i" ((1ul << 56)-PAGE_SIZE));
|
|
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_X86_5LEVEL */
|
|
|
|
#endif /* !__ASSEMBLY__ */
|
|
|
|
#ifdef CONFIG_X86_VSYSCALL_EMULATION
|
|
# define __HAVE_ARCH_GATE_AREA 1
|
|
#endif
|
|
|
|
#endif /* _ASM_X86_PAGE_64_H */
|