summaryrefslogtreecommitdiff
path: root/drivers/md/dm-pcache
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/md/dm-pcache')
-rw-r--r--drivers/md/dm-pcache/cache.c29
-rw-r--r--drivers/md/dm-pcache/cache.h43
-rw-r--r--drivers/md/dm-pcache/cache_dev.c22
-rw-r--r--drivers/md/dm-pcache/cache_gc.c36
-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
9 files changed, 282 insertions, 49 deletions
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..e49cdfd16aaf 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);
@@ -340,7 +341,6 @@ void cache_seg_set_next_seg(struct pcache_cache_segment *cache_seg, u32 seg_id);
/* cache request*/
int pcache_cache_flush(struct pcache_cache *cache);
-void miss_read_end_work_fn(struct work_struct *work);
int pcache_cache_handle_req(struct pcache_cache *cache, struct pcache_request *pcache_req);
/* gc */
@@ -421,6 +421,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 +505,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 +665,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..9cebef16d75e 100644
--- a/drivers/md/dm-pcache/cache_gc.c
+++ b/drivers/md/dm-pcache/cache_gc.c
@@ -6,14 +6,13 @@
/**
* cache_key_gc - Releases the reference of a cache key segment.
- * @cache: Pointer to the pcache_cache structure.
* @key: Pointer to the cache key to be garbage collected.
*
* This function decrements the reference count of the cache segment
* associated with the given key. If the reference count drops to zero,
* the segment may be invalidated and reused.
*/
-static void cache_key_gc(struct pcache_cache *cache, struct pcache_cache_key *key)
+static void cache_key_gc(struct pcache_cache_key *key)
{
cache_seg_put(key->cache_pos.cache_seg);
}
@@ -37,18 +36,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 +73,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 +99,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 +137,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 };
@@ -152,7 +168,7 @@ void pcache_cache_gc_fn(struct work_struct *work)
return;
}
- cache_key_gc(cache, key);
+ cache_key_gc(key);
}
pcache_dev_debug(pcache, "gc advance: %u:%u %u\n",
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);