The memory reserved for module tags does not need to be backed by physical pages until there are tags to store there. Change the way we reserve this memory to allocate only virtual area for the tags and populate it with physical pages as needed when we load a module. [surenb@google.com: avoid execmem_vmap() when !MMU] Link: https://lkml.kernel.org/r/20241031233611.3833002-1-surenb@google.com Link: https://lkml.kernel.org/r/20241023170759.999909-5-surenb@google.com Signed-off-by: Suren Baghdasaryan <surenb@google.com> Reviewed-by: Pasha Tatashin <pasha.tatashin@soleen.com> Cc: Ard Biesheuvel <ardb@kernel.org> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Borislav Petkov (AMD) <bp@alien8.de> Cc: Christoph Hellwig <hch@infradead.org> Cc: Daniel Gomez <da.gomez@samsung.com> Cc: David Hildenbrand <david@redhat.com> Cc: Davidlohr Bueso <dave@stgolabs.net> Cc: David Rientjes <rientjes@google.com> Cc: Dennis Zhou <dennis@kernel.org> Cc: Johannes Weiner <hannes@cmpxchg.org> Cc: John Hubbard <jhubbard@nvidia.com> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Joonsoo Kim <iamjoonsoo.kim@lge.com> Cc: Kalesh Singh <kaleshsingh@google.com> Cc: Kees Cook <keescook@chromium.org> Cc: Kent Overstreet <kent.overstreet@linux.dev> Cc: Liam R. Howlett <Liam.Howlett@Oracle.com> Cc: Luis Chamberlain <mcgrof@kernel.org> Cc: Matthew Wilcox <willy@infradead.org> Cc: Michal Hocko <mhocko@suse.com> Cc: Mike Rapoport (Microsoft) <rppt@kernel.org> Cc: Minchan Kim <minchan@google.com> Cc: Paul E. McKenney <paulmck@kernel.org> Cc: Petr Pavlu <petr.pavlu@suse.com> Cc: Roman Gushchin <roman.gushchin@linux.dev> Cc: Sami Tolvanen <samitolvanen@google.com> Cc: Sourav Panda <souravpanda@google.com> Cc: Steven Rostedt (Google) <rostedt@goodmis.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Thomas Huth <thuth@redhat.com> Cc: Uladzislau Rezki (Sony) <urezki@gmail.com> Cc: Vlastimil Babka <vbabka@suse.cz> Cc: Xiongwei Song <xiongwei.song@windriver.com> Cc: Yu Zhao <yuzhao@google.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> (cherry picked from commit 0f9b685626daa2f8e19a9788625c9b624c223e45) Conflicts: include/linux/execmem.h mm/execmem.c 1. trivial merge conflicts 2. add missing internal.h inclusion in execmem.c Bug: 380948583 Change-Id: I7bdfbaf14dc625ae7c6fa8215986c1dcc4c3e542 Signed-off-by: Suren Baghdasaryan <surenb@google.com>
164 lines
4.1 KiB
C
164 lines
4.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2002 Richard Henderson
|
|
* Copyright (C) 2001 Rusty Russell, 2002, 2010 Rusty Russell IBM.
|
|
* Copyright (C) 2023 Luis Chamberlain <mcgrof@kernel.org>
|
|
* Copyright (C) 2024 Mike Rapoport IBM.
|
|
*/
|
|
|
|
#include <linux/mm.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/execmem.h>
|
|
#include <linux/moduleloader.h>
|
|
|
|
#include "internal.h"
|
|
|
|
static struct execmem_info *execmem_info __ro_after_init;
|
|
static struct execmem_info default_execmem_info __ro_after_init;
|
|
|
|
#ifdef CONFIG_MMU
|
|
struct vm_struct *execmem_vmap(size_t size)
|
|
{
|
|
struct execmem_range *range = &execmem_info->ranges[EXECMEM_MODULE_DATA];
|
|
struct vm_struct *area;
|
|
|
|
area = __get_vm_area_node(size, range->alignment, PAGE_SHIFT, VM_ALLOC,
|
|
range->start, range->end, NUMA_NO_NODE,
|
|
GFP_KERNEL, __builtin_return_address(0));
|
|
if (!area && range->fallback_start)
|
|
area = __get_vm_area_node(size, range->alignment, PAGE_SHIFT, VM_ALLOC,
|
|
range->fallback_start, range->fallback_end,
|
|
NUMA_NO_NODE, GFP_KERNEL, __builtin_return_address(0));
|
|
|
|
return area;
|
|
}
|
|
#endif /* CONFIG_MMU */
|
|
|
|
static void *__execmem_alloc(struct execmem_range *range, size_t size)
|
|
{
|
|
bool kasan = range->flags & EXECMEM_KASAN_SHADOW;
|
|
unsigned long vm_flags = VM_FLUSH_RESET_PERMS;
|
|
gfp_t gfp_flags = GFP_KERNEL | __GFP_NOWARN;
|
|
unsigned long start = range->start;
|
|
unsigned long end = range->end;
|
|
unsigned int align = range->alignment;
|
|
pgprot_t pgprot = range->pgprot;
|
|
void *p;
|
|
|
|
if (kasan)
|
|
vm_flags |= VM_DEFER_KMEMLEAK;
|
|
|
|
p = __vmalloc_node_range(size, align, start, end, gfp_flags,
|
|
pgprot, vm_flags, NUMA_NO_NODE,
|
|
__builtin_return_address(0));
|
|
if (!p && range->fallback_start) {
|
|
start = range->fallback_start;
|
|
end = range->fallback_end;
|
|
p = __vmalloc_node_range(size, align, start, end, gfp_flags,
|
|
pgprot, vm_flags, NUMA_NO_NODE,
|
|
__builtin_return_address(0));
|
|
}
|
|
|
|
if (!p) {
|
|
pr_warn_ratelimited("execmem: unable to allocate memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (kasan && (kasan_alloc_module_shadow(p, size, GFP_KERNEL) < 0)) {
|
|
vfree(p);
|
|
return NULL;
|
|
}
|
|
|
|
return kasan_reset_tag(p);
|
|
}
|
|
|
|
void *execmem_alloc(enum execmem_type type, size_t size)
|
|
{
|
|
struct execmem_range *range = &execmem_info->ranges[type];
|
|
|
|
return __execmem_alloc(range, size);
|
|
}
|
|
|
|
void execmem_free(void *ptr)
|
|
{
|
|
/*
|
|
* This memory may be RO, and freeing RO memory in an interrupt is not
|
|
* supported by vmalloc.
|
|
*/
|
|
WARN_ON(in_interrupt());
|
|
vfree(ptr);
|
|
}
|
|
|
|
static bool execmem_validate(struct execmem_info *info)
|
|
{
|
|
struct execmem_range *r = &info->ranges[EXECMEM_DEFAULT];
|
|
|
|
if (!r->alignment || !r->start || !r->end || !pgprot_val(r->pgprot)) {
|
|
pr_crit("Invalid parameters for execmem allocator, module loading will fail");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void execmem_init_missing(struct execmem_info *info)
|
|
{
|
|
struct execmem_range *default_range = &info->ranges[EXECMEM_DEFAULT];
|
|
|
|
for (int i = EXECMEM_DEFAULT + 1; i < EXECMEM_TYPE_MAX; i++) {
|
|
struct execmem_range *r = &info->ranges[i];
|
|
|
|
if (!r->start) {
|
|
if (i == EXECMEM_MODULE_DATA)
|
|
r->pgprot = PAGE_KERNEL;
|
|
else
|
|
r->pgprot = default_range->pgprot;
|
|
r->alignment = default_range->alignment;
|
|
r->start = default_range->start;
|
|
r->end = default_range->end;
|
|
r->flags = default_range->flags;
|
|
r->fallback_start = default_range->fallback_start;
|
|
r->fallback_end = default_range->fallback_end;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct execmem_info * __weak execmem_arch_setup(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static void __init __execmem_init(void)
|
|
{
|
|
struct execmem_info *info = execmem_arch_setup();
|
|
|
|
if (!info) {
|
|
info = execmem_info = &default_execmem_info;
|
|
info->ranges[EXECMEM_DEFAULT].start = VMALLOC_START;
|
|
info->ranges[EXECMEM_DEFAULT].end = VMALLOC_END;
|
|
info->ranges[EXECMEM_DEFAULT].pgprot = PAGE_KERNEL_EXEC;
|
|
info->ranges[EXECMEM_DEFAULT].alignment = 1;
|
|
}
|
|
|
|
if (!execmem_validate(info))
|
|
return;
|
|
|
|
execmem_init_missing(info);
|
|
|
|
execmem_info = info;
|
|
}
|
|
|
|
#ifdef CONFIG_ARCH_WANTS_EXECMEM_LATE
|
|
static int __init execmem_late_init(void)
|
|
{
|
|
__execmem_init();
|
|
return 0;
|
|
}
|
|
core_initcall(execmem_late_init);
|
|
#else
|
|
void __init execmem_init(void)
|
|
{
|
|
__execmem_init();
|
|
}
|
|
#endif
|