* [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs
@ 2025-07-05 20:33 Alejandro Colomar
2025-07-05 20:33 ` [RFC v1 1/3] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
` (4 more replies)
0 siblings, 5 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-05 20:33 UTC (permalink / raw)
To: linux-mm, linux-hardening, Kees Cook
Cc: Alejandro Colomar, Christopher Bazley, shadow, linux-kernel,
Andrew Morton, kasan-dev, Dmitry Vyukov, Alexander Potapenko,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo
Hi Kees,
As I anticipated in private, here's an API that we're using in the
shadow project. I've added it in the kernel, and started replacing some
existing calls to s*printf() calls, and it was a bug mine.
I haven't even built the code yet. I present it for discussion only at
the moment. (Thus, RFC, not PATCH.) Also, I've used ==NULL style for
null checks, to be more explicit, even if that's against the coding
style. I'll change that for the actual patches, but for now during
discussion, I prefer having the explicit tests for my own readability.
The improvement seems quite obvious. Please let me know your opinion.
I also have a few questions for the maintainers of the specific code, or
at least for someone who deeply understands it, as I found some
questionable code. (See the individual commit messages, and code
comments for those.)
On top of that, I have a question about the functions I'm adding,
and the existing kernel snprintf(3): The standard snprintf(3)
can fail (return -1), but the kernel one doesn't seem to return <0 ever.
Should I assume that snprintf(3) doesn't fail here? (I have a comment
in the source code of the implementation asking for that.)
What do you think?
Alejandro Colomar (3):
vsprintf: Add [v]seprintf(), [v]stprintf()
stacktrace, stackdepot: Add seprintf()-like variants of functions
mm: Use seprintf() instead of less ergonomic APIs
include/linux/stackdepot.h | 13 +++++
include/linux/stacktrace.h | 3 +
kernel/stacktrace.c | 28 ++++++++++
lib/stackdepot.c | 12 ++++
lib/vsprintf.c | 109 +++++++++++++++++++++++++++++++++++++
mm/kfence/kfence_test.c | 24 ++++----
mm/kmsan/kmsan_test.c | 4 +-
mm/mempolicy.c | 18 +++---
mm/page_owner.c | 32 ++++++-----
mm/slub.c | 5 +-
10 files changed, 208 insertions(+), 40 deletions(-)
Range-diff against v0:
-: ------------ > 1: 2d20eaf1752e vsprintf: Add [v]seprintf(), [v]stprintf()
-: ------------ > 2: ec2e375c2d1e stacktrace, stackdepot: Add seprintf()-like variants of functions
-: ------------ > 3: be193e1856aa mm: Use seprintf() instead of less ergonomic APIs
--
2.50.0
^ permalink raw reply [flat|nested] 98+ messages in thread
* [RFC v1 1/3] vsprintf: Add [v]seprintf(), [v]stprintf()
2025-07-05 20:33 [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-05 20:33 ` Alejandro Colomar
2025-07-05 20:40 ` Alejandro Colomar
2025-07-07 9:47 ` Alexander Potapenko
2025-07-05 20:33 ` [RFC v1 2/3] stacktrace, stackdepot: Add seprintf()-like variants of functions Alejandro Colomar
` (3 subsequent siblings)
4 siblings, 2 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-05 20:33 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo
seprintf()
==========
seprintf() is a function similar to stpcpy(3) in the sense that it
returns a pointer that is suitable for chaining to other copy
operations.
It takes a pointer to the end of the buffer as a sentinel for when to
truncate, which unlike a size, doesn't need to be updated after every
call. This makes it much more ergonomic, avoiding manually calculating
the size after each copy, which is error prone.
It also makes error handling much easier, by reporting truncation with
a null pointer, which is accepted and transparently passed down by
subsequent seprintf() calls. This results in only needing to report
errors once after a chain of seprintf() calls, unlike snprintf(3), which
requires checking after every call.
p = buf;
e = buf + countof(buf);
p = seprintf(p, e, foo);
p = seprintf(p, e, bar);
if (p == NULL)
goto trunc;
vs
len = 0;
size = countof(buf);
len += snprintf(buf + len, size - len, foo);
if (len >= size)
goto trunc;
len += snprintf(buf + len, size - len, bar);
if (len >= size)
goto trunc;
And also better than scnprintf() calls:
len = 0;
size = countof(buf);
len += scnprintf(buf + len, size - len, foo);
len += scnprintf(buf + len, size - len, bar);
if (len >= size)
goto trunc;
It seems aparent that it's a more elegant approach to string catenation.
stprintf()
==========
stprintf() is a helper that is needed for implementing seprintf()
--although it could be open-coded within vseprintf(), of course--, but
it's also useful by itself. It has the same interface properties as
strscpy(): that is, it copies with truncation, and reports truncation
with -E2BIG. It would be useful to replace some calls to snprintf(3)
and scnprintf() which don't need chaining, and where it's simpler to
pass a size.
It is better than plain snprintf(3), because it results in simpler error
detection (it doesn't need a check >=countof(buf), but rather <0).
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
lib/vsprintf.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 109 insertions(+)
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 01699852f30c..a3efacadb5e5 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -2892,6 +2892,37 @@ int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
}
EXPORT_SYMBOL(vsnprintf);
+/**
+ * vstprintf - Format a string and place it in a buffer
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
+ *
+ * The return value is the length of the new string.
+ * If the string is truncated, the function returns -E2BIG.
+ *
+ * If you're not already dealing with a va_list consider using stprintf().
+ *
+ * See the vsnprintf() documentation for format string extensions over C99.
+ */
+int vstprintf(char *buf, size_t size, const char *fmt, va_list args)
+{
+ int len;
+
+ len = vsnprintf(buf, size, fmt, args);
+
+ // It seems the kernel's vsnprintf() doesn't fail?
+ //if (unlikely(len < 0))
+ // return -E2BIG;
+
+ if (unlikely(len >= size))
+ return -E2BIG;
+
+ return len;
+}
+EXPORT_SYMBOL(vstprintf);
+
/**
* vscnprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -2923,6 +2954,36 @@ int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
}
EXPORT_SYMBOL(vscnprintf);
+/**
+ * vseprintf - Format a string and place it in a buffer
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
+ *
+ * The return value is a pointer to the trailing '\0'.
+ * If @p is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
+ *
+ * If you're not already dealing with a va_list consider using seprintf().
+ *
+ * See the vsnprintf() documentation for format string extensions over C99.
+ */
+char *vseprintf(char *p, const char end[0], const char *fmt, va_list args)
+{
+ int len;
+
+ if (unlikely(p == NULL))
+ return NULL;
+
+ len = vstprintf(p, end - p, fmt, args);
+ if (unlikely(len < 0))
+ return NULL;
+
+ return p + len;
+}
+EXPORT_SYMBOL(vseprintf);
+
/**
* snprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -2950,6 +3011,30 @@ int snprintf(char *buf, size_t size, const char *fmt, ...)
}
EXPORT_SYMBOL(snprintf);
+/**
+ * stprintf - Format a string and place it in a buffer
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
+ *
+ * The return value is the length of the new string.
+ * If the string is truncated, the function returns -E2BIG.
+ */
+
+int stprintf(char *buf, size_t size, const char *fmt, ...)
+{
+ va_list args;
+ int len;
+
+ va_start(args, fmt);
+ len = vstprintf(buf, size, fmt, args);
+ va_end(args);
+
+ return len;
+}
+EXPORT_SYMBOL(stprintf);
+
/**
* scnprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -2974,6 +3059,30 @@ int scnprintf(char *buf, size_t size, const char *fmt, ...)
}
EXPORT_SYMBOL(scnprintf);
+/**
+ * seprintf - Format a string and place it in a buffer
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
+ *
+ * The return value is a pointer to the trailing '\0'.
+ * If @buf is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
+ */
+
+char *seprintf(char *p, const char end[0], const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ p = vseprintf(p, end, fmt, args);
+ va_end(args);
+
+ return p;
+}
+EXPORT_SYMBOL(seprintf);
+
/**
* vsprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v1 2/3] stacktrace, stackdepot: Add seprintf()-like variants of functions
2025-07-05 20:33 [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
2025-07-05 20:33 ` [RFC v1 1/3] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
@ 2025-07-05 20:33 ` Alejandro Colomar
2025-07-05 20:33 ` [RFC v1 3/3] mm: Use seprintf() instead of less ergonomic APIs Alejandro Colomar
` (2 subsequent siblings)
4 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-05 20:33 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo
I think there's an anomaly in stack_depot_s*print(). If we have zero
entries, we don't copy anything, which means the string is still not a
string. Normally, this function is called surrounded by other calls to
s*printf(), which guarantee that there's a '\0', but maybe we should
make sure to write a '\0' here?
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/stackdepot.h | 13 +++++++++++++
include/linux/stacktrace.h | 3 +++
kernel/stacktrace.c | 28 ++++++++++++++++++++++++++++
lib/stackdepot.c | 12 ++++++++++++
4 files changed, 56 insertions(+)
diff --git a/include/linux/stackdepot.h b/include/linux/stackdepot.h
index 2cc21ffcdaf9..a7749fc3ac7c 100644
--- a/include/linux/stackdepot.h
+++ b/include/linux/stackdepot.h
@@ -219,6 +219,19 @@ void stack_depot_print(depot_stack_handle_t stack);
int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
int spaces);
+/**
+ * stack_depot_seprint - Print a stack trace from stack depot into a buffer
+ *
+ * @handle: Stack depot handle returned from stack_depot_save()
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @spaces: Number of leading spaces to print
+ *
+ * Return: Pointer to trailing '\0'; or NULL on truncation
+ */
+char *stack_depot_seprint(depot_stack_handle_t handle, char *p,
+ const char end[0], int spaces);
+
/**
* stack_depot_put - Drop a reference to a stack trace from stack depot
*
diff --git a/include/linux/stacktrace.h b/include/linux/stacktrace.h
index 97455880ac41..748936386c89 100644
--- a/include/linux/stacktrace.h
+++ b/include/linux/stacktrace.h
@@ -67,6 +67,9 @@ void stack_trace_print(const unsigned long *trace, unsigned int nr_entries,
int spaces);
int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
unsigned int nr_entries, int spaces);
+char *stack_trace_seprint(char *p, const char end[0],
+ const unsigned long *entries, unsigned int nr_entries,
+ int spaces);
unsigned int stack_trace_save(unsigned long *store, unsigned int size,
unsigned int skipnr);
unsigned int stack_trace_save_tsk(struct task_struct *task,
diff --git a/kernel/stacktrace.c b/kernel/stacktrace.c
index afb3c116da91..65caf9e63673 100644
--- a/kernel/stacktrace.c
+++ b/kernel/stacktrace.c
@@ -70,6 +70,34 @@ int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
}
EXPORT_SYMBOL_GPL(stack_trace_snprint);
+/**
+ * stack_trace_seprint - Print the entries in the stack trace into a buffer
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @entries: Pointer to storage array
+ * @nr_entries: Number of entries in the storage array
+ * @spaces: Number of leading spaces to print
+ *
+ * Return: Pointer to the trailing '\0'; or NULL on truncation.
+ */
+char *stack_trace_seprint(char *p, const char end[0],
+ const unsigned long *entries, unsigned int nr_entries,
+ int spaces)
+{
+ unsigned int i;
+
+ if (WARN_ON(!entries))
+ return 0;
+
+ for (i = 0; i < nr_entries; i++) {
+ p = seprintf(p, end, "%*c%pS\n", 1 + spaces, ' ',
+ (void *)entries[i]);
+ }
+
+ return p;
+}
+EXPORT_SYMBOL_GPL(stack_trace_seprint);
+
#ifdef CONFIG_ARCH_STACKWALK
struct stacktrace_cookie {
diff --git a/lib/stackdepot.c b/lib/stackdepot.c
index 73d7b50924ef..a61903040724 100644
--- a/lib/stackdepot.c
+++ b/lib/stackdepot.c
@@ -771,6 +771,18 @@ int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
}
EXPORT_SYMBOL_GPL(stack_depot_snprint);
+char *stack_depot_seprint(depot_stack_handle_t handle, char *p,
+ const char end[0], int spaces)
+{
+ unsigned long *entries;
+ unsigned int nr_entries;
+
+ nr_entries = stack_depot_fetch(handle, &entries);
+ return nr_entries ? stack_trace_seprint(p, e, entries, nr_entries,
+ spaces) : p;
+}
+EXPORT_SYMBOL_GPL(stack_depot_seprint);
+
depot_stack_handle_t __must_check stack_depot_set_extra_bits(
depot_stack_handle_t handle, unsigned int extra_bits)
{
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v1 3/3] mm: Use seprintf() instead of less ergonomic APIs
2025-07-05 20:33 [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
2025-07-05 20:33 ` [RFC v1 1/3] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
2025-07-05 20:33 ` [RFC v1 2/3] stacktrace, stackdepot: Add seprintf()-like variants of functions Alejandro Colomar
@ 2025-07-05 20:33 ` Alejandro Colomar
2025-07-05 21:54 ` Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 0/5] Add and use " Alejandro Colomar
2025-07-08 6:43 ` [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs Rasmus Villemoes
4 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-05 20:33 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo
While doing this, I detected some anomalies in the existing code:
mm/kfence/kfence_test.c:
The last call to scnprintf() did increment 'cur', but it's
unused after that, so it was dead code. I've removed the dead
code in this patch.
mm/mempolicy.c:
This file uses the 'p += snprintf()' anti-pattern. That will
overflow the pointer on truncation, which has undefined
behavior. Using seprintf(), this bug is fixed.
As in the previous file, here there was also dead code in the
last scnprintf() call, by incrementing a pointer that is not
used after the call. I've removed the dead code.
mm/page_owner.c:
Within print_page_owner(), there are some calls to scnprintf(),
which do report truncation. And then there are other calls to
snprintf(), where we handle errors (there are two 'goto err').
I've kept the existing error handling, as I trust it's there for
a good reason (i.e., we may want to avoid calling
print_page_owner_memcg() if we truncated before). Please review
if this amount of error handling is the right one, or if we want
to add or remove some. For seprintf(), a single test for null
after the last call is enough to detect truncation.
mm/slub.c:
Again, the 'p += snprintf()' anti-pattern. This is UB, and by
using seprintf() we've fixed the bug.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/kfence/kfence_test.c | 24 ++++++++++++------------
mm/kmsan/kmsan_test.c | 4 ++--
mm/mempolicy.c | 18 +++++++++---------
mm/page_owner.c | 32 +++++++++++++++++---------------
mm/slub.c | 5 +++--
5 files changed, 43 insertions(+), 40 deletions(-)
diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
index 00034e37bc9f..ff734c514c03 100644
--- a/mm/kfence/kfence_test.c
+++ b/mm/kfence/kfence_test.c
@@ -113,26 +113,26 @@ static bool report_matches(const struct expect_report *r)
end = &expect[0][sizeof(expect[0]) - 1];
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds %s",
+ cur = seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s",
get_access_type(r));
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free %s",
+ cur = seprintf(cur, end, "BUG: KFENCE: use-after-free %s",
get_access_type(r));
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: memory corruption");
+ cur = seprintf(cur, end, "BUG: KFENCE: memory corruption");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid %s",
+ cur = seprintf(cur, end, "BUG: KFENCE: invalid %s",
get_access_type(r));
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid free");
+ cur = seprintf(cur, end, "BUG: KFENCE: invalid free");
break;
}
- scnprintf(cur, end - cur, " in %pS", r->fn);
+ seprintf(cur, end, " in %pS", r->fn);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expect[0], '+');
if (cur)
@@ -144,26 +144,26 @@ static bool report_matches(const struct expect_report *r)
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "Out-of-bounds %s at", get_access_type(r));
+ cur = seprintf(cur, end, "Out-of-bounds %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "Use-after-free %s at", get_access_type(r));
+ cur = seprintf(cur, end, "Use-after-free %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "Corrupted memory at");
+ cur = seprintf(cur, end, "Corrupted memory at");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "Invalid %s at", get_access_type(r));
+ cur = seprintf(cur, end, "Invalid %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "Invalid free of");
+ cur = seprintf(cur, end, "Invalid free of");
break;
}
- cur += scnprintf(cur, end - cur, " 0x%p", (void *)addr);
+ seprintf(cur, end, " 0x%p", (void *)addr);
spin_lock_irqsave(&observed.lock, flags);
if (!report_available())
diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
index 9733a22c46c1..a062a46b2d24 100644
--- a/mm/kmsan/kmsan_test.c
+++ b/mm/kmsan/kmsan_test.c
@@ -107,9 +107,9 @@ static bool report_matches(const struct expect_report *r)
cur = expected_header;
end = &expected_header[sizeof(expected_header) - 1];
- cur += scnprintf(cur, end - cur, "BUG: KMSAN: %s", r->error_type);
+ cur = seprintf(cur, end, "BUG: KMSAN: %s", r->error_type);
- scnprintf(cur, end - cur, " in %s", r->symbol);
+ seprintf(cur, end, " in %s", r->symbol);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expected_header, '+');
if (cur)
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index b28a1e6ae096..c696e4a6f4c2 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -3359,6 +3359,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
{
char *p = buffer;
+ char *e = buffer + maxlen;
nodemask_t nodes = NODE_MASK_NONE;
unsigned short mode = MPOL_DEFAULT;
unsigned short flags = 0;
@@ -3384,33 +3385,32 @@ void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
break;
default:
WARN_ON_ONCE(1);
- snprintf(p, maxlen, "unknown");
+ seprintf(p, e, "unknown");
return;
}
- p += snprintf(p, maxlen, "%s", policy_modes[mode]);
+ p = seprintf(p, e, "%s", policy_modes[mode]);
if (flags & MPOL_MODE_FLAGS) {
- p += snprintf(p, buffer + maxlen - p, "=");
+ p = seprintf(p, e, "=");
/*
* Static and relative are mutually exclusive.
*/
if (flags & MPOL_F_STATIC_NODES)
- p += snprintf(p, buffer + maxlen - p, "static");
+ p = seprintf(p, e, "static");
else if (flags & MPOL_F_RELATIVE_NODES)
- p += snprintf(p, buffer + maxlen - p, "relative");
+ p = seprintf(p, e, "relative");
if (flags & MPOL_F_NUMA_BALANCING) {
if (!is_power_of_2(flags & MPOL_MODE_FLAGS))
- p += snprintf(p, buffer + maxlen - p, "|");
- p += snprintf(p, buffer + maxlen - p, "balancing");
+ p = seprintf(p, e, "|");
+ p = seprintf(p, e, "balancing");
}
}
if (!nodes_empty(nodes))
- p += scnprintf(p, buffer + maxlen - p, ":%*pbl",
- nodemask_pr_args(&nodes));
+ seprintf(p, e, ":%*pbl", nodemask_pr_args(&nodes));
}
#ifdef CONFIG_SYSFS
diff --git a/mm/page_owner.c b/mm/page_owner.c
index cc4a6916eec6..5811738e3320 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -496,7 +496,7 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
/*
* Looking for memcg information and print it out
*/
-static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
+static inline char *print_page_owner_memcg(char *p, const char end[0],
struct page *page)
{
#ifdef CONFIG_MEMCG
@@ -511,8 +511,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
goto out_unlock;
if (memcg_data & MEMCG_DATA_OBJEXTS)
- ret += scnprintf(kbuf + ret, count - ret,
- "Slab cache page\n");
+ p = seprintf(p, end, "Slab cache page\n");
memcg = page_memcg_check(page);
if (!memcg)
@@ -520,7 +519,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
online = (memcg->css.flags & CSS_ONLINE);
cgroup_name(memcg->css.cgroup, name, sizeof(name));
- ret += scnprintf(kbuf + ret, count - ret,
+ p = seprintf(p, end,
"Charged %sto %smemcg %s\n",
PageMemcgKmem(page) ? "(via objcg) " : "",
online ? "" : "offline ",
@@ -529,7 +528,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
rcu_read_unlock();
#endif /* CONFIG_MEMCG */
- return ret;
+ return p;
}
static ssize_t
@@ -538,14 +537,16 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
depot_stack_handle_t handle)
{
int ret, pageblock_mt, page_mt;
- char *kbuf;
+ char *kbuf, *p, *e;
count = min_t(size_t, count, PAGE_SIZE);
kbuf = kmalloc(count, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
- ret = scnprintf(kbuf, count,
+ p = kbuf;
+ e = kbuf + count;
+ p = seprintf(p, e,
"Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n",
page_owner->order, page_owner->gfp_mask,
&page_owner->gfp_mask, page_owner->pid,
@@ -555,7 +556,7 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
/* Print information relevant to grouping pages by mobility */
pageblock_mt = get_pageblock_migratetype(page);
page_mt = gfp_migratetype(page_owner->gfp_mask);
- ret += scnprintf(kbuf + ret, count - ret,
+ p = seprintf(p, e,
"PFN 0x%lx type %s Block %lu type %s Flags %pGp\n",
pfn,
migratetype_names[page_mt],
@@ -563,22 +564,23 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
migratetype_names[pageblock_mt],
&page->flags);
- ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
- if (ret >= count)
- goto err;
+ p = stack_depot_seprint(handle, p, e, 0);
+ if (p == NULL)
+ goto err; // XXX: Should we remove this error handling?
if (page_owner->last_migrate_reason != -1) {
- ret += scnprintf(kbuf + ret, count - ret,
+ p = seprintf(p, e,
"Page has been migrated, last migrate reason: %s\n",
migrate_reason_names[page_owner->last_migrate_reason]);
}
- ret = print_page_owner_memcg(kbuf, count, ret, page);
+ p = print_page_owner_memcg(p, e, page);
- ret += snprintf(kbuf + ret, count - ret, "\n");
- if (ret >= count)
+ p = seprintf(p, e, "\n");
+ if (p == NULL)
goto err;
+ ret = p - kbuf;
if (copy_to_user(buf, kbuf, ret))
ret = -EFAULT;
diff --git a/mm/slub.c b/mm/slub.c
index be8b09e09d30..b67c6ca0d0f7 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -7451,6 +7451,7 @@ static char *create_unique_id(struct kmem_cache *s)
{
char *name = kmalloc(ID_STR_LENGTH, GFP_KERNEL);
char *p = name;
+ char *e = name + ID_STR_LENGTH;
if (!name)
return ERR_PTR(-ENOMEM);
@@ -7475,9 +7476,9 @@ static char *create_unique_id(struct kmem_cache *s)
*p++ = 'A';
if (p != name + 1)
*p++ = '-';
- p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
+ p = seprintf(p, e, "%07u", s->size);
- if (WARN_ON(p > name + ID_STR_LENGTH - 1)) {
+ if (WARN_ON(p == NULL)) {
kfree(name);
return ERR_PTR(-EINVAL);
}
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* Re: [RFC v1 1/3] vsprintf: Add [v]seprintf(), [v]stprintf()
2025-07-05 20:33 ` [RFC v1 1/3] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
@ 2025-07-05 20:40 ` Alejandro Colomar
2025-07-07 9:47 ` Alexander Potapenko
1 sibling, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-05 20:40 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Kees Cook, Christopher Bazley, shadow, linux-kernel,
Andrew Morton, kasan-dev, Dmitry Vyukov, Alexander Potapenko,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo
[-- Attachment #1: Type: text/plain, Size: 7259 bytes --]
On Sat, Jul 05, 2025 at 10:33:49PM +0200, Alejandro Colomar wrote:
> seprintf()
> ==========
>
> seprintf() is a function similar to stpcpy(3) in the sense that it
> returns a pointer that is suitable for chaining to other copy
> operations.
>
> It takes a pointer to the end of the buffer as a sentinel for when to
> truncate, which unlike a size, doesn't need to be updated after every
> call. This makes it much more ergonomic, avoiding manually calculating
> the size after each copy, which is error prone.
>
> It also makes error handling much easier, by reporting truncation with
> a null pointer, which is accepted and transparently passed down by
> subsequent seprintf() calls. This results in only needing to report
> errors once after a chain of seprintf() calls, unlike snprintf(3), which
> requires checking after every call.
>
> p = buf;
> e = buf + countof(buf);
> p = seprintf(p, e, foo);
> p = seprintf(p, e, bar);
> if (p == NULL)
> goto trunc;
>
> vs
>
> len = 0;
> size = countof(buf);
> len += snprintf(buf + len, size - len, foo);
> if (len >= size)
> goto trunc;
>
> len += snprintf(buf + len, size - len, bar);
> if (len >= size)
> goto trunc;
>
> And also better than scnprintf() calls:
>
> len = 0;
> size = countof(buf);
> len += scnprintf(buf + len, size - len, foo);
> len += scnprintf(buf + len, size - len, bar);
> if (len >= size)
> goto trunc;
Oops, this error handling is incorrect, as scnprintf() doesn't report
truncation. I should have compared
p = buf;
e = buf + countof(buf);
p = seprintf(p, e, foo);
p = seprintf(p, e, bar);
vs
len = 0;
size = countof(buf);
len += scnprintf(buf + len, size - len, foo);
len += scnprintf(buf + len, size - len, bar);
>
> It seems aparent that it's a more elegant approach to string catenation.
>
> stprintf()
> ==========
>
> stprintf() is a helper that is needed for implementing seprintf()
> --although it could be open-coded within vseprintf(), of course--, but
> it's also useful by itself. It has the same interface properties as
> strscpy(): that is, it copies with truncation, and reports truncation
> with -E2BIG. It would be useful to replace some calls to snprintf(3)
> and scnprintf() which don't need chaining, and where it's simpler to
> pass a size.
>
> It is better than plain snprintf(3), because it results in simpler error
> detection (it doesn't need a check >=countof(buf), but rather <0).
>
> Cc: Kees Cook <kees@kernel.org>
> Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
> Signed-off-by: Alejandro Colomar <alx@kernel.org>
> ---
> lib/vsprintf.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 109 insertions(+)
>
> diff --git a/lib/vsprintf.c b/lib/vsprintf.c
> index 01699852f30c..a3efacadb5e5 100644
> --- a/lib/vsprintf.c
> +++ b/lib/vsprintf.c
> @@ -2892,6 +2892,37 @@ int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
> }
> EXPORT_SYMBOL(vsnprintf);
>
> +/**
> + * vstprintf - Format a string and place it in a buffer
> + * @buf: The buffer to place the result into
> + * @size: The size of the buffer, including the trailing null space
> + * @fmt: The format string to use
> + * @args: Arguments for the format string
> + *
> + * The return value is the length of the new string.
> + * If the string is truncated, the function returns -E2BIG.
> + *
> + * If you're not already dealing with a va_list consider using stprintf().
> + *
> + * See the vsnprintf() documentation for format string extensions over C99.
> + */
> +int vstprintf(char *buf, size_t size, const char *fmt, va_list args)
> +{
> + int len;
> +
> + len = vsnprintf(buf, size, fmt, args);
> +
> + // It seems the kernel's vsnprintf() doesn't fail?
> + //if (unlikely(len < 0))
> + // return -E2BIG;
> +
> + if (unlikely(len >= size))
> + return -E2BIG;
> +
> + return len;
> +}
> +EXPORT_SYMBOL(vstprintf);
> +
> /**
> * vscnprintf - Format a string and place it in a buffer
> * @buf: The buffer to place the result into
> @@ -2923,6 +2954,36 @@ int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
> }
> EXPORT_SYMBOL(vscnprintf);
>
> +/**
> + * vseprintf - Format a string and place it in a buffer
> + * @p: The buffer to place the result into
> + * @end: A pointer to one past the last character in the buffer
> + * @fmt: The format string to use
> + * @args: Arguments for the format string
> + *
> + * The return value is a pointer to the trailing '\0'.
> + * If @p is NULL, the function returns NULL.
> + * If the string is truncated, the function returns NULL.
> + *
> + * If you're not already dealing with a va_list consider using seprintf().
> + *
> + * See the vsnprintf() documentation for format string extensions over C99.
> + */
> +char *vseprintf(char *p, const char end[0], const char *fmt, va_list args)
> +{
> + int len;
> +
> + if (unlikely(p == NULL))
> + return NULL;
> +
> + len = vstprintf(p, end - p, fmt, args);
> + if (unlikely(len < 0))
> + return NULL;
> +
> + return p + len;
> +}
> +EXPORT_SYMBOL(vseprintf);
> +
> /**
> * snprintf - Format a string and place it in a buffer
> * @buf: The buffer to place the result into
> @@ -2950,6 +3011,30 @@ int snprintf(char *buf, size_t size, const char *fmt, ...)
> }
> EXPORT_SYMBOL(snprintf);
>
> +/**
> + * stprintf - Format a string and place it in a buffer
> + * @buf: The buffer to place the result into
> + * @size: The size of the buffer, including the trailing null space
> + * @fmt: The format string to use
> + * @...: Arguments for the format string
> + *
> + * The return value is the length of the new string.
> + * If the string is truncated, the function returns -E2BIG.
> + */
> +
> +int stprintf(char *buf, size_t size, const char *fmt, ...)
> +{
> + va_list args;
> + int len;
> +
> + va_start(args, fmt);
> + len = vstprintf(buf, size, fmt, args);
> + va_end(args);
> +
> + return len;
> +}
> +EXPORT_SYMBOL(stprintf);
> +
> /**
> * scnprintf - Format a string and place it in a buffer
> * @buf: The buffer to place the result into
> @@ -2974,6 +3059,30 @@ int scnprintf(char *buf, size_t size, const char *fmt, ...)
> }
> EXPORT_SYMBOL(scnprintf);
>
> +/**
> + * seprintf - Format a string and place it in a buffer
> + * @p: The buffer to place the result into
> + * @end: A pointer to one past the last character in the buffer
> + * @fmt: The format string to use
> + * @...: Arguments for the format string
> + *
> + * The return value is a pointer to the trailing '\0'.
> + * If @buf is NULL, the function returns NULL.
> + * If the string is truncated, the function returns NULL.
> + */
> +
> +char *seprintf(char *p, const char end[0], const char *fmt, ...)
> +{
> + va_list args;
> +
> + va_start(args, fmt);
> + p = vseprintf(p, end, fmt, args);
> + va_end(args);
> +
> + return p;
> +}
> +EXPORT_SYMBOL(seprintf);
> +
> /**
> * vsprintf - Format a string and place it in a buffer
> * @buf: The buffer to place the result into
> --
> 2.50.0
>
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v1 3/3] mm: Use seprintf() instead of less ergonomic APIs
2025-07-05 20:33 ` [RFC v1 3/3] mm: Use seprintf() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-05 21:54 ` Alejandro Colomar
0 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-05 21:54 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Kees Cook, Christopher Bazley, shadow, linux-kernel,
Andrew Morton, kasan-dev, Dmitry Vyukov, Alexander Potapenko,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo
[-- Attachment #1: Type: text/plain, Size: 11784 bytes --]
On Sat, Jul 05, 2025 at 10:33:53PM +0200, Alejandro Colomar wrote:
> While doing this, I detected some anomalies in the existing code:
>
> mm/kfence/kfence_test.c:
>
> The last call to scnprintf() did increment 'cur', but it's
> unused after that, so it was dead code. I've removed the dead
> code in this patch.
>
> mm/mempolicy.c:
>
> This file uses the 'p += snprintf()' anti-pattern. That will
> overflow the pointer on truncation, which has undefined
> behavior. Using seprintf(), this bug is fixed.
>
> As in the previous file, here there was also dead code in the
> last scnprintf() call, by incrementing a pointer that is not
> used after the call. I've removed the dead code.
>
> mm/page_owner.c:
>
> Within print_page_owner(), there are some calls to scnprintf(),
> which do report truncation. And then there are other calls to
This is a typo; I meant s/do/don't/
> snprintf(), where we handle errors (there are two 'goto err').
>
> I've kept the existing error handling, as I trust it's there for
> a good reason (i.e., we may want to avoid calling
> print_page_owner_memcg() if we truncated before). Please review
> if this amount of error handling is the right one, or if we want
> to add or remove some. For seprintf(), a single test for null
> after the last call is enough to detect truncation.
>
> mm/slub.c:
>
> Again, the 'p += snprintf()' anti-pattern. This is UB, and by
> using seprintf() we've fixed the bug.
>
> Cc: Kees Cook <kees@kernel.org>
> Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
> Signed-off-by: Alejandro Colomar <alx@kernel.org>
> ---
> mm/kfence/kfence_test.c | 24 ++++++++++++------------
> mm/kmsan/kmsan_test.c | 4 ++--
> mm/mempolicy.c | 18 +++++++++---------
> mm/page_owner.c | 32 +++++++++++++++++---------------
> mm/slub.c | 5 +++--
> 5 files changed, 43 insertions(+), 40 deletions(-)
>
> diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
> index 00034e37bc9f..ff734c514c03 100644
> --- a/mm/kfence/kfence_test.c
> +++ b/mm/kfence/kfence_test.c
> @@ -113,26 +113,26 @@ static bool report_matches(const struct expect_report *r)
> end = &expect[0][sizeof(expect[0]) - 1];
> switch (r->type) {
> case KFENCE_ERROR_OOB:
> - cur += scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds %s",
> + cur = seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s",
> get_access_type(r));
> break;
> case KFENCE_ERROR_UAF:
> - cur += scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free %s",
> + cur = seprintf(cur, end, "BUG: KFENCE: use-after-free %s",
> get_access_type(r));
> break;
> case KFENCE_ERROR_CORRUPTION:
> - cur += scnprintf(cur, end - cur, "BUG: KFENCE: memory corruption");
> + cur = seprintf(cur, end, "BUG: KFENCE: memory corruption");
> break;
> case KFENCE_ERROR_INVALID:
> - cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid %s",
> + cur = seprintf(cur, end, "BUG: KFENCE: invalid %s",
> get_access_type(r));
> break;
> case KFENCE_ERROR_INVALID_FREE:
> - cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid free");
> + cur = seprintf(cur, end, "BUG: KFENCE: invalid free");
> break;
> }
>
> - scnprintf(cur, end - cur, " in %pS", r->fn);
> + seprintf(cur, end, " in %pS", r->fn);
> /* The exact offset won't match, remove it; also strip module name. */
> cur = strchr(expect[0], '+');
> if (cur)
> @@ -144,26 +144,26 @@ static bool report_matches(const struct expect_report *r)
>
> switch (r->type) {
> case KFENCE_ERROR_OOB:
> - cur += scnprintf(cur, end - cur, "Out-of-bounds %s at", get_access_type(r));
> + cur = seprintf(cur, end, "Out-of-bounds %s at", get_access_type(r));
> addr = arch_kfence_test_address(addr);
> break;
> case KFENCE_ERROR_UAF:
> - cur += scnprintf(cur, end - cur, "Use-after-free %s at", get_access_type(r));
> + cur = seprintf(cur, end, "Use-after-free %s at", get_access_type(r));
> addr = arch_kfence_test_address(addr);
> break;
> case KFENCE_ERROR_CORRUPTION:
> - cur += scnprintf(cur, end - cur, "Corrupted memory at");
> + cur = seprintf(cur, end, "Corrupted memory at");
> break;
> case KFENCE_ERROR_INVALID:
> - cur += scnprintf(cur, end - cur, "Invalid %s at", get_access_type(r));
> + cur = seprintf(cur, end, "Invalid %s at", get_access_type(r));
> addr = arch_kfence_test_address(addr);
> break;
> case KFENCE_ERROR_INVALID_FREE:
> - cur += scnprintf(cur, end - cur, "Invalid free of");
> + cur = seprintf(cur, end, "Invalid free of");
> break;
> }
>
> - cur += scnprintf(cur, end - cur, " 0x%p", (void *)addr);
> + seprintf(cur, end, " 0x%p", (void *)addr);
>
> spin_lock_irqsave(&observed.lock, flags);
> if (!report_available())
> diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
> index 9733a22c46c1..a062a46b2d24 100644
> --- a/mm/kmsan/kmsan_test.c
> +++ b/mm/kmsan/kmsan_test.c
> @@ -107,9 +107,9 @@ static bool report_matches(const struct expect_report *r)
> cur = expected_header;
> end = &expected_header[sizeof(expected_header) - 1];
>
> - cur += scnprintf(cur, end - cur, "BUG: KMSAN: %s", r->error_type);
> + cur = seprintf(cur, end, "BUG: KMSAN: %s", r->error_type);
>
> - scnprintf(cur, end - cur, " in %s", r->symbol);
> + seprintf(cur, end, " in %s", r->symbol);
> /* The exact offset won't match, remove it; also strip module name. */
> cur = strchr(expected_header, '+');
> if (cur)
> diff --git a/mm/mempolicy.c b/mm/mempolicy.c
> index b28a1e6ae096..c696e4a6f4c2 100644
> --- a/mm/mempolicy.c
> +++ b/mm/mempolicy.c
> @@ -3359,6 +3359,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
> void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
> {
> char *p = buffer;
> + char *e = buffer + maxlen;
> nodemask_t nodes = NODE_MASK_NONE;
> unsigned short mode = MPOL_DEFAULT;
> unsigned short flags = 0;
> @@ -3384,33 +3385,32 @@ void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
> break;
> default:
> WARN_ON_ONCE(1);
> - snprintf(p, maxlen, "unknown");
> + seprintf(p, e, "unknown");
> return;
> }
>
> - p += snprintf(p, maxlen, "%s", policy_modes[mode]);
> + p = seprintf(p, e, "%s", policy_modes[mode]);
>
> if (flags & MPOL_MODE_FLAGS) {
> - p += snprintf(p, buffer + maxlen - p, "=");
> + p = seprintf(p, e, "=");
>
> /*
> * Static and relative are mutually exclusive.
> */
> if (flags & MPOL_F_STATIC_NODES)
> - p += snprintf(p, buffer + maxlen - p, "static");
> + p = seprintf(p, e, "static");
> else if (flags & MPOL_F_RELATIVE_NODES)
> - p += snprintf(p, buffer + maxlen - p, "relative");
> + p = seprintf(p, e, "relative");
>
> if (flags & MPOL_F_NUMA_BALANCING) {
> if (!is_power_of_2(flags & MPOL_MODE_FLAGS))
> - p += snprintf(p, buffer + maxlen - p, "|");
> - p += snprintf(p, buffer + maxlen - p, "balancing");
> + p = seprintf(p, e, "|");
> + p = seprintf(p, e, "balancing");
> }
> }
>
> if (!nodes_empty(nodes))
> - p += scnprintf(p, buffer + maxlen - p, ":%*pbl",
> - nodemask_pr_args(&nodes));
> + seprintf(p, e, ":%*pbl", nodemask_pr_args(&nodes));
> }
>
> #ifdef CONFIG_SYSFS
> diff --git a/mm/page_owner.c b/mm/page_owner.c
> index cc4a6916eec6..5811738e3320 100644
> --- a/mm/page_owner.c
> +++ b/mm/page_owner.c
> @@ -496,7 +496,7 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
> /*
> * Looking for memcg information and print it out
> */
> -static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
> +static inline char *print_page_owner_memcg(char *p, const char end[0],
> struct page *page)
> {
> #ifdef CONFIG_MEMCG
> @@ -511,8 +511,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
> goto out_unlock;
>
> if (memcg_data & MEMCG_DATA_OBJEXTS)
> - ret += scnprintf(kbuf + ret, count - ret,
> - "Slab cache page\n");
> + p = seprintf(p, end, "Slab cache page\n");
>
> memcg = page_memcg_check(page);
> if (!memcg)
> @@ -520,7 +519,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
>
> online = (memcg->css.flags & CSS_ONLINE);
> cgroup_name(memcg->css.cgroup, name, sizeof(name));
> - ret += scnprintf(kbuf + ret, count - ret,
> + p = seprintf(p, end,
> "Charged %sto %smemcg %s\n",
> PageMemcgKmem(page) ? "(via objcg) " : "",
> online ? "" : "offline ",
> @@ -529,7 +528,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
> rcu_read_unlock();
> #endif /* CONFIG_MEMCG */
>
> - return ret;
> + return p;
> }
>
> static ssize_t
> @@ -538,14 +537,16 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
> depot_stack_handle_t handle)
> {
> int ret, pageblock_mt, page_mt;
> - char *kbuf;
> + char *kbuf, *p, *e;
>
> count = min_t(size_t, count, PAGE_SIZE);
> kbuf = kmalloc(count, GFP_KERNEL);
> if (!kbuf)
> return -ENOMEM;
>
> - ret = scnprintf(kbuf, count,
> + p = kbuf;
> + e = kbuf + count;
> + p = seprintf(p, e,
> "Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n",
> page_owner->order, page_owner->gfp_mask,
> &page_owner->gfp_mask, page_owner->pid,
> @@ -555,7 +556,7 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
> /* Print information relevant to grouping pages by mobility */
> pageblock_mt = get_pageblock_migratetype(page);
> page_mt = gfp_migratetype(page_owner->gfp_mask);
> - ret += scnprintf(kbuf + ret, count - ret,
> + p = seprintf(p, e,
> "PFN 0x%lx type %s Block %lu type %s Flags %pGp\n",
> pfn,
> migratetype_names[page_mt],
> @@ -563,22 +564,23 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
> migratetype_names[pageblock_mt],
> &page->flags);
>
> - ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
> - if (ret >= count)
> - goto err;
> + p = stack_depot_seprint(handle, p, e, 0);
> + if (p == NULL)
> + goto err; // XXX: Should we remove this error handling?
>
> if (page_owner->last_migrate_reason != -1) {
> - ret += scnprintf(kbuf + ret, count - ret,
> + p = seprintf(p, e,
> "Page has been migrated, last migrate reason: %s\n",
> migrate_reason_names[page_owner->last_migrate_reason]);
> }
>
> - ret = print_page_owner_memcg(kbuf, count, ret, page);
> + p = print_page_owner_memcg(p, e, page);
>
> - ret += snprintf(kbuf + ret, count - ret, "\n");
> - if (ret >= count)
> + p = seprintf(p, e, "\n");
> + if (p == NULL)
> goto err;
>
> + ret = p - kbuf;
> if (copy_to_user(buf, kbuf, ret))
> ret = -EFAULT;
>
> diff --git a/mm/slub.c b/mm/slub.c
> index be8b09e09d30..b67c6ca0d0f7 100644
> --- a/mm/slub.c
> +++ b/mm/slub.c
> @@ -7451,6 +7451,7 @@ static char *create_unique_id(struct kmem_cache *s)
> {
> char *name = kmalloc(ID_STR_LENGTH, GFP_KERNEL);
> char *p = name;
> + char *e = name + ID_STR_LENGTH;
>
> if (!name)
> return ERR_PTR(-ENOMEM);
> @@ -7475,9 +7476,9 @@ static char *create_unique_id(struct kmem_cache *s)
> *p++ = 'A';
> if (p != name + 1)
> *p++ = '-';
> - p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
> + p = seprintf(p, e, "%07u", s->size);
>
> - if (WARN_ON(p > name + ID_STR_LENGTH - 1)) {
> + if (WARN_ON(p == NULL)) {
> kfree(name);
> return ERR_PTR(-EINVAL);
> }
> --
> 2.50.0
>
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* [RFC v2 0/5] Add and use seprintf() instead of less ergonomic APIs
@ 2025-07-06 17:37 ` Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 1/5] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
` (8 more replies)
0 siblings, 9 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-06 17:37 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton
Hi Kees,
I've found some more bugs in the same code. There were three off-by-one
bugs in the code I had replaced, and while I had noticed something
weird, I hadn't stopped to think too much about them. I've documented
the bugs, and fixed them in the last commit. I've also added an ENDOF()
macro to prevent these off-by-one bugs when we can avoid them.
This time I've built the kernel, which showed I had forgotten some
prototypes, plus also one typo.
See range-diff below.
This is still not complying to coding style, but is otherwise in working
order. I'll send it as is for discussion. When we agree on the
specific questions on the code I made in v1, I'll turn it into coding-
style compliant.
Have a lovely Sun day!
Alex
Alejandro Colomar (5):
vsprintf: Add [v]seprintf(), [v]stprintf()
stacktrace, stackdepot: Add seprintf()-like variants of functions
mm: Use seprintf() instead of less ergonomic APIs
array_size.h: Add ENDOF()
mm: Fix benign off-by-one bugs
include/linux/array_size.h | 6 ++
include/linux/sprintf.h | 4 ++
include/linux/stackdepot.h | 13 +++++
include/linux/stacktrace.h | 3 +
kernel/stacktrace.c | 28 ++++++++++
lib/stackdepot.c | 12 ++++
lib/vsprintf.c | 109 +++++++++++++++++++++++++++++++++++++
mm/kfence/kfence_test.c | 28 +++++-----
mm/kmsan/kmsan_test.c | 6 +-
mm/mempolicy.c | 18 +++---
mm/page_owner.c | 32 ++++++-----
mm/slub.c | 5 +-
12 files changed, 221 insertions(+), 43 deletions(-)
Range-diff against v1:
1: 2d20eaf1752e ! 1: 64334f0b94d6 vsprintf: Add [v]seprintf(), [v]stprintf()
@@ Commit message
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
+ ## include/linux/sprintf.h ##
+@@ include/linux/sprintf.h: __printf(2, 3) int sprintf(char *buf, const char * fmt, ...);
+ __printf(2, 0) int vsprintf(char *buf, const char *, va_list);
+ __printf(3, 4) int snprintf(char *buf, size_t size, const char *fmt, ...);
+ __printf(3, 0) int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
++__printf(3, 4) int stprintf(char *buf, size_t size, const char *fmt, ...);
++__printf(3, 0) int vstprintf(char *buf, size_t size, const char *fmt, va_list args);
+ __printf(3, 4) int scnprintf(char *buf, size_t size, const char *fmt, ...);
+ __printf(3, 0) int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
++__printf(3, 4) char *seprintf(char *p, const char end[0], const char *fmt, ...);
++__printf(3, 0) char *vseprintf(char *p, const char end[0], const char *fmt, va_list args);
+ __printf(2, 3) __malloc char *kasprintf(gfp_t gfp, const char *fmt, ...);
+ __printf(2, 0) __malloc char *kvasprintf(gfp_t gfp, const char *fmt, va_list args);
+ __printf(2, 0) const char *kvasprintf_const(gfp_t gfp, const char *fmt, va_list args);
+
## lib/vsprintf.c ##
@@ lib/vsprintf.c: int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
}
2: ec2e375c2d1e ! 2: 9c140de9842d stacktrace, stackdepot: Add seprintf()-like variants of functions
@@ lib/stackdepot.c: int stack_depot_snprint(depot_stack_handle_t handle, char *buf
+ unsigned int nr_entries;
+
+ nr_entries = stack_depot_fetch(handle, &entries);
-+ return nr_entries ? stack_trace_seprint(p, e, entries, nr_entries,
++ return nr_entries ? stack_trace_seprint(p, end, entries, nr_entries,
+ spaces) : p;
+}
+EXPORT_SYMBOL_GPL(stack_depot_seprint);
3: be193e1856aa ! 3: e3271b5f2ad9 mm: Use seprintf() instead of less ergonomic APIs
@@ Commit message
mm/kfence/kfence_test.c:
- The last call to scnprintf() did increment 'cur', but it's
- unused after that, so it was dead code. I've removed the dead
- code in this patch.
+ - The last call to scnprintf() did increment 'cur', but it's
+ unused after that, so it was dead code. I've removed the dead
+ code in this patch.
+
+ - 'end' is calculated as
+
+ end = &expect[0][sizeof(expect[0] - 1)];
+
+ However, the '-1' doesn't seem to be necessary. When passing
+ $2 to scnprintf(), the size was specified as 'end - cur'.
+ And scnprintf() --just like snprintf(3)--, won't write more
+ than $2 bytes (including the null byte). That means that
+ scnprintf() wouldn't write more than
+
+ &expect[0][sizeof(expect[0]) - 1] - expect[0]
+
+ which simplifies to
+
+ sizeof(expect[0]) - 1
+
+ bytes. But we have sizeof(expect[0]) bytes available, so
+ we're wasting one byte entirely. This is a benign off-by-one
+ bug. The two occurrences of this bug will be fixed in a
+ following patch in this series.
+
+ mm/kmsan/kmsan_test.c:
+
+ The same benign off-by-one bug calculating the remaining size.
mm/mempolicy.c:
-: ------------ > 4: 5331d286ceca array_size.h: Add ENDOF()
-: ------------ > 5: 08cfdd2bf779 mm: Fix benign off-by-one bugs
--
2.50.0
^ permalink raw reply [flat|nested] 98+ messages in thread
* [RFC v2 1/5] vsprintf: Add [v]seprintf(), [v]stprintf()
2025-07-06 17:37 ` [RFC v2 0/5] Add and use " Alejandro Colomar
@ 2025-07-06 17:37 ` Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 2/5] stacktrace, stackdepot: Add seprintf()-like variants of functions Alejandro Colomar
` (7 subsequent siblings)
8 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-06 17:37 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton
seprintf()
==========
seprintf() is a function similar to stpcpy(3) in the sense that it
returns a pointer that is suitable for chaining to other copy
operations.
It takes a pointer to the end of the buffer as a sentinel for when to
truncate, which unlike a size, doesn't need to be updated after every
call. This makes it much more ergonomic, avoiding manually calculating
the size after each copy, which is error prone.
It also makes error handling much easier, by reporting truncation with
a null pointer, which is accepted and transparently passed down by
subsequent seprintf() calls. This results in only needing to report
errors once after a chain of seprintf() calls, unlike snprintf(3), which
requires checking after every call.
p = buf;
e = buf + countof(buf);
p = seprintf(p, e, foo);
p = seprintf(p, e, bar);
if (p == NULL)
goto trunc;
vs
len = 0;
size = countof(buf);
len += snprintf(buf + len, size - len, foo);
if (len >= size)
goto trunc;
len += snprintf(buf + len, size - len, bar);
if (len >= size)
goto trunc;
And also better than scnprintf() calls:
len = 0;
size = countof(buf);
len += scnprintf(buf + len, size - len, foo);
len += scnprintf(buf + len, size - len, bar);
if (len >= size)
goto trunc;
It seems aparent that it's a more elegant approach to string catenation.
stprintf()
==========
stprintf() is a helper that is needed for implementing seprintf()
--although it could be open-coded within vseprintf(), of course--, but
it's also useful by itself. It has the same interface properties as
strscpy(): that is, it copies with truncation, and reports truncation
with -E2BIG. It would be useful to replace some calls to snprintf(3)
and scnprintf() which don't need chaining, and where it's simpler to
pass a size.
It is better than plain snprintf(3), because it results in simpler error
detection (it doesn't need a check >=countof(buf), but rather <0).
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/sprintf.h | 4 ++
lib/vsprintf.c | 109 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 113 insertions(+)
diff --git a/include/linux/sprintf.h b/include/linux/sprintf.h
index 51cab2def9ec..c3dbfd2efd2b 100644
--- a/include/linux/sprintf.h
+++ b/include/linux/sprintf.h
@@ -11,8 +11,12 @@ __printf(2, 3) int sprintf(char *buf, const char * fmt, ...);
__printf(2, 0) int vsprintf(char *buf, const char *, va_list);
__printf(3, 4) int snprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
+__printf(3, 4) int stprintf(char *buf, size_t size, const char *fmt, ...);
+__printf(3, 0) int vstprintf(char *buf, size_t size, const char *fmt, va_list args);
__printf(3, 4) int scnprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
+__printf(3, 4) char *seprintf(char *p, const char end[0], const char *fmt, ...);
+__printf(3, 0) char *vseprintf(char *p, const char end[0], const char *fmt, va_list args);
__printf(2, 3) __malloc char *kasprintf(gfp_t gfp, const char *fmt, ...);
__printf(2, 0) __malloc char *kvasprintf(gfp_t gfp, const char *fmt, va_list args);
__printf(2, 0) const char *kvasprintf_const(gfp_t gfp, const char *fmt, va_list args);
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 01699852f30c..a3efacadb5e5 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -2892,6 +2892,37 @@ int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
}
EXPORT_SYMBOL(vsnprintf);
+/**
+ * vstprintf - Format a string and place it in a buffer
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
+ *
+ * The return value is the length of the new string.
+ * If the string is truncated, the function returns -E2BIG.
+ *
+ * If you're not already dealing with a va_list consider using stprintf().
+ *
+ * See the vsnprintf() documentation for format string extensions over C99.
+ */
+int vstprintf(char *buf, size_t size, const char *fmt, va_list args)
+{
+ int len;
+
+ len = vsnprintf(buf, size, fmt, args);
+
+ // It seems the kernel's vsnprintf() doesn't fail?
+ //if (unlikely(len < 0))
+ // return -E2BIG;
+
+ if (unlikely(len >= size))
+ return -E2BIG;
+
+ return len;
+}
+EXPORT_SYMBOL(vstprintf);
+
/**
* vscnprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -2923,6 +2954,36 @@ int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
}
EXPORT_SYMBOL(vscnprintf);
+/**
+ * vseprintf - Format a string and place it in a buffer
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
+ *
+ * The return value is a pointer to the trailing '\0'.
+ * If @p is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
+ *
+ * If you're not already dealing with a va_list consider using seprintf().
+ *
+ * See the vsnprintf() documentation for format string extensions over C99.
+ */
+char *vseprintf(char *p, const char end[0], const char *fmt, va_list args)
+{
+ int len;
+
+ if (unlikely(p == NULL))
+ return NULL;
+
+ len = vstprintf(p, end - p, fmt, args);
+ if (unlikely(len < 0))
+ return NULL;
+
+ return p + len;
+}
+EXPORT_SYMBOL(vseprintf);
+
/**
* snprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -2950,6 +3011,30 @@ int snprintf(char *buf, size_t size, const char *fmt, ...)
}
EXPORT_SYMBOL(snprintf);
+/**
+ * stprintf - Format a string and place it in a buffer
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
+ *
+ * The return value is the length of the new string.
+ * If the string is truncated, the function returns -E2BIG.
+ */
+
+int stprintf(char *buf, size_t size, const char *fmt, ...)
+{
+ va_list args;
+ int len;
+
+ va_start(args, fmt);
+ len = vstprintf(buf, size, fmt, args);
+ va_end(args);
+
+ return len;
+}
+EXPORT_SYMBOL(stprintf);
+
/**
* scnprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -2974,6 +3059,30 @@ int scnprintf(char *buf, size_t size, const char *fmt, ...)
}
EXPORT_SYMBOL(scnprintf);
+/**
+ * seprintf - Format a string and place it in a buffer
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
+ *
+ * The return value is a pointer to the trailing '\0'.
+ * If @buf is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
+ */
+
+char *seprintf(char *p, const char end[0], const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ p = vseprintf(p, end, fmt, args);
+ va_end(args);
+
+ return p;
+}
+EXPORT_SYMBOL(seprintf);
+
/**
* vsprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v2 2/5] stacktrace, stackdepot: Add seprintf()-like variants of functions
2025-07-06 17:37 ` [RFC v2 0/5] Add and use " Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 1/5] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
@ 2025-07-06 17:37 ` Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 3/5] mm: Use seprintf() instead of less ergonomic APIs Alejandro Colomar
` (6 subsequent siblings)
8 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-06 17:37 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton
I think there's an anomaly in stack_depot_s*print(). If we have zero
entries, we don't copy anything, which means the string is still not a
string. Normally, this function is called surrounded by other calls to
s*printf(), which guarantee that there's a '\0', but maybe we should
make sure to write a '\0' here?
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/stackdepot.h | 13 +++++++++++++
include/linux/stacktrace.h | 3 +++
kernel/stacktrace.c | 28 ++++++++++++++++++++++++++++
lib/stackdepot.c | 12 ++++++++++++
4 files changed, 56 insertions(+)
diff --git a/include/linux/stackdepot.h b/include/linux/stackdepot.h
index 2cc21ffcdaf9..a7749fc3ac7c 100644
--- a/include/linux/stackdepot.h
+++ b/include/linux/stackdepot.h
@@ -219,6 +219,19 @@ void stack_depot_print(depot_stack_handle_t stack);
int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
int spaces);
+/**
+ * stack_depot_seprint - Print a stack trace from stack depot into a buffer
+ *
+ * @handle: Stack depot handle returned from stack_depot_save()
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @spaces: Number of leading spaces to print
+ *
+ * Return: Pointer to trailing '\0'; or NULL on truncation
+ */
+char *stack_depot_seprint(depot_stack_handle_t handle, char *p,
+ const char end[0], int spaces);
+
/**
* stack_depot_put - Drop a reference to a stack trace from stack depot
*
diff --git a/include/linux/stacktrace.h b/include/linux/stacktrace.h
index 97455880ac41..748936386c89 100644
--- a/include/linux/stacktrace.h
+++ b/include/linux/stacktrace.h
@@ -67,6 +67,9 @@ void stack_trace_print(const unsigned long *trace, unsigned int nr_entries,
int spaces);
int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
unsigned int nr_entries, int spaces);
+char *stack_trace_seprint(char *p, const char end[0],
+ const unsigned long *entries, unsigned int nr_entries,
+ int spaces);
unsigned int stack_trace_save(unsigned long *store, unsigned int size,
unsigned int skipnr);
unsigned int stack_trace_save_tsk(struct task_struct *task,
diff --git a/kernel/stacktrace.c b/kernel/stacktrace.c
index afb3c116da91..65caf9e63673 100644
--- a/kernel/stacktrace.c
+++ b/kernel/stacktrace.c
@@ -70,6 +70,34 @@ int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
}
EXPORT_SYMBOL_GPL(stack_trace_snprint);
+/**
+ * stack_trace_seprint - Print the entries in the stack trace into a buffer
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @entries: Pointer to storage array
+ * @nr_entries: Number of entries in the storage array
+ * @spaces: Number of leading spaces to print
+ *
+ * Return: Pointer to the trailing '\0'; or NULL on truncation.
+ */
+char *stack_trace_seprint(char *p, const char end[0],
+ const unsigned long *entries, unsigned int nr_entries,
+ int spaces)
+{
+ unsigned int i;
+
+ if (WARN_ON(!entries))
+ return 0;
+
+ for (i = 0; i < nr_entries; i++) {
+ p = seprintf(p, end, "%*c%pS\n", 1 + spaces, ' ',
+ (void *)entries[i]);
+ }
+
+ return p;
+}
+EXPORT_SYMBOL_GPL(stack_trace_seprint);
+
#ifdef CONFIG_ARCH_STACKWALK
struct stacktrace_cookie {
diff --git a/lib/stackdepot.c b/lib/stackdepot.c
index 73d7b50924ef..749496e6a6f1 100644
--- a/lib/stackdepot.c
+++ b/lib/stackdepot.c
@@ -771,6 +771,18 @@ int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
}
EXPORT_SYMBOL_GPL(stack_depot_snprint);
+char *stack_depot_seprint(depot_stack_handle_t handle, char *p,
+ const char end[0], int spaces)
+{
+ unsigned long *entries;
+ unsigned int nr_entries;
+
+ nr_entries = stack_depot_fetch(handle, &entries);
+ return nr_entries ? stack_trace_seprint(p, end, entries, nr_entries,
+ spaces) : p;
+}
+EXPORT_SYMBOL_GPL(stack_depot_seprint);
+
depot_stack_handle_t __must_check stack_depot_set_extra_bits(
depot_stack_handle_t handle, unsigned int extra_bits)
{
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v2 3/5] mm: Use seprintf() instead of less ergonomic APIs
2025-07-06 17:37 ` [RFC v2 0/5] Add and use " Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 1/5] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 2/5] stacktrace, stackdepot: Add seprintf()-like variants of functions Alejandro Colomar
@ 2025-07-06 17:37 ` Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 4/5] array_size.h: Add ENDOF() Alejandro Colomar
` (5 subsequent siblings)
8 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-06 17:37 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton
While doing this, I detected some anomalies in the existing code:
mm/kfence/kfence_test.c:
- The last call to scnprintf() did increment 'cur', but it's
unused after that, so it was dead code. I've removed the dead
code in this patch.
- 'end' is calculated as
end = &expect[0][sizeof(expect[0] - 1)];
However, the '-1' doesn't seem to be necessary. When passing
$2 to scnprintf(), the size was specified as 'end - cur'.
And scnprintf() --just like snprintf(3)--, won't write more
than $2 bytes (including the null byte). That means that
scnprintf() wouldn't write more than
&expect[0][sizeof(expect[0]) - 1] - expect[0]
which simplifies to
sizeof(expect[0]) - 1
bytes. But we have sizeof(expect[0]) bytes available, so
we're wasting one byte entirely. This is a benign off-by-one
bug. The two occurrences of this bug will be fixed in a
following patch in this series.
mm/kmsan/kmsan_test.c:
The same benign off-by-one bug calculating the remaining size.
mm/mempolicy.c:
This file uses the 'p += snprintf()' anti-pattern. That will
overflow the pointer on truncation, which has undefined
behavior. Using seprintf(), this bug is fixed.
As in the previous file, here there was also dead code in the
last scnprintf() call, by incrementing a pointer that is not
used after the call. I've removed the dead code.
mm/page_owner.c:
Within print_page_owner(), there are some calls to scnprintf(),
which do report truncation. And then there are other calls to
snprintf(), where we handle errors (there are two 'goto err').
I've kept the existing error handling, as I trust it's there for
a good reason (i.e., we may want to avoid calling
print_page_owner_memcg() if we truncated before). Please review
if this amount of error handling is the right one, or if we want
to add or remove some. For seprintf(), a single test for null
after the last call is enough to detect truncation.
mm/slub.c:
Again, the 'p += snprintf()' anti-pattern. This is UB, and by
using seprintf() we've fixed the bug.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/kfence/kfence_test.c | 24 ++++++++++++------------
mm/kmsan/kmsan_test.c | 4 ++--
mm/mempolicy.c | 18 +++++++++---------
mm/page_owner.c | 32 +++++++++++++++++---------------
mm/slub.c | 5 +++--
5 files changed, 43 insertions(+), 40 deletions(-)
diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
index 00034e37bc9f..ff734c514c03 100644
--- a/mm/kfence/kfence_test.c
+++ b/mm/kfence/kfence_test.c
@@ -113,26 +113,26 @@ static bool report_matches(const struct expect_report *r)
end = &expect[0][sizeof(expect[0]) - 1];
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds %s",
+ cur = seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s",
get_access_type(r));
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free %s",
+ cur = seprintf(cur, end, "BUG: KFENCE: use-after-free %s",
get_access_type(r));
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: memory corruption");
+ cur = seprintf(cur, end, "BUG: KFENCE: memory corruption");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid %s",
+ cur = seprintf(cur, end, "BUG: KFENCE: invalid %s",
get_access_type(r));
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid free");
+ cur = seprintf(cur, end, "BUG: KFENCE: invalid free");
break;
}
- scnprintf(cur, end - cur, " in %pS", r->fn);
+ seprintf(cur, end, " in %pS", r->fn);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expect[0], '+');
if (cur)
@@ -144,26 +144,26 @@ static bool report_matches(const struct expect_report *r)
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "Out-of-bounds %s at", get_access_type(r));
+ cur = seprintf(cur, end, "Out-of-bounds %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "Use-after-free %s at", get_access_type(r));
+ cur = seprintf(cur, end, "Use-after-free %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "Corrupted memory at");
+ cur = seprintf(cur, end, "Corrupted memory at");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "Invalid %s at", get_access_type(r));
+ cur = seprintf(cur, end, "Invalid %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "Invalid free of");
+ cur = seprintf(cur, end, "Invalid free of");
break;
}
- cur += scnprintf(cur, end - cur, " 0x%p", (void *)addr);
+ seprintf(cur, end, " 0x%p", (void *)addr);
spin_lock_irqsave(&observed.lock, flags);
if (!report_available())
diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
index 9733a22c46c1..a062a46b2d24 100644
--- a/mm/kmsan/kmsan_test.c
+++ b/mm/kmsan/kmsan_test.c
@@ -107,9 +107,9 @@ static bool report_matches(const struct expect_report *r)
cur = expected_header;
end = &expected_header[sizeof(expected_header) - 1];
- cur += scnprintf(cur, end - cur, "BUG: KMSAN: %s", r->error_type);
+ cur = seprintf(cur, end, "BUG: KMSAN: %s", r->error_type);
- scnprintf(cur, end - cur, " in %s", r->symbol);
+ seprintf(cur, end, " in %s", r->symbol);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expected_header, '+');
if (cur)
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index b28a1e6ae096..c696e4a6f4c2 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -3359,6 +3359,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
{
char *p = buffer;
+ char *e = buffer + maxlen;
nodemask_t nodes = NODE_MASK_NONE;
unsigned short mode = MPOL_DEFAULT;
unsigned short flags = 0;
@@ -3384,33 +3385,32 @@ void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
break;
default:
WARN_ON_ONCE(1);
- snprintf(p, maxlen, "unknown");
+ seprintf(p, e, "unknown");
return;
}
- p += snprintf(p, maxlen, "%s", policy_modes[mode]);
+ p = seprintf(p, e, "%s", policy_modes[mode]);
if (flags & MPOL_MODE_FLAGS) {
- p += snprintf(p, buffer + maxlen - p, "=");
+ p = seprintf(p, e, "=");
/*
* Static and relative are mutually exclusive.
*/
if (flags & MPOL_F_STATIC_NODES)
- p += snprintf(p, buffer + maxlen - p, "static");
+ p = seprintf(p, e, "static");
else if (flags & MPOL_F_RELATIVE_NODES)
- p += snprintf(p, buffer + maxlen - p, "relative");
+ p = seprintf(p, e, "relative");
if (flags & MPOL_F_NUMA_BALANCING) {
if (!is_power_of_2(flags & MPOL_MODE_FLAGS))
- p += snprintf(p, buffer + maxlen - p, "|");
- p += snprintf(p, buffer + maxlen - p, "balancing");
+ p = seprintf(p, e, "|");
+ p = seprintf(p, e, "balancing");
}
}
if (!nodes_empty(nodes))
- p += scnprintf(p, buffer + maxlen - p, ":%*pbl",
- nodemask_pr_args(&nodes));
+ seprintf(p, e, ":%*pbl", nodemask_pr_args(&nodes));
}
#ifdef CONFIG_SYSFS
diff --git a/mm/page_owner.c b/mm/page_owner.c
index cc4a6916eec6..5811738e3320 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -496,7 +496,7 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
/*
* Looking for memcg information and print it out
*/
-static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
+static inline char *print_page_owner_memcg(char *p, const char end[0],
struct page *page)
{
#ifdef CONFIG_MEMCG
@@ -511,8 +511,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
goto out_unlock;
if (memcg_data & MEMCG_DATA_OBJEXTS)
- ret += scnprintf(kbuf + ret, count - ret,
- "Slab cache page\n");
+ p = seprintf(p, end, "Slab cache page\n");
memcg = page_memcg_check(page);
if (!memcg)
@@ -520,7 +519,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
online = (memcg->css.flags & CSS_ONLINE);
cgroup_name(memcg->css.cgroup, name, sizeof(name));
- ret += scnprintf(kbuf + ret, count - ret,
+ p = seprintf(p, end,
"Charged %sto %smemcg %s\n",
PageMemcgKmem(page) ? "(via objcg) " : "",
online ? "" : "offline ",
@@ -529,7 +528,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
rcu_read_unlock();
#endif /* CONFIG_MEMCG */
- return ret;
+ return p;
}
static ssize_t
@@ -538,14 +537,16 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
depot_stack_handle_t handle)
{
int ret, pageblock_mt, page_mt;
- char *kbuf;
+ char *kbuf, *p, *e;
count = min_t(size_t, count, PAGE_SIZE);
kbuf = kmalloc(count, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
- ret = scnprintf(kbuf, count,
+ p = kbuf;
+ e = kbuf + count;
+ p = seprintf(p, e,
"Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n",
page_owner->order, page_owner->gfp_mask,
&page_owner->gfp_mask, page_owner->pid,
@@ -555,7 +556,7 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
/* Print information relevant to grouping pages by mobility */
pageblock_mt = get_pageblock_migratetype(page);
page_mt = gfp_migratetype(page_owner->gfp_mask);
- ret += scnprintf(kbuf + ret, count - ret,
+ p = seprintf(p, e,
"PFN 0x%lx type %s Block %lu type %s Flags %pGp\n",
pfn,
migratetype_names[page_mt],
@@ -563,22 +564,23 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
migratetype_names[pageblock_mt],
&page->flags);
- ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
- if (ret >= count)
- goto err;
+ p = stack_depot_seprint(handle, p, e, 0);
+ if (p == NULL)
+ goto err; // XXX: Should we remove this error handling?
if (page_owner->last_migrate_reason != -1) {
- ret += scnprintf(kbuf + ret, count - ret,
+ p = seprintf(p, e,
"Page has been migrated, last migrate reason: %s\n",
migrate_reason_names[page_owner->last_migrate_reason]);
}
- ret = print_page_owner_memcg(kbuf, count, ret, page);
+ p = print_page_owner_memcg(p, e, page);
- ret += snprintf(kbuf + ret, count - ret, "\n");
- if (ret >= count)
+ p = seprintf(p, e, "\n");
+ if (p == NULL)
goto err;
+ ret = p - kbuf;
if (copy_to_user(buf, kbuf, ret))
ret = -EFAULT;
diff --git a/mm/slub.c b/mm/slub.c
index be8b09e09d30..b67c6ca0d0f7 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -7451,6 +7451,7 @@ static char *create_unique_id(struct kmem_cache *s)
{
char *name = kmalloc(ID_STR_LENGTH, GFP_KERNEL);
char *p = name;
+ char *e = name + ID_STR_LENGTH;
if (!name)
return ERR_PTR(-ENOMEM);
@@ -7475,9 +7476,9 @@ static char *create_unique_id(struct kmem_cache *s)
*p++ = 'A';
if (p != name + 1)
*p++ = '-';
- p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
+ p = seprintf(p, e, "%07u", s->size);
- if (WARN_ON(p > name + ID_STR_LENGTH - 1)) {
+ if (WARN_ON(p == NULL)) {
kfree(name);
return ERR_PTR(-EINVAL);
}
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v2 4/5] array_size.h: Add ENDOF()
2025-07-06 17:37 ` [RFC v2 0/5] Add and use " Alejandro Colomar
` (2 preceding siblings ...)
2025-07-06 17:37 ` [RFC v2 3/5] mm: Use seprintf() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-06 17:37 ` Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 5/5] mm: Fix benign off-by-one bugs Alejandro Colomar
` (4 subsequent siblings)
8 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-06 17:37 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton
This macro is useful to calculate the second argument to seprintf(),
avoiding off-by-one bugs.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/array_size.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/include/linux/array_size.h b/include/linux/array_size.h
index 06d7d83196ca..a743d4ad5911 100644
--- a/include/linux/array_size.h
+++ b/include/linux/array_size.h
@@ -10,4 +10,10 @@
*/
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
+/**
+ * ENDOF - get a pointer to one past the last element in array @arr
+ * @arr: array
+ */
+#define ENDOF(a) (a + ARRAY_SIZE(a))
+
#endif /* _LINUX_ARRAY_SIZE_H */
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v2 5/5] mm: Fix benign off-by-one bugs
2025-07-06 17:37 ` [RFC v2 0/5] Add and use " Alejandro Colomar
` (3 preceding siblings ...)
2025-07-06 17:37 ` [RFC v2 4/5] array_size.h: Add ENDOF() Alejandro Colomar
@ 2025-07-06 17:37 ` Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
` (3 subsequent siblings)
8 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-06 17:37 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton
We were wasting a byte due to an off-by-one bug. s[c]nprintf()
doesn't write more than $2 bytes including the null byte, so trying to
pass 'size-1' there is wasting one byte. Now that we use seprintf(),
the situation isn't different: seprintf() will stop writing *before*
'end' --that is, at most the terminating null byte will be written at
'end-1'--.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/kfence/kfence_test.c | 4 ++--
mm/kmsan/kmsan_test.c | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
index ff734c514c03..f02c3e23638a 100644
--- a/mm/kfence/kfence_test.c
+++ b/mm/kfence/kfence_test.c
@@ -110,7 +110,7 @@ static bool report_matches(const struct expect_report *r)
/* Title */
cur = expect[0];
- end = &expect[0][sizeof(expect[0]) - 1];
+ end = ENDOF(expect[0]);
switch (r->type) {
case KFENCE_ERROR_OOB:
cur = seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s",
@@ -140,7 +140,7 @@ static bool report_matches(const struct expect_report *r)
/* Access information */
cur = expect[1];
- end = &expect[1][sizeof(expect[1]) - 1];
+ end = ENDOF(expect[1]);
switch (r->type) {
case KFENCE_ERROR_OOB:
diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
index a062a46b2d24..882500807db8 100644
--- a/mm/kmsan/kmsan_test.c
+++ b/mm/kmsan/kmsan_test.c
@@ -105,7 +105,7 @@ static bool report_matches(const struct expect_report *r)
/* Title */
cur = expected_header;
- end = &expected_header[sizeof(expected_header) - 1];
+ end = ENDOF(expected_header);
cur = seprintf(cur, end, "BUG: KMSAN: %s", r->error_type);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs
2025-07-06 17:37 ` [RFC v2 0/5] Add and use " Alejandro Colomar
` (4 preceding siblings ...)
2025-07-06 17:37 ` [RFC v2 5/5] mm: Fix benign off-by-one bugs Alejandro Colomar
@ 2025-07-07 5:06 ` Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 1/7] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
` (7 more replies)
2025-07-10 2:47 ` [RFC v4 0/7] Add and use sprintf_end() " Alejandro Colomar
` (2 subsequent siblings)
8 siblings, 8 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 5:06 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton
Hi,
In this v3:
- I've added Fixes: tags for all commits that introduced issues being
fixed in this patch set. I've also added the people who signed or
reviewed those patches to CC.
- I've fixed a typo in a comment.
- I've also added a STPRINTF() macro and used it to remove explicit
uses of sizeof().
Now, only 5 calls to snprintf(3) remain under mm/:
$ grep -rnI nprint mm/
mm/hugetlb_cgroup.c:674: snprintf(buf, size, "%luGB", hsize / SZ_1G);
mm/hugetlb_cgroup.c:676: snprintf(buf, size, "%luMB", hsize / SZ_1M);
mm/hugetlb_cgroup.c:678: snprintf(buf, size, "%luKB", hsize / SZ_1K);
mm/kfence/report.c:75: int len = scnprintf(buf, sizeof(buf), "%ps", (void *)stack_entries[skipnr]);
mm/kmsan/report.c:42: len = scnprintf(buf, sizeof(buf), "%ps",
The first three are fine. The remaining two, I'd like someone to check
if they should be replaced by one of these wrappers. I had doubts about
it, and would need someone understanding that code to check them.
Mainly, do we really want to ignore truncation?
The questions from v1 still are in the air.
I've written an analysis of snprintf(3), why it's dangerous, and how
these APIs address that, and will present it as a proposal for
standardization of these APIs in ISO C2y. I'll send that as a reply to
this message in a moment, as I believe it will be interesting for
linux-hardening@.
Have a lovely night!
Alex
Alejandro Colomar (7):
vsprintf: Add [v]seprintf(), [v]stprintf()
stacktrace, stackdepot: Add seprintf()-like variants of functions
mm: Use seprintf() instead of less ergonomic APIs
array_size.h: Add ENDOF()
mm: Fix benign off-by-one bugs
sprintf: Add [V]STPRINTF()
mm: Use [V]STPRINTF() to avoid specifying the array size
include/linux/array_size.h | 6 ++
include/linux/sprintf.h | 8 +++
include/linux/stackdepot.h | 13 +++++
include/linux/stacktrace.h | 3 +
kernel/stacktrace.c | 28 ++++++++++
lib/stackdepot.c | 12 ++++
lib/vsprintf.c | 109 +++++++++++++++++++++++++++++++++++++
mm/backing-dev.c | 2 +-
mm/cma.c | 4 +-
mm/cma_debug.c | 2 +-
mm/hugetlb.c | 3 +-
mm/hugetlb_cgroup.c | 2 +-
mm/hugetlb_cma.c | 2 +-
mm/kasan/report.c | 3 +-
mm/kfence/kfence_test.c | 28 +++++-----
mm/kmsan/kmsan_test.c | 6 +-
mm/memblock.c | 4 +-
mm/mempolicy.c | 18 +++---
mm/page_owner.c | 32 ++++++-----
mm/percpu.c | 2 +-
mm/shrinker_debug.c | 2 +-
mm/slub.c | 5 +-
mm/zswap.c | 2 +-
23 files changed, 238 insertions(+), 58 deletions(-)
Range-diff against v2:
1: 64334f0b94d6 = 1: 64334f0b94d6 vsprintf: Add [v]seprintf(), [v]stprintf()
2: 9c140de9842d = 2: 9c140de9842d stacktrace, stackdepot: Add seprintf()-like variants of functions
3: e3271b5f2ad9 ! 3: 033bf00f1fcf mm: Use seprintf() instead of less ergonomic APIs
@@ Commit message
Again, the 'p += snprintf()' anti-pattern. This is UB, and by
using seprintf() we've fixed the bug.
+ Fixes: f99e12b21b84 (2021-07-30; "kfence: add function to mask address bits")
+ [alx: that commit introduced dead code]
+ Fixes: af649773fb25 (2024-07-17; "mm/numa_balancing: teach mpol_to_str about the balancing mode")
+ [alx: that commit added p+=snprintf() calls, which are UB]
+ Fixes: 2291990ab36b (2008-04-28; "mempolicy: clean-up mpol-to-str() mempolicy formatting")
+ [alx: that commit changed p+=sprintf() into p+=snprintf(), which is still UB]
+ Fixes: 948927ee9e4f (2013-11-13; "mm, mempolicy: make mpol_to_str robust and always succeed")
+ [alx: that commit changes old code into p+=snprintf(), which is still UB]
+ [alx: that commit also produced dead code by leaving the last 'p+=...']
+ Fixes: d65360f22406 (2022-09-26; "mm/slub: clean up create_unique_id()")
+ [alx: that commit changed p+=sprintf() into p+=snprintf(), which is still UB]
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
+ Cc: Sven Schnelle <svens@linux.ibm.com>
+ Cc: Marco Elver <elver@google.com>
+ Cc: Heiko Carstens <hca@linux.ibm.com>
+ Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
+ Cc: "Huang, Ying" <ying.huang@intel.com>
+ Cc: Andrew Morton <akpm@linux-foundation.org>
+ Cc: Lee Schermerhorn <lee.schermerhorn@hp.com>
+ Cc: Linus Torvalds <torvalds@linux-foundation.org>
+ Cc: David Rientjes <rientjes@google.com>
+ Cc: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
+ Cc: Hyeonggon Yoo <42.hyeyoo@gmail.com>
+ Cc: Chao Yu <chao.yu@oppo.com>
+ Cc: Vlastimil Babka <vbabka@suse.cz>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
## mm/kfence/kfence_test.c ##
4: 5331d286ceca ! 4: d8bd0e1d308b array_size.h: Add ENDOF()
@@ include/linux/array_size.h
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
+/**
-+ * ENDOF - get a pointer to one past the last element in array @arr
-+ * @arr: array
++ * ENDOF - get a pointer to one past the last element in array @a
++ * @a: array
+ */
+#define ENDOF(a) (a + ARRAY_SIZE(a))
+
5: 08cfdd2bf779 ! 5: 740755c1a888 mm: Fix benign off-by-one bugs
@@ Commit message
'end' --that is, at most the terminating null byte will be written at
'end-1'--.
+ Fixes: bc8fbc5f305a (2021-02-26; "kfence: add test suite")
+ Fixes: 8ed691b02ade (2022-10-03; "kmsan: add tests for KMSAN")
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
+ Cc: Alexander Potapenko <glider@google.com>
+ Cc: Marco Elver <elver@google.com>
+ Cc: Dmitry Vyukov <dvyukov@google.com>
+ Cc: Alexander Potapenko <glider@google.com>
+ Cc: Jann Horn <jannh@google.com>
+ Cc: Andrew Morton <akpm@linux-foundation.org>
+ Cc: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
## mm/kfence/kfence_test.c ##
-: ------------ > 6: 44d05559398c sprintf: Add [V]STPRINTF()
-: ------------ > 7: d0e95db3c80a mm: Use [V]STPRINTF() to avoid specifying the array size
--
2.50.0
^ permalink raw reply [flat|nested] 98+ messages in thread
* [RFC v3 1/7] vsprintf: Add [v]seprintf(), [v]stprintf()
2025-07-07 5:06 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-07 5:06 ` Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 2/7] stacktrace, stackdepot: Add seprintf()-like variants of functions Alejandro Colomar
` (6 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 5:06 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton
seprintf()
==========
seprintf() is a function similar to stpcpy(3) in the sense that it
returns a pointer that is suitable for chaining to other copy
operations.
It takes a pointer to the end of the buffer as a sentinel for when to
truncate, which unlike a size, doesn't need to be updated after every
call. This makes it much more ergonomic, avoiding manually calculating
the size after each copy, which is error prone.
It also makes error handling much easier, by reporting truncation with
a null pointer, which is accepted and transparently passed down by
subsequent seprintf() calls. This results in only needing to report
errors once after a chain of seprintf() calls, unlike snprintf(3), which
requires checking after every call.
p = buf;
e = buf + countof(buf);
p = seprintf(p, e, foo);
p = seprintf(p, e, bar);
if (p == NULL)
goto trunc;
vs
len = 0;
size = countof(buf);
len += snprintf(buf + len, size - len, foo);
if (len >= size)
goto trunc;
len += snprintf(buf + len, size - len, bar);
if (len >= size)
goto trunc;
And also better than scnprintf() calls:
len = 0;
size = countof(buf);
len += scnprintf(buf + len, size - len, foo);
len += scnprintf(buf + len, size - len, bar);
if (len >= size)
goto trunc;
It seems aparent that it's a more elegant approach to string catenation.
stprintf()
==========
stprintf() is a helper that is needed for implementing seprintf()
--although it could be open-coded within vseprintf(), of course--, but
it's also useful by itself. It has the same interface properties as
strscpy(): that is, it copies with truncation, and reports truncation
with -E2BIG. It would be useful to replace some calls to snprintf(3)
and scnprintf() which don't need chaining, and where it's simpler to
pass a size.
It is better than plain snprintf(3), because it results in simpler error
detection (it doesn't need a check >=countof(buf), but rather <0).
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/sprintf.h | 4 ++
lib/vsprintf.c | 109 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 113 insertions(+)
diff --git a/include/linux/sprintf.h b/include/linux/sprintf.h
index 51cab2def9ec..c3dbfd2efd2b 100644
--- a/include/linux/sprintf.h
+++ b/include/linux/sprintf.h
@@ -11,8 +11,12 @@ __printf(2, 3) int sprintf(char *buf, const char * fmt, ...);
__printf(2, 0) int vsprintf(char *buf, const char *, va_list);
__printf(3, 4) int snprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
+__printf(3, 4) int stprintf(char *buf, size_t size, const char *fmt, ...);
+__printf(3, 0) int vstprintf(char *buf, size_t size, const char *fmt, va_list args);
__printf(3, 4) int scnprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
+__printf(3, 4) char *seprintf(char *p, const char end[0], const char *fmt, ...);
+__printf(3, 0) char *vseprintf(char *p, const char end[0], const char *fmt, va_list args);
__printf(2, 3) __malloc char *kasprintf(gfp_t gfp, const char *fmt, ...);
__printf(2, 0) __malloc char *kvasprintf(gfp_t gfp, const char *fmt, va_list args);
__printf(2, 0) const char *kvasprintf_const(gfp_t gfp, const char *fmt, va_list args);
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 01699852f30c..a3efacadb5e5 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -2892,6 +2892,37 @@ int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
}
EXPORT_SYMBOL(vsnprintf);
+/**
+ * vstprintf - Format a string and place it in a buffer
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
+ *
+ * The return value is the length of the new string.
+ * If the string is truncated, the function returns -E2BIG.
+ *
+ * If you're not already dealing with a va_list consider using stprintf().
+ *
+ * See the vsnprintf() documentation for format string extensions over C99.
+ */
+int vstprintf(char *buf, size_t size, const char *fmt, va_list args)
+{
+ int len;
+
+ len = vsnprintf(buf, size, fmt, args);
+
+ // It seems the kernel's vsnprintf() doesn't fail?
+ //if (unlikely(len < 0))
+ // return -E2BIG;
+
+ if (unlikely(len >= size))
+ return -E2BIG;
+
+ return len;
+}
+EXPORT_SYMBOL(vstprintf);
+
/**
* vscnprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -2923,6 +2954,36 @@ int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
}
EXPORT_SYMBOL(vscnprintf);
+/**
+ * vseprintf - Format a string and place it in a buffer
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
+ *
+ * The return value is a pointer to the trailing '\0'.
+ * If @p is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
+ *
+ * If you're not already dealing with a va_list consider using seprintf().
+ *
+ * See the vsnprintf() documentation for format string extensions over C99.
+ */
+char *vseprintf(char *p, const char end[0], const char *fmt, va_list args)
+{
+ int len;
+
+ if (unlikely(p == NULL))
+ return NULL;
+
+ len = vstprintf(p, end - p, fmt, args);
+ if (unlikely(len < 0))
+ return NULL;
+
+ return p + len;
+}
+EXPORT_SYMBOL(vseprintf);
+
/**
* snprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -2950,6 +3011,30 @@ int snprintf(char *buf, size_t size, const char *fmt, ...)
}
EXPORT_SYMBOL(snprintf);
+/**
+ * stprintf - Format a string and place it in a buffer
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
+ *
+ * The return value is the length of the new string.
+ * If the string is truncated, the function returns -E2BIG.
+ */
+
+int stprintf(char *buf, size_t size, const char *fmt, ...)
+{
+ va_list args;
+ int len;
+
+ va_start(args, fmt);
+ len = vstprintf(buf, size, fmt, args);
+ va_end(args);
+
+ return len;
+}
+EXPORT_SYMBOL(stprintf);
+
/**
* scnprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -2974,6 +3059,30 @@ int scnprintf(char *buf, size_t size, const char *fmt, ...)
}
EXPORT_SYMBOL(scnprintf);
+/**
+ * seprintf - Format a string and place it in a buffer
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
+ *
+ * The return value is a pointer to the trailing '\0'.
+ * If @buf is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
+ */
+
+char *seprintf(char *p, const char end[0], const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ p = vseprintf(p, end, fmt, args);
+ va_end(args);
+
+ return p;
+}
+EXPORT_SYMBOL(seprintf);
+
/**
* vsprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v3 2/7] stacktrace, stackdepot: Add seprintf()-like variants of functions
2025-07-07 5:06 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 1/7] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
@ 2025-07-07 5:06 ` Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs Alejandro Colomar
` (5 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 5:06 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton
I think there's an anomaly in stack_depot_s*print(). If we have zero
entries, we don't copy anything, which means the string is still not a
string. Normally, this function is called surrounded by other calls to
s*printf(), which guarantee that there's a '\0', but maybe we should
make sure to write a '\0' here?
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/stackdepot.h | 13 +++++++++++++
include/linux/stacktrace.h | 3 +++
kernel/stacktrace.c | 28 ++++++++++++++++++++++++++++
lib/stackdepot.c | 12 ++++++++++++
4 files changed, 56 insertions(+)
diff --git a/include/linux/stackdepot.h b/include/linux/stackdepot.h
index 2cc21ffcdaf9..a7749fc3ac7c 100644
--- a/include/linux/stackdepot.h
+++ b/include/linux/stackdepot.h
@@ -219,6 +219,19 @@ void stack_depot_print(depot_stack_handle_t stack);
int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
int spaces);
+/**
+ * stack_depot_seprint - Print a stack trace from stack depot into a buffer
+ *
+ * @handle: Stack depot handle returned from stack_depot_save()
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @spaces: Number of leading spaces to print
+ *
+ * Return: Pointer to trailing '\0'; or NULL on truncation
+ */
+char *stack_depot_seprint(depot_stack_handle_t handle, char *p,
+ const char end[0], int spaces);
+
/**
* stack_depot_put - Drop a reference to a stack trace from stack depot
*
diff --git a/include/linux/stacktrace.h b/include/linux/stacktrace.h
index 97455880ac41..748936386c89 100644
--- a/include/linux/stacktrace.h
+++ b/include/linux/stacktrace.h
@@ -67,6 +67,9 @@ void stack_trace_print(const unsigned long *trace, unsigned int nr_entries,
int spaces);
int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
unsigned int nr_entries, int spaces);
+char *stack_trace_seprint(char *p, const char end[0],
+ const unsigned long *entries, unsigned int nr_entries,
+ int spaces);
unsigned int stack_trace_save(unsigned long *store, unsigned int size,
unsigned int skipnr);
unsigned int stack_trace_save_tsk(struct task_struct *task,
diff --git a/kernel/stacktrace.c b/kernel/stacktrace.c
index afb3c116da91..65caf9e63673 100644
--- a/kernel/stacktrace.c
+++ b/kernel/stacktrace.c
@@ -70,6 +70,34 @@ int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
}
EXPORT_SYMBOL_GPL(stack_trace_snprint);
+/**
+ * stack_trace_seprint - Print the entries in the stack trace into a buffer
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @entries: Pointer to storage array
+ * @nr_entries: Number of entries in the storage array
+ * @spaces: Number of leading spaces to print
+ *
+ * Return: Pointer to the trailing '\0'; or NULL on truncation.
+ */
+char *stack_trace_seprint(char *p, const char end[0],
+ const unsigned long *entries, unsigned int nr_entries,
+ int spaces)
+{
+ unsigned int i;
+
+ if (WARN_ON(!entries))
+ return 0;
+
+ for (i = 0; i < nr_entries; i++) {
+ p = seprintf(p, end, "%*c%pS\n", 1 + spaces, ' ',
+ (void *)entries[i]);
+ }
+
+ return p;
+}
+EXPORT_SYMBOL_GPL(stack_trace_seprint);
+
#ifdef CONFIG_ARCH_STACKWALK
struct stacktrace_cookie {
diff --git a/lib/stackdepot.c b/lib/stackdepot.c
index 73d7b50924ef..749496e6a6f1 100644
--- a/lib/stackdepot.c
+++ b/lib/stackdepot.c
@@ -771,6 +771,18 @@ int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
}
EXPORT_SYMBOL_GPL(stack_depot_snprint);
+char *stack_depot_seprint(depot_stack_handle_t handle, char *p,
+ const char end[0], int spaces)
+{
+ unsigned long *entries;
+ unsigned int nr_entries;
+
+ nr_entries = stack_depot_fetch(handle, &entries);
+ return nr_entries ? stack_trace_seprint(p, end, entries, nr_entries,
+ spaces) : p;
+}
+EXPORT_SYMBOL_GPL(stack_depot_seprint);
+
depot_stack_handle_t __must_check stack_depot_set_extra_bits(
depot_stack_handle_t handle, unsigned int extra_bits)
{
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 5:06 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 1/7] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 2/7] stacktrace, stackdepot: Add seprintf()-like variants of functions Alejandro Colomar
@ 2025-07-07 5:06 ` Alejandro Colomar
2025-07-07 7:44 ` Marco Elver
2025-07-07 19:17 ` Linus Torvalds
2025-07-07 5:06 ` [RFC v3 4/7] array_size.h: Add ENDOF() Alejandro Colomar
` (4 subsequent siblings)
7 siblings, 2 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 5:06 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Sven Schnelle, Heiko Carstens, Tvrtko Ursulin,
Huang, Ying, Lee Schermerhorn, Linus Torvalds, Christophe JAILLET,
Hyeonggon Yoo, Chao Yu
While doing this, I detected some anomalies in the existing code:
mm/kfence/kfence_test.c:
- The last call to scnprintf() did increment 'cur', but it's
unused after that, so it was dead code. I've removed the dead
code in this patch.
- 'end' is calculated as
end = &expect[0][sizeof(expect[0] - 1)];
However, the '-1' doesn't seem to be necessary. When passing
$2 to scnprintf(), the size was specified as 'end - cur'.
And scnprintf() --just like snprintf(3)--, won't write more
than $2 bytes (including the null byte). That means that
scnprintf() wouldn't write more than
&expect[0][sizeof(expect[0]) - 1] - expect[0]
which simplifies to
sizeof(expect[0]) - 1
bytes. But we have sizeof(expect[0]) bytes available, so
we're wasting one byte entirely. This is a benign off-by-one
bug. The two occurrences of this bug will be fixed in a
following patch in this series.
mm/kmsan/kmsan_test.c:
The same benign off-by-one bug calculating the remaining size.
mm/mempolicy.c:
This file uses the 'p += snprintf()' anti-pattern. That will
overflow the pointer on truncation, which has undefined
behavior. Using seprintf(), this bug is fixed.
As in the previous file, here there was also dead code in the
last scnprintf() call, by incrementing a pointer that is not
used after the call. I've removed the dead code.
mm/page_owner.c:
Within print_page_owner(), there are some calls to scnprintf(),
which do report truncation. And then there are other calls to
snprintf(), where we handle errors (there are two 'goto err').
I've kept the existing error handling, as I trust it's there for
a good reason (i.e., we may want to avoid calling
print_page_owner_memcg() if we truncated before). Please review
if this amount of error handling is the right one, or if we want
to add or remove some. For seprintf(), a single test for null
after the last call is enough to detect truncation.
mm/slub.c:
Again, the 'p += snprintf()' anti-pattern. This is UB, and by
using seprintf() we've fixed the bug.
Fixes: f99e12b21b84 (2021-07-30; "kfence: add function to mask address bits")
[alx: that commit introduced dead code]
Fixes: af649773fb25 (2024-07-17; "mm/numa_balancing: teach mpol_to_str about the balancing mode")
[alx: that commit added p+=snprintf() calls, which are UB]
Fixes: 2291990ab36b (2008-04-28; "mempolicy: clean-up mpol-to-str() mempolicy formatting")
[alx: that commit changed p+=sprintf() into p+=snprintf(), which is still UB]
Fixes: 948927ee9e4f (2013-11-13; "mm, mempolicy: make mpol_to_str robust and always succeed")
[alx: that commit changes old code into p+=snprintf(), which is still UB]
[alx: that commit also produced dead code by leaving the last 'p+=...']
Fixes: d65360f22406 (2022-09-26; "mm/slub: clean up create_unique_id()")
[alx: that commit changed p+=sprintf() into p+=snprintf(), which is still UB]
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Sven Schnelle <svens@linux.ibm.com>
Cc: Marco Elver <elver@google.com>
Cc: Heiko Carstens <hca@linux.ibm.com>
Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
Cc: "Huang, Ying" <ying.huang@intel.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Lee Schermerhorn <lee.schermerhorn@hp.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: David Rientjes <rientjes@google.com>
Cc: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
Cc: Hyeonggon Yoo <42.hyeyoo@gmail.com>
Cc: Chao Yu <chao.yu@oppo.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/kfence/kfence_test.c | 24 ++++++++++++------------
mm/kmsan/kmsan_test.c | 4 ++--
mm/mempolicy.c | 18 +++++++++---------
mm/page_owner.c | 32 +++++++++++++++++---------------
mm/slub.c | 5 +++--
5 files changed, 43 insertions(+), 40 deletions(-)
diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
index 00034e37bc9f..ff734c514c03 100644
--- a/mm/kfence/kfence_test.c
+++ b/mm/kfence/kfence_test.c
@@ -113,26 +113,26 @@ static bool report_matches(const struct expect_report *r)
end = &expect[0][sizeof(expect[0]) - 1];
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds %s",
+ cur = seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s",
get_access_type(r));
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free %s",
+ cur = seprintf(cur, end, "BUG: KFENCE: use-after-free %s",
get_access_type(r));
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: memory corruption");
+ cur = seprintf(cur, end, "BUG: KFENCE: memory corruption");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid %s",
+ cur = seprintf(cur, end, "BUG: KFENCE: invalid %s",
get_access_type(r));
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid free");
+ cur = seprintf(cur, end, "BUG: KFENCE: invalid free");
break;
}
- scnprintf(cur, end - cur, " in %pS", r->fn);
+ seprintf(cur, end, " in %pS", r->fn);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expect[0], '+');
if (cur)
@@ -144,26 +144,26 @@ static bool report_matches(const struct expect_report *r)
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "Out-of-bounds %s at", get_access_type(r));
+ cur = seprintf(cur, end, "Out-of-bounds %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "Use-after-free %s at", get_access_type(r));
+ cur = seprintf(cur, end, "Use-after-free %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "Corrupted memory at");
+ cur = seprintf(cur, end, "Corrupted memory at");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "Invalid %s at", get_access_type(r));
+ cur = seprintf(cur, end, "Invalid %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "Invalid free of");
+ cur = seprintf(cur, end, "Invalid free of");
break;
}
- cur += scnprintf(cur, end - cur, " 0x%p", (void *)addr);
+ seprintf(cur, end, " 0x%p", (void *)addr);
spin_lock_irqsave(&observed.lock, flags);
if (!report_available())
diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
index 9733a22c46c1..a062a46b2d24 100644
--- a/mm/kmsan/kmsan_test.c
+++ b/mm/kmsan/kmsan_test.c
@@ -107,9 +107,9 @@ static bool report_matches(const struct expect_report *r)
cur = expected_header;
end = &expected_header[sizeof(expected_header) - 1];
- cur += scnprintf(cur, end - cur, "BUG: KMSAN: %s", r->error_type);
+ cur = seprintf(cur, end, "BUG: KMSAN: %s", r->error_type);
- scnprintf(cur, end - cur, " in %s", r->symbol);
+ seprintf(cur, end, " in %s", r->symbol);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expected_header, '+');
if (cur)
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index b28a1e6ae096..c696e4a6f4c2 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -3359,6 +3359,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
{
char *p = buffer;
+ char *e = buffer + maxlen;
nodemask_t nodes = NODE_MASK_NONE;
unsigned short mode = MPOL_DEFAULT;
unsigned short flags = 0;
@@ -3384,33 +3385,32 @@ void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
break;
default:
WARN_ON_ONCE(1);
- snprintf(p, maxlen, "unknown");
+ seprintf(p, e, "unknown");
return;
}
- p += snprintf(p, maxlen, "%s", policy_modes[mode]);
+ p = seprintf(p, e, "%s", policy_modes[mode]);
if (flags & MPOL_MODE_FLAGS) {
- p += snprintf(p, buffer + maxlen - p, "=");
+ p = seprintf(p, e, "=");
/*
* Static and relative are mutually exclusive.
*/
if (flags & MPOL_F_STATIC_NODES)
- p += snprintf(p, buffer + maxlen - p, "static");
+ p = seprintf(p, e, "static");
else if (flags & MPOL_F_RELATIVE_NODES)
- p += snprintf(p, buffer + maxlen - p, "relative");
+ p = seprintf(p, e, "relative");
if (flags & MPOL_F_NUMA_BALANCING) {
if (!is_power_of_2(flags & MPOL_MODE_FLAGS))
- p += snprintf(p, buffer + maxlen - p, "|");
- p += snprintf(p, buffer + maxlen - p, "balancing");
+ p = seprintf(p, e, "|");
+ p = seprintf(p, e, "balancing");
}
}
if (!nodes_empty(nodes))
- p += scnprintf(p, buffer + maxlen - p, ":%*pbl",
- nodemask_pr_args(&nodes));
+ seprintf(p, e, ":%*pbl", nodemask_pr_args(&nodes));
}
#ifdef CONFIG_SYSFS
diff --git a/mm/page_owner.c b/mm/page_owner.c
index cc4a6916eec6..5811738e3320 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -496,7 +496,7 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
/*
* Looking for memcg information and print it out
*/
-static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
+static inline char *print_page_owner_memcg(char *p, const char end[0],
struct page *page)
{
#ifdef CONFIG_MEMCG
@@ -511,8 +511,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
goto out_unlock;
if (memcg_data & MEMCG_DATA_OBJEXTS)
- ret += scnprintf(kbuf + ret, count - ret,
- "Slab cache page\n");
+ p = seprintf(p, end, "Slab cache page\n");
memcg = page_memcg_check(page);
if (!memcg)
@@ -520,7 +519,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
online = (memcg->css.flags & CSS_ONLINE);
cgroup_name(memcg->css.cgroup, name, sizeof(name));
- ret += scnprintf(kbuf + ret, count - ret,
+ p = seprintf(p, end,
"Charged %sto %smemcg %s\n",
PageMemcgKmem(page) ? "(via objcg) " : "",
online ? "" : "offline ",
@@ -529,7 +528,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
rcu_read_unlock();
#endif /* CONFIG_MEMCG */
- return ret;
+ return p;
}
static ssize_t
@@ -538,14 +537,16 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
depot_stack_handle_t handle)
{
int ret, pageblock_mt, page_mt;
- char *kbuf;
+ char *kbuf, *p, *e;
count = min_t(size_t, count, PAGE_SIZE);
kbuf = kmalloc(count, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
- ret = scnprintf(kbuf, count,
+ p = kbuf;
+ e = kbuf + count;
+ p = seprintf(p, e,
"Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n",
page_owner->order, page_owner->gfp_mask,
&page_owner->gfp_mask, page_owner->pid,
@@ -555,7 +556,7 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
/* Print information relevant to grouping pages by mobility */
pageblock_mt = get_pageblock_migratetype(page);
page_mt = gfp_migratetype(page_owner->gfp_mask);
- ret += scnprintf(kbuf + ret, count - ret,
+ p = seprintf(p, e,
"PFN 0x%lx type %s Block %lu type %s Flags %pGp\n",
pfn,
migratetype_names[page_mt],
@@ -563,22 +564,23 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
migratetype_names[pageblock_mt],
&page->flags);
- ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
- if (ret >= count)
- goto err;
+ p = stack_depot_seprint(handle, p, e, 0);
+ if (p == NULL)
+ goto err; // XXX: Should we remove this error handling?
if (page_owner->last_migrate_reason != -1) {
- ret += scnprintf(kbuf + ret, count - ret,
+ p = seprintf(p, e,
"Page has been migrated, last migrate reason: %s\n",
migrate_reason_names[page_owner->last_migrate_reason]);
}
- ret = print_page_owner_memcg(kbuf, count, ret, page);
+ p = print_page_owner_memcg(p, e, page);
- ret += snprintf(kbuf + ret, count - ret, "\n");
- if (ret >= count)
+ p = seprintf(p, e, "\n");
+ if (p == NULL)
goto err;
+ ret = p - kbuf;
if (copy_to_user(buf, kbuf, ret))
ret = -EFAULT;
diff --git a/mm/slub.c b/mm/slub.c
index be8b09e09d30..b67c6ca0d0f7 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -7451,6 +7451,7 @@ static char *create_unique_id(struct kmem_cache *s)
{
char *name = kmalloc(ID_STR_LENGTH, GFP_KERNEL);
char *p = name;
+ char *e = name + ID_STR_LENGTH;
if (!name)
return ERR_PTR(-ENOMEM);
@@ -7475,9 +7476,9 @@ static char *create_unique_id(struct kmem_cache *s)
*p++ = 'A';
if (p != name + 1)
*p++ = '-';
- p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
+ p = seprintf(p, e, "%07u", s->size);
- if (WARN_ON(p > name + ID_STR_LENGTH - 1)) {
+ if (WARN_ON(p == NULL)) {
kfree(name);
return ERR_PTR(-EINVAL);
}
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v3 4/7] array_size.h: Add ENDOF()
2025-07-07 5:06 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
` (2 preceding siblings ...)
2025-07-07 5:06 ` [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-07 5:06 ` Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 5/7] mm: Fix benign off-by-one bugs Alejandro Colomar
` (3 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 5:06 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton
This macro is useful to calculate the second argument to seprintf(),
avoiding off-by-one bugs.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/array_size.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/include/linux/array_size.h b/include/linux/array_size.h
index 06d7d83196ca..781bdb70d939 100644
--- a/include/linux/array_size.h
+++ b/include/linux/array_size.h
@@ -10,4 +10,10 @@
*/
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
+/**
+ * ENDOF - get a pointer to one past the last element in array @a
+ * @a: array
+ */
+#define ENDOF(a) (a + ARRAY_SIZE(a))
+
#endif /* _LINUX_ARRAY_SIZE_H */
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v3 5/7] mm: Fix benign off-by-one bugs
2025-07-07 5:06 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
` (3 preceding siblings ...)
2025-07-07 5:06 ` [RFC v3 4/7] array_size.h: Add ENDOF() Alejandro Colomar
@ 2025-07-07 5:06 ` Alejandro Colomar
2025-07-07 7:46 ` Marco Elver
2025-07-07 5:06 ` [RFC v3 6/7] sprintf: Add [V]STPRINTF() Alejandro Colomar
` (2 subsequent siblings)
7 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 5:06 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Jann Horn, Linus Torvalds
We were wasting a byte due to an off-by-one bug. s[c]nprintf()
doesn't write more than $2 bytes including the null byte, so trying to
pass 'size-1' there is wasting one byte. Now that we use seprintf(),
the situation isn't different: seprintf() will stop writing *before*
'end' --that is, at most the terminating null byte will be written at
'end-1'--.
Fixes: bc8fbc5f305a (2021-02-26; "kfence: add test suite")
Fixes: 8ed691b02ade (2022-10-03; "kmsan: add tests for KMSAN")
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Marco Elver <elver@google.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Jann Horn <jannh@google.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/kfence/kfence_test.c | 4 ++--
mm/kmsan/kmsan_test.c | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
index ff734c514c03..f02c3e23638a 100644
--- a/mm/kfence/kfence_test.c
+++ b/mm/kfence/kfence_test.c
@@ -110,7 +110,7 @@ static bool report_matches(const struct expect_report *r)
/* Title */
cur = expect[0];
- end = &expect[0][sizeof(expect[0]) - 1];
+ end = ENDOF(expect[0]);
switch (r->type) {
case KFENCE_ERROR_OOB:
cur = seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s",
@@ -140,7 +140,7 @@ static bool report_matches(const struct expect_report *r)
/* Access information */
cur = expect[1];
- end = &expect[1][sizeof(expect[1]) - 1];
+ end = ENDOF(expect[1]);
switch (r->type) {
case KFENCE_ERROR_OOB:
diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
index a062a46b2d24..882500807db8 100644
--- a/mm/kmsan/kmsan_test.c
+++ b/mm/kmsan/kmsan_test.c
@@ -105,7 +105,7 @@ static bool report_matches(const struct expect_report *r)
/* Title */
cur = expected_header;
- end = &expected_header[sizeof(expected_header) - 1];
+ end = ENDOF(expected_header);
cur = seprintf(cur, end, "BUG: KMSAN: %s", r->error_type);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v3 6/7] sprintf: Add [V]STPRINTF()
2025-07-07 5:06 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
` (4 preceding siblings ...)
2025-07-07 5:06 ` [RFC v3 5/7] mm: Fix benign off-by-one bugs Alejandro Colomar
@ 2025-07-07 5:06 ` Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 7/7] mm: Use [V]STPRINTF() to avoid specifying the array size Alejandro Colomar
2025-07-07 5:11 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 5:06 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton
These macros take the array size argument implicitly to avoid programmer
mistakes. This guarantees that the input is an array, unlike the common
call
snprintf(buf, sizeof(buf), ...);
which is dangerous if the programmer passes a pointer.
These macros are essentially the same as the 2-argument version of
strscpy(), but with a formatted string.
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/sprintf.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/include/linux/sprintf.h b/include/linux/sprintf.h
index c3dbfd2efd2b..6080d3732055 100644
--- a/include/linux/sprintf.h
+++ b/include/linux/sprintf.h
@@ -4,6 +4,10 @@
#include <linux/compiler_attributes.h>
#include <linux/types.h>
+#include <linux/array_size.h>
+
+#define STPRINTF(a, fmt, ...) stprintf(a, ARRAY_SIZE(a), fmt, ##__VA_ARGS__)
+#define VSTPRINTF(a, fmt, ap) vstprintf(a, ARRAY_SIZE(a), fmt, ap)
int num_to_str(char *buf, int size, unsigned long long num, unsigned int width);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v3 7/7] mm: Use [V]STPRINTF() to avoid specifying the array size
2025-07-07 5:06 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
` (5 preceding siblings ...)
2025-07-07 5:06 ` [RFC v3 6/7] sprintf: Add [V]STPRINTF() Alejandro Colomar
@ 2025-07-07 5:06 ` Alejandro Colomar
2025-07-07 5:11 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 5:06 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/backing-dev.c | 2 +-
mm/cma.c | 4 ++--
mm/cma_debug.c | 2 +-
mm/hugetlb.c | 3 +--
mm/hugetlb_cgroup.c | 2 +-
mm/hugetlb_cma.c | 2 +-
mm/kasan/report.c | 3 +--
mm/memblock.c | 4 ++--
mm/percpu.c | 2 +-
mm/shrinker_debug.c | 2 +-
mm/zswap.c | 2 +-
11 files changed, 13 insertions(+), 15 deletions(-)
diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index 783904d8c5ef..408fdf52ee5d 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -1090,7 +1090,7 @@ int bdi_register_va(struct backing_dev_info *bdi, const char *fmt, va_list args)
if (bdi->dev) /* The driver needs to use separate queues per device */
return 0;
- vsnprintf(bdi->dev_name, sizeof(bdi->dev_name), fmt, args);
+ VSTPRINTF(bdi->dev_name, fmt, args);
dev = device_create(&bdi_class, NULL, MKDEV(0, 0), bdi, bdi->dev_name);
if (IS_ERR(dev))
return PTR_ERR(dev);
diff --git a/mm/cma.c b/mm/cma.c
index c04be488b099..49c54a74d6ce 100644
--- a/mm/cma.c
+++ b/mm/cma.c
@@ -237,9 +237,9 @@ static int __init cma_new_area(const char *name, phys_addr_t size,
cma_area_count++;
if (name)
- snprintf(cma->name, CMA_MAX_NAME, "%s", name);
+ STPRINTF(cma->name, "%s", name);
else
- snprintf(cma->name, CMA_MAX_NAME, "cma%d\n", cma_area_count);
+ STPRINTF(cma->name, "cma%d\n", cma_area_count);
cma->available_count = cma->count = size >> PAGE_SHIFT;
cma->order_per_bit = order_per_bit;
diff --git a/mm/cma_debug.c b/mm/cma_debug.c
index fdf899532ca0..ae94b7ae6710 100644
--- a/mm/cma_debug.c
+++ b/mm/cma_debug.c
@@ -186,7 +186,7 @@ static void cma_debugfs_add_one(struct cma *cma, struct dentry *root_dentry)
rangedir = debugfs_create_dir("ranges", tmp);
for (r = 0; r < cma->nranges; r++) {
cmr = &cma->ranges[r];
- snprintf(rdirname, sizeof(rdirname), "%d", r);
+ STPRINTF(rdirname, "%d", r);
dir = debugfs_create_dir(rdirname, rangedir);
debugfs_create_file("base_pfn", 0444, dir,
&cmr->base_pfn, &cma_debugfs_fops);
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 6a3cf7935c14..6d0bd88eeba9 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -4780,8 +4780,7 @@ void __init hugetlb_add_hstate(unsigned int order)
for (i = 0; i < MAX_NUMNODES; ++i)
INIT_LIST_HEAD(&h->hugepage_freelists[i]);
INIT_LIST_HEAD(&h->hugepage_activelist);
- snprintf(h->name, HSTATE_NAME_LEN, "hugepages-%lukB",
- huge_page_size(h)/SZ_1K);
+ STPRINTF(h->name, "hugepages-%lukB", huge_page_size(h)/SZ_1K);
parsed_hstate = h;
}
diff --git a/mm/hugetlb_cgroup.c b/mm/hugetlb_cgroup.c
index 58e895f3899a..8f5ffe35d16d 100644
--- a/mm/hugetlb_cgroup.c
+++ b/mm/hugetlb_cgroup.c
@@ -822,7 +822,7 @@ hugetlb_cgroup_cfttypes_init(struct hstate *h, struct cftype *cft,
for (i = 0; i < tmpl_size; cft++, tmpl++, i++) {
*cft = *tmpl;
/* rebuild the name */
- snprintf(cft->name, MAX_CFTYPE_NAME, "%s.%s", buf, tmpl->name);
+ STPRINTF(cft->name, "%s.%s", buf, tmpl->name);
/* rebuild the private */
cft->private = MEMFILE_PRIVATE(idx, tmpl->private);
/* rebuild the file_offset */
diff --git a/mm/hugetlb_cma.c b/mm/hugetlb_cma.c
index e0f2d5c3a84c..c28d09e0ce68 100644
--- a/mm/hugetlb_cma.c
+++ b/mm/hugetlb_cma.c
@@ -211,7 +211,7 @@ void __init hugetlb_cma_reserve(int order)
size = round_up(size, PAGE_SIZE << order);
- snprintf(name, sizeof(name), "hugetlb%d", nid);
+ STPRINTF(name, "hugetlb%d", nid);
/*
* Note that 'order per bit' is based on smallest size that
* may be returned to CMA allocator in the case of
diff --git a/mm/kasan/report.c b/mm/kasan/report.c
index 8357e1a33699..62a9bcff236a 100644
--- a/mm/kasan/report.c
+++ b/mm/kasan/report.c
@@ -486,8 +486,7 @@ static void print_memory_metadata(const void *addr)
char buffer[4 + (BITS_PER_LONG / 8) * 2];
char metadata[META_BYTES_PER_ROW];
- snprintf(buffer, sizeof(buffer),
- (i == 0) ? ">%px: " : " %px: ", row);
+ STPRINTF(buffer, (i == 0) ? ">%px: " : " %px: ", row);
/*
* We should not pass a shadow pointer to generic
diff --git a/mm/memblock.c b/mm/memblock.c
index 0e9ebb8aa7fe..20d3928a6b13 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -2021,7 +2021,7 @@ static void __init_memblock memblock_dump(struct memblock_type *type)
flags = rgn->flags;
#ifdef CONFIG_NUMA
if (numa_valid_node(memblock_get_region_node(rgn)))
- snprintf(nid_buf, sizeof(nid_buf), " on node %d",
+ STPRINTF(nid_buf, " on node %d",
memblock_get_region_node(rgn));
#endif
pr_info(" %s[%#x]\t[%pa-%pa], %pa bytes%s flags: %#x\n",
@@ -2379,7 +2379,7 @@ int reserve_mem_release_by_name(const char *name)
start = phys_to_virt(map->start);
end = start + map->size - 1;
- snprintf(buf, sizeof(buf), "reserve_mem:%s", name);
+ STPRINTF(buf, "reserve_mem:%s", name);
free_reserved_area(start, end, 0, buf);
map->size = 0;
diff --git a/mm/percpu.c b/mm/percpu.c
index b35494c8ede2..8d5b5ac7dbef 100644
--- a/mm/percpu.c
+++ b/mm/percpu.c
@@ -3186,7 +3186,7 @@ int __init pcpu_page_first_chunk(size_t reserved_size, pcpu_fc_cpu_to_node_fn_t
int upa;
int nr_g0_units;
- snprintf(psize_str, sizeof(psize_str), "%luK", PAGE_SIZE >> 10);
+ STPRINTF(psize_str, "%luK", PAGE_SIZE >> 10);
ai = pcpu_build_alloc_info(reserved_size, 0, PAGE_SIZE, NULL);
if (IS_ERR(ai))
diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
index 20eaee3e97f7..7194f2de8594 100644
--- a/mm/shrinker_debug.c
+++ b/mm/shrinker_debug.c
@@ -176,7 +176,7 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
return id;
shrinker->debugfs_id = id;
- snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
+ STPRINTF(buf, "%s-%d", shrinker->name, id);
/* create debugfs entry */
entry = debugfs_create_dir(buf, shrinker_debugfs_root);
diff --git a/mm/zswap.c b/mm/zswap.c
index 204fb59da33c..01c96cb5e84f 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -271,7 +271,7 @@ static struct zswap_pool *zswap_pool_create(char *type, char *compressor)
return NULL;
/* unique name for each pool specifically required by zsmalloc */
- snprintf(name, 38, "zswap%x", atomic_inc_return(&zswap_pools_count));
+ STPRINTF(name, "zswap%x", atomic_inc_return(&zswap_pools_count));
pool->zpool = zpool_create_pool(type, name, gfp);
if (!pool->zpool) {
pr_err("%s zpool not available\n", type);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* Re: [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs
2025-07-07 5:06 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
` (6 preceding siblings ...)
2025-07-07 5:06 ` [RFC v3 7/7] mm: Use [V]STPRINTF() to avoid specifying the array size Alejandro Colomar
@ 2025-07-07 5:11 ` Alejandro Colomar
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 5:11 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Kees Cook, Christopher Bazley, shadow, linux-kernel,
Andrew Morton, kasan-dev, Dmitry Vyukov, Alexander Potapenko,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton
[-- Attachment #1: Type: text/plain, Size: 15256 bytes --]
On Mon, Jul 07, 2025 at 07:06:06AM +0200, Alejandro Colomar wrote:
> I've written an analysis of snprintf(3), why it's dangerous, and how
> these APIs address that, and will present it as a proposal for
> standardization of these APIs in ISO C2y. I'll send that as a reply to
> this message in a moment, as I believe it will be interesting for
> linux-hardening@.
Hi,
Here is the proposal for ISO C2y (see below). I'll also send it to the
C Committee for discussion.
Have a lovely night!
Alex
---
Name
alx-0049r1 - add seprintf()
Principles
- Codify existing practice to address evident deficiencies.
- Enable secure programming
Category
Standardize existing libc APIs
Author
Alejandro Colomar <alx@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
History
<https://www.alejandro-colomar.es/src/alx/alx/wg14/alx-0049.git/>
r0 (2025-07-06):
- Initial draft.
r1 (2025-07-06):
- wfix.
- tfix.
- Expand on the off-by-one bugs.
- Note that ignoring truncation is not valid most of the time.
Rationale
snprintf(3) is very difficult to chain for writing parts of a
string in separate calls, such as in a loop.
Let's start from the obvious sprintf(3) code (sprintf(3) will
not prevent overflow, but let's take it as a baseline from which
programmers start thinking):
p = buf;
for (...)
p += sprintf(p, ...);
Then, programmers will start thinking about preventing buffer
overflows. Programmers sometimes will naively add some buffer
size information and use snprintf(3):
p = buf;
size = countof(buf);
for (...)
p += snprintf(p, size - (p - buf), ...);
if (p >= buf + size) // or worse, (p > buf + size - 1)
goto fail;
(Except for minor differences, this kind of code can be found
everywhere. Here are a couple of examples:
<https://elixir.bootlin.com/linux/v6.14/source/mm/slub.c#L7231>
<https://elixir.bootlin.com/linux/v6.14/source/mm/mempolicy.c#L3369>.)
This has several issues, starting with the difficulty of getting
the second argument right. Sometimes, programmers will be too
confused, and slap a -1 there just to be safe.
p = buf;
size = countof(buf);
for (...)
p += snprintf(p, size - (p - buf) - 1, ...);
if (p >= buf + size -1)
goto fail;
(Except for minor differences, this kind of code can be found
everywhere. Here are a couple of examples:
<https://elixir.bootlin.com/linux/v6.14/source/mm/kfence/kfence_test.c#L113>
<https://elixir.bootlin.com/linux/v6.14/source/mm/kmsan/kmsan_test.c#L108>.)
Programmers will sometimes hold a pointer to one past the last
element in the array. This is a wise choice, as that pointer is
constant throughout the lifetime of the object. Then,
programmers might end up with something like this:
p = buf;
e = buf + countof(buf);
for (...)
p += snprintf(p, e - p, ...);
if (p >= end)
goto fail;
This is certainly much cleaner. Now a programmer might focus on
the fact that this can overflow the pointer. An easy approach
would be to make sure that the function never returns more than
the remaining size. That is, one could implement something like
this scnprintf() --name chosen to match the Linux kernel API of
the same name--. For the sake of simplicity, let's ignore
multiple evaluation of arguments.
#define scnprintf(s, size, ...) \
({ \
int len_; \
len_ = snprintf(s, size, __VA_ARGS__); \
if (len_ == -1) \
len_ = 0; \
if (len_ >= size) \
len_ = size - 1; \
\
len_; \
})
p = buf;
e = buf + countof(buf);
for (...)
p += scnprintf(p, e - p, ...);
(Except for minor differences, this kind of code can be found
everywhere. Here's an example:
<https://elixir.bootlin.com/linux/v6.14/source/mm/kfence/kfence_test.c#L131>.)
Now the programmer got rid of pointer overflow. However, they
now have silent truncation that cannot be detected. In some
cases this may seem good enough. However, often it's not. And
anyway, some code remains using snprintf(3) to be able to detect
truncation.
Moreover, this kind of code ignores the fact that vsnprintf(3)
can fail internally, in which case there's not even a truncated
string. In the kernel, they're fine, because their internal
vsnprintf() doesn't seem to ever fail, so they can always rely
on the truncated string. This is not reliable in projects that
rely on the libc vsnprintf(3).
For the code that needs to detect truncation, a programmer might
choose a different path. It would keep using snprintf(3), but
would use a temporary length variable instead of the pointer.
p = buf;
e = buf + countof(buf);
for (...) {
len = snprintf(p, e - p, ...);
if (len == -1)
goto fail;
if (len >= e - p)
goto fail;
p += len;
}
This is naturally error-prone. A colleague of mine --which is an
excellent programmer, to be clear--, had a bug even after
knowing about it and having tried to fix it. That shows how
hard it is to write this correctly:
<https://github.com/nginx/unit/pull/734#discussion_r1043963527>
In a similar fashion, the strlcpy(3) manual page from OpenBSD
documents a similar issue when chaining calls to strlcpy(3)
--which was designed with semantics equivalent to snprintf(3),
except for not formatting the string--:
| char *dir, *file, pname[MAXPATHLEN];
| size_t n;
|
| ...
|
| n = strlcpy(pname, dir, sizeof(pname));
| if (n >= sizeof(pname))
| goto toolong;
| if (strlcpy(pname + n, file, sizeof(pname) - n) >= sizeof(pname) - n)
| goto toolong;
|
| However, one may question the validity of such optimiza‐
| tions, as they defeat the whole purpose of strlcpy() and
| strlcat(). As a matter of fact, the first version of
| this manual page got it wrong.
Finally, a programmer might realize that while this is error-
prone, this is indeed the right thing to do. There's no way to
avoid it. One could then think of encapsulating this into an
API that at least would make it easy to write. Then, one might
wonder what the right parameters are for such an API. The only
immutable thing in the loop is 'e'. And apart from that, one
needs to know where to write, which is 'p'. Let's start with
those, and try to keep all the other information (size, len)
without escaping the API. Again, let's ignore multiple-
evaluation issues in this macro for the sake of simplicity.
#define foo(p, e, ...) \
({ \
int len_ = snprintf(p, e - p, __VA_ARGS__); \
if (len_ == -1) \
p = NULL; \
else if (len_ >= e - p) \
p = NULL; \
else \
p += len_; \
p;
})
p = buf;
e = buf + countof(buf);
for (...) {
p = foo(p, e, ...);
if (p == NULL)
goto fail;
}
We've advanced a lot. We got rid of the buffer overflow; we
also got rid of the error-prone code at call site. However, one
might think that checking for truncation after every call is
cumbersome. Indeed, it is possible to slightly tweak the
internals of foo() to propagate errors from previous calls.
#define seprintf(p, e, ...) \
({ \
if (p != NULL) { \
int len_; \
\
len_ = snprintf(p, e - p, __VA_ARGS__); \
if (len_ == -1) \
p = NULL; \
else if (len_ >= e - p) \
p = NULL; \
else \
p += len_; \
} \
p; \
})
p = buf;
e = buf + countof(buf);
for (...)
p = seprintf(p, e, ...);
if (p == NULL)
goto fail;
By propagating an input null pointer directly to the output of
the API, which I've called seprintf() --the 'e' refers to the
'end' pointer, which is the key in this API--, we've allowed
ignoring null pointers until after the very last call. If we
compare our resulting code to the sprintf(3)-based baseline, we
got --perhaps unsurprisingly-- something quite close to it:
p = buf;
for (...)
p += sprintf(p, ...);
vs
p = buf;
e = buf + countof(buf);
for (...)
p = seprintf(p, e, ...);
if (p == NULL)
goto fail;
And the seprintf() version is safe against both truncation and
buffer overflow.
Some important details of the API are:
- When 'p' is NULL, the API must preserve errno. This is
important to be able to determine the cause of the error
after all the chained calls, even when the error occurred in
some call in the middle of the chain.
- When truncation occurs, a distinct errno value must be used,
to signal the programmer that at least the string is reliable
to be used as a null-terminated string. The error code
chosen is E2BIG, for compatibility with strscpy(), a Linux
kernel internal API with which this API shares many features
in common.
- When a hard error (an internal snprintf(3) error) occurs, an
error code different than E2BIG must be used. It is
important to set errno, because if an implementation would
chose to return NULL without setting errno, an old value of
E2BIG could lead the programmer to believe the string was
successfully written (and truncated), and read it with
nefast consequences.
Prior art
This API is implemented in the shadow-utils project.
Plan9 designed something quite close, which they call
seprint(2). The parameters are the same --the right choice--,
but they got the semantics for corner cases wrong. Ironically,
the existing Plan9 code I've seen seems to expect the semantics
that I chose, regardless of the actual semantics of the Plan9
API. This is --I suspect--, because my semantics are actually
the intuitive semantics that one would naively guess of an API
with these parameters and return value.
I've implemented this API for the Linux kernel, and found and
fixed an amazing amount of bugs and other questionable code in
just the first handful of files that I inspected.
<https://lore.kernel.org/linux-hardening/cover.1751747518.git.alx@kernel.org/T/#t>
<https://lore.kernel.org/linux-hardening/cover.1751823326.git.alx@kernel.org/T/#t>
Future directions
The 'e = buf + _Countof(buf)' construct is something I've found
to be quite common. It would be interesting to have an
_Endof operator that would return a pointer to one past the last
element of an array. It would require an array operand, just
like _Countof. If an _Endof operator is deemed too cumbersome
for implementation, an endof() standard macro that expands to
the obvious implementation with _Countof could be okay.
This operator (or operator-like macro) would prevent off-by-one
bugs when calculating the end sentinel value, such as those
shown above (with links to Linux kernel real bugs).
Proposed wording
Based on N3550.
7.24.6 Input/output <stdio.h> :: Formatted input/output functions
## New section after 7.24.6.6 ("The snprintf function"):
+7.24.6.6+1 The <b>seprintf</b> function
+
+Synopsis
+1 #include <stdio.h>
+ char *seprintf(char *restrict p, const char end[0], const char *restrict format, ...);
+
+Description
+2 The <b>$0</b> function
+ is equivalent to <b>fprintf</b>,
+ except that the output is written into an array
+ (specified by argument <tt>p</tt>)
+ rather than a stream.
+ If <tt>p</tt> is a null pointer,
+ nothing is written,
+ and the function returns a null pointer.
+ Otherwise,
+ <tt>end</tt> shall compare greater than <tt>p</tt>;
+ the function writes at most
+ <tt>end - p - 1</tt> non-null characters,
+ the remaining output characters are discarded,
+ and a null character is written
+ at the end of the characters
+ actually written to the array.
+ If copying takes place between objects that overlap,
+ the behavior is undefined.
+
+Returns
+3 The <b>$0</b> function returns
+ a pointer to the terminating null character
+ if the output was written
+ without discarding any characters.
+
+4
+ If <tt>p</tt> is a null pointer,
+ a null pointer is returned,
+ and <b>errno</b> is not modified.
+
+5
+ If any characters are discarded,
+ a null pointer is returned,
+ and the value of the macro <b>E2BIG</b>
+ is stored in <b>errno</b>.
+
+6
+ If an error occurred,
+ a null pointer is returned,
+ and an implementation-defined non-zero value
+ is stored in <b>errno</b>.
## New section after 7.24.6.13 ("The vsnprintf function"):
+7.24.6.13+1 The <b>vseprintf</b> function
+
+Synopsis
+1 #include <stdio.h>
+ char *vseprintf(char *restrict p, const char end[0], const char *restrict format, va_list arg);
+
+Description
+2 The <b>$0</b> function
+ is equivalent to
+ <b>seprintf</b>,
+ with the varying argument list replaced by <tt>arg</tt>.
+
+3
+ The <tt>va_list</tt> argument to this function
+ shall have been initialized by the <b>va_start</b> macro
+ (and possibly subsequent <b>va_arg</b> invocations).
+ This function does not invoke the <b>va_end</b> macro.343)
7.33.2 Formatted wide character input/output functions
## New section after 7.33.2.4 ("The swprintf function"):
+7.33.2.4+1 The <b>sewprintf</b> function
+
+Synopsis
+1 #include <wchar.h>
+ wchar_t *sewprintf(wchar_t *restrict p, const wchar_t end[0], const wchar_t *restrict format, ...);
+
+Description
+2 The <b>$0</b> function
+ is equivalent to
+ <b>seprintf</b>,
+ except that it handles wide strings.
## New section after 7.33.2.8 ("The vswprintf function"):
+7.33.2.8+1 The <b>vsewprintf</b> function
+
+Synopsis
+1 #include <wchar.h>
+ wchar_t *vsewprintf(wchar_t *restrict p, const wchar_t end[0], const wchar_t *restrict format, va_list arg);
+
+Description
+2 The <b>$0</b> function
+ is equivalent to
+ <b>sewprintf</b>,
+ with the varying argument list replaced by <tt>arg</tt>.
+
+3
+ The <tt>va_list</tt> argument to this function
+ shall have been initialized by the <b>va_start</b> macro
+ (and possibly subsequent <b>va_arg</b> invocations).
+ This function does not invoke the <b>va_end</b> macro.407)
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 5:06 ` [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-07 7:44 ` Marco Elver
2025-07-07 14:39 ` Alejandro Colomar
2025-07-07 19:17 ` Linus Torvalds
1 sibling, 1 reply; 98+ messages in thread
From: Marco Elver @ 2025-07-07 7:44 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Christoph Lameter, David Rientjes,
Vlastimil Babka, Roman Gushchin, Harry Yoo, Andrew Clayton,
Sven Schnelle, Heiko Carstens, Tvrtko Ursulin, Huang, Ying,
Lee Schermerhorn, Linus Torvalds, Christophe JAILLET,
Hyeonggon Yoo, Chao Yu
On Mon, 7 Jul 2025 at 07:06, Alejandro Colomar <alx@kernel.org> wrote:
>
> While doing this, I detected some anomalies in the existing code:
>
> mm/kfence/kfence_test.c:
>
> - The last call to scnprintf() did increment 'cur', but it's
> unused after that, so it was dead code. I've removed the dead
> code in this patch.
That was done to be consistent with the other code for readability,
and to be clear where the next bytes should be appended (if someone
decides to append more). There is no runtime dead code, the compiler
optimizes away the assignment. But I'm indifferent, so removing the
assignment is fine if you prefer that.
Did you run the tests? Do they pass?
> - 'end' is calculated as
>
> end = &expect[0][sizeof(expect[0] - 1)];
>
> However, the '-1' doesn't seem to be necessary. When passing
> $2 to scnprintf(), the size was specified as 'end - cur'.
> And scnprintf() --just like snprintf(3)--, won't write more
> than $2 bytes (including the null byte). That means that
> scnprintf() wouldn't write more than
>
> &expect[0][sizeof(expect[0]) - 1] - expect[0]
>
> which simplifies to
>
> sizeof(expect[0]) - 1
>
> bytes. But we have sizeof(expect[0]) bytes available, so
> we're wasting one byte entirely. This is a benign off-by-one
> bug. The two occurrences of this bug will be fixed in a
> following patch in this series.
>
> mm/kmsan/kmsan_test.c:
>
> The same benign off-by-one bug calculating the remaining size.
Same - does the test pass?
> mm/mempolicy.c:
>
> This file uses the 'p += snprintf()' anti-pattern. That will
> overflow the pointer on truncation, which has undefined
> behavior. Using seprintf(), this bug is fixed.
>
> As in the previous file, here there was also dead code in the
> last scnprintf() call, by incrementing a pointer that is not
> used after the call. I've removed the dead code.
>
> mm/page_owner.c:
>
> Within print_page_owner(), there are some calls to scnprintf(),
> which do report truncation. And then there are other calls to
> snprintf(), where we handle errors (there are two 'goto err').
>
> I've kept the existing error handling, as I trust it's there for
> a good reason (i.e., we may want to avoid calling
> print_page_owner_memcg() if we truncated before). Please review
> if this amount of error handling is the right one, or if we want
> to add or remove some. For seprintf(), a single test for null
> after the last call is enough to detect truncation.
>
> mm/slub.c:
>
> Again, the 'p += snprintf()' anti-pattern. This is UB, and by
> using seprintf() we've fixed the bug.
>
> Fixes: f99e12b21b84 (2021-07-30; "kfence: add function to mask address bits")
> [alx: that commit introduced dead code]
> Fixes: af649773fb25 (2024-07-17; "mm/numa_balancing: teach mpol_to_str about the balancing mode")
> [alx: that commit added p+=snprintf() calls, which are UB]
> Fixes: 2291990ab36b (2008-04-28; "mempolicy: clean-up mpol-to-str() mempolicy formatting")
> [alx: that commit changed p+=sprintf() into p+=snprintf(), which is still UB]
> Fixes: 948927ee9e4f (2013-11-13; "mm, mempolicy: make mpol_to_str robust and always succeed")
> [alx: that commit changes old code into p+=snprintf(), which is still UB]
> [alx: that commit also produced dead code by leaving the last 'p+=...']
> Fixes: d65360f22406 (2022-09-26; "mm/slub: clean up create_unique_id()")
> [alx: that commit changed p+=sprintf() into p+=snprintf(), which is still UB]
> Cc: Kees Cook <kees@kernel.org>
> Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
> Cc: Sven Schnelle <svens@linux.ibm.com>
> Cc: Marco Elver <elver@google.com>
> Cc: Heiko Carstens <hca@linux.ibm.com>
> Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
> Cc: "Huang, Ying" <ying.huang@intel.com>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Lee Schermerhorn <lee.schermerhorn@hp.com>
> Cc: Linus Torvalds <torvalds@linux-foundation.org>
> Cc: David Rientjes <rientjes@google.com>
> Cc: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
> Cc: Hyeonggon Yoo <42.hyeyoo@gmail.com>
> Cc: Chao Yu <chao.yu@oppo.com>
> Cc: Vlastimil Babka <vbabka@suse.cz>
> Signed-off-by: Alejandro Colomar <alx@kernel.org>
> ---
> mm/kfence/kfence_test.c | 24 ++++++++++++------------
> mm/kmsan/kmsan_test.c | 4 ++--
> mm/mempolicy.c | 18 +++++++++---------
> mm/page_owner.c | 32 +++++++++++++++++---------------
> mm/slub.c | 5 +++--
> 5 files changed, 43 insertions(+), 40 deletions(-)
>
> diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
> index 00034e37bc9f..ff734c514c03 100644
> --- a/mm/kfence/kfence_test.c
> +++ b/mm/kfence/kfence_test.c
> @@ -113,26 +113,26 @@ static bool report_matches(const struct expect_report *r)
> end = &expect[0][sizeof(expect[0]) - 1];
> switch (r->type) {
> case KFENCE_ERROR_OOB:
> - cur += scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds %s",
> + cur = seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s",
> get_access_type(r));
> break;
> case KFENCE_ERROR_UAF:
> - cur += scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free %s",
> + cur = seprintf(cur, end, "BUG: KFENCE: use-after-free %s",
> get_access_type(r));
> break;
> case KFENCE_ERROR_CORRUPTION:
> - cur += scnprintf(cur, end - cur, "BUG: KFENCE: memory corruption");
> + cur = seprintf(cur, end, "BUG: KFENCE: memory corruption");
> break;
> case KFENCE_ERROR_INVALID:
> - cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid %s",
> + cur = seprintf(cur, end, "BUG: KFENCE: invalid %s",
> get_access_type(r));
> break;
> case KFENCE_ERROR_INVALID_FREE:
> - cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid free");
> + cur = seprintf(cur, end, "BUG: KFENCE: invalid free");
> break;
> }
>
> - scnprintf(cur, end - cur, " in %pS", r->fn);
> + seprintf(cur, end, " in %pS", r->fn);
> /* The exact offset won't match, remove it; also strip module name. */
> cur = strchr(expect[0], '+');
> if (cur)
> @@ -144,26 +144,26 @@ static bool report_matches(const struct expect_report *r)
>
> switch (r->type) {
> case KFENCE_ERROR_OOB:
> - cur += scnprintf(cur, end - cur, "Out-of-bounds %s at", get_access_type(r));
> + cur = seprintf(cur, end, "Out-of-bounds %s at", get_access_type(r));
> addr = arch_kfence_test_address(addr);
> break;
> case KFENCE_ERROR_UAF:
> - cur += scnprintf(cur, end - cur, "Use-after-free %s at", get_access_type(r));
> + cur = seprintf(cur, end, "Use-after-free %s at", get_access_type(r));
> addr = arch_kfence_test_address(addr);
> break;
> case KFENCE_ERROR_CORRUPTION:
> - cur += scnprintf(cur, end - cur, "Corrupted memory at");
> + cur = seprintf(cur, end, "Corrupted memory at");
> break;
> case KFENCE_ERROR_INVALID:
> - cur += scnprintf(cur, end - cur, "Invalid %s at", get_access_type(r));
> + cur = seprintf(cur, end, "Invalid %s at", get_access_type(r));
> addr = arch_kfence_test_address(addr);
> break;
> case KFENCE_ERROR_INVALID_FREE:
> - cur += scnprintf(cur, end - cur, "Invalid free of");
> + cur = seprintf(cur, end, "Invalid free of");
> break;
> }
>
> - cur += scnprintf(cur, end - cur, " 0x%p", (void *)addr);
> + seprintf(cur, end, " 0x%p", (void *)addr);
>
> spin_lock_irqsave(&observed.lock, flags);
> if (!report_available())
> diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
> index 9733a22c46c1..a062a46b2d24 100644
> --- a/mm/kmsan/kmsan_test.c
> +++ b/mm/kmsan/kmsan_test.c
> @@ -107,9 +107,9 @@ static bool report_matches(const struct expect_report *r)
> cur = expected_header;
> end = &expected_header[sizeof(expected_header) - 1];
>
> - cur += scnprintf(cur, end - cur, "BUG: KMSAN: %s", r->error_type);
> + cur = seprintf(cur, end, "BUG: KMSAN: %s", r->error_type);
>
> - scnprintf(cur, end - cur, " in %s", r->symbol);
> + seprintf(cur, end, " in %s", r->symbol);
> /* The exact offset won't match, remove it; also strip module name. */
> cur = strchr(expected_header, '+');
> if (cur)
> diff --git a/mm/mempolicy.c b/mm/mempolicy.c
> index b28a1e6ae096..c696e4a6f4c2 100644
> --- a/mm/mempolicy.c
> +++ b/mm/mempolicy.c
> @@ -3359,6 +3359,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
> void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
> {
> char *p = buffer;
> + char *e = buffer + maxlen;
> nodemask_t nodes = NODE_MASK_NONE;
> unsigned short mode = MPOL_DEFAULT;
> unsigned short flags = 0;
> @@ -3384,33 +3385,32 @@ void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
> break;
> default:
> WARN_ON_ONCE(1);
> - snprintf(p, maxlen, "unknown");
> + seprintf(p, e, "unknown");
> return;
> }
>
> - p += snprintf(p, maxlen, "%s", policy_modes[mode]);
> + p = seprintf(p, e, "%s", policy_modes[mode]);
>
> if (flags & MPOL_MODE_FLAGS) {
> - p += snprintf(p, buffer + maxlen - p, "=");
> + p = seprintf(p, e, "=");
>
> /*
> * Static and relative are mutually exclusive.
> */
> if (flags & MPOL_F_STATIC_NODES)
> - p += snprintf(p, buffer + maxlen - p, "static");
> + p = seprintf(p, e, "static");
> else if (flags & MPOL_F_RELATIVE_NODES)
> - p += snprintf(p, buffer + maxlen - p, "relative");
> + p = seprintf(p, e, "relative");
>
> if (flags & MPOL_F_NUMA_BALANCING) {
> if (!is_power_of_2(flags & MPOL_MODE_FLAGS))
> - p += snprintf(p, buffer + maxlen - p, "|");
> - p += snprintf(p, buffer + maxlen - p, "balancing");
> + p = seprintf(p, e, "|");
> + p = seprintf(p, e, "balancing");
> }
> }
>
> if (!nodes_empty(nodes))
> - p += scnprintf(p, buffer + maxlen - p, ":%*pbl",
> - nodemask_pr_args(&nodes));
> + seprintf(p, e, ":%*pbl", nodemask_pr_args(&nodes));
> }
>
> #ifdef CONFIG_SYSFS
> diff --git a/mm/page_owner.c b/mm/page_owner.c
> index cc4a6916eec6..5811738e3320 100644
> --- a/mm/page_owner.c
> +++ b/mm/page_owner.c
> @@ -496,7 +496,7 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
> /*
> * Looking for memcg information and print it out
> */
> -static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
> +static inline char *print_page_owner_memcg(char *p, const char end[0],
> struct page *page)
> {
> #ifdef CONFIG_MEMCG
> @@ -511,8 +511,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
> goto out_unlock;
>
> if (memcg_data & MEMCG_DATA_OBJEXTS)
> - ret += scnprintf(kbuf + ret, count - ret,
> - "Slab cache page\n");
> + p = seprintf(p, end, "Slab cache page\n");
>
> memcg = page_memcg_check(page);
> if (!memcg)
> @@ -520,7 +519,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
>
> online = (memcg->css.flags & CSS_ONLINE);
> cgroup_name(memcg->css.cgroup, name, sizeof(name));
> - ret += scnprintf(kbuf + ret, count - ret,
> + p = seprintf(p, end,
> "Charged %sto %smemcg %s\n",
> PageMemcgKmem(page) ? "(via objcg) " : "",
> online ? "" : "offline ",
> @@ -529,7 +528,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
> rcu_read_unlock();
> #endif /* CONFIG_MEMCG */
>
> - return ret;
> + return p;
> }
>
> static ssize_t
> @@ -538,14 +537,16 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
> depot_stack_handle_t handle)
> {
> int ret, pageblock_mt, page_mt;
> - char *kbuf;
> + char *kbuf, *p, *e;
>
> count = min_t(size_t, count, PAGE_SIZE);
> kbuf = kmalloc(count, GFP_KERNEL);
> if (!kbuf)
> return -ENOMEM;
>
> - ret = scnprintf(kbuf, count,
> + p = kbuf;
> + e = kbuf + count;
> + p = seprintf(p, e,
> "Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n",
> page_owner->order, page_owner->gfp_mask,
> &page_owner->gfp_mask, page_owner->pid,
> @@ -555,7 +556,7 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
> /* Print information relevant to grouping pages by mobility */
> pageblock_mt = get_pageblock_migratetype(page);
> page_mt = gfp_migratetype(page_owner->gfp_mask);
> - ret += scnprintf(kbuf + ret, count - ret,
> + p = seprintf(p, e,
> "PFN 0x%lx type %s Block %lu type %s Flags %pGp\n",
> pfn,
> migratetype_names[page_mt],
> @@ -563,22 +564,23 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
> migratetype_names[pageblock_mt],
> &page->flags);
>
> - ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
> - if (ret >= count)
> - goto err;
> + p = stack_depot_seprint(handle, p, e, 0);
> + if (p == NULL)
> + goto err; // XXX: Should we remove this error handling?
>
> if (page_owner->last_migrate_reason != -1) {
> - ret += scnprintf(kbuf + ret, count - ret,
> + p = seprintf(p, e,
> "Page has been migrated, last migrate reason: %s\n",
> migrate_reason_names[page_owner->last_migrate_reason]);
> }
>
> - ret = print_page_owner_memcg(kbuf, count, ret, page);
> + p = print_page_owner_memcg(p, e, page);
>
> - ret += snprintf(kbuf + ret, count - ret, "\n");
> - if (ret >= count)
> + p = seprintf(p, e, "\n");
> + if (p == NULL)
> goto err;
>
> + ret = p - kbuf;
> if (copy_to_user(buf, kbuf, ret))
> ret = -EFAULT;
>
> diff --git a/mm/slub.c b/mm/slub.c
> index be8b09e09d30..b67c6ca0d0f7 100644
> --- a/mm/slub.c
> +++ b/mm/slub.c
> @@ -7451,6 +7451,7 @@ static char *create_unique_id(struct kmem_cache *s)
> {
> char *name = kmalloc(ID_STR_LENGTH, GFP_KERNEL);
> char *p = name;
> + char *e = name + ID_STR_LENGTH;
>
> if (!name)
> return ERR_PTR(-ENOMEM);
> @@ -7475,9 +7476,9 @@ static char *create_unique_id(struct kmem_cache *s)
> *p++ = 'A';
> if (p != name + 1)
> *p++ = '-';
> - p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
> + p = seprintf(p, e, "%07u", s->size);
>
> - if (WARN_ON(p > name + ID_STR_LENGTH - 1)) {
> + if (WARN_ON(p == NULL)) {
> kfree(name);
> return ERR_PTR(-EINVAL);
> }
> --
> 2.50.0
>
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 5/7] mm: Fix benign off-by-one bugs
2025-07-07 5:06 ` [RFC v3 5/7] mm: Fix benign off-by-one bugs Alejandro Colomar
@ 2025-07-07 7:46 ` Marco Elver
2025-07-07 7:53 ` Michal Hocko
0 siblings, 1 reply; 98+ messages in thread
From: Marco Elver @ 2025-07-07 7:46 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Christoph Lameter, David Rientjes,
Vlastimil Babka, Roman Gushchin, Harry Yoo, Andrew Clayton,
Jann Horn, Linus Torvalds
On Mon, 7 Jul 2025 at 07:06, Alejandro Colomar <alx@kernel.org> wrote:
>
> We were wasting a byte due to an off-by-one bug. s[c]nprintf()
> doesn't write more than $2 bytes including the null byte, so trying to
> pass 'size-1' there is wasting one byte. Now that we use seprintf(),
> the situation isn't different: seprintf() will stop writing *before*
> 'end' --that is, at most the terminating null byte will be written at
> 'end-1'--.
>
> Fixes: bc8fbc5f305a (2021-02-26; "kfence: add test suite")
> Fixes: 8ed691b02ade (2022-10-03; "kmsan: add tests for KMSAN")
Not sure about the Fixes - this means it's likely going to be
backported to stable kernels, which is not appropriate. There's no
functional problem, and these are tests only, so not worth the churn.
Did you run the tests?
Otherwise:
Acked-by: Marco Elver <elver@google.com>
> Cc: Kees Cook <kees@kernel.org>
> Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
> Cc: Alexander Potapenko <glider@google.com>
> Cc: Marco Elver <elver@google.com>
> Cc: Dmitry Vyukov <dvyukov@google.com>
> Cc: Alexander Potapenko <glider@google.com>
> Cc: Jann Horn <jannh@google.com>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Linus Torvalds <torvalds@linux-foundation.org>
> Signed-off-by: Alejandro Colomar <alx@kernel.org>
> ---
> mm/kfence/kfence_test.c | 4 ++--
> mm/kmsan/kmsan_test.c | 2 +-
> 2 files changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
> index ff734c514c03..f02c3e23638a 100644
> --- a/mm/kfence/kfence_test.c
> +++ b/mm/kfence/kfence_test.c
> @@ -110,7 +110,7 @@ static bool report_matches(const struct expect_report *r)
>
> /* Title */
> cur = expect[0];
> - end = &expect[0][sizeof(expect[0]) - 1];
> + end = ENDOF(expect[0]);
> switch (r->type) {
> case KFENCE_ERROR_OOB:
> cur = seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s",
> @@ -140,7 +140,7 @@ static bool report_matches(const struct expect_report *r)
>
> /* Access information */
> cur = expect[1];
> - end = &expect[1][sizeof(expect[1]) - 1];
> + end = ENDOF(expect[1]);
>
> switch (r->type) {
> case KFENCE_ERROR_OOB:
> diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
> index a062a46b2d24..882500807db8 100644
> --- a/mm/kmsan/kmsan_test.c
> +++ b/mm/kmsan/kmsan_test.c
> @@ -105,7 +105,7 @@ static bool report_matches(const struct expect_report *r)
>
> /* Title */
> cur = expected_header;
> - end = &expected_header[sizeof(expected_header) - 1];
> + end = ENDOF(expected_header);
>
> cur = seprintf(cur, end, "BUG: KMSAN: %s", r->error_type);
>
> --
> 2.50.0
>
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 5/7] mm: Fix benign off-by-one bugs
2025-07-07 7:46 ` Marco Elver
@ 2025-07-07 7:53 ` Michal Hocko
2025-07-07 14:42 ` Alejandro Colomar
0 siblings, 1 reply; 98+ messages in thread
From: Michal Hocko @ 2025-07-07 7:53 UTC (permalink / raw)
To: Marco Elver
Cc: Alejandro Colomar, linux-mm, linux-hardening, Kees Cook,
Christopher Bazley, shadow, linux-kernel, Andrew Morton,
kasan-dev, Dmitry Vyukov, Alexander Potapenko, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Jann Horn, Linus Torvalds
On Mon 07-07-25 09:46:12, Marco Elver wrote:
> On Mon, 7 Jul 2025 at 07:06, Alejandro Colomar <alx@kernel.org> wrote:
> >
> > We were wasting a byte due to an off-by-one bug. s[c]nprintf()
> > doesn't write more than $2 bytes including the null byte, so trying to
> > pass 'size-1' there is wasting one byte. Now that we use seprintf(),
> > the situation isn't different: seprintf() will stop writing *before*
> > 'end' --that is, at most the terminating null byte will be written at
> > 'end-1'--.
> >
> > Fixes: bc8fbc5f305a (2021-02-26; "kfence: add test suite")
> > Fixes: 8ed691b02ade (2022-10-03; "kmsan: add tests for KMSAN")
>
> Not sure about the Fixes - this means it's likely going to be
> backported to stable kernels, which is not appropriate. There's no
> functional problem, and these are tests only, so not worth the churn.
As long as there is no actual bug fixed then I believe those Fixes tags
are more confusing than actually helpful. And that applies to other
patches in this series as well.
--
Michal Hocko
SUSE Labs
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v1 1/3] vsprintf: Add [v]seprintf(), [v]stprintf()
2025-07-05 20:33 ` [RFC v1 1/3] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
2025-07-05 20:40 ` Alejandro Colomar
@ 2025-07-07 9:47 ` Alexander Potapenko
2025-07-07 14:59 ` Alejandro Colomar
1 sibling, 1 reply; 98+ messages in thread
From: Alexander Potapenko @ 2025-07-07 9:47 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo
On Sat, Jul 5, 2025 at 10:33 PM Alejandro Colomar <alx@kernel.org> wrote:
>
> seprintf()
> ==========
>
> seprintf() is a function similar to stpcpy(3) in the sense that it
> returns a pointer that is suitable for chaining to other copy
> operations.
>
> It takes a pointer to the end of the buffer as a sentinel for when to
> truncate, which unlike a size, doesn't need to be updated after every
> call. This makes it much more ergonomic, avoiding manually calculating
> the size after each copy, which is error prone.
>
> It also makes error handling much easier, by reporting truncation with
> a null pointer, which is accepted and transparently passed down by
> subsequent seprintf() calls. This results in only needing to report
> errors once after a chain of seprintf() calls, unlike snprintf(3), which
> requires checking after every call.
>
> p = buf;
> e = buf + countof(buf);
> p = seprintf(p, e, foo);
> p = seprintf(p, e, bar);
> if (p == NULL)
> goto trunc;
>
> vs
>
> len = 0;
> size = countof(buf);
> len += snprintf(buf + len, size - len, foo);
> if (len >= size)
> goto trunc;
>
> len += snprintf(buf + len, size - len, bar);
> if (len >= size)
> goto trunc;
>
> And also better than scnprintf() calls:
>
> len = 0;
> size = countof(buf);
> len += scnprintf(buf + len, size - len, foo);
> len += scnprintf(buf + len, size - len, bar);
> if (len >= size)
> goto trunc;
>
> It seems aparent that it's a more elegant approach to string catenation.
>
> stprintf()
> ==========
>
> stprintf() is a helper that is needed for implementing seprintf()
> --although it could be open-coded within vseprintf(), of course--, but
> it's also useful by itself. It has the same interface properties as
> strscpy(): that is, it copies with truncation, and reports truncation
> with -E2BIG. It would be useful to replace some calls to snprintf(3)
> and scnprintf() which don't need chaining, and where it's simpler to
> pass a size.
>
> It is better than plain snprintf(3), because it results in simpler error
> detection (it doesn't need a check >=countof(buf), but rather <0).
>
> Cc: Kees Cook <kees@kernel.org>
> Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
> Signed-off-by: Alejandro Colomar <alx@kernel.org>
> ---
> lib/vsprintf.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 109 insertions(+)
>
> diff --git a/lib/vsprintf.c b/lib/vsprintf.c
> index 01699852f30c..a3efacadb5e5 100644
> --- a/lib/vsprintf.c
> +++ b/lib/vsprintf.c
> @@ -2892,6 +2892,37 @@ int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
> }
> EXPORT_SYMBOL(vsnprintf);
>
> +/**
> + * vstprintf - Format a string and place it in a buffer
> + * @buf: The buffer to place the result into
> + * @size: The size of the buffer, including the trailing null space
> + * @fmt: The format string to use
> + * @args: Arguments for the format string
> + *
> + * The return value is the length of the new string.
> + * If the string is truncated, the function returns -E2BIG.
> + *
> + * If you're not already dealing with a va_list consider using stprintf().
> + *
> + * See the vsnprintf() documentation for format string extensions over C99.
> + */
> +int vstprintf(char *buf, size_t size, const char *fmt, va_list args)
> +{
> + int len;
> +
> + len = vsnprintf(buf, size, fmt, args);
> +
> + // It seems the kernel's vsnprintf() doesn't fail?
> + //if (unlikely(len < 0))
> + // return -E2BIG;
> +
> + if (unlikely(len >= size))
> + return -E2BIG;
> +
> + return len;
> +}
> +EXPORT_SYMBOL(vstprintf);
> +
> /**
> * vscnprintf - Format a string and place it in a buffer
> * @buf: The buffer to place the result into
> @@ -2923,6 +2954,36 @@ int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
> }
> EXPORT_SYMBOL(vscnprintf);
>
> +/**
> + * vseprintf - Format a string and place it in a buffer
> + * @p: The buffer to place the result into
> + * @end: A pointer to one past the last character in the buffer
> + * @fmt: The format string to use
> + * @args: Arguments for the format string
> + *
> + * The return value is a pointer to the trailing '\0'.
> + * If @p is NULL, the function returns NULL.
> + * If the string is truncated, the function returns NULL.
> + *
> + * If you're not already dealing with a va_list consider using seprintf().
> + *
> + * See the vsnprintf() documentation for format string extensions over C99.
> + */
> +char *vseprintf(char *p, const char end[0], const char *fmt, va_list args)
> +{
> + int len;
> +
> + if (unlikely(p == NULL))
> + return NULL;
> +
> + len = vstprintf(p, end - p, fmt, args);
It's easy to imagine a situation in which `end` is calculated from the
user input and may overflow.
Maybe we can add a check for `end > p` to be on the safe side?
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 7:44 ` Marco Elver
@ 2025-07-07 14:39 ` Alejandro Colomar
2025-07-07 14:58 ` Marco Elver
0 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 14:39 UTC (permalink / raw)
To: Marco Elver
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Christoph Lameter, David Rientjes,
Vlastimil Babka, Roman Gushchin, Harry Yoo, Andrew Clayton,
Sven Schnelle, Heiko Carstens, Tvrtko Ursulin, Huang, Ying,
Lee Schermerhorn, Linus Torvalds, Christophe JAILLET,
Hyeonggon Yoo, Chao Yu
[-- Attachment #1: Type: text/plain, Size: 17933 bytes --]
Hi Marco,
On Mon, Jul 07, 2025 at 09:44:09AM +0200, Marco Elver wrote:
> On Mon, 7 Jul 2025 at 07:06, Alejandro Colomar <alx@kernel.org> wrote:
> >
> > While doing this, I detected some anomalies in the existing code:
> >
> > mm/kfence/kfence_test.c:
> >
> > - The last call to scnprintf() did increment 'cur', but it's
> > unused after that, so it was dead code. I've removed the dead
> > code in this patch.
>
> That was done to be consistent with the other code for readability,
> and to be clear where the next bytes should be appended (if someone
> decides to append more). There is no runtime dead code, the compiler
> optimizes away the assignment. But I'm indifferent, so removing the
> assignment is fine if you prefer that.
Yeah, I guessed that might be the reason. I'm fine restoring it if you
prefer it. I tend to use -Wunused-but-set-variable, but if it is not
used here and doesn't trigger, I guess it's fine to keep it.
> Did you run the tests? Do they pass?
I don't know how to run them. I've only built the kernel. If you point
me to instructions on how to run them, I'll do so. Thanks!
> > - 'end' is calculated as
> >
> > end = &expect[0][sizeof(expect[0] - 1)];
> >
> > However, the '-1' doesn't seem to be necessary. When passing
> > $2 to scnprintf(), the size was specified as 'end - cur'.
> > And scnprintf() --just like snprintf(3)--, won't write more
> > than $2 bytes (including the null byte). That means that
> > scnprintf() wouldn't write more than
> >
> > &expect[0][sizeof(expect[0]) - 1] - expect[0]
> >
> > which simplifies to
> >
> > sizeof(expect[0]) - 1
> >
> > bytes. But we have sizeof(expect[0]) bytes available, so
> > we're wasting one byte entirely. This is a benign off-by-one
> > bug. The two occurrences of this bug will be fixed in a
> > following patch in this series.
> >
> > mm/kmsan/kmsan_test.c:
> >
> > The same benign off-by-one bug calculating the remaining size.
>
>
> Same - does the test pass?
Same; built the kernel, but didn't know how to run tests.
Have a lovely day!
Alex
> > mm/mempolicy.c:
> >
> > This file uses the 'p += snprintf()' anti-pattern. That will
> > overflow the pointer on truncation, which has undefined
> > behavior. Using seprintf(), this bug is fixed.
> >
> > As in the previous file, here there was also dead code in the
> > last scnprintf() call, by incrementing a pointer that is not
> > used after the call. I've removed the dead code.
> >
> > mm/page_owner.c:
> >
> > Within print_page_owner(), there are some calls to scnprintf(),
> > which do report truncation. And then there are other calls to
> > snprintf(), where we handle errors (there are two 'goto err').
> >
> > I've kept the existing error handling, as I trust it's there for
> > a good reason (i.e., we may want to avoid calling
> > print_page_owner_memcg() if we truncated before). Please review
> > if this amount of error handling is the right one, or if we want
> > to add or remove some. For seprintf(), a single test for null
> > after the last call is enough to detect truncation.
> >
> > mm/slub.c:
> >
> > Again, the 'p += snprintf()' anti-pattern. This is UB, and by
> > using seprintf() we've fixed the bug.
> >
> > Fixes: f99e12b21b84 (2021-07-30; "kfence: add function to mask address bits")
> > [alx: that commit introduced dead code]
> > Fixes: af649773fb25 (2024-07-17; "mm/numa_balancing: teach mpol_to_str about the balancing mode")
> > [alx: that commit added p+=snprintf() calls, which are UB]
> > Fixes: 2291990ab36b (2008-04-28; "mempolicy: clean-up mpol-to-str() mempolicy formatting")
> > [alx: that commit changed p+=sprintf() into p+=snprintf(), which is still UB]
> > Fixes: 948927ee9e4f (2013-11-13; "mm, mempolicy: make mpol_to_str robust and always succeed")
> > [alx: that commit changes old code into p+=snprintf(), which is still UB]
> > [alx: that commit also produced dead code by leaving the last 'p+=...']
> > Fixes: d65360f22406 (2022-09-26; "mm/slub: clean up create_unique_id()")
> > [alx: that commit changed p+=sprintf() into p+=snprintf(), which is still UB]
> > Cc: Kees Cook <kees@kernel.org>
> > Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
> > Cc: Sven Schnelle <svens@linux.ibm.com>
> > Cc: Marco Elver <elver@google.com>
> > Cc: Heiko Carstens <hca@linux.ibm.com>
> > Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
> > Cc: "Huang, Ying" <ying.huang@intel.com>
> > Cc: Andrew Morton <akpm@linux-foundation.org>
> > Cc: Lee Schermerhorn <lee.schermerhorn@hp.com>
> > Cc: Linus Torvalds <torvalds@linux-foundation.org>
> > Cc: David Rientjes <rientjes@google.com>
> > Cc: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
> > Cc: Hyeonggon Yoo <42.hyeyoo@gmail.com>
> > Cc: Chao Yu <chao.yu@oppo.com>
> > Cc: Vlastimil Babka <vbabka@suse.cz>
> > Signed-off-by: Alejandro Colomar <alx@kernel.org>
> > ---
> > mm/kfence/kfence_test.c | 24 ++++++++++++------------
> > mm/kmsan/kmsan_test.c | 4 ++--
> > mm/mempolicy.c | 18 +++++++++---------
> > mm/page_owner.c | 32 +++++++++++++++++---------------
> > mm/slub.c | 5 +++--
> > 5 files changed, 43 insertions(+), 40 deletions(-)
> >
> > diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
> > index 00034e37bc9f..ff734c514c03 100644
> > --- a/mm/kfence/kfence_test.c
> > +++ b/mm/kfence/kfence_test.c
> > @@ -113,26 +113,26 @@ static bool report_matches(const struct expect_report *r)
> > end = &expect[0][sizeof(expect[0]) - 1];
> > switch (r->type) {
> > case KFENCE_ERROR_OOB:
> > - cur += scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds %s",
> > + cur = seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s",
> > get_access_type(r));
> > break;
> > case KFENCE_ERROR_UAF:
> > - cur += scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free %s",
> > + cur = seprintf(cur, end, "BUG: KFENCE: use-after-free %s",
> > get_access_type(r));
> > break;
> > case KFENCE_ERROR_CORRUPTION:
> > - cur += scnprintf(cur, end - cur, "BUG: KFENCE: memory corruption");
> > + cur = seprintf(cur, end, "BUG: KFENCE: memory corruption");
> > break;
> > case KFENCE_ERROR_INVALID:
> > - cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid %s",
> > + cur = seprintf(cur, end, "BUG: KFENCE: invalid %s",
> > get_access_type(r));
> > break;
> > case KFENCE_ERROR_INVALID_FREE:
> > - cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid free");
> > + cur = seprintf(cur, end, "BUG: KFENCE: invalid free");
> > break;
> > }
> >
> > - scnprintf(cur, end - cur, " in %pS", r->fn);
> > + seprintf(cur, end, " in %pS", r->fn);
> > /* The exact offset won't match, remove it; also strip module name. */
> > cur = strchr(expect[0], '+');
> > if (cur)
> > @@ -144,26 +144,26 @@ static bool report_matches(const struct expect_report *r)
> >
> > switch (r->type) {
> > case KFENCE_ERROR_OOB:
> > - cur += scnprintf(cur, end - cur, "Out-of-bounds %s at", get_access_type(r));
> > + cur = seprintf(cur, end, "Out-of-bounds %s at", get_access_type(r));
> > addr = arch_kfence_test_address(addr);
> > break;
> > case KFENCE_ERROR_UAF:
> > - cur += scnprintf(cur, end - cur, "Use-after-free %s at", get_access_type(r));
> > + cur = seprintf(cur, end, "Use-after-free %s at", get_access_type(r));
> > addr = arch_kfence_test_address(addr);
> > break;
> > case KFENCE_ERROR_CORRUPTION:
> > - cur += scnprintf(cur, end - cur, "Corrupted memory at");
> > + cur = seprintf(cur, end, "Corrupted memory at");
> > break;
> > case KFENCE_ERROR_INVALID:
> > - cur += scnprintf(cur, end - cur, "Invalid %s at", get_access_type(r));
> > + cur = seprintf(cur, end, "Invalid %s at", get_access_type(r));
> > addr = arch_kfence_test_address(addr);
> > break;
> > case KFENCE_ERROR_INVALID_FREE:
> > - cur += scnprintf(cur, end - cur, "Invalid free of");
> > + cur = seprintf(cur, end, "Invalid free of");
> > break;
> > }
> >
> > - cur += scnprintf(cur, end - cur, " 0x%p", (void *)addr);
> > + seprintf(cur, end, " 0x%p", (void *)addr);
> >
> > spin_lock_irqsave(&observed.lock, flags);
> > if (!report_available())
> > diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
> > index 9733a22c46c1..a062a46b2d24 100644
> > --- a/mm/kmsan/kmsan_test.c
> > +++ b/mm/kmsan/kmsan_test.c
> > @@ -107,9 +107,9 @@ static bool report_matches(const struct expect_report *r)
> > cur = expected_header;
> > end = &expected_header[sizeof(expected_header) - 1];
> >
> > - cur += scnprintf(cur, end - cur, "BUG: KMSAN: %s", r->error_type);
> > + cur = seprintf(cur, end, "BUG: KMSAN: %s", r->error_type);
> >
> > - scnprintf(cur, end - cur, " in %s", r->symbol);
> > + seprintf(cur, end, " in %s", r->symbol);
> > /* The exact offset won't match, remove it; also strip module name. */
> > cur = strchr(expected_header, '+');
> > if (cur)
> > diff --git a/mm/mempolicy.c b/mm/mempolicy.c
> > index b28a1e6ae096..c696e4a6f4c2 100644
> > --- a/mm/mempolicy.c
> > +++ b/mm/mempolicy.c
> > @@ -3359,6 +3359,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
> > void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
> > {
> > char *p = buffer;
> > + char *e = buffer + maxlen;
> > nodemask_t nodes = NODE_MASK_NONE;
> > unsigned short mode = MPOL_DEFAULT;
> > unsigned short flags = 0;
> > @@ -3384,33 +3385,32 @@ void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
> > break;
> > default:
> > WARN_ON_ONCE(1);
> > - snprintf(p, maxlen, "unknown");
> > + seprintf(p, e, "unknown");
> > return;
> > }
> >
> > - p += snprintf(p, maxlen, "%s", policy_modes[mode]);
> > + p = seprintf(p, e, "%s", policy_modes[mode]);
> >
> > if (flags & MPOL_MODE_FLAGS) {
> > - p += snprintf(p, buffer + maxlen - p, "=");
> > + p = seprintf(p, e, "=");
> >
> > /*
> > * Static and relative are mutually exclusive.
> > */
> > if (flags & MPOL_F_STATIC_NODES)
> > - p += snprintf(p, buffer + maxlen - p, "static");
> > + p = seprintf(p, e, "static");
> > else if (flags & MPOL_F_RELATIVE_NODES)
> > - p += snprintf(p, buffer + maxlen - p, "relative");
> > + p = seprintf(p, e, "relative");
> >
> > if (flags & MPOL_F_NUMA_BALANCING) {
> > if (!is_power_of_2(flags & MPOL_MODE_FLAGS))
> > - p += snprintf(p, buffer + maxlen - p, "|");
> > - p += snprintf(p, buffer + maxlen - p, "balancing");
> > + p = seprintf(p, e, "|");
> > + p = seprintf(p, e, "balancing");
> > }
> > }
> >
> > if (!nodes_empty(nodes))
> > - p += scnprintf(p, buffer + maxlen - p, ":%*pbl",
> > - nodemask_pr_args(&nodes));
> > + seprintf(p, e, ":%*pbl", nodemask_pr_args(&nodes));
> > }
> >
> > #ifdef CONFIG_SYSFS
> > diff --git a/mm/page_owner.c b/mm/page_owner.c
> > index cc4a6916eec6..5811738e3320 100644
> > --- a/mm/page_owner.c
> > +++ b/mm/page_owner.c
> > @@ -496,7 +496,7 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
> > /*
> > * Looking for memcg information and print it out
> > */
> > -static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
> > +static inline char *print_page_owner_memcg(char *p, const char end[0],
> > struct page *page)
> > {
> > #ifdef CONFIG_MEMCG
> > @@ -511,8 +511,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
> > goto out_unlock;
> >
> > if (memcg_data & MEMCG_DATA_OBJEXTS)
> > - ret += scnprintf(kbuf + ret, count - ret,
> > - "Slab cache page\n");
> > + p = seprintf(p, end, "Slab cache page\n");
> >
> > memcg = page_memcg_check(page);
> > if (!memcg)
> > @@ -520,7 +519,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
> >
> > online = (memcg->css.flags & CSS_ONLINE);
> > cgroup_name(memcg->css.cgroup, name, sizeof(name));
> > - ret += scnprintf(kbuf + ret, count - ret,
> > + p = seprintf(p, end,
> > "Charged %sto %smemcg %s\n",
> > PageMemcgKmem(page) ? "(via objcg) " : "",
> > online ? "" : "offline ",
> > @@ -529,7 +528,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
> > rcu_read_unlock();
> > #endif /* CONFIG_MEMCG */
> >
> > - return ret;
> > + return p;
> > }
> >
> > static ssize_t
> > @@ -538,14 +537,16 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
> > depot_stack_handle_t handle)
> > {
> > int ret, pageblock_mt, page_mt;
> > - char *kbuf;
> > + char *kbuf, *p, *e;
> >
> > count = min_t(size_t, count, PAGE_SIZE);
> > kbuf = kmalloc(count, GFP_KERNEL);
> > if (!kbuf)
> > return -ENOMEM;
> >
> > - ret = scnprintf(kbuf, count,
> > + p = kbuf;
> > + e = kbuf + count;
> > + p = seprintf(p, e,
> > "Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n",
> > page_owner->order, page_owner->gfp_mask,
> > &page_owner->gfp_mask, page_owner->pid,
> > @@ -555,7 +556,7 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
> > /* Print information relevant to grouping pages by mobility */
> > pageblock_mt = get_pageblock_migratetype(page);
> > page_mt = gfp_migratetype(page_owner->gfp_mask);
> > - ret += scnprintf(kbuf + ret, count - ret,
> > + p = seprintf(p, e,
> > "PFN 0x%lx type %s Block %lu type %s Flags %pGp\n",
> > pfn,
> > migratetype_names[page_mt],
> > @@ -563,22 +564,23 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
> > migratetype_names[pageblock_mt],
> > &page->flags);
> >
> > - ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
> > - if (ret >= count)
> > - goto err;
> > + p = stack_depot_seprint(handle, p, e, 0);
> > + if (p == NULL)
> > + goto err; // XXX: Should we remove this error handling?
> >
> > if (page_owner->last_migrate_reason != -1) {
> > - ret += scnprintf(kbuf + ret, count - ret,
> > + p = seprintf(p, e,
> > "Page has been migrated, last migrate reason: %s\n",
> > migrate_reason_names[page_owner->last_migrate_reason]);
> > }
> >
> > - ret = print_page_owner_memcg(kbuf, count, ret, page);
> > + p = print_page_owner_memcg(p, e, page);
> >
> > - ret += snprintf(kbuf + ret, count - ret, "\n");
> > - if (ret >= count)
> > + p = seprintf(p, e, "\n");
> > + if (p == NULL)
> > goto err;
> >
> > + ret = p - kbuf;
> > if (copy_to_user(buf, kbuf, ret))
> > ret = -EFAULT;
> >
> > diff --git a/mm/slub.c b/mm/slub.c
> > index be8b09e09d30..b67c6ca0d0f7 100644
> > --- a/mm/slub.c
> > +++ b/mm/slub.c
> > @@ -7451,6 +7451,7 @@ static char *create_unique_id(struct kmem_cache *s)
> > {
> > char *name = kmalloc(ID_STR_LENGTH, GFP_KERNEL);
> > char *p = name;
> > + char *e = name + ID_STR_LENGTH;
> >
> > if (!name)
> > return ERR_PTR(-ENOMEM);
> > @@ -7475,9 +7476,9 @@ static char *create_unique_id(struct kmem_cache *s)
> > *p++ = 'A';
> > if (p != name + 1)
> > *p++ = '-';
> > - p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
> > + p = seprintf(p, e, "%07u", s->size);
> >
> > - if (WARN_ON(p > name + ID_STR_LENGTH - 1)) {
> > + if (WARN_ON(p == NULL)) {
> > kfree(name);
> > return ERR_PTR(-EINVAL);
> > }
> > --
> > 2.50.0
> >
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 5/7] mm: Fix benign off-by-one bugs
2025-07-07 7:53 ` Michal Hocko
@ 2025-07-07 14:42 ` Alejandro Colomar
2025-07-07 15:12 ` Michal Hocko
0 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 14:42 UTC (permalink / raw)
To: Michal Hocko
Cc: Marco Elver, linux-mm, linux-hardening, Kees Cook,
Christopher Bazley, shadow, linux-kernel, Andrew Morton,
kasan-dev, Dmitry Vyukov, Alexander Potapenko, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Jann Horn, Linus Torvalds
[-- Attachment #1: Type: text/plain, Size: 1591 bytes --]
Hi Michal,
On Mon, Jul 07, 2025 at 09:53:31AM +0200, Michal Hocko wrote:
> On Mon 07-07-25 09:46:12, Marco Elver wrote:
> > On Mon, 7 Jul 2025 at 07:06, Alejandro Colomar <alx@kernel.org> wrote:
> > >
> > > We were wasting a byte due to an off-by-one bug. s[c]nprintf()
> > > doesn't write more than $2 bytes including the null byte, so trying to
> > > pass 'size-1' there is wasting one byte. Now that we use seprintf(),
> > > the situation isn't different: seprintf() will stop writing *before*
> > > 'end' --that is, at most the terminating null byte will be written at
> > > 'end-1'--.
> > >
> > > Fixes: bc8fbc5f305a (2021-02-26; "kfence: add test suite")
> > > Fixes: 8ed691b02ade (2022-10-03; "kmsan: add tests for KMSAN")
> >
> > Not sure about the Fixes - this means it's likely going to be
> > backported to stable kernels, which is not appropriate. There's no
> > functional problem, and these are tests only, so not worth the churn.
>
> As long as there is no actual bug fixed then I believe those Fixes tags
> are more confusing than actually helpful. And that applies to other
> patches in this series as well.
For the dead code, I can remove the fixes tags, and even the changes
themselves, since there are good reasons to keep the dead code
(consistency, and avoiding a future programmer forgetting to add it back
when adding a subsequent seprintf() call).
For the fixes to UB, do you prefer the Fixes tags to be removed too?
Have a lovely day!
Alex
> --
> Michal Hocko
> SUSE Labs
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 14:39 ` Alejandro Colomar
@ 2025-07-07 14:58 ` Marco Elver
2025-07-07 18:51 ` Alejandro Colomar
0 siblings, 1 reply; 98+ messages in thread
From: Marco Elver @ 2025-07-07 14:58 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Christoph Lameter, David Rientjes,
Vlastimil Babka, Roman Gushchin, Harry Yoo, Andrew Clayton,
Sven Schnelle, Heiko Carstens, Tvrtko Ursulin, Huang, Ying,
Lee Schermerhorn, Linus Torvalds, Christophe JAILLET,
Hyeonggon Yoo, Chao Yu
On Mon, 7 Jul 2025 at 16:39, Alejandro Colomar <alx@kernel.org> wrote:
>
> Hi Marco,
>
> On Mon, Jul 07, 2025 at 09:44:09AM +0200, Marco Elver wrote:
> > On Mon, 7 Jul 2025 at 07:06, Alejandro Colomar <alx@kernel.org> wrote:
> > >
> > > While doing this, I detected some anomalies in the existing code:
> > >
> > > mm/kfence/kfence_test.c:
> > >
> > > - The last call to scnprintf() did increment 'cur', but it's
> > > unused after that, so it was dead code. I've removed the dead
> > > code in this patch.
> >
> > That was done to be consistent with the other code for readability,
> > and to be clear where the next bytes should be appended (if someone
> > decides to append more). There is no runtime dead code, the compiler
> > optimizes away the assignment. But I'm indifferent, so removing the
> > assignment is fine if you prefer that.
>
> Yeah, I guessed that might be the reason. I'm fine restoring it if you
> prefer it. I tend to use -Wunused-but-set-variable, but if it is not
> used here and doesn't trigger, I guess it's fine to keep it.
Feel free to make it warning-free, I guess that's useful.
> > Did you run the tests? Do they pass?
>
> I don't know how to run them. I've only built the kernel. If you point
> me to instructions on how to run them, I'll do so. Thanks!
Should just be CONFIG_KFENCE_KUNIT_TEST=y -- then boot kernel and
check that the test reports "ok".
Thanks,
-- marco
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v1 1/3] vsprintf: Add [v]seprintf(), [v]stprintf()
2025-07-07 9:47 ` Alexander Potapenko
@ 2025-07-07 14:59 ` Alejandro Colomar
0 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 14:59 UTC (permalink / raw)
To: Alexander Potapenko
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo
[-- Attachment #1: Type: text/plain, Size: 2416 bytes --]
Hi Alexander,
On Mon, Jul 07, 2025 at 11:47:43AM +0200, Alexander Potapenko wrote:
> > +/**
> > + * vseprintf - Format a string and place it in a buffer
> > + * @p: The buffer to place the result into
> > + * @end: A pointer to one past the last character in the buffer
> > + * @fmt: The format string to use
> > + * @args: Arguments for the format string
> > + *
> > + * The return value is a pointer to the trailing '\0'.
> > + * If @p is NULL, the function returns NULL.
> > + * If the string is truncated, the function returns NULL.
> > + *
> > + * If you're not already dealing with a va_list consider using seprintf().
> > + *
> > + * See the vsnprintf() documentation for format string extensions over C99.
> > + */
> > +char *vseprintf(char *p, const char end[0], const char *fmt, va_list args)
> > +{
> > + int len;
> > +
> > + if (unlikely(p == NULL))
> > + return NULL;
> > +
> > + len = vstprintf(p, end - p, fmt, args);
>
> It's easy to imagine a situation in which `end` is calculated from the
> user input and may overflow.
> Maybe we can add a check for `end > p` to be on the safe side?
That would technically be already UB at the moment you hold the 'end'
pointer, so the verification should in theory happen much earlier.
However, if we've arrived here with an overflown 'end', the safety is in
vsnprintf(), which has
/* Reject out-of-range values early. Large positive sizes are
used for unknown buffer sizes. */
if (WARN_ON_ONCE(size > INT_MAX))
return 0;
The sequence is:
- vseprintf() calls vstprintf() where end-p => size.
- vstprintf() calls vsnprintf() with size.
- vsnprintf() would return 0, and the contents of the string are
undefined, as we haven't written anything. It's not even truncated.
Which, indeed, doesn't sound like a safety. We've reported a successful
copy of 0 bytes, but we actually failed.
Which BTW is a reminder that this implementation of vsnprintf() seems
dangerous to me, and not conforming to the standard vsnprintf(3).
Maybe we should do the check in vstprintf() and report an error as
-E2BIG (which is later translated into NULL by vseprintf()). This is
what sized_strscpy() does, so sounds reasonable. I'll add this test.
Thanks!
Have a lovely day!
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 5/7] mm: Fix benign off-by-one bugs
2025-07-07 14:42 ` Alejandro Colomar
@ 2025-07-07 15:12 ` Michal Hocko
2025-07-07 15:29 ` Alejandro Colomar
0 siblings, 1 reply; 98+ messages in thread
From: Michal Hocko @ 2025-07-07 15:12 UTC (permalink / raw)
To: Alejandro Colomar
Cc: Marco Elver, linux-mm, linux-hardening, Kees Cook,
Christopher Bazley, shadow, linux-kernel, Andrew Morton,
kasan-dev, Dmitry Vyukov, Alexander Potapenko, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Jann Horn, Linus Torvalds
On Mon 07-07-25 16:42:43, Alejandro Colomar wrote:
> Hi Michal,
>
> On Mon, Jul 07, 2025 at 09:53:31AM +0200, Michal Hocko wrote:
> > On Mon 07-07-25 09:46:12, Marco Elver wrote:
> > > On Mon, 7 Jul 2025 at 07:06, Alejandro Colomar <alx@kernel.org> wrote:
> > > >
> > > > We were wasting a byte due to an off-by-one bug. s[c]nprintf()
> > > > doesn't write more than $2 bytes including the null byte, so trying to
> > > > pass 'size-1' there is wasting one byte. Now that we use seprintf(),
> > > > the situation isn't different: seprintf() will stop writing *before*
> > > > 'end' --that is, at most the terminating null byte will be written at
> > > > 'end-1'--.
> > > >
> > > > Fixes: bc8fbc5f305a (2021-02-26; "kfence: add test suite")
> > > > Fixes: 8ed691b02ade (2022-10-03; "kmsan: add tests for KMSAN")
> > >
> > > Not sure about the Fixes - this means it's likely going to be
> > > backported to stable kernels, which is not appropriate. There's no
> > > functional problem, and these are tests only, so not worth the churn.
> >
> > As long as there is no actual bug fixed then I believe those Fixes tags
> > are more confusing than actually helpful. And that applies to other
> > patches in this series as well.
>
> For the dead code, I can remove the fixes tags, and even the changes
> themselves, since there are good reasons to keep the dead code
> (consistency, and avoiding a future programmer forgetting to add it back
> when adding a subsequent seprintf() call).
>
> For the fixes to UB, do you prefer the Fixes tags to be removed too?
Are any of those UB a real or just theoretical problems? To be more
precise I do not question to have those plugged but is there any
evidence that older kernels would need those as well other than just in
case?
--
Michal Hocko
SUSE Labs
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 5/7] mm: Fix benign off-by-one bugs
2025-07-07 15:12 ` Michal Hocko
@ 2025-07-07 15:29 ` Alejandro Colomar
0 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 15:29 UTC (permalink / raw)
To: Michal Hocko
Cc: Marco Elver, linux-mm, linux-hardening, Kees Cook,
Christopher Bazley, shadow, linux-kernel, Andrew Morton,
kasan-dev, Dmitry Vyukov, Alexander Potapenko, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Jann Horn, Linus Torvalds
[-- Attachment #1: Type: text/plain, Size: 1231 bytes --]
Hi Michal,
On Mon, Jul 07, 2025 at 05:12:00PM +0200, Michal Hocko wrote:
> > For the dead code, I can remove the fixes tags, and even the changes
> > themselves, since there are good reasons to keep the dead code
> > (consistency, and avoiding a future programmer forgetting to add it back
> > when adding a subsequent seprintf() call).
> >
> > For the fixes to UB, do you prefer the Fixes tags to be removed too?
>
> Are any of those UB a real or just theoretical problems? To be more
> precise I do not question to have those plugged but is there any
> evidence that older kernels would need those as well other than just in
> case?
No, I haven't done any checks to verify that this is exploitable in any
way. I personally wouldn't backport any of this.
About the Fixes: tags, I guess if they are interpreted as something to
be backported, I'll remove them all, as I don't want to backport this.
I guess having them listed in the mailing list archives would be good
enough for speleology purposes (e.g., for someone interested in what
kinds of issues this API fixes).
I'll remove them all.
Cheers,
Alex
>
> --
> Michal Hocko
> SUSE Labs
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 14:58 ` Marco Elver
@ 2025-07-07 18:51 ` Alejandro Colomar
2025-07-07 19:08 ` Marco Elver
0 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 18:51 UTC (permalink / raw)
To: Marco Elver
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Christoph Lameter, David Rientjes,
Vlastimil Babka, Roman Gushchin, Harry Yoo, Andrew Clayton,
Sven Schnelle, Heiko Carstens, Tvrtko Ursulin, Huang, Ying,
Lee Schermerhorn, Linus Torvalds, Christophe JAILLET,
Hyeonggon Yoo, Chao Yu
[-- Attachment #1: Type: text/plain, Size: 1160 bytes --]
Hi Marco,
On Mon, Jul 07, 2025 at 04:58:53PM +0200, Marco Elver wrote:
> Feel free to make it warning-free, I guess that's useful.
Thanks!
> > > Did you run the tests? Do they pass?
> >
> > I don't know how to run them. I've only built the kernel. If you point
> > me to instructions on how to run them, I'll do so. Thanks!
>
> Should just be CONFIG_KFENCE_KUNIT_TEST=y -- then boot kernel and
> check that the test reports "ok".
Hmmm, I can't see the results. Did I miss anything?
alx@debian:~$ uname -a
Linux debian 6.15.0-seprintf-mm+ #5 SMP PREEMPT_DYNAMIC Mon Jul 7 19:16:40 CEST 2025 x86_64 GNU/Linux
alx@debian:~$ cat /boot/config-6.15.0-seprintf-mm+ | grep KFENCE
CONFIG_HAVE_ARCH_KFENCE=y
CONFIG_KFENCE=y
CONFIG_KFENCE_SAMPLE_INTERVAL=0
CONFIG_KFENCE_NUM_OBJECTS=255
# CONFIG_KFENCE_DEFERRABLE is not set
# CONFIG_KFENCE_STATIC_KEYS is not set
CONFIG_KFENCE_STRESS_TEST_FAULTS=0
CONFIG_KFENCE_KUNIT_TEST=y
alx@debian:~$ sudo dmesg | grep -i kfence
alx@debian:~$
I see a lot of new stuff in dmesg, but nothing with 'kfence' in it.
Cheers,
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 18:51 ` Alejandro Colomar
@ 2025-07-07 19:08 ` Marco Elver
2025-07-07 20:53 ` Alejandro Colomar
0 siblings, 1 reply; 98+ messages in thread
From: Marco Elver @ 2025-07-07 19:08 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Christoph Lameter, David Rientjes,
Vlastimil Babka, Roman Gushchin, Harry Yoo, Andrew Clayton,
Sven Schnelle, Heiko Carstens, Tvrtko Ursulin, Huang, Ying,
Lee Schermerhorn, Linus Torvalds, Christophe JAILLET,
Hyeonggon Yoo, Chao Yu
On Mon, 7 Jul 2025 at 20:51, Alejandro Colomar <alx@kernel.org> wrote:
>
> Hi Marco,
>
> On Mon, Jul 07, 2025 at 04:58:53PM +0200, Marco Elver wrote:
> > Feel free to make it warning-free, I guess that's useful.
>
> Thanks!
>
> > > > Did you run the tests? Do they pass?
> > >
> > > I don't know how to run them. I've only built the kernel. If you point
> > > me to instructions on how to run them, I'll do so. Thanks!
> >
> > Should just be CONFIG_KFENCE_KUNIT_TEST=y -- then boot kernel and
> > check that the test reports "ok".
>
> Hmmm, I can't see the results. Did I miss anything?
>
> alx@debian:~$ uname -a
> Linux debian 6.15.0-seprintf-mm+ #5 SMP PREEMPT_DYNAMIC Mon Jul 7 19:16:40 CEST 2025 x86_64 GNU/Linux
> alx@debian:~$ cat /boot/config-6.15.0-seprintf-mm+ | grep KFENCE
> CONFIG_HAVE_ARCH_KFENCE=y
> CONFIG_KFENCE=y
> CONFIG_KFENCE_SAMPLE_INTERVAL=0
^^ This means KFENCE is off.
Not sure why it's 0 (distro default config?), but if you switch it to
something like:
CONFIG_KFENCE_SAMPLE_INTERVAL=10
The test should run. Alternatively set 'kfence.sample_interval=10' as
boot param.
> CONFIG_KFENCE_NUM_OBJECTS=255
> # CONFIG_KFENCE_DEFERRABLE is not set
> # CONFIG_KFENCE_STATIC_KEYS is not set
> CONFIG_KFENCE_STRESS_TEST_FAULTS=0
> CONFIG_KFENCE_KUNIT_TEST=y
> alx@debian:~$ sudo dmesg | grep -i kfence
> alx@debian:~$
>
> I see a lot of new stuff in dmesg, but nothing with 'kfence' in it.
>
>
> Cheers,
> Alex
>
> --
> <https://www.alejandro-colomar.es/>
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 5:06 ` [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs Alejandro Colomar
2025-07-07 7:44 ` Marco Elver
@ 2025-07-07 19:17 ` Linus Torvalds
2025-07-07 19:35 ` Al Viro
` (2 more replies)
1 sibling, 3 replies; 98+ messages in thread
From: Linus Torvalds @ 2025-07-07 19:17 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Sven Schnelle, Heiko Carstens, Tvrtko Ursulin,
Huang, Ying, Lee Schermerhorn, Christophe JAILLET, Hyeonggon Yoo,
Chao Yu
On Sun, 6 Jul 2025 at 22:06, Alejandro Colomar <alx@kernel.org> wrote:
>
> - p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
> + p = seprintf(p, e, "%07u", s->size);
I am *really* not a fan of introducing yet another random non-standard
string function.
This 'seprintf' thing really seems to be a completely made-up thing.
Let's not go there. It just adds more confusion - it may be a simpler
interface, but it's another cogniitive load thing, and honestly, that
"beginning and end" interface is not great.
I think we'd be better off with real "character buffer" interfaces,
and they should be *named* that way, not be yet another "random
character added to the printf family".
The whole "add a random character" thing is a disease. But at least
with printf/fprintf/vprintf/vsnprintf/etc, it's a _standard_ disease,
so people hopefully know about it.
So I really *really* don't like things like seprintf(). It just makes me go WTF?
Interfaces that have worked for us are things like "seq_printf()", which
(a) has sane naming, not "add random characters"
(b) has real abstractions (in that case 'struct seq_file') rather
than adding random extra arguments to the argument list.
and we do have something like that in 'struct seq_buf'. I'm not
convinced that's the optimal interface, but I think it's *better*.
Because it does both encapsulate a proper "this is my buffer" type,
and has a proper "this is a buffer operation" function name.
So I'd *much* rather people would try to convert their uses to things
like that, than add random letter combinations.
Linus
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 19:17 ` Linus Torvalds
@ 2025-07-07 19:35 ` Al Viro
2025-07-07 20:46 ` Linus Torvalds
2025-07-07 20:29 ` Alejandro Colomar
2025-07-12 20:58 ` Christopher Bazley
2 siblings, 1 reply; 98+ messages in thread
From: Al Viro @ 2025-07-07 19:35 UTC (permalink / raw)
To: Linus Torvalds
Cc: Alejandro Colomar, linux-mm, linux-hardening, Kees Cook,
Christopher Bazley, shadow, linux-kernel, Andrew Morton,
kasan-dev, Dmitry Vyukov, Alexander Potapenko, Marco Elver,
Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Sven Schnelle,
Heiko Carstens, Tvrtko Ursulin, Huang, Ying, Lee Schermerhorn,
Christophe JAILLET, Hyeonggon Yoo, Chao Yu
On Mon, Jul 07, 2025 at 12:17:11PM -0700, Linus Torvalds wrote:
> and we do have something like that in 'struct seq_buf'. I'm not
> convinced that's the optimal interface, but I think it's *better*.
> Because it does both encapsulate a proper "this is my buffer" type,
> and has a proper "this is a buffer operation" function name.
>
> So I'd *much* rather people would try to convert their uses to things
> like that, than add random letter combinations.
Lifting struct membuf out of include/linux/regset.h, perhaps, and
adding printf to the family?
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 19:17 ` Linus Torvalds
2025-07-07 19:35 ` Al Viro
@ 2025-07-07 20:29 ` Alejandro Colomar
2025-07-07 20:49 ` Linus Torvalds
2025-07-12 20:58 ` Christopher Bazley
2 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 20:29 UTC (permalink / raw)
To: Linus Torvalds
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Sven Schnelle, Heiko Carstens, Tvrtko Ursulin,
Huang, Ying, Lee Schermerhorn, Christophe JAILLET, Hyeonggon Yoo,
Chao Yu
[-- Attachment #1: Type: text/plain, Size: 3993 bytes --]
Hi Linus,
On Mon, Jul 07, 2025 at 12:17:11PM -0700, Linus Torvalds wrote:
> On Sun, 6 Jul 2025 at 22:06, Alejandro Colomar <alx@kernel.org> wrote:
> >
> > - p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
> > + p = seprintf(p, e, "%07u", s->size);
>
> I am *really* not a fan of introducing yet another random non-standard
> string function.
I am in the C Committee, and have proposed this API for standardization.
I have a feeling that the committee might be open to it.
> This 'seprintf' thing really seems to be a completely made-up thing.
> Let's not go there. It just adds more confusion - it may be a simpler
> interface, but it's another cogniitive load thing,
I understand the part of your concern that relates to
<https://xkcd.com/927/>.
However, I've shown how in mm/, I got rid of most snprintf() and
scnprintf() calls. I could even get rid of the remaining snprintf()
ones; I didn't do it to avoid churn, but they're just 3, so I could do
it, as a way to remove all uses of snprintf(3).
I also got rid of all scnprintf() uses except for 2. Not because those
two cannot be removed, but because the code was scary enough that I
didn't dare touch it. I'd like someone to read it and confirm that it
can be replaced.
> and honestly, that
> "beginning and end" interface is not great.
Just look at the diffs. It is great, in terms of writing less code.
In some cases, it makes sense to pass a size. Those cases are when you
don't want to chain several calls. That's the case of stprintf(), and
it's wrapper STPRINTF(), which calls ARRAY_SIZE() internally.
But most of the time you want to chain calls, and 'end' beats 'size'
there.
> I think we'd be better off with real "character buffer" interfaces,
> and they should be *named* that way, not be yet another "random
> character added to the printf family".
You might want to do that, but I doubt it's an easy change. On the
other hand, this change is trivial, and can be done incrementally,
without needing to modify the buffer since its inception.
And you can come back later to wrap this in some API that does what you
want. Nothing stops you from doing that.
But this fixes several cases of UB in a few files that I've looked at,
with minimal diffs.
> The whole "add a random character" thing is a disease. But at least
> with printf/fprintf/vprintf/vsnprintf/etc, it's a _standard_ disease,
> so people hopefully know about it.
seprint(2) was implemented in Plan9 many decades ago. It's not
standard, because somehow Plan9 has been ignored by history, but the
name has a long history.
<https://plan9.io/magic/man2html/2/print>
Plus, I'm making seprintf() standard (if I can convince the committee).
Yesterday night, I presented the proposal to the committee, informally
(via email). You can read a copy here:
<https://lore.kernel.org/linux-hardening/cover.1751747518.git.alx@kernel.org/T/#m9311035d60b4595db62273844d16671601e77a50>
I'll present it formally in a month, since I have a batch of proposals
for the committee in the works.
Have a lovely day!
Alex
> So I really *really* don't like things like seprintf(). It just makes me go WTF?
>
> Interfaces that have worked for us are things like "seq_printf()", which
>
> (a) has sane naming, not "add random characters"
>
> (b) has real abstractions (in that case 'struct seq_file') rather
> than adding random extra arguments to the argument list.
>
> and we do have something like that in 'struct seq_buf'. I'm not
> convinced that's the optimal interface, but I think it's *better*.
> Because it does both encapsulate a proper "this is my buffer" type,
> and has a proper "this is a buffer operation" function name.
>
> So I'd *much* rather people would try to convert their uses to things
> like that, than add random letter combinations.
>
> Linus
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 19:35 ` Al Viro
@ 2025-07-07 20:46 ` Linus Torvalds
0 siblings, 0 replies; 98+ messages in thread
From: Linus Torvalds @ 2025-07-07 20:46 UTC (permalink / raw)
To: Al Viro
Cc: Alejandro Colomar, linux-mm, linux-hardening, Kees Cook,
Christopher Bazley, shadow, linux-kernel, Andrew Morton,
kasan-dev, Dmitry Vyukov, Alexander Potapenko, Marco Elver,
Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Sven Schnelle,
Heiko Carstens, Tvrtko Ursulin, Huang, Ying, Lee Schermerhorn,
Christophe JAILLET, Hyeonggon Yoo, Chao Yu
On Mon, 7 Jul 2025 at 12:35, Al Viro <viro@zeniv.linux.org.uk> wrote:
>
> Lifting struct membuf out of include/linux/regset.h, perhaps, and
> adding printf to the family?
membuf has its own problems. It doesn't remember the beginning of the
buffer, so while it's good for "fill in this buffer with streaming
data", it's pretty bad for "let's declare a buffer, fill it in, and
then use the buffer for something".
So with membuf, you can do that "fill this buffer" cleanly.
But you can't then do that "ok, it's filled, now flush it" - not
without passing in some other data (namely the original buffer data).
I don't exactly love "struct seq_buf" either - it's big and wasteful
because it has 64-bit sizes - but it at least *retains* the full
state, so you can do things like "print to this buffer" and "flush
this buffer" *without* passing around extra data.
Linus
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 20:29 ` Alejandro Colomar
@ 2025-07-07 20:49 ` Linus Torvalds
2025-07-07 21:05 ` Alejandro Colomar
0 siblings, 1 reply; 98+ messages in thread
From: Linus Torvalds @ 2025-07-07 20:49 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Sven Schnelle, Heiko Carstens, Tvrtko Ursulin,
Huang, Ying, Lee Schermerhorn, Christophe JAILLET, Hyeonggon Yoo,
Chao Yu
On Mon, 7 Jul 2025 at 13:29, Alejandro Colomar <alx@kernel.org> wrote:
>
> I am in the C Committee, and have proposed this API for standardization.
> I have a feeling that the committee might be open to it.
Honestly, how about fixing the serious problems with the language instead?
Get rid of the broken "strict aliasing" garbage.
Get rid of the random "undefined behavior" stuff that is literally
designed to let compilers intentionally mis-compile code.
Because as things are, "I am on the C committee" isn't a
recommendation. It's a "we have decades of bad decisions to show our
credentials".
In the kernel, I have made it very very clear that we do not use
standard C, because standard C is broken.
I stand by my "let's not add random letters to existing functions that
are already too confusing".
Linus
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 19:08 ` Marco Elver
@ 2025-07-07 20:53 ` Alejandro Colomar
0 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 20:53 UTC (permalink / raw)
To: Marco Elver
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Christoph Lameter, David Rientjes,
Vlastimil Babka, Roman Gushchin, Harry Yoo, Andrew Clayton,
Sven Schnelle, Heiko Carstens, Tvrtko Ursulin, Huang, Ying,
Lee Schermerhorn, Linus Torvalds, Christophe JAILLET,
Hyeonggon Yoo, Chao Yu
[-- Attachment #1: Type: text/plain, Size: 17312 bytes --]
Hi Marco,
On Mon, Jul 07, 2025 at 09:08:29PM +0200, Marco Elver wrote:
> > > > > Did you run the tests? Do they pass?
> > > >
> > > > I don't know how to run them. I've only built the kernel. If you point
> > > > me to instructions on how to run them, I'll do so. Thanks!
> > >
> > > Should just be CONFIG_KFENCE_KUNIT_TEST=y -- then boot kernel and
> > > check that the test reports "ok".
> >
> > Hmmm, I can't see the results. Did I miss anything?
> >
> > alx@debian:~$ uname -a
> > Linux debian 6.15.0-seprintf-mm+ #5 SMP PREEMPT_DYNAMIC Mon Jul 7 19:16:40 CEST 2025 x86_64 GNU/Linux
> > alx@debian:~$ cat /boot/config-6.15.0-seprintf-mm+ | grep KFENCE
> > CONFIG_HAVE_ARCH_KFENCE=y
> > CONFIG_KFENCE=y
> > CONFIG_KFENCE_SAMPLE_INTERVAL=0
>
> ^^ This means KFENCE is off.
>
> Not sure why it's 0 (distro default config?), but if you switch it to
> something like:
Yup, Debian default config plus what you told me. :)
>
> CONFIG_KFENCE_SAMPLE_INTERVAL=10
Thanks! Now I see the tests.
I see no regressions. I've tested both v6.15 and my branch, and see no
differences:
This was generated with the kernel built from my branch:
$ sudo dmesg | grep -inC2 kfence | sed 's/^....//' > tmp/log_after
This was generated with a v6.15 kernel with the same exact config:
$ sudo dmesg | grep -inC2 kfence | sed 's/^....//' > tmp/log_before
And here's a diff, ignoring some numbers that were easy to filter out:
$ diff -U999 \
<(cat tmp/log_before \
| sed 's/0x[0-9a-f]*/0x????/g' \
| sed 's/[[:digit:]]\.[[:digit:]]\+/?.?/g' \
| sed 's/#[[:digit:]]\+/#???/g') \
<(cat tmp/log_after \
| sed 's/0x[0-9a-f]*/0x????/g' \
| sed 's/[[:digit:]]\.[[:digit:]]\+/?.?/g' \
| sed 's/#[[:digit:]]\+/#???/g');
--- /dev/fd/63 2025-07-07 22:47:37.395608776 +0200
+++ /dev/fd/62 2025-07-07 22:47:37.395608776 +0200
@@ -1,303 +1,303 @@
[ ?.?] NR_IRQS: 524544, nr_irqs: 1096, preallocated irqs: 16
[ ?.?] rcu: srcu_init: Setting srcu_struct sizes based on contention.
[ ?.?] kfence: initialized - using 2097152 bytes for 255 objects at 0x????(____ptrval____)-0x????(____ptrval____)
[ ?.?] Console: colour dummy device 80x????
[ ?.?] printk: legacy console [tty0] enabled
--
[ ?.?] ok 7 sysctl_test
[ ?.?] KTAP version 1
[ ?.?] # Subtest: kfence
[ ?.?] 1..27
[ ?.?] # test_out_of_bounds_read: test_alloc: size=32, gfp=cc0, policy=left, cache=0
[ ?.?] ==================================================================
[ ?.?] BUG: KFENCE: out-of-bounds read in test_out_of_bounds_read+0x????/0x????
[ ?.?] Out-of-bounds read at 0x???? (1B left of kfence-#???):
[ ?.?] test_out_of_bounds_read+0x????/0x????
[ ?.?] kunit_try_run_case+0x????/0x????
--
[ ?.?] ret_from_fork_asm+0x????/0x????
[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=kmalloc-32
-[ ?.?] allocated by task 281 on cpu 6 at ?.?s (?.?s ago):
+[ ?.?] allocated by task 286 on cpu 8 at ?.?s (?.?s ago):
--
[ ?.?] # test_out_of_bounds_read: test_alloc: size=32, gfp=cc0, policy=right, cache=0
[ ?.?] ==================================================================
[ ?.?] BUG: KFENCE: out-of-bounds read in test_out_of_bounds_read.cold+0x????/0x????
[ ?.?] Out-of-bounds read at 0x???? (32B right of kfence-#???):
[ ?.?] test_out_of_bounds_read.cold+0x????/0x????
[ ?.?] kunit_try_run_case+0x????/0x????
--
[ ?.?] ret_from_fork_asm+0x????/0x????
[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=kmalloc-32
-[ ?.?] allocated by task 281 on cpu 6 at ?.?s (?.?s ago):
+[ ?.?] allocated by task 286 on cpu 11 at ?.?s (?.?s ago):
--
[ ?.?] # test_out_of_bounds_read-memcache: test_alloc: size=32, gfp=cc0, policy=left, cache=1
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: out-of-bounds read in test_out_of_bounds_read+0x????/0x????
-
:[ ?.?] Out-of-bounds read at 0x???? (1B left of kfence-#???):
-[ ?.?] test_out_of_bounds_read+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=test
-
--[ ?.?] allocated by task 284 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 289 on cpu 8 at ?.?s (?.?s ago):
--
-[ ?.?] # test_out_of_bounds_read-memcache: test_alloc: size=32, gfp=cc0, policy=right, cache=1
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: out-of-bounds read in test_out_of_bounds_read.cold+0x????/0x????
-
:[ ?.?] Out-of-bounds read at 0x???? (32B right of kfence-#???):
-[ ?.?] test_out_of_bounds_read.cold+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=test
-
--[ ?.?] allocated by task 284 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 289 on cpu 8 at ?.?s (?.?s ago):
--
-[ ?.?] # test_out_of_bounds_write: test_alloc: size=32, gfp=cc0, policy=left, cache=0
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: out-of-bounds write in test_out_of_bounds_write+0x????/0x????
-
:[ ?.?] Out-of-bounds write at 0x???? (1B left of kfence-#???):
-[ ?.?] test_out_of_bounds_write+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=kmalloc-32
-
--[ ?.?] allocated by task 288 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 291 on cpu 6 at ?.?s (?.?s ago):
--
--[ ?.?] # test_out_of_bounds_write-memcache: test_alloc: size=32, gfp=cc0, policy=left, cache=1
-[ ?.?] ==================================================================
+-[ ?.?] clocksource: tsc: mask: 0x???? max_cycles: 0x????, max_idle_ns: 881590599626 ns
:[ ?.?] BUG: KFENCE: out-of-bounds write in test_out_of_bounds_write+0x????/0x????
-
:[ ?.?] Out-of-bounds write at 0x???? (1B left of kfence-#???):
-[ ?.?] test_out_of_bounds_write+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=test
-
--[ ?.?] allocated by task 290 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 293 on cpu 10 at ?.?s (?.?s ago):
--
-[ ?.?] # test_use_after_free_read: test_alloc: size=32, gfp=cc0, policy=any, cache=0
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: use-after-free read in test_use_after_free_read+0x????/0x????
-
:[ ?.?] Use-after-free read at 0x???? (in kfence-#???):
-[ ?.?] test_use_after_free_read+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=kmalloc-32
-
--[ ?.?] allocated by task 292 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 296 on cpu 10 at ?.?s (?.?s ago):
--
-[ ?.?] # test_use_after_free_read-memcache: test_alloc: size=32, gfp=cc0, policy=any, cache=1
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: use-after-free read in test_use_after_free_read+0x????/0x????
-
:[ ?.?] Use-after-free read at 0x???? (in kfence-#???):
-[ ?.?] test_use_after_free_read+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=test
-
--[ ?.?] allocated by task 294 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 298 on cpu 10 at ?.?s (?.?s ago):
--
-[ ?.?] # test_double_free: test_alloc: size=32, gfp=cc0, policy=any, cache=0
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: invalid free in test_double_free+0x????/0x????
-
:[ ?.?] Invalid free of 0x???? (in kfence-#???):
-[ ?.?] test_double_free+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=kmalloc-32
-
--[ ?.?] allocated by task 300 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 304 on cpu 6 at ?.?s (?.?s ago):
--
-[ ?.?] # test_double_free-memcache: test_alloc: size=32, gfp=cc0, policy=any, cache=1
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: invalid free in test_double_free+0x????/0x????
-
:[ ?.?] Invalid free of 0x???? (in kfence-#???):
-[ ?.?] test_double_free+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=test
-
--[ ?.?] allocated by task 302 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 306 on cpu 8 at ?.?s (?.?s ago):
--
-[ ?.?] # test_invalid_addr_free: test_alloc: size=32, gfp=cc0, policy=any, cache=0
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: invalid free in test_invalid_addr_free+0x????/0x????
-
:[ ?.?] Invalid free of 0x???? (in kfence-#???):
-[ ?.?] test_invalid_addr_free+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=kmalloc-32
-
--[ ?.?] allocated by task 304 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 308 on cpu 8 at ?.?s (?.?s ago):
--
-[ ?.?] # test_invalid_addr_free-memcache: test_alloc: size=32, gfp=cc0, policy=any, cache=1
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: invalid free in test_invalid_addr_free+0x????/0x????
-
:[ ?.?] Invalid free of 0x???? (in kfence-#???):
-[ ?.?] test_invalid_addr_free+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=test
-
--[ ?.?] allocated by task 306 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 310 on cpu 8 at ?.?s (?.?s ago):
--
-[ ?.?] # test_corruption: test_alloc: size=32, gfp=cc0, policy=left, cache=0
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: memory corruption in test_corruption+0x????/0x????
-
:[ ?.?] Corrupted memory at 0x???? [ ! . . . . . . . . . . . . . . . ] (in kfence-#???):
-[ ?.?] test_corruption+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=kmalloc-32
-
--[ ?.?] allocated by task 308 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 312 on cpu 6 at ?.?s (?.?s ago):
--
-[ ?.?] # test_corruption: test_alloc: size=32, gfp=cc0, policy=right, cache=0
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: memory corruption in test_corruption+0x????/0x????
-
:[ ?.?] Corrupted memory at 0x???? [ ! ] (in kfence-#???):
-[ ?.?] test_corruption+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=kmalloc-32
-
--[ ?.?] allocated by task 308 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 312 on cpu 6 at ?.?s (?.?s ago):
--
-[ ?.?] # test_corruption-memcache: test_alloc: size=32, gfp=cc0, policy=left, cache=1
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: memory corruption in test_corruption+0x????/0x????
-
:[ ?.?] Corrupted memory at 0x???? [ ! . . . . . . . . . . . . . . . ] (in kfence-#???):
-[ ?.?] test_corruption+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=test
-
--[ ?.?] allocated by task 310 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 314 on cpu 6 at ?.?s (?.?s ago):
--
-[ ?.?] # test_corruption-memcache: test_alloc: size=32, gfp=cc0, policy=right, cache=1
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: memory corruption in test_corruption+0x????/0x????
-
:[ ?.?] Corrupted memory at 0x???? [ ! ] (in kfence-#???):
-[ ?.?] test_corruption+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=test
-
--[ ?.?] allocated by task 310 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 314 on cpu 6 at ?.?s (?.?s ago):
--
-[ ?.?] # test_kmalloc_aligned_oob_read: test_alloc: size=73, gfp=cc0, policy=right, cache=0
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: out-of-bounds read in test_kmalloc_aligned_oob_read+0x????/0x????
-
:[ ?.?] Out-of-bounds read at 0x???? (105B right of kfence-#???):
-[ ?.?] test_kmalloc_aligned_oob_read+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=73, cache=kmalloc-96
-
--[ ?.?] allocated by task 320 on cpu 10 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 326 on cpu 6 at ?.?s (?.?s ago):
--
-[ ?.?] # test_kmalloc_aligned_oob_write: test_alloc: size=73, gfp=cc0, policy=right, cache=0
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: memory corruption in test_kmalloc_aligned_oob_write+0x????/0x????
-
:[ ?.?] Corrupted memory at 0x???? [ ! . . . . . . . . . . . . . . . ] (in kfence-#???):
-[ ?.?] test_kmalloc_aligned_oob_write+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=73, cache=kmalloc-96
-
--[ ?.?] allocated by task 326 on cpu 8 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 328 on cpu 4 at ?.?s (?.?s ago):
--
-[ ?.?] ok 22 test_memcache_ctor
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: invalid read in test_invalid_access+0x????/0x????
-
-[ ?.?] Invalid read at 0x????:
--
-[ ?.?] # test_memcache_typesafe_by_rcu: test_alloc: size=32, gfp=cc0, policy=any, cache=1
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: use-after-free read in test_memcache_typesafe_by_rcu.cold+0x????/0x????
-
:[ ?.?] Use-after-free read at 0x???? (in kfence-#???):
-[ ?.?] test_memcache_typesafe_by_rcu.cold+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=test
-
--[ ?.?] allocated by task 336 on cpu 6 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 338 on cpu 10 at ?.?s (?.?s ago):
--
-[ ?.?] # test_krealloc: test_alloc: size=32, gfp=cc0, policy=any, cache=0
-[ ?.?] ==================================================================
:[ ?.?] BUG: KFENCE: use-after-free read in test_krealloc+0x????/0x????
-
:[ ?.?] Use-after-free read at 0x???? (in kfence-#???):
-[ ?.?] test_krealloc+0x????/0x????
-[ ?.?] kunit_try_run_case+0x????/0x????
--
-[ ?.?] ret_from_fork_asm+0x????/0x????
-
:[ ?.?] kfence-#???: 0x????-0x????, size=32, cache=kmalloc-32
-
--[ ?.?] allocated by task 338 on cpu 4 at ?.?s (?.?s ago):
+-[ ?.?] allocated by task 340 on cpu 6 at ?.?s (?.?s ago):
--
-[ ?.?] # test_memcache_alloc_bulk: setup_test_cache: size=32, ctor=0x????
-[ ?.?] ok 27 test_memcache_alloc_bulk
:[ ?.?] # kfence: pass:25 fail:0 skip:2 total:27
-[ ?.?] # Totals: pass:25 fail:0 skip:2 total:27
:[ ?.?] ok 8 kfence
-[ ?.?] KTAP version 1
-[ ?.?] # Subtest: damon
If you'd like me to grep for something more specific, please let me
know.
Cheers,
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 20:49 ` Linus Torvalds
@ 2025-07-07 21:05 ` Alejandro Colomar
2025-07-07 21:26 ` Alejandro Colomar
0 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 21:05 UTC (permalink / raw)
To: Linus Torvalds
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Sven Schnelle, Heiko Carstens, Tvrtko Ursulin,
Huang, Ying, Lee Schermerhorn, Christophe JAILLET, Hyeonggon Yoo,
Chao Yu
[-- Attachment #1: Type: text/plain, Size: 1932 bytes --]
Hi Linus,
On Mon, Jul 07, 2025 at 01:49:20PM -0700, Linus Torvalds wrote:
> On Mon, 7 Jul 2025 at 13:29, Alejandro Colomar <alx@kernel.org> wrote:
> >
> > I am in the C Committee, and have proposed this API for standardization.
> > I have a feeling that the committee might be open to it.
>
> Honestly, how about fixing the serious problems with the language instead?
I'm doing some work on that. See the new _Countof() operator? That was
my first introduction in the standard, last year.
I'm working on an extension to it that I believe will make array
parameters safer.
> Get rid of the broken "strict aliasing" garbage.
I don't feel qualified to comment on that.
> Get rid of the random "undefined behavior" stuff that is literally
> designed to let compilers intentionally mis-compile code.
We're indeed working on that. The last committee meeting removed a
large number of undefined behaviors, and turned them into mandatory
diagnostics. And there's ongoing work on removing more of those.
> Because as things are, "I am on the C committee" isn't a
> recommendation. It's a "we have decades of bad decisions to show our
> credentials".
I joined in 2024 because I was fed up with the shit they were producing
and wanted to influence it. You don't need to convince me.
> In the kernel, I have made it very very clear that we do not use
> standard C, because standard C is broken.
I agree. I personally use GNU C and tend to ignore the standard. But
I'm still working on improving the standard, even if just to avoid
having to learn Rust (and also because GCC and glibc don't accept any
improvements or fixes if they don't go through the standard, these
days).
Have a lovely day!
Alex
> I stand by my "let's not add random letters to existing functions that
> are already too confusing".
>
> Linus
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 21:05 ` Alejandro Colomar
@ 2025-07-07 21:26 ` Alejandro Colomar
2025-07-07 22:17 ` Linus Torvalds
0 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-07 21:26 UTC (permalink / raw)
To: Linus Torvalds
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Sven Schnelle, Heiko Carstens, Tvrtko Ursulin,
Huang, Ying, Lee Schermerhorn, Christophe JAILLET, Hyeonggon Yoo,
Chao Yu
[-- Attachment #1: Type: text/plain, Size: 627 bytes --]
On Mon, Jul 07, 2025 at 11:06:06PM +0200, Alejandro Colomar wrote:
> > I stand by my "let's not add random letters to existing functions that
> > are already too confusing".
If the name is your main concern, we can discuss a more explicit name in
the kernel.
I still plan to propose it as seprintf() for standardization, and for
libc, but if that reads as a letter soup to you, I guess we can call it
sprintf_end() or whatever, for the kernel.
Does that sound reasonable enough? What do you think about the diff
itself ignoring the function name?
Cheers,
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 21:26 ` Alejandro Colomar
@ 2025-07-07 22:17 ` Linus Torvalds
2025-07-08 2:20 ` Alejandro Colomar
0 siblings, 1 reply; 98+ messages in thread
From: Linus Torvalds @ 2025-07-07 22:17 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Sven Schnelle, Heiko Carstens, Tvrtko Ursulin,
Huang, Ying, Lee Schermerhorn, Christophe JAILLET, Hyeonggon Yoo,
Chao Yu
On Mon, 7 Jul 2025 at 14:27, Alejandro Colomar <alx@kernel.org> wrote:
>
> If the name is your main concern, we can discuss a more explicit name in
> the kernel.
So as they say: "There are only two hard problems in computer science:
cache invalidation, naming and off-by-one errors".
And the *worst* model for naming is the "add random characters" (ok, I
still remember when people believed the insane "Hungarian Notation"
BS, *that* particular braindamage seems to thankfully have faded away
and was probably even worse, because it was both pointless, unreadable
_and_ caused long identifiers).
Now, we obviously tend to have the usual bike-shedding discussions
that come from naming, but my *personal* preference is to avoid the
myriad of random "does almost the same thing with different
parameters" by using generics.
This is actually something that the kernel has done for decades, with
various odd macro games - things like "get_user()" just automatically
doing the RightThing(tm) based on the size of the argument, rather
than having N different versions for different types.
So we actually have a fair number of "generics" in the kernel, and
while admittedly the header file contortions to implement them can
often be horrendous - the *use* cases tend to be fairly readable.
It's not just get_user() and friends, it's things like our
type-checking min/max macros etc. Lots of small helpers that
And while the traditional C model for this is indeed macro games with
sizeof() and other oddities, these days at least we have _Generic() to
help.
So my personal preference would actually be to not make up new names
at all, but just have the normal names DoTheRightThing(tm)
automatically.
But honestly, that works best when you have good data structure
abstraction - *not* when you pass just random "char *" pointers
around. It tends to help those kinds of _Generic() users, but even
without the use of _Generic() and friends, it helps static type
checking and makes things much less ambiguous even in general.
IOW, there's never any question about "is this string the source or
the destination?" or "is this the start or the end of the buffer", if
you just have a struct with clear naming that contains the arguments.
And while C doesn't have named arguments, it *does* have named
structure initializers, and we use them pretty religiously in the
kernel. Exactly because it helps so much both for readability and for
stability (ie it catches things when you intentionally rename members
because the semantics changed).
Linus
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 22:17 ` Linus Torvalds
@ 2025-07-08 2:20 ` Alejandro Colomar
0 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-08 2:20 UTC (permalink / raw)
To: Linus Torvalds
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Sven Schnelle, Heiko Carstens, Tvrtko Ursulin,
Huang, Ying, Lee Schermerhorn, Christophe JAILLET, Hyeonggon Yoo,
Chao Yu
Hi Linus,
On Mon, Jul 07, 2025 at 03:17:50PM -0700, Linus Torvalds wrote:
> On Mon, 7 Jul 2025 at 14:27, Alejandro Colomar <alx@kernel.org> wrote:
> >
> > If the name is your main concern, we can discuss a more explicit name in
> > the kernel.
>
> So as they say: "There are only two hard problems in computer science:
> cache invalidation, naming and off-by-one errors".
Indeed. And we have two of these classes here. :)
> And the *worst* model for naming is the "add random characters" (ok, I
> still remember when people believed the insane "Hungarian Notation"
> BS, *that* particular braindamage seems to thankfully have faded away
> and was probably even worse, because it was both pointless, unreadable
> _and_ caused long identifiers).
To be fair, one letter is enough if you're used to the name. Everything
of the form s*printf() people know that the differentiating part is that
single letter between 's' and 'p', and a quick look at the function
prototype usually explains the rest.
More than that, and it's unnecessarily noisy to my taste. But not
everyone does string work all the time, so I get why you'd be less prone
to liking the name.
I won't press for the name. Unless you say anything, my next revision
of the series will call it sprintf_end().
> Now, we obviously tend to have the usual bike-shedding discussions
> that come from naming, but my *personal* preference is to avoid the
> myriad of random "does almost the same thing with different
> parameters" by using generics.
>
> This is actually something that the kernel has done for decades, with
> various odd macro games - things like "get_user()" just automatically
> doing the RightThing(tm) based on the size of the argument, rather
> than having N different versions for different types.
In this case, I wouldn't want to go that way and reuse the name
snprintf(3), because the kernel implementation of snprintf(3) is
non-conforming, and both the standard and the kernel snprintf() have
semantics that are importantly different than this API in terms of
handling errors.
I think reusing the name with slightly different semantics would be
prone to bugs.
Anyway, sprintf_end() should be clear enough that I don't expect much
bikeshedding for the name. Feel free to revisit this in the future and
merge names if you don't like it; I won't complain. :)
Have a lovely night!
Alex
P.S.: I'm not able to sign this email.
> So we actually have a fair number of "generics" in the kernel, and
> while admittedly the header file contortions to implement them can
> often be horrendous - the *use* cases tend to be fairly readable.
>
> It's not just get_user() and friends, it's things like our
> type-checking min/max macros etc. Lots of small helpers that
>
> And while the traditional C model for this is indeed macro games with
> sizeof() and other oddities, these days at least we have _Generic() to
> help.
>
> So my personal preference would actually be to not make up new names
> at all, but just have the normal names DoTheRightThing(tm)
> automatically.
>
> But honestly, that works best when you have good data structure
> abstraction - *not* when you pass just random "char *" pointers
> around. It tends to help those kinds of _Generic() users, but even
> without the use of _Generic() and friends, it helps static type
> checking and makes things much less ambiguous even in general.
>
> IOW, there's never any question about "is this string the source or
> the destination?" or "is this the start or the end of the buffer", if
> you just have a struct with clear naming that contains the arguments.
>
> And while C doesn't have named arguments, it *does* have named
> structure initializers, and we use them pretty religiously in the
> kernel. Exactly because it helps so much both for readability and for
> stability (ie it catches things when you intentionally rename members
> because the semantics changed).
>
> Linus
--
<https://www.alejandro-colomar.es/>
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs
2025-07-05 20:33 [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
` (3 preceding siblings ...)
2025-07-06 17:37 ` [RFC v2 0/5] Add and use " Alejandro Colomar
@ 2025-07-08 6:43 ` Rasmus Villemoes
2025-07-08 11:36 ` Alejandro Colomar
4 siblings, 1 reply; 98+ messages in thread
From: Rasmus Villemoes @ 2025-07-08 6:43 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo
On Sat, Jul 05 2025, Alejandro Colomar <alx@kernel.org> wrote:
> On top of that, I have a question about the functions I'm adding,
> and the existing kernel snprintf(3): The standard snprintf(3)
> can fail (return -1), but the kernel one doesn't seem to return <0 ever.
> Should I assume that snprintf(3) doesn't fail here?
Yes. Just because the standard says it may return an error, as a QoI
thing the kernel's implementation never fails. That also means that we
do not ever do memory allocation or similar in the guts of vsnsprintf
(that would anyway be a mine field of locking bugs).
If we hit some invalid or unsupported format specifier (i.e. a bug in
the caller), we return early, but still report what we wrote until
hitting that.
Rasmus
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs
2025-07-08 6:43 ` [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs Rasmus Villemoes
@ 2025-07-08 11:36 ` Alejandro Colomar
2025-07-08 13:51 ` Rasmus Villemoes
0 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-08 11:36 UTC (permalink / raw)
To: Rasmus Villemoes
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo
[-- Attachment #1: Type: text/plain, Size: 1644 bytes --]
Hi Rasmus,
On Tue, Jul 08, 2025 at 08:43:57AM +0200, Rasmus Villemoes wrote:
> On Sat, Jul 05 2025, Alejandro Colomar <alx@kernel.org> wrote:
>
> > On top of that, I have a question about the functions I'm adding,
> > and the existing kernel snprintf(3): The standard snprintf(3)
> > can fail (return -1), but the kernel one doesn't seem to return <0 ever.
> > Should I assume that snprintf(3) doesn't fail here?
>
> Yes. Just because the standard says it may return an error, as a QoI
> thing the kernel's implementation never fails. That also means that we
> do not ever do memory allocation or similar in the guts of vsnsprintf
> (that would anyway be a mine field of locking bugs).
All of that sounds reasonable.
> If we hit some invalid or unsupported format specifier (i.e. a bug in
> the caller), we return early, but still report what we wrote until
> hitting that.
However, there's the early return due to size>INT_MAX || size==0, which
results in no string at all, and there's not an error code for this.
A user might think that the string is reliable after a vsprintf(3) call,
as it returned 0 --as if it had written ""--, but it didn't write
anything.
I would have returned -EOVERFLOW in that case.
I think something similar is true of strscpy(): it returns -E2BIG on
size==0 || size>INT_MAX but it should be a different error code, as
there's no string at all.
I'll propose something very close to strscpy() for standardization, but
the behavior for size==0 will either be undefined, or errno will be
EOVERFLOW.
Have a lovely day!
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs
2025-07-08 11:36 ` Alejandro Colomar
@ 2025-07-08 13:51 ` Rasmus Villemoes
2025-07-08 16:14 ` Alejandro Colomar
0 siblings, 1 reply; 98+ messages in thread
From: Rasmus Villemoes @ 2025-07-08 13:51 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo
On Tue, Jul 08 2025, Alejandro Colomar <alx@kernel.org> wrote:
> Hi Rasmus,
>
> On Tue, Jul 08, 2025 at 08:43:57AM +0200, Rasmus Villemoes wrote:
>> On Sat, Jul 05 2025, Alejandro Colomar <alx@kernel.org> wrote:
>>
>> > On top of that, I have a question about the functions I'm adding,
>> > and the existing kernel snprintf(3): The standard snprintf(3)
>> > can fail (return -1), but the kernel one doesn't seem to return <0 ever.
>> > Should I assume that snprintf(3) doesn't fail here?
>>
>> Yes. Just because the standard says it may return an error, as a QoI
>> thing the kernel's implementation never fails. That also means that we
>> do not ever do memory allocation or similar in the guts of vsnsprintf
>> (that would anyway be a mine field of locking bugs).
>
> All of that sounds reasonable.
>
>> If we hit some invalid or unsupported format specifier (i.e. a bug in
>> the caller), we return early, but still report what we wrote until
>> hitting that.
>
> However, there's the early return due to size>INT_MAX || size==0,
> which
First of all, there's no early return for size==0, that's absolutely
supported and the standard way for the caller to figure out how much to
allocate before redoing the formatting - as userspace asprintf() and
kernel kasprintf() does. And one of the primary reasons for me to write
the kernel's printf test suite in the first place, as a number of the %p
extensions weren't conforming to that requirement.
> results in no string at all, and there's not an error code for this.
> A user might think that the string is reliable after a vsprintf(3) call,
> as it returned 0 --as if it had written ""--, but it didn't write
> anything.
No, because when passed invalid/bogus input we cannot trust that we can
write anything at all to the buffer. We don't return a negative value,
true, but it's not exactly silent - there's a WARN_ON to help find such
bogus callers.
So no, there's "no string at all", but nothing vsnprint() could do in
that situation could help - there's a bug in the caller, we point it out
loudly. Returning -Ewhatever would not remove that bug and would only
make a difference if the caller checked for that.
We don't want to force everybody to check the return value of snprintf()
for errors, and having an interface that says "you have to check for
errors if your code might be buggy", well...
In fact, returning -Ewhatever is more likely to make the problem worse;
the caller mismanages buffer/size computations, so probably he's likely
to just be adding the return value to some size_t or char* variable,
making a subsequent use of that variable point to some completely
out-of-bounds memory.
Rasmus
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs
2025-07-08 13:51 ` Rasmus Villemoes
@ 2025-07-08 16:14 ` Alejandro Colomar
0 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-08 16:14 UTC (permalink / raw)
To: Rasmus Villemoes
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo
[-- Attachment #1: Type: text/plain, Size: 2444 bytes --]
Hi Rasmus,
On Tue, Jul 08, 2025 at 03:51:10PM +0200, Rasmus Villemoes wrote:
> > However, there's the early return due to size>INT_MAX || size==0,
> > which
>
> First of all, there's no early return for size==0, that's absolutely
> supported and the standard way for the caller to figure out how much to
> allocate before redoing the formatting - as userspace asprintf() and
> kernel kasprintf() does. And one of the primary reasons for me to write
> the kernel's printf test suite in the first place, as a number of the %p
> extensions weren't conforming to that requirement.
Yup, sorry, I was talking from memory, and forgot about the size==0.
I've introduced the check of size==0 for seprintf(), but snprintf(3) is
okay with it. My bad. The issue with INT_MAX holds.
> > results in no string at all, and there's not an error code for this.
> > A user might think that the string is reliable after a vsprintf(3) call,
> > as it returned 0 --as if it had written ""--, but it didn't write
> > anything.
>
> No, because when passed invalid/bogus input we cannot trust that we can
> write anything at all to the buffer. We don't return a negative value,
> true, but it's not exactly silent - there's a WARN_ON to help find such
> bogus callers.
Yup, I know. It's silent to the caller, I meant.
> So no, there's "no string at all", but nothing vsnprint() could do in
> that situation could help - there's a bug in the caller, we point it out
> loudly. Returning -Ewhatever would not remove that bug and would only
> make a difference if the caller checked for that.
>
> We don't want to force everybody to check the return value of snprintf()
> for errors, and having an interface that says "you have to check for
> errors if your code might be buggy", well...
>
> In fact, returning -Ewhatever is more likely to make the problem worse;
> the caller mismanages buffer/size computations, so probably he's likely
> to just be adding the return value to some size_t or char* variable,
> making a subsequent use of that variable point to some completely
> out-of-bounds memory.
That's why seprintf() controls that addition, and gives a pointer
directly to the user, which doesn't need to add anything. I think this
is easier to handle. There, I can report NULL for bad input, instead of
adding 0.
Have a lovely day!
Alex
>
> Rasmus
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* [RFC v4 0/7] Add and use sprintf_end() instead of less ergonomic APIs
2025-07-06 17:37 ` [RFC v2 0/5] Add and use " Alejandro Colomar
` (5 preceding siblings ...)
2025-07-07 5:06 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-10 2:47 ` Alejandro Colomar
2025-07-10 2:47 ` alx-0049r2 - add seprintf() Alejandro Colomar
` (7 more replies)
2025-07-10 21:30 ` [RFC v5 0/7] Add and use sprintf_{end,array}() instead of less ergonomic APIs Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 0/8] Add and use sprintf_{end,trunc,array}() instead of less ergonomic APIs Alejandro Colomar
8 siblings, 8 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 2:47 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
Hi,
Changes in v4:
- Added to global CC everyone who participated in the discussion so
far.
- Rename seprintf() => sprintf_end().
- Implement SPRINTF_END().
- Drop stprintf(). We don't need it as an intermediate helper.
- Link to the draft of a standards proposal (which I'll paste as a
reply to this mail again).
- Minor fixes or updates to commit messages.
- Added Marco Elver's Acked-by: tag in commit 5/7.
- In stack_depot_sprint_end(), do sprintf_end(p, end, "") when
nr_entries is 0, to guarantee that the string is valid if this is the
first s*printf() call in a row.
- Document sprintf_end() as 'string end-delimited print formatted'.
This spells the letters in seprintf() for their meaning, in case
anyone thinks the letters are randomly chosen. :)
- Remove comment about vsnprintf(3) not failing in the kernel, after
Rasmus commented this is QoI guaranteed by the kernel.
Remaining questions:
- There are only 3 remaining calls to snprintf(3) under mm/. They are
just fine for now, which is why I didn't replace them. If anyone
wants to replace them, to get rid of all snprintf(3), we could that.
I think for now we can leave them, to minimize the churn.
$ grep -rnI snprintf mm/
mm/hugetlb_cgroup.c:674: snprintf(buf, size, "%luGB", hsize / SZ_1G);
mm/hugetlb_cgroup.c:676: snprintf(buf, size, "%luMB", hsize / SZ_1M);
mm/hugetlb_cgroup.c:678: snprintf(buf, size, "%luKB", hsize / SZ_1K);
- There are only 2 remaining calls to the kernel's scnprintf(). This
one I would really like to get rid of. Also, those calls are quite
suspicious of not being what we want. Please do have a look at them
and confirm what's the appropriate behavior in the 2 cases when the
string is truncated or not copied at all. That code is very scary
for me to try to guess.
$ grep -rnI scnprintf mm/
mm/kfence/report.c:75: int len = scnprintf(buf, sizeof(buf), "%ps", (void *)stack_entries[skipnr]);
mm/kfence/kfence_test.mod.c:22: { 0x96848186, "scnprintf" },
mm/kmsan/report.c:42: len = scnprintf(buf, sizeof(buf), "%ps",
Apart from two calls, I see a string literal with that name. Please
let me know if I should do anything about it. I don't know what that
is.
- I think we should remove one error handling check in
"mm/page_owner.c" (marked with an XXX comment), but I'm not 100%
sure. Please confirm.
Other comments:
- This is still not complying to coding style. I'll keep it like that
while questions remain open.
- I've tested the tests under CONFIG_KFENCE_KUNIT_TEST=y, and this has
no regressions at all.
- With the current style of the sprintf_end() prototyope, this triggers
a diagnostic due to a GCC bug:
<https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108036>
It would be interesting to ask GCC to fix that bug. (Added relevant
GCC maintainers and contributors to CC in this cover letter.)
Have a lovely night!
Alex
Alejandro Colomar (7):
vsprintf: Add [v]sprintf_end()
stacktrace, stackdepot: Add sprintf_end()-like variants of functions
mm: Use sprintf_end() instead of less ergonomic APIs
array_size.h: Add ENDOF()
mm: Fix benign off-by-one bugs
sprintf: Add [V]SPRINTF_END()
mm: Use [V]SPRINTF_END() to avoid specifying the array size
include/linux/array_size.h | 6 ++++
include/linux/sprintf.h | 6 ++++
include/linux/stackdepot.h | 13 +++++++++
include/linux/stacktrace.h | 3 ++
kernel/stacktrace.c | 28 ++++++++++++++++++
lib/stackdepot.c | 13 +++++++++
lib/vsprintf.c | 59 ++++++++++++++++++++++++++++++++++++++
mm/backing-dev.c | 2 +-
mm/cma.c | 4 +--
mm/cma_debug.c | 2 +-
mm/hugetlb.c | 3 +-
mm/hugetlb_cgroup.c | 2 +-
mm/hugetlb_cma.c | 2 +-
mm/kasan/report.c | 3 +-
mm/kfence/kfence_test.c | 28 +++++++++---------
mm/kmsan/kmsan_test.c | 6 ++--
mm/memblock.c | 4 +--
mm/mempolicy.c | 18 ++++++------
mm/page_owner.c | 32 +++++++++++----------
mm/percpu.c | 2 +-
mm/shrinker_debug.c | 2 +-
mm/slub.c | 5 ++--
mm/zswap.c | 2 +-
23 files changed, 187 insertions(+), 58 deletions(-)
Range-diff against v3:
1: 64334f0b94d6 ! 1: 2c4f793de0b8 vsprintf: Add [v]seprintf(), [v]stprintf()
@@ Metadata
Author: Alejandro Colomar <alx@kernel.org>
## Commit message ##
- vsprintf: Add [v]seprintf(), [v]stprintf()
+ vsprintf: Add [v]sprintf_end()
- seprintf()
- ==========
-
- seprintf() is a function similar to stpcpy(3) in the sense that it
+ sprintf_end() is a function similar to stpcpy(3) in the sense that it
returns a pointer that is suitable for chaining to other copy
operations.
@@ Commit message
It also makes error handling much easier, by reporting truncation with
a null pointer, which is accepted and transparently passed down by
- subsequent seprintf() calls. This results in only needing to report
- errors once after a chain of seprintf() calls, unlike snprintf(3), which
- requires checking after every call.
+ subsequent sprintf_end() calls. This results in only needing to report
+ errors once after a chain of sprintf_end() calls, unlike snprintf(3),
+ which requires checking after every call.
p = buf;
e = buf + countof(buf);
- p = seprintf(p, e, foo);
- p = seprintf(p, e, bar);
+ p = sprintf_end(p, e, foo);
+ p = sprintf_end(p, e, bar);
if (p == NULL)
goto trunc;
@@ Commit message
size = countof(buf);
len += scnprintf(buf + len, size - len, foo);
len += scnprintf(buf + len, size - len, bar);
- if (len >= size)
- goto trunc;
+ // No ability to check.
It seems aparent that it's a more elegant approach to string catenation.
- stprintf()
- ==========
-
- stprintf() is a helper that is needed for implementing seprintf()
- --although it could be open-coded within vseprintf(), of course--, but
- it's also useful by itself. It has the same interface properties as
- strscpy(): that is, it copies with truncation, and reports truncation
- with -E2BIG. It would be useful to replace some calls to snprintf(3)
- and scnprintf() which don't need chaining, and where it's simpler to
- pass a size.
-
- It is better than plain snprintf(3), because it results in simpler error
- detection (it doesn't need a check >=countof(buf), but rather <0).
+ These functions will soon be proposed for standardization as
+ [v]seprintf() into C2y, and they exist in Plan9 as seprint(2) --but the
+ Plan9 implementation has important bugs--.
+ Link: <https://www.alejandro-colomar.es/src/alx/alx/wg14/alx-0049.git/tree/alx-0049.txt>
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
+ Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
+ Cc: Marco Elver <elver@google.com>
+ Cc: Michal Hocko <mhocko@suse.com>
+ Cc: Linus Torvalds <torvalds@linux-foundation.org>
+ Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
## include/linux/sprintf.h ##
-@@ include/linux/sprintf.h: __printf(2, 3) int sprintf(char *buf, const char * fmt, ...);
- __printf(2, 0) int vsprintf(char *buf, const char *, va_list);
- __printf(3, 4) int snprintf(char *buf, size_t size, const char *fmt, ...);
+@@ include/linux/sprintf.h: __printf(3, 4) int snprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
-+__printf(3, 4) int stprintf(char *buf, size_t size, const char *fmt, ...);
-+__printf(3, 0) int vstprintf(char *buf, size_t size, const char *fmt, va_list args);
__printf(3, 4) int scnprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
-+__printf(3, 4) char *seprintf(char *p, const char end[0], const char *fmt, ...);
-+__printf(3, 0) char *vseprintf(char *p, const char end[0], const char *fmt, va_list args);
++__printf(3, 4) char *sprintf_end(char *p, const char end[0], const char *fmt, ...);
++__printf(3, 0) char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args);
__printf(2, 3) __malloc char *kasprintf(gfp_t gfp, const char *fmt, ...);
__printf(2, 0) __malloc char *kvasprintf(gfp_t gfp, const char *fmt, va_list args);
__printf(2, 0) const char *kvasprintf_const(gfp_t gfp, const char *fmt, va_list args);
## lib/vsprintf.c ##
-@@ lib/vsprintf.c: int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
- }
- EXPORT_SYMBOL(vsnprintf);
-
-+/**
-+ * vstprintf - Format a string and place it in a buffer
-+ * @buf: The buffer to place the result into
-+ * @size: The size of the buffer, including the trailing null space
-+ * @fmt: The format string to use
-+ * @args: Arguments for the format string
-+ *
-+ * The return value is the length of the new string.
-+ * If the string is truncated, the function returns -E2BIG.
-+ *
-+ * If you're not already dealing with a va_list consider using stprintf().
-+ *
-+ * See the vsnprintf() documentation for format string extensions over C99.
-+ */
-+int vstprintf(char *buf, size_t size, const char *fmt, va_list args)
-+{
-+ int len;
-+
-+ len = vsnprintf(buf, size, fmt, args);
-+
-+ // It seems the kernel's vsnprintf() doesn't fail?
-+ //if (unlikely(len < 0))
-+ // return -E2BIG;
-+
-+ if (unlikely(len >= size))
-+ return -E2BIG;
-+
-+ return len;
-+}
-+EXPORT_SYMBOL(vstprintf);
-+
- /**
- * vscnprintf - Format a string and place it in a buffer
- * @buf: The buffer to place the result into
@@ lib/vsprintf.c: int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
}
EXPORT_SYMBOL(vscnprintf);
+/**
-+ * vseprintf - Format a string and place it in a buffer
++ * vsprintf_end - va_list string end-delimited print formatted
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
@@ lib/vsprintf.c: int vscnprintf(char *buf, size_t size, const char *fmt, va_list
+ * The return value is a pointer to the trailing '\0'.
+ * If @p is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
-+ *
-+ * If you're not already dealing with a va_list consider using seprintf().
++ * If @end <= @p, the function returns NULL.
+ *
+ * See the vsnprintf() documentation for format string extensions over C99.
+ */
-+char *vseprintf(char *p, const char end[0], const char *fmt, va_list args)
++char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args)
+{
+ int len;
++ size_t size;
+
+ if (unlikely(p == NULL))
+ return NULL;
+
-+ len = vstprintf(p, end - p, fmt, args);
-+ if (unlikely(len < 0))
++ size = end - p;
++ if (WARN_ON_ONCE(size == 0 || size > INT_MAX))
++ return NULL;
++
++ len = vsnprintf(p, size, fmt, args);
++ if (unlikely(len >= size))
+ return NULL;
+
+ return p + len;
+}
-+EXPORT_SYMBOL(vseprintf);
++EXPORT_SYMBOL(vsprintf_end);
+
/**
* snprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
-@@ lib/vsprintf.c: int snprintf(char *buf, size_t size, const char *fmt, ...)
- }
- EXPORT_SYMBOL(snprintf);
-
-+/**
-+ * stprintf - Format a string and place it in a buffer
-+ * @buf: The buffer to place the result into
-+ * @size: The size of the buffer, including the trailing null space
-+ * @fmt: The format string to use
-+ * @...: Arguments for the format string
-+ *
-+ * The return value is the length of the new string.
-+ * If the string is truncated, the function returns -E2BIG.
-+ */
-+
-+int stprintf(char *buf, size_t size, const char *fmt, ...)
-+{
-+ va_list args;
-+ int len;
-+
-+ va_start(args, fmt);
-+ len = vstprintf(buf, size, fmt, args);
-+ va_end(args);
-+
-+ return len;
-+}
-+EXPORT_SYMBOL(stprintf);
-+
- /**
- * scnprintf - Format a string and place it in a buffer
- * @buf: The buffer to place the result into
@@ lib/vsprintf.c: int scnprintf(char *buf, size_t size, const char *fmt, ...)
}
EXPORT_SYMBOL(scnprintf);
+/**
-+ * seprintf - Format a string and place it in a buffer
++ * sprintf_end - string end-delimited print formatted
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
@@ lib/vsprintf.c: int scnprintf(char *buf, size_t size, const char *fmt, ...)
+ * The return value is a pointer to the trailing '\0'.
+ * If @buf is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
++ * If @end <= @p, the function returns NULL.
+ */
+
-+char *seprintf(char *p, const char end[0], const char *fmt, ...)
++char *sprintf_end(char *p, const char end[0], const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
-+ p = vseprintf(p, end, fmt, args);
++ p = vsprintf_end(p, end, fmt, args);
+ va_end(args);
+
+ return p;
+}
-+EXPORT_SYMBOL(seprintf);
++EXPORT_SYMBOL(sprintf_end);
+
/**
* vsprintf - Format a string and place it in a buffer
2: 9c140de9842d ! 2: 894d02b08056 stacktrace, stackdepot: Add seprintf()-like variants of functions
@@ Metadata
Author: Alejandro Colomar <alx@kernel.org>
## Commit message ##
- stacktrace, stackdepot: Add seprintf()-like variants of functions
-
- I think there's an anomaly in stack_depot_s*print(). If we have zero
- entries, we don't copy anything, which means the string is still not a
- string. Normally, this function is called surrounded by other calls to
- s*printf(), which guarantee that there's a '\0', but maybe we should
- make sure to write a '\0' here?
+ stacktrace, stackdepot: Add sprintf_end()-like variants of functions
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
+ Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
+ Cc: Marco Elver <elver@google.com>
+ Cc: Michal Hocko <mhocko@suse.com>
+ Cc: Linus Torvalds <torvalds@linux-foundation.org>
+ Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
## include/linux/stackdepot.h ##
@@ include/linux/stackdepot.h: void stack_depot_print(depot_stack_handle_t stack);
int spaces);
+/**
-+ * stack_depot_seprint - Print a stack trace from stack depot into a buffer
++ * stack_depot_sprint_end - Print a stack trace from stack depot into a buffer
+ *
+ * @handle: Stack depot handle returned from stack_depot_save()
+ * @p: Pointer to the print buffer
@@ include/linux/stackdepot.h: void stack_depot_print(depot_stack_handle_t stack);
+ *
+ * Return: Pointer to trailing '\0'; or NULL on truncation
+ */
-+char *stack_depot_seprint(depot_stack_handle_t handle, char *p,
-+ const char end[0], int spaces);
++char *stack_depot_sprint_end(depot_stack_handle_t handle, char *p,
++ const char end[0], int spaces);
+
/**
* stack_depot_put - Drop a reference to a stack trace from stack depot
@@ include/linux/stacktrace.h: void stack_trace_print(const unsigned long *trace, u
int spaces);
int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
unsigned int nr_entries, int spaces);
-+char *stack_trace_seprint(char *p, const char end[0],
-+ const unsigned long *entries, unsigned int nr_entries,
-+ int spaces);
++char *stack_trace_sprint_end(char *p, const char end[0],
++ const unsigned long *entries,
++ unsigned int nr_entries, int spaces);
unsigned int stack_trace_save(unsigned long *store, unsigned int size,
unsigned int skipnr);
unsigned int stack_trace_save_tsk(struct task_struct *task,
@@ kernel/stacktrace.c: int stack_trace_snprint(char *buf, size_t size, const unsig
EXPORT_SYMBOL_GPL(stack_trace_snprint);
+/**
-+ * stack_trace_seprint - Print the entries in the stack trace into a buffer
++ * stack_trace_sprint_end - Print the entries in the stack trace into a buffer
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @entries: Pointer to storage array
@@ kernel/stacktrace.c: int stack_trace_snprint(char *buf, size_t size, const unsig
+ *
+ * Return: Pointer to the trailing '\0'; or NULL on truncation.
+ */
-+char *stack_trace_seprint(char *p, const char end[0],
++char *stack_trace_sprint_end(char *p, const char end[0],
+ const unsigned long *entries, unsigned int nr_entries,
+ int spaces)
+{
@@ kernel/stacktrace.c: int stack_trace_snprint(char *buf, size_t size, const unsig
+ return 0;
+
+ for (i = 0; i < nr_entries; i++) {
-+ p = seprintf(p, end, "%*c%pS\n", 1 + spaces, ' ',
++ p = sprintf_end(p, end, "%*c%pS\n", 1 + spaces, ' ',
+ (void *)entries[i]);
+ }
+
+ return p;
+}
-+EXPORT_SYMBOL_GPL(stack_trace_seprint);
++EXPORT_SYMBOL_GPL(stack_trace_sprint_end);
+
#ifdef CONFIG_ARCH_STACKWALK
@@ lib/stackdepot.c: int stack_depot_snprint(depot_stack_handle_t handle, char *buf
}
EXPORT_SYMBOL_GPL(stack_depot_snprint);
-+char *stack_depot_seprint(depot_stack_handle_t handle, char *p,
-+ const char end[0], int spaces)
++char *stack_depot_sprint_end(depot_stack_handle_t handle, char *p,
++ const char end[0], int spaces)
+{
+ unsigned long *entries;
+ unsigned int nr_entries;
+
+ nr_entries = stack_depot_fetch(handle, &entries);
-+ return nr_entries ? stack_trace_seprint(p, end, entries, nr_entries,
-+ spaces) : p;
++ return nr_entries ?
++ stack_trace_sprint_end(p, end, entries, nr_entries, spaces)
++ : sprintf_end(p, end, "");
+}
-+EXPORT_SYMBOL_GPL(stack_depot_seprint);
++EXPORT_SYMBOL_GPL(stack_depot_sprint_end);
+
depot_stack_handle_t __must_check stack_depot_set_extra_bits(
depot_stack_handle_t handle, unsigned int extra_bits)
3: 033bf00f1fcf ! 3: 690ed4d22f57 mm: Use seprintf() instead of less ergonomic APIs
@@ Metadata
Author: Alejandro Colomar <alx@kernel.org>
## Commit message ##
- mm: Use seprintf() instead of less ergonomic APIs
+ mm: Use sprintf_end() instead of less ergonomic APIs
While doing this, I detected some anomalies in the existing code:
@@ Commit message
This file uses the 'p += snprintf()' anti-pattern. That will
overflow the pointer on truncation, which has undefined
- behavior. Using seprintf(), this bug is fixed.
+ behavior. Using sprintf_end(), this bug is fixed.
As in the previous file, here there was also dead code in the
last scnprintf() call, by incrementing a pointer that is not
@@ Commit message
a good reason (i.e., we may want to avoid calling
print_page_owner_memcg() if we truncated before). Please review
if this amount of error handling is the right one, or if we want
- to add or remove some. For seprintf(), a single test for null
- after the last call is enough to detect truncation.
+ to add or remove some. For sprintf_end(), a single test for
+ null after the last call is enough to detect truncation.
mm/slub.c:
Again, the 'p += snprintf()' anti-pattern. This is UB, and by
- using seprintf() we've fixed the bug.
+ using sprintf_end() we've fixed the bug.
- Fixes: f99e12b21b84 (2021-07-30; "kfence: add function to mask address bits")
- [alx: that commit introduced dead code]
- Fixes: af649773fb25 (2024-07-17; "mm/numa_balancing: teach mpol_to_str about the balancing mode")
- [alx: that commit added p+=snprintf() calls, which are UB]
- Fixes: 2291990ab36b (2008-04-28; "mempolicy: clean-up mpol-to-str() mempolicy formatting")
- [alx: that commit changed p+=sprintf() into p+=snprintf(), which is still UB]
- Fixes: 948927ee9e4f (2013-11-13; "mm, mempolicy: make mpol_to_str robust and always succeed")
- [alx: that commit changes old code into p+=snprintf(), which is still UB]
- [alx: that commit also produced dead code by leaving the last 'p+=...']
- Fixes: d65360f22406 (2022-09-26; "mm/slub: clean up create_unique_id()")
- [alx: that commit changed p+=sprintf() into p+=snprintf(), which is still UB]
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Sven Schnelle <svens@linux.ibm.com>
Cc: Marco Elver <elver@google.com>
Cc: Heiko Carstens <hca@linux.ibm.com>
Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
- Cc: "Huang, Ying" <ying.huang@intel.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
- Cc: Lee Schermerhorn <lee.schermerhorn@hp.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: David Rientjes <rientjes@google.com>
Cc: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
Cc: Hyeonggon Yoo <42.hyeyoo@gmail.com>
Cc: Chao Yu <chao.yu@oppo.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
+ Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
+ Cc: Michal Hocko <mhocko@suse.com>
+ Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
## mm/kfence/kfence_test.c ##
@@ mm/kfence/kfence_test.c: static bool report_matches(const struct expect_report *
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds %s",
-+ cur = seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s",
++ cur = sprintf_end(cur, end, "BUG: KFENCE: out-of-bounds %s",
get_access_type(r));
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free %s",
-+ cur = seprintf(cur, end, "BUG: KFENCE: use-after-free %s",
++ cur = sprintf_end(cur, end, "BUG: KFENCE: use-after-free %s",
get_access_type(r));
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: memory corruption");
-+ cur = seprintf(cur, end, "BUG: KFENCE: memory corruption");
++ cur = sprintf_end(cur, end, "BUG: KFENCE: memory corruption");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid %s",
-+ cur = seprintf(cur, end, "BUG: KFENCE: invalid %s",
++ cur = sprintf_end(cur, end, "BUG: KFENCE: invalid %s",
get_access_type(r));
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid free");
-+ cur = seprintf(cur, end, "BUG: KFENCE: invalid free");
++ cur = sprintf_end(cur, end, "BUG: KFENCE: invalid free");
break;
}
- scnprintf(cur, end - cur, " in %pS", r->fn);
-+ seprintf(cur, end, " in %pS", r->fn);
++ sprintf_end(cur, end, " in %pS", r->fn);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expect[0], '+');
if (cur)
@@ mm/kfence/kfence_test.c: static bool report_matches(const struct expect_report *
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "Out-of-bounds %s at", get_access_type(r));
-+ cur = seprintf(cur, end, "Out-of-bounds %s at", get_access_type(r));
++ cur = sprintf_end(cur, end, "Out-of-bounds %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "Use-after-free %s at", get_access_type(r));
-+ cur = seprintf(cur, end, "Use-after-free %s at", get_access_type(r));
++ cur = sprintf_end(cur, end, "Use-after-free %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "Corrupted memory at");
-+ cur = seprintf(cur, end, "Corrupted memory at");
++ cur = sprintf_end(cur, end, "Corrupted memory at");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "Invalid %s at", get_access_type(r));
-+ cur = seprintf(cur, end, "Invalid %s at", get_access_type(r));
++ cur = sprintf_end(cur, end, "Invalid %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "Invalid free of");
-+ cur = seprintf(cur, end, "Invalid free of");
++ cur = sprintf_end(cur, end, "Invalid free of");
break;
}
- cur += scnprintf(cur, end - cur, " 0x%p", (void *)addr);
-+ seprintf(cur, end, " 0x%p", (void *)addr);
++ sprintf_end(cur, end, " 0x%p", (void *)addr);
spin_lock_irqsave(&observed.lock, flags);
if (!report_available())
@@ mm/kmsan/kmsan_test.c: static bool report_matches(const struct expect_report *r)
end = &expected_header[sizeof(expected_header) - 1];
- cur += scnprintf(cur, end - cur, "BUG: KMSAN: %s", r->error_type);
-+ cur = seprintf(cur, end, "BUG: KMSAN: %s", r->error_type);
++ cur = sprintf_end(cur, end, "BUG: KMSAN: %s", r->error_type);
- scnprintf(cur, end - cur, " in %s", r->symbol);
-+ seprintf(cur, end, " in %s", r->symbol);
++ sprintf_end(cur, end, " in %s", r->symbol);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expected_header, '+');
if (cur)
@@ mm/mempolicy.c: void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol
default:
WARN_ON_ONCE(1);
- snprintf(p, maxlen, "unknown");
-+ seprintf(p, e, "unknown");
++ sprintf_end(p, e, "unknown");
return;
}
- p += snprintf(p, maxlen, "%s", policy_modes[mode]);
-+ p = seprintf(p, e, "%s", policy_modes[mode]);
++ p = sprintf_end(p, e, "%s", policy_modes[mode]);
if (flags & MPOL_MODE_FLAGS) {
- p += snprintf(p, buffer + maxlen - p, "=");
-+ p = seprintf(p, e, "=");
++ p = sprintf_end(p, e, "=");
/*
* Static and relative are mutually exclusive.
*/
if (flags & MPOL_F_STATIC_NODES)
- p += snprintf(p, buffer + maxlen - p, "static");
-+ p = seprintf(p, e, "static");
++ p = sprintf_end(p, e, "static");
else if (flags & MPOL_F_RELATIVE_NODES)
- p += snprintf(p, buffer + maxlen - p, "relative");
-+ p = seprintf(p, e, "relative");
++ p = sprintf_end(p, e, "relative");
if (flags & MPOL_F_NUMA_BALANCING) {
if (!is_power_of_2(flags & MPOL_MODE_FLAGS))
- p += snprintf(p, buffer + maxlen - p, "|");
- p += snprintf(p, buffer + maxlen - p, "balancing");
-+ p = seprintf(p, e, "|");
-+ p = seprintf(p, e, "balancing");
++ p = sprintf_end(p, e, "|");
++ p = sprintf_end(p, e, "balancing");
}
}
if (!nodes_empty(nodes))
- p += scnprintf(p, buffer + maxlen - p, ":%*pbl",
- nodemask_pr_args(&nodes));
-+ seprintf(p, e, ":%*pbl", nodemask_pr_args(&nodes));
++ sprintf_end(p, e, ":%*pbl", nodemask_pr_args(&nodes));
}
#ifdef CONFIG_SYSFS
@@ mm/page_owner.c: static inline int print_page_owner_memcg(char *kbuf, size_t cou
if (memcg_data & MEMCG_DATA_OBJEXTS)
- ret += scnprintf(kbuf + ret, count - ret,
- "Slab cache page\n");
-+ p = seprintf(p, end, "Slab cache page\n");
++ p = sprintf_end(p, end, "Slab cache page\n");
memcg = page_memcg_check(page);
if (!memcg)
@@ mm/page_owner.c: static inline int print_page_owner_memcg(char *kbuf, size_t cou
online = (memcg->css.flags & CSS_ONLINE);
cgroup_name(memcg->css.cgroup, name, sizeof(name));
- ret += scnprintf(kbuf + ret, count - ret,
-+ p = seprintf(p, end,
++ p = sprintf_end(p, end,
"Charged %sto %smemcg %s\n",
PageMemcgKmem(page) ? "(via objcg) " : "",
online ? "" : "offline ",
@@ mm/page_owner.c: print_page_owner(char __user *buf, size_t count, unsigned long
- ret = scnprintf(kbuf, count,
+ p = kbuf;
+ e = kbuf + count;
-+ p = seprintf(p, e,
++ p = sprintf_end(p, e,
"Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n",
page_owner->order, page_owner->gfp_mask,
&page_owner->gfp_mask, page_owner->pid,
@@ mm/page_owner.c: print_page_owner(char __user *buf, size_t count, unsigned long
pageblock_mt = get_pageblock_migratetype(page);
page_mt = gfp_migratetype(page_owner->gfp_mask);
- ret += scnprintf(kbuf + ret, count - ret,
-+ p = seprintf(p, e,
++ p = sprintf_end(p, e,
"PFN 0x%lx type %s Block %lu type %s Flags %pGp\n",
pfn,
migratetype_names[page_mt],
@@ mm/page_owner.c: print_page_owner(char __user *buf, size_t count, unsigned long
- ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
- if (ret >= count)
- goto err;
-+ p = stack_depot_seprint(handle, p, e, 0);
++ p = stack_depot_sprint_end(handle, p, e, 0);
+ if (p == NULL)
+ goto err; // XXX: Should we remove this error handling?
if (page_owner->last_migrate_reason != -1) {
- ret += scnprintf(kbuf + ret, count - ret,
-+ p = seprintf(p, e,
++ p = sprintf_end(p, e,
"Page has been migrated, last migrate reason: %s\n",
migrate_reason_names[page_owner->last_migrate_reason]);
}
@@ mm/page_owner.c: print_page_owner(char __user *buf, size_t count, unsigned long
- ret += snprintf(kbuf + ret, count - ret, "\n");
- if (ret >= count)
-+ p = seprintf(p, e, "\n");
++ p = sprintf_end(p, e, "\n");
+ if (p == NULL)
goto err;
@@ mm/slub.c: static char *create_unique_id(struct kmem_cache *s)
if (p != name + 1)
*p++ = '-';
- p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
-+ p = seprintf(p, e, "%07u", s->size);
++ p = sprintf_end(p, e, "%07u", s->size);
- if (WARN_ON(p > name + ID_STR_LENGTH - 1)) {
+ if (WARN_ON(p == NULL)) {
4: d8bd0e1d308b ! 4: e05c5afabb3c array_size.h: Add ENDOF()
@@ Metadata
## Commit message ##
array_size.h: Add ENDOF()
- This macro is useful to calculate the second argument to seprintf(),
+ This macro is useful to calculate the second argument to sprintf_end(),
avoiding off-by-one bugs.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
+ Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
+ Cc: Marco Elver <elver@google.com>
+ Cc: Michal Hocko <mhocko@suse.com>
+ Cc: Linus Torvalds <torvalds@linux-foundation.org>
+ Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
## include/linux/array_size.h ##
5: 740755c1a888 ! 5: 44a5cfc82acf mm: Fix benign off-by-one bugs
@@ Commit message
'end' --that is, at most the terminating null byte will be written at
'end-1'--.
- Fixes: bc8fbc5f305a (2021-02-26; "kfence: add test suite")
- Fixes: 8ed691b02ade (2022-10-03; "kmsan: add tests for KMSAN")
+ Acked-by: Marco Elver <elver@google.com>
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Alexander Potapenko <glider@google.com>
- Cc: Marco Elver <elver@google.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Jann Horn <jannh@google.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
+ Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
+ Cc: Marco Elver <elver@google.com>
+ Cc: Michal Hocko <mhocko@suse.com>
+ Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
## mm/kfence/kfence_test.c ##
@@ mm/kfence/kfence_test.c: static bool report_matches(const struct expect_report *
+ end = ENDOF(expect[0]);
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur = seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s",
+ cur = sprintf_end(cur, end, "BUG: KFENCE: out-of-bounds %s",
@@ mm/kfence/kfence_test.c: static bool report_matches(const struct expect_report *r)
/* Access information */
@@ mm/kmsan/kmsan_test.c: static bool report_matches(const struct expect_report *r)
- end = &expected_header[sizeof(expected_header) - 1];
+ end = ENDOF(expected_header);
- cur = seprintf(cur, end, "BUG: KMSAN: %s", r->error_type);
+ cur = sprintf_end(cur, end, "BUG: KMSAN: %s", r->error_type);
6: 44d05559398c ! 6: 0314948eb225 sprintf: Add [V]STPRINTF()
@@ Metadata
Author: Alejandro Colomar <alx@kernel.org>
## Commit message ##
- sprintf: Add [V]STPRINTF()
+ sprintf: Add [V]SPRINTF_END()
- These macros take the array size argument implicitly to avoid programmer
- mistakes. This guarantees that the input is an array, unlike the common
- call
+ These macros take the end of the array argument implicitly to avoid
+ programmer mistakes. This guarantees that the input is an array, unlike
snprintf(buf, sizeof(buf), ...);
- which is dangerous if the programmer passes a pointer.
+ which is dangerous if the programmer passes a pointer instead of an
+ array.
These macros are essentially the same as the 2-argument version of
- strscpy(), but with a formatted string.
+ strscpy(), but with a formatted string, and returning a pointer to the
+ terminating '\0' (or NULL, on error).
+ Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
+ Cc: Marco Elver <elver@google.com>
+ Cc: Michal Hocko <mhocko@suse.com>
+ Cc: Linus Torvalds <torvalds@linux-foundation.org>
+ Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
## include/linux/sprintf.h ##
@@ include/linux/sprintf.h
#include <linux/types.h>
+#include <linux/array_size.h>
+
-+#define STPRINTF(a, fmt, ...) stprintf(a, ARRAY_SIZE(a), fmt, ##__VA_ARGS__)
-+#define VSTPRINTF(a, fmt, ap) vstprintf(a, ARRAY_SIZE(a), fmt, ap)
++#define SPRINTF_END(a, fmt, ...) sprintf_end(a, ENDOF(a), fmt, ##__VA_ARGS__)
++#define VSPRINTF_END(a, fmt, ap) vsprintf_end(a, ENDOF(a), fmt, ap)
int num_to_str(char *buf, int size, unsigned long long num, unsigned int width);
7: d0e95db3c80a ! 7: f99632f42eee mm: Use [V]STPRINTF() to avoid specifying the array size
@@ Metadata
Author: Alejandro Colomar <alx@kernel.org>
## Commit message ##
- mm: Use [V]STPRINTF() to avoid specifying the array size
+ mm: Use [V]SPRINTF_END() to avoid specifying the array size
+ Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
+ Cc: Marco Elver <elver@google.com>
+ Cc: Michal Hocko <mhocko@suse.com>
+ Cc: Linus Torvalds <torvalds@linux-foundation.org>
+ Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
## mm/backing-dev.c ##
@@ mm/backing-dev.c: int bdi_register_va(struct backing_dev_info *bdi, const char *
return 0;
- vsnprintf(bdi->dev_name, sizeof(bdi->dev_name), fmt, args);
-+ VSTPRINTF(bdi->dev_name, fmt, args);
++ VSPRINTF_END(bdi->dev_name, fmt, args);
dev = device_create(&bdi_class, NULL, MKDEV(0, 0), bdi, bdi->dev_name);
if (IS_ERR(dev))
return PTR_ERR(dev);
@@ mm/cma.c: static int __init cma_new_area(const char *name, phys_addr_t size,
if (name)
- snprintf(cma->name, CMA_MAX_NAME, "%s", name);
-+ STPRINTF(cma->name, "%s", name);
++ SPRINTF_END(cma->name, "%s", name);
else
- snprintf(cma->name, CMA_MAX_NAME, "cma%d\n", cma_area_count);
-+ STPRINTF(cma->name, "cma%d\n", cma_area_count);
++ SPRINTF_END(cma->name, "cma%d\n", cma_area_count);
cma->available_count = cma->count = size >> PAGE_SHIFT;
cma->order_per_bit = order_per_bit;
@@ mm/cma_debug.c: static void cma_debugfs_add_one(struct cma *cma, struct dentry *
for (r = 0; r < cma->nranges; r++) {
cmr = &cma->ranges[r];
- snprintf(rdirname, sizeof(rdirname), "%d", r);
-+ STPRINTF(rdirname, "%d", r);
++ SPRINTF_END(rdirname, "%d", r);
dir = debugfs_create_dir(rdirname, rangedir);
debugfs_create_file("base_pfn", 0444, dir,
&cmr->base_pfn, &cma_debugfs_fops);
@@ mm/hugetlb.c: void __init hugetlb_add_hstate(unsigned int order)
INIT_LIST_HEAD(&h->hugepage_activelist);
- snprintf(h->name, HSTATE_NAME_LEN, "hugepages-%lukB",
- huge_page_size(h)/SZ_1K);
-+ STPRINTF(h->name, "hugepages-%lukB", huge_page_size(h)/SZ_1K);
++ SPRINTF_END(h->name, "hugepages-%lukB", huge_page_size(h)/SZ_1K);
parsed_hstate = h;
}
@@ mm/hugetlb_cgroup.c: hugetlb_cgroup_cfttypes_init(struct hstate *h, struct cftyp
*cft = *tmpl;
/* rebuild the name */
- snprintf(cft->name, MAX_CFTYPE_NAME, "%s.%s", buf, tmpl->name);
-+ STPRINTF(cft->name, "%s.%s", buf, tmpl->name);
++ SPRINTF_END(cft->name, "%s.%s", buf, tmpl->name);
/* rebuild the private */
cft->private = MEMFILE_PRIVATE(idx, tmpl->private);
/* rebuild the file_offset */
@@ mm/hugetlb_cma.c: void __init hugetlb_cma_reserve(int order)
size = round_up(size, PAGE_SIZE << order);
- snprintf(name, sizeof(name), "hugetlb%d", nid);
-+ STPRINTF(name, "hugetlb%d", nid);
++ SPRINTF_END(name, "hugetlb%d", nid);
/*
* Note that 'order per bit' is based on smallest size that
* may be returned to CMA allocator in the case of
@@ mm/kasan/report.c: static void print_memory_metadata(const void *addr)
- snprintf(buffer, sizeof(buffer),
- (i == 0) ? ">%px: " : " %px: ", row);
-+ STPRINTF(buffer, (i == 0) ? ">%px: " : " %px: ", row);
++ SPRINTF_END(buffer, (i == 0) ? ">%px: " : " %px: ", row);
/*
* We should not pass a shadow pointer to generic
@@ mm/memblock.c: static void __init_memblock memblock_dump(struct memblock_type *t
#ifdef CONFIG_NUMA
if (numa_valid_node(memblock_get_region_node(rgn)))
- snprintf(nid_buf, sizeof(nid_buf), " on node %d",
-+ STPRINTF(nid_buf, " on node %d",
++ SPRINTF_END(nid_buf, " on node %d",
memblock_get_region_node(rgn));
#endif
pr_info(" %s[%#x]\t[%pa-%pa], %pa bytes%s flags: %#x\n",
@@ mm/memblock.c: int reserve_mem_release_by_name(const char *name)
start = phys_to_virt(map->start);
end = start + map->size - 1;
- snprintf(buf, sizeof(buf), "reserve_mem:%s", name);
-+ STPRINTF(buf, "reserve_mem:%s", name);
++ SPRINTF_END(buf, "reserve_mem:%s", name);
free_reserved_area(start, end, 0, buf);
map->size = 0;
@@ mm/percpu.c: int __init pcpu_page_first_chunk(size_t reserved_size, pcpu_fc_cpu_
int nr_g0_units;
- snprintf(psize_str, sizeof(psize_str), "%luK", PAGE_SIZE >> 10);
-+ STPRINTF(psize_str, "%luK", PAGE_SIZE >> 10);
++ SPRINTF_END(psize_str, "%luK", PAGE_SIZE >> 10);
ai = pcpu_build_alloc_info(reserved_size, 0, PAGE_SIZE, NULL);
if (IS_ERR(ai))
@@ mm/shrinker_debug.c: int shrinker_debugfs_add(struct shrinker *shrinker)
shrinker->debugfs_id = id;
- snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
-+ STPRINTF(buf, "%s-%d", shrinker->name, id);
++ SPRINTF_END(buf, "%s-%d", shrinker->name, id);
/* create debugfs entry */
entry = debugfs_create_dir(buf, shrinker_debugfs_root);
@@ mm/zswap.c: static struct zswap_pool *zswap_pool_create(char *type, char *compre
/* unique name for each pool specifically required by zsmalloc */
- snprintf(name, 38, "zswap%x", atomic_inc_return(&zswap_pools_count));
-+ STPRINTF(name, "zswap%x", atomic_inc_return(&zswap_pools_count));
++ SPRINTF_END(name, "zswap%x", atomic_inc_return(&zswap_pools_count));
pool->zpool = zpool_create_pool(type, name, gfp);
if (!pool->zpool) {
pr_err("%s zpool not available\n", type);
--
2.50.0
^ permalink raw reply [flat|nested] 98+ messages in thread
* alx-0049r2 - add seprintf()
2025-07-10 2:47 ` [RFC v4 0/7] Add and use sprintf_end() " Alejandro Colomar
@ 2025-07-10 2:47 ` Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 1/7] vsprintf: Add [v]sprintf_end() Alejandro Colomar
` (6 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 2:47 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
Hi,
Below is a draft of the proposal I'll submit in a few weeks to the
C Committee.
Cheers,
Alex
---
Name
alx-0049r2 - add seprintf()
Principles
- Codify existing practice to address evident deficiencies.
- Enable secure programming
Category
Standardize existing libc APIs
Author
Alejandro Colomar <alx@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
History
<https://www.alejandro-colomar.es/src/alx/alx/wg14/alx-0049.git/>
r0 (2025-07-06):
- Initial draft.
r1 (2025-07-06):
- wfix.
- tfix.
- Expand on the off-by-one bugs.
- Note that ignoring truncation is not valid most of the time.
r2 (2025-07-10):
- tfix.
- Mention SEPRINTF().
Rationale
snprintf(3) is very difficult to chain for writing parts of a
string in separate calls, such as in a loop.
Let's start from the obvious sprintf(3) code (sprintf(3) will
not prevent overflow, but let's take it as a baseline from which
programmers start thinking):
p = buf;
for (...)
p += sprintf(p, ...);
Then, programmers will start thinking about preventing buffer
overflows. Programmers sometimes will naively add some buffer
size information and use snprintf(3):
p = buf;
size = countof(buf);
for (...)
p += snprintf(p, size - (p - buf), ...);
if (p >= buf + size) // or worse, (p > buf + size - 1)
goto fail;
(Except for minor differences, this kind of code can be found
everywhere. Here are a couple of examples:
<https://elixir.bootlin.com/linux/v6.14/source/mm/slub.c#L7231>
<https://elixir.bootlin.com/linux/v6.14/source/mm/mempolicy.c#L3369>.)
This has several issues, starting with the difficulty of getting
the second argument right. Sometimes, programmers will be too
confused, and slap a -1 there just to be safe.
p = buf;
size = countof(buf);
for (...)
p += snprintf(p, size - (p - buf) - 1, ...);
if (p >= buf + size -1)
goto fail;
(Except for minor differences, this kind of code can be found
everywhere. Here are a couple of examples:
<https://elixir.bootlin.com/linux/v6.14/source/mm/kfence/kfence_test.c#L113>
<https://elixir.bootlin.com/linux/v6.14/source/mm/kmsan/kmsan_test.c#L108>.)
Programmers will sometimes hold a pointer to one past the last
element in the array. This is a wise choice, as that pointer is
constant throughout the lifetime of the object. Then,
programmers might end up with something like this:
p = buf;
e = buf + countof(buf);
for (...)
p += snprintf(p, e - p, ...);
if (p >= e)
goto fail;
This is certainly much cleaner. Now a programmer might focus on
the fact that this can overflow the pointer. An easy approach
would be to make sure that the function never returns more than
the remaining size. That is, one could implement something like
this scnprintf() --name chosen to match the Linux kernel API of
the same name--. For the sake of simplicity, let's ignore
multiple evaluation of arguments.
#define scnprintf(s, size, ...) \
({ \
int len_; \
len_ = snprintf(s, size, __VA_ARGS__); \
if (len_ == -1) \
len_ = 0; \
if (len_ >= size) \
len_ = size - 1; \
\
len_; \
})
p = buf;
e = buf + countof(buf);
for (...)
p += scnprintf(p, e - p, ...);
(Except for minor differences, this kind of code can be found
everywhere. Here's an example:
<https://elixir.bootlin.com/linux/v6.14/source/mm/kfence/kfence_test.c#L131>.)
Now the programmer got rid of pointer overflow. However, they
now have silent truncation that cannot be detected. In some
cases this may seem good enough. However, often it's not. And
anyway, some code remains using snprintf(3) to be able to detect
truncation.
Moreover, this kind of code ignores the fact that vsnprintf(3)
can fail internally, in which case there's not even a truncated
string. In the kernel, they're fine, because their internal
vsnprintf() doesn't seem to ever fail, so they can always rely
on the truncated string. This is not reliable in projects that
rely on the libc vsnprintf(3).
For the code that needs to detect truncation, a programmer might
choose a different path. It would keep using snprintf(3), but
would use a temporary length variable instead of the pointer.
p = buf;
e = buf + countof(buf);
for (...) {
len = snprintf(p, e - p, ...);
if (len == -1)
goto fail;
if (len >= e - p)
goto fail;
p += len;
}
This is naturally error-prone. A colleague of mine --which is an
excellent programmer, to be clear--, had a bug even after
knowing about it and having tried to fix it. That shows how
hard it is to write this correctly:
<https://github.com/nginx/unit/pull/734#discussion_r1043963527>
In a similar fashion, the strlcpy(3) manual page from OpenBSD
documents a similar issue when chaining calls to strlcpy(3)
--which was designed with semantics equivalent to snprintf(3),
except for not formatting the string--:
| char *dir, *file, pname[MAXPATHLEN];
| size_t n;
|
| ...
|
| n = strlcpy(pname, dir, sizeof(pname));
| if (n >= sizeof(pname))
| goto toolong;
| if (strlcpy(pname + n, file, sizeof(pname) - n) >= sizeof(pname) - n)
| goto toolong;
|
| However, one may question the validity of such optimiza‐
| tions, as they defeat the whole purpose of strlcpy() and
| strlcat(). As a matter of fact, the first version of
| this manual page got it wrong.
Finally, a programmer might realize that while this is error-
prone, this is indeed the right thing to do. There's no way to
avoid it. One could then think of encapsulating this into an
API that at least would make it easy to write. Then, one might
wonder what the right parameters are for such an API. The only
immutable thing in the loop is 'e'. And apart from that, one
needs to know where to write, which is 'p'. Let's start with
those, and try to keep all the other information (size, len)
without escaping the API. Again, let's ignore multiple-
evaluation issues in this macro for the sake of simplicity.
#define foo(p, e, ...) \
({ \
int len_ = snprintf(p, e - p, __VA_ARGS__); \
if (len_ == -1) \
p = NULL; \
else if (len_ >= e - p) \
p = NULL; \
else \
p += len_; \
p;
})
p = buf;
e = buf + countof(buf);
for (...) {
p = foo(p, e, ...);
if (p == NULL)
goto fail;
}
We've advanced a lot. We got rid of the buffer overflow; we
also got rid of the error-prone code at call site. However, one
might think that checking for truncation after every call is
cumbersome. Indeed, it is possible to slightly tweak the
internals of foo() to propagate errors from previous calls.
#define seprintf(p, e, ...) \
({ \
if (p != NULL) { \
int len_; \
\
len_ = snprintf(p, e - p, __VA_ARGS__); \
if (len_ == -1) \
p = NULL; \
else if (len_ >= e - p) \
p = NULL; \
else \
p += len_; \
} \
p; \
})
p = buf;
e = buf + countof(buf);
for (...)
p = seprintf(p, e, ...);
if (p == NULL)
goto fail;
By propagating an input null pointer directly to the output of
the API, which I've called seprintf() --the 'e' refers to the
'end' pointer, which is the key in this API--, we've allowed
ignoring null pointers until after the very last call. If we
compare our resulting code to the sprintf(3)-based baseline, we
got --perhaps unsurprisingly-- something quite close to it:
p = buf;
for (...)
p += sprintf(p, ...);
vs
p = buf;
e = buf + countof(buf);
for (...)
p = seprintf(p, e, ...);
if (p == NULL)
goto fail;
And the seprintf() version is safe against both truncation and
buffer overflow.
For the case where there is only one call to this function (so
not chained), and the buffer is an array, an even more ergonomic
wrapper can be written, and it is recommended that projects
define this macro themselves:
#define SEPRINTF(a, fmt, ...) \
seprintf(a, a + countof(a), fmt, __VA_ARGS__)
This adds some safety guarantees that $2 is calculated correctly
when it can be automated. Correct use would look like
if (SEPRINTF(buf, "foo") == NULL)
goto fail;
Some important details of the seprintf() API are:
- When 'p' is NULL, the API must preserve errno. This is
important to be able to determine the cause of the error
after all the chained calls, even when the error occurred in
some call in the middle of the chain.
- When truncation occurs, a distinct errno value must be used,
to signal the programmer that at least the string is reliable
to be used as a null-terminated string. The error code
chosen is E2BIG, for compatibility with strscpy(), a Linux
kernel internal API with which this API shares many features
in common.
- When a hard error (an internal snprintf(3) error) occurs, an
error code different than E2BIG must be used. It is
important to set errno, because if an implementation would
chose to return NULL without setting errno, an old value of
E2BIG could lead the programmer to believe the string was
successfully written (and truncated), and read it with
nefast consequences.
Prior art
This API is implemented in the shadow-utils project.
Plan9 designed something quite close, which they call
seprint(2). The parameters are the same --the right choice--,
but they got the semantics for corner cases wrong. Ironically,
the existing Plan9 code I've seen seems to expect the semantics
that I chose, regardless of the actual semantics of the Plan9
API. This is --I suspect--, because my semantics are actually
the intuitive semantics that one would naively guess of an API
with these parameters and return value.
I've implemented this API for the Linux kernel, and found and
fixed an amazing amount of bugs and other questionable code in
just the first handful of files that I inspected.
<https://lore.kernel.org/linux-hardening/cover.1751747518.git.alx@kernel.org/T/#t>
<https://lore.kernel.org/linux-hardening/cover.1751823326.git.alx@kernel.org/T/#t>
Future directions
The 'e = buf + _Countof(buf)' construct is something I've found
to be quite common. It would be interesting to have an
_Endof operator that would return a pointer to one past the last
element of an array. It would require an array operand, just
like _Countof. If an _Endof operator is deemed too cumbersome
for implementation, an endof() standard macro that expands to
the obvious implementation with _Countof could be okay.
This operator (or operator-like macro) would prevent off-by-one
bugs when calculating the end sentinel value, such as those
shown above (with links to Linux kernel real bugs).
Proposed wording
Based on N3550.
7.24.6 Input/output <stdio.h> :: Formatted input/output functions
## New section after 7.24.6.6 ("The snprintf function"):
+7.24.6.6+1 The <b>seprintf</b> function
+
+Synopsis
+1 #include <stdio.h>
+ char *seprintf(char *restrict p, const char end[0], const char *restrict format, ...);
+
+Description
+2 The <b>$0</b> function
+ is equivalent to <b>fprintf</b>,
+ except that the output is written into an array
+ (specified by argument <tt>p</tt>)
+ rather than a stream.
+ If <tt>p</tt> is a null pointer,
+ nothing is written,
+ and the function returns a null pointer.
+ Otherwise,
+ <tt>end</tt> shall compare greater than <tt>p</tt>;
+ the function writes at most
+ <tt>end - p - 1</tt> non-null characters,
+ the remaining output characters are discarded,
+ and a null character is written
+ at the end of the characters
+ actually written to the array.
+ If copying takes place between objects that overlap,
+ the behavior is undefined.
+
+Returns
+3 The <b>$0</b> function returns
+ a pointer to the terminating null character
+ if the output was written
+ without discarding any characters.
+
+4
+ If <tt>p</tt> is a null pointer,
+ a null pointer is returned,
+ and <b>errno</b> is not modified.
+
+5
+ If any characters are discarded,
+ a null pointer is returned,
+ and the value of the macro <b>E2BIG</b>
+ is stored in <b>errno</b>.
+
+6
+ If an error occurred,
+ a null pointer is returned,
+ and an implementation-defined non-zero value
+ is stored in <b>errno</b>.
## New section after 7.24.6.13 ("The vsnprintf function"):
+7.24.6.13+1 The <b>vseprintf</b> function
+
+Synopsis
+1 #include <stdio.h>
+ char *vseprintf(char *restrict p, const char end[0], const char *restrict format, va_list arg);
+
+Description
+2 The <b>$0</b> function
+ is equivalent to
+ <b>seprintf</b>,
+ with the varying argument list replaced by <tt>arg</tt>.
+
+3
+ The <tt>va_list</tt> argument to this function
+ shall have been initialized by the <b>va_start</b> macro
+ (and possibly subsequent <b>va_arg</b> invocations).
+ This function does not invoke the <b>va_end</b> macro.343)
7.33.2 Formatted wide character input/output functions
## New section after 7.33.2.4 ("The swprintf function"):
+7.33.2.4+1 The <b>sewprintf</b> function
+
+Synopsis
+1 #include <wchar.h>
+ wchar_t *sewprintf(wchar_t *restrict p, const wchar_t end[0], const wchar_t *restrict format, ...);
+
+Description
+2 The <b>$0</b> function
+ is equivalent to
+ <b>seprintf</b>,
+ except that it handles wide strings.
## New section after 7.33.2.8 ("The vswprintf function"):
+7.33.2.8+1 The <b>vsewprintf</b> function
+
+Synopsis
+1 #include <wchar.h>
+ wchar_t *vsewprintf(wchar_t *restrict p, const wchar_t end[0], const wchar_t *restrict format, va_list arg);
+
+Description
+2 The <b>$0</b> function
+ is equivalent to
+ <b>sewprintf</b>,
+ with the varying argument list replaced by <tt>arg</tt>.
+
+3
+ The <tt>va_list</tt> argument to this function
+ shall have been initialized by the <b>va_start</b> macro
+ (and possibly subsequent <b>va_arg</b> invocations).
+ This function does not invoke the <b>va_end</b> macro.407)
^ permalink raw reply [flat|nested] 98+ messages in thread
* [RFC v4 1/7] vsprintf: Add [v]sprintf_end()
2025-07-10 2:47 ` [RFC v4 0/7] Add and use sprintf_end() " Alejandro Colomar
2025-07-10 2:47 ` alx-0049r2 - add seprintf() Alejandro Colomar
@ 2025-07-10 2:48 ` Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 2/7] stacktrace, stackdepot: Add sprintf_end()-like variants of functions Alejandro Colomar
` (5 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 2:48 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
sprintf_end() is a function similar to stpcpy(3) in the sense that it
returns a pointer that is suitable for chaining to other copy
operations.
It takes a pointer to the end of the buffer as a sentinel for when to
truncate, which unlike a size, doesn't need to be updated after every
call. This makes it much more ergonomic, avoiding manually calculating
the size after each copy, which is error prone.
It also makes error handling much easier, by reporting truncation with
a null pointer, which is accepted and transparently passed down by
subsequent sprintf_end() calls. This results in only needing to report
errors once after a chain of sprintf_end() calls, unlike snprintf(3),
which requires checking after every call.
p = buf;
e = buf + countof(buf);
p = sprintf_end(p, e, foo);
p = sprintf_end(p, e, bar);
if (p == NULL)
goto trunc;
vs
len = 0;
size = countof(buf);
len += snprintf(buf + len, size - len, foo);
if (len >= size)
goto trunc;
len += snprintf(buf + len, size - len, bar);
if (len >= size)
goto trunc;
And also better than scnprintf() calls:
len = 0;
size = countof(buf);
len += scnprintf(buf + len, size - len, foo);
len += scnprintf(buf + len, size - len, bar);
// No ability to check.
It seems aparent that it's a more elegant approach to string catenation.
These functions will soon be proposed for standardization as
[v]seprintf() into C2y, and they exist in Plan9 as seprint(2) --but the
Plan9 implementation has important bugs--.
Link: <https://www.alejandro-colomar.es/src/alx/alx/wg14/alx-0049.git/tree/alx-0049.txt>
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/sprintf.h | 2 ++
lib/vsprintf.c | 59 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+)
diff --git a/include/linux/sprintf.h b/include/linux/sprintf.h
index 51cab2def9ec..a0dc35574521 100644
--- a/include/linux/sprintf.h
+++ b/include/linux/sprintf.h
@@ -13,6 +13,8 @@ __printf(3, 4) int snprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
__printf(3, 4) int scnprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
+__printf(3, 4) char *sprintf_end(char *p, const char end[0], const char *fmt, ...);
+__printf(3, 0) char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args);
__printf(2, 3) __malloc char *kasprintf(gfp_t gfp, const char *fmt, ...);
__printf(2, 0) __malloc char *kvasprintf(gfp_t gfp, const char *fmt, va_list args);
__printf(2, 0) const char *kvasprintf_const(gfp_t gfp, const char *fmt, va_list args);
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 01699852f30c..d32df53a713a 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -2923,6 +2923,40 @@ int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
}
EXPORT_SYMBOL(vscnprintf);
+/**
+ * vsprintf_end - va_list string end-delimited print formatted
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
+ *
+ * The return value is a pointer to the trailing '\0'.
+ * If @p is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
+ * If @end <= @p, the function returns NULL.
+ *
+ * See the vsnprintf() documentation for format string extensions over C99.
+ */
+char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args)
+{
+ int len;
+ size_t size;
+
+ if (unlikely(p == NULL))
+ return NULL;
+
+ size = end - p;
+ if (WARN_ON_ONCE(size == 0 || size > INT_MAX))
+ return NULL;
+
+ len = vsnprintf(p, size, fmt, args);
+ if (unlikely(len >= size))
+ return NULL;
+
+ return p + len;
+}
+EXPORT_SYMBOL(vsprintf_end);
+
/**
* snprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -2974,6 +3008,31 @@ int scnprintf(char *buf, size_t size, const char *fmt, ...)
}
EXPORT_SYMBOL(scnprintf);
+/**
+ * sprintf_end - string end-delimited print formatted
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
+ *
+ * The return value is a pointer to the trailing '\0'.
+ * If @buf is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
+ * If @end <= @p, the function returns NULL.
+ */
+
+char *sprintf_end(char *p, const char end[0], const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ p = vsprintf_end(p, end, fmt, args);
+ va_end(args);
+
+ return p;
+}
+EXPORT_SYMBOL(sprintf_end);
+
/**
* vsprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v4 2/7] stacktrace, stackdepot: Add sprintf_end()-like variants of functions
2025-07-10 2:47 ` [RFC v4 0/7] Add and use sprintf_end() " Alejandro Colomar
2025-07-10 2:47 ` alx-0049r2 - add seprintf() Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 1/7] vsprintf: Add [v]sprintf_end() Alejandro Colomar
@ 2025-07-10 2:48 ` Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 3/7] mm: Use sprintf_end() instead of less ergonomic APIs Alejandro Colomar
` (4 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 2:48 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/stackdepot.h | 13 +++++++++++++
include/linux/stacktrace.h | 3 +++
kernel/stacktrace.c | 28 ++++++++++++++++++++++++++++
lib/stackdepot.c | 13 +++++++++++++
4 files changed, 57 insertions(+)
diff --git a/include/linux/stackdepot.h b/include/linux/stackdepot.h
index 2cc21ffcdaf9..76182e874f67 100644
--- a/include/linux/stackdepot.h
+++ b/include/linux/stackdepot.h
@@ -219,6 +219,19 @@ void stack_depot_print(depot_stack_handle_t stack);
int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
int spaces);
+/**
+ * stack_depot_sprint_end - Print a stack trace from stack depot into a buffer
+ *
+ * @handle: Stack depot handle returned from stack_depot_save()
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @spaces: Number of leading spaces to print
+ *
+ * Return: Pointer to trailing '\0'; or NULL on truncation
+ */
+char *stack_depot_sprint_end(depot_stack_handle_t handle, char *p,
+ const char end[0], int spaces);
+
/**
* stack_depot_put - Drop a reference to a stack trace from stack depot
*
diff --git a/include/linux/stacktrace.h b/include/linux/stacktrace.h
index 97455880ac41..79ada795d479 100644
--- a/include/linux/stacktrace.h
+++ b/include/linux/stacktrace.h
@@ -67,6 +67,9 @@ void stack_trace_print(const unsigned long *trace, unsigned int nr_entries,
int spaces);
int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
unsigned int nr_entries, int spaces);
+char *stack_trace_sprint_end(char *p, const char end[0],
+ const unsigned long *entries,
+ unsigned int nr_entries, int spaces);
unsigned int stack_trace_save(unsigned long *store, unsigned int size,
unsigned int skipnr);
unsigned int stack_trace_save_tsk(struct task_struct *task,
diff --git a/kernel/stacktrace.c b/kernel/stacktrace.c
index afb3c116da91..f389647d8e44 100644
--- a/kernel/stacktrace.c
+++ b/kernel/stacktrace.c
@@ -70,6 +70,34 @@ int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
}
EXPORT_SYMBOL_GPL(stack_trace_snprint);
+/**
+ * stack_trace_sprint_end - Print the entries in the stack trace into a buffer
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @entries: Pointer to storage array
+ * @nr_entries: Number of entries in the storage array
+ * @spaces: Number of leading spaces to print
+ *
+ * Return: Pointer to the trailing '\0'; or NULL on truncation.
+ */
+char *stack_trace_sprint_end(char *p, const char end[0],
+ const unsigned long *entries, unsigned int nr_entries,
+ int spaces)
+{
+ unsigned int i;
+
+ if (WARN_ON(!entries))
+ return 0;
+
+ for (i = 0; i < nr_entries; i++) {
+ p = sprintf_end(p, end, "%*c%pS\n", 1 + spaces, ' ',
+ (void *)entries[i]);
+ }
+
+ return p;
+}
+EXPORT_SYMBOL_GPL(stack_trace_sprint_end);
+
#ifdef CONFIG_ARCH_STACKWALK
struct stacktrace_cookie {
diff --git a/lib/stackdepot.c b/lib/stackdepot.c
index 73d7b50924ef..48e5c0ff37e8 100644
--- a/lib/stackdepot.c
+++ b/lib/stackdepot.c
@@ -771,6 +771,19 @@ int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
}
EXPORT_SYMBOL_GPL(stack_depot_snprint);
+char *stack_depot_sprint_end(depot_stack_handle_t handle, char *p,
+ const char end[0], int spaces)
+{
+ unsigned long *entries;
+ unsigned int nr_entries;
+
+ nr_entries = stack_depot_fetch(handle, &entries);
+ return nr_entries ?
+ stack_trace_sprint_end(p, end, entries, nr_entries, spaces)
+ : sprintf_end(p, end, "");
+}
+EXPORT_SYMBOL_GPL(stack_depot_sprint_end);
+
depot_stack_handle_t __must_check stack_depot_set_extra_bits(
depot_stack_handle_t handle, unsigned int extra_bits)
{
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v4 3/7] mm: Use sprintf_end() instead of less ergonomic APIs
2025-07-10 2:47 ` [RFC v4 0/7] Add and use sprintf_end() " Alejandro Colomar
` (2 preceding siblings ...)
2025-07-10 2:48 ` [RFC v4 2/7] stacktrace, stackdepot: Add sprintf_end()-like variants of functions Alejandro Colomar
@ 2025-07-10 2:48 ` Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 4/7] array_size.h: Add ENDOF() Alejandro Colomar
` (3 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 2:48 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Sven Schnelle, Heiko Carstens, Tvrtko Ursulin,
Christophe JAILLET, Hyeonggon Yoo, Chao Yu
While doing this, I detected some anomalies in the existing code:
mm/kfence/kfence_test.c:
- The last call to scnprintf() did increment 'cur', but it's
unused after that, so it was dead code. I've removed the dead
code in this patch.
- 'end' is calculated as
end = &expect[0][sizeof(expect[0] - 1)];
However, the '-1' doesn't seem to be necessary. When passing
$2 to scnprintf(), the size was specified as 'end - cur'.
And scnprintf() --just like snprintf(3)--, won't write more
than $2 bytes (including the null byte). That means that
scnprintf() wouldn't write more than
&expect[0][sizeof(expect[0]) - 1] - expect[0]
which simplifies to
sizeof(expect[0]) - 1
bytes. But we have sizeof(expect[0]) bytes available, so
we're wasting one byte entirely. This is a benign off-by-one
bug. The two occurrences of this bug will be fixed in a
following patch in this series.
mm/kmsan/kmsan_test.c:
The same benign off-by-one bug calculating the remaining size.
mm/mempolicy.c:
This file uses the 'p += snprintf()' anti-pattern. That will
overflow the pointer on truncation, which has undefined
behavior. Using sprintf_end(), this bug is fixed.
As in the previous file, here there was also dead code in the
last scnprintf() call, by incrementing a pointer that is not
used after the call. I've removed the dead code.
mm/page_owner.c:
Within print_page_owner(), there are some calls to scnprintf(),
which do report truncation. And then there are other calls to
snprintf(), where we handle errors (there are two 'goto err').
I've kept the existing error handling, as I trust it's there for
a good reason (i.e., we may want to avoid calling
print_page_owner_memcg() if we truncated before). Please review
if this amount of error handling is the right one, or if we want
to add or remove some. For sprintf_end(), a single test for
null after the last call is enough to detect truncation.
mm/slub.c:
Again, the 'p += snprintf()' anti-pattern. This is UB, and by
using sprintf_end() we've fixed the bug.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Sven Schnelle <svens@linux.ibm.com>
Cc: Marco Elver <elver@google.com>
Cc: Heiko Carstens <hca@linux.ibm.com>
Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: David Rientjes <rientjes@google.com>
Cc: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
Cc: Hyeonggon Yoo <42.hyeyoo@gmail.com>
Cc: Chao Yu <chao.yu@oppo.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/kfence/kfence_test.c | 24 ++++++++++++------------
mm/kmsan/kmsan_test.c | 4 ++--
mm/mempolicy.c | 18 +++++++++---------
mm/page_owner.c | 32 +++++++++++++++++---------------
mm/slub.c | 5 +++--
5 files changed, 43 insertions(+), 40 deletions(-)
diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
index 00034e37bc9f..bae382eca4ab 100644
--- a/mm/kfence/kfence_test.c
+++ b/mm/kfence/kfence_test.c
@@ -113,26 +113,26 @@ static bool report_matches(const struct expect_report *r)
end = &expect[0][sizeof(expect[0]) - 1];
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds %s",
+ cur = sprintf_end(cur, end, "BUG: KFENCE: out-of-bounds %s",
get_access_type(r));
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free %s",
+ cur = sprintf_end(cur, end, "BUG: KFENCE: use-after-free %s",
get_access_type(r));
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: memory corruption");
+ cur = sprintf_end(cur, end, "BUG: KFENCE: memory corruption");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid %s",
+ cur = sprintf_end(cur, end, "BUG: KFENCE: invalid %s",
get_access_type(r));
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid free");
+ cur = sprintf_end(cur, end, "BUG: KFENCE: invalid free");
break;
}
- scnprintf(cur, end - cur, " in %pS", r->fn);
+ sprintf_end(cur, end, " in %pS", r->fn);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expect[0], '+');
if (cur)
@@ -144,26 +144,26 @@ static bool report_matches(const struct expect_report *r)
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "Out-of-bounds %s at", get_access_type(r));
+ cur = sprintf_end(cur, end, "Out-of-bounds %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "Use-after-free %s at", get_access_type(r));
+ cur = sprintf_end(cur, end, "Use-after-free %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "Corrupted memory at");
+ cur = sprintf_end(cur, end, "Corrupted memory at");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "Invalid %s at", get_access_type(r));
+ cur = sprintf_end(cur, end, "Invalid %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "Invalid free of");
+ cur = sprintf_end(cur, end, "Invalid free of");
break;
}
- cur += scnprintf(cur, end - cur, " 0x%p", (void *)addr);
+ sprintf_end(cur, end, " 0x%p", (void *)addr);
spin_lock_irqsave(&observed.lock, flags);
if (!report_available())
diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
index 9733a22c46c1..e48ca1972ff3 100644
--- a/mm/kmsan/kmsan_test.c
+++ b/mm/kmsan/kmsan_test.c
@@ -107,9 +107,9 @@ static bool report_matches(const struct expect_report *r)
cur = expected_header;
end = &expected_header[sizeof(expected_header) - 1];
- cur += scnprintf(cur, end - cur, "BUG: KMSAN: %s", r->error_type);
+ cur = sprintf_end(cur, end, "BUG: KMSAN: %s", r->error_type);
- scnprintf(cur, end - cur, " in %s", r->symbol);
+ sprintf_end(cur, end, " in %s", r->symbol);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expected_header, '+');
if (cur)
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index b28a1e6ae096..6beb2710f97c 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -3359,6 +3359,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
{
char *p = buffer;
+ char *e = buffer + maxlen;
nodemask_t nodes = NODE_MASK_NONE;
unsigned short mode = MPOL_DEFAULT;
unsigned short flags = 0;
@@ -3384,33 +3385,32 @@ void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
break;
default:
WARN_ON_ONCE(1);
- snprintf(p, maxlen, "unknown");
+ sprintf_end(p, e, "unknown");
return;
}
- p += snprintf(p, maxlen, "%s", policy_modes[mode]);
+ p = sprintf_end(p, e, "%s", policy_modes[mode]);
if (flags & MPOL_MODE_FLAGS) {
- p += snprintf(p, buffer + maxlen - p, "=");
+ p = sprintf_end(p, e, "=");
/*
* Static and relative are mutually exclusive.
*/
if (flags & MPOL_F_STATIC_NODES)
- p += snprintf(p, buffer + maxlen - p, "static");
+ p = sprintf_end(p, e, "static");
else if (flags & MPOL_F_RELATIVE_NODES)
- p += snprintf(p, buffer + maxlen - p, "relative");
+ p = sprintf_end(p, e, "relative");
if (flags & MPOL_F_NUMA_BALANCING) {
if (!is_power_of_2(flags & MPOL_MODE_FLAGS))
- p += snprintf(p, buffer + maxlen - p, "|");
- p += snprintf(p, buffer + maxlen - p, "balancing");
+ p = sprintf_end(p, e, "|");
+ p = sprintf_end(p, e, "balancing");
}
}
if (!nodes_empty(nodes))
- p += scnprintf(p, buffer + maxlen - p, ":%*pbl",
- nodemask_pr_args(&nodes));
+ sprintf_end(p, e, ":%*pbl", nodemask_pr_args(&nodes));
}
#ifdef CONFIG_SYSFS
diff --git a/mm/page_owner.c b/mm/page_owner.c
index cc4a6916eec6..c00b3be01540 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -496,7 +496,7 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
/*
* Looking for memcg information and print it out
*/
-static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
+static inline char *print_page_owner_memcg(char *p, const char end[0],
struct page *page)
{
#ifdef CONFIG_MEMCG
@@ -511,8 +511,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
goto out_unlock;
if (memcg_data & MEMCG_DATA_OBJEXTS)
- ret += scnprintf(kbuf + ret, count - ret,
- "Slab cache page\n");
+ p = sprintf_end(p, end, "Slab cache page\n");
memcg = page_memcg_check(page);
if (!memcg)
@@ -520,7 +519,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
online = (memcg->css.flags & CSS_ONLINE);
cgroup_name(memcg->css.cgroup, name, sizeof(name));
- ret += scnprintf(kbuf + ret, count - ret,
+ p = sprintf_end(p, end,
"Charged %sto %smemcg %s\n",
PageMemcgKmem(page) ? "(via objcg) " : "",
online ? "" : "offline ",
@@ -529,7 +528,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
rcu_read_unlock();
#endif /* CONFIG_MEMCG */
- return ret;
+ return p;
}
static ssize_t
@@ -538,14 +537,16 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
depot_stack_handle_t handle)
{
int ret, pageblock_mt, page_mt;
- char *kbuf;
+ char *kbuf, *p, *e;
count = min_t(size_t, count, PAGE_SIZE);
kbuf = kmalloc(count, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
- ret = scnprintf(kbuf, count,
+ p = kbuf;
+ e = kbuf + count;
+ p = sprintf_end(p, e,
"Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n",
page_owner->order, page_owner->gfp_mask,
&page_owner->gfp_mask, page_owner->pid,
@@ -555,7 +556,7 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
/* Print information relevant to grouping pages by mobility */
pageblock_mt = get_pageblock_migratetype(page);
page_mt = gfp_migratetype(page_owner->gfp_mask);
- ret += scnprintf(kbuf + ret, count - ret,
+ p = sprintf_end(p, e,
"PFN 0x%lx type %s Block %lu type %s Flags %pGp\n",
pfn,
migratetype_names[page_mt],
@@ -563,22 +564,23 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
migratetype_names[pageblock_mt],
&page->flags);
- ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
- if (ret >= count)
- goto err;
+ p = stack_depot_sprint_end(handle, p, e, 0);
+ if (p == NULL)
+ goto err; // XXX: Should we remove this error handling?
if (page_owner->last_migrate_reason != -1) {
- ret += scnprintf(kbuf + ret, count - ret,
+ p = sprintf_end(p, e,
"Page has been migrated, last migrate reason: %s\n",
migrate_reason_names[page_owner->last_migrate_reason]);
}
- ret = print_page_owner_memcg(kbuf, count, ret, page);
+ p = print_page_owner_memcg(p, e, page);
- ret += snprintf(kbuf + ret, count - ret, "\n");
- if (ret >= count)
+ p = sprintf_end(p, e, "\n");
+ if (p == NULL)
goto err;
+ ret = p - kbuf;
if (copy_to_user(buf, kbuf, ret))
ret = -EFAULT;
diff --git a/mm/slub.c b/mm/slub.c
index be8b09e09d30..dcc857676857 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -7451,6 +7451,7 @@ static char *create_unique_id(struct kmem_cache *s)
{
char *name = kmalloc(ID_STR_LENGTH, GFP_KERNEL);
char *p = name;
+ char *e = name + ID_STR_LENGTH;
if (!name)
return ERR_PTR(-ENOMEM);
@@ -7475,9 +7476,9 @@ static char *create_unique_id(struct kmem_cache *s)
*p++ = 'A';
if (p != name + 1)
*p++ = '-';
- p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
+ p = sprintf_end(p, e, "%07u", s->size);
- if (WARN_ON(p > name + ID_STR_LENGTH - 1)) {
+ if (WARN_ON(p == NULL)) {
kfree(name);
return ERR_PTR(-EINVAL);
}
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v4 4/7] array_size.h: Add ENDOF()
2025-07-10 2:47 ` [RFC v4 0/7] Add and use sprintf_end() " Alejandro Colomar
` (3 preceding siblings ...)
2025-07-10 2:48 ` [RFC v4 3/7] mm: Use sprintf_end() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-10 2:48 ` Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 5/7] mm: Fix benign off-by-one bugs Alejandro Colomar
` (2 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 2:48 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro
This macro is useful to calculate the second argument to sprintf_end(),
avoiding off-by-one bugs.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/array_size.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/include/linux/array_size.h b/include/linux/array_size.h
index 06d7d83196ca..781bdb70d939 100644
--- a/include/linux/array_size.h
+++ b/include/linux/array_size.h
@@ -10,4 +10,10 @@
*/
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
+/**
+ * ENDOF - get a pointer to one past the last element in array @a
+ * @a: array
+ */
+#define ENDOF(a) (a + ARRAY_SIZE(a))
+
#endif /* _LINUX_ARRAY_SIZE_H */
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v4 5/7] mm: Fix benign off-by-one bugs
2025-07-10 2:47 ` [RFC v4 0/7] Add and use sprintf_end() " Alejandro Colomar
` (4 preceding siblings ...)
2025-07-10 2:48 ` [RFC v4 4/7] array_size.h: Add ENDOF() Alejandro Colomar
@ 2025-07-10 2:48 ` Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 6/7] sprintf: Add [V]SPRINTF_END() Alejandro Colomar
2025-07-10 2:49 ` [RFC v4 7/7] mm: Use [V]SPRINTF_END() to avoid specifying the array size Alejandro Colomar
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 2:48 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Jann Horn
We were wasting a byte due to an off-by-one bug. s[c]nprintf()
doesn't write more than $2 bytes including the null byte, so trying to
pass 'size-1' there is wasting one byte. Now that we use seprintf(),
the situation isn't different: seprintf() will stop writing *before*
'end' --that is, at most the terminating null byte will be written at
'end-1'--.
Acked-by: Marco Elver <elver@google.com>
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Jann Horn <jannh@google.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/kfence/kfence_test.c | 4 ++--
mm/kmsan/kmsan_test.c | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
index bae382eca4ab..c635aa9d478b 100644
--- a/mm/kfence/kfence_test.c
+++ b/mm/kfence/kfence_test.c
@@ -110,7 +110,7 @@ static bool report_matches(const struct expect_report *r)
/* Title */
cur = expect[0];
- end = &expect[0][sizeof(expect[0]) - 1];
+ end = ENDOF(expect[0]);
switch (r->type) {
case KFENCE_ERROR_OOB:
cur = sprintf_end(cur, end, "BUG: KFENCE: out-of-bounds %s",
@@ -140,7 +140,7 @@ static bool report_matches(const struct expect_report *r)
/* Access information */
cur = expect[1];
- end = &expect[1][sizeof(expect[1]) - 1];
+ end = ENDOF(expect[1]);
switch (r->type) {
case KFENCE_ERROR_OOB:
diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
index e48ca1972ff3..9bda55992e3d 100644
--- a/mm/kmsan/kmsan_test.c
+++ b/mm/kmsan/kmsan_test.c
@@ -105,7 +105,7 @@ static bool report_matches(const struct expect_report *r)
/* Title */
cur = expected_header;
- end = &expected_header[sizeof(expected_header) - 1];
+ end = ENDOF(expected_header);
cur = sprintf_end(cur, end, "BUG: KMSAN: %s", r->error_type);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v4 6/7] sprintf: Add [V]SPRINTF_END()
2025-07-10 2:47 ` [RFC v4 0/7] Add and use sprintf_end() " Alejandro Colomar
` (5 preceding siblings ...)
2025-07-10 2:48 ` [RFC v4 5/7] mm: Fix benign off-by-one bugs Alejandro Colomar
@ 2025-07-10 2:48 ` Alejandro Colomar
2025-07-10 15:52 ` Linus Torvalds
2025-07-10 2:49 ` [RFC v4 7/7] mm: Use [V]SPRINTF_END() to avoid specifying the array size Alejandro Colomar
7 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 2:48 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro
These macros take the end of the array argument implicitly to avoid
programmer mistakes. This guarantees that the input is an array, unlike
snprintf(buf, sizeof(buf), ...);
which is dangerous if the programmer passes a pointer instead of an
array.
These macros are essentially the same as the 2-argument version of
strscpy(), but with a formatted string, and returning a pointer to the
terminating '\0' (or NULL, on error).
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/sprintf.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/include/linux/sprintf.h b/include/linux/sprintf.h
index a0dc35574521..33eb03d0b9b8 100644
--- a/include/linux/sprintf.h
+++ b/include/linux/sprintf.h
@@ -4,6 +4,10 @@
#include <linux/compiler_attributes.h>
#include <linux/types.h>
+#include <linux/array_size.h>
+
+#define SPRINTF_END(a, fmt, ...) sprintf_end(a, ENDOF(a), fmt, ##__VA_ARGS__)
+#define VSPRINTF_END(a, fmt, ap) vsprintf_end(a, ENDOF(a), fmt, ap)
int num_to_str(char *buf, int size, unsigned long long num, unsigned int width);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v4 7/7] mm: Use [V]SPRINTF_END() to avoid specifying the array size
2025-07-10 2:47 ` [RFC v4 0/7] Add and use sprintf_end() " Alejandro Colomar
` (6 preceding siblings ...)
2025-07-10 2:48 ` [RFC v4 6/7] sprintf: Add [V]SPRINTF_END() Alejandro Colomar
@ 2025-07-10 2:49 ` Alejandro Colomar
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 2:49 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/backing-dev.c | 2 +-
mm/cma.c | 4 ++--
mm/cma_debug.c | 2 +-
mm/hugetlb.c | 3 +--
mm/hugetlb_cgroup.c | 2 +-
mm/hugetlb_cma.c | 2 +-
mm/kasan/report.c | 3 +--
mm/memblock.c | 4 ++--
mm/percpu.c | 2 +-
mm/shrinker_debug.c | 2 +-
mm/zswap.c | 2 +-
11 files changed, 13 insertions(+), 15 deletions(-)
diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index 783904d8c5ef..20a75fd9f205 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -1090,7 +1090,7 @@ int bdi_register_va(struct backing_dev_info *bdi, const char *fmt, va_list args)
if (bdi->dev) /* The driver needs to use separate queues per device */
return 0;
- vsnprintf(bdi->dev_name, sizeof(bdi->dev_name), fmt, args);
+ VSPRINTF_END(bdi->dev_name, fmt, args);
dev = device_create(&bdi_class, NULL, MKDEV(0, 0), bdi, bdi->dev_name);
if (IS_ERR(dev))
return PTR_ERR(dev);
diff --git a/mm/cma.c b/mm/cma.c
index c04be488b099..05f8f036b811 100644
--- a/mm/cma.c
+++ b/mm/cma.c
@@ -237,9 +237,9 @@ static int __init cma_new_area(const char *name, phys_addr_t size,
cma_area_count++;
if (name)
- snprintf(cma->name, CMA_MAX_NAME, "%s", name);
+ SPRINTF_END(cma->name, "%s", name);
else
- snprintf(cma->name, CMA_MAX_NAME, "cma%d\n", cma_area_count);
+ SPRINTF_END(cma->name, "cma%d\n", cma_area_count);
cma->available_count = cma->count = size >> PAGE_SHIFT;
cma->order_per_bit = order_per_bit;
diff --git a/mm/cma_debug.c b/mm/cma_debug.c
index fdf899532ca0..6df439b400c1 100644
--- a/mm/cma_debug.c
+++ b/mm/cma_debug.c
@@ -186,7 +186,7 @@ static void cma_debugfs_add_one(struct cma *cma, struct dentry *root_dentry)
rangedir = debugfs_create_dir("ranges", tmp);
for (r = 0; r < cma->nranges; r++) {
cmr = &cma->ranges[r];
- snprintf(rdirname, sizeof(rdirname), "%d", r);
+ SPRINTF_END(rdirname, "%d", r);
dir = debugfs_create_dir(rdirname, rangedir);
debugfs_create_file("base_pfn", 0444, dir,
&cmr->base_pfn, &cma_debugfs_fops);
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 6a3cf7935c14..2e6aa3efafb2 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -4780,8 +4780,7 @@ void __init hugetlb_add_hstate(unsigned int order)
for (i = 0; i < MAX_NUMNODES; ++i)
INIT_LIST_HEAD(&h->hugepage_freelists[i]);
INIT_LIST_HEAD(&h->hugepage_activelist);
- snprintf(h->name, HSTATE_NAME_LEN, "hugepages-%lukB",
- huge_page_size(h)/SZ_1K);
+ SPRINTF_END(h->name, "hugepages-%lukB", huge_page_size(h)/SZ_1K);
parsed_hstate = h;
}
diff --git a/mm/hugetlb_cgroup.c b/mm/hugetlb_cgroup.c
index 58e895f3899a..4b5330ff9cef 100644
--- a/mm/hugetlb_cgroup.c
+++ b/mm/hugetlb_cgroup.c
@@ -822,7 +822,7 @@ hugetlb_cgroup_cfttypes_init(struct hstate *h, struct cftype *cft,
for (i = 0; i < tmpl_size; cft++, tmpl++, i++) {
*cft = *tmpl;
/* rebuild the name */
- snprintf(cft->name, MAX_CFTYPE_NAME, "%s.%s", buf, tmpl->name);
+ SPRINTF_END(cft->name, "%s.%s", buf, tmpl->name);
/* rebuild the private */
cft->private = MEMFILE_PRIVATE(idx, tmpl->private);
/* rebuild the file_offset */
diff --git a/mm/hugetlb_cma.c b/mm/hugetlb_cma.c
index e0f2d5c3a84c..6bccad5b4216 100644
--- a/mm/hugetlb_cma.c
+++ b/mm/hugetlb_cma.c
@@ -211,7 +211,7 @@ void __init hugetlb_cma_reserve(int order)
size = round_up(size, PAGE_SIZE << order);
- snprintf(name, sizeof(name), "hugetlb%d", nid);
+ SPRINTF_END(name, "hugetlb%d", nid);
/*
* Note that 'order per bit' is based on smallest size that
* may be returned to CMA allocator in the case of
diff --git a/mm/kasan/report.c b/mm/kasan/report.c
index 8357e1a33699..c2c9bef78edf 100644
--- a/mm/kasan/report.c
+++ b/mm/kasan/report.c
@@ -486,8 +486,7 @@ static void print_memory_metadata(const void *addr)
char buffer[4 + (BITS_PER_LONG / 8) * 2];
char metadata[META_BYTES_PER_ROW];
- snprintf(buffer, sizeof(buffer),
- (i == 0) ? ">%px: " : " %px: ", row);
+ SPRINTF_END(buffer, (i == 0) ? ">%px: " : " %px: ", row);
/*
* We should not pass a shadow pointer to generic
diff --git a/mm/memblock.c b/mm/memblock.c
index 0e9ebb8aa7fe..6bb21aacb15d 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -2021,7 +2021,7 @@ static void __init_memblock memblock_dump(struct memblock_type *type)
flags = rgn->flags;
#ifdef CONFIG_NUMA
if (numa_valid_node(memblock_get_region_node(rgn)))
- snprintf(nid_buf, sizeof(nid_buf), " on node %d",
+ SPRINTF_END(nid_buf, " on node %d",
memblock_get_region_node(rgn));
#endif
pr_info(" %s[%#x]\t[%pa-%pa], %pa bytes%s flags: %#x\n",
@@ -2379,7 +2379,7 @@ int reserve_mem_release_by_name(const char *name)
start = phys_to_virt(map->start);
end = start + map->size - 1;
- snprintf(buf, sizeof(buf), "reserve_mem:%s", name);
+ SPRINTF_END(buf, "reserve_mem:%s", name);
free_reserved_area(start, end, 0, buf);
map->size = 0;
diff --git a/mm/percpu.c b/mm/percpu.c
index b35494c8ede2..efe5d1517a96 100644
--- a/mm/percpu.c
+++ b/mm/percpu.c
@@ -3186,7 +3186,7 @@ int __init pcpu_page_first_chunk(size_t reserved_size, pcpu_fc_cpu_to_node_fn_t
int upa;
int nr_g0_units;
- snprintf(psize_str, sizeof(psize_str), "%luK", PAGE_SIZE >> 10);
+ SPRINTF_END(psize_str, "%luK", PAGE_SIZE >> 10);
ai = pcpu_build_alloc_info(reserved_size, 0, PAGE_SIZE, NULL);
if (IS_ERR(ai))
diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
index 20eaee3e97f7..9a6e959882c6 100644
--- a/mm/shrinker_debug.c
+++ b/mm/shrinker_debug.c
@@ -176,7 +176,7 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
return id;
shrinker->debugfs_id = id;
- snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
+ SPRINTF_END(buf, "%s-%d", shrinker->name, id);
/* create debugfs entry */
entry = debugfs_create_dir(buf, shrinker_debugfs_root);
diff --git a/mm/zswap.c b/mm/zswap.c
index 204fb59da33c..7a8041f84e18 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -271,7 +271,7 @@ static struct zswap_pool *zswap_pool_create(char *type, char *compressor)
return NULL;
/* unique name for each pool specifically required by zsmalloc */
- snprintf(name, 38, "zswap%x", atomic_inc_return(&zswap_pools_count));
+ SPRINTF_END(name, "zswap%x", atomic_inc_return(&zswap_pools_count));
pool->zpool = zpool_create_pool(type, name, gfp);
if (!pool->zpool) {
pr_err("%s zpool not available\n", type);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* Re: [RFC v4 6/7] sprintf: Add [V]SPRINTF_END()
2025-07-10 2:48 ` [RFC v4 6/7] sprintf: Add [V]SPRINTF_END() Alejandro Colomar
@ 2025-07-10 15:52 ` Linus Torvalds
2025-07-10 18:30 ` Alejandro Colomar
0 siblings, 1 reply; 98+ messages in thread
From: Linus Torvalds @ 2025-07-10 15:52 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Al Viro
On Wed, 9 Jul 2025 at 19:49, Alejandro Colomar <alx@kernel.org> wrote:
>
> +#define SPRINTF_END(a, fmt, ...) sprintf_end(a, ENDOF(a), fmt, ##__VA_ARGS__)
> +#define VSPRINTF_END(a, fmt, ap) vsprintf_end(a, ENDOF(a), fmt, ap)
So I like vsprintf_end() more as a name ("like more" not being "I love
it", but at least it makes me think it's a bit more self-explanatory).
But I don't love screaming macros. They historically scream because
they are unsafe, but they shouldn't be unsafe in the first place.
And I don't think those [V]SPRINTF_END() and ENDOF() macros are unsafe
- they use our ARRAY_SIZE() macro which does not evaluate the
argument, only the type, and is safe to use.
So honestly, this interface looks easy to use, but the screaming must stop.
And none of this has *anything* to do with "end" in this form anyway.
IOW, why isn't this just
#define sprintf_array(a,...) snprintf(a, ARRAY_SIZE(a), __VA_ARGS__)
which is simpler and more direct, doesn't use the "end" version that
is pointless (it's _literally_ about the size of the array, so
'snprintf' is the right thing to use), doesn't scream, and has a
rather self-explanatory name.
Naming matters.
Linus
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v4 6/7] sprintf: Add [V]SPRINTF_END()
2025-07-10 15:52 ` Linus Torvalds
@ 2025-07-10 18:30 ` Alejandro Colomar
2025-07-10 21:21 ` Alejandro Colomar
0 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 18:30 UTC (permalink / raw)
To: Linus Torvalds
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Al Viro
[-- Attachment #1: Type: text/plain, Size: 2471 bytes --]
Hi Linus,
On Thu, Jul 10, 2025 at 08:52:13AM -0700, Linus Torvalds wrote:
> On Wed, 9 Jul 2025 at 19:49, Alejandro Colomar <alx@kernel.org> wrote:
> >
> > +#define SPRINTF_END(a, fmt, ...) sprintf_end(a, ENDOF(a), fmt, ##__VA_ARGS__)
> > +#define VSPRINTF_END(a, fmt, ap) vsprintf_end(a, ENDOF(a), fmt, ap)
>
> So I like vsprintf_end() more as a name ("like more" not being "I love
> it", but at least it makes me think it's a bit more self-explanatory).
:-)
> But I don't love screaming macros. They historically scream because
> they are unsafe, but they shouldn't be unsafe in the first place.
>
> And I don't think those [V]SPRINTF_END() and ENDOF() macros are unsafe
> - they use our ARRAY_SIZE() macro which does not evaluate the
> argument, only the type, and is safe to use.
Yup, it's safe to use.
> So honestly, this interface looks easy to use, but the screaming must stop.
>
> And none of this has *anything* to do with "end" in this form anyway.
That same thing happened through my head while doing it, but I didn't
think of a better name.
In shadow, we have many interfaces for which we have an uppercase macro
version of many functions that gets array sizes and other extra safety
measures where we can. (So there, the uppercase versions are indeed
extra safety, instead of the historical "there be dragons". I use the
uppercase to mean "this does some magic to be safer".)
> IOW, why isn't this just
>
> #define sprintf_array(a,...) snprintf(a, ARRAY_SIZE(a), __VA_ARGS__)
Agree. This is a better name for the kernel.
> which is simpler and more direct, doesn't use the "end" version that
> is pointless (it's _literally_ about the size of the array, so
> 'snprintf' is the right thing to use),
I disagree with snprintf(3), but not because of the input, but rather
because of the output. I think an API similar to strscpy() would be
better, so it can return an error code for truncation. In fact, up to
v2, I had a stprintf() (T for truncation) that did exactly that.
However, I found out I could do the same with sprintf_end(), which would
mean one less function to grok, which is why I dropped that part.
I'll use your suggested name, as I like it. Expect v5 in a few minutes.
> doesn't scream, and has a
> rather self-explanatory name.
>
> Naming matters.
+1
Have a lovely day!
Alex
>
> Linus
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v4 6/7] sprintf: Add [V]SPRINTF_END()
2025-07-10 18:30 ` Alejandro Colomar
@ 2025-07-10 21:21 ` Alejandro Colomar
2025-07-10 22:08 ` Linus Torvalds
0 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 21:21 UTC (permalink / raw)
To: Linus Torvalds
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Al Viro
[-- Attachment #1: Type: text/plain, Size: 852 bytes --]
Hi Linus,
On Thu, Jul 10, 2025 at 08:30:59PM +0200, Alejandro Colomar wrote:
> > IOW, why isn't this just
> >
> > #define sprintf_array(a,...) snprintf(a, ARRAY_SIZE(a), __VA_ARGS__)
>
> Agree. This is a better name for the kernel.
Oops, I misread. I thought you were implementing it as
#define sprintf_array(a, ...) sprintf_end(a, ENDOF(a), __VA_ARGS__)
So, I prefer my implementation because it returns NULL on truncation.
Compare usage:
if (linus_sprintf_array(a, "foo") >= ARRAY_SIZE(a))
goto fail;
if (alex_sprintf_array(a, "foo") == NULL)
goto fail;
Another approach would be to have
if (third_sprintf_array(a, "foo") < 0) // -E2BIG
goto fail;
Which was my first approach, but since we have sprintf_end(), let's just
reuse it.
Cheers,
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* [RFC v5 0/7] Add and use sprintf_{end,array}() instead of less ergonomic APIs
2025-07-06 17:37 ` [RFC v2 0/5] Add and use " Alejandro Colomar
` (6 preceding siblings ...)
2025-07-10 2:47 ` [RFC v4 0/7] Add and use sprintf_end() " Alejandro Colomar
@ 2025-07-10 21:30 ` Alejandro Colomar
2025-07-10 21:30 ` [RFC v5 1/7] vsprintf: Add [v]sprintf_end() Alejandro Colomar
` (6 more replies)
2025-07-11 1:56 ` [RFC v6 0/8] Add and use sprintf_{end,trunc,array}() instead of less ergonomic APIs Alejandro Colomar
8 siblings, 7 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 21:30 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
Hi,
Changes in v5:
- Minor fix in commit message.
- Rename [V]SPRINTF_END() => [v]sprintf_array(), keeping the
implementation.
Remaining questions:
- There are only 3 remaining calls to snprintf(3) under mm/. They are
just fine for now, which is why I didn't replace them. If anyone
wants to replace them, to get rid of all snprintf(3), we could that.
I think for now we can leave them, to minimize the churn.
$ grep -rnI snprintf mm/
mm/hugetlb_cgroup.c:674: snprintf(buf, size, "%luGB", hsize / SZ_1G);
mm/hugetlb_cgroup.c:676: snprintf(buf, size, "%luMB", hsize / SZ_1M);
mm/hugetlb_cgroup.c:678: snprintf(buf, size, "%luKB", hsize / SZ_1K);
- There are only 2 remaining calls to the kernel's scnprintf(). This
one I would really like to get rid of. Also, those calls are quite
suspicious of not being what we want. Please do have a look at them
and confirm what's the appropriate behavior in the 2 cases when the
string is truncated or not copied at all. That code is very scary
for me to try to guess.
$ grep -rnI scnprintf mm/
mm/kfence/report.c:75: int len = scnprintf(buf, sizeof(buf), "%ps", (void *)stack_entries[skipnr]);
mm/kfence/kfence_test.mod.c:22: { 0x96848186, "scnprintf" },
mm/kmsan/report.c:42: len = scnprintf(buf, sizeof(buf), "%ps",
Apart from two calls, I see a string literal with that name. Please
let me know if I should do anything about it. I don't know what that
is.
- I think we should remove one error handling check in
"mm/page_owner.c" (marked with an XXX comment), but I'm not 100%
sure. Please confirm.
Other comments:
- This is still not complying to coding style. I'll keep it like that
while questions remain open.
- I've tested the tests under CONFIG_KFENCE_KUNIT_TEST=y, and this has
no regressions at all.
- With the current style of the sprintf_end() prototyope, this triggers
a diagnostic due to a GCC bug:
<https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108036>
It would be interesting to ask GCC to fix that bug. (Added relevant
GCC maintainers and contributors to CC in this cover letter.)
For anyone new to the thread, sprintf_end() will be proposed for
standardization soon as seprintf():
<https://lore.kernel.org/linux-hardening/20250710024745.143955-1-alx@kernel.org/T/#u>
Have a lovely night!
Alex
Alejandro Colomar (7):
vsprintf: Add [v]sprintf_end()
stacktrace, stackdepot: Add sprintf_end()-like variants of functions
mm: Use sprintf_end() instead of less ergonomic APIs
array_size.h: Add ENDOF()
mm: Fix benign off-by-one bugs
sprintf: Add [v]sprintf_array()
mm: Use [v]sprintf_array() to avoid specifying the array size
include/linux/array_size.h | 6 ++++
include/linux/sprintf.h | 6 ++++
include/linux/stackdepot.h | 13 +++++++++
include/linux/stacktrace.h | 3 ++
kernel/stacktrace.c | 28 ++++++++++++++++++
lib/stackdepot.c | 13 +++++++++
lib/vsprintf.c | 59 ++++++++++++++++++++++++++++++++++++++
mm/backing-dev.c | 2 +-
mm/cma.c | 4 +--
mm/cma_debug.c | 2 +-
mm/hugetlb.c | 3 +-
mm/hugetlb_cgroup.c | 2 +-
mm/hugetlb_cma.c | 2 +-
mm/kasan/report.c | 3 +-
mm/kfence/kfence_test.c | 28 +++++++++---------
mm/kmsan/kmsan_test.c | 6 ++--
mm/memblock.c | 4 +--
mm/mempolicy.c | 18 ++++++------
mm/page_owner.c | 32 +++++++++++----------
mm/percpu.c | 2 +-
mm/shrinker_debug.c | 2 +-
mm/slub.c | 5 ++--
mm/zswap.c | 2 +-
23 files changed, 187 insertions(+), 58 deletions(-)
Range-diff against v4:
1: 2c4f793de0b8 = 1: 2c4f793de0b8 vsprintf: Add [v]sprintf_end()
2: 894d02b08056 = 2: 894d02b08056 stacktrace, stackdepot: Add sprintf_end()-like variants of functions
3: 690ed4d22f57 = 3: 690ed4d22f57 mm: Use sprintf_end() instead of less ergonomic APIs
4: e05c5afabb3c = 4: e05c5afabb3c array_size.h: Add ENDOF()
5: 44a5cfc82acf ! 5: 515445ae064d mm: Fix benign off-by-one bugs
@@ Commit message
We were wasting a byte due to an off-by-one bug. s[c]nprintf()
doesn't write more than $2 bytes including the null byte, so trying to
- pass 'size-1' there is wasting one byte. Now that we use seprintf(),
- the situation isn't different: seprintf() will stop writing *before*
+ pass 'size-1' there is wasting one byte. Now that we use sprintf_end(),
+ the situation isn't different: sprintf_end() will stop writing *before*
'end' --that is, at most the terminating null byte will be written at
'end-1'--.
6: 0314948eb225 ! 6: 04c1e026a67f sprintf: Add [V]SPRINTF_END()
@@ Metadata
Author: Alejandro Colomar <alx@kernel.org>
## Commit message ##
- sprintf: Add [V]SPRINTF_END()
+ sprintf: Add [v]sprintf_array()
These macros take the end of the array argument implicitly to avoid
programmer mistakes. This guarantees that the input is an array, unlike
@@ include/linux/sprintf.h
#include <linux/types.h>
+#include <linux/array_size.h>
+
-+#define SPRINTF_END(a, fmt, ...) sprintf_end(a, ENDOF(a), fmt, ##__VA_ARGS__)
-+#define VSPRINTF_END(a, fmt, ap) vsprintf_end(a, ENDOF(a), fmt, ap)
++#define sprintf_array(a, fmt, ...) sprintf_end(a, ENDOF(a), fmt, ##__VA_ARGS__)
++#define vsprintf_array(a, fmt, ap) vsprintf_end(a, ENDOF(a), fmt, ap)
int num_to_str(char *buf, int size, unsigned long long num, unsigned int width);
7: f99632f42eee ! 7: e53d87e684ef mm: Use [V]SPRINTF_END() to avoid specifying the array size
@@ Metadata
Author: Alejandro Colomar <alx@kernel.org>
## Commit message ##
- mm: Use [V]SPRINTF_END() to avoid specifying the array size
+ mm: Use [v]sprintf_array() to avoid specifying the array size
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
@@ mm/backing-dev.c: int bdi_register_va(struct backing_dev_info *bdi, const char *
return 0;
- vsnprintf(bdi->dev_name, sizeof(bdi->dev_name), fmt, args);
-+ VSPRINTF_END(bdi->dev_name, fmt, args);
++ vsprintf_array(bdi->dev_name, fmt, args);
dev = device_create(&bdi_class, NULL, MKDEV(0, 0), bdi, bdi->dev_name);
if (IS_ERR(dev))
return PTR_ERR(dev);
@@ mm/cma.c: static int __init cma_new_area(const char *name, phys_addr_t size,
if (name)
- snprintf(cma->name, CMA_MAX_NAME, "%s", name);
-+ SPRINTF_END(cma->name, "%s", name);
++ sprintf_array(cma->name, "%s", name);
else
- snprintf(cma->name, CMA_MAX_NAME, "cma%d\n", cma_area_count);
-+ SPRINTF_END(cma->name, "cma%d\n", cma_area_count);
++ sprintf_array(cma->name, "cma%d\n", cma_area_count);
cma->available_count = cma->count = size >> PAGE_SHIFT;
cma->order_per_bit = order_per_bit;
@@ mm/cma_debug.c: static void cma_debugfs_add_one(struct cma *cma, struct dentry *
for (r = 0; r < cma->nranges; r++) {
cmr = &cma->ranges[r];
- snprintf(rdirname, sizeof(rdirname), "%d", r);
-+ SPRINTF_END(rdirname, "%d", r);
++ sprintf_array(rdirname, "%d", r);
dir = debugfs_create_dir(rdirname, rangedir);
debugfs_create_file("base_pfn", 0444, dir,
&cmr->base_pfn, &cma_debugfs_fops);
@@ mm/hugetlb.c: void __init hugetlb_add_hstate(unsigned int order)
INIT_LIST_HEAD(&h->hugepage_activelist);
- snprintf(h->name, HSTATE_NAME_LEN, "hugepages-%lukB",
- huge_page_size(h)/SZ_1K);
-+ SPRINTF_END(h->name, "hugepages-%lukB", huge_page_size(h)/SZ_1K);
++ sprintf_array(h->name, "hugepages-%lukB", huge_page_size(h)/SZ_1K);
parsed_hstate = h;
}
@@ mm/hugetlb_cgroup.c: hugetlb_cgroup_cfttypes_init(struct hstate *h, struct cftyp
*cft = *tmpl;
/* rebuild the name */
- snprintf(cft->name, MAX_CFTYPE_NAME, "%s.%s", buf, tmpl->name);
-+ SPRINTF_END(cft->name, "%s.%s", buf, tmpl->name);
++ sprintf_array(cft->name, "%s.%s", buf, tmpl->name);
/* rebuild the private */
cft->private = MEMFILE_PRIVATE(idx, tmpl->private);
/* rebuild the file_offset */
@@ mm/hugetlb_cma.c: void __init hugetlb_cma_reserve(int order)
size = round_up(size, PAGE_SIZE << order);
- snprintf(name, sizeof(name), "hugetlb%d", nid);
-+ SPRINTF_END(name, "hugetlb%d", nid);
++ sprintf_array(name, "hugetlb%d", nid);
/*
* Note that 'order per bit' is based on smallest size that
* may be returned to CMA allocator in the case of
@@ mm/kasan/report.c: static void print_memory_metadata(const void *addr)
- snprintf(buffer, sizeof(buffer),
- (i == 0) ? ">%px: " : " %px: ", row);
-+ SPRINTF_END(buffer, (i == 0) ? ">%px: " : " %px: ", row);
++ sprintf_array(buffer, (i == 0) ? ">%px: " : " %px: ", row);
/*
* We should not pass a shadow pointer to generic
@@ mm/memblock.c: static void __init_memblock memblock_dump(struct memblock_type *t
#ifdef CONFIG_NUMA
if (numa_valid_node(memblock_get_region_node(rgn)))
- snprintf(nid_buf, sizeof(nid_buf), " on node %d",
-+ SPRINTF_END(nid_buf, " on node %d",
++ sprintf_array(nid_buf, " on node %d",
memblock_get_region_node(rgn));
#endif
pr_info(" %s[%#x]\t[%pa-%pa], %pa bytes%s flags: %#x\n",
@@ mm/memblock.c: int reserve_mem_release_by_name(const char *name)
start = phys_to_virt(map->start);
end = start + map->size - 1;
- snprintf(buf, sizeof(buf), "reserve_mem:%s", name);
-+ SPRINTF_END(buf, "reserve_mem:%s", name);
++ sprintf_array(buf, "reserve_mem:%s", name);
free_reserved_area(start, end, 0, buf);
map->size = 0;
@@ mm/percpu.c: int __init pcpu_page_first_chunk(size_t reserved_size, pcpu_fc_cpu_
int nr_g0_units;
- snprintf(psize_str, sizeof(psize_str), "%luK", PAGE_SIZE >> 10);
-+ SPRINTF_END(psize_str, "%luK", PAGE_SIZE >> 10);
++ sprintf_array(psize_str, "%luK", PAGE_SIZE >> 10);
ai = pcpu_build_alloc_info(reserved_size, 0, PAGE_SIZE, NULL);
if (IS_ERR(ai))
@@ mm/shrinker_debug.c: int shrinker_debugfs_add(struct shrinker *shrinker)
shrinker->debugfs_id = id;
- snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
-+ SPRINTF_END(buf, "%s-%d", shrinker->name, id);
++ sprintf_array(buf, "%s-%d", shrinker->name, id);
/* create debugfs entry */
entry = debugfs_create_dir(buf, shrinker_debugfs_root);
@@ mm/zswap.c: static struct zswap_pool *zswap_pool_create(char *type, char *compre
/* unique name for each pool specifically required by zsmalloc */
- snprintf(name, 38, "zswap%x", atomic_inc_return(&zswap_pools_count));
-+ SPRINTF_END(name, "zswap%x", atomic_inc_return(&zswap_pools_count));
++ sprintf_array(name, "zswap%x", atomic_inc_return(&zswap_pools_count));
pool->zpool = zpool_create_pool(type, name, gfp);
if (!pool->zpool) {
pr_err("%s zpool not available\n", type);
--
2.50.0
^ permalink raw reply [flat|nested] 98+ messages in thread
* [RFC v5 1/7] vsprintf: Add [v]sprintf_end()
2025-07-10 21:30 ` [RFC v5 0/7] Add and use sprintf_{end,array}() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-10 21:30 ` Alejandro Colomar
2025-07-10 21:30 ` [RFC v5 2/7] stacktrace, stackdepot: Add sprintf_end()-like variants of functions Alejandro Colomar
` (5 subsequent siblings)
6 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 21:30 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
sprintf_end() is a function similar to stpcpy(3) in the sense that it
returns a pointer that is suitable for chaining to other copy
operations.
It takes a pointer to the end of the buffer as a sentinel for when to
truncate, which unlike a size, doesn't need to be updated after every
call. This makes it much more ergonomic, avoiding manually calculating
the size after each copy, which is error prone.
It also makes error handling much easier, by reporting truncation with
a null pointer, which is accepted and transparently passed down by
subsequent sprintf_end() calls. This results in only needing to report
errors once after a chain of sprintf_end() calls, unlike snprintf(3),
which requires checking after every call.
p = buf;
e = buf + countof(buf);
p = sprintf_end(p, e, foo);
p = sprintf_end(p, e, bar);
if (p == NULL)
goto trunc;
vs
len = 0;
size = countof(buf);
len += snprintf(buf + len, size - len, foo);
if (len >= size)
goto trunc;
len += snprintf(buf + len, size - len, bar);
if (len >= size)
goto trunc;
And also better than scnprintf() calls:
len = 0;
size = countof(buf);
len += scnprintf(buf + len, size - len, foo);
len += scnprintf(buf + len, size - len, bar);
// No ability to check.
It seems aparent that it's a more elegant approach to string catenation.
These functions will soon be proposed for standardization as
[v]seprintf() into C2y, and they exist in Plan9 as seprint(2) --but the
Plan9 implementation has important bugs--.
Link: <https://www.alejandro-colomar.es/src/alx/alx/wg14/alx-0049.git/tree/alx-0049.txt>
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/sprintf.h | 2 ++
lib/vsprintf.c | 59 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+)
diff --git a/include/linux/sprintf.h b/include/linux/sprintf.h
index 51cab2def9ec..a0dc35574521 100644
--- a/include/linux/sprintf.h
+++ b/include/linux/sprintf.h
@@ -13,6 +13,8 @@ __printf(3, 4) int snprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
__printf(3, 4) int scnprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
+__printf(3, 4) char *sprintf_end(char *p, const char end[0], const char *fmt, ...);
+__printf(3, 0) char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args);
__printf(2, 3) __malloc char *kasprintf(gfp_t gfp, const char *fmt, ...);
__printf(2, 0) __malloc char *kvasprintf(gfp_t gfp, const char *fmt, va_list args);
__printf(2, 0) const char *kvasprintf_const(gfp_t gfp, const char *fmt, va_list args);
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 01699852f30c..d32df53a713a 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -2923,6 +2923,40 @@ int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
}
EXPORT_SYMBOL(vscnprintf);
+/**
+ * vsprintf_end - va_list string end-delimited print formatted
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
+ *
+ * The return value is a pointer to the trailing '\0'.
+ * If @p is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
+ * If @end <= @p, the function returns NULL.
+ *
+ * See the vsnprintf() documentation for format string extensions over C99.
+ */
+char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args)
+{
+ int len;
+ size_t size;
+
+ if (unlikely(p == NULL))
+ return NULL;
+
+ size = end - p;
+ if (WARN_ON_ONCE(size == 0 || size > INT_MAX))
+ return NULL;
+
+ len = vsnprintf(p, size, fmt, args);
+ if (unlikely(len >= size))
+ return NULL;
+
+ return p + len;
+}
+EXPORT_SYMBOL(vsprintf_end);
+
/**
* snprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -2974,6 +3008,31 @@ int scnprintf(char *buf, size_t size, const char *fmt, ...)
}
EXPORT_SYMBOL(scnprintf);
+/**
+ * sprintf_end - string end-delimited print formatted
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
+ *
+ * The return value is a pointer to the trailing '\0'.
+ * If @buf is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
+ * If @end <= @p, the function returns NULL.
+ */
+
+char *sprintf_end(char *p, const char end[0], const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ p = vsprintf_end(p, end, fmt, args);
+ va_end(args);
+
+ return p;
+}
+EXPORT_SYMBOL(sprintf_end);
+
/**
* vsprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v5 2/7] stacktrace, stackdepot: Add sprintf_end()-like variants of functions
2025-07-10 21:30 ` [RFC v5 0/7] Add and use sprintf_{end,array}() instead of less ergonomic APIs Alejandro Colomar
2025-07-10 21:30 ` [RFC v5 1/7] vsprintf: Add [v]sprintf_end() Alejandro Colomar
@ 2025-07-10 21:30 ` Alejandro Colomar
2025-07-10 21:30 ` [RFC v5 3/7] mm: Use sprintf_end() instead of less ergonomic APIs Alejandro Colomar
` (4 subsequent siblings)
6 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 21:30 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/stackdepot.h | 13 +++++++++++++
include/linux/stacktrace.h | 3 +++
kernel/stacktrace.c | 28 ++++++++++++++++++++++++++++
lib/stackdepot.c | 13 +++++++++++++
4 files changed, 57 insertions(+)
diff --git a/include/linux/stackdepot.h b/include/linux/stackdepot.h
index 2cc21ffcdaf9..76182e874f67 100644
--- a/include/linux/stackdepot.h
+++ b/include/linux/stackdepot.h
@@ -219,6 +219,19 @@ void stack_depot_print(depot_stack_handle_t stack);
int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
int spaces);
+/**
+ * stack_depot_sprint_end - Print a stack trace from stack depot into a buffer
+ *
+ * @handle: Stack depot handle returned from stack_depot_save()
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @spaces: Number of leading spaces to print
+ *
+ * Return: Pointer to trailing '\0'; or NULL on truncation
+ */
+char *stack_depot_sprint_end(depot_stack_handle_t handle, char *p,
+ const char end[0], int spaces);
+
/**
* stack_depot_put - Drop a reference to a stack trace from stack depot
*
diff --git a/include/linux/stacktrace.h b/include/linux/stacktrace.h
index 97455880ac41..79ada795d479 100644
--- a/include/linux/stacktrace.h
+++ b/include/linux/stacktrace.h
@@ -67,6 +67,9 @@ void stack_trace_print(const unsigned long *trace, unsigned int nr_entries,
int spaces);
int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
unsigned int nr_entries, int spaces);
+char *stack_trace_sprint_end(char *p, const char end[0],
+ const unsigned long *entries,
+ unsigned int nr_entries, int spaces);
unsigned int stack_trace_save(unsigned long *store, unsigned int size,
unsigned int skipnr);
unsigned int stack_trace_save_tsk(struct task_struct *task,
diff --git a/kernel/stacktrace.c b/kernel/stacktrace.c
index afb3c116da91..f389647d8e44 100644
--- a/kernel/stacktrace.c
+++ b/kernel/stacktrace.c
@@ -70,6 +70,34 @@ int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
}
EXPORT_SYMBOL_GPL(stack_trace_snprint);
+/**
+ * stack_trace_sprint_end - Print the entries in the stack trace into a buffer
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @entries: Pointer to storage array
+ * @nr_entries: Number of entries in the storage array
+ * @spaces: Number of leading spaces to print
+ *
+ * Return: Pointer to the trailing '\0'; or NULL on truncation.
+ */
+char *stack_trace_sprint_end(char *p, const char end[0],
+ const unsigned long *entries, unsigned int nr_entries,
+ int spaces)
+{
+ unsigned int i;
+
+ if (WARN_ON(!entries))
+ return 0;
+
+ for (i = 0; i < nr_entries; i++) {
+ p = sprintf_end(p, end, "%*c%pS\n", 1 + spaces, ' ',
+ (void *)entries[i]);
+ }
+
+ return p;
+}
+EXPORT_SYMBOL_GPL(stack_trace_sprint_end);
+
#ifdef CONFIG_ARCH_STACKWALK
struct stacktrace_cookie {
diff --git a/lib/stackdepot.c b/lib/stackdepot.c
index 73d7b50924ef..48e5c0ff37e8 100644
--- a/lib/stackdepot.c
+++ b/lib/stackdepot.c
@@ -771,6 +771,19 @@ int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
}
EXPORT_SYMBOL_GPL(stack_depot_snprint);
+char *stack_depot_sprint_end(depot_stack_handle_t handle, char *p,
+ const char end[0], int spaces)
+{
+ unsigned long *entries;
+ unsigned int nr_entries;
+
+ nr_entries = stack_depot_fetch(handle, &entries);
+ return nr_entries ?
+ stack_trace_sprint_end(p, end, entries, nr_entries, spaces)
+ : sprintf_end(p, end, "");
+}
+EXPORT_SYMBOL_GPL(stack_depot_sprint_end);
+
depot_stack_handle_t __must_check stack_depot_set_extra_bits(
depot_stack_handle_t handle, unsigned int extra_bits)
{
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v5 3/7] mm: Use sprintf_end() instead of less ergonomic APIs
2025-07-10 21:30 ` [RFC v5 0/7] Add and use sprintf_{end,array}() instead of less ergonomic APIs Alejandro Colomar
2025-07-10 21:30 ` [RFC v5 1/7] vsprintf: Add [v]sprintf_end() Alejandro Colomar
2025-07-10 21:30 ` [RFC v5 2/7] stacktrace, stackdepot: Add sprintf_end()-like variants of functions Alejandro Colomar
@ 2025-07-10 21:30 ` Alejandro Colomar
2025-07-10 21:31 ` [RFC v5 4/7] array_size.h: Add ENDOF() Alejandro Colomar
` (3 subsequent siblings)
6 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 21:30 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski, Sven Schnelle,
Heiko Carstens, Tvrtko Ursulin, Christophe JAILLET, Hyeonggon Yoo,
Chao Yu
While doing this, I detected some anomalies in the existing code:
mm/kfence/kfence_test.c:
- The last call to scnprintf() did increment 'cur', but it's
unused after that, so it was dead code. I've removed the dead
code in this patch.
- 'end' is calculated as
end = &expect[0][sizeof(expect[0] - 1)];
However, the '-1' doesn't seem to be necessary. When passing
$2 to scnprintf(), the size was specified as 'end - cur'.
And scnprintf() --just like snprintf(3)--, won't write more
than $2 bytes (including the null byte). That means that
scnprintf() wouldn't write more than
&expect[0][sizeof(expect[0]) - 1] - expect[0]
which simplifies to
sizeof(expect[0]) - 1
bytes. But we have sizeof(expect[0]) bytes available, so
we're wasting one byte entirely. This is a benign off-by-one
bug. The two occurrences of this bug will be fixed in a
following patch in this series.
mm/kmsan/kmsan_test.c:
The same benign off-by-one bug calculating the remaining size.
mm/mempolicy.c:
This file uses the 'p += snprintf()' anti-pattern. That will
overflow the pointer on truncation, which has undefined
behavior. Using sprintf_end(), this bug is fixed.
As in the previous file, here there was also dead code in the
last scnprintf() call, by incrementing a pointer that is not
used after the call. I've removed the dead code.
mm/page_owner.c:
Within print_page_owner(), there are some calls to scnprintf(),
which do report truncation. And then there are other calls to
snprintf(), where we handle errors (there are two 'goto err').
I've kept the existing error handling, as I trust it's there for
a good reason (i.e., we may want to avoid calling
print_page_owner_memcg() if we truncated before). Please review
if this amount of error handling is the right one, or if we want
to add or remove some. For sprintf_end(), a single test for
null after the last call is enough to detect truncation.
mm/slub.c:
Again, the 'p += snprintf()' anti-pattern. This is UB, and by
using sprintf_end() we've fixed the bug.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Sven Schnelle <svens@linux.ibm.com>
Cc: Marco Elver <elver@google.com>
Cc: Heiko Carstens <hca@linux.ibm.com>
Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: David Rientjes <rientjes@google.com>
Cc: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
Cc: Hyeonggon Yoo <42.hyeyoo@gmail.com>
Cc: Chao Yu <chao.yu@oppo.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/kfence/kfence_test.c | 24 ++++++++++++------------
mm/kmsan/kmsan_test.c | 4 ++--
mm/mempolicy.c | 18 +++++++++---------
mm/page_owner.c | 32 +++++++++++++++++---------------
mm/slub.c | 5 +++--
5 files changed, 43 insertions(+), 40 deletions(-)
diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
index 00034e37bc9f..bae382eca4ab 100644
--- a/mm/kfence/kfence_test.c
+++ b/mm/kfence/kfence_test.c
@@ -113,26 +113,26 @@ static bool report_matches(const struct expect_report *r)
end = &expect[0][sizeof(expect[0]) - 1];
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds %s",
+ cur = sprintf_end(cur, end, "BUG: KFENCE: out-of-bounds %s",
get_access_type(r));
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free %s",
+ cur = sprintf_end(cur, end, "BUG: KFENCE: use-after-free %s",
get_access_type(r));
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: memory corruption");
+ cur = sprintf_end(cur, end, "BUG: KFENCE: memory corruption");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid %s",
+ cur = sprintf_end(cur, end, "BUG: KFENCE: invalid %s",
get_access_type(r));
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid free");
+ cur = sprintf_end(cur, end, "BUG: KFENCE: invalid free");
break;
}
- scnprintf(cur, end - cur, " in %pS", r->fn);
+ sprintf_end(cur, end, " in %pS", r->fn);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expect[0], '+');
if (cur)
@@ -144,26 +144,26 @@ static bool report_matches(const struct expect_report *r)
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "Out-of-bounds %s at", get_access_type(r));
+ cur = sprintf_end(cur, end, "Out-of-bounds %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "Use-after-free %s at", get_access_type(r));
+ cur = sprintf_end(cur, end, "Use-after-free %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "Corrupted memory at");
+ cur = sprintf_end(cur, end, "Corrupted memory at");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "Invalid %s at", get_access_type(r));
+ cur = sprintf_end(cur, end, "Invalid %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "Invalid free of");
+ cur = sprintf_end(cur, end, "Invalid free of");
break;
}
- cur += scnprintf(cur, end - cur, " 0x%p", (void *)addr);
+ sprintf_end(cur, end, " 0x%p", (void *)addr);
spin_lock_irqsave(&observed.lock, flags);
if (!report_available())
diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
index 9733a22c46c1..e48ca1972ff3 100644
--- a/mm/kmsan/kmsan_test.c
+++ b/mm/kmsan/kmsan_test.c
@@ -107,9 +107,9 @@ static bool report_matches(const struct expect_report *r)
cur = expected_header;
end = &expected_header[sizeof(expected_header) - 1];
- cur += scnprintf(cur, end - cur, "BUG: KMSAN: %s", r->error_type);
+ cur = sprintf_end(cur, end, "BUG: KMSAN: %s", r->error_type);
- scnprintf(cur, end - cur, " in %s", r->symbol);
+ sprintf_end(cur, end, " in %s", r->symbol);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expected_header, '+');
if (cur)
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index b28a1e6ae096..6beb2710f97c 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -3359,6 +3359,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
{
char *p = buffer;
+ char *e = buffer + maxlen;
nodemask_t nodes = NODE_MASK_NONE;
unsigned short mode = MPOL_DEFAULT;
unsigned short flags = 0;
@@ -3384,33 +3385,32 @@ void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
break;
default:
WARN_ON_ONCE(1);
- snprintf(p, maxlen, "unknown");
+ sprintf_end(p, e, "unknown");
return;
}
- p += snprintf(p, maxlen, "%s", policy_modes[mode]);
+ p = sprintf_end(p, e, "%s", policy_modes[mode]);
if (flags & MPOL_MODE_FLAGS) {
- p += snprintf(p, buffer + maxlen - p, "=");
+ p = sprintf_end(p, e, "=");
/*
* Static and relative are mutually exclusive.
*/
if (flags & MPOL_F_STATIC_NODES)
- p += snprintf(p, buffer + maxlen - p, "static");
+ p = sprintf_end(p, e, "static");
else if (flags & MPOL_F_RELATIVE_NODES)
- p += snprintf(p, buffer + maxlen - p, "relative");
+ p = sprintf_end(p, e, "relative");
if (flags & MPOL_F_NUMA_BALANCING) {
if (!is_power_of_2(flags & MPOL_MODE_FLAGS))
- p += snprintf(p, buffer + maxlen - p, "|");
- p += snprintf(p, buffer + maxlen - p, "balancing");
+ p = sprintf_end(p, e, "|");
+ p = sprintf_end(p, e, "balancing");
}
}
if (!nodes_empty(nodes))
- p += scnprintf(p, buffer + maxlen - p, ":%*pbl",
- nodemask_pr_args(&nodes));
+ sprintf_end(p, e, ":%*pbl", nodemask_pr_args(&nodes));
}
#ifdef CONFIG_SYSFS
diff --git a/mm/page_owner.c b/mm/page_owner.c
index cc4a6916eec6..c00b3be01540 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -496,7 +496,7 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
/*
* Looking for memcg information and print it out
*/
-static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
+static inline char *print_page_owner_memcg(char *p, const char end[0],
struct page *page)
{
#ifdef CONFIG_MEMCG
@@ -511,8 +511,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
goto out_unlock;
if (memcg_data & MEMCG_DATA_OBJEXTS)
- ret += scnprintf(kbuf + ret, count - ret,
- "Slab cache page\n");
+ p = sprintf_end(p, end, "Slab cache page\n");
memcg = page_memcg_check(page);
if (!memcg)
@@ -520,7 +519,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
online = (memcg->css.flags & CSS_ONLINE);
cgroup_name(memcg->css.cgroup, name, sizeof(name));
- ret += scnprintf(kbuf + ret, count - ret,
+ p = sprintf_end(p, end,
"Charged %sto %smemcg %s\n",
PageMemcgKmem(page) ? "(via objcg) " : "",
online ? "" : "offline ",
@@ -529,7 +528,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
rcu_read_unlock();
#endif /* CONFIG_MEMCG */
- return ret;
+ return p;
}
static ssize_t
@@ -538,14 +537,16 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
depot_stack_handle_t handle)
{
int ret, pageblock_mt, page_mt;
- char *kbuf;
+ char *kbuf, *p, *e;
count = min_t(size_t, count, PAGE_SIZE);
kbuf = kmalloc(count, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
- ret = scnprintf(kbuf, count,
+ p = kbuf;
+ e = kbuf + count;
+ p = sprintf_end(p, e,
"Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n",
page_owner->order, page_owner->gfp_mask,
&page_owner->gfp_mask, page_owner->pid,
@@ -555,7 +556,7 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
/* Print information relevant to grouping pages by mobility */
pageblock_mt = get_pageblock_migratetype(page);
page_mt = gfp_migratetype(page_owner->gfp_mask);
- ret += scnprintf(kbuf + ret, count - ret,
+ p = sprintf_end(p, e,
"PFN 0x%lx type %s Block %lu type %s Flags %pGp\n",
pfn,
migratetype_names[page_mt],
@@ -563,22 +564,23 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
migratetype_names[pageblock_mt],
&page->flags);
- ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
- if (ret >= count)
- goto err;
+ p = stack_depot_sprint_end(handle, p, e, 0);
+ if (p == NULL)
+ goto err; // XXX: Should we remove this error handling?
if (page_owner->last_migrate_reason != -1) {
- ret += scnprintf(kbuf + ret, count - ret,
+ p = sprintf_end(p, e,
"Page has been migrated, last migrate reason: %s\n",
migrate_reason_names[page_owner->last_migrate_reason]);
}
- ret = print_page_owner_memcg(kbuf, count, ret, page);
+ p = print_page_owner_memcg(p, e, page);
- ret += snprintf(kbuf + ret, count - ret, "\n");
- if (ret >= count)
+ p = sprintf_end(p, e, "\n");
+ if (p == NULL)
goto err;
+ ret = p - kbuf;
if (copy_to_user(buf, kbuf, ret))
ret = -EFAULT;
diff --git a/mm/slub.c b/mm/slub.c
index be8b09e09d30..dcc857676857 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -7451,6 +7451,7 @@ static char *create_unique_id(struct kmem_cache *s)
{
char *name = kmalloc(ID_STR_LENGTH, GFP_KERNEL);
char *p = name;
+ char *e = name + ID_STR_LENGTH;
if (!name)
return ERR_PTR(-ENOMEM);
@@ -7475,9 +7476,9 @@ static char *create_unique_id(struct kmem_cache *s)
*p++ = 'A';
if (p != name + 1)
*p++ = '-';
- p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
+ p = sprintf_end(p, e, "%07u", s->size);
- if (WARN_ON(p > name + ID_STR_LENGTH - 1)) {
+ if (WARN_ON(p == NULL)) {
kfree(name);
return ERR_PTR(-EINVAL);
}
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v5 4/7] array_size.h: Add ENDOF()
2025-07-10 21:30 ` [RFC v5 0/7] Add and use sprintf_{end,array}() instead of less ergonomic APIs Alejandro Colomar
` (2 preceding siblings ...)
2025-07-10 21:30 ` [RFC v5 3/7] mm: Use sprintf_end() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-10 21:31 ` Alejandro Colomar
2025-07-10 21:31 ` [RFC v5 5/7] mm: Fix benign off-by-one bugs Alejandro Colomar
` (2 subsequent siblings)
6 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 21:31 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
This macro is useful to calculate the second argument to sprintf_end(),
avoiding off-by-one bugs.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/array_size.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/include/linux/array_size.h b/include/linux/array_size.h
index 06d7d83196ca..781bdb70d939 100644
--- a/include/linux/array_size.h
+++ b/include/linux/array_size.h
@@ -10,4 +10,10 @@
*/
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
+/**
+ * ENDOF - get a pointer to one past the last element in array @a
+ * @a: array
+ */
+#define ENDOF(a) (a + ARRAY_SIZE(a))
+
#endif /* _LINUX_ARRAY_SIZE_H */
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v5 5/7] mm: Fix benign off-by-one bugs
2025-07-10 21:30 ` [RFC v5 0/7] Add and use sprintf_{end,array}() instead of less ergonomic APIs Alejandro Colomar
` (3 preceding siblings ...)
2025-07-10 21:31 ` [RFC v5 4/7] array_size.h: Add ENDOF() Alejandro Colomar
@ 2025-07-10 21:31 ` Alejandro Colomar
2025-07-10 21:31 ` [RFC v5 6/7] sprintf: Add [v]sprintf_array() Alejandro Colomar
2025-07-10 21:31 ` [RFC v5 7/7] mm: Use [v]sprintf_array() to avoid specifying the array size Alejandro Colomar
6 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 21:31 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski, Jann Horn
We were wasting a byte due to an off-by-one bug. s[c]nprintf()
doesn't write more than $2 bytes including the null byte, so trying to
pass 'size-1' there is wasting one byte. Now that we use sprintf_end(),
the situation isn't different: sprintf_end() will stop writing *before*
'end' --that is, at most the terminating null byte will be written at
'end-1'--.
Acked-by: Marco Elver <elver@google.com>
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Jann Horn <jannh@google.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/kfence/kfence_test.c | 4 ++--
mm/kmsan/kmsan_test.c | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
index bae382eca4ab..c635aa9d478b 100644
--- a/mm/kfence/kfence_test.c
+++ b/mm/kfence/kfence_test.c
@@ -110,7 +110,7 @@ static bool report_matches(const struct expect_report *r)
/* Title */
cur = expect[0];
- end = &expect[0][sizeof(expect[0]) - 1];
+ end = ENDOF(expect[0]);
switch (r->type) {
case KFENCE_ERROR_OOB:
cur = sprintf_end(cur, end, "BUG: KFENCE: out-of-bounds %s",
@@ -140,7 +140,7 @@ static bool report_matches(const struct expect_report *r)
/* Access information */
cur = expect[1];
- end = &expect[1][sizeof(expect[1]) - 1];
+ end = ENDOF(expect[1]);
switch (r->type) {
case KFENCE_ERROR_OOB:
diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
index e48ca1972ff3..9bda55992e3d 100644
--- a/mm/kmsan/kmsan_test.c
+++ b/mm/kmsan/kmsan_test.c
@@ -105,7 +105,7 @@ static bool report_matches(const struct expect_report *r)
/* Title */
cur = expected_header;
- end = &expected_header[sizeof(expected_header) - 1];
+ end = ENDOF(expected_header);
cur = sprintf_end(cur, end, "BUG: KMSAN: %s", r->error_type);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-10 21:30 ` [RFC v5 0/7] Add and use sprintf_{end,array}() instead of less ergonomic APIs Alejandro Colomar
` (4 preceding siblings ...)
2025-07-10 21:31 ` [RFC v5 5/7] mm: Fix benign off-by-one bugs Alejandro Colomar
@ 2025-07-10 21:31 ` Alejandro Colomar
2025-07-10 21:58 ` Linus Torvalds
2025-07-10 21:31 ` [RFC v5 7/7] mm: Use [v]sprintf_array() to avoid specifying the array size Alejandro Colomar
6 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 21:31 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
These macros take the end of the array argument implicitly to avoid
programmer mistakes. This guarantees that the input is an array, unlike
snprintf(buf, sizeof(buf), ...);
which is dangerous if the programmer passes a pointer instead of an
array.
These macros are essentially the same as the 2-argument version of
strscpy(), but with a formatted string, and returning a pointer to the
terminating '\0' (or NULL, on error).
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/sprintf.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/include/linux/sprintf.h b/include/linux/sprintf.h
index a0dc35574521..8576a543e62c 100644
--- a/include/linux/sprintf.h
+++ b/include/linux/sprintf.h
@@ -4,6 +4,10 @@
#include <linux/compiler_attributes.h>
#include <linux/types.h>
+#include <linux/array_size.h>
+
+#define sprintf_array(a, fmt, ...) sprintf_end(a, ENDOF(a), fmt, ##__VA_ARGS__)
+#define vsprintf_array(a, fmt, ap) vsprintf_end(a, ENDOF(a), fmt, ap)
int num_to_str(char *buf, int size, unsigned long long num, unsigned int width);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v5 7/7] mm: Use [v]sprintf_array() to avoid specifying the array size
2025-07-10 21:30 ` [RFC v5 0/7] Add and use sprintf_{end,array}() instead of less ergonomic APIs Alejandro Colomar
` (5 preceding siblings ...)
2025-07-10 21:31 ` [RFC v5 6/7] sprintf: Add [v]sprintf_array() Alejandro Colomar
@ 2025-07-10 21:31 ` Alejandro Colomar
6 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 21:31 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/backing-dev.c | 2 +-
mm/cma.c | 4 ++--
mm/cma_debug.c | 2 +-
mm/hugetlb.c | 3 +--
mm/hugetlb_cgroup.c | 2 +-
mm/hugetlb_cma.c | 2 +-
mm/kasan/report.c | 3 +--
mm/memblock.c | 4 ++--
mm/percpu.c | 2 +-
mm/shrinker_debug.c | 2 +-
mm/zswap.c | 2 +-
11 files changed, 13 insertions(+), 15 deletions(-)
diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index 783904d8c5ef..c4e588135aea 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -1090,7 +1090,7 @@ int bdi_register_va(struct backing_dev_info *bdi, const char *fmt, va_list args)
if (bdi->dev) /* The driver needs to use separate queues per device */
return 0;
- vsnprintf(bdi->dev_name, sizeof(bdi->dev_name), fmt, args);
+ vsprintf_array(bdi->dev_name, fmt, args);
dev = device_create(&bdi_class, NULL, MKDEV(0, 0), bdi, bdi->dev_name);
if (IS_ERR(dev))
return PTR_ERR(dev);
diff --git a/mm/cma.c b/mm/cma.c
index c04be488b099..61d97a387670 100644
--- a/mm/cma.c
+++ b/mm/cma.c
@@ -237,9 +237,9 @@ static int __init cma_new_area(const char *name, phys_addr_t size,
cma_area_count++;
if (name)
- snprintf(cma->name, CMA_MAX_NAME, "%s", name);
+ sprintf_array(cma->name, "%s", name);
else
- snprintf(cma->name, CMA_MAX_NAME, "cma%d\n", cma_area_count);
+ sprintf_array(cma->name, "cma%d\n", cma_area_count);
cma->available_count = cma->count = size >> PAGE_SHIFT;
cma->order_per_bit = order_per_bit;
diff --git a/mm/cma_debug.c b/mm/cma_debug.c
index fdf899532ca0..751eae9f6364 100644
--- a/mm/cma_debug.c
+++ b/mm/cma_debug.c
@@ -186,7 +186,7 @@ static void cma_debugfs_add_one(struct cma *cma, struct dentry *root_dentry)
rangedir = debugfs_create_dir("ranges", tmp);
for (r = 0; r < cma->nranges; r++) {
cmr = &cma->ranges[r];
- snprintf(rdirname, sizeof(rdirname), "%d", r);
+ sprintf_array(rdirname, "%d", r);
dir = debugfs_create_dir(rdirname, rangedir);
debugfs_create_file("base_pfn", 0444, dir,
&cmr->base_pfn, &cma_debugfs_fops);
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 6a3cf7935c14..70acc8b3cbb8 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -4780,8 +4780,7 @@ void __init hugetlb_add_hstate(unsigned int order)
for (i = 0; i < MAX_NUMNODES; ++i)
INIT_LIST_HEAD(&h->hugepage_freelists[i]);
INIT_LIST_HEAD(&h->hugepage_activelist);
- snprintf(h->name, HSTATE_NAME_LEN, "hugepages-%lukB",
- huge_page_size(h)/SZ_1K);
+ sprintf_array(h->name, "hugepages-%lukB", huge_page_size(h)/SZ_1K);
parsed_hstate = h;
}
diff --git a/mm/hugetlb_cgroup.c b/mm/hugetlb_cgroup.c
index 58e895f3899a..0953cea93759 100644
--- a/mm/hugetlb_cgroup.c
+++ b/mm/hugetlb_cgroup.c
@@ -822,7 +822,7 @@ hugetlb_cgroup_cfttypes_init(struct hstate *h, struct cftype *cft,
for (i = 0; i < tmpl_size; cft++, tmpl++, i++) {
*cft = *tmpl;
/* rebuild the name */
- snprintf(cft->name, MAX_CFTYPE_NAME, "%s.%s", buf, tmpl->name);
+ sprintf_array(cft->name, "%s.%s", buf, tmpl->name);
/* rebuild the private */
cft->private = MEMFILE_PRIVATE(idx, tmpl->private);
/* rebuild the file_offset */
diff --git a/mm/hugetlb_cma.c b/mm/hugetlb_cma.c
index e0f2d5c3a84c..bae82a97a43c 100644
--- a/mm/hugetlb_cma.c
+++ b/mm/hugetlb_cma.c
@@ -211,7 +211,7 @@ void __init hugetlb_cma_reserve(int order)
size = round_up(size, PAGE_SIZE << order);
- snprintf(name, sizeof(name), "hugetlb%d", nid);
+ sprintf_array(name, "hugetlb%d", nid);
/*
* Note that 'order per bit' is based on smallest size that
* may be returned to CMA allocator in the case of
diff --git a/mm/kasan/report.c b/mm/kasan/report.c
index 8357e1a33699..3b40225e7873 100644
--- a/mm/kasan/report.c
+++ b/mm/kasan/report.c
@@ -486,8 +486,7 @@ static void print_memory_metadata(const void *addr)
char buffer[4 + (BITS_PER_LONG / 8) * 2];
char metadata[META_BYTES_PER_ROW];
- snprintf(buffer, sizeof(buffer),
- (i == 0) ? ">%px: " : " %px: ", row);
+ sprintf_array(buffer, (i == 0) ? ">%px: " : " %px: ", row);
/*
* We should not pass a shadow pointer to generic
diff --git a/mm/memblock.c b/mm/memblock.c
index 0e9ebb8aa7fe..3eea7a177330 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -2021,7 +2021,7 @@ static void __init_memblock memblock_dump(struct memblock_type *type)
flags = rgn->flags;
#ifdef CONFIG_NUMA
if (numa_valid_node(memblock_get_region_node(rgn)))
- snprintf(nid_buf, sizeof(nid_buf), " on node %d",
+ sprintf_array(nid_buf, " on node %d",
memblock_get_region_node(rgn));
#endif
pr_info(" %s[%#x]\t[%pa-%pa], %pa bytes%s flags: %#x\n",
@@ -2379,7 +2379,7 @@ int reserve_mem_release_by_name(const char *name)
start = phys_to_virt(map->start);
end = start + map->size - 1;
- snprintf(buf, sizeof(buf), "reserve_mem:%s", name);
+ sprintf_array(buf, "reserve_mem:%s", name);
free_reserved_area(start, end, 0, buf);
map->size = 0;
diff --git a/mm/percpu.c b/mm/percpu.c
index b35494c8ede2..a467102c2405 100644
--- a/mm/percpu.c
+++ b/mm/percpu.c
@@ -3186,7 +3186,7 @@ int __init pcpu_page_first_chunk(size_t reserved_size, pcpu_fc_cpu_to_node_fn_t
int upa;
int nr_g0_units;
- snprintf(psize_str, sizeof(psize_str), "%luK", PAGE_SIZE >> 10);
+ sprintf_array(psize_str, "%luK", PAGE_SIZE >> 10);
ai = pcpu_build_alloc_info(reserved_size, 0, PAGE_SIZE, NULL);
if (IS_ERR(ai))
diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
index 20eaee3e97f7..f529ac29557c 100644
--- a/mm/shrinker_debug.c
+++ b/mm/shrinker_debug.c
@@ -176,7 +176,7 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
return id;
shrinker->debugfs_id = id;
- snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
+ sprintf_array(buf, "%s-%d", shrinker->name, id);
/* create debugfs entry */
entry = debugfs_create_dir(buf, shrinker_debugfs_root);
diff --git a/mm/zswap.c b/mm/zswap.c
index 204fb59da33c..e66b5c5b1ecf 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -271,7 +271,7 @@ static struct zswap_pool *zswap_pool_create(char *type, char *compressor)
return NULL;
/* unique name for each pool specifically required by zsmalloc */
- snprintf(name, 38, "zswap%x", atomic_inc_return(&zswap_pools_count));
+ sprintf_array(name, "zswap%x", atomic_inc_return(&zswap_pools_count));
pool->zpool = zpool_create_pool(type, name, gfp);
if (!pool->zpool) {
pr_err("%s zpool not available\n", type);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-10 21:31 ` [RFC v5 6/7] sprintf: Add [v]sprintf_array() Alejandro Colomar
@ 2025-07-10 21:58 ` Linus Torvalds
2025-07-10 23:23 ` Alejandro Colomar
2025-07-11 6:05 ` Martin Uecker
0 siblings, 2 replies; 98+ messages in thread
From: Linus Torvalds @ 2025-07-10 21:58 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Al Viro,
Martin Uecker, Sam James, Andrew Pinski
On Thu, 10 Jul 2025 at 14:31, Alejandro Colomar <alx@kernel.org> wrote:
>
> These macros are essentially the same as the 2-argument version of
> strscpy(), but with a formatted string, and returning a pointer to the
> terminating '\0' (or NULL, on error).
No.
Stop this garbage.
You took my suggestion, and then you messed it up.
Your version of sprintf_array() is broken. It evaluates 'a' twice.
Because unlike ARRAY_SIZE(), your broken ENDOF() macro evaluates the
argument.
And you did it for no reason I can see. You said that you wanted to
return the end of the resulting string, but the fact is, not a single
user seems to care, and honestly, I think it would be wrong to care.
The size of the result is likely the more useful thing, or you could
even make these 'void' or something.
But instead you made the macro be dangerous to use.
This kind of churn is WRONG. It _looks_ like a cleanup that doesn't
change anything, but then it has subtle bugs that will come and bite
us later because you did things wrong.
I'm NAK'ing all of this. This is BAD. Cleanup patches had better be
fundamentally correct, not introduce broken "helpers" that will make
for really subtle bugs.
Maybe nobody ever ends up having that first argument with a side
effect. MAYBE. It's still very very wrong.
Linus
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v4 6/7] sprintf: Add [V]SPRINTF_END()
2025-07-10 21:21 ` Alejandro Colomar
@ 2025-07-10 22:08 ` Linus Torvalds
0 siblings, 0 replies; 98+ messages in thread
From: Linus Torvalds @ 2025-07-10 22:08 UTC (permalink / raw)
To: Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Al Viro
On Thu, 10 Jul 2025 at 14:21, Alejandro Colomar <alx@kernel.org> wrote:
>
> So, I prefer my implementation because it returns NULL on truncation.
As I pointed out, your implementation is WRONG.
If you want to return an error on truncation, do it right. Not by
returning NULL, but by actually returning an error.
For example, in the kernel, we finally fixed 'strcpy()'. After about a
million different versions of 'copy a string' where every single
version was complete garbage, we ended up with 'strscpy()'. Yeah, the
name isn't lovely, but the *use* of it is:
- it returns the length of the result for people who want it - which
is by far the most common thing people want
- it returns an actual honest-to-goodness error code if something
overflowed, instead of the absoilutely horrible "source length" of the
string that strlcpy() does and which is fundamentally broken (because
it requires that you walk *past* the end of the source,
Christ-on-a-stick what a broken interface)
- it can take an array as an argument (without the need for another
name - see my earlier argument about not making up new names by just
having generics)
Now, it has nasty naming (exactly the kind of 'add random character'
naming that I was arguing against), and that comes from so many
different broken versions until we hit on something that works.
strncpy is horrible garbage. strlcpy is even worse. strscpy actually
works and so far hasn't caused issues (there's a 'pad' version for the
very rare situation where you want 'strncpy-like' padding, but it
still guarantees NUL-termination, and still has a good return value).
Let's agree to *not* make horrible garbage when making up new versions
of sprintf.
Linus
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-10 21:58 ` Linus Torvalds
@ 2025-07-10 23:23 ` Alejandro Colomar
2025-07-10 23:24 ` Alejandro Colomar
` (2 more replies)
2025-07-11 6:05 ` Martin Uecker
1 sibling, 3 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 23:23 UTC (permalink / raw)
To: Linus Torvalds
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Al Viro,
Martin Uecker, Sam James, Andrew Pinski
[-- Attachment #1: Type: text/plain, Size: 5082 bytes --]
Hi Linus,
[I'll reply to both of your emails at once]
On Thu, Jul 10, 2025 at 02:58:24PM -0700, Linus Torvalds wrote:
> You took my suggestion, and then you messed it up.
>
> Your version of sprintf_array() is broken. It evaluates 'a' twice.
> Because unlike ARRAY_SIZE(), your broken ENDOF() macro evaluates the
> argument.
An array has no issue being evaluated twice (unless it's a VLA). On the
other hand, I agree it's better to not do that in the first place.
My bad for forgetting about it. Sorry.
On Thu, Jul 10, 2025 at 03:08:29PM -0700, Linus Torvalds wrote:
> If you want to return an error on truncation, do it right. Not by
> returning NULL, but by actually returning an error.
Okay.
> For example, in the kernel, we finally fixed 'strcpy()'. After about a
> million different versions of 'copy a string' where every single
> version was complete garbage, we ended up with 'strscpy()'. Yeah, the
> name isn't lovely, but the *use* of it is:
I have implemented the same thing in shadow, called strtcpy() (T for
truncation). (With the difference that we read the string twice, since
we don't care about threads.)
I also plan to propose standardization of that one in ISO C.
> - it returns the length of the result for people who want it - which
> is by far the most common thing people want
Agree.
> - it returns an actual honest-to-goodness error code if something
> overflowed, instead of the absoilutely horrible "source length" of the
> string that strlcpy() does and which is fundamentally broken (because
> it requires that you walk *past* the end of the source,
> Christ-on-a-stick what a broken interface)
Agree.
> - it can take an array as an argument (without the need for another
> name - see my earlier argument about not making up new names by just
> having generics)
We can't make the same thing with sprintf() variants because they're
variadic, so you can't count the number of arguments. And since the
'end' argument is of the same type as the formatted string, we can't
do it with _Generic reliably either.
> Now, it has nasty naming (exactly the kind of 'add random character'
> naming that I was arguing against), and that comes from so many
> different broken versions until we hit on something that works.
>
> strncpy is horrible garbage. strlcpy is even worse. strscpy actually
> works and so far hasn't caused issues (there's a 'pad' version for the
> very rare situation where you want 'strncpy-like' padding, but it
> still guarantees NUL-termination, and still has a good return value).
Agree.
> Let's agree to *not* make horrible garbage when making up new versions
> of sprintf.
Agree. I indeed introduced the mistake accidentally in v4, after you
complained of having too many functions, as I was introducing not one
but two APIs: seprintf() and stprintf(), where seprintf() is what now
we're calling sprintf_end(), and stprintf() we could call it
sprintf_trunc(). So I did the mistake by trying to reduce the number of
functions to just one, which is wrong.
So, maybe I should go back to those functions, and just give them good
names.
What do you think of the following?
#define sprintf_array(a, ...) sprintf_trunc(a, ARRAY_SIZE(a), __VA_ARGS__)
#define vsprintf_array(a, ap) vsprintf_trunc(a, ARRAY_SIZE(a), ap)
char *sprintf_end(char *p, const char end[0], const char *fmt, ...);
char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args);
int sprintf_trunc(char *buf, size_t size, const char *fmt, ...);
int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args);
char *sprintf_end(char *p, const char end[0], const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
p = vseprintf(p, end, fmt, args);
va_end(args);
return p;
}
char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args)
{
int len;
if (unlikely(p == NULL))
return NULL;
len = vsprintf_trunc(p, end - p, fmt, args);
if (unlikely(len < 0))
return NULL;
return p + len;
}
int sprintf_trunc(char *buf, size_t size, const char *fmt, ...)
{
va_list args;
int len;
va_start(args, fmt);
len = vstprintf(buf, size, fmt, args);
va_end(args);
return len;
}
int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args)
{
int len;
if (WARN_ON_ONCE(size == 0 || size > INT_MAX))
return -EOVERFLOW;
len = vsnprintf(buf, size, fmt, args);
if (unlikely(len >= size))
return -E2BIG;
return len;
}
sprintf_trunc() is like strscpy(), but with a formatted string. It
could replace uses of s[c]nprintf() where there's a single call (no
chained calls).
sprintf_array() is like the 2-argument version of strscpy(). It could
replace s[c]nprintf() calls where there's no chained calls, where the
input is an array.
sprintf_end() would replace the chained calls.
Does this sound good to you?
Cheers,
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-10 23:23 ` Alejandro Colomar
@ 2025-07-10 23:24 ` Alejandro Colomar
2025-07-11 0:19 ` Alejandro Colomar
2025-07-11 17:43 ` David Laight
2 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-10 23:24 UTC (permalink / raw)
To: Linus Torvalds
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Al Viro,
Martin Uecker, Sam James, Andrew Pinski
[-- Attachment #1: Type: text/plain, Size: 5590 bytes --]
On Fri, Jul 11, 2025 at 01:23:56AM +0200, Alejandro Colomar wrote:
> Hi Linus,
>
> [I'll reply to both of your emails at once]
>
> On Thu, Jul 10, 2025 at 02:58:24PM -0700, Linus Torvalds wrote:
> > You took my suggestion, and then you messed it up.
> >
> > Your version of sprintf_array() is broken. It evaluates 'a' twice.
> > Because unlike ARRAY_SIZE(), your broken ENDOF() macro evaluates the
> > argument.
>
> An array has no issue being evaluated twice (unless it's a VLA). On the
> other hand, I agree it's better to not do that in the first place.
> My bad for forgetting about it. Sorry.
>
> On Thu, Jul 10, 2025 at 03:08:29PM -0700, Linus Torvalds wrote:
> > If you want to return an error on truncation, do it right. Not by
> > returning NULL, but by actually returning an error.
>
> Okay.
>
> > For example, in the kernel, we finally fixed 'strcpy()'. After about a
> > million different versions of 'copy a string' where every single
> > version was complete garbage, we ended up with 'strscpy()'. Yeah, the
> > name isn't lovely, but the *use* of it is:
>
> I have implemented the same thing in shadow, called strtcpy() (T for
> truncation). (With the difference that we read the string twice, since
> we don't care about threads.)
>
> I also plan to propose standardization of that one in ISO C.
>
> > - it returns the length of the result for people who want it - which
> > is by far the most common thing people want
>
> Agree.
>
> > - it returns an actual honest-to-goodness error code if something
> > overflowed, instead of the absoilutely horrible "source length" of the
> > string that strlcpy() does and which is fundamentally broken (because
> > it requires that you walk *past* the end of the source,
> > Christ-on-a-stick what a broken interface)
>
> Agree.
>
> > - it can take an array as an argument (without the need for another
> > name - see my earlier argument about not making up new names by just
> > having generics)
>
> We can't make the same thing with sprintf() variants because they're
> variadic, so you can't count the number of arguments. And since the
> 'end' argument is of the same type as the formatted string, we can't
> do it with _Generic reliably either.
>
> > Now, it has nasty naming (exactly the kind of 'add random character'
> > naming that I was arguing against), and that comes from so many
> > different broken versions until we hit on something that works.
> >
> > strncpy is horrible garbage. strlcpy is even worse. strscpy actually
> > works and so far hasn't caused issues (there's a 'pad' version for the
> > very rare situation where you want 'strncpy-like' padding, but it
> > still guarantees NUL-termination, and still has a good return value).
>
> Agree.
>
> > Let's agree to *not* make horrible garbage when making up new versions
> > of sprintf.
>
> Agree. I indeed introduced the mistake accidentally in v4, after you
> complained of having too many functions, as I was introducing not one
> but two APIs: seprintf() and stprintf(), where seprintf() is what now
> we're calling sprintf_end(), and stprintf() we could call it
> sprintf_trunc(). So I did the mistake by trying to reduce the number of
> functions to just one, which is wrong.
>
> So, maybe I should go back to those functions, and just give them good
> names.
>
> What do you think of the following?
>
> #define sprintf_array(a, ...) sprintf_trunc(a, ARRAY_SIZE(a), __VA_ARGS__)
> #define vsprintf_array(a, ap) vsprintf_trunc(a, ARRAY_SIZE(a), ap)
>
> char *sprintf_end(char *p, const char end[0], const char *fmt, ...);
> char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args);
> int sprintf_trunc(char *buf, size_t size, const char *fmt, ...);
> int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args);
>
> char *sprintf_end(char *p, const char end[0], const char *fmt, ...)
> {
> va_list args;
>
> va_start(args, fmt);
> p = vseprintf(p, end, fmt, args);
Typo here. It's vsprintf_end().
> va_end(args);
>
> return p;
> }
>
> char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args)
> {
> int len;
>
> if (unlikely(p == NULL))
> return NULL;
>
> len = vsprintf_trunc(p, end - p, fmt, args);
> if (unlikely(len < 0))
> return NULL;
>
> return p + len;
> }
>
> int sprintf_trunc(char *buf, size_t size, const char *fmt, ...)
> {
> va_list args;
> int len;
>
> va_start(args, fmt);
> len = vstprintf(buf, size, fmt, args);
Typo here. It's vsprintf_trunc().
> va_end(args);
>
> return len;
> }
>
> int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args)
> {
> int len;
>
> if (WARN_ON_ONCE(size == 0 || size > INT_MAX))
> return -EOVERFLOW;
>
> len = vsnprintf(buf, size, fmt, args);
> if (unlikely(len >= size))
> return -E2BIG;
>
> return len;
> }
>
> sprintf_trunc() is like strscpy(), but with a formatted string. It
> could replace uses of s[c]nprintf() where there's a single call (no
> chained calls).
>
> sprintf_array() is like the 2-argument version of strscpy(). It could
> replace s[c]nprintf() calls where there's no chained calls, where the
> input is an array.
>
> sprintf_end() would replace the chained calls.
>
> Does this sound good to you?
>
>
> Cheers,
> Alex
>
> --
> <https://www.alejandro-colomar.es/>
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-10 23:23 ` Alejandro Colomar
2025-07-10 23:24 ` Alejandro Colomar
@ 2025-07-11 0:19 ` Alejandro Colomar
2025-07-11 17:43 ` David Laight
2 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-11 0:19 UTC (permalink / raw)
To: Linus Torvalds
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Al Viro,
Martin Uecker, Sam James, Andrew Pinski
[-- Attachment #1: Type: text/plain, Size: 5548 bytes --]
On Fri, Jul 11, 2025 at 01:23:56AM +0200, Alejandro Colomar wrote:
> Hi Linus,
>
> [I'll reply to both of your emails at once]
>
> On Thu, Jul 10, 2025 at 02:58:24PM -0700, Linus Torvalds wrote:
> > You took my suggestion, and then you messed it up.
> >
> > Your version of sprintf_array() is broken. It evaluates 'a' twice.
> > Because unlike ARRAY_SIZE(), your broken ENDOF() macro evaluates the
> > argument.
>
> An array has no issue being evaluated twice (unless it's a VLA). On the
> other hand, I agree it's better to not do that in the first place.
> My bad for forgetting about it. Sorry.
>
> On Thu, Jul 10, 2025 at 03:08:29PM -0700, Linus Torvalds wrote:
> > If you want to return an error on truncation, do it right. Not by
> > returning NULL, but by actually returning an error.
>
> Okay.
>
> > For example, in the kernel, we finally fixed 'strcpy()'. After about a
> > million different versions of 'copy a string' where every single
> > version was complete garbage, we ended up with 'strscpy()'. Yeah, the
> > name isn't lovely, but the *use* of it is:
>
> I have implemented the same thing in shadow, called strtcpy() (T for
> truncation). (With the difference that we read the string twice, since
> we don't care about threads.)
>
> I also plan to propose standardization of that one in ISO C.
>
> > - it returns the length of the result for people who want it - which
> > is by far the most common thing people want
>
> Agree.
>
> > - it returns an actual honest-to-goodness error code if something
> > overflowed, instead of the absoilutely horrible "source length" of the
> > string that strlcpy() does and which is fundamentally broken (because
> > it requires that you walk *past* the end of the source,
> > Christ-on-a-stick what a broken interface)
>
> Agree.
>
> > - it can take an array as an argument (without the need for another
> > name - see my earlier argument about not making up new names by just
> > having generics)
>
> We can't make the same thing with sprintf() variants because they're
> variadic, so you can't count the number of arguments. And since the
> 'end' argument is of the same type as the formatted string, we can't
> do it with _Generic reliably either.
>
> > Now, it has nasty naming (exactly the kind of 'add random character'
> > naming that I was arguing against), and that comes from so many
> > different broken versions until we hit on something that works.
> >
> > strncpy is horrible garbage. strlcpy is even worse. strscpy actually
> > works and so far hasn't caused issues (there's a 'pad' version for the
> > very rare situation where you want 'strncpy-like' padding, but it
> > still guarantees NUL-termination, and still has a good return value).
>
> Agree.
>
> > Let's agree to *not* make horrible garbage when making up new versions
> > of sprintf.
>
> Agree. I indeed introduced the mistake accidentally in v4, after you
> complained of having too many functions, as I was introducing not one
> but two APIs: seprintf() and stprintf(), where seprintf() is what now
> we're calling sprintf_end(), and stprintf() we could call it
> sprintf_trunc(). So I did the mistake by trying to reduce the number of
> functions to just one, which is wrong.
>
> So, maybe I should go back to those functions, and just give them good
> names.
>
> What do you think of the following?
>
> #define sprintf_array(a, ...) sprintf_trunc(a, ARRAY_SIZE(a), __VA_ARGS__)
> #define vsprintf_array(a, ap) vsprintf_trunc(a, ARRAY_SIZE(a), ap)
Typo: forgot the fmt argument.
>
> char *sprintf_end(char *p, const char end[0], const char *fmt, ...);
> char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args);
> int sprintf_trunc(char *buf, size_t size, const char *fmt, ...);
> int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args);
>
> char *sprintf_end(char *p, const char end[0], const char *fmt, ...)
> {
> va_list args;
>
> va_start(args, fmt);
> p = vseprintf(p, end, fmt, args);
> va_end(args);
>
> return p;
> }
>
> char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args)
> {
> int len;
>
> if (unlikely(p == NULL))
> return NULL;
>
> len = vsprintf_trunc(p, end - p, fmt, args);
> if (unlikely(len < 0))
> return NULL;
>
> return p + len;
> }
>
> int sprintf_trunc(char *buf, size_t size, const char *fmt, ...)
> {
> va_list args;
> int len;
>
> va_start(args, fmt);
> len = vstprintf(buf, size, fmt, args);
> va_end(args);
>
> return len;
> }
>
> int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args)
> {
> int len;
>
> if (WARN_ON_ONCE(size == 0 || size > INT_MAX))
> return -EOVERFLOW;
>
> len = vsnprintf(buf, size, fmt, args);
> if (unlikely(len >= size))
> return -E2BIG;
>
> return len;
> }
>
> sprintf_trunc() is like strscpy(), but with a formatted string. It
> could replace uses of s[c]nprintf() where there's a single call (no
> chained calls).
>
> sprintf_array() is like the 2-argument version of strscpy(). It could
> replace s[c]nprintf() calls where there's no chained calls, where the
> input is an array.
>
> sprintf_end() would replace the chained calls.
>
> Does this sound good to you?
>
>
> Cheers,
> Alex
>
> --
> <https://www.alejandro-colomar.es/>
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* [RFC v6 0/8] Add and use sprintf_{end,trunc,array}() instead of less ergonomic APIs
2025-07-06 17:37 ` [RFC v2 0/5] Add and use " Alejandro Colomar
` (7 preceding siblings ...)
2025-07-10 21:30 ` [RFC v5 0/7] Add and use sprintf_{end,array}() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-11 1:56 ` Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 1/8] vsprintf: Add [v]sprintf_trunc() Alejandro Colomar
` (7 more replies)
8 siblings, 8 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-11 1:56 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
Hi,
Changes in v6:
[As commented in private to Linus, I assume the NAK from Linus in v5
applies to the macro that evaluates twice. This is resolved in v6, so
I send assuming no NAKs to the overall patch set.]
- Don't try to have a single function. Have sprintf_end() for chaining
calls and sprintf_trunc() --which is the fmt version of strscpy()--
for single calls. Then sprintf_array() --which is the fmt version of
the 2-argument strscpy()-- for single calls with an array as input.
- Fix implementation of sprintf_array() to not evaluate twice.
These changes are essentially a roll-back to the general idea in v3,
except for the more explicit names.
Remaining questions:
- There are only 3 remaining calls to snprintf(3) under mm/. They are
just fine for now, which is why I didn't replace them. If anyone
wants to replace them, to get rid of all snprintf(3), we could that.
I think for now we can leave them, to minimize the churn.
$ grep -rnI snprintf mm/
mm/hugetlb_cgroup.c:674: snprintf(buf, size, "%luGB", hsize / SZ_1G);
mm/hugetlb_cgroup.c:676: snprintf(buf, size, "%luMB", hsize / SZ_1M);
mm/hugetlb_cgroup.c:678: snprintf(buf, size, "%luKB", hsize / SZ_1K);
They could be replaced by sprintf_trunc().
- There are only 2 remaining calls to the kernel's scnprintf(). This
one I would really like to get rid of. Also, those calls are quite
suspicious of not being what we want. Please do have a look at them
and confirm what's the appropriate behavior in the 2 cases when the
string is truncated or not copied at all. That code is very scary
for me to try to guess.
$ grep -rnI scnprintf mm/
mm/kfence/report.c:75: int len = scnprintf(buf, sizeof(buf), "%ps", (void *)stack_entries[skipnr]);
mm/kfence/kfence_test.mod.c:22: { 0x96848186, "scnprintf" },
mm/kmsan/report.c:42: len = scnprintf(buf, sizeof(buf), "%ps",
Apart from two calls, I see a string literal with that name. Please
let me know if I should do anything about it. I don't know what that
is.
- I think we should remove one error handling check in
"mm/page_owner.c" (marked with an XXX comment), but I'm not 100%
sure. Please confirm.
Other comments:
- This is still not complying to coding style. I'll keep it like that
while questions remain open.
- I've tested the tests under CONFIG_KFENCE_KUNIT_TEST=y, and this has
no regressions at all.
- With the current style of the sprintf_end() prototyope, this triggers
a diagnostic due to a GCC bug:
<https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108036>
It would be interesting to ask GCC to fix that bug. (Added relevant
GCC maintainers and contributors to CC in this cover letter.)
- The call sprintf_end(p, end, "") in lib/stackdepot.c, within
stack_depot_sprint_end(), produces a warning for having an empty
string. This could be replaced by a strcpy_end(p, end, "") if/when
we add that function.
For anyone new to the thread, sprintf_end() will be proposed for
standardization soon as seprintf():
<https://lore.kernel.org/linux-hardening/20250710024745.143955-1-alx@kernel.org/T/#u>
Have a lovely night!
Alex
Alejandro Colomar (8):
vsprintf: Add [v]sprintf_trunc()
vsprintf: Add [v]sprintf_end()
sprintf: Add [v]sprintf_array()
stacktrace, stackdepot: Add sprintf_end()-like variants of functions
mm: Use sprintf_end() instead of less ergonomic APIs
array_size.h: Add ENDOF()
mm: Fix benign off-by-one bugs
mm: Use [v]sprintf_array() to avoid specifying the array size
include/linux/array_size.h | 6 +++
include/linux/sprintf.h | 8 +++
include/linux/stackdepot.h | 13 +++++
include/linux/stacktrace.h | 3 ++
kernel/stacktrace.c | 28 ++++++++++
lib/stackdepot.c | 13 +++++
lib/vsprintf.c | 107 +++++++++++++++++++++++++++++++++++++
mm/backing-dev.c | 2 +-
mm/cma.c | 4 +-
mm/cma_debug.c | 2 +-
mm/hugetlb.c | 3 +-
mm/hugetlb_cgroup.c | 2 +-
mm/hugetlb_cma.c | 2 +-
mm/kasan/report.c | 3 +-
mm/kfence/kfence_test.c | 28 +++++-----
mm/kmsan/kmsan_test.c | 6 +--
mm/memblock.c | 4 +-
mm/mempolicy.c | 18 +++----
mm/page_owner.c | 32 +++++------
mm/percpu.c | 2 +-
mm/shrinker_debug.c | 2 +-
mm/slub.c | 5 +-
mm/zswap.c | 2 +-
23 files changed, 237 insertions(+), 58 deletions(-)
Range-diff against v5:
-: ------------ > 1: dab6068bef5c vsprintf: Add [v]sprintf_trunc()
1: 2c4f793de0b8 ! 2: c801c9a1a90d vsprintf: Add [v]sprintf_end()
@@ Commit message
Signed-off-by: Alejandro Colomar <alx@kernel.org>
## include/linux/sprintf.h ##
-@@ include/linux/sprintf.h: __printf(3, 4) int snprintf(char *buf, size_t size, const char *fmt, ...);
- __printf(3, 0) int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
- __printf(3, 4) int scnprintf(char *buf, size_t size, const char *fmt, ...);
+@@ include/linux/sprintf.h: __printf(3, 4) int scnprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
+ __printf(3, 4) int sprintf_trunc(char *buf, size_t size, const char *fmt, ...);
+ __printf(3, 0) int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args);
+__printf(3, 4) char *sprintf_end(char *p, const char end[0], const char *fmt, ...);
+__printf(3, 0) char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args);
__printf(2, 3) __malloc char *kasprintf(gfp_t gfp, const char *fmt, ...);
@@ include/linux/sprintf.h: __printf(3, 4) int snprintf(char *buf, size_t size, con
__printf(2, 0) const char *kvasprintf_const(gfp_t gfp, const char *fmt, va_list args);
## lib/vsprintf.c ##
-@@ lib/vsprintf.c: int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
+@@ lib/vsprintf.c: int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args)
}
- EXPORT_SYMBOL(vscnprintf);
+ EXPORT_SYMBOL(vsprintf_trunc);
+/**
+ * vsprintf_end - va_list string end-delimited print formatted
@@ lib/vsprintf.c: int vscnprintf(char *buf, size_t size, const char *fmt, va_list
+char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args)
+{
+ int len;
-+ size_t size;
+
+ if (unlikely(p == NULL))
+ return NULL;
+
-+ size = end - p;
-+ if (WARN_ON_ONCE(size == 0 || size > INT_MAX))
-+ return NULL;
-+
-+ len = vsnprintf(p, size, fmt, args);
-+ if (unlikely(len >= size))
++ len = vsprintf_trunc(p, end - p, fmt, args);
++ if (unlikely(len < 0))
+ return NULL;
+
+ return p + len;
@@ lib/vsprintf.c: int vscnprintf(char *buf, size_t size, const char *fmt, va_list
/**
* snprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
-@@ lib/vsprintf.c: int scnprintf(char *buf, size_t size, const char *fmt, ...)
+@@ lib/vsprintf.c: int sprintf_trunc(char *buf, size_t size, const char *fmt, ...)
}
- EXPORT_SYMBOL(scnprintf);
+ EXPORT_SYMBOL(sprintf_trunc);
+/**
+ * sprintf_end - string end-delimited print formatted
6: 04c1e026a67f ! 3: 9348d5df2d9f sprintf: Add [v]sprintf_array()
@@ Commit message
array.
These macros are essentially the same as the 2-argument version of
- strscpy(), but with a formatted string, and returning a pointer to the
- terminating '\0' (or NULL, on error).
+ strscpy(), but with a formatted string.
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
@@ include/linux/sprintf.h
#include <linux/types.h>
+#include <linux/array_size.h>
+
-+#define sprintf_array(a, fmt, ...) sprintf_end(a, ENDOF(a), fmt, ##__VA_ARGS__)
-+#define vsprintf_array(a, fmt, ap) vsprintf_end(a, ENDOF(a), fmt, ap)
++#define sprintf_array(a, fmt, ...) sprintf_trunc(a, ARRAY_SIZE(a), fmt, ##__VA_ARGS__)
++#define vsprintf_array(a, fmt, ap) vsprintf_trunc(a, ARRAY_SIZE(a), fmt, ap)
int num_to_str(char *buf, int size, unsigned long long num, unsigned int width);
2: 894d02b08056 = 4: 6c5d8e6012f0 stacktrace, stackdepot: Add sprintf_end()-like variants of functions
3: 690ed4d22f57 = 5: 8a0ffc1bf43d mm: Use sprintf_end() instead of less ergonomic APIs
4: e05c5afabb3c = 6: 37b1088dbd01 array_size.h: Add ENDOF()
5: 515445ae064d = 7: c88780354e13 mm: Fix benign off-by-one bugs
7: e53d87e684ef = 8: aa6323cbea64 mm: Use [v]sprintf_array() to avoid specifying the array size
base-commit: 0ff41df1cb268fc69e703a08a57ee14ae967d0ca
--
2.50.0
^ permalink raw reply [flat|nested] 98+ messages in thread
* [RFC v6 1/8] vsprintf: Add [v]sprintf_trunc()
2025-07-11 1:56 ` [RFC v6 0/8] Add and use sprintf_{end,trunc,array}() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-11 1:56 ` Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 2/8] vsprintf: Add [v]sprintf_end() Alejandro Colomar
` (6 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-11 1:56 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
sprintf_trunc() is a function similar to strscpy(). It truncates the
string, and returns an error code on truncation or error. On success,
it returns the length of the new string.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/sprintf.h | 2 ++
lib/vsprintf.c | 53 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+)
diff --git a/include/linux/sprintf.h b/include/linux/sprintf.h
index 51cab2def9ec..5ea6ec9c2e59 100644
--- a/include/linux/sprintf.h
+++ b/include/linux/sprintf.h
@@ -13,6 +13,8 @@ __printf(3, 4) int snprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
__printf(3, 4) int scnprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
+__printf(3, 4) int sprintf_trunc(char *buf, size_t size, const char *fmt, ...);
+__printf(3, 0) int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args);
__printf(2, 3) __malloc char *kasprintf(gfp_t gfp, const char *fmt, ...);
__printf(2, 0) __malloc char *kvasprintf(gfp_t gfp, const char *fmt, va_list args);
__printf(2, 0) const char *kvasprintf_const(gfp_t gfp, const char *fmt, va_list args);
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 01699852f30c..15e780942c56 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -2923,6 +2923,34 @@ int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
}
EXPORT_SYMBOL(vscnprintf);
+/**
+ * vsprintf_trunc - va_list string truncate print formatted
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
+ *
+ * The return value is the length of the string.
+ * If the string is truncated, the function returns -E2BIG.
+ * If @size is invalid, the function returns -EOVERFLOW.
+ *
+ * See the vsnprintf() documentation for format string extensions over C99.
+ */
+int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args)
+{
+ int len;
+
+ if (WARN_ON_ONCE(size == 0 || size > INT_MAX))
+ return -EOVERFLOW;
+
+ len = vsnprintf(buf, size, fmt, args);
+ if (unlikely(len >= size))
+ return -E2BIG;
+
+ return len;
+}
+EXPORT_SYMBOL(vsprintf_trunc);
+
/**
* snprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -2974,6 +3002,31 @@ int scnprintf(char *buf, size_t size, const char *fmt, ...)
}
EXPORT_SYMBOL(scnprintf);
+/**
+ * sprintf_trunc - string truncate print formatted
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
+ *
+ * The return value is the length of the string.
+ * If the string is truncated, the function returns -E2BIG.
+ * If @size is invalid, the function returns -EOVERFLOW.
+ */
+
+int sprintf_trunc(char *buf, size_t size, const char *fmt, ...)
+{
+ int len;
+ va_list args;
+
+ va_start(args, fmt);
+ len = vsprintf_trunc(buf, size, fmt, args);
+ va_end(args);
+
+ return len;
+}
+EXPORT_SYMBOL(sprintf_trunc);
+
/**
* vsprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v6 2/8] vsprintf: Add [v]sprintf_end()
2025-07-11 1:56 ` [RFC v6 0/8] Add and use sprintf_{end,trunc,array}() instead of less ergonomic APIs Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 1/8] vsprintf: Add [v]sprintf_trunc() Alejandro Colomar
@ 2025-07-11 1:56 ` Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 3/8] sprintf: Add [v]sprintf_array() Alejandro Colomar
` (5 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-11 1:56 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
sprintf_end() is a function similar to stpcpy(3) in the sense that it
returns a pointer that is suitable for chaining to other copy
operations.
It takes a pointer to the end of the buffer as a sentinel for when to
truncate, which unlike a size, doesn't need to be updated after every
call. This makes it much more ergonomic, avoiding manually calculating
the size after each copy, which is error prone.
It also makes error handling much easier, by reporting truncation with
a null pointer, which is accepted and transparently passed down by
subsequent sprintf_end() calls. This results in only needing to report
errors once after a chain of sprintf_end() calls, unlike snprintf(3),
which requires checking after every call.
p = buf;
e = buf + countof(buf);
p = sprintf_end(p, e, foo);
p = sprintf_end(p, e, bar);
if (p == NULL)
goto trunc;
vs
len = 0;
size = countof(buf);
len += snprintf(buf + len, size - len, foo);
if (len >= size)
goto trunc;
len += snprintf(buf + len, size - len, bar);
if (len >= size)
goto trunc;
And also better than scnprintf() calls:
len = 0;
size = countof(buf);
len += scnprintf(buf + len, size - len, foo);
len += scnprintf(buf + len, size - len, bar);
// No ability to check.
It seems aparent that it's a more elegant approach to string catenation.
These functions will soon be proposed for standardization as
[v]seprintf() into C2y, and they exist in Plan9 as seprint(2) --but the
Plan9 implementation has important bugs--.
Link: <https://www.alejandro-colomar.es/src/alx/alx/wg14/alx-0049.git/tree/alx-0049.txt>
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/sprintf.h | 2 ++
lib/vsprintf.c | 54 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 56 insertions(+)
diff --git a/include/linux/sprintf.h b/include/linux/sprintf.h
index 5ea6ec9c2e59..8dfc37713747 100644
--- a/include/linux/sprintf.h
+++ b/include/linux/sprintf.h
@@ -15,6 +15,8 @@ __printf(3, 4) int scnprintf(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
__printf(3, 4) int sprintf_trunc(char *buf, size_t size, const char *fmt, ...);
__printf(3, 0) int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args);
+__printf(3, 4) char *sprintf_end(char *p, const char end[0], const char *fmt, ...);
+__printf(3, 0) char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args);
__printf(2, 3) __malloc char *kasprintf(gfp_t gfp, const char *fmt, ...);
__printf(2, 0) __malloc char *kvasprintf(gfp_t gfp, const char *fmt, va_list args);
__printf(2, 0) const char *kvasprintf_const(gfp_t gfp, const char *fmt, va_list args);
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 15e780942c56..5d0c5a0d60fd 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -2951,6 +2951,35 @@ int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args)
}
EXPORT_SYMBOL(vsprintf_trunc);
+/**
+ * vsprintf_end - va_list string end-delimited print formatted
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
+ *
+ * The return value is a pointer to the trailing '\0'.
+ * If @p is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
+ * If @end <= @p, the function returns NULL.
+ *
+ * See the vsnprintf() documentation for format string extensions over C99.
+ */
+char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args)
+{
+ int len;
+
+ if (unlikely(p == NULL))
+ return NULL;
+
+ len = vsprintf_trunc(p, end - p, fmt, args);
+ if (unlikely(len < 0))
+ return NULL;
+
+ return p + len;
+}
+EXPORT_SYMBOL(vsprintf_end);
+
/**
* snprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
@@ -3027,6 +3056,31 @@ int sprintf_trunc(char *buf, size_t size, const char *fmt, ...)
}
EXPORT_SYMBOL(sprintf_trunc);
+/**
+ * sprintf_end - string end-delimited print formatted
+ * @p: The buffer to place the result into
+ * @end: A pointer to one past the last character in the buffer
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
+ *
+ * The return value is a pointer to the trailing '\0'.
+ * If @buf is NULL, the function returns NULL.
+ * If the string is truncated, the function returns NULL.
+ * If @end <= @p, the function returns NULL.
+ */
+
+char *sprintf_end(char *p, const char end[0], const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ p = vsprintf_end(p, end, fmt, args);
+ va_end(args);
+
+ return p;
+}
+EXPORT_SYMBOL(sprintf_end);
+
/**
* vsprintf - Format a string and place it in a buffer
* @buf: The buffer to place the result into
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v6 3/8] sprintf: Add [v]sprintf_array()
2025-07-11 1:56 ` [RFC v6 0/8] Add and use sprintf_{end,trunc,array}() instead of less ergonomic APIs Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 1/8] vsprintf: Add [v]sprintf_trunc() Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 2/8] vsprintf: Add [v]sprintf_end() Alejandro Colomar
@ 2025-07-11 1:56 ` Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 4/8] stacktrace, stackdepot: Add sprintf_end()-like variants of functions Alejandro Colomar
` (4 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-11 1:56 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
These macros take the end of the array argument implicitly to avoid
programmer mistakes. This guarantees that the input is an array, unlike
snprintf(buf, sizeof(buf), ...);
which is dangerous if the programmer passes a pointer instead of an
array.
These macros are essentially the same as the 2-argument version of
strscpy(), but with a formatted string.
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/sprintf.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/include/linux/sprintf.h b/include/linux/sprintf.h
index 8dfc37713747..bd8174224a4a 100644
--- a/include/linux/sprintf.h
+++ b/include/linux/sprintf.h
@@ -4,6 +4,10 @@
#include <linux/compiler_attributes.h>
#include <linux/types.h>
+#include <linux/array_size.h>
+
+#define sprintf_array(a, fmt, ...) sprintf_trunc(a, ARRAY_SIZE(a), fmt, ##__VA_ARGS__)
+#define vsprintf_array(a, fmt, ap) vsprintf_trunc(a, ARRAY_SIZE(a), fmt, ap)
int num_to_str(char *buf, int size, unsigned long long num, unsigned int width);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v6 4/8] stacktrace, stackdepot: Add sprintf_end()-like variants of functions
2025-07-11 1:56 ` [RFC v6 0/8] Add and use sprintf_{end,trunc,array}() instead of less ergonomic APIs Alejandro Colomar
` (2 preceding siblings ...)
2025-07-11 1:56 ` [RFC v6 3/8] sprintf: Add [v]sprintf_array() Alejandro Colomar
@ 2025-07-11 1:56 ` Alejandro Colomar
2025-07-11 1:57 ` [RFC v6 5/8] mm: Use sprintf_end() instead of less ergonomic APIs Alejandro Colomar
` (3 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-11 1:56 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/stackdepot.h | 13 +++++++++++++
include/linux/stacktrace.h | 3 +++
kernel/stacktrace.c | 28 ++++++++++++++++++++++++++++
lib/stackdepot.c | 13 +++++++++++++
4 files changed, 57 insertions(+)
diff --git a/include/linux/stackdepot.h b/include/linux/stackdepot.h
index 2cc21ffcdaf9..76182e874f67 100644
--- a/include/linux/stackdepot.h
+++ b/include/linux/stackdepot.h
@@ -219,6 +219,19 @@ void stack_depot_print(depot_stack_handle_t stack);
int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
int spaces);
+/**
+ * stack_depot_sprint_end - Print a stack trace from stack depot into a buffer
+ *
+ * @handle: Stack depot handle returned from stack_depot_save()
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @spaces: Number of leading spaces to print
+ *
+ * Return: Pointer to trailing '\0'; or NULL on truncation
+ */
+char *stack_depot_sprint_end(depot_stack_handle_t handle, char *p,
+ const char end[0], int spaces);
+
/**
* stack_depot_put - Drop a reference to a stack trace from stack depot
*
diff --git a/include/linux/stacktrace.h b/include/linux/stacktrace.h
index 97455880ac41..79ada795d479 100644
--- a/include/linux/stacktrace.h
+++ b/include/linux/stacktrace.h
@@ -67,6 +67,9 @@ void stack_trace_print(const unsigned long *trace, unsigned int nr_entries,
int spaces);
int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
unsigned int nr_entries, int spaces);
+char *stack_trace_sprint_end(char *p, const char end[0],
+ const unsigned long *entries,
+ unsigned int nr_entries, int spaces);
unsigned int stack_trace_save(unsigned long *store, unsigned int size,
unsigned int skipnr);
unsigned int stack_trace_save_tsk(struct task_struct *task,
diff --git a/kernel/stacktrace.c b/kernel/stacktrace.c
index afb3c116da91..f389647d8e44 100644
--- a/kernel/stacktrace.c
+++ b/kernel/stacktrace.c
@@ -70,6 +70,34 @@ int stack_trace_snprint(char *buf, size_t size, const unsigned long *entries,
}
EXPORT_SYMBOL_GPL(stack_trace_snprint);
+/**
+ * stack_trace_sprint_end - Print the entries in the stack trace into a buffer
+ * @p: Pointer to the print buffer
+ * @end: Pointer to one past the last element in the buffer
+ * @entries: Pointer to storage array
+ * @nr_entries: Number of entries in the storage array
+ * @spaces: Number of leading spaces to print
+ *
+ * Return: Pointer to the trailing '\0'; or NULL on truncation.
+ */
+char *stack_trace_sprint_end(char *p, const char end[0],
+ const unsigned long *entries, unsigned int nr_entries,
+ int spaces)
+{
+ unsigned int i;
+
+ if (WARN_ON(!entries))
+ return 0;
+
+ for (i = 0; i < nr_entries; i++) {
+ p = sprintf_end(p, end, "%*c%pS\n", 1 + spaces, ' ',
+ (void *)entries[i]);
+ }
+
+ return p;
+}
+EXPORT_SYMBOL_GPL(stack_trace_sprint_end);
+
#ifdef CONFIG_ARCH_STACKWALK
struct stacktrace_cookie {
diff --git a/lib/stackdepot.c b/lib/stackdepot.c
index 73d7b50924ef..48e5c0ff37e8 100644
--- a/lib/stackdepot.c
+++ b/lib/stackdepot.c
@@ -771,6 +771,19 @@ int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
}
EXPORT_SYMBOL_GPL(stack_depot_snprint);
+char *stack_depot_sprint_end(depot_stack_handle_t handle, char *p,
+ const char end[0], int spaces)
+{
+ unsigned long *entries;
+ unsigned int nr_entries;
+
+ nr_entries = stack_depot_fetch(handle, &entries);
+ return nr_entries ?
+ stack_trace_sprint_end(p, end, entries, nr_entries, spaces)
+ : sprintf_end(p, end, "");
+}
+EXPORT_SYMBOL_GPL(stack_depot_sprint_end);
+
depot_stack_handle_t __must_check stack_depot_set_extra_bits(
depot_stack_handle_t handle, unsigned int extra_bits)
{
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v6 5/8] mm: Use sprintf_end() instead of less ergonomic APIs
2025-07-11 1:56 ` [RFC v6 0/8] Add and use sprintf_{end,trunc,array}() instead of less ergonomic APIs Alejandro Colomar
` (3 preceding siblings ...)
2025-07-11 1:56 ` [RFC v6 4/8] stacktrace, stackdepot: Add sprintf_end()-like variants of functions Alejandro Colomar
@ 2025-07-11 1:57 ` Alejandro Colomar
2025-07-11 1:57 ` [RFC v6 6/8] array_size.h: Add ENDOF() Alejandro Colomar
` (2 subsequent siblings)
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-11 1:57 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski, Sven Schnelle,
Heiko Carstens, Tvrtko Ursulin, Christophe JAILLET, Hyeonggon Yoo,
Chao Yu
While doing this, I detected some anomalies in the existing code:
mm/kfence/kfence_test.c:
- The last call to scnprintf() did increment 'cur', but it's
unused after that, so it was dead code. I've removed the dead
code in this patch.
- 'end' is calculated as
end = &expect[0][sizeof(expect[0] - 1)];
However, the '-1' doesn't seem to be necessary. When passing
$2 to scnprintf(), the size was specified as 'end - cur'.
And scnprintf() --just like snprintf(3)--, won't write more
than $2 bytes (including the null byte). That means that
scnprintf() wouldn't write more than
&expect[0][sizeof(expect[0]) - 1] - expect[0]
which simplifies to
sizeof(expect[0]) - 1
bytes. But we have sizeof(expect[0]) bytes available, so
we're wasting one byte entirely. This is a benign off-by-one
bug. The two occurrences of this bug will be fixed in a
following patch in this series.
mm/kmsan/kmsan_test.c:
The same benign off-by-one bug calculating the remaining size.
mm/mempolicy.c:
This file uses the 'p += snprintf()' anti-pattern. That will
overflow the pointer on truncation, which has undefined
behavior. Using sprintf_end(), this bug is fixed.
As in the previous file, here there was also dead code in the
last scnprintf() call, by incrementing a pointer that is not
used after the call. I've removed the dead code.
mm/page_owner.c:
Within print_page_owner(), there are some calls to scnprintf(),
which do report truncation. And then there are other calls to
snprintf(), where we handle errors (there are two 'goto err').
I've kept the existing error handling, as I trust it's there for
a good reason (i.e., we may want to avoid calling
print_page_owner_memcg() if we truncated before). Please review
if this amount of error handling is the right one, or if we want
to add or remove some. For sprintf_end(), a single test for
null after the last call is enough to detect truncation.
mm/slub.c:
Again, the 'p += snprintf()' anti-pattern. This is UB, and by
using sprintf_end() we've fixed the bug.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Sven Schnelle <svens@linux.ibm.com>
Cc: Marco Elver <elver@google.com>
Cc: Heiko Carstens <hca@linux.ibm.com>
Cc: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: David Rientjes <rientjes@google.com>
Cc: Christophe JAILLET <christophe.jaillet@wanadoo.fr>
Cc: Hyeonggon Yoo <42.hyeyoo@gmail.com>
Cc: Chao Yu <chao.yu@oppo.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/kfence/kfence_test.c | 24 ++++++++++++------------
mm/kmsan/kmsan_test.c | 4 ++--
mm/mempolicy.c | 18 +++++++++---------
mm/page_owner.c | 32 +++++++++++++++++---------------
mm/slub.c | 5 +++--
5 files changed, 43 insertions(+), 40 deletions(-)
diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
index 00034e37bc9f..bae382eca4ab 100644
--- a/mm/kfence/kfence_test.c
+++ b/mm/kfence/kfence_test.c
@@ -113,26 +113,26 @@ static bool report_matches(const struct expect_report *r)
end = &expect[0][sizeof(expect[0]) - 1];
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds %s",
+ cur = sprintf_end(cur, end, "BUG: KFENCE: out-of-bounds %s",
get_access_type(r));
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free %s",
+ cur = sprintf_end(cur, end, "BUG: KFENCE: use-after-free %s",
get_access_type(r));
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: memory corruption");
+ cur = sprintf_end(cur, end, "BUG: KFENCE: memory corruption");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid %s",
+ cur = sprintf_end(cur, end, "BUG: KFENCE: invalid %s",
get_access_type(r));
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid free");
+ cur = sprintf_end(cur, end, "BUG: KFENCE: invalid free");
break;
}
- scnprintf(cur, end - cur, " in %pS", r->fn);
+ sprintf_end(cur, end, " in %pS", r->fn);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expect[0], '+');
if (cur)
@@ -144,26 +144,26 @@ static bool report_matches(const struct expect_report *r)
switch (r->type) {
case KFENCE_ERROR_OOB:
- cur += scnprintf(cur, end - cur, "Out-of-bounds %s at", get_access_type(r));
+ cur = sprintf_end(cur, end, "Out-of-bounds %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_UAF:
- cur += scnprintf(cur, end - cur, "Use-after-free %s at", get_access_type(r));
+ cur = sprintf_end(cur, end, "Use-after-free %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_CORRUPTION:
- cur += scnprintf(cur, end - cur, "Corrupted memory at");
+ cur = sprintf_end(cur, end, "Corrupted memory at");
break;
case KFENCE_ERROR_INVALID:
- cur += scnprintf(cur, end - cur, "Invalid %s at", get_access_type(r));
+ cur = sprintf_end(cur, end, "Invalid %s at", get_access_type(r));
addr = arch_kfence_test_address(addr);
break;
case KFENCE_ERROR_INVALID_FREE:
- cur += scnprintf(cur, end - cur, "Invalid free of");
+ cur = sprintf_end(cur, end, "Invalid free of");
break;
}
- cur += scnprintf(cur, end - cur, " 0x%p", (void *)addr);
+ sprintf_end(cur, end, " 0x%p", (void *)addr);
spin_lock_irqsave(&observed.lock, flags);
if (!report_available())
diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
index 9733a22c46c1..e48ca1972ff3 100644
--- a/mm/kmsan/kmsan_test.c
+++ b/mm/kmsan/kmsan_test.c
@@ -107,9 +107,9 @@ static bool report_matches(const struct expect_report *r)
cur = expected_header;
end = &expected_header[sizeof(expected_header) - 1];
- cur += scnprintf(cur, end - cur, "BUG: KMSAN: %s", r->error_type);
+ cur = sprintf_end(cur, end, "BUG: KMSAN: %s", r->error_type);
- scnprintf(cur, end - cur, " in %s", r->symbol);
+ sprintf_end(cur, end, " in %s", r->symbol);
/* The exact offset won't match, remove it; also strip module name. */
cur = strchr(expected_header, '+');
if (cur)
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index b28a1e6ae096..6beb2710f97c 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -3359,6 +3359,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
{
char *p = buffer;
+ char *e = buffer + maxlen;
nodemask_t nodes = NODE_MASK_NONE;
unsigned short mode = MPOL_DEFAULT;
unsigned short flags = 0;
@@ -3384,33 +3385,32 @@ void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
break;
default:
WARN_ON_ONCE(1);
- snprintf(p, maxlen, "unknown");
+ sprintf_end(p, e, "unknown");
return;
}
- p += snprintf(p, maxlen, "%s", policy_modes[mode]);
+ p = sprintf_end(p, e, "%s", policy_modes[mode]);
if (flags & MPOL_MODE_FLAGS) {
- p += snprintf(p, buffer + maxlen - p, "=");
+ p = sprintf_end(p, e, "=");
/*
* Static and relative are mutually exclusive.
*/
if (flags & MPOL_F_STATIC_NODES)
- p += snprintf(p, buffer + maxlen - p, "static");
+ p = sprintf_end(p, e, "static");
else if (flags & MPOL_F_RELATIVE_NODES)
- p += snprintf(p, buffer + maxlen - p, "relative");
+ p = sprintf_end(p, e, "relative");
if (flags & MPOL_F_NUMA_BALANCING) {
if (!is_power_of_2(flags & MPOL_MODE_FLAGS))
- p += snprintf(p, buffer + maxlen - p, "|");
- p += snprintf(p, buffer + maxlen - p, "balancing");
+ p = sprintf_end(p, e, "|");
+ p = sprintf_end(p, e, "balancing");
}
}
if (!nodes_empty(nodes))
- p += scnprintf(p, buffer + maxlen - p, ":%*pbl",
- nodemask_pr_args(&nodes));
+ sprintf_end(p, e, ":%*pbl", nodemask_pr_args(&nodes));
}
#ifdef CONFIG_SYSFS
diff --git a/mm/page_owner.c b/mm/page_owner.c
index cc4a6916eec6..c00b3be01540 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -496,7 +496,7 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
/*
* Looking for memcg information and print it out
*/
-static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
+static inline char *print_page_owner_memcg(char *p, const char end[0],
struct page *page)
{
#ifdef CONFIG_MEMCG
@@ -511,8 +511,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
goto out_unlock;
if (memcg_data & MEMCG_DATA_OBJEXTS)
- ret += scnprintf(kbuf + ret, count - ret,
- "Slab cache page\n");
+ p = sprintf_end(p, end, "Slab cache page\n");
memcg = page_memcg_check(page);
if (!memcg)
@@ -520,7 +519,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
online = (memcg->css.flags & CSS_ONLINE);
cgroup_name(memcg->css.cgroup, name, sizeof(name));
- ret += scnprintf(kbuf + ret, count - ret,
+ p = sprintf_end(p, end,
"Charged %sto %smemcg %s\n",
PageMemcgKmem(page) ? "(via objcg) " : "",
online ? "" : "offline ",
@@ -529,7 +528,7 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
rcu_read_unlock();
#endif /* CONFIG_MEMCG */
- return ret;
+ return p;
}
static ssize_t
@@ -538,14 +537,16 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
depot_stack_handle_t handle)
{
int ret, pageblock_mt, page_mt;
- char *kbuf;
+ char *kbuf, *p, *e;
count = min_t(size_t, count, PAGE_SIZE);
kbuf = kmalloc(count, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
- ret = scnprintf(kbuf, count,
+ p = kbuf;
+ e = kbuf + count;
+ p = sprintf_end(p, e,
"Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n",
page_owner->order, page_owner->gfp_mask,
&page_owner->gfp_mask, page_owner->pid,
@@ -555,7 +556,7 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
/* Print information relevant to grouping pages by mobility */
pageblock_mt = get_pageblock_migratetype(page);
page_mt = gfp_migratetype(page_owner->gfp_mask);
- ret += scnprintf(kbuf + ret, count - ret,
+ p = sprintf_end(p, e,
"PFN 0x%lx type %s Block %lu type %s Flags %pGp\n",
pfn,
migratetype_names[page_mt],
@@ -563,22 +564,23 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
migratetype_names[pageblock_mt],
&page->flags);
- ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
- if (ret >= count)
- goto err;
+ p = stack_depot_sprint_end(handle, p, e, 0);
+ if (p == NULL)
+ goto err; // XXX: Should we remove this error handling?
if (page_owner->last_migrate_reason != -1) {
- ret += scnprintf(kbuf + ret, count - ret,
+ p = sprintf_end(p, e,
"Page has been migrated, last migrate reason: %s\n",
migrate_reason_names[page_owner->last_migrate_reason]);
}
- ret = print_page_owner_memcg(kbuf, count, ret, page);
+ p = print_page_owner_memcg(p, e, page);
- ret += snprintf(kbuf + ret, count - ret, "\n");
- if (ret >= count)
+ p = sprintf_end(p, e, "\n");
+ if (p == NULL)
goto err;
+ ret = p - kbuf;
if (copy_to_user(buf, kbuf, ret))
ret = -EFAULT;
diff --git a/mm/slub.c b/mm/slub.c
index be8b09e09d30..dcc857676857 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -7451,6 +7451,7 @@ static char *create_unique_id(struct kmem_cache *s)
{
char *name = kmalloc(ID_STR_LENGTH, GFP_KERNEL);
char *p = name;
+ char *e = name + ID_STR_LENGTH;
if (!name)
return ERR_PTR(-ENOMEM);
@@ -7475,9 +7476,9 @@ static char *create_unique_id(struct kmem_cache *s)
*p++ = 'A';
if (p != name + 1)
*p++ = '-';
- p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
+ p = sprintf_end(p, e, "%07u", s->size);
- if (WARN_ON(p > name + ID_STR_LENGTH - 1)) {
+ if (WARN_ON(p == NULL)) {
kfree(name);
return ERR_PTR(-EINVAL);
}
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v6 6/8] array_size.h: Add ENDOF()
2025-07-11 1:56 ` [RFC v6 0/8] Add and use sprintf_{end,trunc,array}() instead of less ergonomic APIs Alejandro Colomar
` (4 preceding siblings ...)
2025-07-11 1:57 ` [RFC v6 5/8] mm: Use sprintf_end() instead of less ergonomic APIs Alejandro Colomar
@ 2025-07-11 1:57 ` Alejandro Colomar
2025-07-11 1:57 ` [RFC v6 7/8] mm: Fix benign off-by-one bugs Alejandro Colomar
2025-07-11 1:57 ` [RFC v6 8/8] mm: Use [v]sprintf_array() to avoid specifying the array size Alejandro Colomar
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-11 1:57 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
This macro is useful to calculate the second argument to sprintf_end(),
avoiding off-by-one bugs.
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
include/linux/array_size.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/include/linux/array_size.h b/include/linux/array_size.h
index 06d7d83196ca..781bdb70d939 100644
--- a/include/linux/array_size.h
+++ b/include/linux/array_size.h
@@ -10,4 +10,10 @@
*/
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
+/**
+ * ENDOF - get a pointer to one past the last element in array @a
+ * @a: array
+ */
+#define ENDOF(a) (a + ARRAY_SIZE(a))
+
#endif /* _LINUX_ARRAY_SIZE_H */
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v6 7/8] mm: Fix benign off-by-one bugs
2025-07-11 1:56 ` [RFC v6 0/8] Add and use sprintf_{end,trunc,array}() instead of less ergonomic APIs Alejandro Colomar
` (5 preceding siblings ...)
2025-07-11 1:57 ` [RFC v6 6/8] array_size.h: Add ENDOF() Alejandro Colomar
@ 2025-07-11 1:57 ` Alejandro Colomar
2025-07-11 1:57 ` [RFC v6 8/8] mm: Use [v]sprintf_array() to avoid specifying the array size Alejandro Colomar
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-11 1:57 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski, Jann Horn
We were wasting a byte due to an off-by-one bug. s[c]nprintf()
doesn't write more than $2 bytes including the null byte, so trying to
pass 'size-1' there is wasting one byte. Now that we use sprintf_end(),
the situation isn't different: sprintf_end() will stop writing *before*
'end' --that is, at most the terminating null byte will be written at
'end-1'--.
Acked-by: Marco Elver <elver@google.com>
Cc: Kees Cook <kees@kernel.org>
Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Jann Horn <jannh@google.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/kfence/kfence_test.c | 4 ++--
mm/kmsan/kmsan_test.c | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
index bae382eca4ab..c635aa9d478b 100644
--- a/mm/kfence/kfence_test.c
+++ b/mm/kfence/kfence_test.c
@@ -110,7 +110,7 @@ static bool report_matches(const struct expect_report *r)
/* Title */
cur = expect[0];
- end = &expect[0][sizeof(expect[0]) - 1];
+ end = ENDOF(expect[0]);
switch (r->type) {
case KFENCE_ERROR_OOB:
cur = sprintf_end(cur, end, "BUG: KFENCE: out-of-bounds %s",
@@ -140,7 +140,7 @@ static bool report_matches(const struct expect_report *r)
/* Access information */
cur = expect[1];
- end = &expect[1][sizeof(expect[1]) - 1];
+ end = ENDOF(expect[1]);
switch (r->type) {
case KFENCE_ERROR_OOB:
diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c
index e48ca1972ff3..9bda55992e3d 100644
--- a/mm/kmsan/kmsan_test.c
+++ b/mm/kmsan/kmsan_test.c
@@ -105,7 +105,7 @@ static bool report_matches(const struct expect_report *r)
/* Title */
cur = expected_header;
- end = &expected_header[sizeof(expected_header) - 1];
+ end = ENDOF(expected_header);
cur = sprintf_end(cur, end, "BUG: KMSAN: %s", r->error_type);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* [RFC v6 8/8] mm: Use [v]sprintf_array() to avoid specifying the array size
2025-07-11 1:56 ` [RFC v6 0/8] Add and use sprintf_{end,trunc,array}() instead of less ergonomic APIs Alejandro Colomar
` (6 preceding siblings ...)
2025-07-11 1:57 ` [RFC v6 7/8] mm: Fix benign off-by-one bugs Alejandro Colomar
@ 2025-07-11 1:57 ` Alejandro Colomar
7 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-11 1:57 UTC (permalink / raw)
To: linux-mm, linux-hardening
Cc: Alejandro Colomar, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Linus Torvalds,
Al Viro, Martin Uecker, Sam James, Andrew Pinski
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Marco Elver <elver@google.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
mm/backing-dev.c | 2 +-
mm/cma.c | 4 ++--
mm/cma_debug.c | 2 +-
mm/hugetlb.c | 3 +--
mm/hugetlb_cgroup.c | 2 +-
mm/hugetlb_cma.c | 2 +-
mm/kasan/report.c | 3 +--
mm/memblock.c | 4 ++--
mm/percpu.c | 2 +-
mm/shrinker_debug.c | 2 +-
mm/zswap.c | 2 +-
11 files changed, 13 insertions(+), 15 deletions(-)
diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index 783904d8c5ef..c4e588135aea 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -1090,7 +1090,7 @@ int bdi_register_va(struct backing_dev_info *bdi, const char *fmt, va_list args)
if (bdi->dev) /* The driver needs to use separate queues per device */
return 0;
- vsnprintf(bdi->dev_name, sizeof(bdi->dev_name), fmt, args);
+ vsprintf_array(bdi->dev_name, fmt, args);
dev = device_create(&bdi_class, NULL, MKDEV(0, 0), bdi, bdi->dev_name);
if (IS_ERR(dev))
return PTR_ERR(dev);
diff --git a/mm/cma.c b/mm/cma.c
index c04be488b099..61d97a387670 100644
--- a/mm/cma.c
+++ b/mm/cma.c
@@ -237,9 +237,9 @@ static int __init cma_new_area(const char *name, phys_addr_t size,
cma_area_count++;
if (name)
- snprintf(cma->name, CMA_MAX_NAME, "%s", name);
+ sprintf_array(cma->name, "%s", name);
else
- snprintf(cma->name, CMA_MAX_NAME, "cma%d\n", cma_area_count);
+ sprintf_array(cma->name, "cma%d\n", cma_area_count);
cma->available_count = cma->count = size >> PAGE_SHIFT;
cma->order_per_bit = order_per_bit;
diff --git a/mm/cma_debug.c b/mm/cma_debug.c
index fdf899532ca0..751eae9f6364 100644
--- a/mm/cma_debug.c
+++ b/mm/cma_debug.c
@@ -186,7 +186,7 @@ static void cma_debugfs_add_one(struct cma *cma, struct dentry *root_dentry)
rangedir = debugfs_create_dir("ranges", tmp);
for (r = 0; r < cma->nranges; r++) {
cmr = &cma->ranges[r];
- snprintf(rdirname, sizeof(rdirname), "%d", r);
+ sprintf_array(rdirname, "%d", r);
dir = debugfs_create_dir(rdirname, rangedir);
debugfs_create_file("base_pfn", 0444, dir,
&cmr->base_pfn, &cma_debugfs_fops);
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 6a3cf7935c14..70acc8b3cbb8 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -4780,8 +4780,7 @@ void __init hugetlb_add_hstate(unsigned int order)
for (i = 0; i < MAX_NUMNODES; ++i)
INIT_LIST_HEAD(&h->hugepage_freelists[i]);
INIT_LIST_HEAD(&h->hugepage_activelist);
- snprintf(h->name, HSTATE_NAME_LEN, "hugepages-%lukB",
- huge_page_size(h)/SZ_1K);
+ sprintf_array(h->name, "hugepages-%lukB", huge_page_size(h)/SZ_1K);
parsed_hstate = h;
}
diff --git a/mm/hugetlb_cgroup.c b/mm/hugetlb_cgroup.c
index 58e895f3899a..0953cea93759 100644
--- a/mm/hugetlb_cgroup.c
+++ b/mm/hugetlb_cgroup.c
@@ -822,7 +822,7 @@ hugetlb_cgroup_cfttypes_init(struct hstate *h, struct cftype *cft,
for (i = 0; i < tmpl_size; cft++, tmpl++, i++) {
*cft = *tmpl;
/* rebuild the name */
- snprintf(cft->name, MAX_CFTYPE_NAME, "%s.%s", buf, tmpl->name);
+ sprintf_array(cft->name, "%s.%s", buf, tmpl->name);
/* rebuild the private */
cft->private = MEMFILE_PRIVATE(idx, tmpl->private);
/* rebuild the file_offset */
diff --git a/mm/hugetlb_cma.c b/mm/hugetlb_cma.c
index e0f2d5c3a84c..bae82a97a43c 100644
--- a/mm/hugetlb_cma.c
+++ b/mm/hugetlb_cma.c
@@ -211,7 +211,7 @@ void __init hugetlb_cma_reserve(int order)
size = round_up(size, PAGE_SIZE << order);
- snprintf(name, sizeof(name), "hugetlb%d", nid);
+ sprintf_array(name, "hugetlb%d", nid);
/*
* Note that 'order per bit' is based on smallest size that
* may be returned to CMA allocator in the case of
diff --git a/mm/kasan/report.c b/mm/kasan/report.c
index 8357e1a33699..3b40225e7873 100644
--- a/mm/kasan/report.c
+++ b/mm/kasan/report.c
@@ -486,8 +486,7 @@ static void print_memory_metadata(const void *addr)
char buffer[4 + (BITS_PER_LONG / 8) * 2];
char metadata[META_BYTES_PER_ROW];
- snprintf(buffer, sizeof(buffer),
- (i == 0) ? ">%px: " : " %px: ", row);
+ sprintf_array(buffer, (i == 0) ? ">%px: " : " %px: ", row);
/*
* We should not pass a shadow pointer to generic
diff --git a/mm/memblock.c b/mm/memblock.c
index 0e9ebb8aa7fe..3eea7a177330 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -2021,7 +2021,7 @@ static void __init_memblock memblock_dump(struct memblock_type *type)
flags = rgn->flags;
#ifdef CONFIG_NUMA
if (numa_valid_node(memblock_get_region_node(rgn)))
- snprintf(nid_buf, sizeof(nid_buf), " on node %d",
+ sprintf_array(nid_buf, " on node %d",
memblock_get_region_node(rgn));
#endif
pr_info(" %s[%#x]\t[%pa-%pa], %pa bytes%s flags: %#x\n",
@@ -2379,7 +2379,7 @@ int reserve_mem_release_by_name(const char *name)
start = phys_to_virt(map->start);
end = start + map->size - 1;
- snprintf(buf, sizeof(buf), "reserve_mem:%s", name);
+ sprintf_array(buf, "reserve_mem:%s", name);
free_reserved_area(start, end, 0, buf);
map->size = 0;
diff --git a/mm/percpu.c b/mm/percpu.c
index b35494c8ede2..a467102c2405 100644
--- a/mm/percpu.c
+++ b/mm/percpu.c
@@ -3186,7 +3186,7 @@ int __init pcpu_page_first_chunk(size_t reserved_size, pcpu_fc_cpu_to_node_fn_t
int upa;
int nr_g0_units;
- snprintf(psize_str, sizeof(psize_str), "%luK", PAGE_SIZE >> 10);
+ sprintf_array(psize_str, "%luK", PAGE_SIZE >> 10);
ai = pcpu_build_alloc_info(reserved_size, 0, PAGE_SIZE, NULL);
if (IS_ERR(ai))
diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
index 20eaee3e97f7..f529ac29557c 100644
--- a/mm/shrinker_debug.c
+++ b/mm/shrinker_debug.c
@@ -176,7 +176,7 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
return id;
shrinker->debugfs_id = id;
- snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
+ sprintf_array(buf, "%s-%d", shrinker->name, id);
/* create debugfs entry */
entry = debugfs_create_dir(buf, shrinker_debugfs_root);
diff --git a/mm/zswap.c b/mm/zswap.c
index 204fb59da33c..e66b5c5b1ecf 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -271,7 +271,7 @@ static struct zswap_pool *zswap_pool_create(char *type, char *compressor)
return NULL;
/* unique name for each pool specifically required by zsmalloc */
- snprintf(name, 38, "zswap%x", atomic_inc_return(&zswap_pools_count));
+ sprintf_array(name, "zswap%x", atomic_inc_return(&zswap_pools_count));
pool->zpool = zpool_create_pool(type, name, gfp);
if (!pool->zpool) {
pr_err("%s zpool not available\n", type);
--
2.50.0
^ permalink raw reply related [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-10 21:58 ` Linus Torvalds
2025-07-10 23:23 ` Alejandro Colomar
@ 2025-07-11 6:05 ` Martin Uecker
2025-07-11 6:19 ` Martin Uecker
2025-07-11 17:45 ` David Laight
1 sibling, 2 replies; 98+ messages in thread
From: Martin Uecker @ 2025-07-11 6:05 UTC (permalink / raw)
To: Linus Torvalds, Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Al Viro,
Sam James, Andrew Pinski
Am Donnerstag, dem 10.07.2025 um 14:58 -0700 schrieb Linus Torvalds:
> On Thu, 10 Jul 2025 at 14:31, Alejandro Colomar <alx@kernel.org> wrote:
> >
> > These macros are essentially the same as the 2-argument version of
> > strscpy(), but with a formatted string, and returning a pointer to the
> > terminating '\0' (or NULL, on error).
>
> No.
>
> Stop this garbage.
>
> You took my suggestion, and then you messed it up.
>
> Your version of sprintf_array() is broken. It evaluates 'a' twice.
> Because unlike ARRAY_SIZE(), your broken ENDOF() macro evaluates the
> argument.
>
> And you did it for no reason I can see. You said that you wanted to
> return the end of the resulting string, but the fact is, not a single
> user seems to care, and honestly, I think it would be wrong to care.
> The size of the result is likely the more useful thing, or you could
> even make these 'void' or something.
>
> But instead you made the macro be dangerous to use.
>
> This kind of churn is WRONG. It _looks_ like a cleanup that doesn't
> change anything, but then it has subtle bugs that will come and bite
> us later because you did things wrong.
>
> I'm NAK'ing all of this. This is BAD. Cleanup patches had better be
> fundamentally correct, not introduce broken "helpers" that will make
> for really subtle bugs.
>
> Maybe nobody ever ends up having that first argument with a side
> effect. MAYBE. It's still very very wrong.
>
> Linus
What I am puzzled about is that - if you revise your string APIs -,
you do not directly go for a safe abstraction that combines length
and pointer and instead keep using these fragile 80s-style string
functions and open-coded pointer and size computations that everybody
gets wrong all the time.
String handling could also look like this:
https://godbolt.org/z/dqGz9b4sM
and be completely bounds safe.
(Note that those function abort() on allocation failure, but this
is an unfinished demo and also not for kernel use. Also I need to
rewrite this using string views.)
Martin
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-11 6:05 ` Martin Uecker
@ 2025-07-11 6:19 ` Martin Uecker
2025-07-11 17:45 ` David Laight
1 sibling, 0 replies; 98+ messages in thread
From: Martin Uecker @ 2025-07-11 6:19 UTC (permalink / raw)
To: Linus Torvalds, Alejandro Colomar
Cc: linux-mm, linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Al Viro,
Sam James, Andrew Pinski
Am Freitag, dem 11.07.2025 um 08:05 +0200 schrieb Martin Uecker:
> Am Donnerstag, dem 10.07.2025 um 14:58 -0700 schrieb Linus Torvalds:
> > On Thu, 10 Jul 2025 at 14:31, Alejandro Colomar <alx@kernel.org> wrote:
> > >
> > > These macros are essentially the same as the 2-argument version of
> > > strscpy(), but with a formatted string, and returning a pointer to the
> > > terminating '\0' (or NULL, on error).
> >
> > No.
> >
> > Stop this garbage.
> >
> > You took my suggestion, and then you messed it up.
> >
> > Your version of sprintf_array() is broken. It evaluates 'a' twice.
> > Because unlike ARRAY_SIZE(), your broken ENDOF() macro evaluates the
> > argument.
> >
> > And you did it for no reason I can see. You said that you wanted to
> > return the end of the resulting string, but the fact is, not a single
> > user seems to care, and honestly, I think it would be wrong to care.
> > The size of the result is likely the more useful thing, or you could
> > even make these 'void' or something.
> >
> > But instead you made the macro be dangerous to use.
> >
> > This kind of churn is WRONG. It _looks_ like a cleanup that doesn't
> > change anything, but then it has subtle bugs that will come and bite
> > us later because you did things wrong.
> >
> > I'm NAK'ing all of this. This is BAD. Cleanup patches had better be
> > fundamentally correct, not introduce broken "helpers" that will make
> > for really subtle bugs.
> >
> > Maybe nobody ever ends up having that first argument with a side
> > effect. MAYBE. It's still very very wrong.
> >
> > Linus
>
> What I am puzzled about is that - if you revise your string APIs -,
> you do not directly go for a safe abstraction that combines length
> and pointer and instead keep using these fragile 80s-style string
> functions and open-coded pointer and size computations that everybody
> gets wrong all the time.
>
> String handling could also look like this:
>
>
> https://godbolt.org/z/dqGz9b4sM
>
> and be completely bounds safe.
>
> (Note that those function abort() on allocation failure, but this
> is an unfinished demo and also not for kernel use. Also I need to
> rewrite this using string views.)
>
And *if* you want functions that manipulate buffers, why not pass
a pointer to the buffer instead of to its first element to not loose
the type information.
int foo(size_t s, char (*p)[s]);
char buf[10;
foo(ARRAY_SIZE(buf), &buf);
may look slightly unusual but is a lot safer than
int foo(char *buf, size_t len);
char buf[10];
foo(buf, ARRAY_SIZE(buf);
and - once you are used to it - also more logical because why would
you pass a pointer to part of an object to a function that is supposed
to work on the complete object.
Martin
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-10 23:23 ` Alejandro Colomar
2025-07-10 23:24 ` Alejandro Colomar
2025-07-11 0:19 ` Alejandro Colomar
@ 2025-07-11 17:43 ` David Laight
2025-07-11 19:17 ` Alejandro Colomar
2 siblings, 1 reply; 98+ messages in thread
From: David Laight @ 2025-07-11 17:43 UTC (permalink / raw)
To: Alejandro Colomar
Cc: Linus Torvalds, linux-mm, linux-hardening, Kees Cook,
Christopher Bazley, shadow, linux-kernel, Andrew Morton,
kasan-dev, Dmitry Vyukov, Alexander Potapenko, Marco Elver,
Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Rasmus Villemoes,
Michal Hocko, Al Viro, Martin Uecker, Sam James, Andrew Pinski
On Fri, 11 Jul 2025 01:23:49 +0200
Alejandro Colomar <alx@kernel.org> wrote:
> Hi Linus,
>
> [I'll reply to both of your emails at once]
>
> On Thu, Jul 10, 2025 at 02:58:24PM -0700, Linus Torvalds wrote:
> > You took my suggestion, and then you messed it up.
> >
> > Your version of sprintf_array() is broken. It evaluates 'a' twice.
> > Because unlike ARRAY_SIZE(), your broken ENDOF() macro evaluates the
> > argument.
>
> An array has no issue being evaluated twice (unless it's a VLA). On the
> other hand, I agree it's better to not do that in the first place.
> My bad for forgetting about it. Sorry.
Or a function that returns an array...
David
>
> On Thu, Jul 10, 2025 at 03:08:29PM -0700, Linus Torvalds wrote:
> > If you want to return an error on truncation, do it right. Not by
> > returning NULL, but by actually returning an error.
>
> Okay.
>
> > For example, in the kernel, we finally fixed 'strcpy()'. After about a
> > million different versions of 'copy a string' where every single
> > version was complete garbage, we ended up with 'strscpy()'. Yeah, the
> > name isn't lovely, but the *use* of it is:
>
> I have implemented the same thing in shadow, called strtcpy() (T for
> truncation). (With the difference that we read the string twice, since
> we don't care about threads.)
>
> I also plan to propose standardization of that one in ISO C.
>
> > - it returns the length of the result for people who want it - which
> > is by far the most common thing people want
>
> Agree.
>
> > - it returns an actual honest-to-goodness error code if something
> > overflowed, instead of the absoilutely horrible "source length" of the
> > string that strlcpy() does and which is fundamentally broken (because
> > it requires that you walk *past* the end of the source,
> > Christ-on-a-stick what a broken interface)
>
> Agree.
>
> > - it can take an array as an argument (without the need for another
> > name - see my earlier argument about not making up new names by just
> > having generics)
>
> We can't make the same thing with sprintf() variants because they're
> variadic, so you can't count the number of arguments. And since the
> 'end' argument is of the same type as the formatted string, we can't
> do it with _Generic reliably either.
>
> > Now, it has nasty naming (exactly the kind of 'add random character'
> > naming that I was arguing against), and that comes from so many
> > different broken versions until we hit on something that works.
> >
> > strncpy is horrible garbage. strlcpy is even worse. strscpy actually
> > works and so far hasn't caused issues (there's a 'pad' version for the
> > very rare situation where you want 'strncpy-like' padding, but it
> > still guarantees NUL-termination, and still has a good return value).
>
> Agree.
>
> > Let's agree to *not* make horrible garbage when making up new versions
> > of sprintf.
>
> Agree. I indeed introduced the mistake accidentally in v4, after you
> complained of having too many functions, as I was introducing not one
> but two APIs: seprintf() and stprintf(), where seprintf() is what now
> we're calling sprintf_end(), and stprintf() we could call it
> sprintf_trunc(). So I did the mistake by trying to reduce the number of
> functions to just one, which is wrong.
>
> So, maybe I should go back to those functions, and just give them good
> names.
>
> What do you think of the following?
>
> #define sprintf_array(a, ...) sprintf_trunc(a, ARRAY_SIZE(a), __VA_ARGS__)
> #define vsprintf_array(a, ap) vsprintf_trunc(a, ARRAY_SIZE(a), ap)
>
> char *sprintf_end(char *p, const char end[0], const char *fmt, ...);
> char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args);
> int sprintf_trunc(char *buf, size_t size, const char *fmt, ...);
> int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args);
>
> char *sprintf_end(char *p, const char end[0], const char *fmt, ...)
> {
> va_list args;
>
> va_start(args, fmt);
> p = vseprintf(p, end, fmt, args);
> va_end(args);
>
> return p;
> }
>
> char *vsprintf_end(char *p, const char end[0], const char *fmt, va_list args)
> {
> int len;
>
> if (unlikely(p == NULL))
> return NULL;
>
> len = vsprintf_trunc(p, end - p, fmt, args);
> if (unlikely(len < 0))
> return NULL;
>
> return p + len;
> }
>
> int sprintf_trunc(char *buf, size_t size, const char *fmt, ...)
> {
> va_list args;
> int len;
>
> va_start(args, fmt);
> len = vstprintf(buf, size, fmt, args);
> va_end(args);
>
> return len;
> }
>
> int vsprintf_trunc(char *buf, size_t size, const char *fmt, va_list args)
> {
> int len;
>
> if (WARN_ON_ONCE(size == 0 || size > INT_MAX))
> return -EOVERFLOW;
>
> len = vsnprintf(buf, size, fmt, args);
> if (unlikely(len >= size))
> return -E2BIG;
>
> return len;
> }
>
> sprintf_trunc() is like strscpy(), but with a formatted string. It
> could replace uses of s[c]nprintf() where there's a single call (no
> chained calls).
>
> sprintf_array() is like the 2-argument version of strscpy(). It could
> replace s[c]nprintf() calls where there's no chained calls, where the
> input is an array.
>
> sprintf_end() would replace the chained calls.
>
> Does this sound good to you?
>
>
> Cheers,
> Alex
>
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-11 6:05 ` Martin Uecker
2025-07-11 6:19 ` Martin Uecker
@ 2025-07-11 17:45 ` David Laight
2025-07-11 17:58 ` Linus Torvalds
2025-07-11 18:01 ` Martin Uecker
1 sibling, 2 replies; 98+ messages in thread
From: David Laight @ 2025-07-11 17:45 UTC (permalink / raw)
To: Martin Uecker
Cc: Linus Torvalds, Alejandro Colomar, linux-mm, linux-hardening,
Kees Cook, Christopher Bazley, shadow, linux-kernel,
Andrew Morton, kasan-dev, Dmitry Vyukov, Alexander Potapenko,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Rasmus Villemoes,
Michal Hocko, Al Viro, Sam James, Andrew Pinski
On Fri, 11 Jul 2025 08:05:38 +0200
Martin Uecker <ma.uecker@gmail.com> wrote:
> Am Donnerstag, dem 10.07.2025 um 14:58 -0700 schrieb Linus Torvalds:
> > On Thu, 10 Jul 2025 at 14:31, Alejandro Colomar <alx@kernel.org> wrote:
> > >
> > > These macros are essentially the same as the 2-argument version of
> > > strscpy(), but with a formatted string, and returning a pointer to the
> > > terminating '\0' (or NULL, on error).
> >
> > No.
> >
> > Stop this garbage.
> >
> > You took my suggestion, and then you messed it up.
> >
> > Your version of sprintf_array() is broken. It evaluates 'a' twice.
> > Because unlike ARRAY_SIZE(), your broken ENDOF() macro evaluates the
> > argument.
> >
> > And you did it for no reason I can see. You said that you wanted to
> > return the end of the resulting string, but the fact is, not a single
> > user seems to care, and honestly, I think it would be wrong to care.
> > The size of the result is likely the more useful thing, or you could
> > even make these 'void' or something.
> >
> > But instead you made the macro be dangerous to use.
> >
> > This kind of churn is WRONG. It _looks_ like a cleanup that doesn't
> > change anything, but then it has subtle bugs that will come and bite
> > us later because you did things wrong.
> >
> > I'm NAK'ing all of this. This is BAD. Cleanup patches had better be
> > fundamentally correct, not introduce broken "helpers" that will make
> > for really subtle bugs.
> >
> > Maybe nobody ever ends up having that first argument with a side
> > effect. MAYBE. It's still very very wrong.
> >
> > Linus
>
> What I am puzzled about is that - if you revise your string APIs -,
> you do not directly go for a safe abstraction that combines length
> and pointer and instead keep using these fragile 80s-style string
> functions and open-coded pointer and size computations that everybody
> gets wrong all the time.
>
> String handling could also look like this:
What does that actually look like behind all the #defines and generics?
It it continually doing malloc/free it is pretty much inappropriate
for a lot of system/kernel code.
David
>
>
> https://godbolt.org/z/dqGz9b4sM
>
> and be completely bounds safe.
>
> (Note that those function abort() on allocation failure, but this
> is an unfinished demo and also not for kernel use. Also I need to
> rewrite this using string views.)
>
>
> Martin
>
>
>
>
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-11 17:45 ` David Laight
@ 2025-07-11 17:58 ` Linus Torvalds
2025-07-11 19:24 ` Matthew Wilcox
2025-07-15 5:19 ` Kees Cook
2025-07-11 18:01 ` Martin Uecker
1 sibling, 2 replies; 98+ messages in thread
From: Linus Torvalds @ 2025-07-11 17:58 UTC (permalink / raw)
To: David Laight
Cc: Martin Uecker, Alejandro Colomar, linux-mm, linux-hardening,
Kees Cook, Christopher Bazley, shadow, linux-kernel,
Andrew Morton, kasan-dev, Dmitry Vyukov, Alexander Potapenko,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Rasmus Villemoes,
Michal Hocko, Al Viro, Sam James, Andrew Pinski
On Fri, 11 Jul 2025 at 10:45, David Laight <david.laight.linux@gmail.com> wrote:
>
> What does that actually look like behind all the #defines and generics?
> It it continually doing malloc/free it is pretty much inappropriate
> for a lot of system/kernel code.
Honestly, the kernel approximately *never* has "string handling" in
the traditional sense.
But we do have "buffers with text". The difference typically exactly
being that allocation has to happen separately from any text
operation.
It's why I already suggested people look at our various existing
buffer abstractions: we have several, although they tend to often be
somewhat specialized.
So, for example, we have things like "struct qstr" for path
components: it's specialized not only in having an associated hash
value for the string, but because it's a "initialize once" kind of
buffer that gets initialized at creation time, and the string contents
are constant (it literally contains a "const char *" in addition to
the length/hash).
That kind of "string buffer" obviously isn't useful for things like
the printf family, but we do have others. Like "struct seq_buf", which
already has "seq_buf_printf()" helpers.
That's the one you probably should use for most kernel "print to
buffer", but it has very few users despite not being complicated to
use:
struct seq_buf s;
seq_buf_init(&s, buf, szie);
and you're off to the races, and can do things like
seq_buf_printf(&s, ....);
without ever having to worry about overflows etc.
So we already do *have* good interfaces. But they aren't the
traditional ones that everybody knows about.
Linus
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-11 17:45 ` David Laight
2025-07-11 17:58 ` Linus Torvalds
@ 2025-07-11 18:01 ` Martin Uecker
1 sibling, 0 replies; 98+ messages in thread
From: Martin Uecker @ 2025-07-11 18:01 UTC (permalink / raw)
To: David Laight
Cc: Linus Torvalds, Alejandro Colomar, linux-mm, linux-hardening,
Kees Cook, Christopher Bazley, shadow, linux-kernel,
Andrew Morton, kasan-dev, Dmitry Vyukov, Alexander Potapenko,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Rasmus Villemoes,
Michal Hocko, Al Viro, Sam James, Andrew Pinski
Am Freitag, dem 11.07.2025 um 18:45 +0100 schrieb David Laight:
> On Fri, 11 Jul 2025 08:05:38 +0200
> Martin Uecker <ma.uecker@gmail.com> wrote:
>
> > Am Donnerstag, dem 10.07.2025 um 14:58 -0700 schrieb Linus Torvalds:
> > > On Thu, 10 Jul 2025 at 14:31, Alejandro Colomar <alx@kernel.org> wrote:
> > > >
> > > > These macros are essentially the same as the 2-argument version of
> > > > strscpy(), but with a formatted string, and returning a pointer to the
> > > > terminating '\0' (or NULL, on error).
> > >
> > > No.
> > >
> > > Stop this garbage.
> > >
> > > You took my suggestion, and then you messed it up.
> > >
> > > Your version of sprintf_array() is broken. It evaluates 'a' twice.
> > > Because unlike ARRAY_SIZE(), your broken ENDOF() macro evaluates the
> > > argument.
> > >
> > > And you did it for no reason I can see. You said that you wanted to
> > > return the end of the resulting string, but the fact is, not a single
> > > user seems to care, and honestly, I think it would be wrong to care.
> > > The size of the result is likely the more useful thing, or you could
> > > even make these 'void' or something.
> > >
> > > But instead you made the macro be dangerous to use.
> > >
> > > This kind of churn is WRONG. It _looks_ like a cleanup that doesn't
> > > change anything, but then it has subtle bugs that will come and bite
> > > us later because you did things wrong.
> > >
> > > I'm NAK'ing all of this. This is BAD. Cleanup patches had better be
> > > fundamentally correct, not introduce broken "helpers" that will make
> > > for really subtle bugs.
> > >
> > > Maybe nobody ever ends up having that first argument with a side
> > > effect. MAYBE. It's still very very wrong.
> > >
> > > Linus
> >
> > What I am puzzled about is that - if you revise your string APIs -,
> > you do not directly go for a safe abstraction that combines length
> > and pointer and instead keep using these fragile 80s-style string
> > functions and open-coded pointer and size computations that everybody
> > gets wrong all the time.
> >
> > String handling could also look like this:
>
> What does that actually look like behind all the #defines and generics?
> It it continually doing malloc/free it is pretty much inappropriate
> for a lot of system/kernel code.
The example I linked would allocate behind your back and would clearly
not be useful for the kernel also because it would abort() on
allocation failure (as I pointed out below).
Still, I do not see why similar functions could not work for the
kernel. The main point is to keep pointer and length together in a
single struct. But it is certainly more difficult to define APIs
which make sense for the kernel.
I explain a bit how such types work here:
https://uecker.codeberg.page/2025-07-02.html
https://uecker.codeberg.page/2025-07-09.html
Martin
>
> >
> > https://godbolt.org/z/dqGz9b4sM
> >
> > and be completely bounds safe.
> >
> > (Note that those function abort() on allocation failure, but this
> > is an unfinished demo and also not for kernel use. Also I need to
> > rewrite this using string views.)
> >
> >
> > Martin
> >
> >
> >
> >
>
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-11 17:43 ` David Laight
@ 2025-07-11 19:17 ` Alejandro Colomar
2025-07-11 19:21 ` Alejandro Colomar
0 siblings, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-11 19:17 UTC (permalink / raw)
To: David Laight
Cc: Linus Torvalds, linux-mm, linux-hardening, Kees Cook,
Christopher Bazley, shadow, linux-kernel, Andrew Morton,
kasan-dev, Dmitry Vyukov, Alexander Potapenko, Marco Elver,
Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Rasmus Villemoes,
Michal Hocko, Al Viro, Martin Uecker, Sam James, Andrew Pinski
[-- Attachment #1: Type: text/plain, Size: 1025 bytes --]
Hi David,
On Fri, Jul 11, 2025 at 06:43:43PM +0100, David Laight wrote:
> On Fri, 11 Jul 2025 01:23:49 +0200
> Alejandro Colomar <alx@kernel.org> wrote:
>
> > Hi Linus,
> >
> > [I'll reply to both of your emails at once]
> >
> > On Thu, Jul 10, 2025 at 02:58:24PM -0700, Linus Torvalds wrote:
> > > You took my suggestion, and then you messed it up.
> > >
> > > Your version of sprintf_array() is broken. It evaluates 'a' twice.
> > > Because unlike ARRAY_SIZE(), your broken ENDOF() macro evaluates the
> > > argument.
> >
> > An array has no issue being evaluated twice (unless it's a VLA). On the
> > other hand, I agree it's better to not do that in the first place.
> > My bad for forgetting about it. Sorry.
>
> Or a function that returns an array...
Actually, I was forgetting that the array could be gotten from a pointer
to array:
int (*ap)[42] = ...;
ENDOF(ap++); // Evaluates ap++
Anyway, fixed in v6.
Cheers,
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-11 19:17 ` Alejandro Colomar
@ 2025-07-11 19:21 ` Alejandro Colomar
0 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-11 19:21 UTC (permalink / raw)
To: David Laight
Cc: Linus Torvalds, linux-mm, linux-hardening, Kees Cook,
Christopher Bazley, shadow, linux-kernel, Andrew Morton,
kasan-dev, Dmitry Vyukov, Alexander Potapenko, Marco Elver,
Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Rasmus Villemoes,
Michal Hocko, Al Viro, Martin Uecker, Sam James, Andrew Pinski
[-- Attachment #1: Type: text/plain, Size: 1261 bytes --]
On Fri, Jul 11, 2025 at 09:17:28PM +0200, Alejandro Colomar wrote:
> Hi David,
>
> On Fri, Jul 11, 2025 at 06:43:43PM +0100, David Laight wrote:
> > On Fri, 11 Jul 2025 01:23:49 +0200
> > Alejandro Colomar <alx@kernel.org> wrote:
> >
> > > Hi Linus,
> > >
> > > [I'll reply to both of your emails at once]
> > >
> > > On Thu, Jul 10, 2025 at 02:58:24PM -0700, Linus Torvalds wrote:
> > > > You took my suggestion, and then you messed it up.
> > > >
> > > > Your version of sprintf_array() is broken. It evaluates 'a' twice.
> > > > Because unlike ARRAY_SIZE(), your broken ENDOF() macro evaluates the
> > > > argument.
> > >
> > > An array has no issue being evaluated twice (unless it's a VLA). On the
> > > other hand, I agree it's better to not do that in the first place.
> > > My bad for forgetting about it. Sorry.
> >
> > Or a function that returns an array...
>
> Actually, I was forgetting that the array could be gotten from a pointer
> to array:
>
> int (*ap)[42] = ...;
>
> ENDOF(ap++); // Evaluates ap++
D'oh! That should have been ENDOF(*ap++).
> Anyway, fixed in v6.
>
>
> Cheers,
> Alex
>
> --
> <https://www.alejandro-colomar.es/>
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-11 17:58 ` Linus Torvalds
@ 2025-07-11 19:24 ` Matthew Wilcox
2025-07-15 5:19 ` Kees Cook
1 sibling, 0 replies; 98+ messages in thread
From: Matthew Wilcox @ 2025-07-11 19:24 UTC (permalink / raw)
To: Linus Torvalds
Cc: David Laight, Martin Uecker, Alejandro Colomar, linux-mm,
linux-hardening, Kees Cook, Christopher Bazley, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Rasmus Villemoes, Michal Hocko, Al Viro,
Sam James, Andrew Pinski
On Fri, Jul 11, 2025 at 10:58:56AM -0700, Linus Torvalds wrote:
> That kind of "string buffer" obviously isn't useful for things like
> the printf family, but we do have others. Like "struct seq_buf", which
> already has "seq_buf_printf()" helpers.
>
> That's the one you probably should use for most kernel "print to
> buffer", but it has very few users despite not being complicated to
> use:
>
> struct seq_buf s;
> seq_buf_init(&s, buf, szie);
>
> and you're off to the races, and can do things like
>
> seq_buf_printf(&s, ....);
>
> without ever having to worry about overflows etc.
I actually wanted to go one step further with this (that's why I took
readpos out of seq_buf in d0ed46b60396). If you look at the guts of
vsprintf.c, it'd be much improved by using seq_buf internally instead
of passing around buf and end.
Once we've done that, maybe we can strip these annoying %pXYZ out
of vsprintf.c and use seq_buf routines like it's a StringBuilder (or
whatever other language/library convention you prefer).
Anyway, I ran out of time to work on it, but I still think it's
worthwhile. And then there'd be a lot more commonality between regular
printing and trace printing, which would be nice.
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-07 19:17 ` Linus Torvalds
2025-07-07 19:35 ` Al Viro
2025-07-07 20:29 ` Alejandro Colomar
@ 2025-07-12 20:58 ` Christopher Bazley
2025-07-14 7:57 ` Christopher Bazley
2 siblings, 1 reply; 98+ messages in thread
From: Christopher Bazley @ 2025-07-12 20:58 UTC (permalink / raw)
To: Linus Torvalds
Cc: Alejandro Colomar, linux-mm, linux-hardening, Kees Cook, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Sven Schnelle, Heiko Carstens, Tvrtko Ursulin,
Huang, Ying, Lee Schermerhorn, Christophe JAILLET, Hyeonggon Yoo,
Chao Yu
Hi Linus,
On Mon, Jul 7, 2025 at 8:17 PM Linus Torvalds
<torvalds@linux-foundation.org> wrote:
>
> On Sun, 6 Jul 2025 at 22:06, Alejandro Colomar <alx@kernel.org> wrote:
> >
> > - p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
> > + p = seprintf(p, e, "%07u", s->size);
>
> I am *really* not a fan of introducing yet another random non-standard
> string function.
>
> This 'seprintf' thing really seems to be a completely made-up thing.
> Let's not go there. It just adds more confusion - it may be a simpler
> interface, but it's another cogniitive load thing, and honestly, that
> "beginning and end" interface is not great.
>
> I think we'd be better off with real "character buffer" interfaces,
> and they should be *named* that way, not be yet another "random
> character added to the printf family".
I was really interested to see this comment because I presented a
design for a standard character buffer interface, "strb_t", to WG14 in
summer of 2014. The latest published version of that paper is
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3306.pdf (very long)
and the slides (which cover most of the important points) are
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3276.pdf
I contacted you beforehand, for permission to include kasprintf and
kvasprintf in the 'prior art' section of my paper. At the time, you
gave me useful information about the history of those and related
functions. (As an aside, Alejandro has since written a proposal to
standardise a similar function named aprintf, which I support:
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3630.txt )
Going back to "strb_t", I did not bother you about it again because I
didn't anticipate it being used in kernel space, which has its own
interfaces for most things. I'd be interested to hear what you think
of it though. My intent was to make it impossible to abuse, insofar as
that is possible. That led me to make choices (such as use of an
incomplete struct type) that some might consider strange or
overengineered. I didn't see the point in trying to replace one set of
error-prone functions with another.
Alejandro has put a lot of thought into his proposed seprintf
function, but it still fundamentally relies on the programmer passing
the right arguments and it doesn't seem to extend the functionality of
snprintf in any way that I actually need.
For example, some of my goals for the character buffer interface were:
- A buffer should be specified using a single parameter.
- Impossible to accidentally shallow-copy a buffer instead of copying
a reference to it.
- No aspect of character consumption delegated to character producers, e.g.:
* whether to insert or overwrite.
* whether to prepend, insert or append.
* whether to allocate extra storage, and how to do that.
- Minimize the effect of ignoring return values and not require
ubiquitous error-handling.
- Able to put strings directly into a buffer from any source.
- Allow diverse implementations (mostly to allow tailoring to
different platforms).
This small program demonstrates some of those ideas:
https://godbolt.org/z/66Gnre6dx
It uses my ugly hacked-together prototype.
Chris
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
2025-07-12 20:58 ` Christopher Bazley
@ 2025-07-14 7:57 ` Christopher Bazley
0 siblings, 0 replies; 98+ messages in thread
From: Christopher Bazley @ 2025-07-14 7:57 UTC (permalink / raw)
To: Linus Torvalds
Cc: Alejandro Colomar, linux-mm, linux-hardening, Kees Cook, shadow,
linux-kernel, Andrew Morton, kasan-dev, Dmitry Vyukov,
Alexander Potapenko, Marco Elver, Christoph Lameter,
David Rientjes, Vlastimil Babka, Roman Gushchin, Harry Yoo,
Andrew Clayton, Sven Schnelle, Heiko Carstens, Tvrtko Ursulin,
Huang, Ying, Lee Schermerhorn, Christophe JAILLET, Hyeonggon Yoo,
Chao Yu
On Sat, Jul 12, 2025 at 9:58 PM Christopher Bazley
<chris.bazley.wg14@gmail.com> wrote:
>
> Hi Linus,
>
> On Mon, Jul 7, 2025 at 8:17 PM Linus Torvalds
> <torvalds@linux-foundation.org> wrote:
> >
> > On Sun, 6 Jul 2025 at 22:06, Alejandro Colomar <alx@kernel.org> wrote:
> > >
> > > - p += snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size);
> > > + p = seprintf(p, e, "%07u", s->size);
> >
> > I am *really* not a fan of introducing yet another random non-standard
> > string function.
> >
> > This 'seprintf' thing really seems to be a completely made-up thing.
> > Let's not go there. It just adds more confusion - it may be a simpler
> > interface, but it's another cogniitive load thing, and honestly, that
> > "beginning and end" interface is not great.
> >
> > I think we'd be better off with real "character buffer" interfaces,
> > and they should be *named* that way, not be yet another "random
> > character added to the printf family".
>
> I was really interested to see this comment because I presented a
> design for a standard character buffer interface, "strb_t", to WG14 in
> summer of 2014.
Ugh, that should have been 2024. I'm getting old!
Chris
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-11 17:58 ` Linus Torvalds
2025-07-11 19:24 ` Matthew Wilcox
@ 2025-07-15 5:19 ` Kees Cook
2025-07-15 6:24 ` Martin Uecker
2025-07-15 7:08 ` Alejandro Colomar
1 sibling, 2 replies; 98+ messages in thread
From: Kees Cook @ 2025-07-15 5:19 UTC (permalink / raw)
To: Linus Torvalds
Cc: David Laight, Martin Uecker, Alejandro Colomar, linux-mm,
linux-hardening, Christopher Bazley, shadow, linux-kernel,
Andrew Morton, kasan-dev, Dmitry Vyukov, Alexander Potapenko,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Rasmus Villemoes,
Michal Hocko, Al Viro, Sam James, Andrew Pinski
On Fri, Jul 11, 2025 at 10:58:56AM -0700, Linus Torvalds wrote:
> struct seq_buf s;
> seq_buf_init(&s, buf, szie);
And because some folks didn't like this "declaration that requires a
function call", we even added:
DECLARE_SEQ_BUF(s, 32);
to do it in 1 line. :P
I would love to see more string handling replaced with seq_buf.
--
Kees Cook
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-15 5:19 ` Kees Cook
@ 2025-07-15 6:24 ` Martin Uecker
2025-07-17 23:44 ` Kees Cook
2025-07-15 7:08 ` Alejandro Colomar
1 sibling, 1 reply; 98+ messages in thread
From: Martin Uecker @ 2025-07-15 6:24 UTC (permalink / raw)
To: Kees Cook, Linus Torvalds
Cc: David Laight, Alejandro Colomar, linux-mm, linux-hardening,
Christopher Bazley, shadow, linux-kernel, Andrew Morton,
kasan-dev, Dmitry Vyukov, Alexander Potapenko, Marco Elver,
Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Rasmus Villemoes,
Michal Hocko, Al Viro, Sam James, Andrew Pinski
Am Montag, dem 14.07.2025 um 22:19 -0700 schrieb Kees Cook:
> On Fri, Jul 11, 2025 at 10:58:56AM -0700, Linus Torvalds wrote:
> > struct seq_buf s;
> > seq_buf_init(&s, buf, szie);
>
> And because some folks didn't like this "declaration that requires a
> function call", we even added:
>
> DECLARE_SEQ_BUF(s, 32);
>
> to do it in 1 line. :P
>
> I would love to see more string handling replaced with seq_buf.
Why not have?
struct seq_buf s = SEQ_BUF(32);
So the kernel has safe abstractions, there are just not used enough.
Do you also have a string view abstraction? I found this really
useful as basic building block for safe string handling, and
equally important to a string builder type such as seq_buf.
The string builder is for safely construcing new strings, the
string view is for safely accessing parts of existing strings.
Also what I found really convenient and useful in this context
was to have an accessor macro that expose the buffer as a
regular array cast to the correct size:
*( (char(*)[(x)->N]) (x)->data )
(put into statement expressions to avoid double evaluation)
instead of simply returning a char*
You can then access the array directly with [] which then can be
bounds checked with UBsan, one can measure its length with sizeof,
and one can also let it decay and get a char* to pass it to legacy
code (and to some degree this can be protected by BDOS).
Martin
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-15 5:19 ` Kees Cook
2025-07-15 6:24 ` Martin Uecker
@ 2025-07-15 7:08 ` Alejandro Colomar
2025-07-17 23:47 ` Kees Cook
1 sibling, 1 reply; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-15 7:08 UTC (permalink / raw)
To: Kees Cook
Cc: Linus Torvalds, David Laight, Martin Uecker, linux-mm,
linux-hardening, Christopher Bazley, shadow, linux-kernel,
Andrew Morton, kasan-dev, Dmitry Vyukov, Alexander Potapenko,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Rasmus Villemoes,
Michal Hocko, Al Viro, Sam James, Andrew Pinski
[-- Attachment #1: Type: text/plain, Size: 3212 bytes --]
Hi Kees,
On Mon, Jul 14, 2025 at 10:19:39PM -0700, Kees Cook wrote:
> On Fri, Jul 11, 2025 at 10:58:56AM -0700, Linus Torvalds wrote:
> > struct seq_buf s;
> > seq_buf_init(&s, buf, szie);
>
> And because some folks didn't like this "declaration that requires a
> function call", we even added:
>
> DECLARE_SEQ_BUF(s, 32);
>
> to do it in 1 line. :P
>
> I would love to see more string handling replaced with seq_buf.
The thing is, it's not as easy as the fixes I'm proposing, and
sprintf_end() solves a lot of UB in a minimal diff that you can dumbly
apply.
And transitioning from sprintf_end() to seq_buf will still be a
possibility --probably even easier, because the code is simpler than
with s[c]nprintf()--.
Another thing, and this is my opinion, is that I'm not fond of APIs that
keep an internal state. With sprintf_end(), the state is minimal and
external: the state is the 'p' pointer to where you're going to write.
That way, the programmer knows exactly where the writes occur, and can
reason about it without having to read the implementation and keep a
model of the state in its head. With a struct-based approach, you hide
the state inside the structure, which means it's not so easy to reason
about how an action will affect the string, at first glance; you need an
expert in the API to know how to use it.
With sprintf_end(), either one is stupid/careless enough to get the
parameters wrong, or the function necessarily works well, *and is simple
to fully understand*. And considering that we have ENDOF(), it's hard
to understand how one could get it wrong:
p = buf;
e = ENDOF(buf);
p = sprintf_end(p, e, ...);
p = sprintf_end(p, e, ...);
p = sprintf_end(p, e, ...);
p = sprintf_end(p, e, ...);
Admittedly, ENDOF() doesn't compile if buf is not an array, so in those
cases, there's a chance of a paranoic programmer slapping a -1 just in
case, but that doesn't hurt:
p = buf;
e = buf + size; // Someone might accidentally -1 that?
I'm working on extending the _Countof() operator so that it can be
applied to array parameters to functions, so that it can be used to
count arrays that are not arrays:
void
f(size_t n, char buf[n])
{
p = buf;
e = buf + _Countof(buf); // _Countof(buf) will evaluate to n.
...
}
Which will significantly enhance the usability of sprintf_end(). I want
to implement this for GCC next year (there are a few things that need to
be improved first to be able to do that), and also propose it for
standardization.
For a similar comparison of stateful vs stateless functions, there are
strtok(3) and strsep(3), which apart from minor differences (strtok(3)
collapses adjacent delimiters) are more or less the same. But I'd use
strsep(3) over strtok(3), even if just because strtok(3) keeps an
internal state, so I always need to be very careful of reading the
documentation to remind myself of what happens to the state after each
call. strsep(3) is dead simple: you call it, and it updates the pointer
you passed; nothing is kept secretly from the programmer.
Have a lovely day!
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-15 6:24 ` Martin Uecker
@ 2025-07-17 23:44 ` Kees Cook
0 siblings, 0 replies; 98+ messages in thread
From: Kees Cook @ 2025-07-17 23:44 UTC (permalink / raw)
To: Martin Uecker
Cc: Linus Torvalds, David Laight, Alejandro Colomar, linux-mm,
linux-hardening, Christopher Bazley, shadow, linux-kernel,
Andrew Morton, kasan-dev, Dmitry Vyukov, Alexander Potapenko,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Rasmus Villemoes,
Michal Hocko, Al Viro, Sam James, Andrew Pinski
On Tue, Jul 15, 2025 at 08:24:29AM +0200, Martin Uecker wrote:
> Am Montag, dem 14.07.2025 um 22:19 -0700 schrieb Kees Cook:
> > On Fri, Jul 11, 2025 at 10:58:56AM -0700, Linus Torvalds wrote:
> > > struct seq_buf s;
> > > seq_buf_init(&s, buf, szie);
> >
> > And because some folks didn't like this "declaration that requires a
> > function call", we even added:
> >
> > DECLARE_SEQ_BUF(s, 32);
> >
> > to do it in 1 line. :P
> >
> > I would love to see more string handling replaced with seq_buf.
>
> Why not have?
>
> struct seq_buf s = SEQ_BUF(32);
>
>
> So the kernel has safe abstractions, there are just not used enough.
Yeah, that should be fine. The trouble is encapsulating the actual
buffer itself. But things like spinlocks need initialization too, so
it's not too unusual to need a constructor for things living in a
struct.
If the struct had DECLARE which created 2 variables, then an INIT could
just reuse the special name...
> The string builder is for safely construcing new strings, the
> string view is for safely accessing parts of existing strings.
seq_buf doesn't currently have a "view" API, just a "make sure the
result is NUL terminated, please enjoy this char *"
> Also what I found really convenient and useful in this context
> was to have an accessor macro that expose the buffer as a
> regular array cast to the correct size:
>
> *( (char(*)[(x)->N]) (x)->data )
>
> (put into statement expressions to avoid double evaluation)
>
> instead of simply returning a char*
Yeah, I took a look through your proposed C string library routines. I
think it would be pretty nice, but it does feel like it has to go
through a lot of hoops when C should have something native. Though to
be clear, I'm not saying seq_buf is the answer. :)
--
Kees Cook
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-15 7:08 ` Alejandro Colomar
@ 2025-07-17 23:47 ` Kees Cook
2025-07-18 0:56 ` Alejandro Colomar
0 siblings, 1 reply; 98+ messages in thread
From: Kees Cook @ 2025-07-17 23:47 UTC (permalink / raw)
To: Alejandro Colomar
Cc: Linus Torvalds, David Laight, Martin Uecker, linux-mm,
linux-hardening, Christopher Bazley, shadow, linux-kernel,
Andrew Morton, kasan-dev, Dmitry Vyukov, Alexander Potapenko,
Marco Elver, Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Rasmus Villemoes,
Michal Hocko, Al Viro, Sam James, Andrew Pinski
On Tue, Jul 15, 2025 at 09:08:14AM +0200, Alejandro Colomar wrote:
> Hi Kees,
>
> On Mon, Jul 14, 2025 at 10:19:39PM -0700, Kees Cook wrote:
> > On Fri, Jul 11, 2025 at 10:58:56AM -0700, Linus Torvalds wrote:
> > > struct seq_buf s;
> > > seq_buf_init(&s, buf, szie);
> >
> > And because some folks didn't like this "declaration that requires a
> > function call", we even added:
> >
> > DECLARE_SEQ_BUF(s, 32);
> >
> > to do it in 1 line. :P
> >
> > I would love to see more string handling replaced with seq_buf.
>
> The thing is, it's not as easy as the fixes I'm proposing, and
> sprintf_end() solves a lot of UB in a minimal diff that you can dumbly
> apply.
Note that I'm not arguing against your idea -- I just think it's not
going to be likely to end up in Linux soon given Linus's objections. My
perspective is mainly one of pragmatic damage control: what *can* we do
in Linux that would make things better? Currently, seq_buf is better
than raw C strings...
--
Kees Cook
^ permalink raw reply [flat|nested] 98+ messages in thread
* Re: [RFC v5 6/7] sprintf: Add [v]sprintf_array()
2025-07-17 23:47 ` Kees Cook
@ 2025-07-18 0:56 ` Alejandro Colomar
0 siblings, 0 replies; 98+ messages in thread
From: Alejandro Colomar @ 2025-07-18 0:56 UTC (permalink / raw)
To: Kees Cook, Linus Torvalds
Cc: David Laight, Martin Uecker, linux-mm, linux-hardening,
Christopher Bazley, shadow, linux-kernel, Andrew Morton,
kasan-dev, Dmitry Vyukov, Alexander Potapenko, Marco Elver,
Christoph Lameter, David Rientjes, Vlastimil Babka,
Roman Gushchin, Harry Yoo, Andrew Clayton, Rasmus Villemoes,
Michal Hocko, Al Viro, Sam James, Andrew Pinski
[-- Attachment #1: Type: text/plain, Size: 1941 bytes --]
Hi Kees,
On Thu, Jul 17, 2025 at 04:47:04PM -0700, Kees Cook wrote:
> On Tue, Jul 15, 2025 at 09:08:14AM +0200, Alejandro Colomar wrote:
> > Hi Kees,
> >
> > On Mon, Jul 14, 2025 at 10:19:39PM -0700, Kees Cook wrote:
> > > On Fri, Jul 11, 2025 at 10:58:56AM -0700, Linus Torvalds wrote:
> > > > struct seq_buf s;
> > > > seq_buf_init(&s, buf, szie);
> > >
> > > And because some folks didn't like this "declaration that requires a
> > > function call", we even added:
> > >
> > > DECLARE_SEQ_BUF(s, 32);
> > >
> > > to do it in 1 line. :P
> > >
> > > I would love to see more string handling replaced with seq_buf.
> >
> > The thing is, it's not as easy as the fixes I'm proposing, and
> > sprintf_end() solves a lot of UB in a minimal diff that you can dumbly
> > apply.
>
> Note that I'm not arguing against your idea -- I just think it's not
> going to be likely to end up in Linux soon given Linus's objections.
It would be interesting to hear if Linus holds his objections on v6.
> My
> perspective is mainly one of pragmatic damage control: what *can* we do
> in Linux that would make things better? Currently, seq_buf is better
> than raw C strings...
TBH, I'm not fully convinced. While it may look simpler at first
glance, I'm worried that it might bite in the details. I default to not
trusting APIs that hide the complexity in hidden state. On the other
hand, I agree that almost anything is safer than snprintf(3).
But one good thing of snprintf(3) is that it's simple, and thus
relatively obvious to see that it's wrong, so it's easy to fix (it's
easy to transition from snprintf(3) to sprintf_end()). So, maybe
keeping it bogus until it's replaced by sprintf_end() is a better
approach than using seq_buf. (Unless the current code is found
exploitable, but I assume not.)
Have a lovely night!
Alex
--
<https://www.alejandro-colomar.es/>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 98+ messages in thread
end of thread, other threads:[~2025-07-18 0:56 UTC | newest]
Thread overview: 98+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-05 20:33 [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
2025-07-05 20:33 ` [RFC v1 1/3] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
2025-07-05 20:40 ` Alejandro Colomar
2025-07-07 9:47 ` Alexander Potapenko
2025-07-07 14:59 ` Alejandro Colomar
2025-07-05 20:33 ` [RFC v1 2/3] stacktrace, stackdepot: Add seprintf()-like variants of functions Alejandro Colomar
2025-07-05 20:33 ` [RFC v1 3/3] mm: Use seprintf() instead of less ergonomic APIs Alejandro Colomar
2025-07-05 21:54 ` Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 0/5] Add and use " Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 1/5] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 2/5] stacktrace, stackdepot: Add seprintf()-like variants of functions Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 3/5] mm: Use seprintf() instead of less ergonomic APIs Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 4/5] array_size.h: Add ENDOF() Alejandro Colomar
2025-07-06 17:37 ` [RFC v2 5/5] mm: Fix benign off-by-one bugs Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 1/7] vsprintf: Add [v]seprintf(), [v]stprintf() Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 2/7] stacktrace, stackdepot: Add seprintf()-like variants of functions Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs Alejandro Colomar
2025-07-07 7:44 ` Marco Elver
2025-07-07 14:39 ` Alejandro Colomar
2025-07-07 14:58 ` Marco Elver
2025-07-07 18:51 ` Alejandro Colomar
2025-07-07 19:08 ` Marco Elver
2025-07-07 20:53 ` Alejandro Colomar
2025-07-07 19:17 ` Linus Torvalds
2025-07-07 19:35 ` Al Viro
2025-07-07 20:46 ` Linus Torvalds
2025-07-07 20:29 ` Alejandro Colomar
2025-07-07 20:49 ` Linus Torvalds
2025-07-07 21:05 ` Alejandro Colomar
2025-07-07 21:26 ` Alejandro Colomar
2025-07-07 22:17 ` Linus Torvalds
2025-07-08 2:20 ` Alejandro Colomar
2025-07-12 20:58 ` Christopher Bazley
2025-07-14 7:57 ` Christopher Bazley
2025-07-07 5:06 ` [RFC v3 4/7] array_size.h: Add ENDOF() Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 5/7] mm: Fix benign off-by-one bugs Alejandro Colomar
2025-07-07 7:46 ` Marco Elver
2025-07-07 7:53 ` Michal Hocko
2025-07-07 14:42 ` Alejandro Colomar
2025-07-07 15:12 ` Michal Hocko
2025-07-07 15:29 ` Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 6/7] sprintf: Add [V]STPRINTF() Alejandro Colomar
2025-07-07 5:06 ` [RFC v3 7/7] mm: Use [V]STPRINTF() to avoid specifying the array size Alejandro Colomar
2025-07-07 5:11 ` [RFC v3 0/7] Add and use seprintf() instead of less ergonomic APIs Alejandro Colomar
2025-07-10 2:47 ` [RFC v4 0/7] Add and use sprintf_end() " Alejandro Colomar
2025-07-10 2:47 ` alx-0049r2 - add seprintf() Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 1/7] vsprintf: Add [v]sprintf_end() Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 2/7] stacktrace, stackdepot: Add sprintf_end()-like variants of functions Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 3/7] mm: Use sprintf_end() instead of less ergonomic APIs Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 4/7] array_size.h: Add ENDOF() Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 5/7] mm: Fix benign off-by-one bugs Alejandro Colomar
2025-07-10 2:48 ` [RFC v4 6/7] sprintf: Add [V]SPRINTF_END() Alejandro Colomar
2025-07-10 15:52 ` Linus Torvalds
2025-07-10 18:30 ` Alejandro Colomar
2025-07-10 21:21 ` Alejandro Colomar
2025-07-10 22:08 ` Linus Torvalds
2025-07-10 2:49 ` [RFC v4 7/7] mm: Use [V]SPRINTF_END() to avoid specifying the array size Alejandro Colomar
2025-07-10 21:30 ` [RFC v5 0/7] Add and use sprintf_{end,array}() instead of less ergonomic APIs Alejandro Colomar
2025-07-10 21:30 ` [RFC v5 1/7] vsprintf: Add [v]sprintf_end() Alejandro Colomar
2025-07-10 21:30 ` [RFC v5 2/7] stacktrace, stackdepot: Add sprintf_end()-like variants of functions Alejandro Colomar
2025-07-10 21:30 ` [RFC v5 3/7] mm: Use sprintf_end() instead of less ergonomic APIs Alejandro Colomar
2025-07-10 21:31 ` [RFC v5 4/7] array_size.h: Add ENDOF() Alejandro Colomar
2025-07-10 21:31 ` [RFC v5 5/7] mm: Fix benign off-by-one bugs Alejandro Colomar
2025-07-10 21:31 ` [RFC v5 6/7] sprintf: Add [v]sprintf_array() Alejandro Colomar
2025-07-10 21:58 ` Linus Torvalds
2025-07-10 23:23 ` Alejandro Colomar
2025-07-10 23:24 ` Alejandro Colomar
2025-07-11 0:19 ` Alejandro Colomar
2025-07-11 17:43 ` David Laight
2025-07-11 19:17 ` Alejandro Colomar
2025-07-11 19:21 ` Alejandro Colomar
2025-07-11 6:05 ` Martin Uecker
2025-07-11 6:19 ` Martin Uecker
2025-07-11 17:45 ` David Laight
2025-07-11 17:58 ` Linus Torvalds
2025-07-11 19:24 ` Matthew Wilcox
2025-07-15 5:19 ` Kees Cook
2025-07-15 6:24 ` Martin Uecker
2025-07-17 23:44 ` Kees Cook
2025-07-15 7:08 ` Alejandro Colomar
2025-07-17 23:47 ` Kees Cook
2025-07-18 0:56 ` Alejandro Colomar
2025-07-11 18:01 ` Martin Uecker
2025-07-10 21:31 ` [RFC v5 7/7] mm: Use [v]sprintf_array() to avoid specifying the array size Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 0/8] Add and use sprintf_{end,trunc,array}() instead of less ergonomic APIs Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 1/8] vsprintf: Add [v]sprintf_trunc() Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 2/8] vsprintf: Add [v]sprintf_end() Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 3/8] sprintf: Add [v]sprintf_array() Alejandro Colomar
2025-07-11 1:56 ` [RFC v6 4/8] stacktrace, stackdepot: Add sprintf_end()-like variants of functions Alejandro Colomar
2025-07-11 1:57 ` [RFC v6 5/8] mm: Use sprintf_end() instead of less ergonomic APIs Alejandro Colomar
2025-07-11 1:57 ` [RFC v6 6/8] array_size.h: Add ENDOF() Alejandro Colomar
2025-07-11 1:57 ` [RFC v6 7/8] mm: Fix benign off-by-one bugs Alejandro Colomar
2025-07-11 1:57 ` [RFC v6 8/8] mm: Use [v]sprintf_array() to avoid specifying the array size Alejandro Colomar
2025-07-08 6:43 ` [RFC v1 0/3] Add and use seprintf() instead of less ergonomic APIs Rasmus Villemoes
2025-07-08 11:36 ` Alejandro Colomar
2025-07-08 13:51 ` Rasmus Villemoes
2025-07-08 16:14 ` Alejandro Colomar
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).