* [PATCH v11 1/7] alloc_tag: move release_module_tags() above reserve_module_tags()
2026-09-29 8:20 [PATCH v11 0/7] alloc_tag and module codetag section fixes Hao Ge
@ 2026-09-29 8:20 ` Hao Ge
2026-09-29 8:20 ` [PATCH v11 2/7] mm/vmalloc: undo partial mappings inside the mapping functions Hao Ge
` (6 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Hao Ge @ 2026-09-29 8:20 UTC (permalink / raw)
To: Suren Baghdasaryan, =Kent Overstreet, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Andrew Morton, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Vlastimil Babka, Michal Hocko, Brendan Jackman, Johannes Weiner,
Zi Yan, Uladzislau Rezki
Cc: linux-mm, linux-kernel, linux-modules, kasan-dev, Hao Ge,
Sashiko, stable
release_module_tags() is a cleanup helper. reserve_module_tags() can
also fail after storing the reservation in the maple tree, in which
case it should call release_module_tags() to undo it. Move the helper
above reserve_module_tags() so no forward declaration is needed.
No functional change.
Fixes: 4835f747d3ed ("alloc_tag: support for page allocation tag compression")
Fixes: 0f9b685626da ("alloc_tag: populate memory for module tags as needed")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Cc: stable@vger.kernel.org
Acked-by: Suren Baghdasaryan <surenb@google.com>
Signed-off-by: Hao Ge <hao.ge@linux.dev>
---
mm/alloc_tag.c | 92 +++++++++++++++++++++++++-------------------------
1 file changed, 46 insertions(+), 46 deletions(-)
diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c
index b33410310477..2070e682fe10 100644
--- a/mm/alloc_tag.c
+++ b/mm/alloc_tag.c
@@ -835,6 +835,52 @@ static int vm_module_tags_populate(void)
return 0;
}
+static void release_module_tags(struct module *mod, bool used)
+{
+ MA_STATE(mas, &mod_area_mt, module_tags.size, module_tags.size);
+ struct alloc_tag *start_tag;
+ struct alloc_tag *end_tag;
+ struct module *val;
+
+ mas_lock(&mas);
+ mas_for_each_rev(&mas, val, 0)
+ if (val == mod)
+ break;
+
+ if (!val) /* module not found */
+ goto out;
+
+ if (!used)
+ goto release_area;
+
+ start_tag = (struct alloc_tag *)(module_tags.start_addr + mas.index);
+ end_tag = (struct alloc_tag *)(module_tags.start_addr + mas.last);
+ if (!clean_unused_counters(start_tag, end_tag)) {
+ struct alloc_tag *tag;
+
+ for (tag = start_tag; tag <= end_tag; tag++) {
+ struct alloc_tag_counters counter;
+
+ if (!tag->counters)
+ continue;
+
+ counter = alloc_tag_read(tag);
+ pr_info("%s:%u module %s func:%s has %llu allocated at module unload\n",
+ tag->ct.filename, tag->ct.lineno, tag->ct.modname,
+ tag->ct.function, counter.bytes);
+ }
+ } else {
+ used = false;
+ }
+release_area:
+ mas_store(&mas, used ? &unloaded_mod : NULL);
+ val = mas_prev_range(&mas, 0);
+ if (val == &prepend_mod)
+ mas_store(&mas, NULL);
+out:
+ mas_unlock(&mas);
+}
+
static void *reserve_module_tags(struct module *mod, unsigned long size,
unsigned int prepend, unsigned long align)
{
@@ -922,52 +968,6 @@ static void *reserve_module_tags(struct module *mod, unsigned long size,
return (struct alloc_tag *)(module_tags.start_addr + offset);
}
-static void release_module_tags(struct module *mod, bool used)
-{
- MA_STATE(mas, &mod_area_mt, module_tags.size, module_tags.size);
- struct alloc_tag *start_tag;
- struct alloc_tag *end_tag;
- struct module *val;
-
- mas_lock(&mas);
- mas_for_each_rev(&mas, val, 0)
- if (val == mod)
- break;
-
- if (!val) /* module not found */
- goto out;
-
- if (!used)
- goto release_area;
-
- start_tag = (struct alloc_tag *)(module_tags.start_addr + mas.index);
- end_tag = (struct alloc_tag *)(module_tags.start_addr + mas.last);
- if (!clean_unused_counters(start_tag, end_tag)) {
- struct alloc_tag *tag;
-
- for (tag = start_tag; tag <= end_tag; tag++) {
- struct alloc_tag_counters counter;
-
- if (!tag->counters)
- continue;
-
- counter = alloc_tag_read(tag);
- pr_info("%s:%u module %s func:%s has %llu allocated at module unload\n",
- tag->ct.filename, tag->ct.lineno, tag->ct.modname,
- tag->ct.function, counter.bytes);
- }
- } else {
- used = false;
- }
-release_area:
- mas_store(&mas, used ? &unloaded_mod : NULL);
- val = mas_prev_range(&mas, 0);
- if (val == &prepend_mod)
- mas_store(&mas, NULL);
-out:
- mas_unlock(&mas);
-}
-
static int load_module(struct module *mod, struct codetag *start, struct codetag *stop)
{
/* Allocate module alloc_tag percpu counters */
--
2.25.1
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v11 2/7] mm/vmalloc: undo partial mappings inside the mapping functions
2026-09-29 8:20 [PATCH v11 0/7] alloc_tag and module codetag section fixes Hao Ge
2026-09-29 8:20 ` [PATCH v11 1/7] alloc_tag: move release_module_tags() above reserve_module_tags() Hao Ge
@ 2026-09-29 8:20 ` Hao Ge
2026-09-29 8:20 ` [PATCH v11 3/7] alloc_tag: clean up the populate failure path Hao Ge
` (5 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Hao Ge @ 2026-09-29 8:20 UTC (permalink / raw)
To: Suren Baghdasaryan, =Kent Overstreet, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Andrew Morton, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Vlastimil Babka, Michal Hocko, Brendan Jackman, Johannes Weiner,
Zi Yan, Uladzislau Rezki
Cc: linux-mm, linux-kernel, linux-modules, kasan-dev, Hao Ge,
Sashiko, stable
__vmap_pages_range_noflush() and friends can install some PTEs before
failing and leave them mapped, and the kernel callers do not agree on
who cleans them up. For example, pcpu_map_pages() and
kmsan_ioremap_page_range() unmap the leftovers themselves, while
vm_module_tags_populate() and the __GFP_NOFAIL retry loop in
__vmalloc_area_node() relied on the mapping functions cleaning up and
did not call anything like vunmap_range() themselves. When the same
range is mapped again, the attempt hits the leftovers and fails, with
BUG() in vmap_pte_range() for huge mappings.
After discussing with Suren and Ulad, we decided the cleanup belongs
to __vmap_pages_range_noflush() and friends, so the callers no longer
need to unmap the partial mappings themselves. Each function now
undoes the PTEs it installed itself.
The rollback calls the low-level __vunmap_range_noflush(), it just
clears the PTEs of the range it is given, which is all a rollback
needs. It cannot use vunmap_range_noflush() because these mapping
functions also map the KMSAN shadow and origin, and for a metadata
range its hook would look up the metadata of the metadata, get 0
and BUG() on addr >= end. The failed mappings were never accessed,
no TLB flush needed.
Fixes: 9376130c390a ("mm/vmalloc: add support for __GFP_NOFAIL")
Fixes: 0f9b685626da ("alloc_tag: populate memory for module tags as needed")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Cc: stable@vger.kernel.org
Signed-off-by: Hao Ge <hao.ge@linux.dev>
---
mm/kmsan/shadow.c | 4 ++++
mm/vmalloc.c | 31 +++++++++++++++++++++++++++++--
2 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/mm/kmsan/shadow.c b/mm/kmsan/shadow.c
index 0c88d89bf0d6..2166086d3dc3 100644
--- a/mm/kmsan/shadow.c
+++ b/mm/kmsan/shadow.c
@@ -258,6 +258,10 @@ int kmsan_vmap_pages_range_noflush(unsigned long start, unsigned long end,
o_pages, page_shift);
kmsan_leave_runtime();
if (mapped) {
+ /* Undo the shadow mapping set up above. */
+ kmsan_enter_runtime();
+ __vunmap_range_noflush(shadow_start, shadow_end);
+ kmsan_leave_runtime();
err = mapped;
goto ret;
}
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index 859e6d2d57a3..9bbf75706627 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -349,6 +349,10 @@ static int vmap_range_noflush(unsigned long addr, unsigned long end,
if (mask & ARCH_PAGE_TABLE_SYNC_MASK)
arch_sync_kernel_mappings(start, end);
+ /* Undo the PTEs installed before the failure. */
+ if (err)
+ __vunmap_range_noflush(start, end);
+
return err;
}
@@ -363,6 +367,9 @@ int vmap_page_range(unsigned long addr, unsigned long end,
if (!err)
err = kmsan_ioremap_page_range(addr, end, phys_addr, prot,
ioremap_max_page_shift);
+ if (err)
+ __vunmap_range_noflush(addr, end);
+
return err;
}
@@ -667,6 +674,10 @@ static int vmap_small_pages_range_noflush(unsigned long addr, unsigned long end,
if (mask & ARCH_PAGE_TABLE_SYNC_MASK)
arch_sync_kernel_mappings(start, end);
+ /* Undo the PTEs installed before the failure. */
+ if (err)
+ __vunmap_range_noflush(start, end);
+
return err;
}
@@ -683,6 +694,7 @@ int __vmap_pages_range_noflush(unsigned long addr, unsigned long end,
pgprot_t prot, struct page **pages, unsigned int page_shift)
{
unsigned int i, nr = (end - addr) >> PAGE_SHIFT;
+ unsigned long start = addr;
WARN_ON(page_shift < PAGE_SHIFT);
@@ -696,8 +708,14 @@ int __vmap_pages_range_noflush(unsigned long addr, unsigned long end,
err = vmap_range_noflush(addr, addr + (1UL << page_shift),
page_to_phys(pages[i]), prot,
page_shift);
- if (err)
+ if (err) {
+ /*
+ * vmap_range_noflush() undoes its own partial work,
+ * discard the chunks that already mapped.
+ */
+ __vunmap_range_noflush(start, addr);
return err;
+ }
addr += 1UL << page_shift;
}
@@ -714,7 +732,16 @@ int vmap_pages_range_noflush(unsigned long addr, unsigned long end,
if (ret)
return ret;
- return __vmap_pages_range_noflush(addr, end, prot, pages, page_shift);
+
+ ret = __vmap_pages_range_noflush(addr, end, prot, pages, page_shift);
+ /*
+ * The page tables undo themselves on failure. Tear down the
+ * metadata that was fully set up before the mapping failed.
+ */
+ if (ret)
+ kmsan_vunmap_range_noflush(addr, end);
+
+ return ret;
}
static int __vmap_pages_range(unsigned long addr, unsigned long end,
--
2.25.1
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v11 3/7] alloc_tag: clean up the populate failure path
2026-09-29 8:20 [PATCH v11 0/7] alloc_tag and module codetag section fixes Hao Ge
2026-09-29 8:20 ` [PATCH v11 1/7] alloc_tag: move release_module_tags() above reserve_module_tags() Hao Ge
2026-09-29 8:20 ` [PATCH v11 2/7] mm/vmalloc: undo partial mappings inside the mapping functions Hao Ge
@ 2026-09-29 8:20 ` Hao Ge
2026-09-29 8:20 ` [PATCH v11 4/7] module: introduce SH_ENTSIZE_STANDALONE for separately allocated sections Hao Ge
` (4 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Hao Ge @ 2026-09-29 8:20 UTC (permalink / raw)
To: Suren Baghdasaryan, =Kent Overstreet, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Andrew Morton, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Vlastimil Babka, Michal Hocko, Brendan Jackman, Johannes Weiner,
Zi Yan, Uladzislau Rezki
Cc: linux-mm, linux-kernel, linux-modules, kasan-dev, Hao Ge,
Sashiko, stable
The reservation is already stored in the maple tree when
vm_module_tags_populate() fails. A failed load never unloads the
module, so nothing releases the entry. Release it and roll
module_tags.size back. Without the rollback a concurrent load that
already passed needs_section_mem() can reuse the freed gap, skip
vm_module_tags_populate() and write to unmapped memory.
Fixes: 4835f747d3ed ("alloc_tag: support for page allocation tag compression")
Fixes: 0f9b685626da ("alloc_tag: populate memory for module tags as needed")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Cc: stable@vger.kernel.org
Signed-off-by: Hao Ge <hao.ge@linux.dev>
---
mm/alloc_tag.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c
index 2070e682fe10..3e6306ee0764 100644
--- a/mm/alloc_tag.c
+++ b/mm/alloc_tag.c
@@ -947,6 +947,7 @@ static void *reserve_module_tags(struct module *mod, unsigned long size,
return ret;
if (module_tags.size < offset + size) {
+ unsigned long prev_size = module_tags.size;
int grow_res;
module_tags.size = offset + size;
@@ -961,6 +962,8 @@ static void *reserve_module_tags(struct module *mod, unsigned long size,
shutdown_mem_profiling(true);
pr_err("Failed to allocate memory for allocation tags in the module %s. Memory allocation profiling is disabled!\n",
mod->name);
+ release_module_tags(mod, false);
+ module_tags.size = prev_size;
return ERR_PTR(grow_res);
}
}
--
2.25.1
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v11 4/7] module: introduce SH_ENTSIZE_STANDALONE for separately allocated sections
2026-09-29 8:20 [PATCH v11 0/7] alloc_tag and module codetag section fixes Hao Ge
` (2 preceding siblings ...)
2026-09-29 8:20 ` [PATCH v11 3/7] alloc_tag: clean up the populate failure path Hao Ge
@ 2026-09-29 8:20 ` Hao Ge
2026-09-29 8:20 ` [PATCH v11 5/7] module: allocate codetag sections before the regular module layout Hao Ge
` (3 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Hao Ge @ 2026-09-29 8:20 UTC (permalink / raw)
To: Suren Baghdasaryan, =Kent Overstreet, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Andrew Morton, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Vlastimil Babka, Michal Hocko, Brendan Jackman, Johannes Weiner,
Zi Yan, Uladzislau Rezki
Cc: linux-mm, linux-kernel, linux-modules, kasan-dev, Hao Ge,
Sashiko, stable
SHF_ALLOC means, per the ELF spec, that a section occupies memory
during process execution. Some module sections occupy memory
outside the regular module layout, for example the percpu section
with its per-CPU allocations. The loader currently excludes such
a section from the layout by clearing its SHF_ALLOC, which
overloads the flag with a loader-internal meaning.
apply_relocations() needs a special case for the section, and
find_sec(".data..percpu") returns different results before and
after layout_and_allocate().
Introduce SH_ENTSIZE_STANDALONE to mark sections with a separate
allocation. The percpu section is its first user. layout_sections()
and move_module() skip marked sections, and apply_relocations() goes
back to testing only SHF_ALLOC. Based on a patch by Petr Pavlu [1].
.data..percpu keeps SHF_ALLOC, so it would now show up under
/sys/module/*/sections/. The section has one instance per CPU and no
single address to report, and the entry never existed before, so
skip it in add_sect_attrs(). add_notes_attrs() indexes its attrs[]
array and skips it too. No functional or userspace-visible changes
otherwise.
Fixes: 4835f747d3ed ("alloc_tag: support for page allocation tag compression")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Link: https://lore.kernel.org/all/499bb60c-c6e3-43a3-bd92-95a0567ece5e@suse.com/ [1]
Suggested-by: Petr Pavlu <petr.pavlu@suse.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
Reviewed-by: Suren Baghdasaryan <surenb@google.com>
Cc: stable@vger.kernel.org
Signed-off-by: Hao Ge <hao.ge@linux.dev>
---
include/linux/module.h | 2 ++
kernel/module/internal.h | 8 ++++++++
kernel/module/kallsyms.c | 13 +++----------
kernel/module/main.c | 32 +++++++++++++++++---------------
kernel/module/sysfs.c | 17 +++++++++++++----
5 files changed, 43 insertions(+), 29 deletions(-)
diff --git a/include/linux/module.h b/include/linux/module.h
index 96cc98568eea..0c6f32ddcbf2 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -325,6 +325,8 @@ enum mod_mem_type {
MOD_INIT_RODATA,
MOD_MEM_NUM_TYPES,
+
+ MOD_STANDALONE = -2,
MOD_INVALID = -1,
};
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index 061161cc79d9..4c738074a27b 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -29,6 +29,14 @@
#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)
+/*
+ * Marker for sections with a separate allocation, which are not placed
+ * into mod->mem[].
+ */
+#define SH_ENTSIZE_STANDALONE \
+ (((unsigned long)MOD_STANDALONE & SH_ENTSIZE_TYPE_MASK) \
+ << SH_ENTSIZE_TYPE_SHIFT)
+
/* Maximum number of characters written by module_flags() */
#define MODULE_FLAGS_BUF_SIZE (TAINT_FLAGS_COUNT + 4)
diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c
index f23126d804b2..0dca6d40160e 100644
--- a/kernel/module/kallsyms.c
+++ b/kernel/module/kallsyms.c
@@ -76,7 +76,7 @@ static char elf_type(const Elf_Sym *sym, const struct load_info *info)
}
static bool is_core_symbol(const Elf_Sym *src, const Elf_Shdr *sechdrs,
- unsigned int shnum, unsigned int pcpundx)
+ unsigned int shnum)
{
const Elf_Shdr *sec;
enum mod_mem_type type;
@@ -86,11 +86,6 @@ static bool is_core_symbol(const Elf_Sym *src, const Elf_Shdr *sechdrs,
!src->st_name)
return false;
-#ifdef CONFIG_KALLSYMS_ALL
- if (src->st_shndx == pcpundx)
- return true;
-#endif
-
sec = sechdrs + src->st_shndx;
type = sec->sh_entsize >> SH_ENTSIZE_TYPE_SHIFT;
if (!(sec->sh_flags & SHF_ALLOC)
@@ -131,8 +126,7 @@ void layout_symtab(struct module *mod, struct load_info *info)
/* Compute total space required for the core symbols' strtab. */
for (ndst = i = 0; i < nsrc; i++) {
if (i == 0 || is_livepatch_module(mod) ||
- is_core_symbol(src + i, info->sechdrs, info->hdr->e_shnum,
- info->index.pcpu)) {
+ is_core_symbol(src + i, info->sechdrs, info->hdr->e_shnum)) {
strtab_size += strlen(&info->strtab[src[i].st_name]) + 1;
ndst++;
}
@@ -199,8 +193,7 @@ void add_kallsyms(struct module *mod, const struct load_info *info)
for (ndst = i = 0; i < kallsyms->num_symtab; i++) {
kallsyms->typetab[i] = elf_type(src + i, info);
if (i == 0 || is_livepatch_module(mod) ||
- is_core_symbol(src + i, info->sechdrs, info->hdr->e_shnum,
- info->index.pcpu)) {
+ is_core_symbol(src + i, info->sechdrs, info->hdr->e_shnum)) {
ssize_t ret;
mod->core_kallsyms.typetab[ndst] =
diff --git a/kernel/module/main.c b/kernel/module/main.c
index c1b34dc1e89a..ae2678ac7840 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -1619,12 +1619,13 @@ static int apply_relocations(struct module *mod, const struct load_info *info)
/*
* Don't bother with non-allocated sections.
- * An exception is the percpu section, which has separate allocations
- * for individual CPUs. We relocate the percpu section in the initial
- * ELF template and subsequently copy it to the per-CPU destinations.
+ *
+ * Note that .data..percpu has separate allocations for
+ * individual CPUs. We relocate the section in the
+ * initial ELF template and subsequently copy it to the
+ * per-CPU destinations.
*/
- if (!(info->sechdrs[infosec].sh_flags & SHF_ALLOC) &&
- (!infosec || infosec != info->index.pcpu))
+ if (!(info->sechdrs[infosec].sh_flags & SHF_ALLOC))
continue;
if (info->sechdrs[i].sh_flags & SHF_RELA_LIVEPATCH)
@@ -1715,7 +1716,7 @@ static void __layout_sections(struct module *mod, struct load_info *info, bool i
if ((s->sh_flags & masks[m][0]) != masks[m][0]
|| (s->sh_flags & masks[m][1])
- || s->sh_entsize != ~0UL
+ || s->sh_entsize != ~0UL /* offset or standalone */
|| is_init != module_init_layout_section(sname))
continue;
@@ -1745,16 +1746,10 @@ static void __layout_sections(struct module *mod, struct load_info *info, bool i
/*
* Lay out the SHF_ALLOC sections in a way not dissimilar to how ld
* might -- code, read-only data, read-write data, small data. Tally
- * sizes, and place the offsets into sh_entsize fields: high bit means it
- * belongs in init.
+ * sizes, and place the offsets into sh_entsize fields.
*/
static void layout_sections(struct module *mod, struct load_info *info)
{
- unsigned int i;
-
- for (i = 0; i < info->hdr->e_shnum; i++)
- info->sechdrs[i].sh_entsize = ~0UL;
-
pr_debug("Core section allocation order for %s:\n", mod->name);
__layout_sections(mod, info, false);
@@ -2822,7 +2817,8 @@ static int move_module(struct module *mod, struct load_info *info)
Elf_Shdr *shdr = &info->sechdrs[i];
const char *sname;
- if (!(shdr->sh_flags & SHF_ALLOC))
+ if (!(shdr->sh_flags & SHF_ALLOC)
+ || shdr->sh_entsize == SH_ENTSIZE_STANDALONE)
continue;
sname = info->secstrings + shdr->sh_name;
@@ -2954,6 +2950,7 @@ core_param(module_blacklist, module_blacklist, charp, 0400);
static struct module *layout_and_allocate(struct load_info *info, int flags)
{
struct module *mod;
+ unsigned int i;
int err;
/* Allow arches to frob section contents and sizes. */
@@ -2967,8 +2964,13 @@ static struct module *layout_and_allocate(struct load_info *info, int flags)
if (err < 0)
return ERR_PTR(err);
+ /* Repurpose sh_entsize to track where each section is allocated. */
+ for (i = 0; i < info->hdr->e_shnum; i++)
+ info->sechdrs[i].sh_entsize = ~0UL;
+
/* We will do a special allocation for per-cpu sections later. */
- info->sechdrs[info->index.pcpu].sh_flags &= ~(unsigned long)SHF_ALLOC;
+ if (info->index.pcpu)
+ info->sechdrs[info->index.pcpu].sh_entsize = SH_ENTSIZE_STANDALONE;
/*
* Mark relevant sections as SHF_RO_AFTER_INIT so layout_sections() can
diff --git a/kernel/module/sysfs.c b/kernel/module/sysfs.c
index 01c65d608873..15c6baeb39c4 100644
--- a/kernel/module/sysfs.c
+++ b/kernel/module/sysfs.c
@@ -62,6 +62,15 @@ static void free_sect_attrs(struct module_sect_attrs *sect_attrs)
kfree(sect_attrs);
}
+/*
+ * .data..percpu has a separate allocation per CPU and no single
+ * address to report.
+ */
+static bool sect_visible(const struct load_info *info, unsigned int i)
+{
+ return !sect_empty(&info->sechdrs[i]) && i != info->index.pcpu;
+}
+
static int add_sect_attrs(struct module *mod, const struct load_info *info)
{
struct module_sect_attrs *sect_attrs;
@@ -72,7 +81,7 @@ static int add_sect_attrs(struct module *mod, const struct load_info *info)
/* Count loaded sections and allocate structures */
for (i = 0; i < info->hdr->e_shnum; i++)
- if (!sect_empty(&info->sechdrs[i]))
+ if (sect_visible(info, i))
nloaded++;
sect_attrs = kzalloc_flex(*sect_attrs, attrs, nloaded);
if (!sect_attrs)
@@ -92,7 +101,7 @@ static int add_sect_attrs(struct module *mod, const struct load_info *info)
for (i = 0; i < info->hdr->e_shnum; i++) {
Elf_Shdr *sec = &info->sechdrs[i];
- if (sect_empty(sec))
+ if (!sect_visible(info, i))
continue;
sysfs_bin_attr_init(sattr);
sattr->attr.name =
@@ -159,7 +168,7 @@ static int add_notes_attrs(struct module *mod, const struct load_info *info)
/* Count notes sections and allocate structures. */
notes = 0;
for (i = 0; i < info->hdr->e_shnum; i++)
- if (!sect_empty(&info->sechdrs[i]) &&
+ if (sect_visible(info, i) &&
info->sechdrs[i].sh_type == SHT_NOTE)
++notes;
@@ -181,7 +190,7 @@ static int add_notes_attrs(struct module *mod, const struct load_info *info)
nattr = ¬es_attrs->attrs[0];
for (loaded = i = 0; i < info->hdr->e_shnum; ++i) {
- if (sect_empty(&info->sechdrs[i]))
+ if (!sect_visible(info, i))
continue;
if (info->sechdrs[i].sh_type == SHT_NOTE) {
sysfs_bin_attr_init(nattr);
--
2.25.1
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v11 5/7] module: allocate codetag sections before the regular module layout
2026-09-29 8:20 [PATCH v11 0/7] alloc_tag and module codetag section fixes Hao Ge
` (3 preceding siblings ...)
2026-09-29 8:20 ` [PATCH v11 4/7] module: introduce SH_ENTSIZE_STANDALONE for separately allocated sections Hao Ge
@ 2026-09-29 8:20 ` Hao Ge
2026-09-29 8:20 ` [PATCH v11 6/7] alloc_tag: skip percpu counter allocation when profiling is disabled Hao Ge
` (2 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Hao Ge @ 2026-09-29 8:20 UTC (permalink / raw)
To: Suren Baghdasaryan, =Kent Overstreet, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Andrew Morton, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Vlastimil Babka, Michal Hocko, Brendan Jackman, Johannes Weiner,
Zi Yan, Uladzislau Rezki
Cc: linux-mm, linux-kernel, linux-modules, kasan-dev, Hao Ge,
Sashiko, stable
Whether a codetag section goes to the codetag region is decided by
layout_sections() and asked again in move_module(). A concurrent
load can shut profiling down in between, and move_module() then
copies the section to offset 0 of its regular destination,
overwriting whatever is there.
Decide and allocate in one pass, before the layout. Allocation
errors fail the load. On a tag area overflow profiling is already
disabled, so -EAGAIN makes the section fall back to regular module
data and the module still loads. The reservation is released and
module_tags.size rolled back, so a concurrent load which already
passed needs_section_mem() does not skip vm_module_tags_populate().
An SHT_NOBITS codetag section is zeroed explicitly, the tag area
pages are not zeroed on allocation.
When profiling was toggled off the overflow check did not run, a
module could load with more tags than the page flags can address,
and re-enabling profiling then silently corrupted /proc/allocinfo.
The check no longer depends on mem_alloc_profiling_enabled().
Based on a patch by Petr Pavlu [1].
Fixes: 4835f747d3ed ("alloc_tag: support for page allocation tag compression")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Link: https://lore.kernel.org/all/499bb60c-c6e3-43a3-bd92-95a0567ece5e@suse.com/ [1]
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
Reviewed-by: Suren Baghdasaryan <surenb@google.com>
Cc: stable@vger.kernel.org
Signed-off-by: Hao Ge <hao.ge@linux.dev>
---
kernel/module/main.c | 101 +++++++++++++++++++++++--------------------
mm/alloc_tag.c | 9 ++--
2 files changed, 60 insertions(+), 50 deletions(-)
diff --git a/kernel/module/main.c b/kernel/module/main.c
index ae2678ac7840..b7ebcc40bdda 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -1723,20 +1723,6 @@ static void __layout_sections(struct module *mod, struct load_info *info, bool i
if (WARN_ON_ONCE(type == MOD_INVALID))
continue;
- /*
- * Do not allocate codetag memory as we load it into
- * preallocated contiguous memory.
- */
- if (codetag_needs_module_section(mod, sname, s->sh_size)) {
- /*
- * s->sh_entsize won't be used but populate the
- * type field to avoid confusion.
- */
- s->sh_entsize = ((unsigned long)(type) & SH_ENTSIZE_TYPE_MASK)
- << SH_ENTSIZE_TYPE_SHIFT;
- continue;
- }
-
s->sh_entsize = module_get_offset_and_type(mod, type, s, i);
pr_debug("\t%s\n", sname);
}
@@ -2795,7 +2781,6 @@ static int move_module(struct module *mod, struct load_info *info)
{
int i, ret;
enum mod_mem_type t = MOD_MEM_NUM_TYPES;
- bool codetag_section_found = false;
for_each_mod_mem_type(type) {
if (!mod->mem[type].size) {
@@ -2815,35 +2800,13 @@ static int move_module(struct module *mod, struct load_info *info)
for (i = 0; i < info->hdr->e_shnum; i++) {
void *dest;
Elf_Shdr *shdr = &info->sechdrs[i];
- const char *sname;
if (!(shdr->sh_flags & SHF_ALLOC)
|| shdr->sh_entsize == SH_ENTSIZE_STANDALONE)
continue;
- sname = info->secstrings + shdr->sh_name;
- /*
- * Load codetag sections separately as they might still be used
- * after module unload.
- */
- if (codetag_needs_module_section(mod, sname, shdr->sh_size)) {
- dest = codetag_alloc_module_section(mod, sname, shdr->sh_size,
- arch_mod_section_prepend(mod, i), shdr->sh_addralign);
- if (WARN_ON(!dest)) {
- ret = -EINVAL;
- goto out_err;
- }
- if (IS_ERR(dest)) {
- ret = PTR_ERR(dest);
- goto out_err;
- }
- codetag_section_found = true;
- } else {
- enum mod_mem_type type = shdr->sh_entsize >> SH_ENTSIZE_TYPE_SHIFT;
- unsigned long offset = shdr->sh_entsize & SH_ENTSIZE_OFFSET_MASK;
-
- dest = mod->mem[type].base + offset;
- }
+ dest = mod->mem[shdr->sh_entsize >> SH_ENTSIZE_TYPE_SHIFT].base +
+ (shdr->sh_entsize & SH_ENTSIZE_OFFSET_MASK);
if (shdr->sh_type != SHT_NOBITS) {
/*
@@ -2875,8 +2838,6 @@ static int move_module(struct module *mod, struct load_info *info)
module_memory_restore_rox(mod);
while (t--)
module_memory_free(mod, t);
- if (codetag_section_found)
- codetag_free_module_sections(mod);
return ret;
}
@@ -2947,6 +2908,49 @@ static bool blacklisted(const char *module_name)
}
core_param(module_blacklist, module_blacklist, charp, 0400);
+/*
+ * Allocate codetag sections separately. They are loaded into preallocated
+ * contiguous memory because they may still be used after the module is
+ * unloaded.
+ *
+ * If the separate allocation overflows, allocate the section normally
+ * so that the module can still be loaded.
+ */
+static int allocate_codetag_sections(struct load_info *info)
+{
+ for (unsigned int i = 1; i < info->hdr->e_shnum; i++) {
+ Elf_Shdr *shdr = &info->sechdrs[i];
+ const char *sname = info->secstrings + shdr->sh_name;
+ void *dest;
+
+ if (!codetag_needs_module_section(info->mod, sname, shdr->sh_size))
+ continue;
+
+ dest = codetag_alloc_module_section(info->mod, sname, shdr->sh_size,
+ arch_mod_section_prepend(info->mod, i), shdr->sh_addralign);
+ if (WARN_ON(!dest)) {
+ codetag_free_module_sections(info->mod);
+ return -EINVAL;
+ }
+ if (dest == ERR_PTR(-EAGAIN))
+ /* Allocate the section as a regular section. */
+ continue;
+ if (IS_ERR(dest)) {
+ codetag_free_module_sections(info->mod);
+ return PTR_ERR(dest);
+ }
+
+ if (shdr->sh_type != SHT_NOBITS)
+ memcpy(dest, (void *)shdr->sh_addr, shdr->sh_size);
+ else
+ memset(dest, 0, shdr->sh_size);
+ shdr->sh_addr = (unsigned long)dest;
+ shdr->sh_entsize = SH_ENTSIZE_STANDALONE;
+ }
+
+ return 0;
+}
+
static struct module *layout_and_allocate(struct load_info *info, int flags)
{
struct module *mod;
@@ -2979,18 +2983,21 @@ static struct module *layout_and_allocate(struct load_info *info, int flags)
*/
module_mark_ro_after_init(info->hdr, info->sechdrs, info->secstrings);
- /*
- * 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.
- */
+ /* Allow codetag sections to be allocated separately first. */
+ err = allocate_codetag_sections(info);
+ if (err)
+ return ERR_PTR(err);
+
+ /* Determine total sizes and put offsets in sh_entsize. */
layout_sections(info->mod, info);
layout_symtab(info->mod, info);
/* Allocate and move to the final place */
err = move_module(info->mod, info);
- if (err)
+ if (err) {
+ codetag_free_module_sections(info->mod);
return ERR_PTR(err);
+ }
/* 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
index 3e6306ee0764..605c0016f646 100644
--- a/mm/alloc_tag.c
+++ b/mm/alloc_tag.c
@@ -951,10 +951,13 @@ static void *reserve_module_tags(struct module *mod, unsigned long size,
int grow_res;
module_tags.size = offset + size;
- 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);
+ module_tags.size = prev_size;
+ return ERR_PTR(-EAGAIN);
}
grow_res = vm_module_tags_populate();
--
2.25.1
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v11 6/7] alloc_tag: skip percpu counter allocation when profiling is disabled
2026-09-29 8:20 [PATCH v11 0/7] alloc_tag and module codetag section fixes Hao Ge
` (4 preceding siblings ...)
2026-09-29 8:20 ` [PATCH v11 5/7] module: allocate codetag sections before the regular module layout Hao Ge
@ 2026-09-29 8:20 ` Hao Ge
2026-09-29 8:20 ` [PATCH v11 7/7] alloc_tag: fix the /proc/allocinfo lifecycle Hao Ge
2026-09-29 20:44 ` [PATCH v11 0/7] alloc_tag and module codetag section fixes Andrew Morton
7 siblings, 0 replies; 11+ messages in thread
From: Hao Ge @ 2026-09-29 8:20 UTC (permalink / raw)
To: Suren Baghdasaryan, =Kent Overstreet, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Andrew Morton, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Vlastimil Babka, Michal Hocko, Brendan Jackman, Johannes Weiner,
Zi Yan, Uladzislau Rezki
Cc: linux-mm, linux-kernel, linux-modules, kasan-dev, Hao Ge,
Sashiko, stable
After shutdown_mem_profiling() clears mem_profiling_support,
needs_section_mem() returns false, so later modules have their codetag
section placed as regular data and never enter the alloc_tag maple tree.
codetag_load_module() still called load_module(), which allocated a percpu
counter for every tag; release_module_tags() could not find these modules
on unload, so the counters leaked.
Return -EOPNOTSUPP from load_module() when profiling is off:
codetag_module_init() drops the module's cmod, no counters are allocated
and the module loads without its tags. codetag_unload_module() now always
calls free_section_mem(), since a module whose module_load() returned
-EOPNOTSUPP is not in the idr but may still hold a reserved section.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Fixes: 4835f747d3ed ("alloc_tag: support for page allocation tag compression")
Cc: stable@vger.kernel.org
Suggested-by: Suren Baghdasaryan <surenb@google.com>
Acked-by: Suren Baghdasaryan <surenb@google.com>
Signed-off-by: Hao Ge <hao.ge@linux.dev>
---
lib/codetag.c | 10 ++++++++--
mm/alloc_tag.c | 4 ++++
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/lib/codetag.c b/lib/codetag.c
index a9cda4c962a3..a0b600720afc 100644
--- a/lib/codetag.c
+++ b/lib/codetag.c
@@ -240,7 +240,9 @@ static int codetag_module_init(struct codetag_type *cttype, struct module *mod)
if (err < 0) {
kfree(cmod);
- return err;
+ /* -EOPNOTSUPP means we can load the module without its tag. */
+ if (err != -EOPNOTSUPP)
+ return err;
}
return 0;
@@ -388,7 +390,11 @@ void codetag_unload_module(struct module *mod)
++cttype->content_id;
}
up_write(&cttype->mod_lock);
- if (found && cttype->desc.free_section_mem)
+ /*
+ * A module whose module_load() returned -EOPNOTSUPP is not
+ * in the idr but may still hold reserved section memory.
+ */
+ if (cttype->desc.free_section_mem)
cttype->desc.free_section_mem(mod, true);
}
mutex_unlock(&codetag_lock);
diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c
index 605c0016f646..ba8a651769e3 100644
--- a/mm/alloc_tag.c
+++ b/mm/alloc_tag.c
@@ -981,6 +981,10 @@ static int load_module(struct module *mod, struct codetag *start, struct codetag
struct alloc_tag *stop_tag;
struct alloc_tag *tag;
+ /* Profiling disabled: load the module without its tags. */
+ if (!mem_profiling_support)
+ return -EOPNOTSUPP;
+
/* percpu counters for core allocations are already statically allocated */
if (!mod)
return 0;
--
2.25.1
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v11 7/7] alloc_tag: fix the /proc/allocinfo lifecycle
2026-09-29 8:20 [PATCH v11 0/7] alloc_tag and module codetag section fixes Hao Ge
` (5 preceding siblings ...)
2026-09-29 8:20 ` [PATCH v11 6/7] alloc_tag: skip percpu counter allocation when profiling is disabled Hao Ge
@ 2026-09-29 8:20 ` Hao Ge
2026-09-29 20:44 ` [PATCH v11 0/7] alloc_tag and module codetag section fixes Andrew Morton
7 siblings, 0 replies; 11+ messages in thread
From: Hao Ge @ 2026-09-29 8:20 UTC (permalink / raw)
To: Suren Baghdasaryan, =Kent Overstreet, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Andrew Morton, Alexander Potapenko, Marco Elver, Dmitry Vyukov,
Vlastimil Babka, Michal Hocko, Brendan Jackman, Johannes Weiner,
Zi Yan, Uladzislau Rezki
Cc: linux-mm, linux-kernel, linux-modules, kasan-dev, Hao Ge,
Sashiko, stable
shutdown_mem_profiling() calls remove_proc_entry() from
reserve_module_tags(), which runs under mod_lock held for write.
remove_proc_entry() waits for readers, and a reader takes mod_lock for
read in allocinfo_start():
CPU0 (insmod) CPU1 (read /proc/allocinfo)
---------------- ----------------------------
reserve_module_tags()
down_write(&mod_lock) [held]
use_pde() [in_use++]
allocinfo_start()
down_read(&mod_lock) <- blocks
shutdown_mem_profiling()
remove_proc_entry()
wait for in_use == 0 <- blocks
Move remove_proc_entry() to a workqueue.
The deferred removal also affects alloc_tag_init(). The file is
created before the type, so on a failure it is still there with
alloc_tag_cttype NULL or an error pointer, and a reader panics in
allocinfo_start(). Create the file at the end of alloc_tag_init()
instead, a failed init leaves nothing behind.
If proc_create() fails, the codetag type and the module tags memory
leak. Call codetag_unregister_type() and free the memory.
alloc_tag_cttype can now be freed at runtime. alloc_tag_top_users()
reads it from __show_mem() without locks, read the pointer under
rcu_read_lock() and take mod_lock before dropping the RCU lock, the
type stays alive until then. The only caller never sleeps, drop the
can_sleep argument.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Fixes: 4835f747d3ed ("alloc_tag: support for page allocation tag compression")
Cc: stable@vger.kernel.org
Signed-off-by: Hao Ge <hao.ge@linux.dev>
---
include/linux/alloc_tag.h | 2 +-
include/linux/codetag.h | 2 ++
lib/codetag.c | 26 ++++++++++++++++++++
mm/alloc_tag.c | 52 +++++++++++++++++++++++++++------------
mm/show_mem.c | 2 +-
5 files changed, 66 insertions(+), 18 deletions(-)
diff --git a/include/linux/alloc_tag.h b/include/linux/alloc_tag.h
index 7f2d80a59792..852dc10c00ee 100644
--- a/include/linux/alloc_tag.h
+++ b/include/linux/alloc_tag.h
@@ -81,7 +81,7 @@ struct codetag_bytes {
s64 bytes;
};
-size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep);
+size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count);
static inline struct alloc_tag *ct_to_alloc_tag(struct codetag *ct)
{
diff --git a/include/linux/codetag.h b/include/linux/codetag.h
index a25a085c2df1..0c4e0337b474 100644
--- a/include/linux/codetag.h
+++ b/include/linux/codetag.h
@@ -87,6 +87,8 @@ void codetag_to_text(struct seq_buf *out, struct codetag *ct);
struct codetag_type *
codetag_register_type(const struct codetag_type_desc *desc);
+void codetag_unregister_type(struct codetag_type *cttype);
+
#if defined(CONFIG_CODE_TAGGING) && defined(CONFIG_MODULES)
bool codetag_needs_module_section(struct module *mod, const char *name,
diff --git a/lib/codetag.c b/lib/codetag.c
index a0b600720afc..46d0904b08b3 100644
--- a/lib/codetag.c
+++ b/lib/codetag.c
@@ -429,3 +429,29 @@ codetag_register_type(const struct codetag_type_desc *desc)
return cttype;
}
+
+/**
+ * codetag_unregister_type - unregister a codetag type
+ * @cttype: the codetag type to unregister
+ *
+ * Undo codetag_register_type() and free @cttype. The caller must make
+ * sure no lockless reader still uses @cttype, e.g. clear the pointer
+ * to it and wait for an RCU grace period first.
+ */
+void __init codetag_unregister_type(struct codetag_type *cttype)
+{
+ struct codetag_module *cmod;
+ unsigned long id, tmp;
+
+ mutex_lock(&codetag_lock);
+ list_del(&cttype->link);
+ mutex_unlock(&codetag_lock);
+
+ down_write(&cttype->mod_lock);
+ idr_for_each_entry_ul(&cttype->mod_idr, cmod, tmp, id)
+ kfree(cmod);
+ idr_destroy(&cttype->mod_idr);
+ up_write(&cttype->mod_lock);
+
+ kfree(cttype);
+}
diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c
index ba8a651769e3..b9af5fe5bba2 100644
--- a/mm/alloc_tag.c
+++ b/mm/alloc_tag.c
@@ -15,6 +15,7 @@
#include <linux/seq_file.h>
#include <linux/string_choices.h>
#include <linux/vmalloc.h>
+#include <linux/workqueue.h>
#include <linux/kmemleak.h>
#include <uapi/linux/alloc_tag.h>
@@ -484,22 +485,28 @@ static const struct proc_ops allocinfo_proc_ops = {
#endif
};
-size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep)
+size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count)
{
struct codetag_iterator iter;
+ struct codetag_type *cttype;
struct codetag *ct;
struct codetag_bytes n;
unsigned int i, nr = 0;
+ bool locked;
- if (IS_ERR_OR_NULL(alloc_tag_cttype))
+ rcu_read_lock();
+ cttype = READ_ONCE(alloc_tag_cttype);
+ if (IS_ERR_OR_NULL(cttype)) {
+ rcu_read_unlock();
return 0;
+ }
- if (can_sleep)
- codetag_lock_module_list(alloc_tag_cttype);
- else if (!codetag_trylock_module_list(alloc_tag_cttype))
+ locked = codetag_trylock_module_list(cttype);
+ rcu_read_unlock();
+ if (!locked)
return 0;
- iter = codetag_get_ct_iter(alloc_tag_cttype);
+ iter = codetag_get_ct_iter(cttype);
while ((ct = codetag_next_ct(&iter))) {
struct alloc_tag_counters counter = alloc_tag_read(ct_to_alloc_tag(ct));
@@ -520,7 +527,7 @@ size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sl
}
}
- codetag_unlock_module_list(alloc_tag_cttype);
+ codetag_unlock_module_list(cttype);
return nr;
}
@@ -591,6 +598,13 @@ void pgalloc_tag_swap(struct folio *new, struct folio *old)
put_page_tag_ref(handle_new);
}
+static void remove_allocinfo_file(struct work_struct *work)
+{
+ remove_proc_entry(ALLOCINFO_FILE_NAME, NULL);
+}
+
+static DECLARE_WORK(remove_allocinfo_work, remove_allocinfo_file);
+
static void shutdown_mem_profiling(bool remove_file)
{
if (mem_alloc_profiling_enabled())
@@ -600,7 +614,7 @@ static void shutdown_mem_profiling(bool remove_file)
return;
if (remove_file)
- remove_proc_entry(ALLOCINFO_FILE_NAME, NULL);
+ schedule_work(&remove_allocinfo_work);
mem_profiling_support = false;
}
@@ -1351,16 +1365,10 @@ static int __init alloc_tag_init(void)
return 0;
}
- if (!proc_create(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_proc_ops)) {
- pr_err("Failed to create %s file\n", ALLOCINFO_FILE_NAME);
- shutdown_mem_profiling(false);
- return -ENOMEM;
- }
-
res = alloc_mod_tags_mem();
if (res) {
pr_err("Failed to reserve address space for module tags, errno = %d\n", res);
- shutdown_mem_profiling(true);
+ shutdown_mem_profiling(false);
return res;
}
@@ -1368,10 +1376,22 @@ static int __init alloc_tag_init(void)
if (IS_ERR(alloc_tag_cttype)) {
pr_err("Allocation tags registration failed, errno = %pe\n", alloc_tag_cttype);
free_mod_tags_mem();
- shutdown_mem_profiling(true);
+ shutdown_mem_profiling(false);
return PTR_ERR(alloc_tag_cttype);
}
+ if (!proc_create(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_proc_ops)) {
+ struct codetag_type *cttype = alloc_tag_cttype;
+
+ pr_err("Failed to create %s file\n", ALLOCINFO_FILE_NAME);
+ shutdown_mem_profiling(false);
+ WRITE_ONCE(alloc_tag_cttype, NULL);
+ synchronize_rcu();
+ codetag_unregister_type(cttype);
+ free_mod_tags_mem();
+ return -ENOMEM;
+ }
+
return 0;
}
module_init(alloc_tag_init);
diff --git a/mm/show_mem.c b/mm/show_mem.c
index b938cbcd774a..a2e710404a48 100644
--- a/mm/show_mem.c
+++ b/mm/show_mem.c
@@ -439,7 +439,7 @@ void __show_mem(unsigned int filter, const nodemask_t *nodemask,
struct codetag_bytes tags[10];
size_t i, nr;
- nr = alloc_tag_top_users(tags, ARRAY_SIZE(tags), false);
+ nr = alloc_tag_top_users(tags, ARRAY_SIZE(tags));
if (nr) {
pr_notice("Memory allocations (profiling is currently turned %s):\n",
mem_alloc_profiling_enabled() ? "on" : "off");
--
2.25.1
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH v11 0/7] alloc_tag and module codetag section fixes
2026-09-29 8:20 [PATCH v11 0/7] alloc_tag and module codetag section fixes Hao Ge
` (6 preceding siblings ...)
2026-09-29 8:20 ` [PATCH v11 7/7] alloc_tag: fix the /proc/allocinfo lifecycle Hao Ge
@ 2026-09-29 20:44 ` Andrew Morton
2026-09-30 2:39 ` Suren Baghdasaryan
7 siblings, 1 reply; 11+ messages in thread
From: Andrew Morton @ 2026-09-29 20:44 UTC (permalink / raw)
To: Hao Ge
Cc: Suren Baghdasaryan, =Kent Overstreet, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Alexander Potapenko, Marco Elver, Dmitry Vyukov, Vlastimil Babka,
Michal Hocko, Brendan Jackman, Johannes Weiner, Zi Yan,
Uladzislau Rezki, linux-mm, linux-kernel, linux-modules,
kasan-dev
On Tue, 29 Sep 2026 16:20:07 +0800 Hao Ge <hao.ge@linux.dev> wrote:
> With profiling toggled off, the overflow check in
> reserve_module_tags() did not run, a module could load with more
> tags than the page flags can address, and re-enabling profiling
> then silently corrupted /proc/allocinfo. On overflow the fix shuts
> profiling down, releases the reservation and returns -EAGAIN, and
> the codetag section lands as regular module data in the same load,
> so the module loads without profiling.
>
> Review of the earlier series by Sashiko turned up more problems.
>
> One is a race. layout_sections() and move_module() both asked
> codetag_needs_module_section() where a codetag section goes, and
> mem_profiling_support can change between the two calls, for instance
> when another module load overflows the tag index and shuts profiling
> down. move_module() then copied the codetag section to offset 0 of
> its regular destination and clobbered the first section placed in
> that region.
>
> v7 reworks where codetag sections are allocated, on a prototype by
> Petr Pavlu [1]. The allocation runs before layout_sections() and the
> placement is decided in one step, so nothing re-asks the question
> and the race is gone. The retry is gone too, on -EAGAIN the section
> is laid out as regular module data right in the same load.
Thanks.
Seven patches, all cc:stable. Why is a backport proposed?
Documentation/process/stable-kernel-rules.rst gives guidelines - does
this series meet them? Does Suren have thoughts?
Also, Sashiko has been busy again:
https://sashiko.dev/#/patchset/20260929082014.160587-1-hao.ge@linux.dev
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH v11 0/7] alloc_tag and module codetag section fixes
2026-09-29 20:44 ` [PATCH v11 0/7] alloc_tag and module codetag section fixes Andrew Morton
@ 2026-09-30 2:39 ` Suren Baghdasaryan
2026-09-30 3:26 ` Hao Ge
0 siblings, 1 reply; 11+ messages in thread
From: Suren Baghdasaryan @ 2026-09-30 2:39 UTC (permalink / raw)
To: Andrew Morton
Cc: Hao Ge, =Kent Overstreet, Luis Chamberlain, Petr Pavlu,
Daniel Gomez, Sami Tolvanen, Aaron Tomlin, Alexander Potapenko,
Marco Elver, Dmitry Vyukov, Vlastimil Babka, Michal Hocko,
Brendan Jackman, Johannes Weiner, Zi Yan, Uladzislau Rezki,
linux-mm, linux-kernel, linux-modules, kasan-dev
On Tue, Sep 29, 2026 at 8:44 PM Andrew Morton <akpm@linux-foundation.org> wrote:
>
> On Tue, 29 Sep 2026 16:20:07 +0800 Hao Ge <hao.ge@linux.dev> wrote:
>
> > With profiling toggled off, the overflow check in
> > reserve_module_tags() did not run, a module could load with more
> > tags than the page flags can address, and re-enabling profiling
> > then silently corrupted /proc/allocinfo. On overflow the fix shuts
> > profiling down, releases the reservation and returns -EAGAIN, and
> > the codetag section lands as regular module data in the same load,
> > so the module loads without profiling.
> >
> > Review of the earlier series by Sashiko turned up more problems.
> >
> > One is a race. layout_sections() and move_module() both asked
> > codetag_needs_module_section() where a codetag section goes, and
> > mem_profiling_support can change between the two calls, for instance
> > when another module load overflows the tag index and shuts profiling
> > down. move_module() then copied the codetag section to offset 0 of
> > its regular destination and clobbered the first section placed in
> > that region.
> >
> > v7 reworks where codetag sections are allocated, on a prototype by
> > Petr Pavlu [1]. The allocation runs before layout_sections() and the
> > placement is decided in one step, so nothing re-asks the question
> > and the race is gone. The retry is gone too, on -EAGAIN the section
> > is laid out as regular module data right in the same load.
>
> Thanks.
>
> Seven patches, all cc:stable. Why is a backport proposed?
> Documentation/process/stable-kernel-rules.rst gives guidelines - does
> this series meet them? Does Suren have thoughts?
I think vmalloc fix in patch#2 should be posted separately and clearly
quialifies for backporting as if fixes issues Hao found at [1].
The rest are also bug fixes but their size is concerning. I'll review
them first before advising on which ones should be backported. Some
fixes address issues that happen very rarely and if a fix is too
complex it might make sense to leave it be or fix it some other way
with less churn. Please give me a couple of days to review them. With
the upcoming LPC I'm trying to multiplex between several projects.
[1] https://lore.kernel.org/all/b8005c24-ecac-45c4-840a-fd282f9693b5@linux.dev/
>
> Also, Sashiko has been busy again:
> https://sashiko.dev/#/patchset/20260929082014.160587-1-hao.ge@linux.dev
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v11 0/7] alloc_tag and module codetag section fixes
2026-09-30 2:39 ` Suren Baghdasaryan
@ 2026-09-30 3:26 ` Hao Ge
0 siblings, 0 replies; 11+ messages in thread
From: Hao Ge @ 2026-09-30 3:26 UTC (permalink / raw)
To: Suren Baghdasaryan, Andrew Morton
Cc: Kent Overstreet, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen, Aaron Tomlin, Alexander Potapenko, Marco Elver,
Dmitry Vyukov, Vlastimil Babka, Michal Hocko, Brendan Jackman,
Johannes Weiner, Zi Yan, Uladzislau Rezki, linux-mm,
linux-kernel, linux-modules, kasan-dev
Hi Suren and Andrew
On 2026/9/30 10:39, Suren Baghdasaryan wrote:
> On Tue, Sep 29, 2026 at 8:44 PM Andrew Morton <akpm@linux-foundation.org> wrote:
>>
>> On Tue, 29 Sep 2026 16:20:07 +0800 Hao Ge <hao.ge@linux.dev> wrote:
>>
>>> With profiling toggled off, the overflow check in
>>> reserve_module_tags() did not run, a module could load with more
>>> tags than the page flags can address, and re-enabling profiling
>>> then silently corrupted /proc/allocinfo. On overflow the fix shuts
>>> profiling down, releases the reservation and returns -EAGAIN, and
>>> the codetag section lands as regular module data in the same load,
>>> so the module loads without profiling.
>>>
>>> Review of the earlier series by Sashiko turned up more problems.
>>>
>>> One is a race. layout_sections() and move_module() both asked
>>> codetag_needs_module_section() where a codetag section goes, and
>>> mem_profiling_support can change between the two calls, for instance
>>> when another module load overflows the tag index and shuts profiling
>>> down. move_module() then copied the codetag section to offset 0 of
>>> its regular destination and clobbered the first section placed in
>>> that region.
>>>
>>> v7 reworks where codetag sections are allocated, on a prototype by
>>> Petr Pavlu [1]. The allocation runs before layout_sections() and the
>>> placement is decided in one step, so nothing re-asks the question
>>> and the race is gone. The retry is gone too, on -EAGAIN the section
>>> is laid out as regular module data right in the same load.
>>
>> Thanks.
>>
>> Seven patches, all cc:stable. Why is a backport proposed?
>> Documentation/process/stable-kernel-rules.rst gives guidelines - does
>> this series meet them? Does Suren have thoughts?
Thanks, I've just gone through this carefully.
>
> I think vmalloc fix in patch#2 should be posted separately and clearly
> quialifies for backporting as if fixes issues Hao found at [1].
Agreed. Patch #2 was added to this patch series because vm_module_tags_populate
suffers from the same issue. If we want to be more conservative, I'd prefer to keep
vm_module_tags_populate as it was in V10:
https://lore.kernel.org/all/20260915070001.113559-3-hao.ge@linux.dev/
Then we can submit a fix for __GFP_NOFAIL retry loop in __vmalloc_area_node() as a hotfix together
with our new patch #2. While at it, we can also check if there are any unnecessary calls to
vunmap_range family functions that can be cleaned up now that patch #2 is applied.
>
> The rest are also bug fixes but their size is concerning. I'll review
> them first before advising on which ones should be backported. Some
> fixes address issues that happen very rarely and if a fix is too
> complex it might make sense to leave it be or fix it some other way
> with less churn. Please give me a couple of days to review them. With
> the upcoming LPC I'm trying to multiplex between several projects.
Thanks, sorry for all the hassle on you folks.
Thanks
Best Regards
Hao
>
> [1] https://lore.kernel.org/all/b8005c24-ecac-45c4-840a-fd282f9693b5@linux.dev/
>
>>
>> Also, Sashiko has been busy again:
>> https://sashiko.dev/#/patchset/20260929082014.160587-1-hao.ge@linux.dev
^ permalink raw reply [flat|nested] 11+ messages in thread