diff options
| author | Kees Cook <kees@kernel.org> | 2026-10-05 08:56:54 -0700 |
|---|---|---|
| committer | Kees Cook <kees@kernel.org> | 2026-10-11 21:57:29 -0700 |
| commit | 505a4aeea2c98ab9b09bc157e81e19cc0d4bf9cb (patch) | |
| tree | bb1288869e3bba189c3344eac59ed59b3701be72 /lib/tests | |
| parent | cc9d25b451196e1cdbea5891902cb65b03da1226 (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.c | 85 |
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), {} }; |
