public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Alejandro Colomar <alx@kernel.org>
To: LKML <linux-kernel@vger.kernel.org>, Kees Cook <kees@kernel.org>
Cc: corbet@lwn.net, serge@hallyn.com, Martin Uecker <uecker@tugraz.at>
Subject: Re: kalloc_objs() may not be as safe as it seems
Date: Mon, 16 Mar 2026 19:35:12 +0100	[thread overview]
Message-ID: <abhNL1wbFajcHZXC@devuan> (raw)
In-Reply-To: <abhGS0n_RsUG97Ni@devuan>

[-- Attachment #1: Type: text/plain, Size: 3885 bytes --]

On 2026-03-16T19:33:37+0100, Alejandro Colomar wrote:
> Hi Kees,
> 
> I just learnt about kalloc_objs() et al. from
> <https://lwn.net/Articles/1062856/>.
> 
>     ptr = kmalloc_obj(*ptr);
>     ptr = kmalloc_objs(*ptr, n);
> 
> This resembles a lot the macro we have in shadow-utils, malloc_T(),
> which would be used as (to resemble the above):
> 
>     ptr = malloc_T(1, typeof(*ptr));  // But we'd really pass the type
>     ptr = malloc_T(n, typeof(*ptr));  // But we'd really pass the type
> 
> But I've noticed some design mistakes that make it not as safe as it
> seems.
> 
> Default arguments
> ~~~~~~~~~~~~~~~~~
> 
> I tend to think it's simpler to have a single API that works for both
> 1 element and multiple elements.  The special-casing of 1 element seems
> unnecessary, and having a literal 1 works just fine.
> 
> I think the combination of having the macros be variadic (for gfp) with
> having two very similar APIs that differ in number of arguments, and
> all those arguments being integer types, is prone to errors.  Consider
> the case where one would accidentally write
> 
> 	ptr = kmalloc_obj(*ptr, n);  // Bogus
> instead of
> 	ptr = kmalloc_objs(*ptr, n);
> 
> The compiler wouldn't realize at all.  That's a strong argument in
> favour of having default arguments be required to be explicit, with an
> empty argument:
> 
> 	ptr = kmalloc_obj(*ptr,);
> 	ptr = kmalloc_objs(*ptr, n,);
> 
> I know you (and Linus too, FWIW) have previously claimed that it looks
> weird to the eye.  But I'm pretty sure you could get used to it.  That's
> certainly going to be safer.
> 
> With mandatory empty arguments, the compiler would easily distinguish
> mistakes like the one above.
> 
> Type safety
> ~~~~~~~~~~~
> 
> Apart from the issues with the above, the ability to pass a variable
> instead of a type name is also a bad choice.  In shadow-utils, we
> require a type name, and a variable is rejected.  We implement that with
> the typeas() macro:
> 
> 	#define typeas(T)  typeof((T){0})
> 
> This macro works exactly like typeof(), but it requires that the input
> is also a type.  Passing a variable is a syntax error.  We implement
> malloc_T() with it:
> 
> 	// malloc_T - malloc type-safe
> 	#define malloc_T_(n, T)                                       \
> 	({                                                            \
> 		(typeas(T) *){reallocarray(n, sizeof(T))};            \

I meant the following:

		(typeas(T) *){reallocarray(NULL, n, sizeof(T))};

I had the issue while pasting and adapting for simplicity.  The original
code is correct, of course.


> 	})
> 
> which is used as (taking some arbitrary examples from shadow-utils):
> 
> 	lp = xmalloc_T(1, struct link_name);
> 	targs = xmalloc_T(n_args + 3, char *);
> 
> Some reasons for passing a type name instead of a variable are:
> 
> -  It allows grepping for all allocations of a given type.
> -  It adds readability.  It's similar to declaring variables with some
>    explicit type, vs. using 'auto' (__auto_type) everywhere.
> 
> But there's also a safety aspect.  Consider we want to allocate an array
> of 42 ints.  And consider the programmer accidentally swaps arguments.
> 
> 	int *p = malloc_T(int, 42);  // syntax error
> 	int *p = malloc_T(42, int);
> vs
> 	int *p = kmalloc_objs(*p, 42);
> 	int *p = kmalloc_objs(42, *p);  // Bogus
> 
> The latter is dereferencing an uninitialized pointer.  If for some
> reason the pointer had a value before this call, you'd be allocating as
> many elements as *p says, which would be bogus, and since typeof(42) is
> the same as typeof(*p), the return type would be valid, so this would
> still compile.
> 
> 
> Have a lovely night!
> Alex
> 
> -- 
> <https://www.alejandro-colomar.es>



-- 
<https://www.alejandro-colomar.es>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

  reply	other threads:[~2026-03-16 18:35 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-16 18:33 kalloc_objs() may not be as safe as it seems Alejandro Colomar
2026-03-16 18:35 ` Alejandro Colomar [this message]
2026-03-17 21:14 ` Kees Cook
2026-03-17 21:40   ` Alejandro Colomar
2026-03-18 16:33     ` Alejandro Colomar
2026-03-19 20:12       ` Kees Cook
2026-03-19 21:18         ` Alejandro Colomar

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=abhNL1wbFajcHZXC@devuan \
    --to=alx@kernel.org \
    --cc=corbet@lwn.net \
    --cc=kees@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=serge@hallyn.com \
    --cc=uecker@tugraz.at \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox