[PATCH] tracing: Use per-CPU sequence counter in trace_user_fault_read

From: Masami Hiramatsu

Date: Thu Jul 09 2026 - 23:11:00 EST


trace_user_fault_read() copies trace data from user space to the
per-CPU trace buffer. When preemption is enabled during the copy, it
checks if any context switches occurred on the current CPU via
nr_context_switches_cpu() to detect whether the buffer may have been
corrupted by another trace writer.

However, under heavy memory pressure, copying from user space can trigger
page faults (e.g., for swapped-out BSS or anonymous pages) that block and
cause a context switch. Because nr_context_switches_cpu() detects any
context switch (even unrelated ones), it mistakenly assumes the buffer was
corrupted. This leads to repeated retries (up to the 100-try limit), which
causes a WARN_ONCE backtrace and returns -EFAULT to user space, even if
no other task ever accessed the trace buffer.

To mitigate this issue, replace the CPU-wide context switch check with
a dedicated per-CPU sequence counter in struct trace_user_buf.

Since only other tasks invoking trace_user_fault_read() on the same CPU
will increment this counter, unrelated context switches (including those
from page fault sleep) will no longer trigger retries.

Signed-off-by: Masami Hiramatsu (Google) <mhiramat@xxxxxxxxxx>
---
kernel/trace/trace.c | 29 ++++++++++++++++++-----------
1 file changed, 18 insertions(+), 11 deletions(-)

diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 1bc27c0ad029..46cec63f5798 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -5989,6 +5989,7 @@ static ssize_t write_marker_to_buffer(struct trace_array *tr, const char *buf,

struct trace_user_buf {
char *buf;
+ unsigned int sequence;
};

static DEFINE_MUTEX(trace_user_buffer_mutex);
@@ -6031,7 +6032,10 @@ static int user_fault_buffer_enable(struct trace_user_buf_info *tinfo, size_t si

/* Clear each buffer in case of error */
for_each_possible_cpu(cpu) {
- per_cpu_ptr(tinfo->tbuf, cpu)->buf = NULL;
+ struct trace_user_buf *tbuf = per_cpu_ptr(tinfo->tbuf, cpu);
+
+ tbuf->buf = NULL;
+ tbuf->sequence = 0;
}

for_each_possible_cpu(cpu) {
@@ -6196,8 +6200,9 @@ char *trace_user_fault_read(struct trace_user_buf_info *tinfo,
trace_user_buf_copy copy_func, void *data)
{
int cpu = smp_processor_id();
- char *buffer = per_cpu_ptr(tinfo->tbuf, cpu)->buf;
- unsigned int cnt;
+ struct trace_user_buf *tbuf = per_cpu_ptr(tinfo->tbuf, cpu);
+ char *buffer = tbuf->buf;
+ unsigned int seq;
int trys = 0;
int ret;

@@ -6211,10 +6216,10 @@ char *trace_user_fault_read(struct trace_user_buf_info *tinfo,
return NULL;

/*
- * This acts similar to a seqcount. The per CPU context switches are
+ * This acts similar to a seqcount. The per CPU sequence counters are
* recorded, migration is disabled and preemption is enabled. The
* read of the user space memory is copied into the per CPU buffer.
- * Preemption is disabled again, and if the per CPU context switches count
+ * Preemption is disabled again, and if the per CPU sequence count
* is still the same, it means the buffer has not been corrupted.
* If the count is different, it is assumed the buffer is corrupted
* and reading must be tried again.
@@ -6235,7 +6240,8 @@ char *trace_user_fault_read(struct trace_user_buf_info *tinfo,
preempt_enable_notrace();
preempt_disable_notrace();
cpu = smp_processor_id();
- buffer = per_cpu_ptr(tinfo->tbuf, cpu)->buf;
+ tbuf = per_cpu_ptr(tinfo->tbuf, cpu);
+ buffer = tbuf->buf;
}

/*
@@ -6250,8 +6256,9 @@ char *trace_user_fault_read(struct trace_user_buf_info *tinfo,
if (WARN_ONCE(trys++ > 100, "Error: Too many tries to read user space"))
return NULL;

- /* Read the current CPU context switch counter */
- cnt = nr_context_switches_cpu(cpu);
+ /* Increment the per-CPU buffer sequence counter */
+ tbuf->sequence++;
+ seq = tbuf->sequence;

/*
* Preemption is going to be enabled, but this task must
@@ -6282,12 +6289,12 @@ char *trace_user_fault_read(struct trace_user_buf_info *tinfo,
return NULL;

/*
- * Preemption is disabled again, now check the per CPU context
- * switch counter. If it doesn't match, then another user space
+ * Preemption is disabled again, now check the per CPU sequence
+ * counter. If it doesn't match, then another user space
* process may have schedule in and corrupted our buffer. In that
* case the copying must be retried.
*/
- } while (nr_context_switches_cpu(cpu) != cnt);
+ } while (tbuf->sequence != seq);

return buffer;
}
--
2.43.0



--
Masami Hiramatsu (Google) <mhiramat@xxxxxxxxxx>