Re: [PATCH v5 2/2] alloc_tag: fix undetected compressed tag overflow when profiling is disabled
Hao Ge <[email protected]>
| Newsgroups | org.kernel.vger.linux-modules,dev.linux.lists.sashiko-reviews |
|---|---|
| Message-ID | <[email protected]> |
On 2026/8/12 14:32, Hao Ge wrote: > CC Andrew and Suren > > > On 2026/8/12 14:04, [email protected] wrote: >> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: >> >> New issues: >> - [High] Unconditional allocation of per-CPU counters for modules causes a memory leak when memory profiling is disabled and the module load is retried. >> >> Pre-existing issues: >> - [Critical] Concurrent module loading can cause an out-of-bounds memory write and module corruption because the global profiling flag is toggled without cross-phase synchronization. >> - [High] A race condition on the global module tags size variable can lead to unmapped virtual memory access and a kernel panic. >> - [High] If page population fails, a dangling module pointer is left in the maple tree, leading to a Use-After-Free. >> -- >> >> --- Patch [2]: [PATCH v5 2/2] alloc_tag: fix undetected compressed tag overflow when profiling is disabled --- >> commit ffb2b208763f32cdfb3d6cb740d4d4efa5c99ae0 >> Author: Hao Ge <[email protected]> >> >> alloc_tag: fix undetected compressed tag overflow when profiling is disabled >> >> This patch modifies module layout and allocation to retry loading without >> memory profiling if the compressed tag limit is exceeded. It removes the >> profiling enabled guard when checking for tag overflow and handles the >> fallback by resetting section flags and repeating the layout process. >> >>> diff --git a/kernel/module/main.c b/kernel/module/main.c >>> --- a/kernel/module/main.c >>> +++ b/kernel/module/main.c >> >> [ ... ] >> >>> @@ -2966,18 +2967,38 @@ static struct module *layout_and_allocate(struct load_info *info, int flags) >>> */ >>> module_mark_ro_after_init(info->hdr, info->sechdrs, info->secstrings); >>> + /* >>> + * Save the sizes reserved by module_frob_arch_sections() so they can >>> + * be restored if we retry below. >>> + */ >>> + for_each_mod_mem_type(type) >>> + frob_size[type] = info->mod->mem[type].size; >>> + >>> /* >>> * Determine total sizes, and put offsets in sh_entsize. For now >>> * this is done generically; there doesn't appear to be any >>> * special cases for the architectures. >>> */ >>> +retry: >>> layout_sections(info->mod, info); >>> layout_symtab(info->mod, info); >>> /* Allocate and move to the final place */ >>> err = move_module(info->mod, info); >> >> [Severity: Critical] >> This isn't a bug introduced by this patch, but can concurrent module loading >> cause memory corruption if profiling is disabled mid-flight? >> >> In move_module(), if one thread disables profiling globally via >> shutdown_mem_profiling() while another thread is halfway through module >> loading (between layout calculation and memory movement), the second thread's >> section offset queries could evaluate to false. >> >> This would cause the module loader to use an un-incremented 0 offset and >> incorrectly write the codetag section into the base memory, overwriting the >> start of the module's core memory block. >> >>> - if (err) >>> - return ERR_PTR(err); >>> + if (err) { >>> + if (err != -EAGAIN) >>> + return ERR_PTR(err); >>> + /* >>> + * -EAGAIN means profiling was disabled but the module >>> + * can still load without it. Reset state and retry. >>> + */ >>> + rewrite_section_headers(info, flags); >>> + for_each_mod_mem_type(type) >>> + info->mod->mem[type].size = frob_size[type]; >>> + info->sechdrs[info->index.sym].sh_flags &= ~(unsigned long)SHF_ALLOC; >>> + info->sechdrs[info->index.str].sh_flags &= ~(unsigned long)SHF_ALLOC; >>> + goto retry; >> >> [Severity: High] >> Does this retry mechanism cause a memory leak for per-CPU counters? >> >> When the module load is retried via this new loop, the codetag section is >> treated as standard memory and bypasses maple tree registration. However, the >> subsequent codetag module initialization unconditionally allocates per-CPU >> memory for these tags regardless of the profiling state. >> >> During module unload in release_module_tags(), the cleanup path exits early >> because the module cannot be found in the maple tree, permanently leaking >> the per-CPU memory on every unload. > > The per-CPU counter leak is pre-existing, not introduced by the retry > > mechanism. > > The root cause is that shutdown_mem_profiling() permanently sets > > > mem_profiling_support to false, after which needs_section_mem() > > returns false for every subsequent module. Their codetag sections are > > placed as regular data, so they are never inserted into the alloc_tag > > maple tree. Yet codetag_load_module() still unconditionally invokes > > the load_module() callback, which allocates percpu counters that > > release_module_tags() cannot find on unload. > > I'll send out a separate patch to fix this leak later. > > Sorry for the messy indentation — it's down to my mail client's formatting config. A colleague pointed out the official recommended indent configs for kernel mail clients, which I'd missed entirely. https://elixir.bootlin.com/linux/v7.2-rc7/source/Documentation/process/email-clients.rst Sorry for all the hassle I've caused everyone earlier. The patch fixing the per-CPU counter leak has been sent out: https://lore.kernel.org/all/[email protected]/ Thanks Best Regards Hao > >> >>> + } >>> /* Module has been copied to its final place now: return it. */ >>> mod = (void *)info->sechdrs[info->index.mod].sh_addr; >> >>> diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c >>> --- a/mm/alloc_tag.c >>> +++ b/mm/alloc_tag.c >> >> [ ... ] >> >>> @@ -950,10 +950,12 @@ static void *reserve_module_tags(struct module *mod, unsigned long size, >>> int grow_res; >>> module_tags.size = offset + size; >> >> [Severity: High] >> This is a pre-existing issue, but does the update to module_tags.size race >> with concurrent module loads? >> >> The high-water mark for the module tags size is updated here without a lock >> after the maple tree lock is released. If two concurrent module loads race on >> this update, a higher offset might be overwritten by a smaller one. >> >> Subsequently, vm_module_tags_populate() relies on this corrupted size and >> would fail to map the physical pages for the higher virtual addresses, leading >> to a page fault when the module accesses its allocated tags. >> >>> - if (mem_alloc_profiling_enabled() && !tags_addressable()) { >>> + if (!tags_addressable()) { >>> shutdown_mem_profiling(true); >>> - pr_warn("With module %s there are too many tags to fit in %d page flag bits. Memory allocation profiling is disabled!\n", >>> - mod->name, NR_UNUSED_PAGEFLAG_BITS); >>> + pr_warn_once("With module %s there are too many tags to fit in %d page flag bits. Memory allocation profiling is disabled!\n", >>> + mod->name, NR_UNUSED_PAGEFLAG_BITS); >>> + release_module_tags(mod, false); >>> + return ERR_PTR(-EAGAIN); >>> } >>> grow_res = vm_module_tags_populate(); >> >> [Severity: High] >> This is a pre-existing issue, but could this lead to a dangling pointer in >> the maple tree if page population fails? >> >> If vm_module_tags_populate() fails and returns an error, the allocation >> routine returns an error here but doesn't remove the newly inserted module >> pointer from the maple tree. >> >> Since this error bypasses the standard codetag cleanup routines in the module >> loader (where codetag_section_found might remain false in move_module()), >> the maple tree retains a pointer to the aborted module. Once the module >> structure is freed, future maple tree iterations could dereference this freed >> pointer. >>