Linux Documentation
 help / color / mirror / Atom feed
* [RFC PATCH 0/5] mm/slub: preserve previous object lifetime
@ 2026-06-11  6:39 Pengpeng Hou
  2026-06-11  6:39 ` [RFC PATCH 1/5] mm/slub: factor user tracking metadata size calculation Pengpeng Hou
                   ` (6 more replies)
  0 siblings, 7 replies; 9+ messages in thread
From: Pengpeng Hou @ 2026-06-11  6:39 UTC (permalink / raw)
  To: Vlastimil Babka, Harry Yoo, Andrew Morton, linux-mm
  Cc: Hao Li, Christoph Lameter, David Rientjes, Roman Gushchin,
	David Hildenbrand, Lorenzo Stoakes, liam, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
	linux-doc, linux-kernel, Pengpeng Hou

SLAB_STORE_USER currently stores one allocation track and one free track
for an object. This is useful, but it loses part of the previous lifetime
when the object is reused: the new allocation overwrites the allocation
track, and a later stale free can overwrite the free track.

For free-after-reuse bugs, the report can therefore contain the victim
allocation and the stale free, while the earlier alloc/free pair that
explains where the stale pointer came from is no longer available.

This RFC adds an opt-in SLUB debug option to keep one previous completed
object lifetime. The option is disabled by default, is not part of the
default debug flags, and only takes effect when user tracking is already
enabled:

  slab_debug=UH,kmalloc-128

The series intentionally does not attempt to infer semantic ownership or
identify the root cause of a use-after-free. It only preserves and prints
additional track records that SLUB already knows how to collect.

This is sent as RFC because the user-visible interface and the cost/benefit
tradeoff should be agreed on before this becomes a normal patch series.
In particular, feedback would be useful on:

- whether a separate H option is preferable to extending U directly
- whether H should require U, as implemented here, or imply U
- whether the extra per-object metadata is useful enough for this debug path

Not included yet:

- KUnit coverage or a standalone reproducer
- object-size/order comparison data for representative caches
- runtime benchmark data for slab_debug=U vs slab_debug=UH

Those should be added before a non-RFC submission if the direction looks
acceptable.

Pengpeng Hou (5):
  mm/slub: factor user tracking metadata size calculation
  mm/slub: add optional previous lifetime user tracking
  mm/slub: print previous object lifetime in debug reports
  Documentation/mm: document SLUB previous lifetime tracking
  mm/slub: sanitize previous lifetime tracking flags

 Documentation/admin-guide/mm/slab.rst |  22 ++++-
 include/linux/slab.h                  |   3 +
 mm/slab.h                             |   3 +-
 mm/slub.c                             | 118 ++++++++++++++++++++++----
 4 files changed, 128 insertions(+), 18 deletions(-)

-- 
2.50.1 (Apple Git-155)


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

* [RFC PATCH 1/5] mm/slub: factor user tracking metadata size calculation
  2026-06-11  6:39 [RFC PATCH 0/5] mm/slub: preserve previous object lifetime Pengpeng Hou
@ 2026-06-11  6:39 ` Pengpeng Hou
  2026-06-11  6:39 ` [RFC PATCH 2/5] mm/slub: add optional previous lifetime user tracking Pengpeng Hou
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Pengpeng Hou @ 2026-06-11  6:39 UTC (permalink / raw)
  To: Vlastimil Babka, Harry Yoo, Andrew Morton, linux-mm
  Cc: Hao Li, Christoph Lameter, David Rientjes, Roman Gushchin,
	David Hildenbrand, Lorenzo Stoakes, liam, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
	linux-doc, linux-kernel, Pengpeng Hou

SLAB_STORE_USER stores one allocation track and one free track after the
object. Several offset and size calculations open-code that as
2 * sizeof(struct track).

Introduce helpers for the number and size of user tracking records, and
use them for the existing metadata layout calculations. This is a
preparatory cleanup for adding optional extra user tracking records
without changing the current layout or behavior.

Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
 mm/slub.c | 31 +++++++++++++++++++++----------
 1 file changed, 21 insertions(+), 10 deletions(-)

diff --git a/mm/slub.c b/mm/slub.c
index a2bf3756ca7d..a9114dddc976 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -316,7 +316,20 @@ struct track {
 	unsigned long when;	/* When did the operation occur */
 };
 
-enum track_item { TRACK_ALLOC, TRACK_FREE };
+enum track_item { TRACK_ALLOC, TRACK_FREE, TRACK_NR };
+
+static inline unsigned int nr_user_tracks(struct kmem_cache *s)
+{
+	if (!(s->flags & SLAB_STORE_USER))
+		return 0;
+
+	return TRACK_NR;
+}
+
+static inline unsigned int user_tracking_size(struct kmem_cache *s)
+{
+	return nr_user_tracks(s) * sizeof(struct track);
+}
 
 #ifdef SLAB_SUPPORTS_SYSFS
 static int sysfs_slab_add(struct kmem_cache *);
@@ -740,7 +753,7 @@ static inline void set_orig_size(struct kmem_cache *s,
 		return;
 
 	p += get_info_end(s);
-	p += sizeof(struct track) * 2;
+	p += user_tracking_size(s);
 
 	*(unsigned long *)p = orig_size;
 }
@@ -756,7 +769,7 @@ static inline unsigned long get_orig_size(struct kmem_cache *s, void *object)
 		return s->object_size;
 
 	p += get_info_end(s);
-	p += sizeof(struct track) * 2;
+	p += user_tracking_size(s);
 
 	return *(unsigned long *)p;
 }
@@ -873,8 +886,7 @@ static unsigned int obj_exts_offset_in_object(struct kmem_cache *s)
 {
 	unsigned int offset = get_info_end(s);
 
-	if (kmem_cache_debug_flags(s, SLAB_STORE_USER))
-		offset += sizeof(struct track) * 2;
+	offset += user_tracking_size(s);
 
 	if (slub_debug_orig_size(s))
 		offset += sizeof(unsigned long);
@@ -1077,7 +1089,7 @@ static void init_tracking(struct kmem_cache *s, void *object)
 		return;
 
 	p = get_track(s, object, TRACK_ALLOC);
-	memset(p, 0, 2*sizeof(struct track));
+	memset(p, 0, user_tracking_size(s));
 }
 
 static void print_track(const char *s, struct track *t, unsigned long pr_time)
@@ -1185,8 +1197,7 @@ static void print_trailer(struct kmem_cache *s, struct slab *slab, u8 *p)
 
 	off = get_info_end(s);
 
-	if (s->flags & SLAB_STORE_USER)
-		off += 2 * sizeof(struct track);
+	off += user_tracking_size(s);
 
 	if (slub_debug_orig_size(s))
 		off += sizeof(unsigned long);
@@ -1390,7 +1401,7 @@ static int check_pad_bytes(struct kmem_cache *s, struct slab *slab, u8 *p)
 
 	if (s->flags & SLAB_STORE_USER) {
 		/* We also have user information there */
-		off += 2 * sizeof(struct track);
+		off += user_tracking_size(s);
 
 		if (s->flags & SLAB_KMALLOC)
 			off += sizeof(unsigned long);
@@ -7845,7 +7856,7 @@ static int calculate_sizes(struct kmem_cache_args *args, struct kmem_cache *s)
 		 * Need to store information about allocs and frees after
 		 * the object.
 		 */
-		size += 2 * sizeof(struct track);
+		size += user_tracking_size(s);
 
 		/* Save the original kmalloc request size */
 		if (flags & SLAB_KMALLOC)
-- 
2.50.1 (Apple Git-155)


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

* [RFC PATCH 2/5] mm/slub: add optional previous lifetime user tracking
  2026-06-11  6:39 [RFC PATCH 0/5] mm/slub: preserve previous object lifetime Pengpeng Hou
  2026-06-11  6:39 ` [RFC PATCH 1/5] mm/slub: factor user tracking metadata size calculation Pengpeng Hou
@ 2026-06-11  6:39 ` Pengpeng Hou
  2026-06-11  6:39 ` [RFC PATCH 3/5] mm/slub: print previous object lifetime in debug reports Pengpeng Hou
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Pengpeng Hou @ 2026-06-11  6:39 UTC (permalink / raw)
  To: Vlastimil Babka, Harry Yoo, Andrew Morton, linux-mm
  Cc: Hao Li, Christoph Lameter, David Rientjes, Roman Gushchin,
	David Hildenbrand, Lorenzo Stoakes, liam, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
	linux-doc, linux-kernel, Pengpeng Hou

SLAB_STORE_USER currently reserves two per-object tracks, one for the
allocation and one for the free. Add an opt-in SLAB_STORE_HISTORY flag
that extends the user tracking area to hold one previous completed
lifetime.

Expose the option as slab_debug=H, but require it to be used together
with U. This avoids silently enabling user tracking and its stack depot
cost when a user only requested H. The option is not part of the default
debug flags.

No history is recorded or printed yet; this only adds the flag, the
object metadata layout, and the sysfs state file.

Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
 include/linux/slab.h |  3 +++
 mm/slab.h            |  3 ++-
 mm/slub.c            | 36 +++++++++++++++++++++++++++++++-----
 3 files changed, 36 insertions(+), 6 deletions(-)

diff --git a/include/linux/slab.h b/include/linux/slab.h
index 2b5ab488e96b..78b9ec5bc17a 100644
--- a/include/linux/slab.h
+++ b/include/linux/slab.h
@@ -32,6 +32,7 @@ enum _slab_flag_bits {
 	_SLAB_CACHE_DMA,
 	_SLAB_CACHE_DMA32,
 	_SLAB_STORE_USER,
+	_SLAB_STORE_HISTORY,
 	_SLAB_PANIC,
 	_SLAB_TYPESAFE_BY_RCU,
 	_SLAB_TRACE,
@@ -98,6 +99,8 @@ enum _slab_flag_bits {
 #define SLAB_CACHE_DMA32	__SLAB_FLAG_BIT(_SLAB_CACHE_DMA32)
 /* DEBUG: Store the last owner for bug hunting */
 #define SLAB_STORE_USER		__SLAB_FLAG_BIT(_SLAB_STORE_USER)
+/* DEBUG: Store the previous object lifetime for bug hunting */
+#define SLAB_STORE_HISTORY	__SLAB_FLAG_BIT(_SLAB_STORE_HISTORY)
 /* Panic if kmem_cache_create() fails */
 #define SLAB_PANIC		__SLAB_FLAG_BIT(_SLAB_PANIC)
 /**
diff --git a/mm/slab.h b/mm/slab.h
index bf2f87acf5e3..a6af35829f79 100644
--- a/mm/slab.h
+++ b/mm/slab.h
@@ -417,7 +417,8 @@ void flush_rcu_sheaves_on_cache(struct kmem_cache *s);
 			 SLAB_NO_USER_FLAGS | SLAB_KMALLOC | SLAB_NO_MERGE)
 
 #define SLAB_DEBUG_FLAGS (SLAB_RED_ZONE | SLAB_POISON | SLAB_STORE_USER | \
-			  SLAB_TRACE | SLAB_CONSISTENCY_CHECKS)
+			  SLAB_STORE_HISTORY | SLAB_TRACE | \
+			  SLAB_CONSISTENCY_CHECKS)
 
 #define SLAB_FLAGS_PERMITTED (SLAB_CORE_FLAGS | SLAB_DEBUG_FLAGS)
 
diff --git a/mm/slub.c b/mm/slub.c
index a9114dddc976..803c597351ce 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -277,7 +277,7 @@ void *fixup_red_left(struct kmem_cache *s, void *p)
  * issues when checking or reading debug information
  */
 #define SLAB_NO_CMPXCHG (SLAB_CONSISTENCY_CHECKS | SLAB_STORE_USER | \
-				SLAB_TRACE)
+				SLAB_STORE_HISTORY | SLAB_TRACE)
 
 
 /*
@@ -285,7 +285,8 @@ void *fixup_red_left(struct kmem_cache *s, void *p)
  * disabled when slab_debug=O is used and a cache's min order increases with
  * metadata.
  */
-#define DEBUG_METADATA_FLAGS (SLAB_RED_ZONE | SLAB_POISON | SLAB_STORE_USER)
+#define DEBUG_METADATA_FLAGS (SLAB_RED_ZONE | SLAB_POISON | \
+			      SLAB_STORE_USER | SLAB_STORE_HISTORY)
 
 #define OO_SHIFT	16
 #define OO_MASK		((1 << OO_SHIFT) - 1)
@@ -316,14 +317,23 @@ struct track {
 	unsigned long when;	/* When did the operation occur */
 };
 
-enum track_item { TRACK_ALLOC, TRACK_FREE, TRACK_NR };
+enum track_item {
+	TRACK_ALLOC,
+	TRACK_FREE,
+	TRACK_PREV_ALLOC,
+	TRACK_PREV_FREE,
+	TRACK_NR,
+};
 
 static inline unsigned int nr_user_tracks(struct kmem_cache *s)
 {
 	if (!(s->flags & SLAB_STORE_USER))
 		return 0;
 
-	return TRACK_NR;
+	if (s->flags & SLAB_STORE_HISTORY)
+		return TRACK_NR;
+
+	return TRACK_PREV_ALLOC;
 }
 
 static inline unsigned int user_tracking_size(struct kmem_cache *s)
@@ -1837,6 +1847,9 @@ parse_slub_debug_flags(const char *str, slab_flags_t *flags, const char **slabs,
 		case 'u':
 			*flags |= SLAB_STORE_USER;
 			break;
+		case 'h':
+			*flags |= SLAB_STORE_HISTORY;
+			break;
 		case 't':
 			*flags |= SLAB_TRACE;
 			break;
@@ -1855,6 +1868,11 @@ parse_slub_debug_flags(const char *str, slab_flags_t *flags, const char **slabs,
 				pr_err("slab_debug option '%c' unknown. skipped\n", *str);
 		}
 	}
+	if ((*flags & SLAB_STORE_HISTORY) && !(*flags & SLAB_STORE_USER)) {
+		if (init)
+			pr_err("slab_debug option 'H' requires 'U'. skipped\n");
+		*flags &= ~SLAB_STORE_HISTORY;
+	}
 check_slabs:
 	if (*str == ',')
 		*slabs = ++str;
@@ -1969,7 +1987,7 @@ slab_flags_t kmem_cache_flags(slab_flags_t flags, const char *name)
 	 * but let the user enable it via the command line below.
 	 */
 	if (flags & SLAB_NOLEAKTRACE)
-		slub_debug_local &= ~SLAB_STORE_USER;
+		slub_debug_local &= ~(SLAB_STORE_USER | SLAB_STORE_HISTORY);
 
 	len = strlen(name);
 	next_block = slub_debug_string;
@@ -9223,6 +9241,13 @@ static ssize_t store_user_show(struct kmem_cache *s, char *buf)
 
 SLAB_ATTR_RO(store_user);
 
+static ssize_t store_history_show(struct kmem_cache *s, char *buf)
+{
+	return sysfs_emit(buf, "%d\n", !!(s->flags & SLAB_STORE_HISTORY));
+}
+
+SLAB_ATTR_RO(store_history);
+
 static ssize_t validate_show(struct kmem_cache *s, char *buf)
 {
 	return 0;
@@ -9442,6 +9467,7 @@ static const struct attribute *const slab_attrs[] = {
 	&red_zone_attr.attr,
 	&poison_attr.attr,
 	&store_user_attr.attr,
+	&store_history_attr.attr,
 	&validate_attr.attr,
 #endif
 #ifdef CONFIG_ZONE_DMA
-- 
2.50.1 (Apple Git-155)


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

* [RFC PATCH 3/5] mm/slub: print previous object lifetime in debug reports
  2026-06-11  6:39 [RFC PATCH 0/5] mm/slub: preserve previous object lifetime Pengpeng Hou
  2026-06-11  6:39 ` [RFC PATCH 1/5] mm/slub: factor user tracking metadata size calculation Pengpeng Hou
  2026-06-11  6:39 ` [RFC PATCH 2/5] mm/slub: add optional previous lifetime user tracking Pengpeng Hou
@ 2026-06-11  6:39 ` Pengpeng Hou
  2026-06-11  6:39 ` [RFC PATCH 4/5] Documentation/mm: document SLUB previous lifetime tracking Pengpeng Hou
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Pengpeng Hou @ 2026-06-11  6:39 UTC (permalink / raw)
  To: Vlastimil Babka, Harry Yoo, Andrew Morton, linux-mm
  Cc: Hao Li, Christoph Lameter, David Rientjes, Roman Gushchin,
	David Hildenbrand, Lorenzo Stoakes, liam, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
	linux-doc, linux-kernel, Pengpeng Hou

When SLAB_STORE_USER is enabled, each object stores one allocation track
and one free track. Reusing the object overwrites the allocation track,
while a later stale free can overwrite the free track. This can leave a
report with the victim allocation and stale free but without the
previous completed lifetime that explains where the stale pointer came
from.

When SLAB_STORE_HISTORY is enabled, copy the current allocation/free pair
to a previous-lifetime pair before recording the new allocation. Keep
the existing TRACK_FREE semantics unchanged so current store_user and
free_traces behavior is preserved. On SLUB error reports, print the
previous completed lifetime when both records are available.

The extra records are diagnostic information only. They do not infer
semantic ownership or identify the root cause of a use-after-free.

Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
 mm/slub.c | 43 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 42 insertions(+), 1 deletion(-)

diff --git a/mm/slub.c b/mm/slub.c
index 803c597351ce..2dfa8af00a49 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -1083,6 +1083,31 @@ static void set_track_update(struct kmem_cache *s, void *object,
 	p->when = jiffies;
 }
 
+static bool track_has_record(const struct track *track)
+{
+	return track->addr;
+}
+
+static void save_previous_lifetime(struct kmem_cache *s, void *object)
+{
+	struct track *alloc, *free;
+	struct track *prev_alloc, *prev_free;
+
+	if (!(s->flags & SLAB_STORE_HISTORY))
+		return;
+
+	alloc = get_track(s, object, TRACK_ALLOC);
+	free = get_track(s, object, TRACK_FREE);
+	if (!track_has_record(alloc) || !track_has_record(free))
+		return;
+
+	prev_alloc = get_track(s, object, TRACK_PREV_ALLOC);
+	prev_free = get_track(s, object, TRACK_PREV_FREE);
+
+	*prev_alloc = *alloc;
+	*prev_free = *free;
+}
+
 static __always_inline void set_track(struct kmem_cache *s, void *object,
 				      enum track_item alloc, unsigned long addr, gfp_t gfp_flags)
 {
@@ -1123,11 +1148,25 @@ static void print_track(const char *s, struct track *t, unsigned long pr_time)
 void print_tracking(struct kmem_cache *s, void *object)
 {
 	unsigned long pr_time = jiffies;
+	struct track *prev_alloc, *prev_free;
+
 	if (!(s->flags & SLAB_STORE_USER))
 		return;
 
 	print_track("Allocated", get_track(s, object, TRACK_ALLOC), pr_time);
 	print_track("Freed", get_track(s, object, TRACK_FREE), pr_time);
+
+	if (!(s->flags & SLAB_STORE_HISTORY))
+		return;
+
+	prev_alloc = get_track(s, object, TRACK_PREV_ALLOC);
+	prev_free = get_track(s, object, TRACK_PREV_FREE);
+	if (!track_has_record(prev_alloc) || !track_has_record(prev_free))
+		return;
+
+	pr_err("Previous object lifetime:\n");
+	print_track("Previous allocated", prev_alloc, pr_time);
+	print_track("Previous freed", prev_free, pr_time);
 }
 
 static void print_slab_info(const struct slab *slab)
@@ -4505,8 +4544,10 @@ static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,
 	return NULL;
 
 success:
-	if (kmem_cache_debug_flags(s, SLAB_STORE_USER))
+	if (kmem_cache_debug_flags(s, SLAB_STORE_USER)) {
+		save_previous_lifetime(s, object);
 		set_track(s, object, TRACK_ALLOC, addr, gfpflags);
+	}
 
 	return object;
 }
-- 
2.50.1 (Apple Git-155)


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

* [RFC PATCH 4/5] Documentation/mm: document SLUB previous lifetime tracking
  2026-06-11  6:39 [RFC PATCH 0/5] mm/slub: preserve previous object lifetime Pengpeng Hou
                   ` (2 preceding siblings ...)
  2026-06-11  6:39 ` [RFC PATCH 3/5] mm/slub: print previous object lifetime in debug reports Pengpeng Hou
@ 2026-06-11  6:39 ` Pengpeng Hou
  2026-06-11  6:39 ` [RFC PATCH 5/5] mm/slub: sanitize previous lifetime tracking flags Pengpeng Hou
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Pengpeng Hou @ 2026-06-11  6:39 UTC (permalink / raw)
  To: Vlastimil Babka, Harry Yoo, Andrew Morton, linux-mm
  Cc: Hao Li, Christoph Lameter, David Rientjes, Roman Gushchin,
	David Hildenbrand, Lorenzo Stoakes, liam, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
	linux-doc, linux-kernel, Pengpeng Hou

Document the new slab_debug=H option, its store_history sysfs state file,
and the additional previous-lifetime lines in SLUB reports.

Spell out that the previous lifetime is diagnostic information only. It
can help with stale-pointer reports after object reuse, but it does not
identify semantic ownership or the root cause of a use-after-free.

Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
 Documentation/admin-guide/mm/slab.rst | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/Documentation/admin-guide/mm/slab.rst b/Documentation/admin-guide/mm/slab.rst
index 14429ab90611..4f644c4e1baa 100644
--- a/Documentation/admin-guide/mm/slab.rst
+++ b/Documentation/admin-guide/mm/slab.rst
@@ -50,6 +50,8 @@ Possible debug options are::
 	Z		Red zoning
 	P		Poisoning (object and padding)
 	U		User tracking (free and alloc)
+	H		Previous lifetime tracking. Requires U and preserves one
+			previous completed alloc/free lifetime for each object.
 	T		Trace (please only use on single slabs)
 	A		Enable failslab filter mark for the cache
 	O		Switch debugging off for caches that would have
@@ -91,6 +93,17 @@ kmalloc. All other slabs will not get any debugging enabled::
 
 	slab_debug=Z,dentry;U,kmalloc-*
 
+Previous lifetime tracking can be enabled together with user tracking for
+selected caches. This keeps one previous completed alloc/free lifetime in
+addition to the normal user tracking records::
+
+	slab_debug=UH,kmalloc-128
+
+This can help debug reports where an object was freed, reallocated, and later
+accessed or freed again through a stale pointer. The previous lifetime is
+diagnostic information only; it does not identify semantic ownership or the
+root cause of a use-after-free.
+
 You can also enable options (e.g. sanity checks and poisoning) for all caches
 except some that are deemed too performance critical and don't need to be
 debugged by specifying global debug options followed by a list of slab names
@@ -110,6 +123,7 @@ options from the ``slab_debug`` parameter translate to the following files::
 	Z	red_zone
 	P	poison
 	U	store_user
+	H	store_history
 	T	trace
 	A	failslab
 
@@ -245,9 +259,15 @@ into the syslog:
 	cpu> pid=<pid of the process>
      INFO: Freed in <kernel function> age=<jiffies since free> cpu=<freed by cpu>
 	pid=<pid of the process>
+     INFO: Previous object lifetime:
+     INFO: Previous allocated in <kernel function> age=<jiffies since alloc> cpu=<allocated by
+	cpu> pid=<pid of the process>
+     INFO: Previous freed in <kernel function> age=<jiffies since free> cpu=<freed by cpu>
+	pid=<pid of the process>
 
    (Object allocation / free information is only available if SLAB_STORE_USER is
-   set for the slab. slab_debug sets that option)
+   set for the slab. slab_debug sets that option. Previous lifetime information
+   is only available if both SLAB_STORE_USER and SLAB_STORE_HISTORY are set.)
 
 2. The object contents if an object was involved.
 
-- 
2.50.1 (Apple Git-155)


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

* [RFC PATCH 5/5] mm/slub: sanitize previous lifetime tracking flags
  2026-06-11  6:39 [RFC PATCH 0/5] mm/slub: preserve previous object lifetime Pengpeng Hou
                   ` (3 preceding siblings ...)
  2026-06-11  6:39 ` [RFC PATCH 4/5] Documentation/mm: document SLUB previous lifetime tracking Pengpeng Hou
@ 2026-06-11  6:39 ` Pengpeng Hou
  2026-06-11  7:19 ` [RFC PATCH 0/5] mm/slub: preserve previous object lifetime Harry Yoo
  2026-06-11 17:28 ` Vlastimil Babka (SUSE)
  6 siblings, 0 replies; 9+ messages in thread
From: Pengpeng Hou @ 2026-06-11  6:39 UTC (permalink / raw)
  To: Vlastimil Babka, Harry Yoo, Andrew Morton, linux-mm
  Cc: Hao Li, Christoph Lameter, David Rientjes, Roman Gushchin,
	David Hildenbrand, Lorenzo Stoakes, liam, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
	linux-doc, linux-kernel, Pengpeng Hou

SLAB_STORE_HISTORY only has meaning together with SLAB_STORE_USER because
the previous lifetime records live in the user tracking metadata area.

The slab_debug parser rejects H without U, but kmem_cache_create()
callers may also pass debug flags directly. Clear SLAB_STORE_HISTORY
whenever SLAB_STORE_USER is not present so caches cannot end up
reporting store_history without any user tracking storage.

Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
 mm/slub.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/mm/slub.c b/mm/slub.c
index 2dfa8af00a49..931e6d04ba2b 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -341,6 +341,14 @@ static inline unsigned int user_tracking_size(struct kmem_cache *s)
 	return nr_user_tracks(s) * sizeof(struct track);
 }
 
+static inline slab_flags_t sanitize_user_tracking_flags(slab_flags_t flags)
+{
+	if ((flags & SLAB_STORE_HISTORY) && !(flags & SLAB_STORE_USER))
+		flags &= ~SLAB_STORE_HISTORY;
+
+	return flags;
+}
+
 #ifdef SLAB_SUPPORTS_SYSFS
 static int sysfs_slab_add(struct kmem_cache *);
 #else
@@ -1910,7 +1918,7 @@ parse_slub_debug_flags(const char *str, slab_flags_t *flags, const char **slabs,
 	if ((*flags & SLAB_STORE_HISTORY) && !(*flags & SLAB_STORE_USER)) {
 		if (init)
 			pr_err("slab_debug option 'H' requires 'U'. skipped\n");
-		*flags &= ~SLAB_STORE_HISTORY;
+		*flags = sanitize_user_tracking_flags(*flags);
 	}
 check_slabs:
 	if (*str == ',')
@@ -2052,7 +2060,7 @@ slab_flags_t kmem_cache_flags(slab_flags_t flags, const char *name)
 
 			if (!strncmp(name, iter, cmplen)) {
 				flags |= block_flags;
-				return flags;
+				return sanitize_user_tracking_flags(flags);
 			}
 
 			if (!*end || *end == ';')
@@ -2061,7 +2069,7 @@ slab_flags_t kmem_cache_flags(slab_flags_t flags, const char *name)
 		}
 	}
 
-	return flags | slub_debug_local;
+	return sanitize_user_tracking_flags(flags | slub_debug_local);
 }
 #else /* !CONFIG_SLUB_DEBUG */
 static inline void setup_object_debug(struct kmem_cache *s, void *object) {}
-- 
2.50.1 (Apple Git-155)


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

* Re: [RFC PATCH 0/5] mm/slub: preserve previous object lifetime
  2026-06-11  6:39 [RFC PATCH 0/5] mm/slub: preserve previous object lifetime Pengpeng Hou
                   ` (4 preceding siblings ...)
  2026-06-11  6:39 ` [RFC PATCH 5/5] mm/slub: sanitize previous lifetime tracking flags Pengpeng Hou
@ 2026-06-11  7:19 ` Harry Yoo
  2026-06-11 17:13   ` Vlastimil Babka (SUSE)
  2026-06-11 17:28 ` Vlastimil Babka (SUSE)
  6 siblings, 1 reply; 9+ messages in thread
From: Harry Yoo @ 2026-06-11  7:19 UTC (permalink / raw)
  To: Pengpeng Hou, Vlastimil Babka, Andrew Morton, linux-mm
  Cc: Hao Li, Christoph Lameter, David Rientjes, Roman Gushchin,
	David Hildenbrand, Lorenzo Stoakes, liam, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
	linux-doc, linux-kernel


[-- Attachment #1.1: Type: text/plain, Size: 1476 bytes --]

Hi Pengpeng,

On 6/11/26 3:39 PM, Pengpeng Hou wrote:
> SLAB_STORE_USER currently stores one allocation track and one free track
> for an object. This is useful, but it loses part of the previous lifetime
> when the object is reused: the new allocation overwrites the allocation
> track, and a later stale free can overwrite the free track.

I'm not sure what you meant by "stale free", UAF is accessing object
that are freed. What makes the free "stale"?

In general, I don't think slab_debug=UP is the right tool to debug
use-after-frees, because slab will never know _when_ the object was
overwritten. It can only tell that somebody has overwritten freed
objects by checking if the object content is POISON_FREE or POISON_END.

KASAN is a better tool to debug use-after-frees, because it can
tell you which kernel code is accessing memory it shouldn't. (It also
quarantines slab objects to avoid immediately reusing the object for
better coverage).

So I have to ask, "Why not use KASAN instead?" before enhancing
slab_debug (neither is intended for production anyway).

> For free-after-reuse bugs, the report can therefore contain the victim
> allocation and the stale free, while the earlier alloc/free pair that
> explains where the stale pointer came from is no longer available.

Again, I'm confused. I have no idea what "free-after-reuse" means.
Objects cannot be reused until they are not freed, no?

-- 
Cheers,
Harry / Hyeonggon

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

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

* Re: [RFC PATCH 0/5] mm/slub: preserve previous object lifetime
  2026-06-11  7:19 ` [RFC PATCH 0/5] mm/slub: preserve previous object lifetime Harry Yoo
@ 2026-06-11 17:13   ` Vlastimil Babka (SUSE)
  0 siblings, 0 replies; 9+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-06-11 17:13 UTC (permalink / raw)
  To: Harry Yoo, Pengpeng Hou, Andrew Morton, linux-mm
  Cc: Hao Li, Christoph Lameter, David Rientjes, Roman Gushchin,
	David Hildenbrand, Lorenzo Stoakes, liam, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
	linux-doc, linux-kernel

On 6/11/26 09:19, Harry Yoo wrote:
> Hi Pengpeng,
> 
> On 6/11/26 3:39 PM, Pengpeng Hou wrote:
>> SLAB_STORE_USER currently stores one allocation track and one free track
>> for an object. This is useful, but it loses part of the previous lifetime
>> when the object is reused: the new allocation overwrites the allocation
>> track, and a later stale free can overwrite the free track.
> 
> I'm not sure what you meant by "stale free", UAF is accessing object
> that are freed. What makes the free "stale"?

I'm guessing it means 'second/duplicated free" of the previous owner.
Accesses (UAF) perhaps may not happen by that owner, or if they happen after
he object is reallocated, they are not recognized as such.

> In general, I don't think slab_debug=UP is the right tool to debug
> use-after-frees, because slab will never know _when_ the object was
> overwritten. It can only tell that somebody has overwritten freed
> objects by checking if the object content is POISON_FREE or POISON_END.

It could give more information about double frees like this, however.

> KASAN is a better tool to debug use-after-frees, because it can
> tell you which kernel code is accessing memory it shouldn't. (It also
> quarantines slab objects to avoid immediately reusing the object for
> better coverage).
> 
> So I have to ask, "Why not use KASAN instead?" before enhancing
> slab_debug (neither is intended for production anyway).

From my distro experience, it's very useful to tell a user to just enable
slub_debug for a specific cache with the existing kernel, with some but not
prohibitive overhead. And with some luck it gives you enough information to
find the root cause too. So in that sense it can be used in production.
KASAN is indeed superior wrt catching issues, but almost never applicable in
such environment. It would need a rebuilt kernel and the overhead is much
higher. So it's a tradeoff.

>> For free-after-reuse bugs, the report can therefore contain the victim
>> allocation and the stale free, while the earlier alloc/free pair that
>> explains where the stale pointer came from is no longer available.
> 
> Again, I'm confused. I have no idea what "free-after-reuse" means.
> Objects cannot be reused until they are not freed, no?





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

* Re: [RFC PATCH 0/5] mm/slub: preserve previous object lifetime
  2026-06-11  6:39 [RFC PATCH 0/5] mm/slub: preserve previous object lifetime Pengpeng Hou
                   ` (5 preceding siblings ...)
  2026-06-11  7:19 ` [RFC PATCH 0/5] mm/slub: preserve previous object lifetime Harry Yoo
@ 2026-06-11 17:28 ` Vlastimil Babka (SUSE)
  6 siblings, 0 replies; 9+ messages in thread
From: Vlastimil Babka (SUSE) @ 2026-06-11 17:28 UTC (permalink / raw)
  To: Pengpeng Hou, Harry Yoo, Andrew Morton, linux-mm
  Cc: Hao Li, Christoph Lameter, David Rientjes, Roman Gushchin,
	David Hildenbrand, Lorenzo Stoakes, liam, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan,
	linux-doc, linux-kernel

On 6/11/26 08:39, Pengpeng Hou wrote:
> SLAB_STORE_USER currently stores one allocation track and one free track
> for an object. This is useful, but it loses part of the previous lifetime
> when the object is reused: the new allocation overwrites the allocation
> track, and a later stale free can overwrite the free track.
> 
> For free-after-reuse bugs, the report can therefore contain the victim
> allocation and the stale free, while the earlier alloc/free pair that
> explains where the stale pointer came from is no longer available.
> 
> This RFC adds an opt-in SLUB debug option to keep one previous completed
> object lifetime. The option is disabled by default, is not part of the
> default debug flags, and only takes effect when user tracking is already
> enabled:

Sounds useful!

>   slab_debug=UH,kmalloc-128
> 
> The series intentionally does not attempt to infer semantic ownership or
> identify the root cause of a use-after-free. It only preserves and prints
> additional track records that SLUB already knows how to collect.

Indeed, it would be generally impossible to infer I think.

> This is sent as RFC because the user-visible interface and the cost/benefit
> tradeoff should be agreed on before this becomes a normal patch series.
> In particular, feedback would be useful on:
> 
> - whether a separate H option is preferable to extending U directly

I think before we converted U to stackdepot, the memory overhead of the
stacks was higher than U+H with stackdepot. So I think it would be
acceptable to extend directly. If a user is willing to pay the current U
overhead to debug something in production, the addition of U+H shouldn't
make it suddenly unacceptable.

> - whether H should require U, as implemented here, or imply U
> - whether the extra per-object metadata is useful enough for this debug path

One could think of scenarios where even longer object history would be
needed to find the culprit. But adding one extra lifetime probably has the
biggest impact.

> Not included yet:
> 
> - KUnit coverage or a standalone reproducer

That would be nice.

> - object-size/order comparison data for representative caches

Could be useful too.

> - runtime benchmark data for slab_debug=U vs slab_debug=UH

This is probably not necessary as we do expect debugging has a performance
cost, and this is not doing anything extra slow, only copying the previous
tracking info? In practice the overhead depends on the workload anyway.

Thanks!

> Those should be added before a non-RFC submission if the direction looks
> acceptable.
> 
> Pengpeng Hou (5):
>   mm/slub: factor user tracking metadata size calculation
>   mm/slub: add optional previous lifetime user tracking
>   mm/slub: print previous object lifetime in debug reports
>   Documentation/mm: document SLUB previous lifetime tracking
>   mm/slub: sanitize previous lifetime tracking flags
> 
>  Documentation/admin-guide/mm/slab.rst |  22 ++++-
>  include/linux/slab.h                  |   3 +
>  mm/slab.h                             |   3 +-
>  mm/slub.c                             | 118 ++++++++++++++++++++++----
>  4 files changed, 128 insertions(+), 18 deletions(-)
> 


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

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

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-11  6:39 [RFC PATCH 0/5] mm/slub: preserve previous object lifetime Pengpeng Hou
2026-06-11  6:39 ` [RFC PATCH 1/5] mm/slub: factor user tracking metadata size calculation Pengpeng Hou
2026-06-11  6:39 ` [RFC PATCH 2/5] mm/slub: add optional previous lifetime user tracking Pengpeng Hou
2026-06-11  6:39 ` [RFC PATCH 3/5] mm/slub: print previous object lifetime in debug reports Pengpeng Hou
2026-06-11  6:39 ` [RFC PATCH 4/5] Documentation/mm: document SLUB previous lifetime tracking Pengpeng Hou
2026-06-11  6:39 ` [RFC PATCH 5/5] mm/slub: sanitize previous lifetime tracking flags Pengpeng Hou
2026-06-11  7:19 ` [RFC PATCH 0/5] mm/slub: preserve previous object lifetime Harry Yoo
2026-06-11 17:13   ` Vlastimil Babka (SUSE)
2026-06-11 17:28 ` Vlastimil Babka (SUSE)

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