Linux Hardening
 help / color / mirror / Atom feed
* [PATCH 0/7] seq_buf: Add seq_buf_strlen()
@ 2026-09-17  0:23 Kees Cook
  2026-09-17  0:23 ` [PATCH 1/7] seq_buf: Do not pop from an overflowed seq_buf Kees Cook
                   ` (7 more replies)
  0 siblings, 8 replies; 14+ messages in thread
From: Kees Cook @ 2026-09-17  0:23 UTC (permalink / raw)
  To: Bill Wendling; +Cc: Kees Cook, Steven Rostedt, Andy Shevchenko, linux-hardening

Hi,

While working on seq_buf conversions, we found there was a need for
seq_buf_strlen() (since calling strlen(seq_buf_str()) would be a waste
of time: seq_buf already knows the length). And while implementing that,
I found a bunch of other related things that needed fixing. This is that
series, with tests for each fix.

-Kees

Kees Cook (7):
  seq_buf: Do not pop from an overflowed seq_buf
  seq_buf: Copy what fits when seq_buf_puts() and seq_buf_putmem()
    overflow
  seq_buf: Clear what a writer did not claim when a seq_buf overflows
  seq_buf: Add seq_buf_strlen()
  seq_buf: Use seq_buf_strlen() for the string end in
    seq_buf_do_printk()
  powerpc/papr_scm: Return the string length from the sysfs show
    functions
  nvdimm: ndtest: Return the string length from flags_show()

 include/linux/seq_buf.h                   |  69 ++++-
 include/linux/trace_seq.h                 |   4 +-
 arch/powerpc/platforms/pseries/papr_scm.c |   4 +-
 lib/seq_buf.c                             |  25 +-
 lib/tests/seq_buf_kunit.c                 | 290 +++++++++++++++++++++-
 tools/testing/nvdimm/test/ndtest.c        |   2 +-
 6 files changed, 377 insertions(+), 17 deletions(-)

-- 
2.34.1


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

* [PATCH 1/7] seq_buf: Do not pop from an overflowed seq_buf
  2026-09-17  0:23 [PATCH 0/7] seq_buf: Add seq_buf_strlen() Kees Cook
@ 2026-09-17  0:23 ` Kees Cook
  2026-09-17  0:23 ` [PATCH 2/7] seq_buf: Copy what fits when seq_buf_puts() and seq_buf_putmem() overflow Kees Cook
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Kees Cook @ 2026-09-17  0:23 UTC (permalink / raw)
  To: Bill Wendling
  Cc: Kees Cook, Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Mickaël Salaün, Günther Noack, Andy Shevchenko,
	Petr Mladek, Matthew Wilcox (Oracle), Shuvam Pandey, David Gow,
	Andrew Morton, linux-trace-kernel, linux-security-module, bpf,
	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 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
Signed-off-by: Kees Cook <kees@kernel.org>
---
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: "Mickaël Salaün" <mic@digikod.net>
Cc: "Günther Noack" <gnoack@google.com>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Petr Mladek <pmladek@suse.com>
Cc: "Matthew Wilcox (Oracle)" <willy@infradead.org>
Cc: Shuvam Pandey <shuvampandey1@gmail.com>
Cc: David Gow <david@davidgow.net>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: <linux-trace-kernel@vger.kernel.org>
Cc: <linux-security-module@vger.kernel.org>
Cc: <bpf@vger.kernel.org>
---
 include/linux/seq_buf.h   |  4 ++--
 include/linux/trace_seq.h |  4 +++-
 lib/tests/seq_buf_kunit.c | 42 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 47 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..9aaa141d0965 100644
--- a/include/linux/trace_seq.h
+++ b/include/linux/trace_seq.h
@@ -86,7 +86,9 @@ 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 @s is empty or its buffer has
+ * overflowed. Note that a @s that is full, but has not overflowed, still
+ * pops.
  */
 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 eb466386bbef..9048037f6ba0 100644
--- a/lib/tests/seq_buf_kunit.c
+++ b/lib/tests/seq_buf_kunit.c
@@ -113,6 +113,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);
@@ -223,6 +264,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] 14+ messages in thread

* [PATCH 2/7] seq_buf: Copy what fits when seq_buf_puts() and seq_buf_putmem() overflow
  2026-09-17  0:23 [PATCH 0/7] seq_buf: Add seq_buf_strlen() Kees Cook
  2026-09-17  0:23 ` [PATCH 1/7] seq_buf: Do not pop from an overflowed seq_buf Kees Cook
@ 2026-09-17  0:23 ` Kees Cook
  2026-09-17  0:23 ` [PATCH 3/7] seq_buf: Clear what a writer did not claim when a seq_buf overflows Kees Cook
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Kees Cook @ 2026-09-17  0:23 UTC (permalink / raw)
  To: Bill Wendling
  Cc: Kees Cook, Andrew Morton, Steven Rostedt, Andy Shevchenko,
	Petr Mladek, Matthew Wilcox (Oracle), Shuvam Pandey, David Gow,
	linux-hardening

When seq_buf_puts() or seq_buf_putmem() is given more than fits, it
copies nothing and only marks the seq_buf as overflowed. If seq_buf_str()
is used, it will terminate the buffer in its last byte, so every byte
between the end of the data and the end of the buffer becomes part of
the string, though the seq_buf never wrote them.

seq_buf_printf() does not have this problem, because vsnprintf() writes
as much of the output as fits, followed by a NUL. Repeat this behavior
in seq_buf_puts(), using strscpy(), and in seq_buf_putmem(), which also
covers seq_buf_putmem_hex(). seq_buf_putc() needs no change, as it can
only overflow when the buffer is already full.

Each writer now records the buffer as full once it has copied what fits,
so that what it wrote can be told apart from bytes nothing touched.

Update seq_buf_putmem_hex_overflow_test, which expected a hex group that
did not fit whole to be left out entirely, and add tests that overflow
seq_buf_puts() and seq_buf_putmem_hex() with stale bytes in the buffer.

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.

Assisted-by: LLM
Signed-off-by: Kees Cook <kees@kernel.org>
---
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Petr Mladek <pmladek@suse.com>
Cc: "Matthew Wilcox (Oracle)" <willy@infradead.org>
Cc: Shuvam Pandey <shuvampandey1@gmail.com>
Cc: David Gow <david@davidgow.net>
---
 include/linux/seq_buf.h   |  6 +++++
 lib/seq_buf.c             | 16 +++++++++++--
 lib/tests/seq_buf_kunit.c | 47 +++++++++++++++++++++++++++++++++++++--
 3 files changed, 65 insertions(+), 4 deletions(-)

diff --git a/include/linux/seq_buf.h b/include/linux/seq_buf.h
index f5a350347bc5..77e76e283370 100644
--- a/include/linux/seq_buf.h
+++ b/include/linux/seq_buf.h
@@ -55,6 +55,12 @@ seq_buf_has_overflowed(struct seq_buf *s)
 	return s->len > s->size;
 }
 
+/*
+ * Mark @s as overflowed, which discards the length of what it holds. The
+ * bytes up to its last one are the string from then on, as that is where
+ * seq_buf_str() terminates it, so a caller that could not fill the buffer
+ * has to NUL them itself before calling this.
+ */
 static inline void
 seq_buf_set_overflow(struct seq_buf *s)
 {
diff --git a/lib/seq_buf.c b/lib/seq_buf.c
index a92093f346da..4d67fe25e916 100644
--- a/lib/seq_buf.c
+++ b/lib/seq_buf.c
@@ -175,7 +175,9 @@ int seq_buf_bprintf(struct seq_buf *s, const char *fmt, const u32 *binary)
  * @s: seq_buf descriptor
  * @str: simple string to record
  *
- * Copy a simple string into the sequence buffer.
+ * Copy a simple string into the sequence buffer. If @str does not fit,
+ * as much of it as fits is copied, followed by a null byte, as
+ * seq_buf_printf() does.
  *
  * Returns: zero on success, -1 on overflow.
  */
@@ -194,6 +196,11 @@ int seq_buf_puts(struct seq_buf *s, const char *str)
 		s->len += len - 1;
 		return 0;
 	}
+	/* Copy what fits, so the buffer never holds stale bytes */
+	if (s->len < s->size) {
+		strscpy(s->buffer + s->len, str, s->size - s->len);
+		s->len = s->size;
+	}
 	seq_buf_set_overflow(s);
 	return -1;
 }
@@ -229,7 +236,7 @@ EXPORT_SYMBOL_GPL(seq_buf_putc);
  *
  * There may be cases where raw memory needs to be written into the
  * buffer and a strcpy() would not work. Using this function allows
- * for such cases.
+ * for such cases. If @mem does not fit, as much of it as fits is copied.
  *
  * Returns: zero on success, -1 on overflow.
  */
@@ -242,6 +249,11 @@ int seq_buf_putmem(struct seq_buf *s, const void *mem, unsigned int len)
 		s->len += len;
 		return 0;
 	}
+	/* Copy what fits, so the buffer never holds stale bytes */
+	if (s->len < s->size) {
+		memcpy(s->buffer + s->len, mem, s->size - s->len);
+		s->len = s->size;
+	}
 	seq_buf_set_overflow(s);
 	return -1;
 }
diff --git a/lib/tests/seq_buf_kunit.c b/lib/tests/seq_buf_kunit.c
index 9048037f6ba0..3f3b076dd6fa 100644
--- a/lib/tests/seq_buf_kunit.c
+++ b/lib/tests/seq_buf_kunit.c
@@ -246,9 +246,9 @@ static void seq_buf_putmem_hex_overflow_test(struct kunit *test)
 	DECLARE_SEQ_BUF(s, 20);
 	const u8 data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
 #ifdef __BIG_ENDIAN
-	const char *expected = "0001020304050607 ";
+	const char *expected = "0001020304050607 08";
 #else
-	const char *expected = "0706050403020100 ";
+	const char *expected = "0706050403020100 09";
 #endif
 
 	KUNIT_EXPECT_EQ(test, seq_buf_putmem_hex(&s, data, sizeof(data)), -1);
@@ -257,6 +257,47 @@ static void seq_buf_putmem_hex_overflow_test(struct kunit *test)
 	KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), expected);
 }
 
+static void seq_buf_puts_partial_overflow_test(struct kunit *test)
+{
+	DECLARE_SEQ_BUF(s, 16);
+	struct seq_buf t;
+	char buf[8];
+
+	/* As much of the string as fits is written, like seq_buf_printf(). */
+	seq_buf_puts(&s, "hello");
+	KUNIT_EXPECT_EQ(test, seq_buf_puts(&s, " world, again"), -1);
+	KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s));
+	KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 16);
+	KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hello world, ag");
+
+	/* Stale bytes after the data must not show up in the string. */
+	memset(buf, 'X', sizeof(buf));
+	seq_buf_init(&t, buf, sizeof(buf));
+	seq_buf_putc(&t, 'a');
+	KUNIT_EXPECT_EQ(test, seq_buf_puts(&t, "bcdefghij"), -1);
+	KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&t));
+	KUNIT_EXPECT_STREQ(test, seq_buf_str(&t), "abcdefg");
+}
+
+static void seq_buf_putmem_hex_partial_overflow_test(struct kunit *test)
+{
+	const u8 data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+#ifdef __BIG_ENDIAN
+	const char *expected = "0001020304050607 08";
+#else
+	const char *expected = "0706050403020100 09";
+#endif
+	struct seq_buf s;
+	char buf[20];
+
+	/* Stale bytes after the data must not show up in the string. */
+	memset(buf, 'X', sizeof(buf));
+	seq_buf_init(&s, buf, sizeof(buf));
+	KUNIT_EXPECT_EQ(test, seq_buf_putmem_hex(&s, data, sizeof(data)), -1);
+	KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s));
+	KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), expected);
+}
+
 static struct kunit_case seq_buf_test_cases[] = {
 	KUNIT_CASE(seq_buf_init_test),
 	KUNIT_CASE(seq_buf_declare_test),
@@ -270,6 +311,8 @@ static struct kunit_case seq_buf_test_cases[] = {
 	KUNIT_CASE(seq_buf_get_buf_commit_test),
 	KUNIT_CASE(seq_buf_putmem_hex_test),
 	KUNIT_CASE(seq_buf_putmem_hex_overflow_test),
+	KUNIT_CASE(seq_buf_puts_partial_overflow_test),
+	KUNIT_CASE(seq_buf_putmem_hex_partial_overflow_test),
 	{}
 };
 
-- 
2.34.1


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

* [PATCH 3/7] seq_buf: Clear what a writer did not claim when a seq_buf overflows
  2026-09-17  0:23 [PATCH 0/7] seq_buf: Add seq_buf_strlen() Kees Cook
  2026-09-17  0:23 ` [PATCH 1/7] seq_buf: Do not pop from an overflowed seq_buf Kees Cook
  2026-09-17  0:23 ` [PATCH 2/7] seq_buf: Copy what fits when seq_buf_puts() and seq_buf_putmem() overflow Kees Cook
@ 2026-09-17  0:23 ` Kees Cook
  2026-09-17  0:23 ` [PATCH 4/7] seq_buf: Add seq_buf_strlen() Kees Cook
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Kees Cook @ 2026-09-17  0:23 UTC (permalink / raw)
  To: Bill Wendling
  Cc: Kees Cook, Andrew Morton, Steven Rostedt, Shuvam Pandey,
	David Gow, Jiri Kosina, Petr Mladek, Andy Shevchenko,
	linux-hardening

Using seq_buf_set_overflow() would leave the bytes between "len"
and "size" untouched, so if seq_buf_str() is used on an overflowed
seq_buf, those bytes may be exposed. For any paths that don't claim
partially written bytes, by setting "len = size" before calling
seq_buf_set_overflow(), wipe the unclaimed bytes. The seq_buf_puts()
and related APIs already claim those bytes now, so only the unclaimed
cases remain. A specific example of this was seq_buf_path() which uses
d_path() and would write to the tail before discovering it was out
of space, and would correctly mark a seq_buf as overflowed, but the
path fragment would be left over.

Clear from len to the end of the buffer in seq_buf_set_overflow(), which
every overflow goes through, including seq_buf_commit() with a negative
count.

Add a test that fills a seq_buf, leaves it too little room for a path, and
checks that nothing of the path is left in the buffer. The tests run before
anything writable is mounted, so it takes its file from shmem.

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.

Assisted-by: LLM
Signed-off-by: Kees Cook <kees@kernel.org>
---
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Shuvam Pandey <shuvampandey1@gmail.com>
Cc: David Gow <david@davidgow.net>
Cc: Jiri Kosina <jikos@kernel.org>
Cc: Petr Mladek <pmladek@suse.cz>
---
 include/linux/seq_buf.h   |  8 +++++--
 lib/seq_buf.c             |  4 ++++
 lib/tests/seq_buf_kunit.c | 44 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 54 insertions(+), 2 deletions(-)

diff --git a/include/linux/seq_buf.h b/include/linux/seq_buf.h
index 77e76e283370..0c0a0db04b09 100644
--- a/include/linux/seq_buf.h
+++ b/include/linux/seq_buf.h
@@ -5,6 +5,7 @@
 #include <linux/bug.h>
 #include <linux/minmax.h>
 #include <linux/seq_file.h>
+#include <linux/string.h>
 #include <linux/types.h>
 
 /*
@@ -58,12 +59,15 @@ seq_buf_has_overflowed(struct seq_buf *s)
 /*
  * Mark @s as overflowed, which discards the length of what it holds. The
  * bytes up to its last one are the string from then on, as that is where
- * seq_buf_str() terminates it, so a caller that could not fill the buffer
- * has to NUL them itself before calling this.
+ * seq_buf_str() terminates it, so clear whatever was not written: a writer
+ * sets len to how much it filled, and anything past that was never adopted.
  */
 static inline void
 seq_buf_set_overflow(struct seq_buf *s)
 {
+	if (s->len < s->size)
+		memset(s->buffer + s->len, 0, s->size - s->len);
+
 	s->len = s->size + 1;
 }
 
diff --git a/lib/seq_buf.c b/lib/seq_buf.c
index 4d67fe25e916..65806d5e4bb4 100644
--- a/lib/seq_buf.c
+++ b/lib/seq_buf.c
@@ -76,6 +76,8 @@ int seq_buf_vprintf(struct seq_buf *s, const char *fmt, va_list args)
 			s->len += len;
 			return 0;
 		}
+		/* vsnprintf() wrote as much as fits, so none of it is stale */
+		s->len = s->size;
 	}
 	seq_buf_set_overflow(s);
 	return -1;
@@ -164,6 +166,8 @@ int seq_buf_bprintf(struct seq_buf *s, const char *fmt, const u32 *binary)
 			s->len += ret;
 			return 0;
 		}
+		/* bstr_printf() wrote as much as fits, so none of it is stale */
+		s->len = s->size;
 	}
 	seq_buf_set_overflow(s);
 	return -1;
diff --git a/lib/tests/seq_buf_kunit.c b/lib/tests/seq_buf_kunit.c
index 3f3b076dd6fa..94fe928d537e 100644
--- a/lib/tests/seq_buf_kunit.c
+++ b/lib/tests/seq_buf_kunit.c
@@ -6,7 +6,9 @@
  */
 
 #include <kunit/test.h>
+#include <linux/fs.h>
 #include <linux/seq_buf.h>
+#include <linux/shmem_fs.h>
 
 static void seq_buf_init_test(struct kunit *test)
 {
@@ -298,6 +300,47 @@ static void seq_buf_putmem_hex_partial_overflow_test(struct kunit *test)
 	KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), expected);
 }
 
+/* Long enough that it cannot fit in the room the test leaves for it. */
+#define SEQ_BUF_TEST_PATH	"/seq_buf_kunit_path_name"
+
+static void seq_buf_path_overflow_test(struct kunit *test)
+{
+	DECLARE_SEQ_BUF(s, 32);
+	const char *expected = "keep:xxxxxxxxxxxxxxxxxxxxxxx";
+	struct file *file;
+	size_t len;
+	int i;
+
+	/*
+	 * The tests run before anything writable is mounted, so take the file
+	 * whose path gets printed from shmem, which needs no mount of its own.
+	 */
+	file = shmem_file_setup(SEQ_BUF_TEST_PATH, 0, EMPTY_VMA_FLAGS);
+	if (IS_ERR(file))
+		kunit_skip(test, "cannot create a file to print the path of");
+
+	/* Leave less room than the path needs, so d_path() cannot fit it. */
+	seq_buf_puts(&s, "keep:");
+	len = seq_buf_used(&s);
+	for (i = len; i < 28; i++)
+		seq_buf_putc(&s, 'x');
+
+	KUNIT_EXPECT_EQ(test, seq_buf_path(&s, &file->f_path, "\n"), -1);
+	fput(file);
+
+	KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s));
+
+	/*
+	 * d_path() keeps as much of the path as fits when it does not fit
+	 * whole, and seq_buf_str() would hand out that fragment, as it ends
+	 * the string at the last byte of an overflowed buffer.
+	 */
+	for (i = 28; i < 32; i++)
+		KUNIT_EXPECT_EQ_MSG(test, s.buffer[i], '\0',
+				    "byte %d past the data is not cleared", i);
+	KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), expected);
+}
+
 static struct kunit_case seq_buf_test_cases[] = {
 	KUNIT_CASE(seq_buf_init_test),
 	KUNIT_CASE(seq_buf_declare_test),
@@ -313,6 +356,7 @@ static struct kunit_case seq_buf_test_cases[] = {
 	KUNIT_CASE(seq_buf_putmem_hex_overflow_test),
 	KUNIT_CASE(seq_buf_puts_partial_overflow_test),
 	KUNIT_CASE(seq_buf_putmem_hex_partial_overflow_test),
+	KUNIT_CASE(seq_buf_path_overflow_test),
 	{}
 };
 
-- 
2.34.1


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

* [PATCH 4/7] seq_buf: Add seq_buf_strlen()
  2026-09-17  0:23 [PATCH 0/7] seq_buf: Add seq_buf_strlen() Kees Cook
                   ` (2 preceding siblings ...)
  2026-09-17  0:23 ` [PATCH 3/7] seq_buf: Clear what a writer did not claim when a seq_buf overflows Kees Cook
@ 2026-09-17  0:23 ` Kees Cook
  2026-09-17  0:23 ` [PATCH 5/7] seq_buf: Use seq_buf_strlen() for the string end in seq_buf_do_printk() Kees Cook
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Kees Cook @ 2026-09-17  0:23 UTC (permalink / raw)
  To: Bill Wendling
  Cc: Kees Cook, Steven Rostedt, Andy Shevchenko, Petr Mladek,
	Matthew Wilcox (Oracle), Shuvam Pandey, David Gow, Andrew Morton,
	linux-hardening

seq_buf_used() is not the length of the string in a seq_buf. Once the
buffer is full or has overflowed it returns the buffer size, which
counts the byte that seq_buf_str() replaces with the NUL, so a caller
that needs the string and its length has to call seq_buf_str() and then
walk the string with strlen().

Move the termination out of seq_buf_str() into a helper that returns
where it put the NUL, and add seq_buf_strlen(), which terminates the
buffer in the same way and returns that offset.

Add tests comparing seq_buf_strlen() against strlen() of seq_buf_str()
for empty, appended, truncated, exactly full, and overflowed buffers,
and checking that seq_buf_strlen() alone terminates a full buffer.

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.

Assisted-by: LLM
Signed-off-by: Kees Cook <kees@kernel.org>
---
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Petr Mladek <pmladek@suse.com>
Cc: "Matthew Wilcox (Oracle)" <willy@infradead.org>
Cc: Shuvam Pandey <shuvampandey1@gmail.com>
Cc: David Gow <david@davidgow.net>
Cc: Andrew Morton <akpm@linux-foundation.org>
---
 include/linux/seq_buf.h   | 55 ++++++++++++++++++++---
 lib/tests/seq_buf_kunit.c | 94 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 144 insertions(+), 5 deletions(-)

diff --git a/include/linux/seq_buf.h b/include/linux/seq_buf.h
index 0c0a0db04b09..45e7a6b4ca6f 100644
--- a/include/linux/seq_buf.h
+++ b/include/linux/seq_buf.h
@@ -89,6 +89,27 @@ static inline unsigned int seq_buf_used(struct seq_buf *s)
 	return min(s->len, s->size);
 }
 
+/*
+ * NUL-terminate the buffer in @s: directly after the data when there is
+ * room for it, otherwise in the last byte of the buffer. @s->size must not
+ * be zero.
+ *
+ * Returns: the offset of the NUL.
+ */
+static inline size_t __seq_buf_terminate(struct seq_buf *s)
+{
+	size_t end;
+
+	if (seq_buf_buffer_left(s))
+		end = s->len;
+	else
+		end = s->size - 1;
+
+	s->buffer[end] = 0;
+
+	return end;
+}
+
 /**
  * seq_buf_str - get NUL-terminated C string from seq_buf
  * @s: the seq_buf handle
@@ -98,7 +119,9 @@ static inline unsigned int seq_buf_used(struct seq_buf *s)
  *
  * Note, if this is called when the buffer has overflowed, then
  * the last byte of the buffer is zeroed, and the len will still
- * point passed it.
+ * point passed it. The same happens when the buffer is exactly
+ * full: the NUL takes the place of the last byte written, which is
+ * lost, though seq_buf_used() still counts it.
  *
  * After this function is called, s->buffer is safe to use
  * in string operations.
@@ -110,14 +133,36 @@ static inline const char *seq_buf_str(struct seq_buf *s)
 	if (WARN_ON(s->size == 0))
 		return "";
 
-	if (seq_buf_buffer_left(s))
-		s->buffer[s->len] = 0;
-	else
-		s->buffer[s->size - 1] = 0;
+	__seq_buf_terminate(s);
 
 	return s->buffer;
 }
 
+/**
+ * seq_buf_strlen - get the length of the NUL-terminated C string in seq_buf
+ * @s: the seq_buf handle
+ *
+ * This makes sure that the buffer in @s is NUL-terminated, exactly as
+ * seq_buf_str() does, and returns the length of the resulting string
+ * without walking it. Unlike seq_buf_used(), this does not count the byte
+ * given up to the NUL when the buffer is full or has overflowed. When the
+ * buffer is exactly full, that byte is the last one written, and calling
+ * either function loses it.
+ *
+ * After this function is called, s->buffer is safe to use
+ * in string operations.
+ *
+ * Returns: the offset of the NUL in @s->buffer, which is the length of the
+ * string unless the data written to @s itself contains a NUL.
+ */
+static inline size_t seq_buf_strlen(struct seq_buf *s)
+{
+	if (WARN_ON(s->size == 0))
+		return 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/lib/tests/seq_buf_kunit.c b/lib/tests/seq_buf_kunit.c
index 94fe928d537e..2b8cacf000cb 100644
--- a/lib/tests/seq_buf_kunit.c
+++ b/lib/tests/seq_buf_kunit.c
@@ -341,6 +341,96 @@ static void seq_buf_path_overflow_test(struct kunit *test)
 	KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), expected);
 }
 
+static void seq_buf_strlen_test(struct kunit *test)
+{
+	DECLARE_SEQ_BUF(s, 16);
+
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 0);
+	KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "");
+
+	seq_buf_puts(&s, "hello");
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 5);
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s)));
+
+	seq_buf_printf(&s, " %s", "world");
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 11);
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s)));
+}
+
+static void seq_buf_strlen_printf_overflow_test(struct kunit *test)
+{
+	DECLARE_SEQ_BUF(s, 16);
+	DECLARE_SEQ_BUF(t, 8);
+
+	seq_buf_printf(&s, "%s", "1234567890abcdefghij");
+	KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s));
+	KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 16);
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 15);
+	KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "1234567890abcde");
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s)));
+
+	/* Output one byte too long for the NUL. */
+	seq_buf_printf(&t, "%s", "12345678");
+	KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&t));
+	KUNIT_EXPECT_EQ(test, seq_buf_used(&t), 8);
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&t), 7);
+	KUNIT_EXPECT_STREQ(test, seq_buf_str(&t), "1234567");
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&t), strlen(seq_buf_str(&t)));
+}
+
+static void seq_buf_strlen_full_test(struct kunit *test)
+{
+	DECLARE_SEQ_BUF(s, 4);
+	DECLARE_SEQ_BUF(t, 8);
+	char *buf;
+	size_t len;
+
+	/* Filled exactly, with no room left for a NUL, but not overflowed. */
+	seq_buf_putc(&s, 'a');
+	seq_buf_putc(&s, 'b');
+	seq_buf_putc(&s, 'c');
+	seq_buf_putc(&s, 'd');
+	KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(&s));
+	KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 4);
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 3);
+	/* seq_buf_strlen() terminates the buffer by itself. */
+	KUNIT_EXPECT_EQ(test, s.buffer[3], '\0');
+	KUNIT_EXPECT_EQ(test, strnlen(s.buffer, s.size), 3);
+	KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "abc");
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s)));
+
+	/* A printf into a full buffer writes nothing. */
+	KUNIT_EXPECT_EQ(test, seq_buf_printf(&s, "%s", "x"), -1);
+	KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s));
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 3);
+	KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "abc");
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s)));
+
+	len = seq_buf_get_buf(&t, &buf);
+	KUNIT_ASSERT_EQ(test, len, 8);
+	memset(buf, 'z', len);
+	seq_buf_commit(&t, len);
+	KUNIT_EXPECT_FALSE(test, seq_buf_has_overflowed(&t));
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&t), 7);
+	KUNIT_EXPECT_EQ(test, t.buffer[7], '\0');
+	KUNIT_EXPECT_EQ(test, strnlen(t.buffer, t.size), 7);
+	KUNIT_EXPECT_STREQ(test, seq_buf_str(&t), "zzzzzzz");
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&t), strlen(seq_buf_str(&t)));
+}
+
+static void seq_buf_strlen_puts_overflow_test(struct kunit *test)
+{
+	DECLARE_SEQ_BUF(s, 16);
+
+	/* A puts that does not fit copies as much as fits. */
+	seq_buf_puts(&s, "hello");
+	KUNIT_EXPECT_EQ(test, seq_buf_puts(&s, " this does not fit"), -1);
+	KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s));
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 15);
+	KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "hello this does");
+	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s)));
+}
+
 static struct kunit_case seq_buf_test_cases[] = {
 	KUNIT_CASE(seq_buf_init_test),
 	KUNIT_CASE(seq_buf_declare_test),
@@ -357,6 +447,10 @@ static struct kunit_case seq_buf_test_cases[] = {
 	KUNIT_CASE(seq_buf_puts_partial_overflow_test),
 	KUNIT_CASE(seq_buf_putmem_hex_partial_overflow_test),
 	KUNIT_CASE(seq_buf_path_overflow_test),
+	KUNIT_CASE(seq_buf_strlen_test),
+	KUNIT_CASE(seq_buf_strlen_printf_overflow_test),
+	KUNIT_CASE(seq_buf_strlen_full_test),
+	KUNIT_CASE(seq_buf_strlen_puts_overflow_test),
 	{}
 };
 
-- 
2.34.1


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

* [PATCH 5/7] seq_buf: Use seq_buf_strlen() for the string end in seq_buf_do_printk()
  2026-09-17  0:23 [PATCH 0/7] seq_buf: Add seq_buf_strlen() Kees Cook
                   ` (3 preceding siblings ...)
  2026-09-17  0:23 ` [PATCH 4/7] seq_buf: Add seq_buf_strlen() Kees Cook
@ 2026-09-17  0:23 ` Kees Cook
  2026-09-17  0:23 ` [PATCH 6/7] powerpc/papr_scm: Return the string length from the sysfs show functions Kees Cook
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 14+ messages in thread
From: Kees Cook @ 2026-09-17  0:23 UTC (permalink / raw)
  To: Bill Wendling
  Cc: Kees Cook, Andrew Morton, Steven Rostedt, Shuvam Pandey,
	David Gow, Petr Mladek, Sergey Senozhatsky, Andy Shevchenko,
	linux-hardening

seq_buf_do_printk() prints whatever follows the last line feed when
"start" is still inside the buffer, and for an overflowed seq_buf,
this may end up pointing at the NUL, so an extra blank line would be
emitted.

Take the end from seq_buf_strlen() instead, which is where the string
ends whether the buffer overflowed, is exactly full, or has room left.

The only caller is the memory cgroup OOM report, so this only ever
added a blank line to a report whose stats did not fit.

Add a test that registers a console to count the records that
seq_buf_do_printk() emits, with a seq_buf filled so that its string
ends in a line feed. It counts only the records carrying the test's
marker and the records holding nothing but a line feed, so that
unrelated kernel messages do not disturb it.

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.

Assisted-by: LLM
Signed-off-by: Kees Cook <kees@kernel.org>
---
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Shuvam Pandey <shuvampandey1@gmail.com>
Cc: David Gow <david@davidgow.net>
Cc: Petr Mladek <pmladek@suse.com>
Cc: Sergey Senozhatsky <senozhatsky@chromium.org>
---
 lib/seq_buf.c             |  5 ++--
 lib/tests/seq_buf_kunit.c | 63 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 66 insertions(+), 2 deletions(-)

diff --git a/lib/seq_buf.c b/lib/seq_buf.c
index 65806d5e4bb4..9977c2a6a315 100644
--- a/lib/seq_buf.c
+++ b/lib/seq_buf.c
@@ -116,12 +116,13 @@ EXPORT_SYMBOL_GPL(seq_buf_printf);
  */
 void seq_buf_do_printk(struct seq_buf *s, const char *lvl)
 {
-	const char *start, *lf;
+	const char *start, *lf, *end;
 
 	if (s->size == 0 || s->len == 0)
 		return;
 
 	start = seq_buf_str(s);
+	end = s->buffer + seq_buf_strlen(s);
 	while ((lf = strchr(start, '\n'))) {
 		int len = lf - start + 1;
 
@@ -130,7 +131,7 @@ void seq_buf_do_printk(struct seq_buf *s, const char *lvl)
 	}
 
 	/* No trailing LF */
-	if (start < s->buffer + s->len)
+	if (start < end)
 		printk("%s%s\n", lvl, start);
 }
 EXPORT_SYMBOL_GPL(seq_buf_do_printk);
diff --git a/lib/tests/seq_buf_kunit.c b/lib/tests/seq_buf_kunit.c
index 2b8cacf000cb..43ca47ffdb7d 100644
--- a/lib/tests/seq_buf_kunit.c
+++ b/lib/tests/seq_buf_kunit.c
@@ -6,6 +6,7 @@
  */
 
 #include <kunit/test.h>
+#include <linux/console.h>
 #include <linux/fs.h>
 #include <linux/seq_buf.h>
 #include <linux/shmem_fs.h>
@@ -431,6 +432,67 @@ static void seq_buf_strlen_puts_overflow_test(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), strlen(seq_buf_str(&s)));
 }
 
+/*
+ * Counters for the console that seq_buf_do_printk_test() registers while it
+ * runs. Only records carrying the marker, and records holding nothing but a
+ * line feed, are counted, so unrelated kernel messages do not disturb them.
+ */
+#define SEQ_BUF_PRINTK_MARKER	"sbdpkx"
+
+static unsigned int seq_buf_printk_marked;
+static unsigned int seq_buf_printk_empty;
+
+static void seq_buf_printk_capture(struct console *con, const char *s,
+				   unsigned int count)
+{
+	const char *text = s;
+	const char *prefix;
+
+	/* Skip the "[   12.345678] " timestamp, when there is one. */
+	prefix = memchr(s, ']', count);
+	if (prefix && prefix + 2 <= s + count && prefix[1] == ' ')
+		text = prefix + 2;
+	count -= text - s;
+
+	if (count == 0 || (count == 1 && text[0] == '\n'))
+		seq_buf_printk_empty++;
+	else if (strnstr(text, SEQ_BUF_PRINTK_MARKER, count))
+		seq_buf_printk_marked++;
+}
+
+static void seq_buf_do_printk_test(struct kunit *test)
+{
+	static struct console capture = {
+		.name = "sbufcap",
+		.write = seq_buf_printk_capture,
+		.flags = CON_ENABLED,
+		.index = -1,
+	};
+	DECLARE_SEQ_BUF(s, 8);
+
+	/*
+	 * Fill the buffer exactly, so that the NUL takes the place of the
+	 * last byte and the string ends with the line feed before it.
+	 */
+	seq_buf_puts(&s, SEQ_BUF_PRINTK_MARKER);
+	seq_buf_putc(&s, '\n');
+	seq_buf_putc(&s, '!');
+	KUNIT_ASSERT_FALSE(test, seq_buf_has_overflowed(&s));
+	KUNIT_ASSERT_EQ(test, seq_buf_used(&s), 8);
+	KUNIT_ASSERT_EQ(test, seq_buf_strlen(&s), 7);
+
+	seq_buf_printk_marked = 0;
+	seq_buf_printk_empty = 0;
+
+	register_console(&capture);
+	seq_buf_do_printk(&s, KERN_INFO);
+	unregister_console(&capture);
+
+	/* The one line that was written, and nothing after it. */
+	KUNIT_EXPECT_EQ(test, seq_buf_printk_marked, 1);
+	KUNIT_EXPECT_EQ(test, seq_buf_printk_empty, 0);
+}
+
 static struct kunit_case seq_buf_test_cases[] = {
 	KUNIT_CASE(seq_buf_init_test),
 	KUNIT_CASE(seq_buf_declare_test),
@@ -451,6 +513,7 @@ static struct kunit_case seq_buf_test_cases[] = {
 	KUNIT_CASE(seq_buf_strlen_printf_overflow_test),
 	KUNIT_CASE(seq_buf_strlen_full_test),
 	KUNIT_CASE(seq_buf_strlen_puts_overflow_test),
+	KUNIT_CASE(seq_buf_do_printk_test),
 	{}
 };
 
-- 
2.34.1


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

* [PATCH 6/7] powerpc/papr_scm: Return the string length from the sysfs show functions
  2026-09-17  0:23 [PATCH 0/7] seq_buf: Add seq_buf_strlen() Kees Cook
                   ` (4 preceding siblings ...)
  2026-09-17  0:23 ` [PATCH 5/7] seq_buf: Use seq_buf_strlen() for the string end in seq_buf_do_printk() Kees Cook
@ 2026-09-17  0:23 ` Kees Cook
  2026-09-17 12:50   ` Andy Shevchenko
  2026-09-17  0:23 ` [PATCH 7/7] nvdimm: ndtest: Return the string length from flags_show() Kees Cook
  2026-09-17 12:51 ` [PATCH 0/7] seq_buf: Add seq_buf_strlen() Andy Shevchenko
  7 siblings, 1 reply; 14+ messages in thread
From: Kees Cook @ 2026-09-17  0:23 UTC (permalink / raw)
  To: Bill Wendling
  Cc: Kees Cook, Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
	Christophe Leroy (CS GROUP), Uwe Kleine-König, Thorsten Blum,
	Shivaprasad G Bhat, linuxppc-dev, Steven Rostedt, Andy Shevchenko,
	linux-hardening

perf_stats_show() and flags_show() build their output with a seq_buf
and return seq_buf_used(), which may include the trailing NUL byte
when the seq_buf has overflowed. Use seq_buf_strlen() instead.

Build tested ARCH=powerpc ppc64_defconfig with GCC powerpc64-linux-gnu
16.1.0:
arch/powerpc/platforms/pseries/papr_scm.o

Assisted-by: LLM
Signed-off-by: Kees Cook <kees@kernel.org>
---
Cc: Madhavan Srinivasan <maddy@linux.ibm.com>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Nicholas Piggin <npiggin@gmail.com>
Cc: "Christophe Leroy (CS GROUP)" <chleroy@kernel.org>
Cc: "Uwe Kleine-König" <u.kleine-koenig@baylibre.com>
Cc: Thorsten Blum <blum@kernel.org>
Cc: Shivaprasad G Bhat <sbhat@linux.ibm.com>
Cc: <linuxppc-dev@lists.ozlabs.org>
---
 arch/powerpc/platforms/pseries/papr_scm.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/powerpc/platforms/pseries/papr_scm.c b/arch/powerpc/platforms/pseries/papr_scm.c
index 75da96c08cdd..6c87c8b4de8e 100644
--- a/arch/powerpc/platforms/pseries/papr_scm.c
+++ b/arch/powerpc/platforms/pseries/papr_scm.c
@@ -1108,7 +1108,7 @@ static ssize_t perf_stats_show(struct device *dev,
 
 free_stats:
 	kfree(stats);
-	return rc ? rc : (ssize_t)seq_buf_used(&s);
+	return rc ? rc : (ssize_t)seq_buf_strlen(&s);
 }
 static DEVICE_ATTR_ADMIN_RO(perf_stats);
 
@@ -1150,7 +1150,7 @@ static ssize_t flags_show(struct device *dev,
 	if (seq_buf_used(&s))
 		seq_buf_printf(&s, "\n");
 
-	return seq_buf_used(&s);
+	return seq_buf_strlen(&s);
 }
 DEVICE_ATTR_RO(flags);
 
-- 
2.34.1


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

* [PATCH 7/7] nvdimm: ndtest: Return the string length from flags_show()
  2026-09-17  0:23 [PATCH 0/7] seq_buf: Add seq_buf_strlen() Kees Cook
                   ` (5 preceding siblings ...)
  2026-09-17  0:23 ` [PATCH 6/7] powerpc/papr_scm: Return the string length from the sysfs show functions Kees Cook
@ 2026-09-17  0:23 ` Kees Cook
  2026-09-18  3:24   ` Dave Jiang
  2026-09-17 12:51 ` [PATCH 0/7] seq_buf: Add seq_buf_strlen() Andy Shevchenko
  7 siblings, 1 reply; 14+ messages in thread
From: Kees Cook @ 2026-09-17  0:23 UTC (permalink / raw)
  To: Bill Wendling
  Cc: Kees Cook, Alison Schofield, Vishal Verma, Dave Jiang, Ira Weiny,
	Greg Kroah-Hartman, Uwe Kleine-König, Guangshuo Li, nvdimm,
	Steven Rostedt, Andy Shevchenko, linux-hardening

flags_show() build their output with a seq_buf and return seq_buf_used(),
which may include the trailing NUL byte when the seq_buf has
overflowed. Use seq_buf_strlen() instead.

The flag names are far shorter than the PAGE_SIZE buffer sysfs
provides, so this cannot overflow today.

Build tested ARCH=x86_64 with GCC 16.2.0, built out of tree with
make M=tools/testing/nvdimm:
tools/testing/nvdimm/test/ndtest.o

Assisted-by: LLM
Signed-off-by: Kees Cook <kees@kernel.org>
---
Cc: Alison Schofield <alison.schofield@intel.com>
Cc: Vishal Verma <vishal.l.verma@intel.com>
Cc: Dave Jiang <dave.jiang@intel.com>
Cc: Ira Weiny <iweiny@kernel.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Cc: Guangshuo Li <lgs201920130244@gmail.com>
Cc: <nvdimm@lists.linux.dev>
---
 tools/testing/nvdimm/test/ndtest.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/testing/nvdimm/test/ndtest.c b/tools/testing/nvdimm/test/ndtest.c
index 2051ad5d4882..f097f2992966 100644
--- a/tools/testing/nvdimm/test/ndtest.c
+++ b/tools/testing/nvdimm/test/ndtest.c
@@ -693,7 +693,7 @@ static ssize_t flags_show(struct device *dev,
 	if (seq_buf_used(&s))
 		seq_buf_printf(&s, "\n");
 
-	return seq_buf_used(&s);
+	return seq_buf_strlen(&s);
 }
 static DEVICE_ATTR_RO(flags);
 
-- 
2.34.1


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

* Re: [PATCH 6/7] powerpc/papr_scm: Return the string length from the sysfs show functions
  2026-09-17  0:23 ` [PATCH 6/7] powerpc/papr_scm: Return the string length from the sysfs show functions Kees Cook
@ 2026-09-17 12:50   ` Andy Shevchenko
  2026-09-17 21:19     ` Kees Cook
  0 siblings, 1 reply; 14+ messages in thread
From: Andy Shevchenko @ 2026-09-17 12:50 UTC (permalink / raw)
  To: Kees Cook
  Cc: Bill Wendling, Madhavan Srinivasan, Michael Ellerman,
	Nicholas Piggin, Christophe Leroy (CS GROUP),
	Uwe Kleine-König, Thorsten Blum, Shivaprasad G Bhat,
	linuxppc-dev, Steven Rostedt, linux-hardening

On Wed, Sep 16, 2026 at 05:23:18PM -0700, Kees Cook wrote:
> perf_stats_show() and flags_show() build their output with a seq_buf
> and return seq_buf_used(), which may include the trailing NUL byte
> when the seq_buf has overflowed. Use seq_buf_strlen() instead.
> 
> Build tested ARCH=powerpc ppc64_defconfig with GCC powerpc64-linux-gnu
> 16.1.0:
> arch/powerpc/platforms/pseries/papr_scm.o

...

>  free_stats:
>  	kfree(stats);
> -	return rc ? rc : (ssize_t)seq_buf_used(&s);
> +	return rc ? rc : (ssize_t)seq_buf_strlen(&s);
>  }

On a brief look it's a single ternary like this in the file, perhaps while at
it move to Elvis op?

	return rc ?: (ssize_t)seq_buf_strlen(&s);

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH 0/7] seq_buf: Add seq_buf_strlen()
  2026-09-17  0:23 [PATCH 0/7] seq_buf: Add seq_buf_strlen() Kees Cook
                   ` (6 preceding siblings ...)
  2026-09-17  0:23 ` [PATCH 7/7] nvdimm: ndtest: Return the string length from flags_show() Kees Cook
@ 2026-09-17 12:51 ` Andy Shevchenko
  2026-09-17 21:21   ` Kees Cook
  7 siblings, 1 reply; 14+ messages in thread
From: Andy Shevchenko @ 2026-09-17 12:51 UTC (permalink / raw)
  To: Kees Cook; +Cc: Bill Wendling, Steven Rostedt, linux-hardening

On Wed, Sep 16, 2026 at 05:23:12PM -0700, Kees Cook wrote:

> While working on seq_buf conversions, we found there was a need for
> seq_buf_strlen() (since calling strlen(seq_buf_str()) would be a waste
> of time: seq_buf already knows the length). And while implementing that,
> I found a bunch of other related things that needed fixing. This is that
> series, with tests for each fix.

I like the series, especially the trick on how to capture printk() messages.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH 6/7] powerpc/papr_scm: Return the string length from the sysfs show functions
  2026-09-17 12:50   ` Andy Shevchenko
@ 2026-09-17 21:19     ` Kees Cook
  0 siblings, 0 replies; 14+ messages in thread
From: Kees Cook @ 2026-09-17 21:19 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Bill Wendling, Madhavan Srinivasan, Michael Ellerman,
	Nicholas Piggin, Christophe Leroy (CS GROUP),
	Uwe Kleine-König, Thorsten Blum, Shivaprasad G Bhat,
	linuxppc-dev, Steven Rostedt, linux-hardening

On Thu, Sep 17, 2026 at 03:50:23PM +0300, Andy Shevchenko wrote:
> On Wed, Sep 16, 2026 at 05:23:18PM -0700, Kees Cook wrote:
> > perf_stats_show() and flags_show() build their output with a seq_buf
> > and return seq_buf_used(), which may include the trailing NUL byte
> > when the seq_buf has overflowed. Use seq_buf_strlen() instead.
> > 
> > Build tested ARCH=powerpc ppc64_defconfig with GCC powerpc64-linux-gnu
> > 16.1.0:
> > arch/powerpc/platforms/pseries/papr_scm.o
> 
> ...
> 
> >  free_stats:
> >  	kfree(stats);
> > -	return rc ? rc : (ssize_t)seq_buf_used(&s);
> > +	return rc ? rc : (ssize_t)seq_buf_strlen(&s);
> >  }
> 
> On a brief look it's a single ternary like this in the file, perhaps while at
> it move to Elvis op?
> 
> 	return rc ?: (ssize_t)seq_buf_strlen(&s);

Sure! I've updated this for v2.

-- 
Kees Cook

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

* Re: [PATCH 0/7] seq_buf: Add seq_buf_strlen()
  2026-09-17 12:51 ` [PATCH 0/7] seq_buf: Add seq_buf_strlen() Andy Shevchenko
@ 2026-09-17 21:21   ` Kees Cook
  2026-09-18  7:45     ` Steven Rostedt
  0 siblings, 1 reply; 14+ messages in thread
From: Kees Cook @ 2026-09-17 21:21 UTC (permalink / raw)
  To: Andy Shevchenko; +Cc: Bill Wendling, Steven Rostedt, linux-hardening

On Thu, Sep 17, 2026 at 03:51:41PM +0300, Andy Shevchenko wrote:
> On Wed, Sep 16, 2026 at 05:23:12PM -0700, Kees Cook wrote:
> 
> > While working on seq_buf conversions, we found there was a need for
> > seq_buf_strlen() (since calling strlen(seq_buf_str()) would be a waste
> > of time: seq_buf already knows the length). And while implementing that,
> > I found a bunch of other related things that needed fixing. This is that
> > series, with tests for each fix.
> 
> I like the series, especially the trick on how to capture printk() messages.
> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>

Thanks! I'm going to spin a v2 with Bill's seq_buf_init_append()[1] (though
I think I want to call it seq_buf_init_existing()? I'm open to ideas
here...)

-Kees

[1] https://lore.kernel.org/lkml/20260916221528.1256283-1-morbo@google.com/

-- 
Kees Cook

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

* Re: [PATCH 7/7] nvdimm: ndtest: Return the string length from flags_show()
  2026-09-17  0:23 ` [PATCH 7/7] nvdimm: ndtest: Return the string length from flags_show() Kees Cook
@ 2026-09-18  3:24   ` Dave Jiang
  0 siblings, 0 replies; 14+ messages in thread
From: Dave Jiang @ 2026-09-18  3:24 UTC (permalink / raw)
  To: Kees Cook, Bill Wendling
  Cc: Alison Schofield, Vishal Verma, Ira Weiny, Greg Kroah-Hartman,
	Uwe Kleine-König, Guangshuo Li, nvdimm, Steven Rostedt,
	Andy Shevchenko, linux-hardening



On 9/16/26 5:23 PM, Kees Cook wrote:
> flags_show() build their output with a seq_buf and return seq_buf_used(),
> which may include the trailing NUL byte when the seq_buf has
> overflowed. Use seq_buf_strlen() instead.
> 
> The flag names are far shorter than the PAGE_SIZE buffer sysfs
> provides, so this cannot overflow today.
> 
> Build tested ARCH=x86_64 with GCC 16.2.0, built out of tree with
> make M=tools/testing/nvdimm:
> tools/testing/nvdimm/test/ndtest.o
> 
> Assisted-by: LLM
> Signed-off-by: Kees Cook <kees@kernel.org>

Reviewed-by: Dave Jiang <dave.jiang@intel.com>

> ---
> Cc: Alison Schofield <alison.schofield@intel.com>
> Cc: Vishal Verma <vishal.l.verma@intel.com>
> Cc: Dave Jiang <dave.jiang@intel.com>
> Cc: Ira Weiny <iweiny@kernel.org>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
> Cc: Guangshuo Li <lgs201920130244@gmail.com>
> Cc: <nvdimm@lists.linux.dev>
> ---
>  tools/testing/nvdimm/test/ndtest.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/tools/testing/nvdimm/test/ndtest.c b/tools/testing/nvdimm/test/ndtest.c
> index 2051ad5d4882..f097f2992966 100644
> --- a/tools/testing/nvdimm/test/ndtest.c
> +++ b/tools/testing/nvdimm/test/ndtest.c
> @@ -693,7 +693,7 @@ static ssize_t flags_show(struct device *dev,
>  	if (seq_buf_used(&s))
>  		seq_buf_printf(&s, "\n");
>  
> -	return seq_buf_used(&s);
> +	return seq_buf_strlen(&s);
>  }
>  static DEVICE_ATTR_RO(flags);
>  


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

* Re: [PATCH 0/7] seq_buf: Add seq_buf_strlen()
  2026-09-17 21:21   ` Kees Cook
@ 2026-09-18  7:45     ` Steven Rostedt
  0 siblings, 0 replies; 14+ messages in thread
From: Steven Rostedt @ 2026-09-18  7:45 UTC (permalink / raw)
  To: Kees Cook; +Cc: Andy Shevchenko, Bill Wendling, linux-hardening

On Thu, 17 Sep 2026 14:21:34 -0700
Kees Cook <kees@kernel.org> wrote:

> Thanks! I'm going to spin a v2 with Bill's seq_buf_init_append()[1] (though
> I think I want to call it seq_buf_init_existing()? I'm open to ideas
> here...)

After I finish my slides for Kernel Recipes, I'll take a look at all
this. Probably over the weekend.

-- Steve

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

end of thread, other threads:[~2026-09-18  7:45 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-17  0:23 [PATCH 0/7] seq_buf: Add seq_buf_strlen() Kees Cook
2026-09-17  0:23 ` [PATCH 1/7] seq_buf: Do not pop from an overflowed seq_buf Kees Cook
2026-09-17  0:23 ` [PATCH 2/7] seq_buf: Copy what fits when seq_buf_puts() and seq_buf_putmem() overflow Kees Cook
2026-09-17  0:23 ` [PATCH 3/7] seq_buf: Clear what a writer did not claim when a seq_buf overflows Kees Cook
2026-09-17  0:23 ` [PATCH 4/7] seq_buf: Add seq_buf_strlen() Kees Cook
2026-09-17  0:23 ` [PATCH 5/7] seq_buf: Use seq_buf_strlen() for the string end in seq_buf_do_printk() Kees Cook
2026-09-17  0:23 ` [PATCH 6/7] powerpc/papr_scm: Return the string length from the sysfs show functions Kees Cook
2026-09-17 12:50   ` Andy Shevchenko
2026-09-17 21:19     ` Kees Cook
2026-09-17  0:23 ` [PATCH 7/7] nvdimm: ndtest: Return the string length from flags_show() Kees Cook
2026-09-18  3:24   ` Dave Jiang
2026-09-17 12:51 ` [PATCH 0/7] seq_buf: Add seq_buf_strlen() Andy Shevchenko
2026-09-17 21:21   ` Kees Cook
2026-09-18  7:45     ` Steven Rostedt

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