Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c614584c authored by Ming-Hung Tsai's avatar Ming-Hung Tsai Committed by Greg Kroah-Hartman
Browse files

dm cache: prevent BUG_ON by blocking retries on failed device resumes



[ Upstream commit 5da692e2262b8f81993baa9592f57d12c2703dea ]

A cache device failing to resume due to mapping errors should not be
retried, as the failure leaves a partially initialized policy object.
Repeating the resume operation risks triggering BUG_ON when reloading
cache mappings into the incomplete policy object.

Reproduce steps:

1. create a cache metadata consisting of 512 or more cache blocks,
   with some mappings stored in the first array block of the mapping
   array. Here we use cache_restore v1.0 to build the metadata.

cat <<EOF >> cmeta.xml
<superblock uuid="" block_size="128" nr_cache_blocks="512" \
policy="smq" hint_width="4">
  <mappings>
    <mapping cache_block="0" origin_block="0" dirty="false"/>
  </mappings>
</superblock>
EOF
dmsetup create cmeta --table "0 8192 linear /dev/sdc 0"
cache_restore -i cmeta.xml -o /dev/mapper/cmeta --metadata-version=2
dmsetup remove cmeta

2. wipe the second array block of the mapping array to simulate
   data degradations.

mapping_root=$(dd if=/dev/sdc bs=1c count=8 skip=192 \
2>/dev/null | hexdump -e '1/8 "%u\n"')
ablock=$(dd if=/dev/sdc bs=1c count=8 skip=$((4096*mapping_root+2056)) \
2>/dev/null | hexdump -e '1/8 "%u\n"')
dd if=/dev/zero of=/dev/sdc bs=4k count=1 seek=$ablock

3. try bringing up the cache device. The resume is expected to fail
   due to the broken array block.

dmsetup create cmeta --table "0 8192 linear /dev/sdc 0"
dmsetup create cdata --table "0 65536 linear /dev/sdc 8192"
dmsetup create corig --table "0 524288 linear /dev/sdc 262144"
dmsetup create cache --notable
dmsetup load cache --table "0 524288 cache /dev/mapper/cmeta \
/dev/mapper/cdata /dev/mapper/corig 128 2 metadata2 writethrough smq 0"
dmsetup resume cache

4. try resuming the cache again. An unexpected BUG_ON is triggered
   while loading cache mappings.

dmsetup resume cache

Kernel logs:

(snip)
------------[ cut here ]------------
kernel BUG at drivers/md/dm-cache-policy-smq.c:752!
Oops: invalid opcode: 0000 [#1] PREEMPT SMP KASAN NOPTI
CPU: 0 UID: 0 PID: 332 Comm: dmsetup Not tainted 6.13.4 #3
RIP: 0010:smq_load_mapping+0x3e5/0x570

Fix by disallowing resume operations for devices that failed the
initial attempt.

Signed-off-by: default avatarMing-Hung Tsai <mtsai@redhat.com>
Signed-off-by: default avatarMikulas Patocka <mpatocka@redhat.com>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
parent 33cc2620
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -2996,6 +2996,27 @@ static dm_cblock_t get_cache_dev_size(struct cache *cache)
	return to_cblock(size);
}

static bool can_resume(struct cache *cache)
{
	/*
	 * Disallow retrying the resume operation for devices that failed the
	 * first resume attempt, as the failure leaves the policy object partially
	 * initialized. Retrying could trigger BUG_ON when loading cache mappings
	 * into the incomplete policy object.
	 */
	if (cache->sized && !cache->loaded_mappings) {
		if (get_cache_mode(cache) != CM_WRITE)
			DMERR("%s: unable to resume a failed-loaded cache, please check metadata.",
			      cache_device_name(cache));
		else
			DMERR("%s: unable to resume cache due to missing proper cache table reload",
			      cache_device_name(cache));
		return false;
	}

	return true;
}

static bool can_resize(struct cache *cache, dm_cblock_t new_size)
{
	if (from_cblock(new_size) > from_cblock(cache->cache_size)) {
@@ -3044,6 +3065,9 @@ static int cache_preresume(struct dm_target *ti)
	struct cache *cache = ti->private;
	dm_cblock_t csize = get_cache_dev_size(cache);

	if (!can_resume(cache))
		return -EINVAL;

	/*
	 * Check to see if the cache has resized.
	 */