summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorKees Cook <kees@kernel.org>2026-10-05 08:56:56 -0700
committerKees Cook <kees@kernel.org>2026-10-11 21:57:29 -0700
commit8d1478bfd63be0f065defd1066d2fa294f419ae2 (patch)
tree326d90ef34e55602e2d55110d23f5153666b78ed /lib
parentea5666e56723e9ac7094ff9849cf154dcb0e436e (diff)
downloadlinux-8d1478bfd63be0f065defd1066d2fa294f419ae2.tar.gz
linux-8d1478bfd63be0f065defd1066d2fa294f419ae2.zip
seq_buf: Add seq_buf_strlen()
Several strlcat() call sites being converted to seq_buf need behavior seq_buf doesn't currently provide. The return from 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. As discussed in review, don't add WARN_ON() for seq_buf_strlen() and drop it from seq_buf_str(). Add tests comparing seq_buf_strlen() against strlen() of seq_buf_str() for empty, appended, truncated, exactly full, and overflowed buffers, checking that seq_buf_strlen() alone terminates a full buffer, and checking that a zero-sized seq_buf reports an empty string from both accessors without touching 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.2.0. Assisted-by: LLM Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Link: https://patch.msgid.link/20261005155708.1471260-6-kees@kernel.org Signed-off-by: Kees Cook <kees@kernel.org>
Diffstat (limited to 'lib')
-rw-r--r--lib/tests/seq_buf_kunit.c129
1 files changed, 129 insertions, 0 deletions
diff --git a/lib/tests/seq_buf_kunit.c b/lib/tests/seq_buf_kunit.c
index 5eaf024fa3a5..c6d941a4502f 100644
--- a/lib/tests/seq_buf_kunit.c
+++ b/lib/tests/seq_buf_kunit.c
@@ -25,6 +25,7 @@ static void seq_buf_init_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, seq_buf_buffer_left(&s), 32);
KUNIT_EXPECT_EQ(test, seq_buf_used(&s), 0);
KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), "");
+ KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 0);
}
static void seq_buf_declare_test(struct kunit *test)
@@ -509,6 +510,128 @@ 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 void seq_buf_strlen_embedded_nul_test(struct kunit *test)
+{
+ static const char data[] = "ab\0cd";
+ DECLARE_SEQ_BUF(s, 16);
+
+ /*
+ * seq_buf_strlen() reports where it put the terminator, not where
+ * the first NUL is, so data carrying a NUL of its own makes the two
+ * disagree. That is expected, and is what the documented caveat is
+ * about.
+ */
+ seq_buf_putmem(&s, data, sizeof(data) - 1);
+ KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 5);
+ KUNIT_EXPECT_EQ(test, strlen(seq_buf_str(&s)), 2);
+}
+
+static void seq_buf_strlen_zero_size_test(struct kunit *test)
+{
+ char buf[] = "untouched";
+ struct seq_buf s;
+
+ /*
+ * A zero-sized seq_buf has nowhere to put a terminator. Both
+ * accessors report an empty string and leave the buffer alone
+ * rather than writing outside it.
+ */
+ seq_buf_init(&s, buf, 0);
+ KUNIT_EXPECT_EQ(test, seq_buf_strlen(&s), 0);
+ KUNIT_EXPECT_STREQ(test, seq_buf_str(&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),
@@ -526,6 +649,12 @@ static struct kunit_case seq_buf_test_cases[] = {
KUNIT_CASE(seq_buf_putmem_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),
+ KUNIT_CASE(seq_buf_strlen_embedded_nul_test),
+ KUNIT_CASE(seq_buf_strlen_zero_size_test),
KUNIT_CASE(seq_buf_do_printk_test),
{}
};