seq_buf: Add seq_buf_strlen()
Subsystems (9)
v5 cover seq_buf: Add seq_buf_strlen()
Hi,
While working on seq_buf conversions[1], 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), seq_buf_init_append() (to start a seq_buf from an existing string buffer), and seq_buf_terminate() (as there are many callers using seq_buf_str() just for its termination side-effect). And while implementing that, we found a bunch of other related things that needed fixing. This is that ever-growing series, with tests for each fix.
v5:
- add Alexei's Acked-by for the bpf bits (07/12, 08/12)
- 03/12: new: lpfc_rx_monitor_report() drops a record that does not
fit itself, instead of relying on seq_buf_puts() leaving it out,
which 04/12 changes (BPF CI)
- 04/12: note the audit of seq_buf_puts() and seq_buf_putmem() callers
in the commit message (BPF CI)
- 04/12, 05/12: drop the inaccurate "unlike the other writers" from
the export notes (BPF CI)
- 05/12: drop a duplicate #include from the tests
- 06/12: note in seq_buf_str()'s kernel-doc that only a zero-sized
seq_buf returns the static empty string (Andy)
- 07/12: write the seq_buf_terminate() test data with
seq_buf_putmem(), which leaves no NUL, so the test fails if
seq_buf_terminate() does nothing (Sashiko)
- 09/12: fix the commit message's description of seq_buf_init(), which
empties the buffer rather than setting its size to 0 (BPF CI)
- 09/12: check the length in the test of a string longer than the
size, so an unbounded scan fails it (Sashiko)
- v4..v5 diff: https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git/diff/?id=dev/v7.3-rc2/seq_buf/v5&id2=dev/v7.3-rc2/seq_buf/v4
v4: https://lore.kernel.org/all/[email protected]/
v3: https://lore.kernel.org/all/[email protected]/
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/
Thanks!
-Kees
[1] https://lore.kernel.org/all/[email protected]/
Bill Wendling (1):
seq_buf: Add seq_buf_init_append()
Kees Cook (11):
seq_buf: Do not print an empty line from an overflowed
seq_buf_do_printk()
seq_buf: Do not pop from an overflowed seq_buf
scsi: lpfc: Print rx monitor records straight into the 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: Add seq_buf_terminate()
bpf: Remove dead newline stripping from format_disasm_line() powerpc/papr_scm: Return the string length from the sysfs show
functions
nvdimm: ndtest: Return the string length from flags_show()
docs: core-api: Document the seq_buf API
Documentation/core-api/kernel-api.rst | 9 + include/linux/seq_buf.h | 141 +++++- include/linux/trace_seq.h | 5 +- arch/powerpc/platforms/pseries/papr_scm.c | 6 +- drivers/scsi/lpfc/lpfc_sli.c | 45 +- kernel/bpf/diagnostics.c | 8 +- kernel/trace/trace_events.c | 4 +- kernel/trace/trace_events_hist.c | 6 +- lib/seq_buf.c | 37 +- lib/tests/seq_buf_kunit.c | 537 +++++++++++++++++++++- tools/testing/nvdimm/test/ndtest.c | 2 +- 11 files changed, 746 insertions(+), 54 deletions(-)
Signature · 1 line
2.55.0
PATCH v5 1/12 seq_buf: Do not print an empty line from an overflowed seq_buf_do_printk()
seq_buf_do_printk() prints a buffer line by line, then prints whatever follows the last newline. When a string has overflowed at exactly a newline, an empty line is printed since the pointer hasn't reached the overflow mark of the seq_buf. Switch to just check if the string is already empty and only print if not.
The only caller is the memory cgroup OOM report, so this could only ever add a blank line to a report whose statistics already did not fit.
Add a test that registers a console to count the records that seq_buf_do_printk() emits. It counts the records carrying the test's marker, and the records holding nothing but a line feed that arrive after one, so that unrelated kernel messages do not disturb it. Both states that reach the flaw are covered: exactly full, and overflowed.
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.
--- lib/seq_buf.c | 2 +- lib/tests/seq_buf_kunit.c | 132 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-)
lib/seq_buf.c +1 -1
@@ -128,7 +128,7 @@ void seq_buf_do_printk(struct seq_buf *s, const char *lvl) }/* No trailing LF */ -if (start < s->buffer + s->len) +if (*start)printk ("%s%s\n" , lvl, start); }EXPORT_SYMBOL_GPL (seq_buf_do_printk);
lib/tests/seq_buf_kunit.c +132 -0
151 lines not on this page · raw message
Signature · 1 line
2.55.0
Re: [PATCH v5 01/12] seq_buf: Do not print an empty line from an overflowed seq_buf_do_printk()
Quoted 15 lines · On Mon, 5 Oct 2026 08:56:51 -0700 Kees Cook <[email protected]> wrote:
seq_buf_do_printk()prints a buffer line by line, then prints whatever follows the last newline. When a string has overflowed at exactly a newline, an empty line is printed since the pointer hasn't reached the overflow mark of theseq_buf. Switch to just check if the string is already empty and only print if not.The only caller is the memory cgroup OOM report, so this could only ever add a blank line to a report whose statistics already did not fit.
Add a test that registers a console to count the records that
seq_buf_do_printk()emits. It counts the records carrying the test's marker, and the records holding nothing but a line feed that arrive after one, so that unrelated kernel messages do not disturb it. Both states that reach the flaw are covered: exactly full, and overflowed.
Quoted 7 lines
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.Fixes 96928d9032a7c ("seq_buf: Add seq_buf_do_printk() helper")Assisted-by LLMSigned-off-by Kees Cook <[email protected]>---
-- Steve
PATCH v5 2/12 seq_buf: Do not pop from an overflowed seq_buf
In reply to Kees CookWhen 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.
--- 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(-)
include/linux/seq_buf.h +2 -2
@@ -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--;
include/linux/trace_seq.h +4 -1
@@ -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) {
lib/tests/seq_buf_kunit.c +42 -0
54 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v5 02/12] seq_buf: Do not pop from an overflowed seq_buf
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 18 lines
When a
seq_bufhas overflowed, its len is size + 1, soseq_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 truncatedseq_bufthen looks like a complete, full one.An overflowed
seq_buflogically 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 byvsnprintf()or bytes that were never committed. Return -1 for an overflowedseq_buf, as for an empty one, and leave it overflowed, as the rest of theseq_bufAPI does untilseq_buf_clear()orseq_buf_init().The current callers do not reach this, e.g.
trace_syscallsonly callstrace_seq_pop()when thetrace_seqit pops from has not overflowed, andkernel/bpf/diagnostics.csets the length fromstrnlen()before popping.Add tests for the pop corner cases.
Quoted 7 lines
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 LLMReviewed-by Andy Shevchenko <[email protected]>Signed-off-by Kees Cook <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Re: [PATCH v5 02/12] seq_buf: Do not pop from an overflowed seq_buf
In reply to Kees CookQuoted 18 lines · On Mon, 5 Oct 2026 08:56:52 -0700 Kees Cook <[email protected]> wrote:
When a
seq_bufhas overflowed, its len is size + 1, soseq_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 truncatedseq_bufthen looks like a complete, full one.An overflowed
seq_buflogically 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 byvsnprintf()or bytes that were never committed. Return -1 for an overflowedseq_buf, as for an empty one, and leave it overflowed, as the rest of theseq_bufAPI does untilseq_buf_clear()orseq_buf_init().The current callers do not reach this, e.g.
trace_syscallsonly callstrace_seq_pop()when thetrace_seqit pops from has not overflowed, andkernel/bpf/diagnostics.csets the length fromstrnlen()before popping.Add tests for the pop corner cases.
Quoted 8 lines
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 LLMReviewed-by Andy Shevchenko <[email protected]>Signed-off-by Kees Cook <[email protected]>---
-- Steve
PATCH v5 3/12 scsi: lpfc: Print rx monitor records straight into the seq_buf
In reply to Kees Cooklpfc_rx_monitor_report() formats each record into a stack buffer, then appends it with seq_buf_puts(), relying on seq_buf_puts() appending nothing when a string does not fit: the debugfs output then ends at the last whole record, and the record left out is read in full next time. The next patch makes seq_buf_puts() copy as much as fits instead, as seq_buf_printf() and strlcat() do.
Print each record with seq_buf_printf(), and when it does not fit, end the string where the record began, since the reader takes the buffer up to its NUL. This also takes the DBG_LOG_STR_SZ buffer off the stack.
Build tested ARCH=x86_64 with GCC 16.2.0, CONFIG_SCSI_LPFC=m and W=1.
--- drivers/scsi/lpfc/lpfc_sli.c | 45 +++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 18 deletions(-)
drivers/scsi/lpfc/lpfc_sli.c +27 -18
@@ -8108,7 +8108,6 @@ u32 lpfc_rx_monitor_report(struct lpfc_hba *phba,spinlock_t *ring_lock = &rx_monitor->lock;u32 ring_size = rx_monitor->entries;u32 cnt =0 ; -char tmp[DBG_LOG_STR_SZ ] = {0 };bool log_to_kmsg = (!buf || !buf_len) ? true : false;struct seq_buf s;
@@ -8133,27 +8132,37 @@ u32 lpfc_rx_monitor_report(struct lpfc_hba *phba,/* Read out this entry's data. */ if (!log_to_kmsg) { +unsigned int len; + +/* A header that did not fit leaves no room. */ +if (seq_buf_has_overflowed (&s)) +break ; + len =seq_buf_used (&s); + +seq_buf_printf (&s, +"%03d:\t%-16llu%-16llu%-16llu%-16llu%-8llu%-8llu%-8llu%-8u%-8u%-8u%u(%u)\n" , + *head_idx, + entry->max_bytes_per_interval, + entry->cmf_bytes, + entry->total_bytes, + entry->rcv_bytes, + entry->avg_io_latency, + entry->avg_io_size, + entry->max_read_cnt, + entry->cmf_busy, entry->io_cnt, + entry->cmf_info, + entry->timer_utilization, + entry->timer_interval); +/* * Drop a record whole if it does not fit, without -* consuming its ring entry. +* consuming its ring entry: the reader takes the +* string up to its NUL. */ -scnprintf (tmp,sizeof (tmp), -"%03d:\t%-16llu%-16llu%-16llu%-16llu%-8llu%-8llu%-8llu%-8u%-8u%-8u%u(%u)\n" , - *head_idx, - entry->max_bytes_per_interval, - entry->cmf_bytes, - entry->total_bytes, - entry->rcv_bytes, - entry->avg_io_latency, - entry->avg_io_size, - entry->max_read_cnt, - entry->cmf_busy, entry->io_cnt, - entry->cmf_info, - entry->timer_utilization, - entry->timer_interval); - -if (seq_buf_puts (&s, tmp) <0 ) +if (seq_buf_has_overflowed (&s)) { + buf[len] ='\0' ;break ; + } }else {lpfc_printf_log (phba,KERN_INFO ,LOG_CGN_MGMT , "4410 %02u: MBPI %llu Xmit %llu "
Signature · 1 line
2.55.0
Re: [PATCH v5 03/12] scsi: lpfc: Print rx monitor records straight into the seq_buf
Quoted 71 lines · On Mon, 5 Oct 2026 08:56:53 -0700 Kees Cook <[email protected]> wrote:
lpfc_rx_monitor_report()formats each record into a stack buffer, then appends it withseq_buf_puts(), relying onseq_buf_puts()appending nothing when a string does not fit: the debugfs output then ends at the last whole record, and the record left out is read in full next time. The next patch makesseq_buf_puts()copy as much as fits instead, asseq_buf_printf()andstrlcat()do.Print each record with
seq_buf_printf(), and when it does not fit, end the string where the record began, since the reader takes the buffer up to its NUL. This also takes theDBG_LOG_STR_SZbuffer off the stack.Build tested ARCH=x86_64 with GCC 16.2.0,
CONFIG_SCSI_LPFC=mand W=1.Assisted-by LLMSigned-off-by Kees Cook <[email protected]>--- drivers/scsi/lpfc/lpfc_sli.c | 45 +++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 18 deletions(-)drivers/scsi/lpfc/lpfc_sli.c +24
-10@@ -8108,7 +8108,6 @@ u32 lpfc_rx_monitor_report(struct lpfc_hba *phba,spinlock_t *ring_lock = &rx_monitor->lock;u32 ring_size = rx_monitor->entries;u32 cnt =0 ; -char tmp[DBG_LOG_STR_SZ ] = {0 };bool log_to_kmsg = (!buf || !buf_len) ? true : false;struct seq_buf s;@@ -8133,27 +8132,37 @@ u32 lpfc_rx_monitor_report(struct lpfc_hba *phba,/* Read out this entry's data. */ if (!log_to_kmsg) { +unsigned int len; + +/* A header that did not fit leaves no room. */ +if (seq_buf_has_overflowed (&s)) +break ; + len =seq_buf_used (&s); + +seq_buf_printf (&s, +"%03d:\t%-16llu%-16llu%-16llu%-16llu%-8llu%-8llu%-8llu%-8u%-8u%-8u%u(%u)\n" , + *head_idx, + entry->max_bytes_per_interval, + entry->cmf_bytes, + entry->total_bytes, + entry->rcv_bytes, + entry->avg_io_latency, + entry->avg_io_size, + entry->max_read_cnt, + entry->cmf_busy, entry->io_cnt, + entry->cmf_info, + entry->timer_utilization, + entry->timer_interval); +/* * Drop a record whole if it does not fit, without -* consuming its ring entry. +* consuming its ring entry: the reader takes the +* string up to its NUL. */ - scnprintf(tmp, sizeof(tmp), - "%03d:\t%-16llu%-16llu%-16llu%-16llu%-8llu%-8llu%-8llu%-8u%-8u%-8u%u(%u)\n", - *head_idx, - entry->max_bytes_per_interval, - entry->cmf_bytes, - entry->total_bytes, - entry->rcv_bytes, - entry->avg_io_latency,
drivers/scsi/lpfc/lpfc_sli.c u32 lpfc_rx_monitor_report(struct lpfc_hba *phba, · line 8163
Quoted 10 lines
drivers/scsi/lpfc/lpfc_sli.c +2
-8- entry->avg_io_size, - entry->max_read_cnt, - entry->cmf_busy, entry->io_cnt, - entry->cmf_info, - entry->timer_utilization, - entry->timer_interval); - -if (seq_buf_puts (&s, tmp) <0 ) +if (seq_buf_has_overflowed (&s)) { + buf[len] ='\0' ;
There should probably be a seq_buf_truncate() to do that. Then a request for the length will be correct.
David
Quoted 5 lines
drivers/scsi/lpfc/lpfc_sli.c +1
-0break ; + } }else {lpfc_printf_log (phba,KERN_INFO ,LOG_CGN_MGMT , "4410 %02u: MBPI %llu Xmit %llu "
Re: [PATCH v5 03/12] scsi: lpfc: Print rx monitor records straight into the seq_buf
Quoted 79 lines · David Laight
Quoted 76 lines · On Mon, 5 Oct 2026 08:56:53 -0700 Kees Cook <[email protected]> wrote:
lpfc_rx_monitor_report()formats each record into a stack buffer, then appends it withseq_buf_puts(), relying onseq_buf_puts()appending nothing when a string does not fit: the debugfs output then ends at the last whole record, and the record left out is read in full next time. The next patch makesseq_buf_puts()copy as much as fits instead, asseq_buf_printf()andstrlcat()do.Print each record with
seq_buf_printf(), and when it does not fit, end the string where the record began, since the reader takes the buffer up to its NUL. This also takes theDBG_LOG_STR_SZbuffer off the stack.Build tested ARCH=x86_64 with GCC 16.2.0,
CONFIG_SCSI_LPFC=mand W=1.Assisted-by LLMSigned-off-by Kees Cook <[email protected]>--- drivers/scsi/lpfc/lpfc_sli.c | 45 +++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 18 deletions(-)drivers/scsi/lpfc/lpfc_sli.c +24
-15@@ -8108,7 +8108,6 @@ u32 lpfc_rx_monitor_report(struct lpfc_hba *phba,spinlock_t *ring_lock = &rx_monitor->lock;u32 ring_size = rx_monitor->entries;u32 cnt =0 ; -char tmp[DBG_LOG_STR_SZ ] = {0 };bool log_to_kmsg = (!buf || !buf_len) ? true : false;struct seq_buf s;@@ -8133,27 +8132,37 @@ u32 lpfc_rx_monitor_report(struct lpfc_hba *phba,/* Read out this entry's data. */ if (!log_to_kmsg) { +unsigned int len; + +/* A header that did not fit leaves no room. */ +if (seq_buf_has_overflowed (&s)) +break ; + len =seq_buf_used (&s); + +seq_buf_printf (&s, +"%03d:\t%-16llu%-16llu%-16llu%-16llu%-8llu%-8llu%-8llu%-8u%-8u%-8u%u(%u)\n" , + *head_idx, + entry->max_bytes_per_interval, + entry->cmf_bytes, + entry->total_bytes, + entry->rcv_bytes, + entry->avg_io_latency, + entry->avg_io_size, + entry->max_read_cnt, + entry->cmf_busy, entry->io_cnt, + entry->cmf_info, + entry->timer_utilization, + entry->timer_interval); +/* * Drop a record whole if it does not fit, without -* consuming its ring entry. +* consuming its ring entry: the reader takes the +* string up to its NUL. */ - scnprintf(tmp, sizeof(tmp), - "%03d:\t%-16llu%-16llu%-16llu%-16llu%-8llu%-8llu%-8llu%-8u%-8u%-8u%u(%u)\n", - *head_idx, - entry->max_bytes_per_interval, - entry->cmf_bytes, - entry->total_bytes, - entry->rcv_bytes, - entry->avg_io_latency, - entry->avg_io_size, - entry->max_read_cnt, - entry->cmf_busy, entry->io_cnt, - entry->cmf_info, - entry->timer_utilization,
Quoted 8 lines
drivers/scsi/lpfc/lpfc_sli.c u32 lpfc_rx_monitor_report(struct lpfc_hba *phba, · line 8163
Quoted 5 lines
drivers/scsi/lpfc/lpfc_sli.c +2
-3- entry->timer_interval); - -if (seq_buf_puts (&s, tmp) <0 ) +if (seq_buf_has_overflowed (&s)) { + buf[len] ='\0' ;There should probably be a
seq_buf_truncate()to do that. Then a request for the length will be correct.
We don't really have a "rewind" API. (Oh please don't make me add another API ... I'm at 3 already in this series...)
Signature · 1 line
Kees Cook
PATCH v5 4/12 seq_buf: Copy what fits when seq_buf_puts() and seq_buf_putmem() overflow
In reply to Kees CookWhen 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).
--- include/linux/seq_buf.h | 6 +++ lib/seq_buf.c | 17 +++++++- lib/tests/seq_buf_kunit.c | 85 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 4 deletions(-)
include/linux/seq_buf.h +6 -0
@@ -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) {
lib/seq_buf.c +15 -2
@@ -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,9 +249,15 @@ 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 ; } +EXPORT_SYMBOL_GPL (seq_buf_putmem);#define MAX_MEMHEX_BYTES 8U#define HEX_CHARS (MAX_MEMHEX_BYTES*2 + 1)
lib/tests/seq_buf_kunit.c +83 -2
104 lines not on this page · raw message
Signature · 1 line
2.55.0
Re: [PATCH v5 04/12] seq_buf: Copy what fits when seq_buf_puts() and seq_buf_putmem() overflow
Quoted 38 lines · On Mon, 5 Oct 2026 08:56:54 -0700 Kees Cook <[email protected]> wrote:
When
seq_buf_puts()orseq_buf_putmem()is given more than fits, it copies nothing and only marks theseq_bufas overflowed. Ifseq_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 theseq_bufnever wrote them.
seq_buf_printf()does not have this problem, becausevsnprintf()writes as much of the output as fits, followed by a NUL. Repeat this behavior inseq_buf_puts(), usingstrscpy(), and inseq_buf_putmem(), which also coversseq_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 overflowseq_buf_puts(),seq_buf_putmem()andseq_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, andseq_buf_puts()relies onstrscpy()to write one. That byte is exactly whereseq_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.
Quoted 8 lines
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 LLMReviewed-by Andy Shevchenko <[email protected]>Signed-off-by Kees Cook <[email protected]>---
-- Steve
PATCH v5 5/12 seq_buf: Clear what a writer did not claim when a seq_buf overflows
In reply to Kees CookUsing 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.
seq_buf_path() 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).
--- include/linux/seq_buf.h | 8 +++++-- lib/seq_buf.c | 5 +++++ lib/tests/seq_buf_kunit.c | 44 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-)
include/linux/seq_buf.h +6 -2
@@ -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 ; }
lib/seq_buf.c +5 -0
@@ -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 ;
@@ -343,6 +347,7 @@ int seq_buf_path(struct seq_buf *s, const struct path *path, const char *esc)return res; } +EXPORT_SYMBOL_GPL (seq_buf_path);/** * seq_buf_to_user - copy the sequence buffer to user space
lib/tests/seq_buf_kunit.c +44 -0
63 lines not on this page · raw message
Signature · 1 line
2.55.0
Re: [PATCH v5 05/12] seq_buf: Clear what a writer did not claim when a seq_buf overflows
Quoted 51 lines · On Mon, 5 Oct 2026 08:56:55 -0700 Kees Cook <[email protected]> wrote:
Using
seq_buf_set_overflow()would leave the bytes between "len" and "size" untouched, so ifseq_buf_str()is used on an overflowedseq_buf, those bytes may be exposed. For any paths that don't claim partially written bytes, by setting "len = size" before callingseq_buf_set_overflow(), wipe the unclaimed bytes. Theseq_buf_puts()and related APIs already claim those bytes now, so only the unclaimed cases remain. A specific example of this wasseq_buf_path()which usesd_path()and would write to the tail before discovering it was out of space, and would correctly mark aseq_bufas 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, includingseq_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.
seq_buf_path()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 LLMReviewed-by Andy Shevchenko <[email protected]>Signed-off-by Kees Cook <[email protected]>--- include/linux/seq_buf.h | 8 +++++-- lib/seq_buf.c | 5 +++++ lib/tests/seq_buf_kunit.c | 44 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-)include/linux/seq_buf.h +1
-0@@ -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
include/linux/seq_buf.h seq_buf_has_overflowed(struct seq_buf *s) · line 69
Quoted 10 lines
include/linux/seq_buf.h +4
-2-* 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);
What is the performance impact of zeroing the buffer?
I don't think it would be a good idea to be zeroing the buffer on entry either (I've not looked to see it that happens - but there will be PAGE_SIZE buffers (maybe 64k) that get a a small number of characters written to them). Writing a single '\0' really ought to be enough.
David
Quoted 104 lines
include/linux/seq_buf.h +1
-0+ s->len = s->size +1 ; }lib/seq_buf.c +5
-0@@ -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 ;@@ -343,6 +347,7 @@ int seq_buf_path(struct seq_buf *s, const struct path *path, const char *esc)return res; } +EXPORT_SYMBOL_GPL (seq_buf_path);/** * seq_buf_to_user - copy the sequence buffer to user space lib/tests/seq_buf_kunit.c +44
-0@@ -7,7 +7,9 @@#include <kunit/test.h> #include <linux/console.h> +#include <linux/fs.h> #include <linux/seq_buf.h> +#include <linux/shmem_fs.h> #include <linux/string.h> static void seq_buf_init_test (struct kunit *test)@@ -466,6 +468,47 @@ static void seq_buf_do_printk_test(struct kunit *test)KUNIT_EXPECT_EQ (test, seq_buf_printk_empty,0 ); } +/* 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),@@ -482,6 +525,7 @@ static struct kunit_case seq_buf_test_cases[] = {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_path_overflow_test), KUNIT_CASE(seq_buf_do_printk_test), {} };
Re: [PATCH v5 05/12] seq_buf: Clear what a writer did not claim when a seq_buf overflows
Quoted 7 lines · David Laight
include/linux/seq_buf.h seq_buf_has_overflowed(struct seq_buf *s) · line 69
Quoted 5 lines
static inline void
seq_buf_set_overflow(struct seq_buf *s)
{include/linux/seq_buf.h +2
-0+if (s->len < s->size) +memset (s->buffer + s->len,0 , s->size - s->len);What is the performance impact of zeroing the buffer?
Overflow is never a fast path: it happens at most once per seq_buf (after that len > size, so the memset is skipped), and it is already a failure the caller has to handle.
It is also usually zero bytes. printf, bprintf, puts, putmem, and putc all fill to the end before overflowing, so len == size and there is nothing to clear. Only a writer handed the tail by seq_buf_get_buf() that then gives up leaves anything behind (seq_buf_path() with d_path(), or landlock's string_escape_mem()), and then the clear covers only the space that writer was given, which it may have partly filled.
Quoted 3 lines
I don't think it would be a good idea to be zeroing the buffer on entry either (I've not looked to see it that happens - but there will be
PAGE_SIZEbuffers (maybe 64k) that get a a small number of characters written to them).
Agreed, and it doesn't: seq_buf_init() writes a single NUL.
Quoted 1 line
Writing a single '\0' really ought to be enough.
It would be for seq_buf_str(), but once a seq_buf has overflowed, seq_buf_used() reports the whole buffer, and seq_buf_print_seq() and seq_buf_to_user() copy that many bytes. Whatever followed the NUL (the path fragment d_path() left at the end, say) would still reach the seq_file or userspace.
-Kees
Signature · 1 line
Kees Cook
Steven Rostedt
maintainerRe: [PATCH v5 05/12] seq_buf: Clear what a writer did not claim when a seq_buf overflows
Quoted 13 lines · On Tue, 6 Oct 2026 14:26:56 -0700 Kees Cook <[email protected]> wrote:
Quoted 7 lines · David Laight
include/linux/seq_buf.h seq_buf_has_overflowed(struct seq_buf *s) · line 69
Quoted 5 lines
static inline void
seq_buf_set_overflow(struct seq_buf *s)
{include/linux/seq_buf.h +2
-0+if (s->len < s->size) +memset (s->buffer + s->len,0 , s->size - s->len);What is the performance impact of zeroing the buffer?
Overflow is never a fast path: it happens at most once per
seq_buf(after that len > size, so the memset is skipped), and it is already a failure the caller has to handle.
Quoted 6 lines
It is also usually zero bytes. printf, bprintf, puts, putmem, and putc all fill to the end before overflowing, so len == size and there is nothing to clear. Only a writer handed the tail by
seq_buf_get_buf()that then gives up leaves anything behind (seq_buf_path()withd_path(), or landlock'sstring_escape_mem()), and then the clear covers only the space that writer was given, which it may have partly filled.
Correct, overflow means we "failed" (but not critically). And when we do, it's just remnants that need to be zero'd; so likely to be small.
Quoted 7 lines
Quoted 4 lines
I don't think it would be a good idea to be zeroing the buffer on entry either (I've not looked to see it that happens - but there will be
PAGE_SIZEbuffers (maybe 64k) that get a a small number of characters written to them).Agreed, and it doesn't:
seq_buf_init()writes a single NUL.
Quoted 7 lines
Quoted 1 line
Writing a single '\0' really ought to be enough.
It would be for
seq_buf_str(), but once aseq_bufhas overflowed,seq_buf_used()reports the whole buffer, andseq_buf_print_seq()andseq_buf_to_user()copy that many bytes. Whatever followed the NUL (the path fragmentd_path()left at the end, say) would still reach theseq_fileor userspace.
Agreed.
-- Steve
Re: [PATCH v5 05/12] seq_buf: Clear what a writer did not claim when a seq_buf overflows
In reply to Kees CookQuoted 22 lines · On Mon, 5 Oct 2026 08:56:55 -0700 Kees Cook <[email protected]> wrote:
Using
seq_buf_set_overflow()would leave the bytes between "len" and "size" untouched, so ifseq_buf_str()is used on an overflowedseq_buf, those bytes may be exposed. For any paths that don't claim partially written bytes, by setting "len = size" before callingseq_buf_set_overflow(), wipe the unclaimed bytes. Theseq_buf_puts()and related APIs already claim those bytes now, so only the unclaimed cases remain. A specific example of this wasseq_buf_path()which usesd_path()and would write to the tail before discovering it was out of space, and would correctly mark aseq_bufas 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, includingseq_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.
seq_buf_path()was not exported, so the test failed to link as a module. Export it.
Quoted 8 lines
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 LLMReviewed-by Andy Shevchenko <[email protected]>Signed-off-by Kees Cook <[email protected]>---
-- Steve
PATCH v5 6/12 seq_buf: Add seq_buf_strlen()
In reply to Kees CookSeveral 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.
--- include/linux/seq_buf.h | 67 ++++++++++++++++++-- lib/tests/seq_buf_kunit.c | 129 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 6 deletions(-)
include/linux/seq_buf.h +61 -6
@@ -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,14 @@ 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. +* +* A zero-sized seq_buf has nowhere to put a NUL, so the empty string +* is returned instead of writing to @s->buffer. Any other seq_buf +* returns @s->buffer, even when it holds an empty string, so callers +* always get their own buffer back. * * After this function is called, s->buffer is safe to use * in string operations.
@@ -107,17 +135,44 @@ static inline unsigned int seq_buf_used(struct seq_buf *s)*/ static inline const char *seq_buf_str (struct seq_buf *s) { -if (WARN_ON (s->size ==0 )) +if (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. +* +* A zero-sized seq_buf holds no string, so 0 is returned without writing +* to @s->buffer, matching what seq_buf_str() returns for one. +* +* After this function is called, s->buffer is safe to use +* in string operations. +* +* Returns: the offset of the NUL that terminates @s->buffer. That is the +* length of the string unless an earlier NUL is in the way, either one the +* data written to @s carried itself, or one seq_buf_set_overflow() left +* behind when it cleared what no writer had claimed. +*/ +static inline size_t seq_buf_strlen (struct seq_buf *s) +{ +if (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
lib/tests/seq_buf_kunit.c +129 -0
147 lines not on this page · raw message
Signature · 1 line
2.55.0
Steven Rostedt
maintainerQuoted 7 lines · On Mon, 5 Oct 2026 08:56:56 -0700 Kees Cook <[email protected]> wrote:
include/linux/seq_buf.h +7
-0+/* +* 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. +*/
include/linux/seq_buf.h static inline unsigned int seq_buf_used(struct seq_buf *s) · line 108
Quoted 10 lines
include/linux/seq_buf.h +10
-0+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 ;
Hmm, I just realized that this could inadvertently overflow the buffer. If len == size, it may not be overflowed. If using seq_buf_putmem() and filling the entire buffer, calling this would force an overflow as we just truncated the last byte.
Quoted 32 lines
include/linux/seq_buf.h +13
-3+ +return end; +} +/** * seq_buf_str - get NUL-terminated C string from seq_buf * @s: the seq_buf handle @@ -98,7 +119,14 @@ 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. +* +* A zero-sized seq_buf has nowhere to put a NUL, so the empty string +* is returned instead of writing to @s->buffer. Any other seq_buf +* returns @s->buffer, even when it holds an empty string, so callers +* always get their own buffer back. * * After this function is called, s->buffer is safe to use * in string operations. @@ -107,17 +135,44 @@ static inline unsigned int seq_buf_used(struct seq_buf *s)*/ static inline const char *seq_buf_str (struct seq_buf *s) { -if (WARN_ON (s->size ==0 )) +if (s->size ==0 )return "" ; -if (seq_buf_buffer_left (s))
include/linux/seq_buf.h static inline unsigned int seq_buf_used(struct seq_buf *s) · line 147
Quoted 10 lines
include/linux/seq_buf.h +3
-3- 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
Hmm,
I don't really like the way this modifies the string, as the request is just to return the string length.
Just like the above, this could cause an inadvertent overflow.
I could envision a use case where we want to use the entire buffer and do not expect it to be nul terminated. And this function could be used to get the length to print. That is, it may want to use the entire length.
Having "strlen()" modify the string just seems wrong to me.
Is it really needed? If the user wants to make sure the string is terminated, it should use the next patch's "seq_buf_terminate()".
And we need to update it so that if seq_buf_terminate() causes a truncation, it needs to set that it overflowed.
-- Steve
Quoted 31 lines
include/linux/seq_buf.h +28
-0+* @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. +* +* A zero-sized seq_buf holds no string, so 0 is returned without writing +* to @s->buffer, matching what seq_buf_str() returns for one. +* +* After this function is called, s->buffer is safe to use +* in string operations. +* +* Returns: the offset of the NUL that terminates @s->buffer. That is the +* length of the string unless an earlier NUL is in the way, either one the +* data written to @s carried itself, or one seq_buf_set_overflow() left +* behind when it cleared what no writer had claimed. +*/ +static inline size_t seq_buf_strlen (struct seq_buf *s) +{ +if (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
Quoted 17 lines · Steven Rostedt
Quoted 14 lines · On Mon, 5 Oct 2026 08:56:56 -0700 Kees Cook <[email protected]> wrote:
include/linux/seq_buf.h +14
-0+/* +* 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
Quoted 8 lines
include/linux/seq_buf.h static inline unsigned int seq_buf_used(struct seq_buf *s) · line 108
Quoted 3 lines
include/linux/seq_buf.h +3
-0+ end = s->size -1 ; + + s->buffer[end] =0 ;Hmm, I just realized that this could inadvertently overflow the buffer. If len == size, it may not be overflowed. If using
seq_buf_putmem()and filling the entire buffer, calling this would force an overflow as we just truncated the last byte.
Hm, yes. So the "else" branch needs to include:
s->len = s->size + 1;
Quoted 46 lines
Quoted 33 lines
include/linux/seq_buf.h +13
-3+ +return end; +} +/** * seq_buf_str - get NUL-terminated C string from seq_buf * @s: the seq_buf handle @@ -98,7 +119,14 @@ 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. +* +* A zero-sized seq_buf has nowhere to put a NUL, so the empty string +* is returned instead of writing to @s->buffer. Any other seq_buf +* returns @s->buffer, even when it holds an empty string, so callers +* always get their own buffer back. * * After this function is called, s->buffer is safe to use * in string operations. @@ -107,17 +135,44 @@ static inline unsigned int seq_buf_used(struct seq_buf *s)*/ static inline const char *seq_buf_str (struct seq_buf *s) { -if (WARN_ON (s->size ==0 )) +if (s->size ==0 )return "" ; -if (seq_buf_buffer_left (s))include/linux/seq_buf.h static inline unsigned int seq_buf_used(struct seq_buf *s) · line 147
Quoted 10 lines
include/linux/seq_buf.h +3
-3- 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 Hmm,
Quoted 8 lines
I don't really like the way this modifies the string, as the request is just to return the string length.
Just like the above, this could cause an inadvertent overflow.
I could envision a use case where we want to use the entire buffer and do not expect it to be nul terminated. And this function could be used to get the length to print. That is, it may want to use the entire length.
Non-C String users would be using seq_buf_used(), not seq_buf_strlen().
Quoted 2 lines
Having "
strlen()" modify the string just seems wrong to me.
No, this is absolutely required. It's nonsensical to work on a C String without termination. (And it's the entire purpose of this series...)
Quoted 2 lines
Is it really needed? If the user wants to make sure the string is terminated, it should use the next patch's "
seq_buf_terminate()".
The ergonomics on this are very bad. I explicitly don't want to leave a foot-gun around for this. :) (i.e. require all C String users also remember to terminate it first; the whole point of the API is to have the string always be safe.)
Quoted 2 lines
And we need to update it so that if
seq_buf_terminate()causes a truncation, it needs to set that it overflowed.
That I agree with. I will send a patch on top of the series since this has been in -next for a while and the merge window is here now. :P
Signature · 1 line
Kees Cook
Hi Kees,
Quoted 30 lines
Date: 2026-10-22 11:06:32-0700
From: Kees Cook <[email protected]>Quoted 17 lines · Steven Rostedt
Quoted 14 lines · On Mon, 5 Oct 2026 08:56:56 -0700 Kees Cook <[email protected]> wrote:
include/linux/seq_buf.h +14
-0+/* +* 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 Quoted 8 lines
include/linux/seq_buf.h static inline unsigned int seq_buf_used(struct seq_buf *s) · line 108
Quoted 3 lines
include/linux/seq_buf.h +3
-0+ end = s->size -1 ; + + s->buffer[end] =0 ;Hmm, I just realized that this could inadvertently overflow the buffer. If len == size, it may not be overflowed. If using
seq_buf_putmem()and filling the entire buffer, calling this would force an overflow as we just truncated the last byte.
Quoted 3 lines
Hm, yes. So the "else" branch needs to include:
s->len = s->size + 1;
Please think again about the possibility of using pointers instead of lengths. This is a source of off-by-one bugs that's only caused by the choice of data, I believe. If using pointers, truncation would be stored as a NULL pointer, and off-by-ones would be less likely.
Consider this off-by-one to be just a sympthom of the underlying problematic design. Whenever a design forces you to write +1 or -1, there are high chances that the design is sub-optimal regarding safety.
[...]
Quoted 22 lines
Quoted 5 lines
Quoted 2 lines
+/**
+ *seq_buf_strlen- get the length of the NUL-terminated C string inseq_bufHmm,
Quoted 8 lines
I don't really like the way this modifies the string, as the request is just to return the string length.
Just like the above, this could cause an inadvertent overflow.
I could envision a use case where we want to use the entire buffer and do not expect it to be nul terminated. And this function could be used to get the length to print. That is, it may want to use the entire length.
Non-C String users would be using
seq_buf_used(), notseq_buf_strlen().Quoted 2 lines
Having "
strlen()" modify the string just seems wrong to me.No, this is absolutely required. It's nonsensical to work on a C String without termination. (And it's the entire purpose of this series...)
Quoted 7 lines
Quoted 2 lines
Is it really needed? If the user wants to make sure the string is terminated, it should use the next patch's "
seq_buf_terminate()".The ergonomics on this are very bad. I explicitly don't want to leave a foot-gun around for this. :) (i.e. require all C String users also remember to terminate it first; the whole point of the API is to have the string always be safe.)
Agree.
Have a lovely night!
Alex
Quoted 9 lines
Quoted 2 lines
And we need to update it so that if
seq_buf_terminate()causes a truncation, it needs to set that it overflowed.That I agree with. I will send a patch on top of the series since this has been in -next for a while and the merge window is here now. :P
Signature · 1 line
Kees Cook
Signature · 1 line
<https://www.alejandro-colomar.es>
Steven Rostedt
maintainerQuoted 9 lines · On Thu, 22 Oct 2026 21:41:53 +0200 Alejandro Colomar…
Quoted 8 lines
Quoted 4 lines
Hmm, I just realized that this could inadvertently overflow the buffer. If len == size, it may not be overflowed. If using
seq_buf_putmem()and filling the entire buffer, calling this would force an overflow as we just truncated the last byte.Hm, yes. So the "else" branch needs to include:
s->len = s->size + 1;
Quoted 8 lines
Please think again about the possibility of using pointers instead of lengths. This is a source of off-by-one bugs that's only caused by the choice of data, I believe. If using pointers, truncation would be stored as a NULL pointer, and off-by-ones would be less likely.
Consider this off-by-one to be just a sympthom of the underlying problematic design. Whenever a design forces you to write +1 or -1, there are high chances that the design is sub-optimal regarding safety.
It's not an off-by-one error here. The s->len = size - 1 is correct, but it also has a side effect of removing a character without marking it overflowed. The seq_buf defines being "overflowed" as s->len == s->size + 1. What the else statement should have is:
else seq_buf_set_overflow(s);
Which would be less confusing to reviewers as that is defined as:
static inline voidseq_buf_set_overflow(struct seq_buf *s)
{
s->len = s->size + 1;
}-- Steve
Steven Rostedt
maintainerQuoted 8 lines · On Thu, 22 Oct 2026 15:57:51 -0400 Steven Rostedt <[email protected]> wrote:
It's not an off-by-one error here. The
s->len= size - 1 is correct, but it also has a side effect of removing a character without marking it overflowed. Theseq_bufdefines being "overflowed" ass->len==s->size+ 1. What the else statement should have is:else seq_buf_set_overflow(s);
Quoted 7 lines
Which would be less confusing to reviewers as that is defined as:
static inline void
seq_buf_set_overflow(struct seq_buf *s){ s->len = s->size + 1; }
More exactly:
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;
seq_buf_set_overflow(s);
}
s->buffer[end] = 0;
return end;
}Hi Steven,
Quoted 20 lines
Date: 2026-10-22 15:57:51-0400
From: Steven Rostedt <[email protected]>Quoted 14 lines · On Thu, 22 Oct 2026 21:41:53 +0200 Alejandro Colomar…
Quoted 8 lines
Quoted 4 lines
Hmm, I just realized that this could inadvertently overflow the buffer. If len == size, it may not be overflowed. If using
seq_buf_putmem()and filling the entire buffer, calling this would force an overflow as we just truncated the last byte.Hm, yes. So the "else" branch needs to include:
s->len = s->size + 1;Please think again about the possibility of using pointers instead of lengths. This is a source of off-by-one bugs that's only caused by the choice of data, I believe. If using pointers, truncation would be stored as a NULL pointer, and off-by-ones would be less likely.
Quoted 7 lines
Quoted 3 lines
Consider this off-by-one to be just a sympthom of the underlying problematic design. Whenever a design forces you to write +1 or -1, there are high chances that the design is sub-optimal regarding safety.
It's not an off-by-one error here. The
s->len= size - 1 is correct, but it also has a side effect of removing a character without marking it overflowed.
It's correct, but at the same time an opportunity for off-by-one mistakes.
Quoted 6 lines
The
seq_bufdefines being "overflowed" ass->len==s->size+ 1. What the else statement should have is:else seq_buf_set_overflow(s);
Quoted 7 lines
Which would be less confusing to reviewers as that is defined as:
static inline void
seq_buf_set_overflow(struct seq_buf *s){ s->len = s->size + 1; }
Agree.
But still, a better design would do:
static inline void
seq_buf_set_overflow(struct seq_buf *s)
{
s->p = NULL;
}This is way more robust, and also easier to understand. My head is boiling when trying to review this patch set for correctness. That's not a good sign.
Have a lovely night!
Alex
Quoted 2 lines
-- Steve
Signature · 1 line
<https://www.alejandro-colomar.es>
Steven Rostedt
maintainerQuoted 10 lines · On Thu, 22 Oct 2026 22:11:27 +0200 Alejandro Colomar…
Agree.
But still, a better design would do:
static inline void seq_buf_set_overflow(struct seq_buf *s) { s->p = NULL; }
Quoted 3 lines
This is way more robust, and also easier to understand. My head is boiling when trying to review this patch set for correctness. That's not a good sign.
But that loses all data. We still want access to what was written.
-- Steve
Hi Steven,
Quoted 16 lines
Date: 2026-10-22 16:20:00-0400
From: Steven Rostedt <[email protected]>Quoted 10 lines · On Thu, 22 Oct 2026 22:11:27 +0200 Alejandro Colomar…
Agree.
But still, a better design would do:
static inline void seq_buf_set_overflow(struct seq_buf *s) { s->p = NULL; }
Quoted 5 lines
Quoted 3 lines
This is way more robust, and also easier to understand. My head is boiling when trying to review this patch set for correctness. That's not a good sign.
But that loses all data. We still want access to what was written.
No, it doesn't lose data. My proposal is to have this:
struct my_seq_buf {
// .buffer points to the start of the buffer
char *buffer;
/* .p points to the current position. This is the last
* '\0' that has been written to the buffer. On
* truncation, it is a NULL pointer.
*/
char *p;
/* .end always holds ARRAY_END(buffer). It is a pointer
* one-past-the-last character in the buffer.
*/
char *end;
};This would be initialized as (of course, wrapper in static inline functions as needed) this:
char buf[100]; struct seq_buf b; b.buffer = buf; b.p = buf; b.end = ARRAY_END(buf);
Then, you would write to it as:
b.p = stpecpy(b.p, b.end, "foo"); b.p = stpecpy(b.p, b.end, "bar"); ... b.p = stpecpy(b.p, b.end, "baz"); if (b.p == NULL) goto fail;
Then, at some point, you might want to initialize a new buffer out of the remainings of it:
struct seq_buf b2; if (b.p == NULL) goto fail; b2.start = b.p; b2.p = b.p; b2.end = b.end;
See how simple this becomes? (So much that you may doubt the necessity of the set of APIs at all, but they don't hurt much either.)
Cheers,
Alex
Quoted 2 lines
-- Steve
Signature · 1 line
<https://www.alejandro-colomar.es>
Quoted 64 lines
Date: 2026-10-22 22:38:12+0200
From: Alejandro Colomar <[email protected]>Hi Steven,
Quoted 16 lines
Date: 2026-10-22 16:20:00-0400
From: Steven Rostedt <[email protected]>Quoted 10 lines · On Thu, 22 Oct 2026 22:11:27 +0200 Alejandro Colomar…
Agree.
But still, a better design would do:
static inline void seq_buf_set_overflow(struct seq_buf *s) { s->p = NULL; }Quoted 5 lines
Quoted 3 lines
This is way more robust, and also easier to understand. My head is boiling when trying to review this patch set for correctness. That's not a good sign.
But that loses all data. We still want access to what was written.
No, it doesn't lose data. My proposal is to have this:
struct my_seq_buf { // .buffer points to the start of the buffer char *buffer; /* .p points to the current position. This is the last * '\0' that has been written to the buffer. On * truncation, it is a NULL pointer. */ char *p; /* .end always holds ARRAY_END(buffer). It is a pointer * one-past-the-last character in the buffer. */ char *end; };This would be initialized as (of course, wrapper in static inline functions as needed) this:
char buf[100]; struct seq_buf b; b.buffer = buf; b.p = buf; b.end = ARRAY_END(buf);Then, you would write to it as:
b.p = stpecpy(b.p, b.end, "foo"); b.p = stpecpy(b.p, b.end, "bar"); ... b.p = stpecpy(b.p, b.end, "baz"); if (b.p == NULL) goto fail;
Quoted 8 lines
Then, at some point, you might want to initialize a new buffer out of the remainings of it:
struct seq_buf b2; if (b.p == NULL) goto fail; b2.start = b.p;
s/start/buffer/
Quoted 5 lines
b2.p = b.p; b2.end = b.end;See how simple this becomes? (So much that you may doubt the necessity of the set of APIs at all, but they don't hurt much either.)
I didn't mention it, but I think it's obvious: you can still read the string by reading 's.buffer'.
Cheers,
Alex
Quoted 10 lines
Cheers,
AlexQuoted 2 lines
-- Steve
Signature · 1 line
<https://www.alejandro-colomar.es>
Signature · 1 line
<https://www.alejandro-colomar.es>
Steven Rostedt
maintainerQuoted 48 lines · On Thu, 22 Oct 2026 22:38:03 +0200 Alejandro Colomar…
No, it doesn't lose data. My proposal is to have this:
struct my_seq_buf { // .buffer points to the start of the buffer char *buffer; /* .p points to the current position. This is the last * '\0' that has been written to the buffer. On * truncation, it is a NULL pointer. */ char *p; /* .end always holds ARRAY_END(buffer). It is a pointer * one-past-the-last character in the buffer. */ char *end; };This would be initialized as (of course, wrapper in static inline functions as needed) this:
char buf[100]; struct seq_buf b; b.buffer = buf; b.p = buf; b.end = ARRAY_END(buf);Then, you would write to it as:
b.p = stpecpy(b.p, b.end, "foo"); b.p = stpecpy(b.p, b.end, "bar"); ... b.p = stpecpy(b.p, b.end, "baz"); if (b.p == NULL) goto fail;Then, at some point, you might want to initialize a new buffer out of the remainings of it:
struct seq_buf b2; if (b.p == NULL) goto fail; b2.start = b.p; b2.p = b.p; b2.end = b.end;
Quoted 2 lines
See how simple this becomes? (So much that you may doubt the necessity of the set of APIs at all, but they don't hurt much either.)
I rather not increase its size. It is embedded in the trace_seq.
Yes the seq_buf code can be complex, but it's in one location where it can be highly tested and verified. The point of seq_buf is to do the complexity so that other places in the kernel doesn't need to.
-- Steve
Hi Steven,
Quoted 3 lines
Date: 2026-10-22 16:53:36-0400
From: Steven Rostedt <[email protected]>
[...]
Quoted 16 lines
Quoted 16 lines
struct my_seq_buf { // .buffer points to the start of the buffer char *buffer; /* .p points to the current position. This is the last * '\0' that has been written to the buffer. On * truncation, it is a NULL pointer. */ char *p; /* .end always holds ARRAY_END(buffer). It is a pointer * one-past-the-last character in the buffer. */ char *end; };
[...]
Quoted 2 lines
I rather not increase its size. It is embedded in the
trace_seq.
The size shouldn't change:
alx@debian:~/tmp$ cat seq_buf_size_comparison.c #include <stddef.h> #include <stdio.h>
struct seq_buf {
char *buffer;
size_t size;
size_t len;
};
struct alx_seq_buf {
char *buffer;
char *p;
char *end;
};
int
main(void)
{
printf("%zu\n", sizeof(struct seq_buf));
printf("%zu\n", sizeof(struct alx_seq_buf));
}
alx@debian:~/tmp$ gcc -Wall -Wextra seq_buf_size_comparison.c
alx@debian:~/tmp$ ./a.out
24
24size_t and pointers should always have the same size, so this doesn't change the size.
Am I missing something?
Cheers,
Alex
Quoted 7 lines
Yes the
seq_bufcode can be complex, but it's in one location where it can be highly tested and verified. The point ofseq_bufis to do the complexity so that other places in the kernel doesn't need to.-- Steve
Signature · 1 line
<https://www.alejandro-colomar.es>
Steven Rostedt
maintainerQuoted 9 lines · On Thu, 22 Oct 2026 23:02:36 +0200 Alejandro Colomar…
Quoted 2 lines
I rather not increase its size. It is embedded in the
trace_seq.The size shouldn't change:
alx@debian:~/tmp$ cat seq_buf_size_comparison.c #include <stddef.h> #include <stdio.h>
Quoted 11 lines
struct seq_buf {
char *buffer;
size_t size;
size_t len;
};
struct alx_seq_buf {
char *buffer;
char *p;
char *end;
};OK, I was thinking you were adding another pointer.
Quoted 12 lines
int
main(void)
{
printf("%zu\n", sizeof(struct seq_buf));
printf("%zu\n", sizeof(struct alx_seq_buf));
}
alx@debian:~/tmp$ gcc -Wall -Wextra seq_buf_size_comparison.c
alx@debian:~/tmp$ ./a.out
24
24Quoted 12 lines
size_tand pointers should always have the same size, so this doesn't change the size.Am I missing something?
Cheers,
AlexQuoted 3 lines
Yes the
seq_bufcode can be complex, but it's in one location where it can be highly tested and verified. The point ofseq_bufis to do the complexity so that other places in the kernel doesn't need to.
We could look into updating it to pointers later, but right now I rather keep what we have. Let's focus first on the new stuff. It's not that hard to validate.
-- Steve
Hi Steven,
Quoted 3 lines
Date: 2026-10-22 20:37:13-0400
From: Steven Rostedt <[email protected]>
[...]
Quoted 35 lines
Quoted 11 lines
struct seq_buf { char *buffer; size_t size; size_t len; }; struct alx_seq_buf { char *buffer; char *p; char *end; };OK, I was thinking you were adding another pointer.
Quoted 21 lines
int main(void) { printf("%zu\n", sizeof(struct seq_buf)); printf("%zu\n", sizeof(struct alx_seq_buf)); } alx@debian:~/tmp$ gcc -Wall -Wextra seq_buf_size_comparison.c alx@debian:~/tmp$ ./a.out 24 24
size_tand pointers should always have the same size, so this doesn't change the size.Am I missing something?
Cheers,
Alex
Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
Yes the
seq_bufcode can be complex, but it's in one location where it can be highly tested and verified. The point ofseq_bufis to do the complexity so that other places in the kernel doesn't need to.We could look into updating it to pointers later, but right now I rather keep what we have. Let's focus first on the new stuff. It's not that hard to validate.
I agree to doing that later. But I want to do that later. :-)
Have a lovely day!
Alex
Quoted 2 lines
-- Steve
Signature · 1 line
<https://www.alejandro-colomar.es>
Quoted 3 lines
Date: 2026-10-22 22:38:12+0200
From: Alejandro Colomar <[email protected]>
[...]
Quoted 37 lines
No, it doesn't lose data. My proposal is to have this:
struct my_seq_buf { // .buffer points to the start of the buffer char *buffer; /* .p points to the current position. This is the last * '\0' that has been written to the buffer. On * truncation, it is a NULL pointer. */ char *p; /* .end always holds ARRAY_END(buffer). It is a pointer * one-past-the-last character in the buffer. */ char *end; };This would be initialized as (of course, wrapper in static inline functions as needed) this:
char buf[100]; struct seq_buf b; b.buffer = buf; b.p = buf; b.end = ARRAY_END(buf);Then, you would write to it as:
b.p = stpecpy(b.p, b.end, "foo"); b.p = stpecpy(b.p, b.end, "bar"); ... b.p = stpecpy(b.p, b.end, "baz"); if (b.p == NULL) goto fail;
Quoted 8 lines
Then, at some point, you might want to initialize a new buffer out of the remainings of it:
struct seq_buf b2; if (b.p == NULL) goto fail; b2.start = b.p;
We can actually avoid this error by setting up the new buffer to have an empty string if the parent buffer overflowed:
b2.start = b.p ?: b.end-1;
This is probably related to that empty string that has been showing in seq_buf when the string had overflowed, except that here we don't need to point to a new string, but can reuse the old buffer always.
b.end-1 is guaranteed to exist and point to an empty string if b.p==NULL because b.buffer must come from a real array with at least 1 element, and the last element must be a '\0' if the string has overflowed (actually, truncated).
Have a lovely day!
Alex
Quoted 15 lines
b2.p = b.p; b2.end = b.end;See how simple this becomes? (So much that you may doubt the necessity of the set of APIs at all, but they don't hurt much either.)
Cheers,
AlexQuoted 2 lines
-- Steve
Signature · 1 line
<https://www.alejandro-colomar.es>
Signature · 1 line
<https://www.alejandro-colomar.es>
PATCH v5 7/12 seq_buf: Add seq_buf_terminate()
In reply to Kees CookSeven 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.
--- 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 | 36 ++++++++++++++++++++++++++++++++ 5 files changed, 66 insertions(+), 9 deletions(-)
include/linux/seq_buf.h +23 -0
@@ -173,6 +173,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
kernel/bpf/diagnostics.c +3 -3
@@ -351,7 +351,7 @@ static void diag_fmt_restore(struct bpf_verifier_env *env, struct diag_fmt_markif (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; }
kernel/trace/trace_events.c +2 -2
@@ -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 ;
kernel/trace/trace_events_hist.c +2 -4
@@ -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);
lib/tests/seq_buf_kunit.c +36 -0
@@ -632,6 +632,41 @@ 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; + +/* +* seq_buf_putmem() writes no NUL, so only seq_buf_terminate() can +* end these strings early. The NUL in the last byte of buf keeps +* the comparisons in bounds if it does not. +*/ + +/* Terminates directly after the data when there is room. */ +memset (buf,'z' ,sizeof (buf) -1 ); + buf[sizeof (buf) -1 ] ='\0' ; +seq_buf_init (&s, buf,sizeof (buf)); +seq_buf_putmem (&s,"ab" ,2 ); +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) -1 ); + buf[sizeof (buf) -1 ] ='\0' ; +seq_buf_init (&s, buf,4 ); +seq_buf_putmem (&s,"abcdef" ,6 ); +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),
@@ -655,6 +690,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), {} };
Signature · 1 line
2.55.0
PATCH v5 8/12 bpf: Remove dead newline stripping from format_disasm_line()
In reply to Kees Cookformat_disasm_line() strips trailing newlines from the disassembly it stores, as diag_print_insn_context() adds its own. Since commit 483a1bb0b6cf8 ("bpf: Do not print a newline after disassembly in bpf_verbose_insn()"), print_bpf_insn() does not emit one, so there is nothing left to strip.
The strnlen() resync of the seq_buf length only existed so the loop could index the buffer safely after an overflow, and the second termination only restored the NUL that seq_buf_pop() does not write. Remove all of it, keeping a single seq_buf_terminate().
Build tested ARCH=x86_64 defconfig with GCC 16.2.0 and CONFIG_BPF_SYSCALL=y.
--- kernel/bpf/diagnostics.c | 4 ---- 1 file changed, 4 deletions(-)
kernel/bpf/diagnostics.c +0 -4
@@ -632,10 +632,6 @@ static void format_disasm_line(struct bpf_verifier_env *env, int insn_idx,print_bpf_insn (&cbs, insn, env->allow_ptr_leaks);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_terminate (&ctx.seq); line->valid = true; }
Signature · 1 line
2.55.0
PATCH v5 9/12 seq_buf: Add seq_buf_init_append()
In reply to Kees CookFrom: Bill Wendling <[email protected]>
Several strlcat() call sites being converted to seq_buf need behavior seq_buf doesn't currently provide. The normal seq_buf_init() always empties the buffer (sets its "len" to 0) via seq_buf_clear(). Code migrating from strlcat(buf, ...), which appends to whatever buf already contains, can't use seq_buf_init() without discarding that existing content. Add seq_buf_init_append(), which preserves the existing contents and positions the seq_buf to append after it. A buffer with no NUL within its size starts out overflowed, as strlcat() treats it as already truncated. Add KUnit tests for behavior coverage.
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.
--- include/linux/seq_buf.h | 25 ++++++++++++++ lib/tests/seq_buf_kunit.c | 69 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+)
include/linux/seq_buf.h +25 -0
@@ -71,6 +71,31 @@ seq_buf_set_overflow(struct seq_buf *s) s->len = s->size +1 ; } +/** +* seq_buf_init_append - initialize a seq_buf over a buffer that may +* already hold NUL-terminated content +* @s: the seq_buf handle +* @buf: pointer to the (possibly non-empty) buffer +* @size: total size of @buf +* +* Unlike seq_buf_init(), which always starts @buf at len=0, this +* preserves whatever NUL-terminated content @buf already holds and +* positions @s to append after it. Useful for converting code that used +* to append to an existing buffer with strlcat()/scnprintf() and friends. +* +* If @buf holds no NUL within @size, @s starts out overflowed, as +* strlcat() treats such a buffer as already truncated. +*/ +static inline void +seq_buf_init_append (struct seq_buf *s,char *buf,unsigned int size) +{ + s->buffer = buf; + s->size = size; + s->len =strnlen (buf, size); +if (s->len == size) +seq_buf_set_overflow (s); +} +/* * How much buffer is left on the seq_buf? */
lib/tests/seq_buf_kunit.c +69 -0
81 lines not on this page · raw message
Signature · 1 line
2.55.0
Re: [PATCH v5 09/12] seq_buf: Add seq_buf_init_append()
Quoted 15 lines · On Mon, 5 Oct 2026 08:56:59 -0700 Kees Cook <[email protected]> wrote:
From: Bill Wendling <[email protected]>
Several
strlcat()call sites being converted toseq_bufneed behaviorseq_bufdoesn't currently provide. The normalseq_buf_init()always empties the buffer (sets its "len" to 0) viaseq_buf_clear(). Code migrating fromstrlcat(buf, ...), which appends to whatever buf already contains, can't useseq_buf_init()without discarding that existing content. Addseq_buf_init_append(), which preserves the existing contents and positions theseq_bufto append after it. A buffer with no NUL within its size starts out overflowed, asstrlcat()treats it as already truncated. Add KUnit tests for behavior coverage.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.
Quoted 6 lines
Assisted-by LLMSigned-off-by Bill Wendling <[email protected]>Reviewed-by Andy Shevchenko <[email protected]>Co-developed-by Kees Cook <[email protected]>Signed-off-by Kees Cook <[email protected]>---
-- Steve
PATCH v5 10/12 powerpc/papr_scm: Return the string length from the sysfs show functions
In reply to Kees Cookperf_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. As suggested in review, use it as well for flags_show()'s check whether any flag was printed.
Build tested ARCH=powerpc ppc64_defconfig with GCC powerpc64-linux-gnu 16.2.0:arch/powerpc/platforms/pseries/papr_scm.o
--- arch/powerpc/platforms/pseries/papr_scm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
arch/powerpc/platforms/pseries/papr_scm.c +3 -3
@@ -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 ?: (ssize_t )seq_buf_strlen (&s); }static DEVICE_ATTR_ADMIN_RO (perf_stats);
@@ -1147,10 +1147,10 @@ static ssize_t flags_show(struct device *dev,if (health &PAPR_PMEM_SCRUBBED_AND_LOCKED )seq_buf_printf (&s,"scrubbed locked " ); -if (seq_buf_used (&s)) +if (seq_buf_strlen (&s))seq_buf_printf (&s,"\n" ); -return seq_buf_used (&s); +return seq_buf_strlen (&s); }DEVICE_ATTR_RO (flags);
Signature · 1 line
2.55.0
PATCH v5 11/12 nvdimm: ndtest: Return the string length from flags_show()
In reply to Kees Cookflags_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
--- tools/testing/nvdimm/test/ndtest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
tools/testing/nvdimm/test/ndtest.c +1 -1
@@ -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);
Signature · 1 line
2.55.0
PATCH v5 12/12 docs: core-api: Document the seq_buf API
In reply to Kees CookThe kernel-doc in include/linux/seq_buf.h and lib/seq_buf.c documents the seq_buf interface, but no .rst file pulls either of them in, so none of it reaches the generated documentation.
Add the missing kernel-doc for seq_buf_clear() and seq_buf_init(), and a Sequence Buffers section to the kernel API documentation. The static internal helper seq_buf_can_fit() is left out. Additionally fix seq_buf_hex_dump() indentation to avoid the reported Sphinx error:
ERROR: Unexpected indentation.
WARNING: Block quote ends without a blank line; unexpected unindent.
Verified with "make SPHINXDIRS=core-api htmldocs", which rendered happily into core-api/kernel-api.html.
--- Documentation/core-api/kernel-api.rst | 9 +++++++++ include/linux/seq_buf.h | 12 ++++++++++++ lib/seq_buf.c | 13 +++++++------ 3 files changed, 28 insertions(+), 6 deletions(-)
Documentation/core-api/kernel-api.rst +9 -0
@@ -96,6 +96,15 @@ Error Pointers .. kernel-doc:: include/linux/err.h :internal: +Sequence Buffers +---------------- + +.. kernel-doc:: include/linux/seq_buf.h + :internal: + +.. kernel-doc:: lib/seq_buf.c + :no-identifiers: seq_buf_can_fit + Sorting -------
include/linux/seq_buf.h +12 -0
@@ -31,6 +31,10 @@ struct seq_buf { .size = SIZE, \ } +/** +* seq_buf_clear - reset the seq_buf to be read / appended from the beginning +* @s: the seq_buf handle +*/ static inline void seq_buf_clear (struct seq_buf *s) { s->len =0 ;
@@ -38,6 +42,14 @@ static inline void seq_buf_clear(struct seq_buf *s) s->buffer[0 ] ='\0' ; } +/** +* seq_buf_init - initialize a seq_buf +* @s: the seq_buf handle +* @buf: pointer to the buffer +* @size: total size of @buf +* +* The contents of the buffer are ignored. +*/ static inline void seq_buf_init (struct seq_buf *s,char *buf,unsigned int size) {
lib/seq_buf.c +7 -6
@@ -409,12 +409,13 @@ int seq_buf_to_user(struct seq_buf *s, char __user *ubuf, size_t start, int cnt)* * Function is an analogue of print_hex_dump() and thus has similar interface. * -* linebuf size is maximal length for one line. -* 32 * 3 - maximum bytes per line, each printed into 2 chars + 1 for -* separating space -* 2 - spaces separating hex dump and ASCII representation -* 32 - ASCII representation -* 1 - terminating '\0' +* linebuf size is maximal length for one line:: +* +* 32 * 3 - maximum bytes per line, each printed into 2 chars + 1 for +* separating space +* 2 - spaces separating hex dump and ASCII representation +* 32 - ASCII representation +* 1 - terminating '\0' * * Returns: zero on success, -1 on overflow. */
Signature · 1 line
2.55.0
Re: [PATCH v5 12/12] docs: core-api: Document the seq_buf API
Quoted 15 lines · On Mon, 5 Oct 2026 08:57:02 -0700 Kees Cook <[email protected]> wrote:
The kernel-doc in
include/linux/seq_buf.handlib/seq_buf.cdocuments theseq_bufinterface, but no .rst file pulls either of them in, so none of it reaches the generated documentation.Add the missing kernel-doc for
seq_buf_clear()andseq_buf_init(), and a Sequence Buffers section to the kernel API documentation. The static internal helperseq_buf_can_fit()is left out. Additionally fixseq_buf_hex_dump()indentation to avoid the reported Sphinx error:ERROR: Unexpected indentation.
WARNING: Block quote ends without a blank line; unexpected unindent.Verified with "make SPHINXDIRS=core-api htmldocs", which rendered happily into core-api/kernel-api.html.
Quoted 7 lines
Assisted-by LLMCo-developed-by Bill Wendling <[email protected]>Signed-off-by Bill Wendling <[email protected]>Tested-by Randy Dunlap <[email protected]>Reviewed-by Randy Dunlap <[email protected]>Signed-off-by Kees Cook <[email protected]>---
-- Steve
Kees Cook
AppliedQuoted 10 lines · Kees Cook
While working on
seq_bufconversions[1], we found there was a need forseq_buf_strlen()(since calling strlen(seq_buf_str()) would be a waste of time:seq_bufalready knows the length),seq_buf_init_append()(to start aseq_buffrom an existing string buffer), andseq_buf_terminate()(as there are many callers usingseq_buf_str()just for its termination side-effect). And while implementing that, we found a bunch of other related things that needed fixing. This is that ever-growing series, with tests for each fix.[...]
Applied to for-next/hardening.
[01/12] seq_buf: Do not print an empty line from an overflowed seq_buf_do_printk()
https://git.kernel.org/kees/c/a09fb764832d [02/12] seq_buf: Do not pop from an overflowed seq_buf https://git.kernel.org/kees/c/b7e688b5635a [03/12] scsi: lpfc: Print rx monitor records straight into the seq_buf https://git.kernel.org/kees/c/cc9d25b45119 [04/12] seq_buf: Copy what fits when seq_buf_puts() and seq_buf_putmem() overflow https://git.kernel.org/kees/c/505a4aeea2c9 [05/12] seq_buf: Clear what a writer did not claim when a seq_buf overflows https://git.kernel.org/kees/c/ea5666e56723 [06/12] seq_buf: Add seq_buf_strlen() https://git.kernel.org/kees/c/8d1478bfd63b [07/12] seq_buf: Add seq_buf_terminate() https://git.kernel.org/kees/c/88c652df02cc [08/12] bpf: Remove dead newline stripping from format_disasm_line() https://git.kernel.org/kees/c/c8cd1725d259 [09/12] seq_buf: Add seq_buf_init_append() https://git.kernel.org/kees/c/61ef3f1490f8 [10/12] powerpc/papr_scm: Return the string length from the sysfs show functions https://git.kernel.org/kees/c/63ae65caa98a [11/12] nvdimm: ndtest: Return the string length from flags_show() https://git.kernel.org/kees/c/9ac219976ecf [12/12] docs: core-api: Document the seq_buf API https://git.kernel.org/kees/c/e3f8dfafd7d6
Take care,
Signature · 1 line
Kees Cook