summaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
authorKees Cook <kees@kernel.org>2026-10-05 08:56:56 -0700
committerKees Cook <kees@kernel.org>2026-10-11 21:57:29 -0700
commit8d1478bfd63be0f065defd1066d2fa294f419ae2 (patch)
tree326d90ef34e55602e2d55110d23f5153666b78ed /include
parentea5666e56723e9ac7094ff9849cf154dcb0e436e (diff)
downloadlinux-next-8d1478bfd63be0f065defd1066d2fa294f419ae2.tar.gz
linux-next-8d1478bfd63be0f065defd1066d2fa294f419ae2.zip
seq_buf: Add seq_buf_strlen()
Several strlcat() call sites being converted to seq_buf need behavior seq_buf doesn't currently provide. The return from seq_buf_used() is not the length of the string in a seq_buf. Once the buffer is full or has overflowed it returns the buffer size, which counts the byte that seq_buf_str() replaces with the NUL, so a caller that needs the string and its length has to call seq_buf_str() and then walk the string with strlen(). Move the termination out of seq_buf_str() into a helper that returns where it put the NUL, and add seq_buf_strlen(), which terminates the buffer in the same way and returns that offset. As discussed in review, don't add WARN_ON() for seq_buf_strlen() and drop it from seq_buf_str(). Add tests comparing seq_buf_strlen() against strlen() of seq_buf_str() for empty, appended, truncated, exactly full, and overflowed buffers, checking that seq_buf_strlen() alone terminates a full buffer, and checking that a zero-sized seq_buf reports an empty string from both accessors without touching the buffer. Tests passed under qemu on ARCH=x86_64 with GCC 16.2.0 and CONFIG_KASAN=y, and on big-endian ARCH=s390 with GCC s390x-linux-gnu 16.2.0. Assisted-by: LLM Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Link: https://patch.msgid.link/20261005155708.1471260-6-kees@kernel.org Signed-off-by: Kees Cook <kees@kernel.org>
Diffstat (limited to 'include')
-rw-r--r--include/linux/seq_buf.h67
1 files changed, 61 insertions, 6 deletions
diff --git a/include/linux/seq_buf.h b/include/linux/seq_buf.h
index 0c0a0db04b09..7abeca1c166b 100644
--- a/include/linux/seq_buf.h
+++ b/include/linux/seq_buf.h
@@ -89,6 +89,27 @@ static inline unsigned int seq_buf_used(struct seq_buf *s)
return min(s->len, s->size);
}
+/*
+ * NUL-terminate the buffer in @s: directly after the data when there is
+ * room for it, otherwise in the last byte of the buffer. @s->size must not
+ * be zero.
+ *
+ * Returns: the offset of the NUL.
+ */
+static inline size_t __seq_buf_terminate(struct seq_buf *s)
+{
+ size_t end;
+
+ if (seq_buf_buffer_left(s))
+ end = s->len;
+ else
+ end = s->size - 1;
+
+ s->buffer[end] = 0;
+
+ return end;
+}
+
/**
* seq_buf_str - get NUL-terminated C string from seq_buf
* @s: the seq_buf handle
@@ -98,7 +119,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,18 +135,45 @@ 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
* @bufp: the beginning of the buffer is stored here