Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] mm: snapshot alloc tags in alloc_tag_top_users() to avoid UAF
@ 2026-11-14 16:47 shao.mingyin
  2026-11-14 17:31 ` Lorenzo Stoakes (ARM)
  0 siblings, 1 reply; 2+ messages in thread
From: shao.mingyin @ 2026-11-14 16:47 UTC (permalink / raw)
  To: surenb, akpm; +Cc: linux-mm, hao.ge, linux-kernel

From: Shao Mingyin <shao.mingyin@zte.com.cn>

mm: snapshot alloc tags in alloc_tag_top_users() to avoid UAF

alloc_tag_top_users() selects tags under the codetag module-list lock,
then returns raw codetag pointers. __show_mem() dereferences the tags,
their strings and per-CPU counters after that lock has been released.
Concurrent module unloading can invalidate those objects before they
are printed in the OOM or SysRq-m report.

Copy the selected tags and counters while holding the module-list lock,
and print only the copies. Reuse allocinfo_to_params(), which copies
names into fixed-size buffers and retains at most their last 63
characters. Preserve the signed comparison used to select the top tags.

Keep the larger output array static under the existing show_mem lock
to avoid increasing the stack frame. The counters remain approximate
lockless per-CPU observations; the copy protects object lifetime.

Fixes: 1438d349d16b ("lib: add memory allocations report in show_mem()")
Signed-off-by: Shao Mingyin <shao.mingyin@zte.com.cn>
---
Build-tested mm/alloc_tag.o and mm/show_mem.o with W=1 and
CONFIG_MEM_ALLOC_PROFILING=y on x86_64. The lock lifetime was reviewed
statically. No module-unload race was reproduced under KASAN.


 include/linux/alloc_tag.h |  9 +++------
 mm/alloc_tag.c            | 17 ++++++++++-------
 mm/show_mem.c             | 29 +++++++++++++++++------------
 3 files changed, 30 insertions(+), 25 deletions(-)

diff --git a/include/linux/alloc_tag.h b/include/linux/alloc_tag.h
index 9e42f23577b9638a8cad3ce42db7c7a2fb1a3d5a..018dbfc9db12e178c50ed0a67063db4e024bfccc 100644
--- a/include/linux/alloc_tag.h
+++ b/include/linux/alloc_tag.h
@@ -7,6 +7,7 @@
 
 #include <linux/bug.h>
 #include <linux/codetag.h>
+#include <uapi/linux/alloc_tag.h>
 #include <linux/container_of.h>
 #include <linux/preempt.h>
 #include <asm/percpu.h>
@@ -76,12 +77,8 @@ static inline void set_codetag_empty(union codetag_ref *ref)
 
 #define ALLOC_TAG_SECTION_NAME	"alloc_tags"
 
-struct codetag_bytes {
-	struct codetag *ct;
-	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 allocinfo_tag_data *tags, size_t count,
+			   bool can_sleep);
 
 static inline struct alloc_tag *ct_to_alloc_tag(struct codetag *ct)
 {
diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c
index e9b70268eacb046f429107dc493cb5ade3620690..fe8269694c7dbc68a0c7188dcf87b3bbefbfb751 100644
--- a/mm/alloc_tag.c
+++ b/mm/alloc_tag.c
@@ -610,11 +610,11 @@ void alloc_tag_trace_mem_free(const void *ptr, struct alloc_tag *tag,
 }
 EXPORT_SYMBOL(alloc_tag_trace_mem_free);
 
-size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep)
+size_t alloc_tag_top_users(struct allocinfo_tag_data *tags, size_t count,
+			   bool can_sleep)
 {
 	struct codetag_iterator iter;
 	struct codetag *ct;
-	struct codetag_bytes n;
 	unsigned int i, nr = 0;
 
 	if (IS_ERR_OR_NULL(alloc_tag_cttype))
@@ -629,11 +629,8 @@ size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sl
 	while ((ct = codetag_next_ct(&iter))) {
 		struct alloc_tag_counters counter = alloc_tag_read(ct_to_alloc_tag(ct));
 
-		n.ct	= ct;
-		n.bytes = counter.bytes;
-
 		for (i = 0; i < nr; i++)
-			if (n.bytes > tags[i].bytes)
+			if ((s64)counter.bytes > (s64)tags[i].counter.bytes)
 				break;
 
 		if (i < count) {
@@ -642,7 +639,13 @@ size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sl
 				&tags[i],
 				sizeof(tags[0]) * (nr - i));
 			nr++;
-			tags[i] = n;
+			/*
+			 * Snapshot the strings and counters while still
+			 * holding the module list lock, so the caller does
+			 * not dereference a module codetag that may have
+			 * been unloaded by then.
+			 */
+			allocinfo_to_params(ct, &tags[i], &counter);
 		}
 	}
 
diff --git a/mm/show_mem.c b/mm/show_mem.c
index b938cbcd774a1e2ec732f8c3703cae0dbe57d488..8d99a809f27fa9ca40a5184770ad0ec5b43f2aa1 100644
--- a/mm/show_mem.c
+++ b/mm/show_mem.c
@@ -436,7 +436,11 @@ void __show_mem(unsigned int filter, const nodemask_t *nodemask,
 	static DEFINE_SPINLOCK(mem_alloc_profiling_spinlock);
 
 	if (spin_trylock(&mem_alloc_profiling_spinlock)) {
-		struct codetag_bytes tags[10];
+		/*
+		 * Protected by mem_alloc_profiling_spinlock; kept static to
+		 * avoid a large stack frame in this potentially atomic path.
+		 */
+		static struct allocinfo_tag_data tags[10];
 		size_t i, nr;
 
 		nr = alloc_tag_top_users(tags, ARRAY_SIZE(tags), false);
@@ -444,22 +448,23 @@ void __show_mem(unsigned int filter, const nodemask_t *nodemask,
 			pr_notice("Memory allocations (profiling is currently turned %s):\n",
 				mem_alloc_profiling_enabled() ? "on" : "off");
 			for (i = 0; i < nr; i++) {
-				struct codetag *ct = tags[i].ct;
-				struct alloc_tag *tag = ct_to_alloc_tag(ct);
-				struct alloc_tag_counters counter = alloc_tag_read(tag);
+				struct allocinfo_tag *tag = &tags[i].tag;
+				struct allocinfo_counter *counter = &tags[i].counter;
 				char bytes[10];
 
-				string_get_size(counter.bytes, 1, STRING_UNITS_2, bytes, sizeof(bytes));
+				string_get_size(counter->bytes, 1,
+						STRING_UNITS_2, bytes,
+						sizeof(bytes));
 
 				/* Same as alloc_tag_to_text() but w/o intermediate buffer */
-				if (ct->modname)
-					pr_notice("%12s %8llu %s:%u [%s] func:%s\n",
-						  bytes, counter.calls, ct->filename,
-						  ct->lineno, ct->modname, ct->function);
+				if (tag->modname[0])
+					pr_notice("%12s %8llu %s:%llu [%s] func:%s\n",
+						  bytes, counter->calls, tag->filename,
+						  tag->lineno, tag->modname, tag->function);
 				else
-					pr_notice("%12s %8llu %s:%u func:%s\n",
-						  bytes, counter.calls, ct->filename,
-						  ct->lineno, ct->function);
+					pr_notice("%12s %8llu %s:%llu func:%s\n",
+						  bytes, counter->calls, tag->filename,
+						  tag->lineno, tag->function);
 			}
 		}
 		spin_unlock(&mem_alloc_profiling_spinlock);

base-commit: 84abb955b1015477aa11a96e2f3ac1b938b77f48
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-11-14 17:31 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-14 16:47 [PATCH] mm: snapshot alloc tags in alloc_tag_top_users() to avoid UAF shao.mingyin
2026-11-14 17:31 ` Lorenzo Stoakes (ARM)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox