mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 20:54:03 +02:00
dm-pcache: validate the persisted dirty_tail chain at load
The writeback worker follows the persisted dirty_tail chain, which is
decoded from the cache device independently of the key_tail chain that
cache_replay() walks and bounds. A crafted image, whose on-media fields are
authenticated only by a crc32c with a fixed seed, can aim dirty_tail at a
chain of last ksets that never terminates, so cache_writeback_fn() re-arms
itself with no delay forever.
Walk the dirty_tail chain once at load with the same hop cap cache_replay()
uses and fail the table load with -EIO if it does not reach an end within
n_segs hops.
Fixes: 1d57628ff9 ("dm-pcache: add persistent cache target in device-mapper")
Cc: stable@vger.kernel.org
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
This commit is contained in:
parent
62d92e45ab
commit
58d620ee9e
|
|
@ -202,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);
|
||||
|
|
@ -218,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;
|
||||
|
|
|
|||
|
|
@ -666,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 */
|
||||
|
|
|
|||
|
|
@ -858,6 +858,75 @@ int cache_replay(struct pcache_cache *cache)
|
|||
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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user