* [PATCH v3 02/11] seq_buf: Do not pop from an overflowed seq_buf
[not found] <20260930235231.out.387-kees@kernel.org>
@ 2026-09-30 23:52 ` Kees Cook
2026-09-30 23:59 ` sashiko-bot
2026-09-30 23:52 ` [PATCH v3 06/11] seq_buf: Add seq_buf_terminate() Kees Cook
1 sibling, 1 reply; 3+ messages in thread
From: Kees Cook @ 2026-09-30 23:52 UTC (permalink / raw)
To: Bill Wendling
Cc: Kees Cook, Günther Noack, Matthew Wilcox (Oracle),
Mickaël Salaün, bpf, linux-security-module,
linux-trace-kernel, Andrew Morton, Andy Shevchenko, David Gow,
Masami Hiramatsu, Mathieu Desnoyers, Petr Mladek, Shuvam Pandey,
Steven Rostedt, nikitash.mariiaw, Greg KH, linux-kernel,
linux-hardening
When a seq_buf has overflowed, its len is size + 1, so seq_buf_pop()
decrements len to size and reads buffer[size], one byte past the end of
the buffer. It also leaves len equal to size, which no longer counts as
overflowed, so a truncated seq_buf then looks like a complete, full one.
An overflowed seq_buf logically has no last character to pop: the
length of what was written has been lost, and the last byte of the
buffer may be the NUL written by vsnprintf() or bytes that were never
committed. Return -1 for an overflowed seq_buf, as for an empty one,
and leave it overflowed, as the rest of the seq_buf API does until
seq_buf_clear() or seq_buf_init().
The current callers do not reach this, e.g. trace_syscalls only calls
trace_seq_pop() when the trace_seq it pops from has not overflowed, and
kernel/bpf/diagnostics.c sets the length from strnlen() before popping.
Add tests for the pop corner cases.
Tests passed under qemu on ARCH=x86_64 with GCC 16.2.0 and CONFIG_KASAN=y,
and on big-endian ARCH=s390 with GCC s390x-linux-gnu 16.1.0.
Fixes: 32e0f607ac6a2 ("tracing: Add trace_seq_pop() and seq_buf_pop()")
Assisted-by: LLM
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Kees Cook <kees@kernel.org>
---
include/linux/seq_buf.h | 4 ++--
include/linux/trace_seq.h | 5 ++++-
lib/tests/seq_buf_kunit.c | 42 +++++++++++++++++++++++++++++++++++++++
3 files changed, 48 insertions(+), 3 deletions(-)
diff --git a/include/linux/seq_buf.h b/include/linux/seq_buf.h
index 9f2839e73f8a..f5a350347bc5 100644
--- a/include/linux/seq_buf.h
+++ b/include/linux/seq_buf.h
@@ -155,11 +155,11 @@ static inline void seq_buf_commit(struct seq_buf *s, int num)
*
* Removes the last written character to the seq_buf @s.
*
- * Returns the last character or -1 if it is empty.
+ * Returns the last character, or -1 if @s is empty or has overflowed.
*/
static inline int seq_buf_pop(struct seq_buf *s)
{
- if (!s->len)
+ if (!s->len || seq_buf_has_overflowed(s))
return -1;
s->len--;
diff --git a/include/linux/trace_seq.h b/include/linux/trace_seq.h
index 697d619aafdc..7174ebf3f015 100644
--- a/include/linux/trace_seq.h
+++ b/include/linux/trace_seq.h
@@ -86,7 +86,10 @@ static inline bool trace_seq_has_overflowed(struct trace_seq *s)
*
* Removes the last written character to the trace_seq @s.
*
- * Returns the last character or -1 if it is empty.
+ * Returns the last character, or -1 if the underlying seq_buf is empty or
+ * has overflowed. Note that only that buffer is consulted: a @s marked
+ * full by a write that did not fit, which trace_seq_has_overflowed()
+ * reports as overflowed, still pops the last character written.
*/
static inline int trace_seq_pop(struct trace_seq *s)
{
diff --git a/lib/tests/seq_buf_kunit.c b/lib/tests/seq_buf_kunit.c
index 9ceccdc3029f..d5a0c618b880 100644
--- a/lib/tests/seq_buf_kunit.c
+++ b/lib/tests/seq_buf_kunit.c
@@ -115,6 +115,47 @@ static void seq_buf_putc_test(struct kunit *test)
KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "");
}
+static void seq_buf_pop_test(struct kunit *test)
+{
+ DECLARE_SEQ_BUF(s, 8);
+ struct seq_buf t;
+ char *buf;
+
+ /* Nothing to pop. */
+ KUNIT_EXPECT_EQ(test, seq_buf_pop(&s), -1);
+ KUNIT_EXPECT_EQ(test, s.len, 0);
+
+ seq_buf_puts(&s, "hello");
+ KUNIT_EXPECT_EQ(test, seq_buf_pop(&s), 'o');
+ KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 4);
+ KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hell");
+
+ /* A 0xff byte must not be mistaken for an empty buffer. */
+ seq_buf_putc(&s, 0xff);
+ KUNIT_EXPECT_EQ(test, seq_buf_pop(&s), 0xff);
+
+ /* A full buffer pops its last byte. */
+ seq_buf_puts(&s, "abc");
+ seq_buf_putc(&s, 'd');
+ KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(&s));
+ KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 8);
+ KUNIT_EXPECT_EQ(test, seq_buf_pop(&s), 'd');
+ KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 7);
+ KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hellabc");
+
+ /*
+ * An overflowed buffer has nothing to pop, and stays overflowed. Use
+ * a buffer allocated at its exact size, so that KASAN reports any
+ * read past its end.
+ */
+ buf = kunit_kmalloc(test, 16, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, buf);
+ seq_buf_init(&t, buf, 16);
+ KUNIT_EXPECT_EQ(test, seq_buf_printf(&t, "%s", "longer than sixteen"), -1);
+ KUNIT_EXPECT_EQ(test, seq_buf_pop(&t), -1);
+ KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&t));
+}
+
static void seq_buf_printf_test(struct kunit *test)
{
DECLARE_SEQ_BUF(s, 32);
@@ -355,6 +396,7 @@ static struct kunit_case seq_buf_test_cases[] = {
KUNIT_CASE(seq_buf_puts_test),
KUNIT_CASE(seq_buf_puts_overflow_test),
KUNIT_CASE(seq_buf_putc_test),
+ KUNIT_CASE(seq_buf_pop_test),
KUNIT_CASE(seq_buf_printf_test),
KUNIT_CASE(seq_buf_printf_overflow_test),
KUNIT_CASE(seq_buf_get_buf_commit_test),
--
2.34.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH v3 06/11] seq_buf: Add seq_buf_terminate()
[not found] <20260930235231.out.387-kees@kernel.org>
2026-09-30 23:52 ` [PATCH v3 02/11] seq_buf: Do not pop from an overflowed seq_buf Kees Cook
@ 2026-09-30 23:52 ` Kees Cook
1 sibling, 0 replies; 3+ messages in thread
From: Kees Cook @ 2026-09-30 23:52 UTC (permalink / raw)
To: Bill Wendling
Cc: Kees Cook, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Andy Shevchenko, Petr Mladek,
Matthew Wilcox (Oracle), Shuvam Pandey, David Gow, Andrew Morton,
bpf, linux-trace-kernel, nikitash.mariiaw, Greg KH, linux-kernel,
linux-hardening
Seven callers call seq_buf_str() only to NUL-terminate the buffer,
discarding the returned pointer. Two of them need a comment to say so.
Add seq_buf_terminate(), wrapping the __seq_buf_terminate() helper that
seq_buf_str() and seq_buf_strlen() already use, and convert those
callers. It returns void: returning the offset would just be
seq_buf_strlen() under another name. A zero-sized seq_buf is left
untouched, as in the other accessors.
Add tests for the three cases: room for the NUL after the data, an
overflowed buffer where it lands in the last byte, and a zero-sized
buffer that must not be written to.
Build tested ARCH=x86_64 defconfig with GCC 16.2.0, plus
CONFIG_HIST_TRIGGERS=y and CONFIG_BPF_SYSCALL=y to reach the converted
call sites in kernel/trace/trace_events_hist.c and
kernel/bpf/diagnostics.c. Tests run 24/24 passing on ARCH=um.
Assisted-by: LLM
Signed-off-by: Kees Cook <kees@kernel.org>
---
include/linux/seq_buf.h | 23 +++++++++++++++++++++++
kernel/bpf/diagnostics.c | 6 +++---
kernel/trace/trace_events.c | 4 ++--
kernel/trace/trace_events_hist.c | 6 ++----
lib/tests/seq_buf_kunit.c | 28 ++++++++++++++++++++++++++++
5 files changed, 58 insertions(+), 9 deletions(-)
diff --git a/include/linux/seq_buf.h b/include/linux/seq_buf.h
index 87ccc62f1c62..195e612a212a 100644
--- a/include/linux/seq_buf.h
+++ b/include/linux/seq_buf.h
@@ -171,6 +171,29 @@ static inline size_t seq_buf_strlen(struct seq_buf *s)
return __seq_buf_terminate(s);
}
+/**
+ * seq_buf_terminate - NUL-terminate the string in a seq_buf
+ * @s: the seq_buf handle
+ *
+ * Terminate @s->buffer exactly as seq_buf_str() and seq_buf_strlen() do,
+ * for callers that want neither the pointer nor the length and only need
+ * the buffer to be safe to read as a C string. A zero-sized seq_buf has
+ * nowhere to put a NUL and is left untouched.
+ *
+ * Nothing is returned on purpose: a caller that wants the length should
+ * use seq_buf_strlen(), which says so.
+ *
+ * After this function is called, s->buffer is safe to use
+ * in string operations.
+ */
+static inline void seq_buf_terminate(struct seq_buf *s)
+{
+ if (s->size == 0)
+ return;
+
+ __seq_buf_terminate(s);
+}
+
/**
* seq_buf_get_buf - get buffer to write arbitrary data to
* @s: the seq_buf handle
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index 0abbbe177e31..594cf3c8b74c 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -351,7 +351,7 @@ static void diag_fmt_restore(struct bpf_verifier_env *env, struct diag_fmt_mark
if (mark.chunk) {
mark.chunk->seq.len = mark.len;
- seq_buf_str(&mark.chunk->seq);
+ seq_buf_terminate(&mark.chunk->seq);
}
}
@@ -631,11 +631,11 @@ static void format_disasm_line(struct bpf_verifier_env *env, int insn_idx,
return;
print_bpf_insn(&cbs, insn, env->allow_ptr_leaks);
- seq_buf_str(&ctx.seq);
+ seq_buf_terminate(&ctx.seq);
ctx.seq.len = strnlen(line->text, sizeof(line->text));
while (ctx.seq.len && line->text[ctx.seq.len - 1] == '\n')
seq_buf_pop(&ctx.seq);
- seq_buf_str(&ctx.seq);
+ seq_buf_terminate(&ctx.seq);
line->valid = true;
}
diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index 9dbc2441763b..39bb391546de 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -4906,7 +4906,7 @@ static __init int event_trace_enable(void)
*/
__trace_early_add_events(tr);
- seq_buf_str(&bootup_event_seq);
+ seq_buf_terminate(&bootup_event_seq);
early_enable_events(tr, bootup_event_buf, false);
trace_printk_start_comm();
@@ -4935,7 +4935,7 @@ static __init int event_trace_enable_again(void)
if (!tr)
return -ENODEV;
- seq_buf_str(&bootup_event_seq);
+ seq_buf_terminate(&bootup_event_seq);
early_enable_events(tr, bootup_event_buf, true);
return 0;
diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index 963e0d6b61fd..57bd1cd5c657 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -2988,8 +2988,7 @@ find_synthetic_field_var(struct hist_trigger_data *target_hist_data,
seq_buf_init(&s, synthetic_name, MAX_FILTER_STR_VAL);
seq_buf_printf(&s, "synthetic_%s", field_name);
- /* Terminate synthetic_name with a NUL. */
- seq_buf_str(&s);
+ seq_buf_terminate(&s);
if (seq_buf_has_overflowed(&s)) {
kfree(synthetic_name);
@@ -3106,8 +3105,7 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
if (saved_filter)
seq_buf_printf(&s, " if %s", saved_filter);
- /* Terminate cmd with a NUL. */
- seq_buf_str(&s);
+ seq_buf_terminate(&s);
if (seq_buf_has_overflowed(&s)) {
kfree(cmd);
diff --git a/lib/tests/seq_buf_kunit.c b/lib/tests/seq_buf_kunit.c
index 9859a44dd959..8e879a4082ad 100644
--- a/lib/tests/seq_buf_kunit.c
+++ b/lib/tests/seq_buf_kunit.c
@@ -635,6 +635,33 @@ static void seq_buf_strlen_zero_size_test(struct kunit *test)
KUNIT_EXPECT_STREQ(test, buf, "untouched");
}
+static void seq_buf_terminate_test(struct kunit *test)
+{
+ char buf[16];
+ struct seq_buf s;
+
+ /* Terminates directly after the data when there is room. */
+ memset(buf, 'z', sizeof(buf));
+ seq_buf_init(&s, buf, sizeof(buf));
+ seq_buf_puts(&s, "ab");
+ seq_buf_terminate(&s);
+ KUNIT_EXPECT_STREQ(test, buf, "ab");
+
+ /* Terminates in the last byte once the buffer has overflowed. */
+ memset(buf, 'z', sizeof(buf));
+ seq_buf_init(&s, buf, 4);
+ seq_buf_puts(&s, "abcdef");
+ KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s));
+ seq_buf_terminate(&s);
+ KUNIT_EXPECT_STREQ(test, buf, "abc");
+
+ /* A zero-sized seq_buf is left alone. */
+ strscpy(buf, "untouched", sizeof(buf));
+ seq_buf_init(&s, buf, 0);
+ seq_buf_terminate(&s);
+ KUNIT_EXPECT_STREQ(test, buf, "untouched");
+}
+
static struct kunit_case seq_buf_test_cases[] = {
KUNIT_CASE(seq_buf_init_test),
KUNIT_CASE(seq_buf_declare_test),
@@ -658,6 +685,7 @@ static struct kunit_case seq_buf_test_cases[] = {
KUNIT_CASE(seq_buf_strlen_puts_overflow_test),
KUNIT_CASE(seq_buf_strlen_embedded_nul_test),
KUNIT_CASE(seq_buf_strlen_zero_size_test),
+ KUNIT_CASE(seq_buf_terminate_test),
KUNIT_CASE(seq_buf_do_printk_test),
{}
};
--
2.34.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH v3 02/11] seq_buf: Do not pop from an overflowed seq_buf
2026-09-30 23:52 ` [PATCH v3 02/11] seq_buf: Do not pop from an overflowed seq_buf Kees Cook
@ 2026-09-30 23:59 ` sashiko-bot
0 siblings, 0 replies; 3+ messages in thread
From: sashiko-bot @ 2026-09-30 23:59 UTC (permalink / raw)
To: Kees Cook; +Cc: linux-security-module, bpf, linux-trace-kernel
> When a seq_buf has overflowed, its len is size + 1, so seq_buf_pop()
> decrements len to size and reads buffer[size], one byte past the end of
> the buffer. It also leaves len equal to size, which no longer counts as
> overflowed, so a truncated seq_buf then looks like a complete, full one.
>
> An overflowed seq_buf logically has no last character to pop: the
> length of what was written has been lost, and the last byte of the
> buffer may be the NUL written by vsnprintf() or bytes that were never
> committed. Return -1 for an overflowed seq_buf, as for an empty one,
> and leave it overflowed, as the rest of the seq_buf API does until
> seq_buf_clear() or seq_buf_init().
>
> The current callers do not reach this, e.g. trace_syscalls only calls
> trace_seq_pop() when the trace_seq it pops from has not overflowed, and
> kernel/bpf/diagnostics.c sets the length from strnlen() before popping.
>
> Add tests for the pop corner cases.
>
> Tests passed under qemu on ARCH=x86_64 with GCC 16.2.0 and CONFIG_KASAN=y,
> and on big-endian ARCH=s390 with GCC s390x-linux-gnu 16.1.0.
>
> Fixes: 32e0f607ac6a2 ("tracing: Add trace_seq_pop() and seq_buf_pop()")
> Assisted-by: LLM
> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> Signed-off-by: Kees Cook <kees@kernel.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260930235231.out.387-kees@kernel.org?part=2
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-30 23:59 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260930235231.out.387-kees@kernel.org>
2026-09-30 23:52 ` [PATCH v3 02/11] seq_buf: Do not pop from an overflowed seq_buf Kees Cook
2026-09-30 23:59 ` sashiko-bot
2026-09-30 23:52 ` [PATCH v3 06/11] seq_buf: Add seq_buf_terminate() Kees Cook
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox