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 --- 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)