Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

unwind deferred: Add unwind_completed mask to stop spurious callbacks

If there's more than one registered tracer to the unwind deferred
infrastructure, it is currently possible that one tracer could cause extra
callbacks to happen for another tracer if the former requests a deferred
stacktrace after the latter's callback was executed and before the task
went back to user space.

Here's an example of how this could occur:

[Task enters kernel]
tracer 1 request -> add cookie to its buffer
tracer 1 request -> add cookie to its buffer
<..>
[ task work executes ]
tracer 1 callback -> add trace + cookie to its buffer

[tracer 2 requests and triggers the task work again]
[ task work executes again ]
tracer 1 callback -> add trace + cookie to its buffer
tracer 2 callback -> add trace + cookie to its buffer
[Task exits back to user space]

This is because the bit for tracer 1 gets set in the task's unwind_mask
when it did its request and does not get cleared until the task returns
back to user space. But if another tracer were to request another deferred
stacktrace, then the next task work will executed all tracer's callbacks
that have their bits set in the task's unwind_mask.

To fix this issue, add another mask called unwind_completed and place it
into the task's info->cache structure. The cache structure is allocated
on the first occurrence of a deferred stacktrace and this unwind_completed
mask is not needed until then. It's better to have it in the cache than to
permanently waste space in the task_struct.

After a tracer's callback is executed, it's bit gets set in this
unwind_completed mask. When the task_work enters, it will AND the task's
unwind_mask with the inverse of the unwind_completed which will eliminate
any work that already had its callback executed since the task entered the
kernel.

When the task leaves the kernel, it will reset this unwind_completed mask
just like it resets the other values as it enters user space.

Link: https://lore.kernel.org/all/20250716142609.47f0e4a5@batman.local.home/

Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Josh Poimboeuf <jpoimboe@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Andrii Nakryiko <andrii@kernel.org>
Cc: Indu Bhagat <indu.bhagat@oracle.com>
Cc: "Jose E. Marchesi" <jemarch@gnu.org>
Cc: Beau Belgrave <beaub@linux.microsoft.com>
Cc: Jens Remus <jremus@linux.ibm.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Jens Axboe <axboe@kernel.dk>
Cc: Florian Weimer <fweimer@redhat.com>
Cc: Sam James <sam@gentoo.org>
Link: https://lore.kernel.org/20250729182405.989222722@kernel.org
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>

+19 -5
+3 -1
include/linux/unwind_deferred.h
··· 55 55 * depends on nr_entries being cleared on exit to user, 56 56 * this needs to be a separate conditional. 57 57 */ 58 - if (unlikely(info->cache)) 58 + if (unlikely(info->cache)) { 59 59 info->cache->nr_entries = 0; 60 + info->cache->unwind_completed = 0; 61 + } 60 62 } 61 63 62 64 #else /* !CONFIG_UNWIND_USER */
+1
include/linux/unwind_deferred_types.h
··· 3 3 #define _LINUX_UNWIND_USER_DEFERRED_TYPES_H 4 4 5 5 struct unwind_cache { 6 + unsigned long unwind_completed; 6 7 unsigned int nr_entries; 7 8 unsigned long entries[]; 8 9 };
+15 -4
kernel/unwind/deferred.c
··· 166 166 167 167 unwind_user_faultable(&trace); 168 168 169 + if (info->cache) 170 + bits &= ~(info->cache->unwind_completed); 171 + 169 172 cookie = info->id.id; 170 173 171 174 guard(mutex)(&callback_mutex); 172 175 list_for_each_entry(work, &callbacks, list) { 173 - if (test_bit(work->bit, &bits)) 176 + if (test_bit(work->bit, &bits)) { 174 177 work->func(work, &trace, cookie); 178 + if (info->cache) 179 + info->cache->unwind_completed |= BIT(work->bit); 180 + } 175 181 } 176 182 } 177 183 ··· 266 260 void unwind_deferred_cancel(struct unwind_work *work) 267 261 { 268 262 struct task_struct *g, *t; 263 + int bit; 269 264 270 265 if (!work) 271 266 return; 272 267 268 + bit = work->bit; 269 + 273 270 /* No work should be using a reserved bit */ 274 - if (WARN_ON_ONCE(BIT(work->bit) & RESERVED_BITS)) 271 + if (WARN_ON_ONCE(BIT(bit) & RESERVED_BITS)) 275 272 return; 276 273 277 274 guard(mutex)(&callback_mutex); 278 275 list_del(&work->list); 279 276 280 - __clear_bit(work->bit, &unwind_mask); 277 + __clear_bit(bit, &unwind_mask); 281 278 282 279 guard(rcu)(); 283 280 /* Clear this bit from all threads */ 284 281 for_each_process_thread(g, t) { 285 - clear_bit(work->bit, &t->unwind_info.unwind_mask); 282 + clear_bit(bit, &t->unwind_info.unwind_mask); 283 + if (t->unwind_info.cache) 284 + clear_bit(bit, &t->unwind_info.cache->unwind_completed); 286 285 } 287 286 } 288 287