[PATCH v15 9/9] lib/tests: memcpy_kunit: add memcpy_mc() and memcpy_mc_large() test
From: Ruidong Tian <hidden>
Date: 2026-06-18 09:22:48
Also in:
linux-arm-kernel, linux-mm, lkml
Subsystem:
fortify_source, library code, the rest · Maintainers:
Kees Cook, Andrew Morton, Linus Torvalds
memcpy_mc() is the Machine-Check safe memcpy variant that returns the number of bytes NOT copied on a hardware memory error, or 0 on success. Add two test cases modeled after the existing memcpy_test() and memcpy_large_test() implementations. Signed-off-by: Ruidong Tian <redacted> --- lib/tests/memcpy_kunit.c | 121 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-)
diff --git a/lib/tests/memcpy_kunit.c b/lib/tests/memcpy_kunit.c
index 812c1fa20fd9..87585fbe78c7 100644
--- a/lib/tests/memcpy_kunit.c
+++ b/lib/tests/memcpy_kunit.c@@ -554,6 +554,121 @@ static void copy_mc_page_test(struct kunit *test) } #endif /* __HAVE_ARCH_COPY_MC_PAGE */ +#ifdef __HAVE_ARCH_MEMCPY_MC +/* + * memcpy_mc() is a Machine-Check safe memcpy variant. + * Signature: int memcpy_mc(void *dst, const void *src, size_t len) + * Returns: 0 on success, or number of bytes NOT copied on MC error. + * + * In the normal (no-poison) path it must behave identically to memcpy() + * and always return 0. + */ +static void memcpy_mc_test(struct kunit *test) +{ +#define TEST_OP "memcpy_mc" + 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 = { }; + unsigned long ret; + 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. */ + ret = memcpy_mc(dest.data, zero.data, sizeof(dest.data)); + KUNIT_ASSERT_EQ(test, ret, 0); + compare("complete overwrite", dest, zero); + + /* Verify middle overwrite: 7 bytes at offset 12. */ + dest = control; + ret = memcpy_mc(dest.data + 12, zero.data, 7); + KUNIT_ASSERT_EQ(test, ret, 0); + compare("middle overwrite", dest, middle); + + /* Verify zero-length copy is a no-op. */ + dest = control; + ret = memcpy_mc(dest.data, zero.data, 0); + KUNIT_ASSERT_EQ(test, ret, 0); + compare("zero length", dest, control); + + /* Verify argument side-effects aren't repeated. */ + dest = control; + ptr = dest.data; + count = 1; + ret = memcpy_mc(ptr++, zero.data, count++); + KUNIT_ASSERT_EQ(test, ret, 0); + ptr += 8; + ret = memcpy_mc(ptr++, zero.data, count++); + KUNIT_ASSERT_EQ(test, ret, 0); + compare("argument side-effects", dest, three); +#undef TEST_OP +} + +static void memcpy_mc_large_test(struct kunit *test) +{ + init_large(test); + + /* Sweep 1..1024 bytes x shifting offset to cover all template paths. */ + for (int bytes = 1; bytes <= ARRAY_SIZE(large_src); bytes++) { + for (int offset = 0; offset < ARRAY_SIZE(large_src); offset++) { + int right_zero_pos = offset + bytes; + int right_zero_size = ARRAY_SIZE(large_dst) - right_zero_pos; + int ret; + + ret = memcpy_mc(large_dst + offset, large_src, bytes); + KUNIT_ASSERT_EQ_MSG(test, ret, 0, + "memcpy_mc returned %d with size %d at offset %d", + ret, bytes, offset); + + /* No write before copy area. */ + KUNIT_ASSERT_EQ_MSG(test, + memcmp(large_dst, large_zero, offset), 0, + "with size %d at offset %d", bytes, offset); + /* No write after copy area. */ + KUNIT_ASSERT_EQ_MSG(test, + memcmp(&large_dst[right_zero_pos], large_zero, + right_zero_size), 0, + "with size %d at offset %d", bytes, offset); + /* Byte-for-byte exact. */ + KUNIT_ASSERT_EQ_MSG(test, + memcmp(large_dst + offset, large_src, bytes), 0, + "with size %d at offset %d", bytes, offset); + + memset(large_dst + offset, 0, bytes); + } + cond_resched(); + } +} +#endif /* __HAVE_ARCH_MEMCPY_MC */ + static struct kunit_case memcpy_test_cases[] = { KUNIT_CASE(memset_test), KUNIT_CASE(memcpy_test),
@@ -564,6 +679,10 @@ static struct kunit_case memcpy_test_cases[] = { KUNIT_CASE(copy_page_test), #ifdef __HAVE_ARCH_COPY_MC_PAGE KUNIT_CASE(copy_mc_page_test), +#endif +#ifdef __HAVE_ARCH_MEMCPY_MC + KUNIT_CASE(memcpy_mc_test), + KUNIT_CASE_SLOW(memcpy_mc_large_test), #endif {} };
@@ -575,5 +694,5 @@ static struct kunit_suite memcpy_test_suite = { kunit_test_suite(memcpy_test_suite); -MODULE_DESCRIPTION("test cases for memcpy(), memmove(), memset() and copy_page()"); +MODULE_DESCRIPTION("test cases for memcpy(), memmove(), memset(), copy_page() and memcpy_mc()"); MODULE_LICENSE("GPL");
--
2.39.3