[RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs

Alejandro Colomar posted 7 patches 3 months ago
[RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Alejandro Colomar 3 months ago
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
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Linus Torvalds 3 months ago
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
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Christopher Bazley 2 months, 3 weeks ago
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
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Christopher Bazley 2 months, 3 weeks ago
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
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Alejandro Colomar 3 months ago
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/>
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Linus Torvalds 3 months ago
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
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Alejandro Colomar 3 months ago
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/>
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Alejandro Colomar 3 months ago
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/>
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Linus Torvalds 3 months ago
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
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Alejandro Colomar 3 months ago
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/>
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Al Viro 3 months ago
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?
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Linus Torvalds 3 months ago
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
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Marco Elver 3 months ago
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
>
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Alejandro Colomar 3 months ago
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/>
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Marco Elver 3 months ago
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
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Alejandro Colomar 3 months ago
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/>
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Marco Elver 3 months ago
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/>
Re: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs
Posted by Alejandro Colomar 3 months ago
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/>