summaryrefslogtreecommitdiff
path: root/lib/tests
diff options
context:
space:
mode:
authorKees Cook <kees@kernel.org>2026-10-05 08:56:54 -0700
committerKees Cook <kees@kernel.org>2026-10-11 21:57:29 -0700
commit505a4aeea2c98ab9b09bc157e81e19cc0d4bf9cb (patch)
treebb1288869e3bba189c3344eac59ed59b3701be72 /lib/tests
parentcc9d25b451196e1cdbea5891902cb65b03da1226 (diff)
seq_buf: Copy what fits when seq_buf_puts() and seq_buf_putmem() overflow
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. Of the callers that check the return value or test for overflow afterward, only lpfc_rx_monitor_report() relied on a string that did not fit being left out, and the previous patch has it drop such a record itself. The others treat overflow as an error or report it. The callers that ignore overflow use whatever the buffer holds, which now ends with the part of the string that fit. 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(), seq_buf_putmem() and seq_buf_putmem_hex() with stale bytes in the buffer. The three partial-overflow tests check the buffer itself rather than seq_buf_str(). Neither writer leaves the last byte of the buffer alone: seq_buf_putmem() copies raw bytes and writes no NUL, and seq_buf_puts() relies on strscpy() to write one. That byte is exactly where seq_buf_str() writes its terminator, so asserting only on the string would pass whether the copy stopped a byte early or dropped the NUL entirely. seq_buf_putmem() was not exported, so the test failed to link as a module. Export 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.2.0, and the test builds as a module (CONFIG_SEQ_BUF_KUNIT_TEST=m). Assisted-by: LLM Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Link: https://patch.msgid.link/20261005155708.1471260-4-kees@kernel.org Signed-off-by: Kees Cook <kees@kernel.org>
Diffstat (limited to 'lib/tests')
-rw-r--r--lib/tests/seq_buf_kunit.c85
1 files changed, 83 insertions, 2 deletions
diff --git a/lib/tests/seq_buf_kunit.c b/lib/tests/seq_buf_kunit.c
index de491f96c1ac..046614100c77 100644
--- a/lib/tests/seq_buf_kunit.c
+++ b/lib/tests/seq_buf_kunit.c
@@ -248,9 +248,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);
@@ -259,6 +259,84 @@ 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)
+{
+ static const char expected[] = "abcdefg";
+ 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));
+
+ /*
+ * Check the buffer before seq_buf_str() does: it would write the
+ * terminator over the last byte itself, hiding whether the copy
+ * placed one there. The literal's own NUL is the eighth byte.
+ */
+ KUNIT_EXPECT_MEMEQ(test, buf, expected, sizeof(buf));
+ KUNIT_EXPECT_STREQ(test, seq_buf_str(&t), "abcdefg");
+}
+
+static void seq_buf_putmem_partial_overflow_test(struct kunit *test)
+{
+ const u8 data[] = { 1, 2, 3, 4, 5, 6, 7 };
+ const char expected[] = { 'a', 'b', 1, 2, 3, 4, 5, 6 };
+ struct seq_buf s;
+ char buf[8];
+
+ memset(buf, 'X', sizeof(buf));
+ seq_buf_init(&s, buf, sizeof(buf));
+ seq_buf_putmem(&s, "ab", 2);
+
+ /* One byte too many, so the last byte of @data is dropped. */
+ KUNIT_EXPECT_EQ(test, seq_buf_putmem(&s, data, sizeof(data)), -1);
+ KUNIT_EXPECT_TRUE(test, seq_buf_has_overflowed(&s));
+
+ /*
+ * Check the buffer rather than seq_buf_str(): seq_buf_putmem() writes
+ * no NUL of its own, so a short copy leaves a stale byte at the end,
+ * exactly where seq_buf_str() would then write the terminator and
+ * hide it.
+ */
+ KUNIT_EXPECT_MEMEQ(test, buf, expected, sizeof(buf));
+}
+
+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";
+ static const char expected_raw[] = "0001020304050607 080";
+#else
+ const char *expected = "0706050403020100 09";
+ static const char expected_raw[] = "0706050403020100 090";
+#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));
+
+ /* Before seq_buf_str() writes the terminator over the last byte. */
+ KUNIT_EXPECT_MEMEQ(test, buf, expected_raw, sizeof(buf));
+ KUNIT_EXPECT_STREQ(test, seq_buf_str(&s), expected);
+}
+
/*
* Counters for the console that seq_buf_do_printk_test() registers while it
* runs. Only records carrying the marker are counted, so unrelated kernel
@@ -401,6 +479,9 @@ 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_partial_overflow_test),
+ KUNIT_CASE(seq_buf_putmem_hex_partial_overflow_test),
KUNIT_CASE(seq_buf_do_printk_test),
{}
};