Revision 8e7f82deb0c0386a03b62e30082574347f8b57d5 authored by Filipe Manana on 12 September 2023, 10:45:39 UTC, committed by David Sterba on 14 September 2023, 21:24:42 UTC
When opening a directory (opendir(3)) or rewinding it (rewinddir(3)), we
are not holding the directory's inode locked, and this can result in later
attempting to add two entries to the directory with the same index number,
resulting in a transaction abort, with -EEXIST (-17), when inserting the
second delayed dir index. This results in a trace like the following:

  Sep 11 22:34:59 myhostname kernel: BTRFS error (device dm-3): err add delayed dir index item(name: cockroach-stderr.log) into the insertion tree of the delayed node(root id: 5, inode id: 4539217, errno: -17)
  Sep 11 22:34:59 myhostname kernel: ------------[ cut here ]------------
  Sep 11 22:34:59 myhostname kernel: kernel BUG at fs/btrfs/delayed-inode.c:1504!
  Sep 11 22:34:59 myhostname kernel: invalid opcode: 0000 [#1] PREEMPT SMP NOPTI
  Sep 11 22:34:59 myhostname kernel: CPU: 0 PID: 7159 Comm: cockroach Not tainted 6.4.15-200.fc38.x86_64 #1
  Sep 11 22:34:59 myhostname kernel: Hardware name: ASUS ESC500 G3/P9D WS, BIOS 2402 06/27/2018
  Sep 11 22:34:59 myhostname kernel: RIP: 0010:btrfs_insert_delayed_dir_index+0x1da/0x260
  Sep 11 22:34:59 myhostname kernel: Code: eb dd 48 (...)
  Sep 11 22:34:59 myhostname kernel: RSP: 0000:ffffa9980e0fbb28 EFLAGS: 00010282
  Sep 11 22:34:59 myhostname kernel: RAX: 0000000000000000 RBX: ffff8b10b8f4a3c0 RCX: 0000000000000000
  Sep 11 22:34:59 myhostname kernel: RDX: 0000000000000000 RSI: ffff8b177ec21540 RDI: ffff8b177ec21540
  Sep 11 22:34:59 myhostname kernel: RBP: ffff8b110cf80888 R08: 0000000000000000 R09: ffffa9980e0fb938
  Sep 11 22:34:59 myhostname kernel: R10: 0000000000000003 R11: ffffffff86146508 R12: 0000000000000014
  Sep 11 22:34:59 myhostname kernel: R13: ffff8b1131ae5b40 R14: ffff8b10b8f4a418 R15: 00000000ffffffef
  Sep 11 22:34:59 myhostname kernel: FS:  00007fb14a7fe6c0(0000) GS:ffff8b177ec00000(0000) knlGS:0000000000000000
  Sep 11 22:34:59 myhostname kernel: CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
  Sep 11 22:34:59 myhostname kernel: CR2: 000000c00143d000 CR3: 00000001b3b4e002 CR4: 00000000001706f0
  Sep 11 22:34:59 myhostname kernel: Call Trace:
  Sep 11 22:34:59 myhostname kernel:  <TASK>
  Sep 11 22:34:59 myhostname kernel:  ? die+0x36/0x90
  Sep 11 22:34:59 myhostname kernel:  ? do_trap+0xda/0x100
  Sep 11 22:34:59 myhostname kernel:  ? btrfs_insert_delayed_dir_index+0x1da/0x260
  Sep 11 22:34:59 myhostname kernel:  ? do_error_trap+0x6a/0x90
  Sep 11 22:34:59 myhostname kernel:  ? btrfs_insert_delayed_dir_index+0x1da/0x260
  Sep 11 22:34:59 myhostname kernel:  ? exc_invalid_op+0x50/0x70
  Sep 11 22:34:59 myhostname kernel:  ? btrfs_insert_delayed_dir_index+0x1da/0x260
  Sep 11 22:34:59 myhostname kernel:  ? asm_exc_invalid_op+0x1a/0x20
  Sep 11 22:34:59 myhostname kernel:  ? btrfs_insert_delayed_dir_index+0x1da/0x260
  Sep 11 22:34:59 myhostname kernel:  ? btrfs_insert_delayed_dir_index+0x1da/0x260
  Sep 11 22:34:59 myhostname kernel:  btrfs_insert_dir_item+0x200/0x280
  Sep 11 22:34:59 myhostname kernel:  btrfs_add_link+0xab/0x4f0
  Sep 11 22:34:59 myhostname kernel:  ? ktime_get_real_ts64+0x47/0xe0
  Sep 11 22:34:59 myhostname kernel:  btrfs_create_new_inode+0x7cd/0xa80
  Sep 11 22:34:59 myhostname kernel:  btrfs_symlink+0x190/0x4d0
  Sep 11 22:34:59 myhostname kernel:  ? schedule+0x5e/0xd0
  Sep 11 22:34:59 myhostname kernel:  ? __d_lookup+0x7e/0xc0
  Sep 11 22:34:59 myhostname kernel:  vfs_symlink+0x148/0x1e0
  Sep 11 22:34:59 myhostname kernel:  do_symlinkat+0x130/0x140
  Sep 11 22:34:59 myhostname kernel:  __x64_sys_symlinkat+0x3d/0x50
  Sep 11 22:34:59 myhostname kernel:  do_syscall_64+0x5d/0x90
  Sep 11 22:34:59 myhostname kernel:  ? syscall_exit_to_user_mode+0x2b/0x40
  Sep 11 22:34:59 myhostname kernel:  ? do_syscall_64+0x6c/0x90
  Sep 11 22:34:59 myhostname kernel:  entry_SYSCALL_64_after_hwframe+0x72/0xdc

The race leading to the problem happens like this:

1) Directory inode X is loaded into memory, its ->index_cnt field is
   initialized to (u64)-1 (at btrfs_alloc_inode());

2) Task A is adding a new file to directory X, holding its vfs inode lock,
   and calls btrfs_set_inode_index() to get an index number for the entry.

   Because the inode's index_cnt field is set to (u64)-1 it calls
   btrfs_inode_delayed_dir_index_count() which fails because no dir index
   entries were added yet to the delayed inode and then it calls
   btrfs_set_inode_index_count(). This functions finds the last dir index
   key and then sets index_cnt to that index value + 1. It found that the
   last index key has an offset of 100. However before it assigns a value
   of 101 to index_cnt...

3) Task B calls opendir(3), ending up at btrfs_opendir(), where the VFS
   lock for inode X is not taken, so it calls btrfs_get_dir_last_index()
   and sees index_cnt still with a value of (u64)-1. Because of that it
   calls btrfs_inode_delayed_dir_index_count() which fails since no dir
   index entries were added to the delayed inode yet, and then it also
   calls btrfs_set_inode_index_count(). This also finds that the last
   index key has an offset of 100, and before it assigns the value 101
   to the index_cnt field of inode X...

4) Task A assigns a value of 101 to index_cnt. And then the code flow
   goes to btrfs_set_inode_index() where it increments index_cnt from
   101 to 102. Task A then creates a delayed dir index entry with a
   sequence number of 101 and adds it to the delayed inode;

5) Task B assigns 101 to the index_cnt field of inode X;

6) At some later point when someone tries to add a new entry to the
   directory, btrfs_set_inode_index() will return 101 again and shortly
   after an attempt to add another delayed dir index key with index
   number 101 will fail with -EEXIST resulting in a transaction abort.

Fix this by locking the inode at btrfs_get_dir_last_index(), which is only
only used when opening a directory or attempting to lseek on it.

Reported-by: ken <ken@bllue.org>
Link: https://lore.kernel.org/linux-btrfs/CAE6xmH+Lp=Q=E61bU+v9eWX8gYfLvu6jLYxjxjFpo3zHVPR0EQ@mail.gmail.com/
Reported-by: syzbot+d13490c82ad5353c779d@syzkaller.appspotmail.com
Link: https://lore.kernel.org/linux-btrfs/00000000000036e1290603e097e0@google.com/
Fixes: 9b378f6ad48c ("btrfs: fix infinite directory reads")
CC: stable@vger.kernel.org # 6.5+
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
1 parent e60aa5d
Raw File
memcpy_kunit.c
// 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);	\
	for (size_t i = 0; i < sizeof(instance.data); i++) {	\
		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)); \
	for (size_t i = 0; i < sizeof(one); i++) {	\
		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 unsigned char larger_array [2048];

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);

	/* Verify larger overlapping moves. */
	larger_array[256] = 0xAAu;
	/*
	 * Test a backwards overlapping memmove first. 256 and 1024 are
	 * important for i386 to use rep movsl.
	 */
	memmove(larger_array, larger_array + 256, 1024);
	KUNIT_ASSERT_EQ(test, larger_array[0], 0xAAu);
	KUNIT_ASSERT_EQ(test, larger_array[256], 0x00);
	KUNIT_ASSERT_NULL(test,
		memchr(larger_array + 1, 0xaa, ARRAY_SIZE(larger_array) - 1));
	/* Test a forwards overlapping memmove. */
	larger_array[0] = 0xBBu;
	memmove(larger_array + 256, larger_array, 1024);
	KUNIT_ASSERT_EQ(test, larger_array[0], 0xBBu);
	KUNIT_ASSERT_EQ(test, larger_array[256], 0xBBu);
	KUNIT_ASSERT_NULL(test, memchr(larger_array + 1, 0xBBu, 256 - 1));
	KUNIT_ASSERT_NULL(test,
		memchr(larger_array + 257, 0xBBu, ARRAY_SIZE(larger_array) - 257));
#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,
			},
	};
	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,
			},
	};
	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,
			},
	};
	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);

	/* Verify memset_after() */
	dest = control;
	memset_after(&dest, 0x72, three);
	compare("memset_after()", dest, after);

	/* Verify memset_startat() */
	dest = control;
	memset_startat(&dest, 0x79, four);
	compare("memset_startat()", dest, startat);
#undef TEST_OP
}

static u8 large_src[1024];
static u8 large_dst[2048];
static const u8 large_zero[2048];

static void set_random_nonzero(struct kunit *test, u8 *byte)
{
	int failed_rng = 0;

	while (*byte == 0) {
		get_random_bytes(byte, 1);
		KUNIT_ASSERT_LT_MSG(test, failed_rng++, 100,
				    "Is the RNG broken?");
	}
}

static void init_large(struct kunit *test)
{
	if (!IS_ENABLED(CONFIG_MEMCPY_SLOW_KUNIT_TEST))
		kunit_skip(test, "Slow test skipped. Enable with CONFIG_MEMCPY_SLOW_KUNIT_TEST=y");

	/* Get many bit patterns. */
	get_random_bytes(large_src, ARRAY_SIZE(large_src));

	/* Make sure we have non-zero edges. */
	set_random_nonzero(test, &large_src[0]);
	set_random_nonzero(test, &large_src[ARRAY_SIZE(large_src) - 1]);

	/* Explicitly zero the entire destination. */
	memset(large_dst, 0, ARRAY_SIZE(large_dst));
}

/*
 * Instead of an indirect function call for "copy" or a giant macro,
 * use a bool to pick memcpy or memmove.
 */
static void copy_large_test(struct kunit *test, bool use_memmove)
{
	init_large(test);

	/* Copy a growing number of non-overlapping bytes ... */
	for (int bytes = 1; bytes <= ARRAY_SIZE(large_src); bytes++) {
		/* Over a shifting destination window ... */
		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;

			/* Copy! */
			if (use_memmove)
				memmove(large_dst + offset, large_src, bytes);
			else
				memcpy(large_dst + offset, large_src, bytes);

			/* Did we touch anything before the copy area? */
			KUNIT_ASSERT_EQ_MSG(test,
				memcmp(large_dst, large_zero, offset), 0,
				"with size %d at offset %d", bytes, offset);
			/* Did we touch anything after the 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);

			/* Are we byte-for-byte exact across the copy? */
			KUNIT_ASSERT_EQ_MSG(test,
				memcmp(large_dst + offset, large_src, bytes), 0,
				"with size %d at offset %d", bytes, offset);

			/* Zero out what we copied for the next cycle. */
			memset(large_dst + offset, 0, bytes);
		}
		/* Avoid stall warnings if this loop gets slow. */
		cond_resched();
	}
}

static void memcpy_large_test(struct kunit *test)
{
	copy_large_test(test, false);
}

static void memmove_large_test(struct kunit *test)
{
	copy_large_test(test, true);
}

/*
 * On the assumption that boundary conditions are going to be the most
 * sensitive, instead of taking a full step (inc) each iteration,
 * take single index steps for at least the first "inc"-many indexes
 * from the "start" and at least the last "inc"-many indexes before
 * the "end". When in the middle, take full "inc"-wide steps. For
 * example, calling next_step(idx, 1, 15, 3) with idx starting at 0
 * would see the following pattern: 1 2 3 4 7 10 11 12 13 14 15.
 */
static int next_step(int idx, int start, int end, int inc)
{
	start += inc;
	end -= inc;

	if (idx < start || idx + inc > end)
		inc = 1;
	return idx + inc;
}

static void inner_loop(struct kunit *test, int bytes, int d_off, int s_off)
{
	int left_zero_pos, left_zero_size;
	int right_zero_pos, right_zero_size;
	int src_pos, src_orig_pos, src_size;
	int pos;

	/* Place the source in the destination buffer. */
	memcpy(&large_dst[s_off], large_src, bytes);

	/* Copy to destination offset. */
	memmove(&large_dst[d_off], &large_dst[s_off], bytes);

	/* Make sure destination entirely matches. */
	KUNIT_ASSERT_EQ_MSG(test, memcmp(&large_dst[d_off], large_src, bytes), 0,
		"with size %d at src offset %d and dest offset %d",
		bytes, s_off, d_off);

	/* Calculate the expected zero spans. */
	if (s_off < d_off) {
		left_zero_pos = 0;
		left_zero_size = s_off;

		right_zero_pos = d_off + bytes;
		right_zero_size = ARRAY_SIZE(large_dst) - right_zero_pos;

		src_pos = s_off;
		src_orig_pos = 0;
		src_size = d_off - s_off;
	} else {
		left_zero_pos = 0;
		left_zero_size = d_off;

		right_zero_pos = s_off + bytes;
		right_zero_size = ARRAY_SIZE(large_dst) - right_zero_pos;

		src_pos = d_off + bytes;
		src_orig_pos = src_pos - s_off;
		src_size = right_zero_pos - src_pos;
	}

	/* Check non-overlapping source is unchanged.*/
	KUNIT_ASSERT_EQ_MSG(test,
		memcmp(&large_dst[src_pos], &large_src[src_orig_pos], src_size), 0,
		"with size %d at src offset %d and dest offset %d",
		bytes, s_off, d_off);

	/* Check leading buffer contents are zero. */
	KUNIT_ASSERT_EQ_MSG(test,
		memcmp(&large_dst[left_zero_pos], large_zero, left_zero_size), 0,
		"with size %d at src offset %d and dest offset %d",
		bytes, s_off, d_off);
	/* Check trailing buffer contents are zero. */
	KUNIT_ASSERT_EQ_MSG(test,
		memcmp(&large_dst[right_zero_pos], large_zero, right_zero_size), 0,
		"with size %d at src offset %d and dest offset %d",
		bytes, s_off, d_off);

	/* Zero out everything not already zeroed.*/
	pos = left_zero_pos + left_zero_size;
	memset(&large_dst[pos], 0, right_zero_pos - pos);
}

static void memmove_overlap_test(struct kunit *test)
{
	/*
	 * Running all possible offset and overlap combinations takes a
	 * very long time. Instead, only check up to 128 bytes offset
	 * into the destination buffer (which should result in crossing
	 * cachelines), with a step size of 1 through 7 to try to skip some
	 * redundancy.
	 */
	static const int offset_max = 128; /* less than ARRAY_SIZE(large_src); */
	static const int bytes_step = 7;
	static const int window_step = 7;

	static const int bytes_start = 1;
	static const int bytes_end = ARRAY_SIZE(large_src) + 1;

	init_large(test);

	/* Copy a growing number of overlapping bytes ... */
	for (int bytes = bytes_start; bytes < bytes_end;
	     bytes = next_step(bytes, bytes_start, bytes_end, bytes_step)) {

		/* Over a shifting destination window ... */
		for (int d_off = 0; d_off < offset_max; d_off++) {
			int s_start = max(d_off - bytes, 0);
			int s_end = min_t(int, d_off + bytes, ARRAY_SIZE(large_src));

			/* Over a shifting source window ... */
			for (int s_off = s_start; s_off < s_end;
			     s_off = next_step(s_off, s_start, s_end, window_step))
				inner_loop(test, bytes, d_off, s_off);

			/* Avoid stall warnings. */
			cond_resched();
		}
	}
}

static void strtomem_test(struct kunit *test)
{
	static const char input[sizeof(unsigned long)] = "hi";
	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);
}

static struct kunit_case memcpy_test_cases[] = {
	KUNIT_CASE(memset_test),
	KUNIT_CASE(memcpy_test),
	KUNIT_CASE(memcpy_large_test),
	KUNIT_CASE(memmove_test),
	KUNIT_CASE(memmove_large_test),
	KUNIT_CASE(memmove_overlap_test),
	KUNIT_CASE(strtomem_test),
	{}
};

static struct kunit_suite memcpy_test_suite = {
	.name = "memcpy",
	.test_cases = memcpy_test_cases,
};

kunit_test_suite(memcpy_test_suite);

MODULE_LICENSE("GPL");
back to top