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.c18
-rw-r--r--drivers/md/dm-pcache/cache.h21
-rw-r--r--drivers/md/dm-pcache/cache_dev.c22
-rw-r--r--drivers/md/dm-pcache/cache_gc.c10
-rw-r--r--drivers/md/dm-pcache/cache_key.c32
-rw-r--r--drivers/md/dm-pcache/cache_segment.c12
-rw-r--r--drivers/md/dm-pcache/cache_writeback.c10
-rw-r--r--drivers/md/dm-pcache/dm_pcache.c4
8 files changed, 104 insertions, 25 deletions
diff --git a/drivers/md/dm-pcache/cache.c b/drivers/md/dm-pcache/cache.c
index 534bf07b794f..887157fde68d 100644
--- a/drivers/md/dm-pcache/cache.c
+++ b/drivers/md/dm-pcache/cache.c
@@ -119,6 +119,10 @@ int cache_pos_decode(struct pcache_cache *cache,
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);
@@ -246,6 +250,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:
@@ -261,6 +272,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..afc112b79496 100644
--- a/drivers/md/dm-pcache/cache.h
+++ b/drivers/md/dm-pcache/cache.h
@@ -491,6 +491,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;
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..3088cfef951b 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;
}
diff --git a/drivers/md/dm-pcache/cache_key.c b/drivers/md/dm-pcache/cache_key.c
index 2b77e121f89b..a3bb1f95afb9 100644
--- a/drivers/md/dm-pcache/cache_key.c
+++ b/drivers/md/dm-pcache/cache_key.c
@@ -97,6 +97,14 @@ int cache_key_decode(struct pcache_cache *cache,
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;
@@ -728,18 +736,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 +761,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 +778,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 +796,11 @@ int cache_replay(struct pcache_cache *cache)
pcache_dev_debug(pcache, "last kset replay, next: %u\n", kset_onmedia->next_cache_seg_id);
+ 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;
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..3df8687e2382 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;
}
diff --git a/drivers/md/dm-pcache/dm_pcache.c b/drivers/md/dm-pcache/dm_pcache.c
index 87bcc1c515a6..f6da50b57c67 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);