summaryrefslogtreecommitdiff
path: root/drivers/md
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-27 14:21:15 +0100
committerMark Brown <broonie@kernel.org>2026-07-27 14:21:15 +0100
commit3d6c2fda852eedc895548b76f214a045353b667f (patch)
tree71632d46fd712a080583e63f926834da3a07f07f /drivers/md
parentcaa51ac6fa94e3b8182bfa6bc5fe3f2210f7eeae (diff)
parenta46fd918f7907ecdfd1b1d03210463eef80f0def (diff)
downloadlinux-next-3d6c2fda852eedc895548b76f214a045353b667f.tar.gz
linux-next-3d6c2fda852eedc895548b76f214a045353b667f.zip
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/device-mapper/linux-dm.git
Diffstat (limited to 'drivers/md')
-rw-r--r--drivers/md/dm-cache-target.c56
-rw-r--r--drivers/md/dm-inlinecrypt.c1
-rw-r--r--drivers/md/dm-integrity.c6
-rw-r--r--drivers/md/dm-ioctl.c216
-rw-r--r--drivers/md/dm-log-userspace-base.c1
-rw-r--r--drivers/md/dm-pcache/cache.c29
-rw-r--r--drivers/md/dm-pcache/cache.h42
-rw-r--r--drivers/md/dm-pcache/cache_dev.c22
-rw-r--r--drivers/md/dm-pcache/cache_gc.c31
-rw-r--r--drivers/md/dm-pcache/cache_key.c128
-rw-r--r--drivers/md/dm-pcache/cache_req.c19
-rw-r--r--drivers/md/dm-pcache/cache_segment.c12
-rw-r--r--drivers/md/dm-pcache/cache_writeback.c38
-rw-r--r--drivers/md/dm-pcache/dm_pcache.c4
-rw-r--r--drivers/md/dm-snap.c4
-rw-r--r--drivers/md/dm-switch.c2
-rw-r--r--drivers/md/dm-table.c2
-rw-r--r--drivers/md/dm-vdo/repair.c6
-rw-r--r--drivers/md/dm-verity-target.c2
-rw-r--r--drivers/md/dm-zoned-metadata.c8
-rw-r--r--drivers/md/dm-zoned-reclaim.c4
21 files changed, 415 insertions, 218 deletions
diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c
index 097315a9bf0f..33dbc71b730f 100644
--- a/drivers/md/dm-cache-target.c
+++ b/drivers/md/dm-cache-target.c
@@ -16,6 +16,7 @@
#include <linux/dm-kcopyd.h>
#include <linux/jiffies.h>
#include <linux/init.h>
+#include <linux/kstrtox.h>
#include <linux/mempool.h>
#include <linux/module.h>
#include <linux/rwsem.h>
@@ -3311,42 +3312,46 @@ struct cblock_range {
dm_cblock_t end;
};
+static inline dm_cblock_t cblock_succ(dm_cblock_t b)
+{
+ return to_cblock(from_cblock(b) + 1);
+}
+
/*
* A cache block range can take two forms:
*
* i) A single cblock, eg. '3456'
* ii) A begin and end cblock with a dash between, eg. 123-234
*/
-static int parse_cblock_range(struct cache *cache, const char *str,
+static int parse_cblock_range(struct cache *cache, char *str,
struct cblock_range *result)
{
- char dummy;
- uint64_t b, e;
+ char *blocknr = strsep(&str, "-");
+ unsigned int b, e;
int r;
- /*
- * Try and parse form (ii) first.
- */
- r = sscanf(str, "%llu-%llu%c", &b, &e, &dummy);
+ r = kstrtouint(blocknr, 10, &b);
+ if (r)
+ goto bad;
- if (r == 2) {
- result->begin = to_cblock(b);
- result->end = to_cblock(e);
- return 0;
- }
+ result->begin = to_cblock(b);
- /*
- * That didn't work, try form (i).
- */
- r = sscanf(str, "%llu%c", &b, &dummy);
+ if (str) {
+ blocknr = str;
- if (r == 1) {
- result->begin = to_cblock(b);
- result->end = to_cblock(from_cblock(result->begin) + 1u);
- return 0;
+ r = kstrtouint(blocknr, 10, &e);
+ if (r)
+ goto bad;
+
+ result->end = to_cblock(e);
+ } else {
+ result->end = cblock_succ(result->begin);
}
- DMERR("%s: invalid cblock range '%s'", cache_device_name(cache), str);
+ return 0;
+
+bad:
+ DMERR("%s: invalid cblock range '%s'", cache_device_name(cache), blocknr);
return -EINVAL;
}
@@ -3377,11 +3382,6 @@ static int validate_cblock_range(struct cache *cache, struct cblock_range *range
return 0;
}
-static inline dm_cblock_t cblock_succ(dm_cblock_t b)
-{
- return to_cblock(from_cblock(b) + 1);
-}
-
static int request_invalidation(struct cache *cache, struct cblock_range *range)
{
int r = 0;
@@ -3405,7 +3405,7 @@ static int request_invalidation(struct cache *cache, struct cblock_range *range)
}
static int process_invalidate_cblocks_message(struct cache *cache, unsigned int count,
- const char **cblock_ranges)
+ char **cblock_ranges)
{
int r = 0;
unsigned int i;
@@ -3460,7 +3460,7 @@ static int cache_message(struct dm_target *ti, unsigned int argc, char **argv,
}
if (!strcasecmp(argv[0], "invalidate_cblocks"))
- return process_invalidate_cblocks_message(cache, argc - 1, (const char **) argv + 1);
+ return process_invalidate_cblocks_message(cache, argc - 1, argv + 1);
if (argc != 2)
return -EINVAL;
diff --git a/drivers/md/dm-inlinecrypt.c b/drivers/md/dm-inlinecrypt.c
index f50970db0f94..66fa2f9d1fdc 100644
--- a/drivers/md/dm-inlinecrypt.c
+++ b/drivers/md/dm-inlinecrypt.c
@@ -347,7 +347,6 @@ static int inlinecrypt_ctr(struct dm_target *ti, unsigned int argc, char **argv)
err = get_key_size(&argv[1]);
if (err < 0) {
ti->error = "Cannot parse key size";
- err = -EINVAL;
goto bad;
}
ctx->key_size = err;
diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c
index 1f2593f113f6..5156079785dc 100644
--- a/drivers/md/dm-integrity.c
+++ b/drivers/md/dm-integrity.c
@@ -2782,7 +2782,8 @@ static void integrity_bio_wait(struct work_struct *w)
switch (r) {
case DM_MAPIO_KILL:
bio->bi_status = BLK_STS_IOERR;
- fallthrough;
+ bio_endio(bio);
+ return;
case DM_MAPIO_REMAPPED:
submit_bio_noacct(bio);
fallthrough;
@@ -3040,7 +3041,7 @@ static void do_journal_write(struct dm_integrity_c *ic, unsigned int write_start
r = dm_integrity_rw_tag(ic, journal_entry_tag(ic, je2), &metadata_block, &metadata_offset,
ic->tag_size, TAG_WRITE);
if (unlikely(r))
- dm_integrity_io_error(ic, "reading tags", r);
+ dm_integrity_io_error(ic, "writing tags", r);
}
atomic_inc(&comp.in_flight);
@@ -4634,6 +4635,7 @@ retest_commit_id:
if (!ic->journal_tree) {
*error = "Could not allocate memory for journal tree";
r = -ENOMEM;
+ goto bad;
}
bad:
kfree(crypt_data);
diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c
index 61af2a437a05..a6b8e97755cd 100644
--- a/drivers/md/dm-ioctl.c
+++ b/drivers/md/dm-ioctl.c
@@ -54,10 +54,8 @@ struct hash_cell {
};
struct vers_iter {
- size_t param_size;
struct dm_target_versions *vers, *old_vers;
char *end;
- uint32_t flags;
};
@@ -694,118 +692,108 @@ static int list_devices(struct file *filp, struct dm_ioctl *param, size_t param_
{
struct rb_node *n;
struct hash_cell *hc;
- size_t len, needed = 0;
- struct gendisk *disk;
- struct dm_name_list *orig_nl, *nl, *old_nl = NULL;
+ size_t len;
+ struct dm_name_list *nl, *old_nl = NULL;
+ void *result_start, *result_limit;
uint32_t *event_nr;
- down_write(&_hash_lock);
-
- /*
- * Loop through all the devices working out how much
- * space we need.
- */
- for (n = rb_first(&name_rb_tree); n; n = rb_next(n)) {
- hc = container_of(n, struct hash_cell, name_node);
- if (!filter_device(hc, param->name, param->uuid))
- continue;
- needed += align_val(offsetof(struct dm_name_list, name) + strlen(hc->name) + 1);
- needed += align_val(sizeof(uint32_t) * 2);
- if (param->flags & DM_UUID_FLAG && hc->uuid)
- needed += align_val(strlen(hc->uuid) + 1);
- }
-
/*
* Grab our output buffer.
*/
- nl = orig_nl = get_result_buffer(param, param_size, &len);
- if (len < needed || len < sizeof(nl->dev)) {
- param->flags |= DM_BUFFER_FULL_FLAG;
- goto out;
- }
- param->data_size = param->data_start + needed;
+ nl = result_start = get_result_buffer(param, param_size, &len);
+ result_limit = result_start + len;
- nl->dev = 0; /* Flags no data */
+ if (len >= sizeof(*nl))
+ nl->dev = 0; /* Flags no data */
+
+ down_write(&_hash_lock);
/*
- * Now loop through filling out the names.
+ * Loop through filling out the names.
*/
for (n = rb_first(&name_rb_tree); n; n = rb_next(n)) {
- void *uuid_ptr;
+ void *next_nl;
hc = container_of(n, struct hash_cell, name_node);
if (!filter_device(hc, param->name, param->uuid))
continue;
- if (old_nl)
- old_nl->next = (uint32_t) ((void *) nl -
- (void *) old_nl);
- disk = dm_disk(hc->md);
- nl->dev = huge_encode_dev(disk_devt(disk));
- nl->next = 0;
- strcpy(nl->name, hc->name);
- old_nl = nl;
- event_nr = align_ptr(nl->name + strlen(hc->name) + 1);
+ len = strlen(hc->name);
+ event_nr = align_ptr(nl->name + len + 1);
+ next_nl = event_nr + 2;
+ if (next_nl > result_limit)
+ break;
+
+ ((u64 *)event_nr)[-1] = 0;
+ memcpy(nl->name, hc->name, len);
+
+ nl->dev = huge_encode_dev(disk_devt(dm_disk(hc->md)));
+
event_nr[0] = dm_get_event_nr(hc->md);
event_nr[1] = 0;
- uuid_ptr = align_ptr(event_nr + 2);
+
if (param->flags & DM_UUID_FLAG) {
if (hc->uuid) {
+ len = strlen(hc->uuid);
+ next_nl = align_ptr(next_nl + len + 1);
+ if (next_nl > result_limit)
+ break;
event_nr[1] |= DM_NAME_LIST_FLAG_HAS_UUID;
- strcpy(uuid_ptr, hc->uuid);
- uuid_ptr = align_ptr(uuid_ptr + strlen(hc->uuid) + 1);
+ ((u64 *)next_nl)[-1] = 0;
+ memcpy(event_nr + 2, hc->uuid, len);
} else {
event_nr[1] |= DM_NAME_LIST_FLAG_DOESNT_HAVE_UUID;
}
}
- nl = uuid_ptr;
+ nl->next = next_nl - (void *)nl;
+ old_nl = nl;
+ nl = next_nl;
}
- /*
- * If mismatch happens, security may be compromised due to buffer
- * overflow, so it's better to crash.
- */
- BUG_ON((char *)nl - (char *)orig_nl != needed);
- out:
- up_write(&_hash_lock);
- return 0;
-}
+ if (old_nl)
+ old_nl->next = 0;
-static void list_version_get_needed(struct target_type *tt, void *needed_param)
-{
- size_t *needed = needed_param;
+ if (n)
+ param->flags |= DM_BUFFER_FULL_FLAG;
+ else
+ param->data_size = param->data_start + ((void *)nl - result_start);
- *needed += sizeof(struct dm_target_versions);
- *needed += strlen(tt->name) + 1;
- *needed += ALIGN_MASK;
+ up_write(&_hash_lock);
+ return 0;
}
static void list_version_get_info(struct target_type *tt, void *param)
{
struct vers_iter *info = param;
+ struct dm_target_versions *vers = info->vers;
+ size_t name_len = strlen(tt->name);
+
+ if (!vers)
+ return;
+
+ info->old_vers = vers;
+ info->vers = align_ptr((void *)(info->vers + 1) + name_len + 1);
- /* Check space - it might have changed since the first iteration */
- if ((char *)info->vers + sizeof(struct dm_target_versions) + strlen(tt->name) + 1 > info->end) {
- info->flags = DM_BUFFER_FULL_FLAG;
+ /* Check space */
+ if ((char *)info->vers > info->end) {
+ info->vers = NULL;
return;
}
- if (info->old_vers)
- info->old_vers->next = (uint32_t) ((void *)info->vers - (void *)info->old_vers);
+ /* Zero padding and terminate vers->name[] */
+ ((u64 *)info->vers)[-1] = 0;
- info->vers->version[0] = tt->version[0];
- info->vers->version[1] = tt->version[1];
- info->vers->version[2] = tt->version[2];
- info->vers->next = 0;
- strcpy(info->vers->name, tt->name);
+ vers->next = (char *)info->vers - (char *)vers;
- info->old_vers = info->vers;
- info->vers = align_ptr((void *)(info->vers + 1) + strlen(tt->name) + 1);
+ vers->version[0] = tt->version[0];
+ vers->version[1] = tt->version[1];
+ vers->version[2] = tt->version[2];
+ memcpy(vers->name, tt->name, name_len);
}
static int __list_versions(struct dm_ioctl *param, size_t param_size, const char *name)
{
- size_t len, needed = 0;
+ size_t len;
struct dm_target_versions *vers;
struct vers_iter iter_info;
struct target_type *tt = NULL;
@@ -817,40 +805,30 @@ static int __list_versions(struct dm_ioctl *param, size_t param_size, const char
}
/*
- * Loop through all the devices working out how much
- * space we need.
- */
- if (!tt)
- dm_target_iterate(list_version_get_needed, &needed);
- else
- list_version_get_needed(tt, &needed);
-
- /*
* Grab our output buffer.
*/
vers = get_result_buffer(param, param_size, &len);
- if (len < needed) {
- param->flags |= DM_BUFFER_FULL_FLAG;
- goto out;
- }
- param->data_size = param->data_start + needed;
- iter_info.param_size = param_size;
iter_info.old_vers = NULL;
iter_info.vers = vers;
- iter_info.flags = 0;
- iter_info.end = (char *)vers + needed;
+ iter_info.end = (char *)vers + len;
/*
- * Now loop through filling out the names & versions.
+ * Loop through filling out the names & versions.
*/
if (!tt)
dm_target_iterate(list_version_get_info, &iter_info);
else
list_version_get_info(tt, &iter_info);
- param->flags |= iter_info.flags;
- out:
+ if (iter_info.vers) {
+ if (iter_info.old_vers)
+ iter_info.old_vers->next = 0;
+ param->data_size = param->data_start + ((char *)iter_info.vers - (char *)vers);
+ } else {
+ param->flags |= DM_BUFFER_FULL_FLAG;
+ }
+
if (tt)
dm_put_target_type(tt);
return 0;
@@ -1444,10 +1422,6 @@ static void retrieve_status(struct dm_table *table,
outptr += sizeof(struct dm_target_spec);
remaining = len - (outptr - outbuf);
- if (remaining <= 0) {
- param->flags |= DM_BUFFER_FULL_FLAG;
- break;
- }
/* Get the status/table string from the target driver */
if (ti->type->status) {
@@ -1998,33 +1972,32 @@ static int target_message(struct file *filp, struct dm_ioctl *param, size_t para
static ioctl_fn lookup_ioctl(unsigned int cmd, int *ioctl_flags)
{
static const struct {
- int cmd;
int flags;
ioctl_fn fn;
} _ioctls[] = {
- {DM_VERSION_CMD, 0, NULL}, /* version is dealt with elsewhere */
- {DM_REMOVE_ALL_CMD, IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, remove_all},
- {DM_LIST_DEVICES_CMD, 0, list_devices},
-
- {DM_DEV_CREATE_CMD, IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_create},
- {DM_DEV_REMOVE_CMD, IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_remove},
- {DM_DEV_RENAME_CMD, IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_rename},
- {DM_DEV_SUSPEND_CMD, IOCTL_FLAGS_NO_PARAMS, dev_suspend},
- {DM_DEV_STATUS_CMD, IOCTL_FLAGS_NO_PARAMS, dev_status},
- {DM_DEV_WAIT_CMD, 0, dev_wait},
-
- {DM_TABLE_LOAD_CMD, 0, table_load},
- {DM_TABLE_CLEAR_CMD, IOCTL_FLAGS_NO_PARAMS, table_clear},
- {DM_TABLE_DEPS_CMD, 0, table_deps},
- {DM_TABLE_STATUS_CMD, 0, table_status},
-
- {DM_LIST_VERSIONS_CMD, 0, list_versions},
-
- {DM_TARGET_MSG_CMD, 0, target_message},
- {DM_DEV_SET_GEOMETRY_CMD, 0, dev_set_geometry},
- {DM_DEV_ARM_POLL_CMD, IOCTL_FLAGS_NO_PARAMS, dev_arm_poll},
- {DM_GET_TARGET_VERSION_CMD, 0, get_target_version},
- {DM_MPATH_PROBE_PATHS_CMD, 0, NULL}, /* block device ioctl */
+ [DM_VERSION_CMD] = {0, NULL}, /* version is dealt with elsewhere */
+ [DM_REMOVE_ALL_CMD] = {IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, remove_all},
+ [DM_LIST_DEVICES_CMD] = {0, list_devices},
+
+ [DM_DEV_CREATE_CMD] = {IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_create},
+ [DM_DEV_REMOVE_CMD] = {IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_remove},
+ [DM_DEV_RENAME_CMD] = {IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_rename},
+ [DM_DEV_SUSPEND_CMD] = {IOCTL_FLAGS_NO_PARAMS, dev_suspend},
+ [DM_DEV_STATUS_CMD] = {IOCTL_FLAGS_NO_PARAMS, dev_status},
+ [DM_DEV_WAIT_CMD] = {0, dev_wait},
+
+ [DM_TABLE_LOAD_CMD] = {0, table_load},
+ [DM_TABLE_CLEAR_CMD] = {IOCTL_FLAGS_NO_PARAMS, table_clear},
+ [DM_TABLE_DEPS_CMD] = {0, table_deps},
+ [DM_TABLE_STATUS_CMD] = {0, table_status},
+
+ [DM_LIST_VERSIONS_CMD] = {0, list_versions},
+
+ [DM_TARGET_MSG_CMD] = {0, target_message},
+ [DM_DEV_SET_GEOMETRY_CMD] = {0, dev_set_geometry},
+ [DM_DEV_ARM_POLL_CMD] = {IOCTL_FLAGS_NO_PARAMS, dev_arm_poll},
+ [DM_GET_TARGET_VERSION_CMD] = {0, get_target_version},
+ [DM_MPATH_PROBE_PATHS_CMD] = {0, NULL}, /* block device ioctl */
};
if (unlikely(cmd >= ARRAY_SIZE(_ioctls)))
@@ -2273,12 +2246,9 @@ static long dm_compat_ctl_ioctl(struct file *file, uint command, ulong u)
static int dm_open(struct inode *inode, struct file *filp)
{
- int r;
struct dm_file *priv;
- r = nonseekable_open(inode, filp);
- if (unlikely(r))
- return r;
+ nonseekable_open(inode, filp);
priv = filp->private_data = kmalloc_obj(struct dm_file);
if (!priv)
diff --git a/drivers/md/dm-log-userspace-base.c b/drivers/md/dm-log-userspace-base.c
index 9d5918eb0a30..3ca35aa4e057 100644
--- a/drivers/md/dm-log-userspace-base.c
+++ b/drivers/md/dm-log-userspace-base.c
@@ -139,6 +139,7 @@ static int build_constructor_string(struct dm_target *ti,
str_size += strlen(argv[i]) + 1; /* +1 for space between args */
str_size += 20; /* Max number of chars in a printed u64 number */
+ str_size++; /* For NUL-terminator */
str = kzalloc(str_size, GFP_KERNEL);
if (!str) {
diff --git a/drivers/md/dm-pcache/cache.c b/drivers/md/dm-pcache/cache.c
index bb1ada31e483..b0b3e21677de 100644
--- a/drivers/md/dm-pcache/cache.c
+++ b/drivers/md/dm-pcache/cache.c
@@ -118,7 +118,14 @@ int cache_pos_decode(struct pcache_cache *cache,
if (!latest_addr)
return -EIO;
+ if (!cache_seg_id_valid(cache, latest.cache_seg_id))
+ return -EIO;
+
pos->cache_seg = &cache->segments[latest.cache_seg_id];
+
+ if (latest.seg_off >= pos->cache_seg->segment.data_size)
+ return -EIO;
+
pos->seg_off = latest.seg_off;
*seq = latest.header.seq;
*index = (latest_addr - pos_onmedia);
@@ -155,6 +162,7 @@ static int cache_init(struct dm_pcache *pcache)
cache->cache_dev = &pcache->cache_dev;
cache->n_segs = cache_dev->seg_num;
atomic_set(&cache->gc_errors, 0);
+ atomic_set(&cache->writeback_errors, 0);
spin_lock_init(&cache->seg_map_lock);
spin_lock_init(&cache->key_head_lock);
@@ -194,6 +202,7 @@ static int cache_tail_init(struct pcache_cache *cache)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
bool new_cache = !(cache->cache_info.flags & PCACHE_CACHE_FLAGS_INIT_DONE);
+ int ret;
if (new_cache) {
__set_bit(0, cache->seg_map);
@@ -210,6 +219,12 @@ static int cache_tail_init(struct pcache_cache *cache)
pcache_dev_err(pcache, "Corrupted key tail or dirty tail.\n");
return -EIO;
}
+
+ ret = cache_verify_dirty_tail(cache);
+ if (ret) {
+ pcache_dev_err(pcache, "dirty tail chain does not terminate (crafted cache image?)\n");
+ return ret;
+ }
}
return 0;
@@ -247,6 +262,13 @@ static int get_seg_id(struct pcache_cache *cache,
} else {
*seg_id = cache->cache_info.seg_id;
}
+
+ if (*seg_id >= cache_dev->seg_num) {
+ pcache_dev_err(pcache, "invalid segment id %u from cache device (seg_num %u)\n",
+ *seg_id, cache_dev->seg_num);
+ ret = -EIO;
+ goto err;
+ }
}
return 0;
err:
@@ -262,6 +284,13 @@ static int cache_segs_init(struct pcache_cache *cache)
int ret;
u32 i;
+ if (cache_info->n_segs > cache->cache_dev->seg_num) {
+ pcache_dev_err(CACHE_TO_PCACHE(cache),
+ "cache_info n_segs %u exceeds cache device segments %u\n",
+ cache_info->n_segs, cache->cache_dev->seg_num);
+ return -EIO;
+ }
+
for (i = 0; i < cache_info->n_segs; i++) {
ret = get_seg_id(cache, prev_cache_seg, new_cache, &seg_id);
if (ret)
diff --git a/drivers/md/dm-pcache/cache.h b/drivers/md/dm-pcache/cache.h
index 27613b56be54..021292c43677 100644
--- a/drivers/md/dm-pcache/cache.h
+++ b/drivers/md/dm-pcache/cache.h
@@ -180,6 +180,7 @@ struct pcache_cache {
u32 advance;
int ret;
} writeback_ctx;
+ atomic_t writeback_errors;
char gc_kset_onmedia_buf[PCACHE_KSET_ONMEDIA_SIZE_MAX];
struct delayed_work gc_work;
@@ -273,7 +274,7 @@ struct pcache_cache_subtree_walk_ctx {
struct list_head *submit_req_list;
/*
- * |--------| key_tmp
+ * |--------| key_tmp
* |====| key
*/
int (*before)(struct pcache_cache_key *key, struct pcache_cache_key *key_tmp,
@@ -281,7 +282,7 @@ struct pcache_cache_subtree_walk_ctx {
/*
* |----------| key_tmp
- * |=====| key
+ * |=====| key
*/
int (*after)(struct pcache_cache_key *key, struct pcache_cache_key *key_tmp,
struct pcache_cache_subtree_walk_ctx *ctx);
@@ -421,6 +422,20 @@ static inline bool cache_seg_is_ctrl_seg(u32 cache_seg_id)
}
/**
+ * cache_seg_id_valid - Validate a cache segment id read from the cache device.
+ * @cache: Pointer to the pcache_cache structure.
+ * @cache_seg_id: Segment id decoded from on-media metadata.
+ *
+ * On-media segment ids are only protected by a CRC, which an attacker who can
+ * format the cache device computes over their chosen value. Reject any id that
+ * would index cache->segments[] out of bounds before it is dereferenced.
+ */
+static inline bool cache_seg_id_valid(struct pcache_cache *cache, u32 cache_seg_id)
+{
+ return cache_seg_id < cache->cache_info.n_segs;
+}
+
+/**
* cache_key_cutfront - Cuts a specified length from the front of a cache key.
* @key: Pointer to pcache_cache_key structure.
* @cut_len: Length to cut from the front.
@@ -491,6 +506,27 @@ static inline u32 cache_key_data_crc(struct pcache_cache_key *key)
return crc32c(PCACHE_CRC_SEED, data, key->len);
}
+/**
+ * kset_onmedia_valid - Validate a kset header read from the cache device.
+ * @kset_onmedia: Pointer to the kset copied from on-media metadata.
+ *
+ * The magic and CRC are attacker-computable (fixed public seed). A non-last
+ * kset stores key_num keys inline, and cache_kset_crc() and the replay loop
+ * read struct_size(.., data, key_num) bytes from a buffer sized for
+ * PCACHE_KSET_KEYS_MAX keys, so key_num must be bounded before any such use.
+ */
+static inline bool kset_onmedia_valid(struct pcache_cache_kset_onmedia *kset_onmedia)
+{
+ if (kset_onmedia->magic != PCACHE_KSET_MAGIC)
+ return false;
+
+ if (!(kset_onmedia->flags & PCACHE_KSET_FLAGS_LAST) &&
+ kset_onmedia->key_num > PCACHE_KSET_KEYS_MAX)
+ return false;
+
+ return true;
+}
+
static inline u32 cache_kset_crc(struct pcache_cache_kset_onmedia *kset_onmedia)
{
u32 crc_size;
@@ -630,6 +666,8 @@ static inline int cache_decode_dirty_tail(struct pcache_cache *cache)
&cache->dirty_tail_index);
}
+int cache_verify_dirty_tail(struct pcache_cache *cache);
+
int pcache_cache_init(void);
void pcache_cache_exit(void);
#endif /* _PCACHE_CACHE_H */
diff --git a/drivers/md/dm-pcache/cache_dev.c b/drivers/md/dm-pcache/cache_dev.c
index ece689e6ce59..f0259353ee39 100644
--- a/drivers/md/dm-pcache/cache_dev.c
+++ b/drivers/md/dm-pcache/cache_dev.c
@@ -242,6 +242,8 @@ int cache_dev_start(struct dm_pcache *pcache)
struct pcache_cache_dev *cache_dev = &pcache->cache_dev;
struct pcache_sb sb;
bool format = false;
+ u32 seg_num;
+ u64 max_segs;
int ret;
mutex_init(&cache_dev->seg_lock);
@@ -269,7 +271,25 @@ int cache_dev_start(struct dm_pcache *pcache)
goto dax_release;
cache_dev->sb_flags = le32_to_cpu(sb.flags);
- ret = cache_dev_init(cache_dev, le32_to_cpu(sb.seg_num));
+
+ /*
+ * seg_num is read from the crc32c-only superblock, so whoever supplies
+ * the cache device controls it. It is the ceiling every later on-media
+ * segment id is validated against, so bound it against what the device
+ * physically holds before it is trusted, or a forged seg_num lets a
+ * segment id address past the DAX mapping.
+ */
+ seg_num = le32_to_cpu(sb.seg_num);
+ max_segs = (bdev_nr_bytes(cache_dev->dm_dev->bdev) - PCACHE_SEGMENTS_OFF) /
+ PCACHE_SEG_SIZE;
+ if (seg_num == 0 || seg_num > max_segs || seg_num > PCACHE_CACHE_SEGS_MAX) {
+ pcache_dev_err(pcache, "invalid seg_num %u from cache device (device holds %llu, max %u)\n",
+ seg_num, max_segs, (u32)PCACHE_CACHE_SEGS_MAX);
+ ret = -EIO;
+ goto dax_release;
+ }
+
+ ret = cache_dev_init(cache_dev, seg_num);
if (ret)
goto dax_release;
diff --git a/drivers/md/dm-pcache/cache_gc.c b/drivers/md/dm-pcache/cache_gc.c
index 94f8b276a021..d551848912b4 100644
--- a/drivers/md/dm-pcache/cache_gc.c
+++ b/drivers/md/dm-pcache/cache_gc.c
@@ -37,18 +37,18 @@ static bool need_gc(struct pcache_cache *cache, struct pcache_cache_pos *dirty_t
kset_onmedia = (struct pcache_cache_kset_onmedia *)cache->gc_kset_onmedia_buf;
- to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, PCACHE_SEG_SIZE - key_tail->seg_off);
+ to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, cache_seg_remain(key_tail));
ret = copy_mc_to_kernel(kset_onmedia, key_addr, to_copy);
if (ret) {
pcache_dev_err(pcache, "error to read kset: %d", ret);
return false;
}
- /* Check if kset_onmedia is corrupted */
- if (kset_onmedia->magic != PCACHE_KSET_MAGIC) {
- pcache_dev_debug(pcache, "gc error: magic is not as expected. key_tail: %u:%u magic: %llx, expected: %llx\n",
+ /* Reject a corrupted or out-of-bounds kset before reading its keys */
+ if (!kset_onmedia_valid(kset_onmedia)) {
+ pcache_dev_debug(pcache, "gc error: invalid kset. key_tail: %u:%u magic: %llx, key_num: %u\n",
key_tail->cache_seg->cache_seg_id, key_tail->seg_off,
- kset_onmedia->magic, PCACHE_KSET_MAGIC);
+ kset_onmedia->magic, kset_onmedia->key_num);
return false;
}
@@ -74,11 +74,17 @@ static bool need_gc(struct pcache_cache *cache, struct pcache_cache_pos *dirty_t
* @cache: Pointer to the pcache_cache structure.
* @kset_onmedia: Pointer to the kset_onmedia structure for the last kset.
*/
-static void last_kset_gc(struct pcache_cache *cache, struct pcache_cache_kset_onmedia *kset_onmedia)
+static int last_kset_gc(struct pcache_cache *cache, struct pcache_cache_kset_onmedia *kset_onmedia)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
struct pcache_cache_segment *cur_seg, *next_seg;
+ if (!cache_seg_id_valid(cache, kset_onmedia->next_cache_seg_id)) {
+ pcache_dev_err(pcache, "invalid next_cache_seg_id %u in gc (n_segs %u)\n",
+ kset_onmedia->next_cache_seg_id, cache->n_segs);
+ return -EIO;
+ }
+
cur_seg = cache->key_tail.cache_seg;
next_seg = &cache->segments[kset_onmedia->next_cache_seg_id];
@@ -94,6 +100,8 @@ static void last_kset_gc(struct pcache_cache *cache, struct pcache_cache_kset_on
spin_lock(&cache->seg_map_lock);
__clear_bit(cur_seg->cache_seg_id, cache->seg_map);
spin_unlock(&cache->seg_map_lock);
+
+ return 0;
}
void pcache_cache_gc_fn(struct work_struct *work)
@@ -130,10 +138,19 @@ void pcache_cache_gc_fn(struct work_struct *work)
if (dirty_tail.cache_seg == key_tail.cache_seg)
break;
- last_kset_gc(cache, kset_onmedia);
+ ret = last_kset_gc(cache, kset_onmedia);
+ if (ret) {
+ atomic_inc(&cache->gc_errors);
+ return;
+ }
continue;
}
+ if (get_kset_onmedia_size(kset_onmedia) > cache_seg_remain(&key_tail)) {
+ atomic_inc(&cache->gc_errors);
+ return;
+ }
+
for (i = 0; i < kset_onmedia->key_num; i++) {
struct pcache_cache_key key_tmp = { 0 };
diff --git a/drivers/md/dm-pcache/cache_key.c b/drivers/md/dm-pcache/cache_key.c
index e068e878231b..195157bef0f9 100644
--- a/drivers/md/dm-pcache/cache_key.c
+++ b/drivers/md/dm-pcache/cache_key.c
@@ -90,13 +90,36 @@ int cache_key_decode(struct pcache_cache *cache,
struct pcache_cache_key *key)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
+ u64 dev_bytes = (u64)cache->dev_size << SECTOR_SHIFT;
key->off = key_onmedia->off;
key->len = key_onmedia->len;
+ if (key_onmedia->len == 0 ||
+ key_onmedia->len > dev_bytes ||
+ key_onmedia->off > dev_bytes - key_onmedia->len) {
+ pcache_dev_err(pcache, "key off %llu + len %u exceeds device size\n",
+ key_onmedia->off, key_onmedia->len);
+ return -EIO;
+ }
+
+ if (!cache_seg_id_valid(cache, key_onmedia->cache_seg_id)) {
+ pcache_dev_err(pcache, "invalid cache_seg_id %u in cache key (n_segs %u)\n",
+ key_onmedia->cache_seg_id, cache->n_segs);
+ return -EIO;
+ }
+
key->cache_pos.cache_seg = &cache->segments[key_onmedia->cache_seg_id];
key->cache_pos.seg_off = key_onmedia->cache_seg_off;
+ if ((u64)key->cache_pos.seg_off + key->len >
+ key->cache_pos.cache_seg->segment.data_size) {
+ pcache_dev_err(pcache, "key seg_off %u + len %u exceeds segment data size %u\n",
+ key->cache_pos.seg_off, key->len,
+ key->cache_pos.cache_seg->segment.data_size);
+ return -EIO;
+ }
+
key->seg_gen = key_onmedia->seg_gen;
key->flags = key_onmedia->flags;
@@ -265,7 +288,7 @@ int cache_subtree_walk(struct pcache_cache_subtree_walk_ctx *ctx)
/*
* If key_tmp starts after the end of key, stop traversing.
- * |--------|
+ * |--------|
* |====|
*/
if (cache_key_lstart(key_tmp) >= cache_key_lend(key)) {
@@ -728,18 +751,17 @@ static int kset_replay(struct pcache_cache *cache, struct pcache_cache_kset_onme
goto err;
}
- __set_bit(key->cache_pos.cache_seg->cache_seg_id, cache->seg_map);
-
/* Check if the segment generation is valid for insertion. */
if (key->seg_gen < key->cache_pos.cache_seg->gen) {
cache_key_put(key);
- } else {
- cache_subtree = get_subtree(&cache->req_key_tree, key->off);
- spin_lock(&cache_subtree->tree_lock);
- cache_key_insert(&cache->req_key_tree, key, true);
- spin_unlock(&cache_subtree->tree_lock);
+ continue;
}
+ __set_bit(key->cache_pos.cache_seg->cache_seg_id, cache->seg_map);
+ cache_subtree = get_subtree(&cache->req_key_tree, key->off);
+ spin_lock(&cache_subtree->tree_lock);
+ cache_key_insert(&cache->req_key_tree, key, true);
+ spin_unlock(&cache_subtree->tree_lock);
cache_seg_get(key->cache_pos.cache_seg);
}
@@ -754,7 +776,7 @@ int cache_replay(struct pcache_cache *cache)
struct pcache_cache_pos pos_tail;
struct pcache_cache_pos *pos;
struct pcache_cache_kset_onmedia *kset_onmedia;
- u32 to_copy, count = 0;
+ u32 to_copy, count = 0, last_hops = 0;
int ret = 0;
kset_onmedia = kzalloc(PCACHE_KSET_ONMEDIA_SIZE_MAX, GFP_KERNEL);
@@ -771,14 +793,14 @@ int cache_replay(struct pcache_cache *cache)
__set_bit(pos->cache_seg->cache_seg_id, cache->seg_map);
while (true) {
- to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, PCACHE_SEG_SIZE - pos->seg_off);
+ to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, cache_seg_remain(pos));
ret = copy_mc_to_kernel(kset_onmedia, cache_pos_addr(pos), to_copy);
if (ret) {
ret = -EIO;
goto out;
}
- if (kset_onmedia->magic != PCACHE_KSET_MAGIC ||
+ if (!kset_onmedia_valid(kset_onmedia) ||
kset_onmedia->crc != cache_kset_crc(kset_onmedia)) {
break;
}
@@ -789,6 +811,16 @@ int cache_replay(struct pcache_cache *cache)
pcache_dev_debug(pcache, "last kset replay, next: %u\n", kset_onmedia->next_cache_seg_id);
+ if (!cache_seg_id_valid(cache, kset_onmedia->next_cache_seg_id)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ if (++last_hops > cache->n_segs) {
+ ret = -EIO;
+ goto out;
+ }
+
next_seg = &cache->segments[kset_onmedia->next_cache_seg_id];
pos->cache_seg = next_seg;
@@ -799,6 +831,11 @@ int cache_replay(struct pcache_cache *cache)
}
/* Replay the kset and check for errors. */
+ if (get_kset_onmedia_size(kset_onmedia) > cache_seg_remain(pos)) {
+ ret = -EIO;
+ goto out;
+ }
+
ret = kset_replay(cache, kset_onmedia);
if (ret)
goto out;
@@ -820,6 +857,75 @@ out:
return ret;
}
+/*
+ * cache_verify_dirty_tail - reject a persisted dirty_tail whose last-kset
+ * chain does not terminate.
+ *
+ * dirty_tail is decoded independently of the key_tail chain cache_replay()
+ * walks, so replay's hop cap does not cover it. A crafted chain that loops
+ * back on itself makes the writeback worker re-arm forever; walk it once here
+ * with the same cap and fail the load if it does not end within n_segs hops.
+ */
+int cache_verify_dirty_tail(struct pcache_cache *cache)
+{
+ struct pcache_cache_pos pos;
+ struct pcache_cache_kset_onmedia *kset_onmedia;
+ u32 to_copy, last_hops = 0, count = 0;
+ int ret = 0;
+
+ kset_onmedia = kzalloc(PCACHE_KSET_ONMEDIA_SIZE_MAX, GFP_KERNEL);
+ if (!kset_onmedia)
+ return -ENOMEM;
+
+ cache_pos_copy(&pos, &cache->dirty_tail);
+
+ while (true) {
+ to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, cache_seg_remain(&pos));
+ ret = copy_mc_to_kernel(kset_onmedia, cache_pos_addr(&pos), to_copy);
+ if (ret) {
+ ret = -EIO;
+ goto out;
+ }
+
+ /* A missing, short or corrupt kset is the normal end of the chain. */
+ if (!kset_onmedia_valid(kset_onmedia) ||
+ kset_onmedia->crc != cache_kset_crc(kset_onmedia)) {
+ ret = 0;
+ goto out;
+ }
+
+ if (kset_onmedia->flags & PCACHE_KSET_FLAGS_LAST) {
+ if (!cache_seg_id_valid(cache, kset_onmedia->next_cache_seg_id)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ if (++last_hops > cache->n_segs) {
+ ret = -EIO;
+ goto out;
+ }
+
+ pos.cache_seg = &cache->segments[kset_onmedia->next_cache_seg_id];
+ pos.seg_off = 0;
+ continue;
+ }
+
+ if (get_kset_onmedia_size(kset_onmedia) > cache_seg_remain(&pos)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ cache_pos_advance(&pos, get_kset_onmedia_size(kset_onmedia));
+ if (++count > 512) {
+ cond_resched();
+ count = 0;
+ }
+ }
+out:
+ kfree(kset_onmedia);
+ return ret;
+}
+
int cache_tree_init(struct pcache_cache *cache, struct pcache_cache_tree *cache_tree, u32 n_subtrees)
{
int ret;
diff --git a/drivers/md/dm-pcache/cache_req.c b/drivers/md/dm-pcache/cache_req.c
index 7854a30e07b7..b2cb3c7615d4 100644
--- a/drivers/md/dm-pcache/cache_req.c
+++ b/drivers/md/dm-pcache/cache_req.c
@@ -39,13 +39,11 @@ static int cache_data_alloc(struct pcache_cache *cache, struct pcache_cache_key
struct pcache_cache_pos *head_pos;
struct pcache_cache_segment *cache_seg;
u32 seg_remain;
- u32 allocated = 0, to_alloc;
int ret = 0;
preempt_disable();
data_head = get_data_head(cache);
again:
- to_alloc = key->len - allocated;
if (!data_head->head_pos.cache_seg) {
seg_remain = 0;
} else {
@@ -57,10 +55,9 @@ again:
seg_remain = cache_seg_remain(head_pos);
}
- if (seg_remain > to_alloc) {
+ if (seg_remain > key->len) {
/* If remaining space in segment is sufficient for the cache key, allocate it. */
- cache_pos_advance(head_pos, to_alloc);
- allocated += to_alloc;
+ cache_pos_advance(head_pos, key->len);
cache_seg_get(cache_seg);
} else if (seg_remain) {
/* If remaining space is not enough, allocate the remaining space and adjust the cache key length. */
@@ -317,7 +314,7 @@ static struct pcache_backing_dev_req *get_pre_alloc_req(struct pcache_cache_subt
*
* The scenario handled here:
*
- * |--------| key_tmp (existing cached range)
+ * |--------| key_tmp (existing cached range)
* |====| key (requested range, preceding key_tmp)
*
* Since `key` is before `key_tmp`, it signifies that the requested data
@@ -352,7 +349,7 @@ static int read_before(struct pcache_cache_key *key, struct pcache_cache_key *ke
* During cache_subtree_walk, this function manages a scenario where part of the
* requested data range overlaps with an existing cache node (`key_tmp`).
*
- * |----------------| key_tmp (existing cached range)
+ * |----------------| key_tmp (existing cached range)
* |===========| key (requested range, overlapping the tail of key_tmp)
*/
static int read_overlap_tail(struct pcache_cache_key *key, struct pcache_cache_key *key_tmp,
@@ -474,8 +471,8 @@ static int read_overlap_contain(struct pcache_cache_key *key, struct pcache_cach
}
/*
- * |-----------| key_tmp (existing cached range)
- * |====| key (requested range, fully within key_tmp)
+ * |-----------| key_tmp (existing cached range)
+ * |====| key (requested range, fully within key_tmp)
*
* If `key_tmp` contains valid cached data, this function copies the relevant
* portion to the request's bio. Otherwise, it sends a backing request to
@@ -524,8 +521,8 @@ static int read_overlap_contained(struct pcache_cache_key *key, struct pcache_ca
}
/*
- * |--------| key_tmp (existing cached range)
- * |==========| key (requested range, overlapping the head of key_tmp)
+ * |--------| key_tmp (existing cached range)
+ * |==========| key (requested range, overlapping the head of key_tmp)
*/
static int read_overlap_head(struct pcache_cache_key *key, struct pcache_cache_key *key_tmp,
struct pcache_cache_subtree_walk_ctx *ctx)
diff --git a/drivers/md/dm-pcache/cache_segment.c b/drivers/md/dm-pcache/cache_segment.c
index 9d92e2b067ed..c698ebbc626d 100644
--- a/drivers/md/dm-pcache/cache_segment.c
+++ b/drivers/md/dm-pcache/cache_segment.c
@@ -243,8 +243,16 @@ struct pcache_cache_segment *get_cache_segment(struct pcache_cache *cache)
spin_lock(&cache->seg_map_lock);
again:
- seg_id = find_next_zero_bit(cache->seg_map, cache->n_segs, cache->last_cache_seg);
- if (seg_id == cache->n_segs) {
+ /*
+ * Only allocate initialized segments. cache_segs_init() initializes
+ * cache_info.n_segs of the cache->n_segs device segments; a forged
+ * smaller cache_info.n_segs leaves the rest as zeroed structs whose data
+ * pointer is NULL. Bounding the search to cache_info.n_segs keeps such a
+ * segment from reaching cache_kset_close(), which writes through it.
+ */
+ seg_id = find_next_zero_bit(cache->seg_map, cache->cache_info.n_segs,
+ cache->last_cache_seg);
+ if (seg_id == cache->cache_info.n_segs) {
/* reset the hint of ->last_cache_seg and retry */
if (cache->last_cache_seg) {
cache->last_cache_seg = 0;
diff --git a/drivers/md/dm-pcache/cache_writeback.c b/drivers/md/dm-pcache/cache_writeback.c
index 87a82b3fe836..c8a4c8110a58 100644
--- a/drivers/md/dm-pcache/cache_writeback.c
+++ b/drivers/md/dm-pcache/cache_writeback.c
@@ -48,18 +48,18 @@ static inline bool is_cache_clean(struct pcache_cache *cache, struct pcache_cach
addr = cache_pos_addr(dirty_tail);
kset_onmedia = (struct pcache_cache_kset_onmedia *)cache->wb_kset_onmedia_buf;
- to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, PCACHE_SEG_SIZE - dirty_tail->seg_off);
+ to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, cache_seg_remain(dirty_tail));
ret = copy_mc_to_kernel(kset_onmedia, addr, to_copy);
if (ret) {
pcache_dev_err(pcache, "error to read kset: %d", ret);
return true;
}
- /* Check if the magic number matches the expected value */
- if (kset_onmedia->magic != PCACHE_KSET_MAGIC) {
- pcache_dev_debug(pcache, "dirty_tail: %u:%u magic: %llx, not expected: %llx\n",
+ /* Reject a corrupted or out-of-bounds kset before reading its keys */
+ if (!kset_onmedia_valid(kset_onmedia)) {
+ pcache_dev_debug(pcache, "dirty_tail: %u:%u invalid kset magic: %llx, key_num: %u\n",
dirty_tail->cache_seg->cache_seg_id, dirty_tail->seg_off,
- kset_onmedia->magic, PCACHE_KSET_MAGIC);
+ kset_onmedia->magic, kset_onmedia->key_num);
return true;
}
@@ -196,12 +196,18 @@ clear_tree:
return ret;
}
-static void last_kset_writeback(struct pcache_cache *cache,
+static int last_kset_writeback(struct pcache_cache *cache,
struct pcache_cache_kset_onmedia *last_kset_onmedia)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
struct pcache_cache_segment *next_seg;
+ if (!cache_seg_id_valid(cache, last_kset_onmedia->next_cache_seg_id)) {
+ pcache_dev_err(pcache, "invalid next_cache_seg_id %u in writeback (n_segs %u)\n",
+ last_kset_onmedia->next_cache_seg_id, cache->n_segs);
+ return -EIO;
+ }
+
pcache_dev_debug(pcache, "last kset, next: %u\n", last_kset_onmedia->next_cache_seg_id);
next_seg = &cache->segments[last_kset_onmedia->next_cache_seg_id];
@@ -211,6 +217,8 @@ static void last_kset_writeback(struct pcache_cache *cache,
cache->dirty_tail.seg_off = 0;
cache_encode_dirty_tail(cache);
mutex_unlock(&cache->dirty_tail_lock);
+
+ return 0;
}
void cache_writeback_fn(struct work_struct *work)
@@ -229,6 +237,9 @@ void cache_writeback_fn(struct work_struct *work)
if (pcache_is_stopping(pcache))
goto unlock;
+ if (atomic_read(&cache->writeback_errors))
+ goto unlock;
+
kset_onmedia = (struct pcache_cache_kset_onmedia *)cache->wb_kset_onmedia_buf;
mutex_lock(&cache->dirty_tail_lock);
@@ -241,15 +252,24 @@ void cache_writeback_fn(struct work_struct *work)
}
if (kset_onmedia->flags & PCACHE_KSET_FLAGS_LAST) {
- last_kset_writeback(cache, kset_onmedia);
+ ret = last_kset_writeback(cache, kset_onmedia);
+ if (ret) {
+ atomic_inc(&cache->writeback_errors);
+ goto unlock;
+ }
delay = 0;
goto queue_work;
}
+ if (get_kset_onmedia_size(kset_onmedia) > cache_seg_remain(&dirty_tail)) {
+ atomic_inc(&cache->writeback_errors);
+ goto unlock;
+ }
+
ret = cache_kset_insert_tree(cache, kset_onmedia);
if (ret) {
- delay = PCACHE_CACHE_WRITEBACK_INTERVAL;
- goto queue_work;
+ atomic_inc(&cache->writeback_errors);
+ goto unlock;
}
cache_wb_tree_writeback(cache, get_kset_onmedia_size(kset_onmedia));
diff --git a/drivers/md/dm-pcache/dm_pcache.c b/drivers/md/dm-pcache/dm_pcache.c
index d5cfd162c063..645fc27d82ba 100644
--- a/drivers/md/dm-pcache/dm_pcache.c
+++ b/drivers/md/dm-pcache/dm_pcache.c
@@ -439,13 +439,13 @@ static int dm_pcache_message(struct dm_target *ti, unsigned int argc,
char **argv, char *result, unsigned int maxlen)
{
struct dm_pcache *pcache = ti->private;
- unsigned long val;
+ u8 val;
if (argc != 2)
goto err;
if (!strcasecmp(argv[0], "gc_percent")) {
- if (kstrtoul(argv[1], 10, &val))
+ if (kstrtou8(argv[1], 10, &val))
goto err;
return pcache_cache_set_gc_percent(&pcache->cache, val);
diff --git a/drivers/md/dm-snap.c b/drivers/md/dm-snap.c
index 1489fda9d24a..c8a9ca1eeae7 100644
--- a/drivers/md/dm-snap.c
+++ b/drivers/md/dm-snap.c
@@ -911,9 +911,7 @@ static int init_hash_tables(struct dm_snapshot *s)
static void merge_shutdown(struct dm_snapshot *s)
{
- clear_bit_unlock(RUNNING_MERGE, &s->state_bits);
- smp_mb__after_atomic();
- wake_up_bit(&s->state_bits, RUNNING_MERGE);
+ clear_and_wake_up_bit(RUNNING_MERGE, &s->state_bits);
}
static struct bio *__release_queued_bios_after_merge(struct dm_snapshot *s)
diff --git a/drivers/md/dm-switch.c b/drivers/md/dm-switch.c
index 5952f02de1e6..e5b507b4fa7b 100644
--- a/drivers/md/dm-switch.c
+++ b/drivers/md/dm-switch.c
@@ -184,7 +184,7 @@ static void switch_region_table_write(struct switch_ctx *sctx, unsigned long reg
pte = sctx->region_table[region_index];
pte &= ~((((region_table_slot_t)1 << sctx->region_table_entry_bits) - 1) << bit);
pte |= (region_table_slot_t)value << bit;
- sctx->region_table[region_index] = pte;
+ WRITE_ONCE(sctx->region_table[region_index], pte);
}
/*
diff --git a/drivers/md/dm-table.c b/drivers/md/dm-table.c
index dc2eff6b739d..a483f49dd4ce 100644
--- a/drivers/md/dm-table.c
+++ b/drivers/md/dm-table.c
@@ -2035,7 +2035,7 @@ int dm_table_set_restrictions(struct dm_table *t, struct request_queue *q,
limits->features &= ~BLK_FEAT_NOWAIT;
/*
- * The current polling impementation does not support request based
+ * The current polling implementation does not support request based
* stacking.
*/
if (!__table_type_bio_based(t->type))
diff --git a/drivers/md/dm-vdo/repair.c b/drivers/md/dm-vdo/repair.c
index bfed62260280..e5263a5716f6 100644
--- a/drivers/md/dm-vdo/repair.c
+++ b/drivers/md/dm-vdo/repair.c
@@ -1696,6 +1696,7 @@ void vdo_repair(struct vdo_completion *parent)
struct vdo *vdo = parent->vdo;
struct recovery_journal *journal = vdo->recovery_journal;
physical_block_number_t pbn = journal->origin;
+ block_count_t i;
block_count_t remaining = journal->size;
block_count_t vio_count = DIV_ROUND_UP(remaining, MAX_BLOCKS_PER_VIO);
page_count_t page_count = min_t(page_count_t,
@@ -1749,9 +1750,8 @@ void vdo_repair(struct vdo_completion *parent)
remaining -= blocks;
}
- for (vio_count = 0; vio_count < repair->vio_count;
- vio_count++, pbn += MAX_BLOCKS_PER_VIO) {
- vdo_submit_metadata_vio(&repair->vios[vio_count], pbn, read_journal_endio,
+ for (i = 0; i < vio_count; i++, pbn += MAX_BLOCKS_PER_VIO) {
+ vdo_submit_metadata_vio(&repair->vios[i], pbn, read_journal_endio,
handle_journal_load_error, REQ_OP_READ);
}
}
diff --git a/drivers/md/dm-verity-target.c b/drivers/md/dm-verity-target.c
index 1b0763091254..241d3f3747e7 100644
--- a/drivers/md/dm-verity-target.c
+++ b/drivers/md/dm-verity-target.c
@@ -316,9 +316,7 @@ static int verity_verify_level(struct dm_verity *v, struct dm_verity_io *io,
else if (verity_handle_err(v,
DM_VERITY_BLOCK_TYPE_METADATA,
hash_block)) {
- struct bio *bio;
io->had_mismatch = true;
- bio = dm_bio_from_per_bio_data(io, v->ti->per_io_data_size);
dm_audit_log_bio(DM_MSG_PREFIX, "verify-metadata", bio,
block, 0);
r = -EIO;
diff --git a/drivers/md/dm-zoned-metadata.c b/drivers/md/dm-zoned-metadata.c
index f4f81c79a080..094b63215882 100644
--- a/drivers/md/dm-zoned-metadata.c
+++ b/drivers/md/dm-zoned-metadata.c
@@ -519,9 +519,7 @@ static void dmz_mblock_bio_end_io(struct bio *bio)
else
flag = DMZ_META_READING;
- clear_bit_unlock(flag, &mblk->state);
- smp_mb__after_atomic();
- wake_up_bit(&mblk->state, flag);
+ clear_and_wake_up_bit(flag, &mblk->state);
bio_put(bio);
}
@@ -1910,9 +1908,7 @@ void dmz_unlock_zone_reclaim(struct dm_zone *zone)
WARN_ON(dmz_is_active(zone));
WARN_ON(!dmz_in_reclaim(zone));
- clear_bit_unlock(DMZ_RECLAIM, &zone->flags);
- smp_mb__after_atomic();
- wake_up_bit(&zone->flags, DMZ_RECLAIM);
+ clear_and_wake_up_bit(DMZ_RECLAIM, &zone->flags);
}
/*
diff --git a/drivers/md/dm-zoned-reclaim.c b/drivers/md/dm-zoned-reclaim.c
index ad9c7bc21d54..c041413c729e 100644
--- a/drivers/md/dm-zoned-reclaim.c
+++ b/drivers/md/dm-zoned-reclaim.c
@@ -106,9 +106,7 @@ static void dmz_reclaim_kcopy_end(int read_err, unsigned long write_err,
else
zrc->kc_err = 0;
- clear_bit_unlock(DMZ_RECLAIM_KCOPY, &zrc->flags);
- smp_mb__after_atomic();
- wake_up_bit(&zrc->flags, DMZ_RECLAIM_KCOPY);
+ clear_and_wake_up_bit(DMZ_RECLAIM_KCOPY, &zrc->flags);
}
/*