2021-06-26 00:45:15 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
|
|
|
* Test cases for memcpy(), memmove(), and memset().
|
|
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
|
|
|
#include <kunit/test.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/overflow.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/vmalloc.h>
|
|
|
|
|
|
|
|
struct some_bytes {
|
|
|
|
union {
|
|
|
|
u8 data[32];
|
|
|
|
struct {
|
|
|
|
u32 one;
|
|
|
|
u16 two;
|
|
|
|
u8 three;
|
|
|
|
/* 1 byte hole */
|
|
|
|
u32 four[4];
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
#define check(instance, v) do { \
|
|
|
|
BUILD_BUG_ON(sizeof(instance.data) != 32); \
|
string: Introduce strtomem() and strtomem_pad()
One of the "legitimate" uses of strncpy() is copying a NUL-terminated
string into a fixed-size non-NUL-terminated character array. To avoid
the weaknesses and ambiguity of intent when using strncpy(), provide
replacement functions that explicitly distinguish between trailing
padding and not, and require the destination buffer size be discoverable
by the compiler.
For example:
struct obj {
int foo;
char small[4] __nonstring;
char big[8] __nonstring;
int bar;
};
struct obj p;
/* This will truncate to 4 chars with no trailing NUL */
strncpy(p.small, "hello", sizeof(p.small));
/* p.small contains 'h', 'e', 'l', 'l' */
/* This will NUL pad to 8 chars. */
strncpy(p.big, "hello", sizeof(p.big));
/* p.big contains 'h', 'e', 'l', 'l', 'o', '\0', '\0', '\0' */
When the "__nonstring" attributes are missing, the intent of the
programmer becomes ambiguous for whether the lack of a trailing NUL
in the p.small copy is a bug. Additionally, it's not clear whether
the trailing padding in the p.big copy is _needed_. Both cases
become unambiguous with:
strtomem(p.small, "hello");
strtomem_pad(p.big, "hello", 0);
See also https://github.com/KSPP/linux/issues/90
Expand the memcpy KUnit tests to include these functions.
Cc: Wolfram Sang <wsa+renesas@sang-engineering.com>
Cc: Nick Desaulniers <ndesaulniers@google.com>
Cc: Geert Uytterhoeven <geert@linux-m68k.org>
Cc: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Kees Cook <keescook@chromium.org>
2022-08-26 18:04:43 +00:00
|
|
|
for (size_t i = 0; i < sizeof(instance.data); i++) { \
|
2021-06-26 00:45:15 +00:00
|
|
|
KUNIT_ASSERT_EQ_MSG(test, instance.data[i], v, \
|
|
|
|
"line %d: '%s' not initialized to 0x%02x @ %d (saw 0x%02x)\n", \
|
|
|
|
__LINE__, #instance, v, i, instance.data[i]); \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define compare(name, one, two) do { \
|
|
|
|
BUILD_BUG_ON(sizeof(one) != sizeof(two)); \
|
string: Introduce strtomem() and strtomem_pad()
One of the "legitimate" uses of strncpy() is copying a NUL-terminated
string into a fixed-size non-NUL-terminated character array. To avoid
the weaknesses and ambiguity of intent when using strncpy(), provide
replacement functions that explicitly distinguish between trailing
padding and not, and require the destination buffer size be discoverable
by the compiler.
For example:
struct obj {
int foo;
char small[4] __nonstring;
char big[8] __nonstring;
int bar;
};
struct obj p;
/* This will truncate to 4 chars with no trailing NUL */
strncpy(p.small, "hello", sizeof(p.small));
/* p.small contains 'h', 'e', 'l', 'l' */
/* This will NUL pad to 8 chars. */
strncpy(p.big, "hello", sizeof(p.big));
/* p.big contains 'h', 'e', 'l', 'l', 'o', '\0', '\0', '\0' */
When the "__nonstring" attributes are missing, the intent of the
programmer becomes ambiguous for whether the lack of a trailing NUL
in the p.small copy is a bug. Additionally, it's not clear whether
the trailing padding in the p.big copy is _needed_. Both cases
become unambiguous with:
strtomem(p.small, "hello");
strtomem_pad(p.big, "hello", 0);
See also https://github.com/KSPP/linux/issues/90
Expand the memcpy KUnit tests to include these functions.
Cc: Wolfram Sang <wsa+renesas@sang-engineering.com>
Cc: Nick Desaulniers <ndesaulniers@google.com>
Cc: Geert Uytterhoeven <geert@linux-m68k.org>
Cc: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Kees Cook <keescook@chromium.org>
2022-08-26 18:04:43 +00:00
|
|
|
for (size_t i = 0; i < sizeof(one); i++) { \
|
2021-06-26 00:45:15 +00:00
|
|
|
KUNIT_EXPECT_EQ_MSG(test, one.data[i], two.data[i], \
|
|
|
|
"line %d: %s.data[%d] (0x%02x) != %s.data[%d] (0x%02x)\n", \
|
|
|
|
__LINE__, #one, i, one.data[i], #two, i, two.data[i]); \
|
|
|
|
} \
|
|
|
|
kunit_info(test, "ok: " TEST_OP "() " name "\n"); \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
static void memcpy_test(struct kunit *test)
|
|
|
|
{
|
|
|
|
#define TEST_OP "memcpy"
|
|
|
|
struct some_bytes control = {
|
|
|
|
.data = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
|
|
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
|
|
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
|
|
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
struct some_bytes zero = { };
|
|
|
|
struct some_bytes middle = {
|
|
|
|
.data = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
|
|
0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
|
|
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
struct some_bytes three = {
|
|
|
|
.data = { 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
|
|
0x20, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
|
|
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
|
|
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
struct some_bytes dest = { };
|
|
|
|
int count;
|
|
|
|
u8 *ptr;
|
|
|
|
|
|
|
|
/* Verify static initializers. */
|
|
|
|
check(control, 0x20);
|
|
|
|
check(zero, 0);
|
|
|
|
compare("static initializers", dest, zero);
|
|
|
|
|
|
|
|
/* Verify assignment. */
|
|
|
|
dest = control;
|
|
|
|
compare("direct assignment", dest, control);
|
|
|
|
|
|
|
|
/* Verify complete overwrite. */
|
|
|
|
memcpy(dest.data, zero.data, sizeof(dest.data));
|
|
|
|
compare("complete overwrite", dest, zero);
|
|
|
|
|
|
|
|
/* Verify middle overwrite. */
|
|
|
|
dest = control;
|
|
|
|
memcpy(dest.data + 12, zero.data, 7);
|
|
|
|
compare("middle overwrite", dest, middle);
|
|
|
|
|
|
|
|
/* Verify argument side-effects aren't repeated. */
|
|
|
|
dest = control;
|
|
|
|
ptr = dest.data;
|
|
|
|
count = 1;
|
|
|
|
memcpy(ptr++, zero.data, count++);
|
|
|
|
ptr += 8;
|
|
|
|
memcpy(ptr++, zero.data, count++);
|
|
|
|
compare("argument side-effects", dest, three);
|
|
|
|
#undef TEST_OP
|
|
|
|
}
|
|
|
|
|
|
|
|
static void memmove_test(struct kunit *test)
|
|
|
|
{
|
|
|
|
#define TEST_OP "memmove"
|
|
|
|
struct some_bytes control = {
|
|
|
|
.data = { 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
struct some_bytes zero = { };
|
|
|
|
struct some_bytes middle = {
|
|
|
|
.data = { 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
0x99, 0x99, 0x99, 0x99, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
struct some_bytes five = {
|
|
|
|
.data = { 0x00, 0x00, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
0x99, 0x99, 0x00, 0x00, 0x00, 0x99, 0x99, 0x99,
|
|
|
|
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
struct some_bytes overlap = {
|
|
|
|
.data = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
|
|
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
|
|
|
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
struct some_bytes overlap_expected = {
|
|
|
|
.data = { 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x07,
|
|
|
|
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
|
|
|
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
struct some_bytes dest = { };
|
|
|
|
int count;
|
|
|
|
u8 *ptr;
|
|
|
|
|
|
|
|
/* Verify static initializers. */
|
|
|
|
check(control, 0x99);
|
|
|
|
check(zero, 0);
|
|
|
|
compare("static initializers", zero, dest);
|
|
|
|
|
|
|
|
/* Verify assignment. */
|
|
|
|
dest = control;
|
|
|
|
compare("direct assignment", dest, control);
|
|
|
|
|
|
|
|
/* Verify complete overwrite. */
|
|
|
|
memmove(dest.data, zero.data, sizeof(dest.data));
|
|
|
|
compare("complete overwrite", dest, zero);
|
|
|
|
|
|
|
|
/* Verify middle overwrite. */
|
|
|
|
dest = control;
|
|
|
|
memmove(dest.data + 12, zero.data, 7);
|
|
|
|
compare("middle overwrite", dest, middle);
|
|
|
|
|
|
|
|
/* Verify argument side-effects aren't repeated. */
|
|
|
|
dest = control;
|
|
|
|
ptr = dest.data;
|
|
|
|
count = 2;
|
|
|
|
memmove(ptr++, zero.data, count++);
|
|
|
|
ptr += 9;
|
|
|
|
memmove(ptr++, zero.data, count++);
|
|
|
|
compare("argument side-effects", dest, five);
|
|
|
|
|
|
|
|
/* Verify overlapping overwrite is correct. */
|
|
|
|
ptr = &overlap.data[2];
|
|
|
|
memmove(ptr, overlap.data, 5);
|
|
|
|
compare("overlapping write", overlap, overlap_expected);
|
|
|
|
#undef TEST_OP
|
|
|
|
}
|
|
|
|
|
|
|
|
static void memset_test(struct kunit *test)
|
|
|
|
{
|
|
|
|
#define TEST_OP "memset"
|
|
|
|
struct some_bytes control = {
|
|
|
|
.data = { 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
|
|
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
|
|
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
|
|
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
struct some_bytes complete = {
|
|
|
|
.data = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
struct some_bytes middle = {
|
|
|
|
.data = { 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x31,
|
|
|
|
0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
|
|
|
|
0x31, 0x31, 0x31, 0x31, 0x30, 0x30, 0x30, 0x30,
|
|
|
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
struct some_bytes three = {
|
|
|
|
.data = { 0x60, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
|
|
|
0x30, 0x61, 0x61, 0x30, 0x30, 0x30, 0x30, 0x30,
|
|
|
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
|
|
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
|
|
|
},
|
|
|
|
};
|
2021-05-18 03:16:57 +00:00
|
|
|
struct some_bytes after = {
|
|
|
|
.data = { 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x72,
|
|
|
|
0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72,
|
|
|
|
0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72,
|
|
|
|
0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72,
|
|
|
|
},
|
|
|
|
};
|
2021-05-18 03:16:57 +00:00
|
|
|
struct some_bytes startat = {
|
|
|
|
.data = { 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
|
|
|
0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
|
|
|
|
0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
|
|
|
|
0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
|
|
|
|
},
|
|
|
|
};
|
2021-06-26 00:45:15 +00:00
|
|
|
struct some_bytes dest = { };
|
|
|
|
int count, value;
|
|
|
|
u8 *ptr;
|
|
|
|
|
|
|
|
/* Verify static initializers. */
|
|
|
|
check(control, 0x30);
|
|
|
|
check(dest, 0);
|
|
|
|
|
|
|
|
/* Verify assignment. */
|
|
|
|
dest = control;
|
|
|
|
compare("direct assignment", dest, control);
|
|
|
|
|
|
|
|
/* Verify complete overwrite. */
|
|
|
|
memset(dest.data, 0xff, sizeof(dest.data));
|
|
|
|
compare("complete overwrite", dest, complete);
|
|
|
|
|
|
|
|
/* Verify middle overwrite. */
|
|
|
|
dest = control;
|
|
|
|
memset(dest.data + 4, 0x31, 16);
|
|
|
|
compare("middle overwrite", dest, middle);
|
|
|
|
|
|
|
|
/* Verify argument side-effects aren't repeated. */
|
|
|
|
dest = control;
|
|
|
|
ptr = dest.data;
|
|
|
|
value = 0x60;
|
|
|
|
count = 1;
|
|
|
|
memset(ptr++, value++, count++);
|
|
|
|
ptr += 8;
|
|
|
|
memset(ptr++, value++, count++);
|
|
|
|
compare("argument side-effects", dest, three);
|
2021-05-18 03:16:57 +00:00
|
|
|
|
|
|
|
/* Verify memset_after() */
|
|
|
|
dest = control;
|
|
|
|
memset_after(&dest, 0x72, three);
|
|
|
|
compare("memset_after()", dest, after);
|
|
|
|
|
2021-05-18 03:16:57 +00:00
|
|
|
/* Verify memset_startat() */
|
|
|
|
dest = control;
|
|
|
|
memset_startat(&dest, 0x79, four);
|
|
|
|
compare("memset_startat()", dest, startat);
|
2021-06-26 00:45:15 +00:00
|
|
|
#undef TEST_OP
|
|
|
|
}
|
|
|
|
|
string: Introduce strtomem() and strtomem_pad()
One of the "legitimate" uses of strncpy() is copying a NUL-terminated
string into a fixed-size non-NUL-terminated character array. To avoid
the weaknesses and ambiguity of intent when using strncpy(), provide
replacement functions that explicitly distinguish between trailing
padding and not, and require the destination buffer size be discoverable
by the compiler.
For example:
struct obj {
int foo;
char small[4] __nonstring;
char big[8] __nonstring;
int bar;
};
struct obj p;
/* This will truncate to 4 chars with no trailing NUL */
strncpy(p.small, "hello", sizeof(p.small));
/* p.small contains 'h', 'e', 'l', 'l' */
/* This will NUL pad to 8 chars. */
strncpy(p.big, "hello", sizeof(p.big));
/* p.big contains 'h', 'e', 'l', 'l', 'o', '\0', '\0', '\0' */
When the "__nonstring" attributes are missing, the intent of the
programmer becomes ambiguous for whether the lack of a trailing NUL
in the p.small copy is a bug. Additionally, it's not clear whether
the trailing padding in the p.big copy is _needed_. Both cases
become unambiguous with:
strtomem(p.small, "hello");
strtomem_pad(p.big, "hello", 0);
See also https://github.com/KSPP/linux/issues/90
Expand the memcpy KUnit tests to include these functions.
Cc: Wolfram Sang <wsa+renesas@sang-engineering.com>
Cc: Nick Desaulniers <ndesaulniers@google.com>
Cc: Geert Uytterhoeven <geert@linux-m68k.org>
Cc: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Kees Cook <keescook@chromium.org>
2022-08-26 18:04:43 +00:00
|
|
|
static void strtomem_test(struct kunit *test)
|
|
|
|
{
|
kunit/memcpy: Avoid pathological compile-time string size
The memcpy() KUnit tests are trying to sanity-check run-time behaviors,
but tripped compile-time warnings about a pathological condition of a
too-small buffer being used for input. Avoid this by explicitly resizing
the buffer, but leaving the string short. Avoid the following warning:
lib/memcpy_kunit.c: In function 'strtomem_test':
include/linux/string.h:303:42: warning: 'strnlen' specified bound 4 exceeds source size 3 [-Wstringop-overread]
303 | memcpy(dest, src, min(_dest_len, strnlen(src, _dest_len))); \
include/linux/minmax.h:32:39: note: in definition of macro '__cmp_once'
32 | typeof(y) unique_y = (y); \
| ^
include/linux/minmax.h:45:25: note: in expansion of macro '__careful_cmp'
45 | #define min(x, y) __careful_cmp(x, y, <)
| ^~~~~~~~~~~~~
include/linux/string.h:303:27: note: in expansion of macro 'min'
303 | memcpy(dest, src, min(_dest_len, strnlen(src, _dest_len))); \
| ^~~
lib/memcpy_kunit.c:290:9: note: in expansion of macro 'strtomem'
290 | strtomem(wrap.output, input);
| ^~~~~~~~
lib/memcpy_kunit.c:275:27: note: source object allocated here
275 | static const char input[] = "hi";
| ^~~~~
Reported-by: kernel test robot <lkp@intel.com>
Link: https://lore.kernel.org/linux-mm/202209070728.o3stvgVt-lkp@intel.com
Fixes: dfbafa70bde2 ("string: Introduce strtomem() and strtomem_pad()")
Signed-off-by: Kees Cook <keescook@chromium.org>
2022-09-07 23:27:06 +00:00
|
|
|
static const char input[sizeof(unsigned long)] = "hi";
|
string: Introduce strtomem() and strtomem_pad()
One of the "legitimate" uses of strncpy() is copying a NUL-terminated
string into a fixed-size non-NUL-terminated character array. To avoid
the weaknesses and ambiguity of intent when using strncpy(), provide
replacement functions that explicitly distinguish between trailing
padding and not, and require the destination buffer size be discoverable
by the compiler.
For example:
struct obj {
int foo;
char small[4] __nonstring;
char big[8] __nonstring;
int bar;
};
struct obj p;
/* This will truncate to 4 chars with no trailing NUL */
strncpy(p.small, "hello", sizeof(p.small));
/* p.small contains 'h', 'e', 'l', 'l' */
/* This will NUL pad to 8 chars. */
strncpy(p.big, "hello", sizeof(p.big));
/* p.big contains 'h', 'e', 'l', 'l', 'o', '\0', '\0', '\0' */
When the "__nonstring" attributes are missing, the intent of the
programmer becomes ambiguous for whether the lack of a trailing NUL
in the p.small copy is a bug. Additionally, it's not clear whether
the trailing padding in the p.big copy is _needed_. Both cases
become unambiguous with:
strtomem(p.small, "hello");
strtomem_pad(p.big, "hello", 0);
See also https://github.com/KSPP/linux/issues/90
Expand the memcpy KUnit tests to include these functions.
Cc: Wolfram Sang <wsa+renesas@sang-engineering.com>
Cc: Nick Desaulniers <ndesaulniers@google.com>
Cc: Geert Uytterhoeven <geert@linux-m68k.org>
Cc: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Kees Cook <keescook@chromium.org>
2022-08-26 18:04:43 +00:00
|
|
|
static const char truncate[] = "this is too long";
|
|
|
|
struct {
|
|
|
|
unsigned long canary1;
|
|
|
|
unsigned char output[sizeof(unsigned long)] __nonstring;
|
|
|
|
unsigned long canary2;
|
|
|
|
} wrap;
|
|
|
|
|
|
|
|
memset(&wrap, 0xFF, sizeof(wrap));
|
|
|
|
KUNIT_EXPECT_EQ_MSG(test, wrap.canary1, ULONG_MAX,
|
|
|
|
"bad initial canary value");
|
|
|
|
KUNIT_EXPECT_EQ_MSG(test, wrap.canary2, ULONG_MAX,
|
|
|
|
"bad initial canary value");
|
|
|
|
|
|
|
|
/* Check unpadded copy leaves surroundings untouched. */
|
|
|
|
strtomem(wrap.output, input);
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.canary1, ULONG_MAX);
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.output[0], input[0]);
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.output[1], input[1]);
|
|
|
|
for (size_t i = 2; i < sizeof(wrap.output); i++)
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.output[i], 0xFF);
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.canary2, ULONG_MAX);
|
|
|
|
|
|
|
|
/* Check truncated copy leaves surroundings untouched. */
|
|
|
|
memset(&wrap, 0xFF, sizeof(wrap));
|
|
|
|
strtomem(wrap.output, truncate);
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.canary1, ULONG_MAX);
|
|
|
|
for (size_t i = 0; i < sizeof(wrap.output); i++)
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.output[i], truncate[i]);
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.canary2, ULONG_MAX);
|
|
|
|
|
|
|
|
/* Check padded copy leaves only string padded. */
|
|
|
|
memset(&wrap, 0xFF, sizeof(wrap));
|
|
|
|
strtomem_pad(wrap.output, input, 0xAA);
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.canary1, ULONG_MAX);
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.output[0], input[0]);
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.output[1], input[1]);
|
|
|
|
for (size_t i = 2; i < sizeof(wrap.output); i++)
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.output[i], 0xAA);
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.canary2, ULONG_MAX);
|
|
|
|
|
|
|
|
/* Check truncated padded copy has no padding. */
|
|
|
|
memset(&wrap, 0xFF, sizeof(wrap));
|
|
|
|
strtomem(wrap.output, truncate);
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.canary1, ULONG_MAX);
|
|
|
|
for (size_t i = 0; i < sizeof(wrap.output); i++)
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.output[i], truncate[i]);
|
|
|
|
KUNIT_EXPECT_EQ(test, wrap.canary2, ULONG_MAX);
|
|
|
|
}
|
|
|
|
|
2021-06-26 00:45:15 +00:00
|
|
|
static struct kunit_case memcpy_test_cases[] = {
|
|
|
|
KUNIT_CASE(memset_test),
|
|
|
|
KUNIT_CASE(memcpy_test),
|
|
|
|
KUNIT_CASE(memmove_test),
|
string: Introduce strtomem() and strtomem_pad()
One of the "legitimate" uses of strncpy() is copying a NUL-terminated
string into a fixed-size non-NUL-terminated character array. To avoid
the weaknesses and ambiguity of intent when using strncpy(), provide
replacement functions that explicitly distinguish between trailing
padding and not, and require the destination buffer size be discoverable
by the compiler.
For example:
struct obj {
int foo;
char small[4] __nonstring;
char big[8] __nonstring;
int bar;
};
struct obj p;
/* This will truncate to 4 chars with no trailing NUL */
strncpy(p.small, "hello", sizeof(p.small));
/* p.small contains 'h', 'e', 'l', 'l' */
/* This will NUL pad to 8 chars. */
strncpy(p.big, "hello", sizeof(p.big));
/* p.big contains 'h', 'e', 'l', 'l', 'o', '\0', '\0', '\0' */
When the "__nonstring" attributes are missing, the intent of the
programmer becomes ambiguous for whether the lack of a trailing NUL
in the p.small copy is a bug. Additionally, it's not clear whether
the trailing padding in the p.big copy is _needed_. Both cases
become unambiguous with:
strtomem(p.small, "hello");
strtomem_pad(p.big, "hello", 0);
See also https://github.com/KSPP/linux/issues/90
Expand the memcpy KUnit tests to include these functions.
Cc: Wolfram Sang <wsa+renesas@sang-engineering.com>
Cc: Nick Desaulniers <ndesaulniers@google.com>
Cc: Geert Uytterhoeven <geert@linux-m68k.org>
Cc: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Kees Cook <keescook@chromium.org>
2022-08-26 18:04:43 +00:00
|
|
|
KUNIT_CASE(strtomem_test),
|
2021-06-26 00:45:15 +00:00
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct kunit_suite memcpy_test_suite = {
|
|
|
|
.name = "memcpy",
|
|
|
|
.test_cases = memcpy_test_cases,
|
|
|
|
};
|
|
|
|
|
|
|
|
kunit_test_suite(memcpy_test_suite);
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|