mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-05-24 10:39:52 +00:00

Loading modules with finit_module() can end up using vmalloc(), vmap() and vmalloc() again, for a total of up to 3 separate allocations in the worst case for a single module. We always kernel_read*() the module, that's a vmalloc(). Then vmap() is used for the module decompression, and if so the last read buffer is freed as we use the now decompressed module buffer to stuff data into our copy module. The last allocation is specific to each architectures but pretty much that's generally a series of vmalloc() calls or a variation of vmalloc to handle ELF sections with special permissions. Evaluation with new stress-ng module support [1] with just 100 ops is proving that you can end up using GiBs of data easily even with all care we have in the kernel and userspace today in trying to not load modules which are already loaded. 100 ops seems to resemble the sort of pressure a system with about 400 CPUs can create on module loading. Although issues relating to duplicate module requests due to each CPU inucurring a new module reuest is silly and some of these are being fixed, we currently lack proper tooling to help diagnose easily what happened, when it happened and who likely is to blame -- userspace or kernel module autoloading. Provide an initial set of stats which use debugfs to let us easily scrape post-boot information about failed loads. This sort of information can be used on production worklaods to try to optimize *avoiding* redundant memory pressure using finit_module(). There's a few examples that can be provided: A 255 vCPU system without the next patch in this series applied: Startup finished in 19.143s (kernel) + 7.078s (userspace) = 26.221s graphical.target reached after 6.988s in userspace And 13.58 GiB of virtual memory space lost due to failed module loading: root@big ~ # cat /sys/kernel/debug/modules/stats Mods ever loaded 67 Mods failed on kread 0 Mods failed on decompress 0 Mods failed on becoming 0 Mods failed on load 1411 Total module size 11464704 Total mod text size 4194304 Failed kread bytes 0 Failed decompress bytes 0 Failed becoming bytes 0 Failed kmod bytes 14588526272 Virtual mem wasted bytes 14588526272 Average mod size 171115 Average mod text size 62602 Average fail load bytes 10339140 Duplicate failed modules: module-name How-many-times Reason kvm_intel 249 Load kvm 249 Load irqbypass 8 Load crct10dif_pclmul 128 Load ghash_clmulni_intel 27 Load sha512_ssse3 50 Load sha512_generic 200 Load aesni_intel 249 Load crypto_simd 41 Load cryptd 131 Load evdev 2 Load serio_raw 1 Load virtio_pci 3 Load nvme 3 Load nvme_core 3 Load virtio_pci_legacy_dev 3 Load virtio_pci_modern_dev 3 Load t10_pi 3 Load virtio 3 Load crc32_pclmul 6 Load crc64_rocksoft 3 Load crc32c_intel 40 Load virtio_ring 3 Load crc64 3 Load The following screen shot, of a simple 8vcpu 8 GiB KVM guest with the next patch in this series applied, shows 226.53 MiB are wasted in virtual memory allocations which due to duplicate module requests during boot. It also shows an average module memory size of 167.10 KiB and an an average module .text + .init.text size of 61.13 KiB. The end shows all modules which were detected as duplicate requests and whether or not they failed early after just the first kernel_read*() call or late after we've already allocated the private space for the module in layout_and_allocate(). A system with module decompression would reveal more wasted virtual memory space. We should put effort now into identifying the source of these duplicate module requests and trimming these down as much possible. Larger systems will obviously show much more wasted virtual memory allocations. root@kmod ~ # cat /sys/kernel/debug/modules/stats Mods ever loaded 67 Mods failed on kread 0 Mods failed on decompress 0 Mods failed on becoming 83 Mods failed on load 16 Total module size 11464704 Total mod text size 4194304 Failed kread bytes 0 Failed decompress bytes 0 Failed becoming bytes 228959096 Failed kmod bytes 8578080 Virtual mem wasted bytes 237537176 Average mod size 171115 Average mod text size 62602 Avg fail becoming bytes 2758544 Average fail load bytes 536130 Duplicate failed modules: module-name How-many-times Reason kvm_intel 7 Becoming kvm 7 Becoming irqbypass 6 Becoming & Load crct10dif_pclmul 7 Becoming & Load ghash_clmulni_intel 7 Becoming & Load sha512_ssse3 6 Becoming & Load sha512_generic 7 Becoming & Load aesni_intel 7 Becoming crypto_simd 7 Becoming & Load cryptd 3 Becoming & Load evdev 1 Becoming serio_raw 1 Becoming nvme 3 Becoming nvme_core 3 Becoming t10_pi 3 Becoming virtio_pci 3 Becoming crc32_pclmul 6 Becoming & Load crc64_rocksoft 3 Becoming crc32c_intel 3 Becoming virtio_pci_modern_dev 2 Becoming virtio_pci_legacy_dev 1 Becoming crc64 2 Becoming virtio 2 Becoming virtio_ring 2 Becoming [0] https://github.com/ColinIanKing/stress-ng.git [1] echo 0 > /proc/sys/vm/oom_dump_tasks ./stress-ng --module 100 --module-name xfs Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
380 lines
11 KiB
C
380 lines
11 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/* Module internals
|
|
*
|
|
* Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*/
|
|
|
|
#include <linux/elf.h>
|
|
#include <linux/compiler.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/rculist.h>
|
|
#include <linux/rcupdate.h>
|
|
#include <linux/mm.h>
|
|
|
|
#ifndef ARCH_SHF_SMALL
|
|
#define ARCH_SHF_SMALL 0
|
|
#endif
|
|
|
|
/*
|
|
* Use highest 4 bits of sh_entsize to store the mod_mem_type of this
|
|
* section. This leaves 28 bits for offset on 32-bit systems, which is
|
|
* about 256 MiB (WARN_ON_ONCE if we exceed that).
|
|
*/
|
|
|
|
#define SH_ENTSIZE_TYPE_BITS 4
|
|
#define SH_ENTSIZE_TYPE_SHIFT (BITS_PER_LONG - SH_ENTSIZE_TYPE_BITS)
|
|
#define SH_ENTSIZE_TYPE_MASK ((1UL << SH_ENTSIZE_TYPE_BITS) - 1)
|
|
#define SH_ENTSIZE_OFFSET_MASK ((1UL << (BITS_PER_LONG - SH_ENTSIZE_TYPE_BITS)) - 1)
|
|
|
|
/* Maximum number of characters written by module_flags() */
|
|
#define MODULE_FLAGS_BUF_SIZE (TAINT_FLAGS_COUNT + 4)
|
|
|
|
extern struct mutex module_mutex;
|
|
extern struct list_head modules;
|
|
|
|
extern struct module_attribute *modinfo_attrs[];
|
|
extern size_t modinfo_attrs_count;
|
|
|
|
/* Provided by the linker */
|
|
extern const struct kernel_symbol __start___ksymtab[];
|
|
extern const struct kernel_symbol __stop___ksymtab[];
|
|
extern const struct kernel_symbol __start___ksymtab_gpl[];
|
|
extern const struct kernel_symbol __stop___ksymtab_gpl[];
|
|
extern const s32 __start___kcrctab[];
|
|
extern const s32 __start___kcrctab_gpl[];
|
|
|
|
struct load_info {
|
|
const char *name;
|
|
/* pointer to module in temporary copy, freed at end of load_module() */
|
|
struct module *mod;
|
|
Elf_Ehdr *hdr;
|
|
unsigned long len;
|
|
Elf_Shdr *sechdrs;
|
|
char *secstrings, *strtab;
|
|
unsigned long symoffs, stroffs, init_typeoffs, core_typeoffs;
|
|
bool sig_ok;
|
|
#ifdef CONFIG_KALLSYMS
|
|
unsigned long mod_kallsyms_init_off;
|
|
#endif
|
|
#ifdef CONFIG_MODULE_DECOMPRESS
|
|
#ifdef CONFIG_MODULE_STATS
|
|
unsigned long compressed_len;
|
|
#endif
|
|
struct page **pages;
|
|
unsigned int max_pages;
|
|
unsigned int used_pages;
|
|
#endif
|
|
struct {
|
|
unsigned int sym, str, mod, vers, info, pcpu;
|
|
} index;
|
|
};
|
|
|
|
enum mod_license {
|
|
NOT_GPL_ONLY,
|
|
GPL_ONLY,
|
|
};
|
|
|
|
struct find_symbol_arg {
|
|
/* Input */
|
|
const char *name;
|
|
bool gplok;
|
|
bool warn;
|
|
|
|
/* Output */
|
|
struct module *owner;
|
|
const s32 *crc;
|
|
const struct kernel_symbol *sym;
|
|
enum mod_license license;
|
|
};
|
|
|
|
int mod_verify_sig(const void *mod, struct load_info *info);
|
|
int try_to_force_load(struct module *mod, const char *reason);
|
|
bool find_symbol(struct find_symbol_arg *fsa);
|
|
struct module *find_module_all(const char *name, size_t len, bool even_unformed);
|
|
int cmp_name(const void *name, const void *sym);
|
|
long module_get_offset_and_type(struct module *mod, enum mod_mem_type type,
|
|
Elf_Shdr *sechdr, unsigned int section);
|
|
char *module_flags(struct module *mod, char *buf, bool show_state);
|
|
size_t module_flags_taint(unsigned long taints, char *buf);
|
|
|
|
char *module_next_tag_pair(char *string, unsigned long *secsize);
|
|
|
|
#define for_each_modinfo_entry(entry, info, name) \
|
|
for (entry = get_modinfo(info, name); entry; entry = get_next_modinfo(info, name, entry))
|
|
|
|
static inline void module_assert_mutex_or_preempt(void)
|
|
{
|
|
#ifdef CONFIG_LOCKDEP
|
|
if (unlikely(!debug_locks))
|
|
return;
|
|
|
|
WARN_ON_ONCE(!rcu_read_lock_sched_held() &&
|
|
!lockdep_is_held(&module_mutex));
|
|
#endif
|
|
}
|
|
|
|
static inline unsigned long kernel_symbol_value(const struct kernel_symbol *sym)
|
|
{
|
|
#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
|
|
return (unsigned long)offset_to_ptr(&sym->value_offset);
|
|
#else
|
|
return sym->value;
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_LIVEPATCH
|
|
int copy_module_elf(struct module *mod, struct load_info *info);
|
|
void free_module_elf(struct module *mod);
|
|
#else /* !CONFIG_LIVEPATCH */
|
|
static inline int copy_module_elf(struct module *mod, struct load_info *info)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void free_module_elf(struct module *mod) { }
|
|
#endif /* CONFIG_LIVEPATCH */
|
|
|
|
static inline bool set_livepatch_module(struct module *mod)
|
|
{
|
|
#ifdef CONFIG_LIVEPATCH
|
|
mod->klp = true;
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* enum fail_dup_mod_reason - state at which a duplicate module was detected
|
|
*
|
|
* @FAIL_DUP_MOD_BECOMING: the module is read properly, passes all checks but
|
|
* we've determined that another module with the same name is already loaded
|
|
* or being processed on our &modules list. This happens on early_mod_check()
|
|
* right before layout_and_allocate(). The kernel would have already
|
|
* vmalloc()'d space for the entire module through finit_module(). If
|
|
* decompression was used two vmap() spaces were used. These failures can
|
|
* happen when userspace has not seen the module present on the kernel and
|
|
* tries to load the module multiple times at same time.
|
|
* @FAIL_DUP_MOD_LOAD: the module has been read properly, passes all validation
|
|
* checks and the kernel determines that the module was unique and because
|
|
* of this allocated yet another private kernel copy of the module space in
|
|
* layout_and_allocate() but after this determined in add_unformed_module()
|
|
* that another module with the same name is already loaded or being processed.
|
|
* These failures should be mitigated as much as possible and are indicative
|
|
* of really fast races in loading modules. Without module decompression
|
|
* they waste twice as much vmap space. With module decompression three
|
|
* times the module's size vmap space is wasted.
|
|
*/
|
|
enum fail_dup_mod_reason {
|
|
FAIL_DUP_MOD_BECOMING = 0,
|
|
FAIL_DUP_MOD_LOAD,
|
|
};
|
|
|
|
#ifdef CONFIG_MODULE_DEBUGFS
|
|
extern struct dentry *mod_debugfs_root;
|
|
#endif
|
|
|
|
#ifdef CONFIG_MODULE_STATS
|
|
|
|
#define mod_stat_add_long(count, var) atomic_long_add(count, var)
|
|
#define mod_stat_inc(name) atomic_inc(name)
|
|
|
|
extern atomic_long_t total_mod_size;
|
|
extern atomic_long_t total_text_size;
|
|
extern atomic_long_t invalid_kread_bytes;
|
|
extern atomic_long_t invalid_decompress_bytes;
|
|
|
|
extern atomic_t modcount;
|
|
extern atomic_t failed_kreads;
|
|
extern atomic_t failed_decompress;
|
|
struct mod_fail_load {
|
|
struct list_head list;
|
|
char name[MODULE_NAME_LEN];
|
|
atomic_long_t count;
|
|
unsigned long dup_fail_mask;
|
|
};
|
|
|
|
int try_add_failed_module(const char *name, enum fail_dup_mod_reason reason);
|
|
void mod_stat_bump_invalid(struct load_info *info, int flags);
|
|
void mod_stat_bump_becoming(struct load_info *info, int flags);
|
|
|
|
#else
|
|
|
|
#define mod_stat_add_long(name, var)
|
|
#define mod_stat_inc(name)
|
|
|
|
static inline int try_add_failed_module(const char *name,
|
|
enum fail_dup_mod_reason reason)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void mod_stat_bump_invalid(struct load_info *info, int flags)
|
|
{
|
|
}
|
|
|
|
static inline void mod_stat_bump_becoming(struct load_info *info, int flags)
|
|
{
|
|
}
|
|
|
|
#endif /* CONFIG_MODULE_STATS */
|
|
|
|
#ifdef CONFIG_MODULE_UNLOAD_TAINT_TRACKING
|
|
struct mod_unload_taint {
|
|
struct list_head list;
|
|
char name[MODULE_NAME_LEN];
|
|
unsigned long taints;
|
|
u64 count;
|
|
};
|
|
|
|
int try_add_tainted_module(struct module *mod);
|
|
void print_unloaded_tainted_modules(void);
|
|
#else /* !CONFIG_MODULE_UNLOAD_TAINT_TRACKING */
|
|
static inline int try_add_tainted_module(struct module *mod)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void print_unloaded_tainted_modules(void)
|
|
{
|
|
}
|
|
#endif /* CONFIG_MODULE_UNLOAD_TAINT_TRACKING */
|
|
|
|
#ifdef CONFIG_MODULE_DECOMPRESS
|
|
int module_decompress(struct load_info *info, const void *buf, size_t size);
|
|
void module_decompress_cleanup(struct load_info *info);
|
|
#else
|
|
static inline int module_decompress(struct load_info *info,
|
|
const void *buf, size_t size)
|
|
{
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static inline void module_decompress_cleanup(struct load_info *info)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
struct mod_tree_root {
|
|
#ifdef CONFIG_MODULES_TREE_LOOKUP
|
|
struct latch_tree_root root;
|
|
#endif
|
|
unsigned long addr_min;
|
|
unsigned long addr_max;
|
|
#ifdef CONFIG_ARCH_WANTS_MODULES_DATA_IN_VMALLOC
|
|
unsigned long data_addr_min;
|
|
unsigned long data_addr_max;
|
|
#endif
|
|
};
|
|
|
|
extern struct mod_tree_root mod_tree;
|
|
|
|
#ifdef CONFIG_MODULES_TREE_LOOKUP
|
|
void mod_tree_insert(struct module *mod);
|
|
void mod_tree_remove_init(struct module *mod);
|
|
void mod_tree_remove(struct module *mod);
|
|
struct module *mod_find(unsigned long addr, struct mod_tree_root *tree);
|
|
#else /* !CONFIG_MODULES_TREE_LOOKUP */
|
|
|
|
static inline void mod_tree_insert(struct module *mod) { }
|
|
static inline void mod_tree_remove_init(struct module *mod) { }
|
|
static inline void mod_tree_remove(struct module *mod) { }
|
|
static inline struct module *mod_find(unsigned long addr, struct mod_tree_root *tree)
|
|
{
|
|
struct module *mod;
|
|
|
|
list_for_each_entry_rcu(mod, &modules, list,
|
|
lockdep_is_held(&module_mutex)) {
|
|
if (within_module(addr, mod))
|
|
return mod;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
#endif /* CONFIG_MODULES_TREE_LOOKUP */
|
|
|
|
void module_enable_ro(const struct module *mod, bool after_init);
|
|
void module_enable_nx(const struct module *mod);
|
|
void module_enable_x(const struct module *mod);
|
|
int module_enforce_rwx_sections(Elf_Ehdr *hdr, Elf_Shdr *sechdrs,
|
|
char *secstrings, struct module *mod);
|
|
|
|
#ifdef CONFIG_MODULE_SIG
|
|
int module_sig_check(struct load_info *info, int flags);
|
|
#else /* !CONFIG_MODULE_SIG */
|
|
static inline int module_sig_check(struct load_info *info, int flags)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif /* !CONFIG_MODULE_SIG */
|
|
|
|
#ifdef CONFIG_DEBUG_KMEMLEAK
|
|
void kmemleak_load_module(const struct module *mod, const struct load_info *info);
|
|
#else /* !CONFIG_DEBUG_KMEMLEAK */
|
|
static inline void kmemleak_load_module(const struct module *mod,
|
|
const struct load_info *info) { }
|
|
#endif /* CONFIG_DEBUG_KMEMLEAK */
|
|
|
|
#ifdef CONFIG_KALLSYMS
|
|
void init_build_id(struct module *mod, const struct load_info *info);
|
|
void layout_symtab(struct module *mod, struct load_info *info);
|
|
void add_kallsyms(struct module *mod, const struct load_info *info);
|
|
unsigned long find_kallsyms_symbol_value(struct module *mod, const char *name);
|
|
|
|
static inline bool sect_empty(const Elf_Shdr *sect)
|
|
{
|
|
return !(sect->sh_flags & SHF_ALLOC) || sect->sh_size == 0;
|
|
}
|
|
#else /* !CONFIG_KALLSYMS */
|
|
static inline void init_build_id(struct module *mod, const struct load_info *info) { }
|
|
static inline void layout_symtab(struct module *mod, struct load_info *info) { }
|
|
static inline void add_kallsyms(struct module *mod, const struct load_info *info) { }
|
|
#endif /* CONFIG_KALLSYMS */
|
|
|
|
#ifdef CONFIG_SYSFS
|
|
int mod_sysfs_setup(struct module *mod, const struct load_info *info,
|
|
struct kernel_param *kparam, unsigned int num_params);
|
|
void mod_sysfs_teardown(struct module *mod);
|
|
void init_param_lock(struct module *mod);
|
|
#else /* !CONFIG_SYSFS */
|
|
static inline int mod_sysfs_setup(struct module *mod,
|
|
const struct load_info *info,
|
|
struct kernel_param *kparam,
|
|
unsigned int num_params)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void mod_sysfs_teardown(struct module *mod) { }
|
|
static inline void init_param_lock(struct module *mod) { }
|
|
#endif /* CONFIG_SYSFS */
|
|
|
|
#ifdef CONFIG_MODVERSIONS
|
|
int check_version(const struct load_info *info,
|
|
const char *symname, struct module *mod, const s32 *crc);
|
|
void module_layout(struct module *mod, struct modversion_info *ver, struct kernel_param *kp,
|
|
struct kernel_symbol *ks, struct tracepoint * const *tp);
|
|
int check_modstruct_version(const struct load_info *info, struct module *mod);
|
|
int same_magic(const char *amagic, const char *bmagic, bool has_crcs);
|
|
#else /* !CONFIG_MODVERSIONS */
|
|
static inline int check_version(const struct load_info *info,
|
|
const char *symname,
|
|
struct module *mod,
|
|
const s32 *crc)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static inline int check_modstruct_version(const struct load_info *info,
|
|
struct module *mod)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static inline int same_magic(const char *amagic, const char *bmagic, bool has_crcs)
|
|
{
|
|
return strcmp(amagic, bmagic) == 0;
|
|
}
|
|
#endif /* CONFIG_MODVERSIONS */
|