string.h: Introduce memtostr() and memtostr_pad()
authorKees Cook <keescook@chromium.org>
Wed, 10 Apr 2024 02:31:50 +0000 (19:31 -0700)
committerKees Cook <keescook@chromium.org>
Wed, 24 Apr 2024 15:57:09 +0000 (08:57 -0700)
commit0efc5990bca540b8d438fda23db3a72efa733eb0
treea906f4892c15306be2b88ed4a307d100386607b1
parentdde915c5cba1fe49e980efe72662d9bc2a6b7ffd
string.h: Introduce memtostr() and memtostr_pad()

Another ambiguous use of strncpy() is to copy from strings that may not
be NUL-terminated. These cases depend on having the destination buffer
be explicitly larger than the source buffer's maximum size, having
the size of the copy exactly match the source buffer's maximum size,
and for the destination buffer to get explicitly NUL terminated.

This usually happens when parsing protocols or hardware character arrays
that are not guaranteed to be NUL-terminated. The code pattern is
effectively this:

char dest[sizeof(src) + 1];

strncpy(dest, src, sizeof(src));
dest[sizeof(dest) - 1] = '\0';

In practice it usually looks like:

struct from_hardware {
...
char name[HW_NAME_SIZE] __nonstring;
...
};

struct from_hardware *p = ...;
char name[HW_NAME_SIZE + 1];

strncpy(name, p->name, HW_NAME_SIZE);
name[NW_NAME_SIZE] = '\0';

This cannot be replaced with:

strscpy(name, p->name, sizeof(name));

because p->name is smaller and not NUL-terminated, so FORTIFY will
trigger when strnlen(p->name, sizeof(name)) is used. And it cannot be
replaced with:

strscpy(name, p->name, sizeof(p->name));

because then "name" may contain a 1 character early truncation of
p->name.

Provide an unambiguous interface for converting a maybe not-NUL-terminated
string to a NUL-terminated string, with compile-time buffer size checking
so that it can never fail at runtime: memtostr() and memtostr_pad(). Also
add KUnit tests for both.

Link: https://lore.kernel.org/r/20240410023155.2100422-1-keescook@chromium.org
Signed-off-by: Kees Cook <keescook@chromium.org>
include/linux/string.h
lib/string_kunit.c