Re: [PATCH 3/3] dm-pcache: validate kset key_num and intra-segment bounds

Zheng Gu <[email protected]>
Newsgroups dev.linux.lists.dm-devel,org.kernel.vger.linux-kernel
Message-ID <CADSj-VqXChj64SBtN2--tx36dYX1QsPoUOBzZTE3kJuOeD2o4Q@mail.gmail.com>
On Fri, Jun 19, 2026 at 7:44 PM Bryam Vargas via B4 Relay
<[email protected]> wrote:
>
> From: Bryam Vargas <[email protected]>
>
> Two more fields decoded from the cache device go unbounded. The kset
> key_num drives cache_kset_crc() and the replay loop in cache_replay(),
> the writeback worker and the GC worker, but only the magic and a
> fixed-seed CRC are checked first, so a non-last kset whose key_num exceeds
> the PCACHE_KSET_KEYS_MAX buffer reads past its end before the CRC compare.
> A key's intra-segment offset and length in cache_key_decode() are taken
> verbatim, so a key running past its segment is replayed into the cache
> tree and the data CRC check and every later read hit then copy adjacent
> persistent memory into the caller's bio -- an out-of-bounds read that
> leaks to user space. Both fields are controlled by whoever supplies the
> cache device (CAP_SYS_ADMIN); the CRC seed is public.
>
> Add kset_onmedia_valid() to bound key_num before any kset read, and
> reject a key whose offset plus length, computed in 64 bits, exceeds the
> segment data_size. Valid metadata is unaffected.
>
> Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper")
> Cc: [email protected]
> Signed-off-by: Bryam Vargas <[email protected]>
> ---
>  drivers/md/dm-pcache/cache.h           | 21 +++++++++++++++++++++
>  drivers/md/dm-pcache/cache_gc.c        |  8 ++++----
>  drivers/md/dm-pcache/cache_key.c       | 10 +++++++++-
>  drivers/md/dm-pcache/cache_writeback.c |  8 ++++----
>  4 files changed, 38 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/md/dm-pcache/cache.h b/drivers/md/dm-pcache/cache.h
> index 653ceea43131..d470e457b23f 100644
> --- a/drivers/md/dm-pcache/cache.h
> +++ b/drivers/md/dm-pcache/cache.h
> @@ -505,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;
> diff --git a/drivers/md/dm-pcache/cache_gc.c b/drivers/md/dm-pcache/cache_gc.c
> index 02fa0ce03134..1ed513745023 100644
> --- a/drivers/md/dm-pcache/cache_gc.c
> +++ b/drivers/md/dm-pcache/cache_gc.c
> @@ -44,11 +44,11 @@ static bool need_gc(struct pcache_cache *cache, struct pcache_cache_pos *dirty_t
>                 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 8eec5238c5da..f4459b2e1b3b 100644
> --- a/drivers/md/dm-pcache/cache_key.c
> +++ b/drivers/md/dm-pcache/cache_key.c
> @@ -103,6 +103,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;
>
> @@ -784,7 +792,7 @@ int cache_replay(struct pcache_cache *cache)
>                         goto out;
>                 }
>
> -               if (kset_onmedia->magic != PCACHE_KSET_MAGIC ||
> +               if (!kset_onmedia_valid(kset_onmedia) ||
>                                 kset_onmedia->crc != cache_kset_crc(kset_onmedia)) {
>                         break;
>                 }
> diff --git a/drivers/md/dm-pcache/cache_writeback.c b/drivers/md/dm-pcache/cache_writeback.c
> index a104e6ee8aa8..f96657a1dc1a 100644
> --- a/drivers/md/dm-pcache/cache_writeback.c
> +++ b/drivers/md/dm-pcache/cache_writeback.c
> @@ -55,11 +55,11 @@ static inline bool is_cache_clean(struct pcache_cache *cache, struct pcache_cach
>                 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;
>         }
>
>
> --
> 2.43.0
>
>

Reviewed-by:  Zheng Gu <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.